@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 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, but
71
- its own instructions still describe the old direct-upload/`QUALFLARE_TOKEN` setup and haven't been
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/