@qualflare/cypress 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/index.cjs +38 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.cjs +18 -11
- package/dist/plugin/index.cjs.map +1 -1
- package/dist/plugin/index.d.cts +10 -9
- package/dist/plugin/index.d.ts +10 -9
- package/dist/plugin/index.js +18 -10
- package/dist/plugin/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,9 @@ exercised in CI against a real `cypress run`; majors newer than that are unteste
|
|
|
26
26
|
refused — please [open an issue](https://github.com/Qualflare/qualflare-cypress/issues) if
|
|
27
27
|
one misbehaves.
|
|
28
28
|
|
|
29
|
+
(Contributors: the repo itself now develops against Cypress 15, which requires Node `>=20.1`.
|
|
30
|
+
The `>=18` floor above is the *consumer* requirement and still holds for Cypress 12–14.)
|
|
31
|
+
|
|
29
32
|
## Quickstart
|
|
30
33
|
|
|
31
34
|
```ts
|
|
@@ -67,9 +70,8 @@ qf my-project collect ./qualflare-results
|
|
|
67
70
|
```
|
|
68
71
|
|
|
69
72
|
That's it — suite/test results, retries, and automatic-on-failure screenshots show up as one Launch
|
|
70
|
-
once `qualflare-cli collect` runs. [`examples/basic/`](./examples/basic) has a runnable project
|
|
71
|
-
|
|
72
|
-
updated for the `outputDir` model yet — follow the steps above instead, not that example's README.
|
|
73
|
+
once `qualflare-cli collect` runs. [`examples/basic/`](./examples/basic) has a runnable project
|
|
74
|
+
demonstrating the same flow end to end, including the `qualflare.*` metadata API.
|
|
73
75
|
|
|
74
76
|
## Enriching your tests
|
|
75
77
|
|
|
@@ -101,12 +103,21 @@ Every option can be set either as a plugin option (`qualflareCypress(on, config,
|
|
|
101
103
|
`QUALFLARE_*` environment variable. Full table, precedence rules, and auto-detection behavior (git
|
|
102
104
|
branch/commit, CI provider/build/PR, browser/OS) in [`docs/CONFIGURATION.md`](./docs/CONFIGURATION.md).
|
|
103
105
|
|
|
106
|
+
One option is worth calling out because it fails late: `environment` is matched against the
|
|
107
|
+
environment's **uid (slug)**, not its display name, so **Staging** in the UI is `staging` here. A
|
|
108
|
+
wrong value cannot fail at run time — this package makes no network calls — so the run succeeds and
|
|
109
|
+
`collect` 404s afterwards. See
|
|
110
|
+
[the note in the configuration docs](./docs/CONFIGURATION.md#environment-is-matched-by-uid-not-display-name).
|
|
111
|
+
|
|
104
112
|
## Known limitations
|
|
105
113
|
|
|
106
114
|
- **Sharded CI runs merge automatically, but only at collect time** — point every shard's
|
|
107
115
|
`cypress run` at the same shared `outputDir`; `qualflare-cli collect` merges every report file it
|
|
108
116
|
finds there into one Launch, no extra flag needed (see
|
|
109
117
|
[`docs/LIMITATIONS.md`](./docs/LIMITATIONS.md)).
|
|
118
|
+
- **A stale `outputDir` is refused, not merged** — each report carries a `runId`, and `qf collect`
|
|
119
|
+
errors rather than merging files from two different runs. Needs `@qualflare/cli` v0.1.19+; older
|
|
120
|
+
CLIs merge as before.
|
|
110
121
|
- **Command-log step nesting is two levels only** (Cypress's own API limit) — `qualflare.step()`
|
|
111
122
|
supports arbitrary nesting depth.
|
|
112
123
|
|
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,8 @@ var MAX_LABELS_PER_CASE = 100;
|
|
|
45
45
|
var MAX_LINKS_PER_CASE = 20;
|
|
46
46
|
var MAX_TAGS_PER_CASE = 64;
|
|
47
47
|
var MAX_TAG_LENGTH = 255;
|
|
48
|
+
var MAX_ATTEMPTS_PER_CASE = 50;
|
|
49
|
+
var MAX_ATTEMPT_MESSAGE_RUNES = 8192;
|
|
48
50
|
var MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
49
51
|
var MAX_STEPS_PER_TEST_ATTEMPT = 300;
|
|
50
52
|
|
|
@@ -298,6 +300,18 @@ function registerCommandLogListener(buffer) {
|
|
|
298
300
|
});
|
|
299
301
|
}
|
|
300
302
|
|
|
303
|
+
// src/shared/text.ts
|
|
304
|
+
function truncateRunes(value, maxRunes) {
|
|
305
|
+
if (value.length <= maxRunes) {
|
|
306
|
+
return value;
|
|
307
|
+
}
|
|
308
|
+
const runes = Array.from(value);
|
|
309
|
+
if (runes.length <= maxRunes) {
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
return runes.slice(0, maxRunes).join("");
|
|
313
|
+
}
|
|
314
|
+
|
|
301
315
|
// src/browser/case-builder.ts
|
|
302
316
|
function combineSteps(autoSteps, manualSteps) {
|
|
303
317
|
const auto = autoSteps ?? [];
|
|
@@ -314,6 +328,26 @@ function combineSteps(autoSteps, manualSteps) {
|
|
|
314
328
|
});
|
|
315
329
|
return [...auto, ...offsetManual];
|
|
316
330
|
}
|
|
331
|
+
function buildAttempts(attempts) {
|
|
332
|
+
if (attempts.length < 2) {
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
let kept = attempts;
|
|
336
|
+
if (attempts.length > MAX_ATTEMPTS_PER_CASE) {
|
|
337
|
+
kept = [...attempts.slice(0, MAX_ATTEMPTS_PER_CASE - 1), attempts[attempts.length - 1]];
|
|
338
|
+
}
|
|
339
|
+
return kept.map((a, i) => {
|
|
340
|
+
const attempt = {
|
|
341
|
+
attempt: i + 1,
|
|
342
|
+
status: a.status,
|
|
343
|
+
duration: msToNs(a.duration)
|
|
344
|
+
};
|
|
345
|
+
if (a.error) {
|
|
346
|
+
attempt.message = truncateRunes(a.error, MAX_ATTEMPT_MESSAGE_RUNES);
|
|
347
|
+
}
|
|
348
|
+
return attempt;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
317
351
|
function collapseAttempts(attempts) {
|
|
318
352
|
if (attempts.length === 0) {
|
|
319
353
|
throw new Error("collapseAttempts: at least one attempt is required");
|
|
@@ -322,11 +356,13 @@ function collapseAttempts(attempts) {
|
|
|
322
356
|
const retryCount = attempts.length - 1;
|
|
323
357
|
const isFlaky = retryCount > 0 && final.status === "passed" && attempts.some((a) => a.status !== "passed");
|
|
324
358
|
const duration = attempts.reduce((sum, a) => sum + a.duration, 0);
|
|
359
|
+
const attemptHistory = buildAttempts(attempts);
|
|
325
360
|
return {
|
|
326
361
|
status: final.status,
|
|
327
362
|
duration,
|
|
328
363
|
retryCount,
|
|
329
364
|
isFlaky,
|
|
365
|
+
...attemptHistory ? { attempts: attemptHistory } : {},
|
|
330
366
|
error: final.status === "passed" ? void 0 : final.error,
|
|
331
367
|
steps: combineSteps(final.steps, final.manualSteps),
|
|
332
368
|
labels: final.labels,
|
|
@@ -353,6 +389,8 @@ function flushCase(test, attempts) {
|
|
|
353
389
|
duration: msToNs(collapsed.duration),
|
|
354
390
|
retryCount: collapsed.retryCount,
|
|
355
391
|
isFlaky: collapsed.isFlaky,
|
|
392
|
+
// Already nanoseconds — collapseAttempts converts, unlike `duration` above.
|
|
393
|
+
attempts: collapsed.attempts,
|
|
356
394
|
error: collapsed.error,
|
|
357
395
|
steps: collapsed.steps,
|
|
358
396
|
// qualflare.* author-facing metadata API calls (labels/links/tags/
|