@qualflare/cypress 0.3.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/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 +1 -1
- package/dist/plugin/index.cjs.map +1 -1
- package/dist/plugin/index.js +1 -1
- package/dist/plugin/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@ var MAX_LABELS_PER_CASE = 100;
|
|
|
19
19
|
var MAX_LINKS_PER_CASE = 20;
|
|
20
20
|
var MAX_TAGS_PER_CASE = 64;
|
|
21
21
|
var MAX_TAG_LENGTH = 255;
|
|
22
|
+
var MAX_ATTEMPTS_PER_CASE = 50;
|
|
23
|
+
var MAX_ATTEMPT_MESSAGE_RUNES = 8192;
|
|
22
24
|
var MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
23
25
|
var MAX_STEPS_PER_TEST_ATTEMPT = 300;
|
|
24
26
|
|
|
@@ -272,6 +274,18 @@ function registerCommandLogListener(buffer) {
|
|
|
272
274
|
});
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
// src/shared/text.ts
|
|
278
|
+
function truncateRunes(value, maxRunes) {
|
|
279
|
+
if (value.length <= maxRunes) {
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
const runes = Array.from(value);
|
|
283
|
+
if (runes.length <= maxRunes) {
|
|
284
|
+
return value;
|
|
285
|
+
}
|
|
286
|
+
return runes.slice(0, maxRunes).join("");
|
|
287
|
+
}
|
|
288
|
+
|
|
275
289
|
// src/browser/case-builder.ts
|
|
276
290
|
function combineSteps(autoSteps, manualSteps) {
|
|
277
291
|
const auto = autoSteps ?? [];
|
|
@@ -288,6 +302,26 @@ function combineSteps(autoSteps, manualSteps) {
|
|
|
288
302
|
});
|
|
289
303
|
return [...auto, ...offsetManual];
|
|
290
304
|
}
|
|
305
|
+
function buildAttempts(attempts) {
|
|
306
|
+
if (attempts.length < 2) {
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
let kept = attempts;
|
|
310
|
+
if (attempts.length > MAX_ATTEMPTS_PER_CASE) {
|
|
311
|
+
kept = [...attempts.slice(0, MAX_ATTEMPTS_PER_CASE - 1), attempts[attempts.length - 1]];
|
|
312
|
+
}
|
|
313
|
+
return kept.map((a, i) => {
|
|
314
|
+
const attempt = {
|
|
315
|
+
attempt: i + 1,
|
|
316
|
+
status: a.status,
|
|
317
|
+
duration: msToNs(a.duration)
|
|
318
|
+
};
|
|
319
|
+
if (a.error) {
|
|
320
|
+
attempt.message = truncateRunes(a.error, MAX_ATTEMPT_MESSAGE_RUNES);
|
|
321
|
+
}
|
|
322
|
+
return attempt;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
291
325
|
function collapseAttempts(attempts) {
|
|
292
326
|
if (attempts.length === 0) {
|
|
293
327
|
throw new Error("collapseAttempts: at least one attempt is required");
|
|
@@ -296,11 +330,13 @@ function collapseAttempts(attempts) {
|
|
|
296
330
|
const retryCount = attempts.length - 1;
|
|
297
331
|
const isFlaky = retryCount > 0 && final.status === "passed" && attempts.some((a) => a.status !== "passed");
|
|
298
332
|
const duration = attempts.reduce((sum, a) => sum + a.duration, 0);
|
|
333
|
+
const attemptHistory = buildAttempts(attempts);
|
|
299
334
|
return {
|
|
300
335
|
status: final.status,
|
|
301
336
|
duration,
|
|
302
337
|
retryCount,
|
|
303
338
|
isFlaky,
|
|
339
|
+
...attemptHistory ? { attempts: attemptHistory } : {},
|
|
304
340
|
error: final.status === "passed" ? void 0 : final.error,
|
|
305
341
|
steps: combineSteps(final.steps, final.manualSteps),
|
|
306
342
|
labels: final.labels,
|
|
@@ -327,6 +363,8 @@ function flushCase(test, attempts) {
|
|
|
327
363
|
duration: msToNs(collapsed.duration),
|
|
328
364
|
retryCount: collapsed.retryCount,
|
|
329
365
|
isFlaky: collapsed.isFlaky,
|
|
366
|
+
// Already nanoseconds — collapseAttempts converts, unlike `duration` above.
|
|
367
|
+
attempts: collapsed.attempts,
|
|
330
368
|
error: collapsed.error,
|
|
331
369
|
steps: collapsed.steps,
|
|
332
370
|
// qualflare.* author-facing metadata API calls (labels/links/tags/
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser/browser-integration-guard.ts","../src/shared/constants.ts","../src/shared/duration.ts","../src/shared/logger.ts","../src/browser/console-props.ts","../src/browser/command-log-listener.ts","../src/browser/case-builder.ts","../src/browser/queue.ts","../src/browser/test-metadata-buffer.ts","../src/browser/mocha-listener.ts","../src/browser/test-phase-signal.ts","../src/browser/index.ts","../src/browser/metadata-api.ts"],"sourcesContent":["/**\n * Cypress compiles the support file and each spec file as SEPARATE webpack\n * bundles (see `test-metadata-buffer.ts`'s header comment for the first time\n * this bit us). A spec file that does `import { qualflare } from\n * '@qualflare/cypress'` — the plugin's own documented pattern for the\n * author-facing metadata API — causes `browser/index.ts`'s side effects to\n * re-evaluate in that spec's own bundle, in addition to the support file's\n * `import '@qualflare/cypress'`. Without a guard, that means\n * `registerMochaListener()`/`registerTestPhaseSignal()` each run a second\n * time, registering a SECOND, independent set of listeners against the SAME\n * real `Cypress.mocha.getRunner()` / `Cypress.on(...)` / global\n * `afterEach()`/`beforeEach()` — these ARE genuinely shared across bundles\n * (unlike ES module state, which is not) — so every real test in that spec\n * ends up flushed to the Node side TWICE. Verified against a real Cypress\n * consumer project (app-ui): a spec directly importing `{ qualflare }`\n * uploaded every one of its tests twice, with `qualflare.label()`/`.tag()`\n * data landing on only one of the two duplicate uploads (whichever\n * registration's metadata buffer hadn't already been drained by the other).\n *\n * Fixed the same way `test-metadata-buffer.ts` already fixed the analogous\n * state-sharing problem: anchor a flag on the `Cypress` global, which is the\n * one object genuinely shared across every bundle evaluated within the same\n * spec-runner page. Guarded ONCE, in `browser/index.ts`, rather than inside\n * each individual `register*()` function, so a future third registration\n * function automatically benefits without anyone needing to remember to add\n * its own guard.\n *\n * The flag naturally resets between spec files (Cypress reloads the page —\n * and with it, the whole `Cypress` global — between specs in `cypress run`),\n * which is correct: each spec's own `Cypress.mocha.getRunner()` is a\n * different runner instance and genuinely needs its own registration.\n *\n * Extracted into its own module (rather than living inline in\n * `browser/index.ts`) purely for testability, mirroring this codebase's\n * established pure-logic/thin-Cypress-glue split (`CommandLogBuffer` vs.\n * `registerCommandLogListener`, `MochaAttemptTracker` vs.\n * `registerMochaListener`): `browser/index.ts` itself calls\n * `initializeBrowserIntegration()` as a top-level module-load side effect,\n * which — like `registerMochaListener` — has never supported running\n * outside a real Cypress page (it unconditionally needs `Cypress` to exist\n * for the real registration functions it wires up), so `browser/index.ts`\n * cannot itself be imported under a plain Node/Vitest environment. This\n * module has no such top-level self-invocation, so it can be.\n */\ninterface CypressWithBrowserRegistration {\n __qualflareBrowserRegistered?: boolean;\n}\n\n/**\n * Calls `register()` at most once per distinct `Cypress` object — i.e. once\n * per real spec-file page load, no matter how many separate bundles\n * re-evaluate the module that calls this. When `Cypress` is undefined,\n * there's no flag to anchor on and no meaningful fallback value to hand\n * back (unlike `test-metadata-buffer.ts`'s `getDefaultMetadataBuffer`), so\n * `register()` is invoked unconditionally on every call — preserving this\n * codebase's pre-existing behavior for that scenario exactly (calling the\n * real `registerMochaListener`/`registerTestPhaseSignal` without a real\n * Cypress has never worked, before or after this fix; that's an inherent\n * requirement of what they register, not something this guard changes).\n */\nexport function initializeBrowserIntegration(register: () => void): void {\n const target = typeof Cypress === 'undefined' ? undefined : (Cypress as unknown as CypressWithBrowserRegistration);\n if (target?.__qualflareBrowserRegistered) {\n return;\n }\n if (target) {\n target.__qualflareBrowserRegistered = true;\n }\n register();\n}\n","/**\n * Shared constants that both the browser-side support script and the\n * Node-side plugin must agree on exactly (task names in particular — a\n * typo on either side silently breaks `cy.task()` at runtime with no\n * compile-time signal, since Cypress tasks are looked up by string).\n */\n\n/** `cy.task()` name the browser side uses to hand a finished test's Case\n * object over to the Node side. */\nexport const TASK_REPORT_CASE = 'qualflareReportCase';\n\n/** `cy.task()` name a one-shot root-level `beforeEach` (registered by\n * `src/browser/index.ts`) uses to tell the Node side \"the first test of this\n * spec has started (all applicable `before()` hooks have already run)\" — see\n * `src/plugin/state.ts`'s `TestPhaseGate` for why this exists: it lets\n * `events.ts` distinguish a screenshot taken in a `before()` hook (which\n * should be treated as orphaned, like an `after()`-hook screenshot already\n * is) from one taken during a real test's own execution. */\nexport const TASK_MARK_TEST_PHASE_STARTED = 'qualflareMarkTestPhaseStarted';\n\n/** Server-side caps this client should respect defensively (see\n * `api-service/internal/core/domain/launch/launch.go`). */\nexport const MAX_SUITES_PER_LAUNCH = 2000;\nexport const MAX_CASES_PER_SUITE = 5000;\nexport const MAX_STEPS_PER_CASE = 1000;\nexport const MAX_PARAMETERS_PER_STEP = 50;\nexport const MAX_ATTACHMENTS_PER_CASE = 50;\nexport const MAX_LABELS_PER_CASE = 100;\nexport const MAX_LINKS_PER_CASE = 20;\nexport const MAX_TAGS_PER_CASE = 64;\nexport const MAX_TAG_LENGTH = 255;\n\n/** Mirrors `launch.MaxAttachmentUploadFileSize` — the server's hard cap on a\n * single `POST /api/v1/attachments/upload-url` request (video). */\nexport const MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;\n\n/** Client-side SOFT cap on steps recorded per test attempt — well under the\n * server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). There's no reason\n * to build/serialize thousands of command-log entries for one test; once hit,\n * further entries within that attempt are dropped (with a one-time warning),\n * not queued and truncated later. */\nexport const MAX_STEPS_PER_TEST_ATTEMPT = 300;\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_000_000;\n\n/**\n * Converts a Cypress/Mocha millisecond duration into the wire format's\n * raw-nanosecond integer (see `NanosecondDuration` in ./types.ts).\n *\n * Rounds (not truncates) so fractional-ms input doesn't lose precision by\n * always rounding toward zero. Negative input is clamped to 0 — a negative\n * duration is never legitimate and silently clamping is safer for an\n * ingest payload than throwing and aborting an otherwise-good report.\n */\nexport function msToNs(ms: number): NanosecondDuration {\n if (!Number.isFinite(ms) || ms <= 0) {\n return 0;\n }\n return Math.round(ms * NS_PER_MS);\n}\n","/**\n * A minimal logger writing to stderr (Node) / console (browser). Node-side\n * output deliberately avoids stdout, since that's typically Cypress's own\n * test-output stream and shouldn't be polluted with plugin diagnostics.\n *\n * Safe to import from both browser-side and Node-side code (isomorphic) —\n * `console.*` exists in both environments; only the underlying stream\n * differs, which is not something this module needs to control explicitly\n * since `console.error`/`console.warn` already default to stderr in Node.\n */\n\nconst PREFIX = '[qualflare-cypress]';\n\nexport const logger = {\n debug(...args: unknown[]): void {\n console.debug(PREFIX, ...args);\n },\n info(...args: unknown[]): void {\n console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]): void {\n console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]): void {\n console.error(PREFIX, ...args);\n },\n};\n","import { MAX_PARAMETERS_PER_STEP } from '../shared/constants.js';\nimport type { Parameter } from '../shared/types.js';\n\nconst MAX_VALUE_CHARS = 500;\nconst MAX_STRINGIFY_DEPTH = 3;\n\n/**\n * Renders an arbitrary value into a short, human-readable string for a\n * `Parameter.value`. Must NEVER throw, regardless of input shape — a\n * command's `consoleProps` is inherently unpredictable (varies per command\n * type, may contain DOM elements, functions, circular references, huge\n * arrays/strings) since it exists purely for devtools-console display, not\n * as a stable API contract.\n *\n * - Functions are skipped entirely (rendered as `'[function]'`) rather than\n * attempting to serialize their source.\n * - A value that looks like a DOM element (duck-typed via `.tagName`, since\n * this file must stay isomorphic and can't `instanceof Element` safely in\n * every context this module might be evaluated in) is rendered as its tag\n * name plus a short attribute summary, not a full serialization.\n * - Circular references are broken via a `seen` WeakSet, rendered as\n * `'[circular]'` at the point of recursion.\n * - Long strings are truncated at `MAX_VALUE_CHARS`.\n */\nexport function safeStringify(value: unknown, depth = 0, seen = new WeakSet<object>()): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n\n const type = typeof value;\n if (type === 'string') {\n return truncate(value as string);\n }\n if (type === 'number' || type === 'boolean' || type === 'bigint') {\n // Routed through truncate() like every other branch: a number/boolean\n // is always short, but an arbitrarily large BigInt (plausible in\n // numeric-heavy command output) is not, and previously bypassed the\n // documented MAX_VALUE_CHARS cap entirely.\n return truncate(String(value));\n }\n if (type === 'function') {\n return '[function]';\n }\n if (type === 'symbol') {\n return (value as symbol).toString();\n }\n\n // From here down, `value` is an object (or array) — the only remaining\n // `typeof` result besides 'object'.\n const obj = value as object;\n if (seen.has(obj)) {\n return '[circular]';\n }\n\n // Every read of an unpredictable property on `obj` — including\n // `isDomElementLike`'s `.tagName` duck-type check and\n // `describeDomElementLike`'s `.id`/`.className` reads below — MUST happen\n // inside this try/catch, not before it. A Proxy with a throwing `get`\n // trap, or a plain object with a throwing `tagName`/`id`/`className`\n // getter, previously threw straight out of this function (the DOM-element\n // check ran BEFORE the guarded region even started), violating this\n // function's own \"must never throw\" contract — found via deep adversarial\n // self-review. `consoleProps` is explicitly unpredictable input; nothing\n // about it should be trusted to read safely without a guard.\n seen.add(obj);\n try {\n if (isDomElementLike(obj)) {\n return describeDomElementLike(obj);\n }\n\n if (depth >= MAX_STRINGIFY_DEPTH) {\n return Array.isArray(obj) ? '[array]' : '[object]';\n }\n\n if (Array.isArray(obj)) {\n const items = obj.slice(0, 20).map((item) => safeStringify(item, depth + 1, seen));\n const suffix = obj.length > 20 ? `, …(${obj.length - 20} more)` : '';\n return truncate(`[${items.join(', ')}${suffix}]`);\n }\n if (obj instanceof Error) {\n return truncate(obj.message ? `${obj.name}: ${obj.message}` : obj.name);\n }\n const entries = Object.entries(obj as Record<string, unknown>).slice(0, 20);\n const rendered = entries.map(([key, val]) => `${key}: ${safeStringify(val, depth + 1, seen)}`);\n return truncate(`{${rendered.join(', ')}}`);\n } catch {\n // Getters can throw; a value this hostile just becomes an opaque marker\n // rather than aborting the whole parameter-collection pass.\n return '[unserializable]';\n } finally {\n seen.delete(obj);\n }\n}\n\nfunction truncate(text: string): string {\n return text.length > MAX_VALUE_CHARS ? `${text.slice(0, MAX_VALUE_CHARS)}…` : text;\n}\n\nfunction isDomElementLike(obj: object): obj is { tagName: string; id?: string; className?: string } {\n return typeof (obj as { tagName?: unknown }).tagName === 'string';\n}\n\nfunction describeDomElementLike(el: { tagName: string; id?: string; className?: string }): string {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : '';\n const cls = typeof el.className === 'string' && el.className ? `.${el.className.split(/\\s+/).join('.')}` : '';\n return `<${tag}${id}${cls}>`;\n}\n\n/**\n * Flattens a command's `consoleProps` object into `Parameter[]`, capped at\n * the server's per-step limit.\n *\n * Cypress consistently wraps a command's actual human-meaningful detail one\n * level down, under a `props` key — `consoleProps()` for a real command\n * returns `{ name: 'visit', type: 'command', props: { 'Resolved Url': ...,\n * Redirects: [...], 'Cookies Set': [...] } }`, not the detail fields\n * directly at the top level. Verified against real captured command-log\n * output across seven different command/event types (visit, get, contains,\n * assert, wait, request, route) — every one had exactly this\n * `{name, type, props}` shape, `name`/`type` always redundant with\n * information already carried elsewhere (`name` duplicates the step's own\n * `name`/message; `type` is just `'command'` or `'event'`). Flattening\n * `props`'s keys instead of the outer object's turns one opaque\n * `props: \"{Resolved Url: ..., Redirects: ...}\"` blob into separate\n * `Resolved Url`/`Redirects` parameters a reader can actually scan.\n *\n * Falls back to flattening the outer object directly when `props` isn't a\n * plain object (absent, an array, a primitive) — a custom command's\n * `consoleProps`, or a future Cypress version, may not follow the standard\n * shape, and the top-level flatten is still strictly better than nothing\n * for those.\n *\n * Accepts `unknown` because the caller (`command-log-listener.ts`) reads\n * `consoleProps` off a log entry whose real runtime shape isn't reliably\n * typed (see that file's header comment) — it may be a plain object, a\n * zero-arg function returning one, `undefined`, or something unexpected.\n */\nexport function buildParametersFromConsoleProps(consoleProps: unknown): Parameter[] {\n let resolved: unknown = consoleProps;\n if (typeof resolved === 'function') {\n try {\n resolved = (resolved as () => unknown)();\n } catch {\n return [];\n }\n }\n if (resolved === null || typeof resolved !== 'object') {\n return [];\n }\n\n const outer = resolved as Record<string, unknown>;\n const nestedProps = outer.props;\n const source = nestedProps !== null && typeof nestedProps === 'object' && !Array.isArray(nestedProps)\n ? (nestedProps as Record<string, unknown>)\n : outer;\n\n const parameters: Parameter[] = [];\n for (const [name, value] of Object.entries(source)) {\n if (typeof value === 'function') {\n // Functions carry no useful reportable value (see safeStringify) and\n // are common in consoleProps (e.g. a `Snapshot` accessor) — skip\n // rather than emit a near-useless '[function]' parameter for every one.\n continue;\n }\n parameters.push({ name, value: safeStringify(value) });\n if (parameters.length >= MAX_PARAMETERS_PER_STEP) {\n break;\n }\n }\n return parameters;\n}\n","import { MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { CaseStatus, Step } from '../shared/types.js';\nimport { buildParametersFromConsoleProps } from './console-props.js';\n\n/**\n * The runtime shape of a Cypress command-log entry, as actually observed —\n * NOT the same as `Cypress.LogConfig` in `node_modules/cypress/types/cypress.d.ts`\n * (checked directly against Cypress 14.5.4's shipped types before writing\n * this file, not assumed from memory or from allure-cypress's implementation).\n *\n * What the types confirm:\n * - `Cypress.on('log:added'|'log:changed', (attributes, log) => void)` —\n * `attributes` is typed `ObjectLike` (`{[key: string]: any}`, i.e. no\n * fixed shape enforced by the compiler) and `log` itself is typed `any`.\n * - `LogConfig` (what `log.get()` returns) declares `id`, `type: 'parent' |\n * 'child'`, `name`, `displayName`, `message`, and `consoleProps(): ObjectLike`\n * — i.e. THE TYPES SAY `consoleProps` IS A METHOD, not a property. A\n * sibling type, `LogAttrs`, declares `consoleProps: ObjectLike` as a plain\n * property instead — the two disagree, which is itself evidence this has\n * genuinely varied across Cypress versions/call sites. `consoleProps` is\n * therefore read defensively in `console-props.ts` (call it if it's a\n * function, use it directly otherwise).\n * - No `state` (pass/fail/pending) field and no elapsed-time field\n * (`wallClockStartedAt` or similar) appear ANYWHERE in the typed surface.\n *\n * Real Cypress command logs DO carry a runtime `state` in practice (every\n * published Cypress reporter — allure-cypress, cypress-mochawesome-reporter —\n * reads it the same way), it's just not part of the declared `.d.ts` surface;\n * `ObjectLike`'s open index signature means TypeScript won't stop reading it,\n * so it's accessed here the same documented-by-convention way this codebase\n * already reads `Mocha.Test['_currentRetry']` in `mocha-listener.ts` — cast\n * through a local interface, not the `any`-typed real parameter directly.\n *\n * Because there is NO typed/reliable elapsed-time signal, this module\n * approximates a step's duration as wall-clock time between when the log\n * entry was first seen (`log:added`) and the last update observed for it\n * (`log:changed`, debounced by Cypress itself) or, failing that, the moment\n * the buffer is drained — this is a real approximation, not authoritative\n * per-command timing, and is documented as such rather than presented as\n * precise.\n */\ninterface RawLogAttributes {\n id?: string;\n name?: string;\n displayName?: string;\n /** The one genuinely typed nesting signal Cypress exposes (see file header)\n * — used directly as this module's nesting strategy, in place of the\n * group/parent-graph mechanism an earlier draft of the implementation plan\n * speculated about (which does not appear anywhere in the shipped types). */\n type?: 'parent' | 'child';\n state?: string;\n err?: { message?: string; stack?: string } | Error | string;\n consoleProps?: unknown;\n /** \"additional information to include in the log\" per `Cypress.LogConfig`\n * (typed `any` there — this is the field Cypress's own Command Log UI uses\n * to show e.g. a `cy.visit()`'s URL or a `cy.get()`'s selector next to the\n * bare command name). Read defensively: most commands set a short string,\n * but the type permits anything, and some commands set none at all. */\n message?: unknown;\n}\n\n/** Cap on the `message` text folded into a step's name — this is a display\n * enrichment, not a data field with its own budget, so an unusually long\n * message (a huge typed string, a multi-line selector) is truncated rather\n * than bloating the step name indefinitely. */\nconst MAX_STEP_MESSAGE_CHARS = 200;\n\n/** Extracts a usable short string from a log entry's `message`, or\n * `undefined` if it isn't one worth appending — `message` is typed `any` in\n * Cypress's own declarations, so this must not assume it is always a\n * non-empty string. */\nfunction describeMessage(message: unknown): string | undefined {\n if (typeof message !== 'string') {\n return undefined;\n }\n const trimmed = message.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n return trimmed.length > MAX_STEP_MESSAGE_CHARS ? `${trimmed.slice(0, MAX_STEP_MESSAGE_CHARS)}…` : trimmed;\n}\n\ninterface StepRecord {\n name: string;\n keyword?: string;\n status: CaseStatus;\n error?: string;\n location?: string;\n parentIndex?: number;\n startedAt: number;\n lastSeenAt: number;\n consoleProps?: unknown;\n}\n\nfunction formatLogError(err: RawLogAttributes['err']): string | undefined {\n if (err === undefined || err === null) {\n return undefined;\n }\n if (typeof err === 'string') {\n return err;\n }\n if (err instanceof Error) {\n return err.stack ? `${err.message}\\n${err.stack}` : err.message;\n }\n return err.stack ? `${err.message ?? ''}\\n${err.stack}`.trim() : err.message;\n}\n\n/** Maps a log entry's runtime `state` to the shared status vocabulary.\n * `'failed'` ALWAYS maps to `'failed'` — never silently defaults to\n * `'passed'` for an ambiguous/unrecognized/missing state, matching this\n * org's documented history of \"silently uploads as green\" bugs elsewhere in\n * the platform (see api-service/docs/code-quality/public-api-cli-review.md).\n * An unresolved/unknown state honestly reports as `'pending'` — a real,\n * valid `CaseStatus` meaning \"we never observed a terminal outcome for\n * this,\" not a guess in either direction. */\nfunction mapLogState(state: string | undefined): CaseStatus {\n if (state === 'failed') return 'failed';\n if (state === 'passed') return 'passed';\n return 'pending';\n}\n\n/**\n * Accumulates one test-ATTEMPT's worth of command-log entries into `Step[]`.\n * One instance is shared for the whole spec file's lifetime; `reset()` is\n * called at the start of every test attempt (including retries — a fresh\n * attempt gets a fresh, empty step buffer, matching how `AttemptSnapshot`\n * itself is per-attempt) and `drain()` at the end of one.\n *\n * Filtering: only log entries with a non-empty `name` become steps — this is\n * a deliberately simple heuristic (there is no reliably typed signal to\n * distinguish user-meaningful commands/assertions from Cypress's internal\n * bookkeeping log entries), not an exhaustive noise filter. Good enough to\n * avoid recording obviously-empty entries; may need refinement once\n * exercised against real command-log output from a live Cypress run.\n */\nexport class CommandLogBuffer {\n private records: StepRecord[] = [];\n private indexById = new Map<string, number>();\n private lastParentIndex: number | undefined;\n private capWarned = false;\n\n handleAdded(attrs: RawLogAttributes, now: number = Date.now()): void {\n if (!attrs.name) {\n return;\n }\n if (this.records.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!this.capWarned) {\n this.capWarned = true;\n logger.warn(\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-test soft cap — further command-log ` +\n 'entries for this test attempt will not be recorded.',\n );\n }\n return;\n }\n\n const index = this.records.length;\n const parentIndex = attrs.type === 'child' ? this.lastParentIndex : undefined;\n if (attrs.type === 'parent') {\n this.lastParentIndex = index;\n }\n\n // Enriches the bare command verb with its target/detail — \"visit\" alone\n // vs. \"visit http://localhost:3000/login\" — the same information\n // Cypress's own Command Log UI shows next to the command name. Computed\n // once here rather than kept live via handleChanged: name/keyword are\n // otherwise immutable after creation in this class (only\n // status/error/consoleProps update later), and every message observed\n // in real captured command-log output was already present at\n // 'log:added' time.\n const message = describeMessage(attrs.message);\n const name = message ? `${attrs.name} ${message}` : attrs.name;\n\n this.records.push({\n name,\n keyword: attrs.displayName && attrs.displayName !== attrs.name ? attrs.displayName : undefined,\n status: mapLogState(attrs.state),\n error: formatLogError(attrs.err),\n parentIndex,\n startedAt: now,\n lastSeenAt: now,\n consoleProps: attrs.consoleProps,\n });\n if (attrs.id) {\n this.indexById.set(attrs.id, index);\n }\n }\n\n handleChanged(attrs: RawLogAttributes, now: number = Date.now()): void {\n const index = attrs.id ? this.indexById.get(attrs.id) : undefined;\n if (index === undefined) {\n // A 'log:changed' for an id we never saw 'log:added' for (e.g. it\n // arrived for an entry recorded before this buffer's current attempt\n // started, or was dropped by the soft cap) — nothing to update.\n return;\n }\n const record = this.records[index];\n if (!record) {\n return;\n }\n record.lastSeenAt = now;\n if (attrs.state !== undefined) {\n record.status = mapLogState(attrs.state);\n }\n if (attrs.err !== undefined) {\n record.error = formatLogError(attrs.err);\n }\n if (attrs.consoleProps !== undefined) {\n record.consoleProps = attrs.consoleProps;\n }\n }\n\n /** Returns this attempt's steps as wire-shaped `Step[]` (durations already\n * converted to nanoseconds — unlike `Case`-level duration, which stays in\n * milliseconds until `queue.ts`, `Step` has no separate internal/ms-shaped\n * representation elsewhere in this codebase, so there's no benefit to\n * threading one through here only to convert it later) and clears the\n * buffer for the next attempt. */\n drain(): Step[] {\n const steps = this.records.map((record) => {\n const step: Step = {\n name: record.name,\n status: record.status,\n duration: msToNs(Math.max(0, record.lastSeenAt - record.startedAt)),\n };\n if (record.keyword) step.keyword = record.keyword;\n if (record.error) step.error = record.error;\n if (record.location) step.location = record.location;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex;\n const parameters = buildParametersFromConsoleProps(record.consoleProps);\n if (parameters.length > 0) step.parameters = parameters;\n return step;\n });\n this.reset();\n return steps;\n }\n\n /** Starts a fresh attempt: clears all recorded steps and nesting state.\n * Steps from an abandoned (retried) attempt are discarded, never merged\n * into the next attempt's buffer — see `mocha-listener.ts`, which calls\n * this on every `runner.on('test', ...)` (fired once per attempt,\n * including retries) so only the FINAL attempt's steps ever reach\n * `drain()`. */\n reset(): void {\n this.records = [];\n this.indexById.clear();\n this.lastParentIndex = undefined;\n this.capWarned = false;\n }\n}\n\n/**\n * Registers the real Cypress log-event wiring around a `CommandLogBuffer`.\n * Kept as a thin adapter over the buffer's pure, independently-testable\n * methods — this function itself is not unit-tested directly (it can't be,\n * without a real Cypress runtime), `CommandLogBuffer`'s methods are.\n */\nexport function registerCommandLogListener(buffer: CommandLogBuffer): void {\n Cypress.on('log:added', (attributes: RawLogAttributes) => {\n buffer.handleAdded(attributes);\n });\n Cypress.on('log:changed', (attributes: RawLogAttributes) => {\n buffer.handleChanged(attributes);\n });\n}\n","import { msToNs } from '../shared/duration.js';\nimport type { Attachment, CasePriority, CaseStatus, Label, Link, Step } from '../shared/types.js';\nimport type { ManualStepRecord, TestMetadataSnapshot } from './test-metadata-buffer.js';\n\n/** One attempt of a test — Cypress's built-in retry mechanism re-runs the\n * same logical test in place; each run produces one of these. Durations\n * here are plain MILLISECONDS (Mocha's native unit) — conversion to the\n * wire format's nanoseconds happens later, in `queue.ts`, not here, so this\n * module stays independently testable without needing to know about the\n * wire format's unit convention. */\nexport interface AttemptSnapshot extends TestMetadataSnapshot {\n status: CaseStatus;\n /** Milliseconds. */\n duration: number;\n error?: string;\n /** This attempt's command-log-derived (auto-captured) steps, already\n * wire-shaped — see `command-log-listener.ts`'s `CommandLogBuffer.drain()`.\n * Kept separate from `manualSteps` (inherited from `TestMetadataSnapshot`,\n * from `qualflare.step()` calls — see `test-metadata-buffer.ts`) until\n * `collapseAttempts` combines the two; each has its own independent\n * `parentIndex` numbering until then. */\n steps?: Step[];\n}\n\nexport interface CollapsedResult {\n status: CaseStatus;\n /** Milliseconds — sum of every attempt (reflects true CI wall-clock cost,\n * not just the final attempt's duration). */\n duration: number;\n retryCount: number;\n isFlaky: boolean;\n error?: string;\n /** Only the FINAL attempt's steps — an abandoned (retried) attempt's step\n * trace would misrepresent a single execution as if the same commands ran\n * twice, so earlier attempts' steps are discarded, never merged. This is\n * the fully-combined array (auto-captured command-log steps followed by\n * `qualflare.step()`-declared manual steps, `parentIndex` values already\n * adjusted so both sets index correctly into this one array) — ready to\n * assign directly to `Case.steps`. */\n steps?: Step[];\n labels?: Label[];\n links?: Link[];\n tags?: string[];\n description?: string;\n priority?: CasePriority;\n properties?: Record<string, string>;\n attachments?: Attachment[];\n}\n\n/** Appends `manualSteps` (from `qualflare.step()`, indices/`parentIndex`\n * valid only relative to each other) after `autoSteps` (from\n * `CommandLogBuffer`, indices/`parentIndex` valid only relative to each\n * other) into one combined, correctly-indexed `Step[]`. A manual step's\n * `parentIndex`, if set, refers to another manual step — never an auto\n * step — so it only ever needs shifting by `autoSteps.length`, never\n * cross-referencing into the auto range (see `test-metadata-buffer.ts`'s\n * header comment for why the two nesting mechanisms are kept independent\n * rather than unified). */\nfunction combineSteps(autoSteps: Step[] | undefined, manualSteps: ManualStepRecord[] | undefined): Step[] | undefined {\n const auto = autoSteps ?? [];\n const manual = manualSteps ?? [];\n if (auto.length === 0 && manual.length === 0) {\n return undefined;\n }\n const offsetManual: Step[] = manual.map((record) => {\n const step: Step = { name: record.name, status: record.status, duration: msToNs(record.durationMs ?? 0) };\n if (record.error) step.error = record.error;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex + auto.length;\n if (record.parameters && record.parameters.length > 0) step.parameters = record.parameters;\n return step;\n });\n return [...auto, ...offsetManual];\n}\n\n/**\n * Collapses every attempt of one logical test (as recorded across Cypress's\n * automatic retries) into the single `Case` this reporter uploads. Cypress\n * re-runs the same Mocha `Test` object in place on retry — from the\n * runner's perspective there is one 'test'/'pass'/'fail' event per attempt,\n * not per logical test — so the caller is responsible for grouping\n * attempts by a stable per-test key (this function only does the collapse).\n */\nexport function collapseAttempts(attempts: AttemptSnapshot[]): CollapsedResult {\n if (attempts.length === 0) {\n throw new Error('collapseAttempts: at least one attempt is required');\n }\n const final = attempts[attempts.length - 1]!;\n const retryCount = attempts.length - 1;\n const isFlaky = retryCount > 0 && final.status === 'passed' && attempts.some((a) => a.status !== 'passed');\n const duration = attempts.reduce((sum, a) => sum + a.duration, 0);\n\n return {\n status: final.status,\n duration,\n retryCount,\n isFlaky,\n error: final.status === 'passed' ? undefined : final.error,\n steps: combineSteps(final.steps, final.manualSteps),\n labels: final.labels,\n links: final.links,\n tags: final.tags,\n description: final.description,\n priority: final.priority,\n properties: final.properties,\n attachments: final.attachments,\n };\n}\n","import { TASK_REPORT_CASE } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport type { Case } from '../shared/types.js';\nimport { collapseAttempts, type AttemptSnapshot } from './case-builder.js';\n\n/**\n * Collapses one logical test's recorded attempts into a final `Case` and\n * hands it to the Node side via `cy.task()`. Called once per test, from the\n * root-level `afterEach` in `mocha-listener.ts` — which Cypress guarantees\n * runs only after that test's built-in retries (if any) are exhausted, and\n * (via Mocha's innermost-first `afterEach` ordering) after any `afterEach`\n * the spec itself authored.\n *\n * `{ log: false }` on the `cy.task()` call keeps this plumbing out of the\n * Cypress Command Log — it's reporter bookkeeping, not something a test\n * author needs to see.\n */\nexport function flushCase(test: Mocha.Test, attempts: AttemptSnapshot[]): void {\n if (attempts.length === 0) {\n // Nothing was ever recorded for this test (e.g. it was skipped by a\n // parent-level `.skip` before any runner event fired for it) — nothing\n // to report.\n return;\n }\n\n const collapsed = collapseAttempts(attempts);\n const testCase: Case = {\n id: test.fullTitle(),\n name: test.title,\n className: test.parent?.fullTitle() || undefined,\n status: collapsed.status,\n duration: msToNs(collapsed.duration),\n retryCount: collapsed.retryCount,\n isFlaky: collapsed.isFlaky,\n error: collapsed.error,\n steps: collapsed.steps,\n // qualflare.* author-facing metadata API calls (labels/links/tags/\n // description/priority/properties from qualflare.label()/link()/tag()/\n // description()/priority()/parameter(); attachments from\n // qualflare.attachment()/attachmentFromFile() — the latter carry only\n // a `path`, resolved into inline content Node-side by the existing\n // screenshot-attachment pipeline, see tasks.ts/attachment-reader.ts)\n // from the FINAL attempt only, same \"abandoned attempts are discarded\"\n // rule as `steps`.\n labels: collapsed.labels,\n links: collapsed.links,\n tags: collapsed.tags,\n description: collapsed.description,\n priority: collapsed.priority,\n properties: collapsed.properties,\n attachments: collapsed.attachments,\n };\n\n cy.task(TASK_REPORT_CASE, testCase, { log: false });\n}\n","import {\n MAX_ATTACHMENTS_PER_CASE,\n MAX_LABELS_PER_CASE,\n MAX_LINKS_PER_CASE,\n MAX_PARAMETERS_PER_STEP,\n MAX_STEPS_PER_TEST_ATTEMPT,\n MAX_TAGS_PER_CASE,\n MAX_TAG_LENGTH,\n} from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment, CasePriority, CaseStatus, Label, Link, LinkType, Parameter } from '../shared/types.js';\n\n/** One manually-declared step (`qualflare.step()`), tracked entirely\n * separately from `CommandLogBuffer`'s auto-captured command-log steps (see\n * `command-log-listener.ts`). Combining the two into one truly\n * chronologically-interleaved array would require this buffer and that one\n * to share mutable state — fragile for two independently-testable modules to\n * coordinate. Simpler, deliberate choice instead: manual steps get their own\n * flat array with their own nesting stack (supporting arbitrary depth, unlike\n * Cypress's own flat parent/child signal), and are appended AFTER all\n * auto-captured steps at the point the two arrays are combined\n * (`case-builder.ts`'s `collapseAttempts`), with `parentIndex` shifted by the\n * auto-steps' count so indices remain valid into the final combined array. */\nexport interface ManualStepRecord {\n name: string;\n status: CaseStatus;\n error?: string;\n parentIndex?: number;\n parameters?: Parameter[];\n /** Milliseconds — captured at the actual `cy.then()` execution point in\n * `metadata-api.ts`'s `step()`, i.e. real wall-clock time the wrapped\n * commands ran, not when `qualflare.step()` was textually called. */\n startedAt: number;\n /** Milliseconds — set by `endStep()`; `undefined` until then (e.g. if a\n * step's wrapped commands fail/throw before `endStep()` ever runs — see\n * `metadata-api.ts`'s `step()` doc comment). */\n durationMs?: number;\n}\n\n/** Everything one test-ATTEMPT's `qualflare.*` calls accumulated, drained at\n * the end of that attempt (mirrors `CommandLogBuffer.drain()`'s per-attempt\n * lifecycle — see `mocha-listener.ts`). `manualSteps` is intentionally kept\n * separate from wire-shaped `Step[]` here (its `parentIndex` values are only\n * valid within this array, not yet offset into a combined steps array) —\n * `case-builder.ts` does that combination. */\nexport interface TestMetadataSnapshot {\n labels?: Label[];\n links?: Link[];\n tags?: string[];\n description?: string;\n priority?: CasePriority;\n properties?: Record<string, string>;\n attachments?: Attachment[];\n manualSteps?: ManualStepRecord[];\n}\n\n/**\n * Accumulates one test-attempt's worth of author-facing `qualflare.*` API\n * calls (see `metadata-api.ts`). Attempt-scoped, exactly like\n * `CommandLogBuffer`: `reset()` at the start of every attempt (including\n * retries), `drain()` at the end — so an abandoned/retried attempt's\n * labels/tags/attachments/etc. are discarded, never merged into the next\n * attempt's data, matching how steps already behave (`case-builder.ts`'s\n * \"final attempt wins\" rule).\n *\n * `active` distinguishes \"a test is currently running\" from \"no test is in\n * progress\" (e.g. a `qualflare.*` call made in a `before`/`after` hook, or at\n * spec-file module-load time) — every public method checks it and warns\n * instead of silently recording into data nothing will ever drain, per this\n * codebase's established \"never let incidental misuse abort the run\"\n * philosophy (see `command-log-listener.ts`/`case-builder.ts`).\n */\nexport class TestMetadataBuffer {\n private active = false;\n private labels: Label[] = [];\n private links: Link[] = [];\n private tags: string[] = [];\n private descriptionText: string | undefined;\n private priorityValue: CasePriority | undefined;\n private properties: Record<string, string> = {};\n private attachments: Attachment[] = [];\n private manualSteps: ManualStepRecord[] = [];\n private manualStepStack: number[] = [];\n private cappedWarnings = new Set<string>();\n\n isActive(): boolean {\n return this.active;\n }\n\n /** Starts a fresh attempt: clears all accumulated data and marks the\n * buffer active. Called from `mocha-listener.ts`'s `runner.on('test', ...)`\n * — fired once per attempt, including retries. */\n reset(): void {\n this.active = true;\n this.labels = [];\n this.links = [];\n this.tags = [];\n this.descriptionText = undefined;\n this.priorityValue = undefined;\n this.properties = {};\n this.attachments = [];\n this.manualSteps = [];\n this.manualStepStack = [];\n this.cappedWarnings.clear();\n }\n\n /** Ends the current attempt: returns everything accumulated (undefined for\n * any field with nothing recorded, matching this codebase's\n * omit-rather-than-empty-array convention elsewhere), marks the buffer\n * inactive, and clears state. */\n drain(): TestMetadataSnapshot {\n const snapshot: TestMetadataSnapshot = {\n labels: this.labels.length > 0 ? this.labels : undefined,\n links: this.links.length > 0 ? this.links : undefined,\n tags: this.tags.length > 0 ? this.tags : undefined,\n description: this.descriptionText,\n priority: this.priorityValue,\n properties: Object.keys(this.properties).length > 0 ? this.properties : undefined,\n attachments: this.attachments.length > 0 ? this.attachments : undefined,\n manualSteps: this.manualSteps.length > 0 ? this.manualSteps : undefined,\n };\n this.active = false;\n this.labels = [];\n this.links = [];\n this.tags = [];\n this.descriptionText = undefined;\n this.priorityValue = undefined;\n this.properties = {};\n this.attachments = [];\n this.manualSteps = [];\n this.manualStepStack = [];\n return snapshot;\n }\n\n private warnInactive(fnName: string): void {\n logger.warn(\n `qualflare.${fnName}() was called while no test is currently running (e.g. from a before/after ` +\n 'hook, or at module-load time) — this call has no effect.',\n );\n }\n\n /** Warns at most once per (buffer lifetime, cap-name) pair, so a loop that\n * blows through a cap doesn't spam the log once per iteration. */\n private warnCappedOnce(capName: string, message: string): void {\n if (this.cappedWarnings.has(capName)) return;\n this.cappedWarnings.add(capName);\n logger.warn(message);\n }\n\n label(name: string, value: string): void {\n if (!this.active) return this.warnInactive('label');\n if (this.labels.length >= MAX_LABELS_PER_CASE) {\n return this.warnCappedOnce(\n 'labels',\n `reached the ${MAX_LABELS_PER_CASE}-label-per-case cap — further qualflare.label() calls this test will be dropped.`,\n );\n }\n this.labels.push({ name, value });\n }\n\n link(url: string, opts?: { type?: LinkType; name?: string }): void {\n if (!this.active) return this.warnInactive('link');\n if (this.links.length >= MAX_LINKS_PER_CASE) {\n return this.warnCappedOnce(\n 'links',\n `reached the ${MAX_LINKS_PER_CASE}-link-per-case cap — further qualflare.link() calls this test will be dropped.`,\n );\n }\n const link: Link = { type: opts?.type ?? 'custom', url };\n if (opts?.name) link.name = opts.name;\n this.links.push(link);\n }\n\n /** `Case.tags` is a REJECT-not-truncate field server-side (`max=64` items,\n * `max=255` chars each) — unlike most other caps in this file, exceeding\n * it 400s the whole launch, not just this one test's tags. Count is\n * enforced the same warn-and-drop-excess way as `label()`/`link()`; an\n * individual over-length tag is truncated (not dropped) instead, since a\n * single long string is a shortenable formatting issue, not a structural\n * one — matching how other length-only limits elsewhere in this codebase\n * (e.g. `console-props.ts`'s `truncate()`) are handled. */\n tag(...tags: string[]): void {\n if (!this.active) return this.warnInactive('tag');\n for (const rawTag of tags) {\n if (this.tags.length >= MAX_TAGS_PER_CASE) {\n this.warnCappedOnce(\n 'tags',\n `reached the ${MAX_TAGS_PER_CASE}-tag-per-case cap — further qualflare.tag() calls this test will be dropped.`,\n );\n return;\n }\n let tag = rawTag;\n if (tag.length > MAX_TAG_LENGTH) {\n this.warnCappedOnce('tag-length', `a tag exceeded ${MAX_TAG_LENGTH} characters and was truncated.`);\n tag = tag.slice(0, MAX_TAG_LENGTH);\n }\n this.tags.push(tag);\n }\n }\n\n /** Last-write-wins if called more than once in one test — simpler than\n * concatenation, and matches how most comparable metadata APIs (a single\n * \"set the description\" call, not an accumulating log) behave. */\n description(text: string): void {\n if (!this.active) return this.warnInactive('description');\n this.descriptionText = text;\n }\n\n /** Last-write-wins if called more than once in one test, same as\n * `description()`. Server-side, an unrecognized value is normalized/\n * dropped rather than rejecting the request (`shared/types.ts`), so —\n * like `link()`'s `type` option — this does no runtime validation of\n * its own and simply takes the caller's word for it. */\n priority(value: CasePriority): void {\n if (!this.active) return this.warnInactive('priority');\n this.priorityValue = value;\n }\n\n /**\n * Placement decision (the wire contract has no top-level `Parameter[]` on\n * `Case` — only `Step.parameters` exists, see `shared/types.ts`): a call\n * made while a `qualflare.step()` is currently open attaches to that\n * step's `parameters[]` (capped at `MAX_PARAMETERS_PER_STEP`, shared with\n * whatever `consoleProps`-derived parameters that step might separately\n * accumulate — no, actually a manual step never has consoleProps, only\n * auto-captured command-log steps do, so no sharing/collision is possible\n * here). A call made OUTSIDE any step becomes a `Case.properties` entry\n * instead, since that's the only test-level key/value bag the wire\n * contract offers. `opts.masked` has no analog on `properties` (a plain\n * `Record<string,string>`) and is silently ignored in that branch — real,\n * documented limitation, not a bug: masking only has meaning for a\n * step-level `Parameter`.\n */\n parameter(name: string, value?: string, opts?: { masked?: boolean }): void {\n if (!this.active) return this.warnInactive('parameter');\n const openStepIndex = this.manualStepStack[this.manualStepStack.length - 1];\n if (openStepIndex !== undefined) {\n const step = this.manualSteps[openStepIndex];\n if (!step) return;\n step.parameters ??= [];\n if (step.parameters.length >= MAX_PARAMETERS_PER_STEP) {\n return this.warnCappedOnce(\n 'step-parameters',\n `reached the ${MAX_PARAMETERS_PER_STEP}-parameter-per-step cap — further qualflare.parameter() ` +\n 'calls within this step will be dropped.',\n );\n }\n const parameter: Parameter = { name };\n if (value !== undefined) parameter.value = value;\n if (opts?.masked) parameter.masked = true;\n step.parameters.push(parameter);\n return;\n }\n this.properties[name] = value ?? '';\n }\n\n /** `encoding` defaults to `'utf8'`: `content` is treated as plain text and\n * base64-encoded before being placed into the wire format's\n * always-base64 `Attachment.content` (see `shared/types.ts`). `'base64'`\n * means the caller already has base64 text and it's passed through\n * unencoded. Uses `TextEncoder`/`btoa` rather than Node's `Buffer` — this\n * module runs in the browser (Cypress's actual browser context, not\n * Node), where `Buffer` does not exist; both are standard Web APIs\n * available in every browser Cypress supports. */\n attachment(name: string, content: string, opts?: { encoding?: 'utf8' | 'base64'; mimeType?: string }): void {\n if (!this.active) return this.warnInactive('attachment');\n if (this.attachments.length >= MAX_ATTACHMENTS_PER_CASE) {\n return this.warnCappedOnce(\n 'attachments',\n `reached the ${MAX_ATTACHMENTS_PER_CASE}-attachment-per-case cap — further qualflare.attachment()/` +\n 'attachmentFromFile() calls this test will be dropped. Note this cap is enforced independently ' +\n 'of any screenshots captured during the same test, which are merged in Node-side — the combined ' +\n 'total is not currently capped.',\n );\n }\n const base64 = opts?.encoding === 'base64' ? content : utf8ToBase64(content);\n const attachment: Attachment = { name, content: base64 };\n if (opts?.mimeType) attachment.mimeType = opts.mimeType;\n this.attachments.push(attachment);\n }\n\n /** Mirrors the screenshot flow (`plugin/attachment-reader.ts`): the file's\n * bytes are never read here (this runs browser-side, with no filesystem\n * access) — a path-only `Attachment{name, path, mimeType}` is queued, and\n * the EXISTING Node-side `resolveAttachments()` pipeline (already\n * generic — it reads and size-guards any attachment that has a `path` but\n * no `content`) resolves it once this test's `Case` reaches\n * `TASK_REPORT_CASE`. No new Node-side code needed for this to work. */\n attachmentFromFile(name: string, path: string, opts?: { mimeType?: string }): void {\n if (!this.active) return this.warnInactive('attachmentFromFile');\n if (this.attachments.length >= MAX_ATTACHMENTS_PER_CASE) {\n return this.warnCappedOnce(\n 'attachments',\n `reached the ${MAX_ATTACHMENTS_PER_CASE}-attachment-per-case cap — further qualflare.attachment()/` +\n 'attachmentFromFile() calls this test will be dropped.',\n );\n }\n const attachment: Attachment = { name, path };\n if (opts?.mimeType) attachment.mimeType = opts.mimeType;\n this.attachments.push(attachment);\n }\n\n /** Starts a manually-declared step, nested under whatever manual step (if\n * any) is currently open — an independent nesting stack from\n * `CommandLogBuffer`'s command-log-derived parent/child tracking (see this\n * file's header comment for why the two aren't unified). Returns an index\n * to pass back to `endStep()`. Soft-capped at `MAX_STEPS_PER_TEST_ATTEMPT`,\n * same limit `CommandLogBuffer` uses (the two counts aren't combined\n * against a single shared budget — a deliberate, documented simplification;\n * either buffer alone can reach its own cap independently). Returns\n * `undefined` if inactive or capped — `endStep(undefined, ...)` is a\n * documented no-op, so callers don't need to branch on this themselves. */\n beginStep(name: string, now: number = Date.now()): number | undefined {\n if (!this.active) {\n this.warnInactive('step');\n return undefined;\n }\n if (this.manualSteps.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n this.warnCappedOnce(\n 'manual-steps',\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-test soft cap — further qualflare.step() calls ` +\n 'this test attempt will still run their wrapped commands, but will not be recorded as steps.',\n );\n return undefined;\n }\n const index = this.manualSteps.length;\n const parentIndex = this.manualStepStack[this.manualStepStack.length - 1];\n const record: ManualStepRecord = { name, status: 'pending', startedAt: now };\n if (parentIndex !== undefined) record.parentIndex = parentIndex;\n this.manualSteps.push(record);\n this.manualStepStack.push(index);\n return index;\n }\n\n /** Finalizes a step started by `beginStep()`, recording its real\n * wall-clock duration. A no-op if `index` is `undefined` (the documented\n * signal from `beginStep()` that nothing was actually recorded —\n * inactive buffer or step-count cap reached). If a step's wrapped\n * commands fail/throw, this never runs — see `metadata-api.ts`'s\n * `step()` doc comment — so `durationMs` stays `undefined` and\n * `combineSteps` (`case-builder.ts`) falls back to 0 for that step. */\n endStep(index: number | undefined, status: CaseStatus, error?: string, now: number = Date.now()): void {\n if (index === undefined) return;\n const record = this.manualSteps[index];\n if (record) {\n record.status = status;\n if (error) record.error = error;\n record.durationMs = Math.max(0, now - record.startedAt);\n }\n // Pop defensively rather than assuming `index` is the stack's current\n // top: if steps somehow end out of order (shouldn't happen given the\n // cy.then()-interleaved design in metadata-api.ts, but this keeps the\n // stack from getting stuck open if it ever does), remove this specific\n // index wherever it sits rather than only ever popping the tail.\n const stackPos = this.manualStepStack.lastIndexOf(index);\n if (stackPos !== -1) {\n this.manualStepStack.splice(stackPos, 1);\n }\n }\n}\n\nfunction utf8ToBase64(text: string): string {\n const bytes = new TextEncoder().encode(text);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n\n/**\n * One instance per spec-file load, shared between `metadata-api.ts` (the\n * `qualflare` object a test imports and calls directly) and\n * `mocha-listener.ts` (which drives its reset/drain lifecycle).\n *\n * Deliberately NOT a plain `export const ... = new TestMetadataBuffer()`\n * module-level singleton — verified by running a real `cypress run` (see\n * `test/integration/`) that this breaks in practice: Cypress compiles the\n * support file and each spec file as SEPARATE webpack bundles, and a spec\n * file importing this module gets its own independently-evaluated copy of\n * it (a fresh `TestMetadataBuffer` instance), not the SAME instance the\n * support file's `mocha-listener.ts` is reading from — so every\n * `qualflare.label()`/`.tag()`/etc. call from a spec silently wrote into a\n * buffer nothing ever drained. Anchoring the singleton to a property on the\n * `Cypress` object instead works because `Cypress` itself is injected once,\n * genuinely shared across every bundle running in the same spec-runner page\n * — unlike ES module state, which is NOT guaranteed shared across separate\n * webpack compilations even when they resolve to the identical source file.\n */\ninterface CypressWithMetadataBuffer {\n __qualflareMetadataBuffer?: TestMetadataBuffer;\n}\n\n/** A module-level fallback used only when `Cypress` isn't defined (e.g. this\n * module loaded under Vitest for unit testing, not inside a real Cypress\n * browser) — plain module-singleton semantics are fine there, since a unit\n * test never spans multiple webpack bundles. */\nlet fallbackBuffer: TestMetadataBuffer | undefined;\n\n/** Returns the one shared `TestMetadataBuffer` instance, creating it on\n * first call. A function (not a top-level `const`) specifically so that\n * merely importing this module never touches the `Cypress` global at\n * module-evaluation time — every call site (`metadata-api.ts`,\n * `mocha-listener.ts`) calls this fresh rather than closing over a\n * module-level object reference, avoiding any `this`-binding subtlety a\n * Proxy-based \"looks like a plain object\" wrapper would introduce. */\nexport function getDefaultMetadataBuffer(): TestMetadataBuffer {\n if (typeof Cypress === 'undefined') {\n fallbackBuffer ??= new TestMetadataBuffer();\n return fallbackBuffer;\n }\n const target = Cypress as unknown as CypressWithMetadataBuffer;\n target.__qualflareMetadataBuffer ??= new TestMetadataBuffer();\n return target.__qualflareMetadataBuffer;\n}\n","import type { CaseStatus } from '../shared/types.js';\nimport type { AttemptSnapshot } from './case-builder.js';\nimport { CommandLogBuffer, registerCommandLogListener } from './command-log-listener.js';\nimport { flushCase } from './queue.js';\nimport { getDefaultMetadataBuffer } from './test-metadata-buffer.js';\n\n/**\n * `Cypress.mocha` is not part of Cypress's public TypeScript surface, but\n * `Cypress.mocha.getRunner()` is the documented-by-convention way every\n * existing Cypress reporter (allure-cypress, cypress-mochawesome-reporter,\n * etc.) accesses the live Mocha runner — there is no public/typed\n * alternative. Declared locally rather than widened globally so the\n * `any`-shaped access stays contained to this one file.\n */\ninterface CypressWithMocha {\n mocha: {\n getRunner(): Mocha.Runner;\n };\n}\n\n/** `Runnable._currentRetry`/`_retries` are declared `private` on Mocha's\n * `Runnable` class in the bundled type definitions (compile-time only —\n * they're ordinary properties at runtime). A standalone (non-intersected)\n * shape cast through `unknown` is required to read them — intersecting\n * directly with `Mocha.Test` collapses to `never`, since TypeScript treats\n * a private member of the same name as incompatible with any other\n * declaration of it, public or not. Also documented-by-convention: every\n * existing Cypress reporter reads these two fields the same way, since\n * Mocha exposes no public accessor. */\ninterface RetryFields {\n _currentRetry: number;\n}\n\n/** The runtime shape of a Mocha `Hook` runnable relevant to detecting a\n * `beforeEach` hook failure — verified directly against Cypress 14.5.4's\n * bundled runner source (`packages/runner/dist/cypress_runner.js`), not\n * assumed:\n * - `Runner.prototype.hook`'s `next(i)` closure sets `hook.ctx.currentTest\n * = self.test` before running a `beforeEach`/`afterEach` hook (a\n * DIFFERENT assignment applies for `before all`/`after all`, see below),\n * and only `delete`s it on the hook's SUCCESS path — on failure it\n * remains set, which is exactly what lets a failure handler recover\n * which test the hook was guarding.\n * - `Runner.prototype.failHook` runs `hook.originalTitle = hook.originalTitle\n * || hook.title` BEFORE emitting `'fail'`, and Mocha's own\n * `Suite.prototype.beforeEach` always seeds a fresh hook's title as the\n * literal string `'\"before each\" hook'` (optionally suffixed with\n * `: <name>`) — so `originalTitle` reliably starts with that exact\n * prefix for (and only for) a beforeEach-flavored hook, regardless of\n * Cypress-driver-internal properties (like `hookName`) whose presence on\n * the raw Mocha object at this exact point wasn't verifiable the same\n * way.\n */\ninterface HookRunnable {\n type?: string;\n originalTitle?: string;\n title?: string;\n ctx?: { currentTest?: Mocha.Test };\n}\n\nfunction retryIndex(test: Mocha.Test): number {\n return (test as unknown as RetryFields)._currentRetry ?? 0;\n}\n\nfunction formatError(err: unknown): string | undefined {\n if (err === undefined || err === null) {\n return undefined;\n }\n if (err instanceof Error) {\n return err.stack ? `${err.message}\\n${err.stack}` : err.message;\n }\n return String(err);\n}\n\ninterface AttemptTrackerEntry {\n test: Mocha.Test;\n attempts: AttemptSnapshot[];\n lastRecordedRetryIndex?: number;\n willRetry: boolean;\n}\n\n/**\n * Pure, Cypress/Mocha-independent bookkeeping for collapsing one logical\n * test's retry attempts into the array `case-builder.ts`'s\n * `collapseAttempts` expects — extracted so it's unit-testable without a\n * real Mocha runtime (mirrors `CommandLogBuffer`'s pure-class/thin-adapter\n * split in `command-log-listener.ts`).\n *\n * Keyed by `test.fullTitle()` — Mocha permits duplicate/colliding titles\n * across different `describe` blocks — but WITHOUT ever leaking dedup state\n * across two different tests that happen to share a key: every piece of\n * per-attempt dedup bookkeeping (`lastRecordedRetryIndex`) lives INSIDE the\n * same entry as the attempts it protects, and both are deleted together the\n * instant that test is finalized (`takeIfFinal`/`drainOrphaned`). A LATER,\n * unrelated test that reuses the same key after the first one is finalized\n * starts from a brand-new entry with no memory of the first test's\n * attempts. This replaces an earlier design (a single spec-lifetime-global,\n * never-cleared `Set` of dedup keys) that caused a title-colliding test's\n * data to be silently dropped forever, independent of retries — found via\n * a deep adversarial self-review, never by any automated test (this file\n * previously had zero unit coverage).\n *\n * Each entry also retains the `Mocha.Test` reference it was recorded\n * against, not just its attempts — needed so `drainOrphaned` can hand back\n * something `queue.ts`'s `flushCase` can actually use, for entries whose\n * own test will never get a normal `afterEach` (see that method's doc\n * comment for why immediate flushing from other call sites was tried and\n * found unsafe).\n */\nexport class MochaAttemptTracker {\n private entries = new Map<string, AttemptTrackerEntry>();\n\n private entryFor(key: string, test: Mocha.Test): AttemptTrackerEntry {\n let entry = this.entries.get(key);\n if (!entry) {\n entry = { test, attempts: [], willRetry: false };\n this.entries.set(key, entry);\n }\n return entry;\n }\n\n /** Records one attempt, deduped against only the immediately-preceding\n * record for THIS test (never against any other test's history). More\n * than one Mocha/Cypress event can legitimately fire for the same\n * physical attempt (verified empirically: `runner.on('fail', ...)` AND\n * `runner.on('retry', ...)` both fire for one failing-and-retried\n * attempt) — a second call with the same `retryIndex` for the same key\n * is a no-op rather than double-recording. */\n record(key: string, retryIdx: number, test: Mocha.Test, snapshot: AttemptSnapshot): void {\n const entry = this.entryFor(key, test);\n if (entry.lastRecordedRetryIndex === retryIdx) {\n return;\n }\n entry.lastRecordedRetryIndex = retryIdx;\n entry.attempts.push(snapshot);\n }\n\n /** Marks this test's most-recently-recorded attempt as non-final — more\n * attempts are coming, so `takeIfFinal`/`drainOrphaned` must not flush\n * yet. Only meaningful immediately after a `record()` call for the same\n * attempt (Cypress's retry mechanism always records the failing attempt\n * before emitting `'retry'`). A no-op if no entry exists yet for `key`. */\n markWillRetry(key: string): void {\n const entry = this.entries.get(key);\n if (entry) {\n entry.willRetry = true;\n }\n }\n\n /** The `afterEach`-driven path for the CURRENTLY-ending test: returns its\n * attempts and forgets them — UNLESS `markWillRetry` was called for the\n * attempt just recorded, in which case this consumes that flag and\n * returns `undefined`, leaving the entry in place so the next attempt\n * appends to the same array instead of starting fresh. Returns\n * `undefined` (nothing to do) if no entry exists for `key` at all. */\n takeIfFinal(key: string): AttemptSnapshot[] | undefined {\n const entry = this.entries.get(key);\n if (!entry) {\n return undefined;\n }\n if (entry.willRetry) {\n entry.willRetry = false;\n return undefined;\n }\n this.entries.delete(key);\n return entry.attempts;\n }\n\n /**\n * Sweeps every OTHER finalized-but-never-collected entry (excluding\n * `excludeKey`, the test this `afterEach` firing is already handling via\n * `takeIfFinal`), skipping anything still mid-retry. This exists for\n * exactly one real scenario: a statically-skipped test (`it.skip(...)` or\n * an inherited `.skip`) fires `'pending'` and gets `record()`ed, but\n * Mocha's skip path never runs `afterEach` for it at all — so nothing\n * else will ever collect it.\n *\n * The obvious-looking alternative — flush a skipped test immediately,\n * right in the `'pending'` handler — was tried and is UNSAFE: verified\n * empirically (a real `cypress run` against a fixture spec containing\n * `it.skip(...)`) that calling `cy.task()` synchronously from that\n * handler doesn't just silently no-op, it HANGS the entire run\n * indefinitely (Cypress's command-queue machinery for a test whose body\n * never executes at all appears to never reach a state where a\n * newly-enqueued command can be processed). `drainOrphaned` instead waits\n * until the NEXT real `afterEach` fires (a call site independently\n * proven safe for `cy.task()`, both here and by every other flush in this\n * file) and sweeps anything left over at that point.\n *\n * Residual, honestly-documented limitation: if EVERY test in a spec file\n * is statically skipped, no real `afterEach` ever fires at all in that\n * spec, so a lingering skip entry is never swept — the same outcome as\n * before this fix, for that one narrower sub-case. The common case (at\n * least one non-skipped test in the spec) is fully fixed.\n */\n drainOrphaned(excludeKey: string): Array<{ test: Mocha.Test; attempts: AttemptSnapshot[] }> {\n const drained: Array<{ test: Mocha.Test; attempts: AttemptSnapshot[] }> = [];\n for (const [key, entry] of this.entries) {\n if (key === excludeKey || entry.willRetry) {\n continue;\n }\n drained.push({ test: entry.test, attempts: entry.attempts });\n this.entries.delete(key);\n }\n return drained;\n }\n}\n\n/**\n * Wires the browser-side Mocha listener: accumulates one `AttemptSnapshot`\n * per test-attempt (Cypress's built-in retry re-runs the same `Test`\n * object in place, firing one 'pass'/'fail'/'retry'/'pending' per attempt —\n * not per logical test) into a `MochaAttemptTracker`, and flushes each\n * test's collapsed result to the Node side once its outcome is final.\n *\n * CRITICAL, verified-by-running-real-Cypress detail: Mocha's `afterEach`\n * fires after EVERY physical attempt of a retried test — including ones\n * that will be retried again — not just once after retries are exhausted.\n * The fix is `runner.on('retry', ...)`: Mocha fires this (not 'fail')\n * specifically for an attempt that has retries remaining — 'fail' is\n * reserved for a truly final failure. `MochaAttemptTracker.markWillRetry`\n * tracks which test is mid-retry so `takeIfFinal` (called from the root\n * `afterEach` below) can skip flushing for that attempt and wait for the\n * actual final one.\n *\n * Also verified directly against Cypress's bundled runner source\n * (`Runner.prototype.hook`'s retry-emission block): `runner.on('retry', ...)`\n * can fire for an attempt that actually PASSED, under Cypress's\n * experimental flake-detection retry strategies (`test.hasAttemptPassed`) —\n * in that case `err` is guaranteed falsy (the source's own comment: \"we can\n * assume the test attempt failed as 'err' would have to be present here\"\n * otherwise), so the handler below records `'passed'`, not `'failed'`, when\n * `err` is absent. This package doesn't advertise/configure that\n * experimental strategy today, so this is defensive, not exercised by any\n * current caller.\n *\n * Every flush in this file (`flushCase`, which calls `cy.task()`) happens\n * ONLY from the root-level `afterEach` below — verified the hard way (a\n * real hung `cypress run`) that calling it directly from other Mocha\n * runner event handlers (`'pending'`, a hook-failure `'fail'`) is not\n * reliably safe. Both `'pending'` and the hook-failure branch below only\n * `record()`; `afterEach` is what actually collects and uploads.\n *\n * A root-level `afterEach`, registered here at support-file load time,\n * flushes each test's accumulated attempts once confirmed final — Mocha's\n * innermost-first `afterEach` ordering guarantees this also runs after any\n * `afterEach` the spec itself authored.\n */\nexport function registerMochaListener(): void {\n const runner = (Cypress as unknown as CypressWithMocha).mocha.getRunner();\n const tracker = new MochaAttemptTracker();\n\n // One buffer, shared across the whole spec file's lifetime; reset per\n // attempt (below) and drained into each AttemptSnapshot at record() time —\n // see command-log-listener.ts for why steps are attempt-scoped, not\n // test-scoped (an abandoned/retried attempt's steps must never survive\n // into the next attempt's buffer).\n const stepBuffer = new CommandLogBuffer();\n registerCommandLogListener(stepBuffer);\n\n function buildSnapshot(test: Mocha.Test, status: CaseStatus, err?: unknown): AttemptSnapshot {\n const steps = stepBuffer.drain();\n const metadata = getDefaultMetadataBuffer().drain();\n return {\n status,\n duration: test.duration ?? 0,\n error: formatError(err),\n steps: steps.length > 0 ? steps : undefined,\n ...metadata,\n };\n }\n\n // Fires once per ATTEMPT (Cypress's retry mechanism re-runs the same Test\n // object in place, so this fires again on each retry) — resets both the\n // command-log step buffer AND the qualflare.* metadata buffer, so a fresh\n // attempt starts with empty state rather than inheriting entries from\n // whatever attempt (if any) just finished. The shared metadata buffer also\n // becomes \"active\" here (see `test-metadata-buffer.ts`) — a qualflare.*\n // call made before the first 'test' event of a spec (e.g. module-load\n // time, or inside a `before()` hook) correctly warns-and-no-ops instead of\n // silently writing into a buffer nothing has activated yet.\n runner.on('test', () => {\n stepBuffer.reset();\n getDefaultMetadataBuffer().reset();\n });\n\n runner.on('pass', (test) => {\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'passed'));\n });\n\n runner.on('fail', (test, err) => {\n const runnable = test as unknown as HookRunnable;\n if (runnable.type === 'test') {\n // Reached for a truly final failure (no retries remaining) OR —\n // verified empirically — sometimes ALSO for an attempt that 'retry'\n // below just handled; `tracker.record`'s per-attempt dedup covers\n // both cases.\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'failed', err));\n return;\n }\n\n // A Hook-typed failure. Only a `beforeEach` hook failure is handled\n // here: it fires BEFORE the guarded test's own body has run, so\n // nothing else will ever record that test's result — without this, the\n // test simply vanishes from the report (found via deep self-review,\n // verified against Cypress's actual runner source — see the\n // `HookRunnable` doc comment above for exactly how `ctx.currentTest`\n // and the `'\"before each\" hook'` title prefix were confirmed).\n // `before`/`afterEach`/`after` hook failures are deliberately NOT\n // synthesized here: `before` guards a whole suite, not one identifiable\n // test, and by the time an `afterEach`/`after` hook fails, the guarded\n // test's own pass/fail/pending has already been recorded normally —\n // re-recording it here would incorrectly overwrite a real result with\n // an unrelated hook-cleanup failure. This is a documented, deliberate\n // scope limitation, not an oversight.\n //\n // Deliberately only `record()`s here — does NOT call `flushCase`\n // directly (see the file header comment on why). Mocha's own\n // hook-failure handling still runs the suite's `afterEach` hooks\n // (\"jumps to corresponding after each hook\" — confirmed both in\n // Cypress's runner source comments and by observing this codebase's\n // own root-level `afterEach` correctly receiving\n // `this.currentTest === guardedTest` in a real run), so the normal\n // `afterEach` flush below picks this up via `takeIfFinal` exactly like\n // any other final attempt.\n const guardedTest = runnable.ctx?.currentTest;\n const isBeforeEachHook = (runnable.originalTitle ?? runnable.title ?? '').startsWith('\"before each\" hook');\n if (guardedTest && isBeforeEachHook) {\n tracker.record(guardedTest.fullTitle(), retryIndex(guardedTest), guardedTest, buildSnapshot(guardedTest, 'failed', err));\n }\n });\n\n // Fires for an attempt that has retries remaining — NOT necessarily a\n // failure (see the file header comment re: experimental flake-detection\n // strategies re-running a PASSED attempt). Still record its data\n // (steps/error/duration all feed `collapseAttempts`), and mark it so the\n // afterEach below skips flushing this attempt and waits for the actual\n // final one.\n runner.on('retry', (test: Mocha.Test, err: unknown) => {\n const status: CaseStatus = err ? 'failed' : 'passed';\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, status, err));\n tracker.markWillRetry(test.fullTitle());\n });\n\n runner.on('pending', (test) => {\n // A pending/skipped test never retries, so its outcome is always final\n // the moment 'pending' fires — but do NOT flush here (see the file\n // header comment: calling cy.task() from this handler was found, via a\n // real hung cypress run, to hang the whole process for a statically\n // skipped test). Just record; `drainOrphaned` (called from the next\n // real afterEach) sweeps this up from a call site proven safe.\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'skipped'));\n });\n\n // Fallback for failures that don't reach the Runner's own 'fail'/'retry'\n // events at all (e.g. some uncaught-exception paths) — `tracker.record`'s\n // dedup guard means this never double-counts an attempt already captured\n // through the normal path above.\n Cypress.on('fail', (err, runnable) => {\n // `Mocha.Runnable` (the base class) doesn't declare `type` — only its\n // `Test`/`Hook` subclasses do — so this is checked via a loose cast\n // rather than the type system, matching runner.on('fail')'s own\n // runtime-vs-declared-type mismatch above.\n if ((runnable as unknown as { type?: string }).type !== 'test') {\n throw err;\n }\n const test = runnable as Mocha.Test;\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'failed', err));\n throw err;\n });\n\n afterEach(function flushCurrentTest(this: Mocha.Context) {\n const test = this.currentTest;\n if (!test) {\n return;\n }\n const key = test.fullTitle();\n const attempts = tracker.takeIfFinal(key);\n if (attempts) {\n flushCase(test, attempts);\n }\n // Sweep any orphaned entries (statically-skipped tests, whose own\n // afterEach never runs) now that we know cy.task() is safe — we're\n // inside a real afterEach for a real test.\n for (const orphan of tracker.drainOrphaned(key)) {\n flushCase(orphan.test, orphan.attempts);\n }\n });\n}\n","import { TASK_MARK_TEST_PHASE_STARTED } from '../shared/constants.js';\n\n/**\n * Registers a one-shot, root-level `beforeEach` that tells the Node side\n * \"the first test of this spec is about to run\" — see\n * `src/plugin/state.ts`'s `TestPhaseGate` for the full rationale (letting\n * `events.ts` distinguish a screenshot taken in a root `before()` hook,\n * which cannot be attributed to any test, from one taken during a real\n * test's own execution).\n *\n * Deliberately a real, global `beforeEach()` — NOT a raw\n * `Cypress.mocha.getRunner().on('test', ...)` listener like\n * `mocha-listener.ts` uses for its own bookkeeping. A `beforeEach()`\n * function runs as part of Cypress's normal command-queue processing for\n * that test (the same mechanism `queue.ts`'s already-proven-safe\n * `flushCase`, called from a real `afterEach()`, relies on) — calling\n * `cy.task()` from within it is exactly as safe as calling it from a test\n * body itself. This is a DIFFERENT, safer call site than the raw Mocha\n * runner-event listeners Tier 1 of this remediation effort found (by\n * actually running a real `cypress run`) could hang the whole process.\n *\n * Runs once per spec file (module-eval time registers this one\n * `beforeEach`), and only actually sends the task on the FIRST test's\n * `beforeEach` firing — Mocha runs root-level `beforeEach` hooks\n * outermost-first, so this is guaranteed to run after every applicable\n * `before()` hook (root or nested) has already completed for that test,\n * which is exactly the \"before-hook phase is over\" signal needed. Every\n * subsequent test's `beforeEach` firing is a no-op (the gate only needs to\n * flip once per spec).\n */\nexport function registerTestPhaseSignal(): void {\n let signaled = false;\n beforeEach(function qualflareMarkTestPhaseStarted() {\n if (signaled) {\n return;\n }\n signaled = true;\n cy.task(TASK_MARK_TEST_PHASE_STARTED, null, { log: false });\n });\n}\n","import { initializeBrowserIntegration } from './browser-integration-guard.js';\nimport { registerMochaListener } from './mocha-listener.js';\nimport { registerTestPhaseSignal } from './test-phase-signal.js';\n\n// See browser-integration-guard.ts for the full rationale: this guards\n// against registerMochaListener()/registerTestPhaseSignal() double-firing\n// when a spec file also imports '@qualflare/cypress' directly (Cypress\n// evaluates the support file and each spec file as separate webpack\n// bundles). Deliberately thin/untested, like registerMochaListener itself —\n// it unconditionally needs a real Cypress page to do anything meaningful,\n// so it can't be imported under a plain Node/Vitest environment; the actual\n// guard logic lives in browser-integration-guard.ts, which can be.\ninitializeBrowserIntegration(() => {\n registerMochaListener();\n registerTestPhaseSignal();\n});\n","import type { CasePriority, LinkType } from '../shared/types.js';\nimport { getDefaultMetadataBuffer } from './test-metadata-buffer.js';\n\n/**\n * The author-facing metadata API, imported by test code as:\n *\n * ```ts\n * import { qualflare } from '@qualflare/cypress';\n *\n * it('logs in', () => {\n * qualflare.label('epic', 'Authentication');\n * qualflare.step('fill in credentials', () => {\n * cy.get('#user').type('a@example.com');\n * cy.get('#pass').type('secret');\n * });\n * });\n * ```\n *\n * `label`/`link`/`tag`/`description`/`priority`/`parameter`/`attachment`/\n * `attachmentFromFile` are PLAIN SYNCHRONOUS FUNCTIONS, not Cypress\n * commands — safe because Mocha runs a test's function body synchronously\n * to completion before any of ITS queued `cy.*()` commands actually\n * execute, so exactly one test is ever \"current\" (tracked by the shared\n * metadata buffer's — see `test-metadata-buffer.ts` — active/reset/drain\n * lifecycle, driven by `mocha-listener.ts`) regardless of where these are textually written\n * relative to `cy.*()` calls in the same test body. They append directly\n * into the buffer and return immediately.\n *\n * `step()` is different and MUST interleave into the actual Cypress command\n * queue — see its own doc comment below.\n */\nexport const qualflare = {\n label(name: string, value: string): void {\n getDefaultMetadataBuffer().label(name, value);\n },\n\n link(url: string, opts?: { type?: LinkType; name?: string }): void {\n getDefaultMetadataBuffer().link(url, opts);\n },\n\n tag(...tags: string[]): void {\n getDefaultMetadataBuffer().tag(...tags);\n },\n\n description(text: string): void {\n getDefaultMetadataBuffer().description(text);\n },\n\n priority(value: CasePriority): void {\n getDefaultMetadataBuffer().priority(value);\n },\n\n parameter(name: string, value?: string, opts?: { masked?: boolean }): void {\n getDefaultMetadataBuffer().parameter(name, value, opts);\n },\n\n attachment(name: string, content: string, opts?: { encoding?: 'utf8' | 'base64'; mimeType?: string }): void {\n getDefaultMetadataBuffer().attachment(name, content, opts);\n },\n\n attachmentFromFile(name: string, path: string, opts?: { mimeType?: string }): void {\n getDefaultMetadataBuffer().attachmentFromFile(name, path, opts);\n },\n\n /**\n * Wraps `fn` as a named, reportable step. Unlike every other function on\n * this object, a step's start/end only has meaning relative to when its\n * wrapped `cy.*()` commands actually EXECUTE — not when `step()` is\n * textually called, which happens synchronously, before any queued\n * command has run. So this interleaves `beginStep`/`endStep` INTO the\n * command queue itself via `cy.then()`, at the exact point the wrapped\n * commands run, rather than calling them eagerly.\n *\n * Verified directly against `node_modules/cypress/types/cypress.d.ts`\n * (Cypress 14.5.4) before relying on any of this, rather than assumed:\n * - `Cypress.isCy(obj: any): obj is Chainable` exists exactly as the\n * plan's sketch expected.\n * - `cy.wrap<S>(object: S, options?: Partial<Loggable & Timeoutable>)`\n * accepts `{ log: false }` — this is properly typed.\n * - `cy.then<S>(options: Partial<Timeoutable>, fn): ...` does NOT accept\n * `Loggable` in its options type (only `wrap()` does) — passing\n * `{ log: false }` to `.then()` is a genuine type error under this\n * version's declarations. Cypress's own runtime DOES honor `log: false`\n * on `.then()` in practice (a long-documented, widely-relied-upon\n * behavior across the Cypress plugin ecosystem — every reporter that\n * injects bookkeeping commands into the queue uses this), so `.then()`\n * calls below pass it via a narrow, explicitly-commented type\n * assertion rather than omitting it and cluttering every test's\n * Command Log with reporter-internal entries.\n */\n step<T = void>(name: string, fn: () => T | Cypress.Chainable<T>): Cypress.Chainable<T> {\n // `beginStep()` runs SYNCHRONOUSLY, immediately — not deferred into a\n // queued `cy.then()` as an earlier version of this function did. Verified\n // wrong by running a real Cypress spec (see `test/integration/`): `fn()`\n // below executes synchronously right after this call, but a `cy.then()`\n // callback only runs later, once the Cypress command queue reaches it —\n // so any `qualflare.parameter()`/nested `qualflare.step()` call made\n // directly in `fn()`'s synchronous body would run BEFORE the deferred\n // `beginStep()` ever pushed onto the nesting stack, and would incorrectly\n // see \"no step is open.\" Pushing synchronously here means every call\n // made during `fn()`'s own synchronous execution sees the correct,\n // currently-open step — the tradeoff is that `startedAt` (used for this\n // step's duration) reflects when `step()` was JS-called, not the actual\n // Cypress-queue moment its first wrapped command executes; a documented\n // approximation, consistent with the auto-captured command-log steps'\n // own timing caveat (see `command-log-listener.ts`).\n const stepIndex = getDefaultMetadataBuffer().beginStep(name);\n\n // `fn()` itself can throw SYNCHRONOUSLY (a plain `throw` in the step\n // body, not a failing `cy.*()` command — those fail asynchronously,\n // later in the queue, and are unaffected by this catch). Without this,\n // `endStep()` would never run for this step (only reachable via the\n // `.then()` success path below), leaving it `'pending'` forever with no\n // captured error — found via deep adversarial self-review. Record the\n // failure, then re-throw unchanged: the test itself must still fail\n // normally, this only makes sure the step record reflects what actually\n // happened before that propagates.\n let result: T | Cypress.Chainable<T>;\n try {\n result = fn();\n } catch (err) {\n getDefaultMetadataBuffer().endStep(stepIndex, 'failed', formatSyncStepError(err));\n throw err;\n }\n\n // `Cypress.isCy(obj: any): obj is Chainable` narrows to the untyped\n // generic `Chainable`, not `Chainable<T>` — TS can't prove a runtime\n // check on an `any`-typed parameter preserves a specific type argument,\n // so the branch still needs an explicit assertion back to `Chainable<T>`\n // (safe: `fn`'s own declared return type guarantees this at the call site).\n const chained: Cypress.Chainable<T> = Cypress.isCy(result)\n ? (result as Cypress.Chainable<T>)\n : cy.wrap(result as T, { log: false });\n\n return chained.then(withoutLog(), (value: T) => {\n getDefaultMetadataBuffer().endStep(stepIndex, 'passed');\n return value;\n });\n },\n};\n\n/** Mirrors `mocha-listener.ts`'s `formatError` — kept as a small local copy\n * rather than a shared import, since this file is deliberately independent\n * of that one (see its own module boundary reasoning elsewhere in this\n * codebase). */\nfunction formatSyncStepError(err: unknown): string {\n if (err instanceof Error) {\n return err.stack ? `${err.message}\\n${err.stack}` : err.message;\n }\n return String(err);\n}\n\n/**\n * `{ log: false }`, typed as `Partial<Cypress.Timeoutable>` to satisfy\n * `.then()`'s declared signature — see the long comment on `step()` above\n * for why this is a deliberate, verified-safe type assertion rather than an\n * oversight. Factored into one helper so the justification lives in exactly\n * one place instead of being repeated at each `.then()` call site.\n */\nfunction withoutLog(): Partial<Cypress.Timeoutable> {\n return { log: false } as Partial<Cypress.Timeoutable>;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser/browser-integration-guard.ts","../src/shared/constants.ts","../src/shared/duration.ts","../src/shared/logger.ts","../src/browser/console-props.ts","../src/browser/command-log-listener.ts","../src/shared/text.ts","../src/browser/case-builder.ts","../src/browser/queue.ts","../src/browser/test-metadata-buffer.ts","../src/browser/mocha-listener.ts","../src/browser/test-phase-signal.ts","../src/browser/index.ts","../src/browser/metadata-api.ts"],"sourcesContent":["/**\n * Cypress compiles the support file and each spec file as SEPARATE webpack\n * bundles (see `test-metadata-buffer.ts`'s header comment for the first time\n * this bit us). A spec file that does `import { qualflare } from\n * '@qualflare/cypress'` — the plugin's own documented pattern for the\n * author-facing metadata API — causes `browser/index.ts`'s side effects to\n * re-evaluate in that spec's own bundle, in addition to the support file's\n * `import '@qualflare/cypress'`. Without a guard, that means\n * `registerMochaListener()`/`registerTestPhaseSignal()` each run a second\n * time, registering a SECOND, independent set of listeners against the SAME\n * real `Cypress.mocha.getRunner()` / `Cypress.on(...)` / global\n * `afterEach()`/`beforeEach()` — these ARE genuinely shared across bundles\n * (unlike ES module state, which is not) — so every real test in that spec\n * ends up flushed to the Node side TWICE. Verified against a real Cypress\n * consumer project (app-ui): a spec directly importing `{ qualflare }`\n * uploaded every one of its tests twice, with `qualflare.label()`/`.tag()`\n * data landing on only one of the two duplicate uploads (whichever\n * registration's metadata buffer hadn't already been drained by the other).\n *\n * Fixed the same way `test-metadata-buffer.ts` already fixed the analogous\n * state-sharing problem: anchor a flag on the `Cypress` global, which is the\n * one object genuinely shared across every bundle evaluated within the same\n * spec-runner page. Guarded ONCE, in `browser/index.ts`, rather than inside\n * each individual `register*()` function, so a future third registration\n * function automatically benefits without anyone needing to remember to add\n * its own guard.\n *\n * The flag naturally resets between spec files (Cypress reloads the page —\n * and with it, the whole `Cypress` global — between specs in `cypress run`),\n * which is correct: each spec's own `Cypress.mocha.getRunner()` is a\n * different runner instance and genuinely needs its own registration.\n *\n * Extracted into its own module (rather than living inline in\n * `browser/index.ts`) purely for testability, mirroring this codebase's\n * established pure-logic/thin-Cypress-glue split (`CommandLogBuffer` vs.\n * `registerCommandLogListener`, `MochaAttemptTracker` vs.\n * `registerMochaListener`): `browser/index.ts` itself calls\n * `initializeBrowserIntegration()` as a top-level module-load side effect,\n * which — like `registerMochaListener` — has never supported running\n * outside a real Cypress page (it unconditionally needs `Cypress` to exist\n * for the real registration functions it wires up), so `browser/index.ts`\n * cannot itself be imported under a plain Node/Vitest environment. This\n * module has no such top-level self-invocation, so it can be.\n */\ninterface CypressWithBrowserRegistration {\n __qualflareBrowserRegistered?: boolean;\n}\n\n/**\n * Calls `register()` at most once per distinct `Cypress` object — i.e. once\n * per real spec-file page load, no matter how many separate bundles\n * re-evaluate the module that calls this. When `Cypress` is undefined,\n * there's no flag to anchor on and no meaningful fallback value to hand\n * back (unlike `test-metadata-buffer.ts`'s `getDefaultMetadataBuffer`), so\n * `register()` is invoked unconditionally on every call — preserving this\n * codebase's pre-existing behavior for that scenario exactly (calling the\n * real `registerMochaListener`/`registerTestPhaseSignal` without a real\n * Cypress has never worked, before or after this fix; that's an inherent\n * requirement of what they register, not something this guard changes).\n */\nexport function initializeBrowserIntegration(register: () => void): void {\n const target = typeof Cypress === 'undefined' ? undefined : (Cypress as unknown as CypressWithBrowserRegistration);\n if (target?.__qualflareBrowserRegistered) {\n return;\n }\n if (target) {\n target.__qualflareBrowserRegistered = true;\n }\n register();\n}\n","/**\n * Shared constants that both the browser-side support script and the\n * Node-side plugin must agree on exactly (task names in particular — a\n * typo on either side silently breaks `cy.task()` at runtime with no\n * compile-time signal, since Cypress tasks are looked up by string).\n */\n\n/** `cy.task()` name the browser side uses to hand a finished test's Case\n * object over to the Node side. */\nexport const TASK_REPORT_CASE = 'qualflareReportCase';\n\n/** `cy.task()` name a one-shot root-level `beforeEach` (registered by\n * `src/browser/index.ts`) uses to tell the Node side \"the first test of this\n * spec has started (all applicable `before()` hooks have already run)\" — see\n * `src/plugin/state.ts`'s `TestPhaseGate` for why this exists: it lets\n * `events.ts` distinguish a screenshot taken in a `before()` hook (which\n * should be treated as orphaned, like an `after()`-hook screenshot already\n * is) from one taken during a real test's own execution. */\nexport const TASK_MARK_TEST_PHASE_STARTED = 'qualflareMarkTestPhaseStarted';\n\n/** Server-side caps this client should respect defensively (see\n * `api-service/internal/core/domain/launch/launch.go`). */\nexport const MAX_SUITES_PER_LAUNCH = 2000;\nexport const MAX_CASES_PER_SUITE = 5000;\nexport const MAX_STEPS_PER_CASE = 1000;\nexport const MAX_PARAMETERS_PER_STEP = 50;\nexport const MAX_ATTACHMENTS_PER_CASE = 50;\nexport const MAX_LABELS_PER_CASE = 100;\nexport const MAX_LINKS_PER_CASE = 20;\nexport const MAX_TAGS_PER_CASE = 64;\nexport const MAX_TAG_LENGTH = 255;\n\n/** Mirrors `launch.MaxCaseAttempts`. Beyond this the server keeps the first\n * 49 attempts plus the final one and drops the middle, so sending more is\n * wasted payload rather than an error. */\nexport const MAX_ATTEMPTS_PER_CASE = 50;\n\n/** Mirrors the server's per-attempt text bounds (`launch.MaxAttempt*Runes`).\n *\n * Clamped CLIENT-side, not left to the server, because attempts are the only\n * repeated-per-case payload with no size budget of its own. Measured: one\n * retried test with a deep stack and a chatty log serializes to ~630KB\n * unclamped — most of it text the server discards on write — against a 10MB\n * request body limit that, once exceeded, loses the ENTIRE launch. Sending\n * bytes the server will throw away is pure risk. */\nexport const MAX_ATTEMPT_MESSAGE_RUNES = 8192;\nexport const MAX_ATTEMPT_TRACE_RUNES = 32768;\nexport const MAX_ATTEMPT_SNIPPET_RUNES = 4096;\nexport const MAX_ATTEMPT_OUTPUT_RUNES = 16384;\nexport const MAX_ATTEMPT_OUTPUT_LINES = 200;\n\n/** Mirrors `launch.MaxAttachmentUploadFileSize` — the server's hard cap on a\n * single `POST /api/v1/attachments/upload-url` request (video). */\nexport const MAX_VIDEO_UPLOAD_BYTES = 50 * 1024 * 1024;\n\n/** Client-side SOFT cap on steps recorded per test attempt — well under the\n * server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). There's no reason\n * to build/serialize thousands of command-log entries for one test; once hit,\n * further entries within that attempt are dropped (with a one-time warning),\n * not queued and truncated later. */\nexport const MAX_STEPS_PER_TEST_ATTEMPT = 300;\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_000_000;\n\n/**\n * Converts a Cypress/Mocha millisecond duration into the wire format's\n * raw-nanosecond integer (see `NanosecondDuration` in ./types.ts).\n *\n * Rounds (not truncates) so fractional-ms input doesn't lose precision by\n * always rounding toward zero. Negative input is clamped to 0 — a negative\n * duration is never legitimate and silently clamping is safer for an\n * ingest payload than throwing and aborting an otherwise-good report.\n */\nexport function msToNs(ms: number): NanosecondDuration {\n if (!Number.isFinite(ms) || ms <= 0) {\n return 0;\n }\n return Math.round(ms * NS_PER_MS);\n}\n","/**\n * A minimal logger writing to stderr (Node) / console (browser). Node-side\n * output deliberately avoids stdout, since that's typically Cypress's own\n * test-output stream and shouldn't be polluted with plugin diagnostics.\n *\n * Safe to import from both browser-side and Node-side code (isomorphic) —\n * `console.*` exists in both environments; only the underlying stream\n * differs, which is not something this module needs to control explicitly\n * since `console.error`/`console.warn` already default to stderr in Node.\n */\n\nconst PREFIX = '[qualflare-cypress]';\n\nexport const logger = {\n debug(...args: unknown[]): void {\n console.debug(PREFIX, ...args);\n },\n info(...args: unknown[]): void {\n console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]): void {\n console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]): void {\n console.error(PREFIX, ...args);\n },\n};\n","import { MAX_PARAMETERS_PER_STEP } from '../shared/constants.js';\nimport type { Parameter } from '../shared/types.js';\n\nconst MAX_VALUE_CHARS = 500;\nconst MAX_STRINGIFY_DEPTH = 3;\n\n/**\n * Renders an arbitrary value into a short, human-readable string for a\n * `Parameter.value`. Must NEVER throw, regardless of input shape — a\n * command's `consoleProps` is inherently unpredictable (varies per command\n * type, may contain DOM elements, functions, circular references, huge\n * arrays/strings) since it exists purely for devtools-console display, not\n * as a stable API contract.\n *\n * - Functions are skipped entirely (rendered as `'[function]'`) rather than\n * attempting to serialize their source.\n * - A value that looks like a DOM element (duck-typed via `.tagName`, since\n * this file must stay isomorphic and can't `instanceof Element` safely in\n * every context this module might be evaluated in) is rendered as its tag\n * name plus a short attribute summary, not a full serialization.\n * - Circular references are broken via a `seen` WeakSet, rendered as\n * `'[circular]'` at the point of recursion.\n * - Long strings are truncated at `MAX_VALUE_CHARS`.\n */\nexport function safeStringify(value: unknown, depth = 0, seen = new WeakSet<object>()): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n\n const type = typeof value;\n if (type === 'string') {\n return truncate(value as string);\n }\n if (type === 'number' || type === 'boolean' || type === 'bigint') {\n // Routed through truncate() like every other branch: a number/boolean\n // is always short, but an arbitrarily large BigInt (plausible in\n // numeric-heavy command output) is not, and previously bypassed the\n // documented MAX_VALUE_CHARS cap entirely.\n return truncate(String(value));\n }\n if (type === 'function') {\n return '[function]';\n }\n if (type === 'symbol') {\n return (value as symbol).toString();\n }\n\n // From here down, `value` is an object (or array) — the only remaining\n // `typeof` result besides 'object'.\n const obj = value as object;\n if (seen.has(obj)) {\n return '[circular]';\n }\n\n // Every read of an unpredictable property on `obj` — including\n // `isDomElementLike`'s `.tagName` duck-type check and\n // `describeDomElementLike`'s `.id`/`.className` reads below — MUST happen\n // inside this try/catch, not before it. A Proxy with a throwing `get`\n // trap, or a plain object with a throwing `tagName`/`id`/`className`\n // getter, previously threw straight out of this function (the DOM-element\n // check ran BEFORE the guarded region even started), violating this\n // function's own \"must never throw\" contract — found via deep adversarial\n // self-review. `consoleProps` is explicitly unpredictable input; nothing\n // about it should be trusted to read safely without a guard.\n seen.add(obj);\n try {\n if (isDomElementLike(obj)) {\n return describeDomElementLike(obj);\n }\n\n if (depth >= MAX_STRINGIFY_DEPTH) {\n return Array.isArray(obj) ? '[array]' : '[object]';\n }\n\n if (Array.isArray(obj)) {\n const items = obj.slice(0, 20).map((item) => safeStringify(item, depth + 1, seen));\n const suffix = obj.length > 20 ? `, …(${obj.length - 20} more)` : '';\n return truncate(`[${items.join(', ')}${suffix}]`);\n }\n if (obj instanceof Error) {\n return truncate(obj.message ? `${obj.name}: ${obj.message}` : obj.name);\n }\n const entries = Object.entries(obj as Record<string, unknown>).slice(0, 20);\n const rendered = entries.map(([key, val]) => `${key}: ${safeStringify(val, depth + 1, seen)}`);\n return truncate(`{${rendered.join(', ')}}`);\n } catch {\n // Getters can throw; a value this hostile just becomes an opaque marker\n // rather than aborting the whole parameter-collection pass.\n return '[unserializable]';\n } finally {\n seen.delete(obj);\n }\n}\n\nfunction truncate(text: string): string {\n return text.length > MAX_VALUE_CHARS ? `${text.slice(0, MAX_VALUE_CHARS)}…` : text;\n}\n\nfunction isDomElementLike(obj: object): obj is { tagName: string; id?: string; className?: string } {\n return typeof (obj as { tagName?: unknown }).tagName === 'string';\n}\n\nfunction describeDomElementLike(el: { tagName: string; id?: string; className?: string }): string {\n const tag = el.tagName.toLowerCase();\n const id = el.id ? `#${el.id}` : '';\n const cls = typeof el.className === 'string' && el.className ? `.${el.className.split(/\\s+/).join('.')}` : '';\n return `<${tag}${id}${cls}>`;\n}\n\n/**\n * Flattens a command's `consoleProps` object into `Parameter[]`, capped at\n * the server's per-step limit.\n *\n * Cypress consistently wraps a command's actual human-meaningful detail one\n * level down, under a `props` key — `consoleProps()` for a real command\n * returns `{ name: 'visit', type: 'command', props: { 'Resolved Url': ...,\n * Redirects: [...], 'Cookies Set': [...] } }`, not the detail fields\n * directly at the top level. Verified against real captured command-log\n * output across seven different command/event types (visit, get, contains,\n * assert, wait, request, route) — every one had exactly this\n * `{name, type, props}` shape, `name`/`type` always redundant with\n * information already carried elsewhere (`name` duplicates the step's own\n * `name`/message; `type` is just `'command'` or `'event'`). Flattening\n * `props`'s keys instead of the outer object's turns one opaque\n * `props: \"{Resolved Url: ..., Redirects: ...}\"` blob into separate\n * `Resolved Url`/`Redirects` parameters a reader can actually scan.\n *\n * Falls back to flattening the outer object directly when `props` isn't a\n * plain object (absent, an array, a primitive) — a custom command's\n * `consoleProps`, or a future Cypress version, may not follow the standard\n * shape, and the top-level flatten is still strictly better than nothing\n * for those.\n *\n * Accepts `unknown` because the caller (`command-log-listener.ts`) reads\n * `consoleProps` off a log entry whose real runtime shape isn't reliably\n * typed (see that file's header comment) — it may be a plain object, a\n * zero-arg function returning one, `undefined`, or something unexpected.\n */\nexport function buildParametersFromConsoleProps(consoleProps: unknown): Parameter[] {\n let resolved: unknown = consoleProps;\n if (typeof resolved === 'function') {\n try {\n resolved = (resolved as () => unknown)();\n } catch {\n return [];\n }\n }\n if (resolved === null || typeof resolved !== 'object') {\n return [];\n }\n\n const outer = resolved as Record<string, unknown>;\n const nestedProps = outer.props;\n const source = nestedProps !== null && typeof nestedProps === 'object' && !Array.isArray(nestedProps)\n ? (nestedProps as Record<string, unknown>)\n : outer;\n\n const parameters: Parameter[] = [];\n for (const [name, value] of Object.entries(source)) {\n if (typeof value === 'function') {\n // Functions carry no useful reportable value (see safeStringify) and\n // are common in consoleProps (e.g. a `Snapshot` accessor) — skip\n // rather than emit a near-useless '[function]' parameter for every one.\n continue;\n }\n parameters.push({ name, value: safeStringify(value) });\n if (parameters.length >= MAX_PARAMETERS_PER_STEP) {\n break;\n }\n }\n return parameters;\n}\n","import { MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { CaseStatus, Step } from '../shared/types.js';\nimport { buildParametersFromConsoleProps } from './console-props.js';\n\n/**\n * The runtime shape of a Cypress command-log entry, as actually observed —\n * NOT the same as `Cypress.LogConfig` in `node_modules/cypress/types/cypress.d.ts`\n * (checked directly against Cypress 14.5.4's shipped types before writing\n * this file, not assumed from memory or from allure-cypress's implementation).\n *\n * What the types confirm:\n * - `Cypress.on('log:added'|'log:changed', (attributes, log) => void)` —\n * `attributes` is typed `ObjectLike` (`{[key: string]: any}`, i.e. no\n * fixed shape enforced by the compiler) and `log` itself is typed `any`.\n * - `LogConfig` (what `log.get()` returns) declares `id`, `type: 'parent' |\n * 'child'`, `name`, `displayName`, `message`, and `consoleProps(): ObjectLike`\n * — i.e. THE TYPES SAY `consoleProps` IS A METHOD, not a property. A\n * sibling type, `LogAttrs`, declares `consoleProps: ObjectLike` as a plain\n * property instead — the two disagree, which is itself evidence this has\n * genuinely varied across Cypress versions/call sites. `consoleProps` is\n * therefore read defensively in `console-props.ts` (call it if it's a\n * function, use it directly otherwise).\n * - No `state` (pass/fail/pending) field and no elapsed-time field\n * (`wallClockStartedAt` or similar) appear ANYWHERE in the typed surface.\n *\n * Real Cypress command logs DO carry a runtime `state` in practice (every\n * published Cypress reporter — allure-cypress, cypress-mochawesome-reporter —\n * reads it the same way), it's just not part of the declared `.d.ts` surface;\n * `ObjectLike`'s open index signature means TypeScript won't stop reading it,\n * so it's accessed here the same documented-by-convention way this codebase\n * already reads `Mocha.Test['_currentRetry']` in `mocha-listener.ts` — cast\n * through a local interface, not the `any`-typed real parameter directly.\n *\n * Because there is NO typed/reliable elapsed-time signal, this module\n * approximates a step's duration as wall-clock time between when the log\n * entry was first seen (`log:added`) and the last update observed for it\n * (`log:changed`, debounced by Cypress itself) or, failing that, the moment\n * the buffer is drained — this is a real approximation, not authoritative\n * per-command timing, and is documented as such rather than presented as\n * precise.\n */\ninterface RawLogAttributes {\n id?: string;\n name?: string;\n displayName?: string;\n /** The one genuinely typed nesting signal Cypress exposes (see file header)\n * — used directly as this module's nesting strategy, in place of the\n * group/parent-graph mechanism an earlier draft of the implementation plan\n * speculated about (which does not appear anywhere in the shipped types). */\n type?: 'parent' | 'child';\n state?: string;\n err?: { message?: string; stack?: string } | Error | string;\n consoleProps?: unknown;\n /** \"additional information to include in the log\" per `Cypress.LogConfig`\n * (typed `any` there — this is the field Cypress's own Command Log UI uses\n * to show e.g. a `cy.visit()`'s URL or a `cy.get()`'s selector next to the\n * bare command name). Read defensively: most commands set a short string,\n * but the type permits anything, and some commands set none at all. */\n message?: unknown;\n}\n\n/** Cap on the `message` text folded into a step's name — this is a display\n * enrichment, not a data field with its own budget, so an unusually long\n * message (a huge typed string, a multi-line selector) is truncated rather\n * than bloating the step name indefinitely. */\nconst MAX_STEP_MESSAGE_CHARS = 200;\n\n/** Extracts a usable short string from a log entry's `message`, or\n * `undefined` if it isn't one worth appending — `message` is typed `any` in\n * Cypress's own declarations, so this must not assume it is always a\n * non-empty string. */\nfunction describeMessage(message: unknown): string | undefined {\n if (typeof message !== 'string') {\n return undefined;\n }\n const trimmed = message.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n return trimmed.length > MAX_STEP_MESSAGE_CHARS ? `${trimmed.slice(0, MAX_STEP_MESSAGE_CHARS)}…` : trimmed;\n}\n\ninterface StepRecord {\n name: string;\n keyword?: string;\n status: CaseStatus;\n error?: string;\n location?: string;\n parentIndex?: number;\n startedAt: number;\n lastSeenAt: number;\n consoleProps?: unknown;\n}\n\nfunction formatLogError(err: RawLogAttributes['err']): string | undefined {\n if (err === undefined || err === null) {\n return undefined;\n }\n if (typeof err === 'string') {\n return err;\n }\n if (err instanceof Error) {\n return err.stack ? `${err.message}\\n${err.stack}` : err.message;\n }\n return err.stack ? `${err.message ?? ''}\\n${err.stack}`.trim() : err.message;\n}\n\n/** Maps a log entry's runtime `state` to the shared status vocabulary.\n * `'failed'` ALWAYS maps to `'failed'` — never silently defaults to\n * `'passed'` for an ambiguous/unrecognized/missing state, matching this\n * org's documented history of \"silently uploads as green\" bugs elsewhere in\n * the platform (see api-service/docs/code-quality/public-api-cli-review.md).\n * An unresolved/unknown state honestly reports as `'pending'` — a real,\n * valid `CaseStatus` meaning \"we never observed a terminal outcome for\n * this,\" not a guess in either direction. */\nfunction mapLogState(state: string | undefined): CaseStatus {\n if (state === 'failed') return 'failed';\n if (state === 'passed') return 'passed';\n return 'pending';\n}\n\n/**\n * Accumulates one test-ATTEMPT's worth of command-log entries into `Step[]`.\n * One instance is shared for the whole spec file's lifetime; `reset()` is\n * called at the start of every test attempt (including retries — a fresh\n * attempt gets a fresh, empty step buffer, matching how `AttemptSnapshot`\n * itself is per-attempt) and `drain()` at the end of one.\n *\n * Filtering: only log entries with a non-empty `name` become steps — this is\n * a deliberately simple heuristic (there is no reliably typed signal to\n * distinguish user-meaningful commands/assertions from Cypress's internal\n * bookkeeping log entries), not an exhaustive noise filter. Good enough to\n * avoid recording obviously-empty entries; may need refinement once\n * exercised against real command-log output from a live Cypress run.\n */\nexport class CommandLogBuffer {\n private records: StepRecord[] = [];\n private indexById = new Map<string, number>();\n private lastParentIndex: number | undefined;\n private capWarned = false;\n\n handleAdded(attrs: RawLogAttributes, now: number = Date.now()): void {\n if (!attrs.name) {\n return;\n }\n if (this.records.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!this.capWarned) {\n this.capWarned = true;\n logger.warn(\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-test soft cap — further command-log ` +\n 'entries for this test attempt will not be recorded.',\n );\n }\n return;\n }\n\n const index = this.records.length;\n const parentIndex = attrs.type === 'child' ? this.lastParentIndex : undefined;\n if (attrs.type === 'parent') {\n this.lastParentIndex = index;\n }\n\n // Enriches the bare command verb with its target/detail — \"visit\" alone\n // vs. \"visit http://localhost:3000/login\" — the same information\n // Cypress's own Command Log UI shows next to the command name. Computed\n // once here rather than kept live via handleChanged: name/keyword are\n // otherwise immutable after creation in this class (only\n // status/error/consoleProps update later), and every message observed\n // in real captured command-log output was already present at\n // 'log:added' time.\n const message = describeMessage(attrs.message);\n const name = message ? `${attrs.name} ${message}` : attrs.name;\n\n this.records.push({\n name,\n keyword: attrs.displayName && attrs.displayName !== attrs.name ? attrs.displayName : undefined,\n status: mapLogState(attrs.state),\n error: formatLogError(attrs.err),\n parentIndex,\n startedAt: now,\n lastSeenAt: now,\n consoleProps: attrs.consoleProps,\n });\n if (attrs.id) {\n this.indexById.set(attrs.id, index);\n }\n }\n\n handleChanged(attrs: RawLogAttributes, now: number = Date.now()): void {\n const index = attrs.id ? this.indexById.get(attrs.id) : undefined;\n if (index === undefined) {\n // A 'log:changed' for an id we never saw 'log:added' for (e.g. it\n // arrived for an entry recorded before this buffer's current attempt\n // started, or was dropped by the soft cap) — nothing to update.\n return;\n }\n const record = this.records[index];\n if (!record) {\n return;\n }\n record.lastSeenAt = now;\n if (attrs.state !== undefined) {\n record.status = mapLogState(attrs.state);\n }\n if (attrs.err !== undefined) {\n record.error = formatLogError(attrs.err);\n }\n if (attrs.consoleProps !== undefined) {\n record.consoleProps = attrs.consoleProps;\n }\n }\n\n /** Returns this attempt's steps as wire-shaped `Step[]` (durations already\n * converted to nanoseconds — unlike `Case`-level duration, which stays in\n * milliseconds until `queue.ts`, `Step` has no separate internal/ms-shaped\n * representation elsewhere in this codebase, so there's no benefit to\n * threading one through here only to convert it later) and clears the\n * buffer for the next attempt. */\n drain(): Step[] {\n const steps = this.records.map((record) => {\n const step: Step = {\n name: record.name,\n status: record.status,\n duration: msToNs(Math.max(0, record.lastSeenAt - record.startedAt)),\n };\n if (record.keyword) step.keyword = record.keyword;\n if (record.error) step.error = record.error;\n if (record.location) step.location = record.location;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex;\n const parameters = buildParametersFromConsoleProps(record.consoleProps);\n if (parameters.length > 0) step.parameters = parameters;\n return step;\n });\n this.reset();\n return steps;\n }\n\n /** Starts a fresh attempt: clears all recorded steps and nesting state.\n * Steps from an abandoned (retried) attempt are discarded, never merged\n * into the next attempt's buffer — see `mocha-listener.ts`, which calls\n * this on every `runner.on('test', ...)` (fired once per attempt,\n * including retries) so only the FINAL attempt's steps ever reach\n * `drain()`. */\n reset(): void {\n this.records = [];\n this.indexById.clear();\n this.lastParentIndex = undefined;\n this.capWarned = false;\n }\n}\n\n/**\n * Registers the real Cypress log-event wiring around a `CommandLogBuffer`.\n * Kept as a thin adapter over the buffer's pure, independently-testable\n * methods — this function itself is not unit-tested directly (it can't be,\n * without a real Cypress runtime), `CommandLogBuffer`'s methods are.\n */\nexport function registerCommandLogListener(buffer: CommandLogBuffer): void {\n Cypress.on('log:added', (attributes: RawLogAttributes) => {\n buffer.handleAdded(attributes);\n });\n Cypress.on('log:changed', (attributes: RawLogAttributes) => {\n buffer.handleChanged(attributes);\n });\n}\n","/**\n * Rune-safe truncation for wire fields the server bounds.\n *\n * \"Runes\" means Unicode CODE POINTS, which is what the server counts. A plain\n * `s.slice(0, n)` counts UTF-16 code units instead, so it both over-counts\n * (an emoji is two units, one rune) and can cut a surrogate pair in half,\n * putting a lone surrogate on the wire. Test output contains emoji routinely.\n */\nexport function truncateRunes(value: string, maxRunes: number): string {\n // Fast path: UTF-16 length is always >= the code-point count, so if the\n // cheap measure already fits, the real one does too. Matters because these\n // are called per attempt on strings that can be hundreds of KB.\n if (value.length <= maxRunes) {\n return value;\n }\n const runes = Array.from(value);\n if (runes.length <= maxRunes) {\n return value;\n }\n return runes.slice(0, maxRunes).join('');\n}\n\n/**\n * Bounds captured stdout/stderr to what the server actually stores: the first\n * `maxLines` lines, then a total-rune budget across them.\n *\n * The server joins the lines with newlines into one column and truncates the\n * result, so the `+ 1` per line accounts for the separator it will add.\n * Returns `undefined` when nothing survives, so the field is omitted rather\n * than sent empty.\n */\nexport function clampOutputLines(\n lines: readonly string[],\n maxLines: number,\n maxRunes: number,\n): string[] | undefined {\n const out: string[] = [];\n let budget = maxRunes;\n\n for (const line of lines.slice(0, maxLines)) {\n const cost = Array.from(line).length + 1;\n if (cost > budget) {\n // Keep a partial final line rather than dropping it whole — a truncated\n // last line of a stack trace is still worth more than nothing.\n if (budget > 1) {\n out.push(truncateRunes(line, budget - 1));\n }\n break;\n }\n out.push(line);\n budget -= cost;\n }\n\n return out.length > 0 ? out : undefined;\n}\n","import { msToNs } from '../shared/duration.js';\nimport { MAX_ATTEMPTS_PER_CASE, MAX_ATTEMPT_MESSAGE_RUNES } from '../shared/constants.js';\nimport { truncateRunes } from '../shared/text.js';\nimport type { Attachment, Attempt, CasePriority, CaseStatus, Label, Link, Step } from '../shared/types.js';\nimport type { ManualStepRecord, TestMetadataSnapshot } from './test-metadata-buffer.js';\n\n/** One attempt of a test — Cypress's built-in retry mechanism re-runs the\n * same logical test in place; each run produces one of these. Durations\n * here are plain MILLISECONDS (Mocha's native unit) — conversion to the\n * wire format's nanoseconds happens later, in `queue.ts`, not here, so this\n * module stays independently testable without needing to know about the\n * wire format's unit convention. */\nexport interface AttemptSnapshot extends TestMetadataSnapshot {\n status: CaseStatus;\n /** Milliseconds. */\n duration: number;\n error?: string;\n /** This attempt's command-log-derived (auto-captured) steps, already\n * wire-shaped — see `command-log-listener.ts`'s `CommandLogBuffer.drain()`.\n * Kept separate from `manualSteps` (inherited from `TestMetadataSnapshot`,\n * from `qualflare.step()` calls — see `test-metadata-buffer.ts`) until\n * `collapseAttempts` combines the two; each has its own independent\n * `parentIndex` numbering until then. */\n steps?: Step[];\n}\n\nexport interface CollapsedResult {\n status: CaseStatus;\n /** Milliseconds — sum of every attempt (reflects true CI wall-clock cost,\n * not just the final attempt's duration). */\n duration: number;\n retryCount: number;\n isFlaky: boolean;\n /** Per-attempt history, present only when the test actually retried (>= 2\n * attempts). Durations are already NANOSECONDS here, unlike `duration`\n * above — the same convention `steps` follows in this module, since both\n * are wire-shaped types where the unit is part of the contract. */\n attempts?: Attempt[];\n error?: string;\n /** Only the FINAL attempt's steps — an abandoned (retried) attempt's step\n * trace would misrepresent a single execution as if the same commands ran\n * twice, so earlier attempts' steps are discarded, never merged. This is\n * the fully-combined array (auto-captured command-log steps followed by\n * `qualflare.step()`-declared manual steps, `parentIndex` values already\n * adjusted so both sets index correctly into this one array) — ready to\n * assign directly to `Case.steps`. */\n steps?: Step[];\n labels?: Label[];\n links?: Link[];\n tags?: string[];\n description?: string;\n priority?: CasePriority;\n properties?: Record<string, string>;\n attachments?: Attachment[];\n}\n\n/** Appends `manualSteps` (from `qualflare.step()`, indices/`parentIndex`\n * valid only relative to each other) after `autoSteps` (from\n * `CommandLogBuffer`, indices/`parentIndex` valid only relative to each\n * other) into one combined, correctly-indexed `Step[]`. A manual step's\n * `parentIndex`, if set, refers to another manual step — never an auto\n * step — so it only ever needs shifting by `autoSteps.length`, never\n * cross-referencing into the auto range (see `test-metadata-buffer.ts`'s\n * header comment for why the two nesting mechanisms are kept independent\n * rather than unified). */\nfunction combineSteps(autoSteps: Step[] | undefined, manualSteps: ManualStepRecord[] | undefined): Step[] | undefined {\n const auto = autoSteps ?? [];\n const manual = manualSteps ?? [];\n if (auto.length === 0 && manual.length === 0) {\n return undefined;\n }\n const offsetManual: Step[] = manual.map((record) => {\n const step: Step = { name: record.name, status: record.status, duration: msToNs(record.durationMs ?? 0) };\n if (record.error) step.error = record.error;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex + auto.length;\n if (record.parameters && record.parameters.length > 0) step.parameters = record.parameters;\n return step;\n });\n return [...auto, ...offsetManual];\n}\n\n/**\n * Collapses every attempt of one logical test (as recorded across Cypress's\n * automatic retries) into the single `Case` this reporter uploads. Cypress\n * re-runs the same Mocha `Test` object in place on retry — from the\n * runner's perspective there is one 'test'/'pass'/'fail' event per attempt,\n * not per logical test — so the caller is responsible for grouping\n * attempts by a stable per-test key (this function only does the collapse).\n */\n/**\n * Builds the per-attempt history, or `undefined` when there is nothing worth\n * sending.\n *\n * Unlike the rest of `collapseAttempts`, which keeps only the final attempt's\n * data, this preserves every attempt — that is the entire point. `retryCount`\n * and `isFlaky` say a test retried; this says what went wrong each time.\n *\n * # Why a single attempt sends nothing\n *\n * The server discards a one-element array (there is no history in a test that\n * ran once — its status, duration and error are already on the Case), so\n * sending one spends payload against the 10MB body limit for a dropped row.\n *\n * # Why the whole error string goes into `message`\n *\n * Cypress hands us `${message}\\n${stack}` already flattened by\n * `mocha-listener.ts`'s `formatError`, with no separate stack field to read.\n * Splitting on the first newline to fill `trace` would look tidier and would\n * corrupt every multiline assertion message — which Cypress produces routinely\n * (\"expected X to deep equal Y\" wraps). The server truncates `message` at 8192\n * runes rather than rejecting, and the Case's own `error` (65536 runes) still\n * carries the final attempt's full text, so nothing is actually lost.\n */\nfunction buildAttempts(attempts: AttemptSnapshot[]): Attempt[] | undefined {\n if (attempts.length < 2) {\n return undefined;\n }\n\n // Past the cap the server keeps the first 49 plus the final one and drops the\n // middle. Mirroring that here means the bytes are never sent, and the FINAL\n // attempt survives the trim — a plain slice(0, 50) would discard it.\n let kept = attempts;\n if (attempts.length > MAX_ATTEMPTS_PER_CASE) {\n kept = [...attempts.slice(0, MAX_ATTEMPTS_PER_CASE - 1), attempts[attempts.length - 1]!];\n }\n\n return kept.map((a, i) => {\n const attempt: Attempt = {\n attempt: i + 1,\n status: a.status,\n duration: msToNs(a.duration),\n };\n if (a.error) {\n // Bounded to what the server stores. The whole formatted error goes into\n // `message` (see the note above), so this single field carries the stack\n // too and is what makes an attempt unboundedly large.\n attempt.message = truncateRunes(a.error, MAX_ATTEMPT_MESSAGE_RUNES);\n }\n return attempt;\n });\n}\n\nexport function collapseAttempts(attempts: AttemptSnapshot[]): CollapsedResult {\n if (attempts.length === 0) {\n throw new Error('collapseAttempts: at least one attempt is required');\n }\n const final = attempts[attempts.length - 1]!;\n const retryCount = attempts.length - 1;\n const isFlaky = retryCount > 0 && final.status === 'passed' && attempts.some((a) => a.status !== 'passed');\n const duration = attempts.reduce((sum, a) => sum + a.duration, 0);\n const attemptHistory = buildAttempts(attempts);\n\n return {\n status: final.status,\n duration,\n retryCount,\n isFlaky,\n ...(attemptHistory ? { attempts: attemptHistory } : {}),\n error: final.status === 'passed' ? undefined : final.error,\n steps: combineSteps(final.steps, final.manualSteps),\n labels: final.labels,\n links: final.links,\n tags: final.tags,\n description: final.description,\n priority: final.priority,\n properties: final.properties,\n attachments: final.attachments,\n };\n}\n","import { TASK_REPORT_CASE } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport type { Case } from '../shared/types.js';\nimport { collapseAttempts, type AttemptSnapshot } from './case-builder.js';\n\n/**\n * Collapses one logical test's recorded attempts into a final `Case` and\n * hands it to the Node side via `cy.task()`. Called once per test, from the\n * root-level `afterEach` in `mocha-listener.ts` — which Cypress guarantees\n * runs only after that test's built-in retries (if any) are exhausted, and\n * (via Mocha's innermost-first `afterEach` ordering) after any `afterEach`\n * the spec itself authored.\n *\n * `{ log: false }` on the `cy.task()` call keeps this plumbing out of the\n * Cypress Command Log — it's reporter bookkeeping, not something a test\n * author needs to see.\n */\nexport function flushCase(test: Mocha.Test, attempts: AttemptSnapshot[]): void {\n if (attempts.length === 0) {\n // Nothing was ever recorded for this test (e.g. it was skipped by a\n // parent-level `.skip` before any runner event fired for it) — nothing\n // to report.\n return;\n }\n\n const collapsed = collapseAttempts(attempts);\n const testCase: Case = {\n id: test.fullTitle(),\n name: test.title,\n className: test.parent?.fullTitle() || undefined,\n status: collapsed.status,\n duration: msToNs(collapsed.duration),\n retryCount: collapsed.retryCount,\n isFlaky: collapsed.isFlaky,\n // Already nanoseconds — collapseAttempts converts, unlike `duration` above.\n attempts: collapsed.attempts,\n error: collapsed.error,\n steps: collapsed.steps,\n // qualflare.* author-facing metadata API calls (labels/links/tags/\n // description/priority/properties from qualflare.label()/link()/tag()/\n // description()/priority()/parameter(); attachments from\n // qualflare.attachment()/attachmentFromFile() — the latter carry only\n // a `path`, resolved into inline content Node-side by the existing\n // screenshot-attachment pipeline, see tasks.ts/attachment-reader.ts)\n // from the FINAL attempt only, same \"abandoned attempts are discarded\"\n // rule as `steps`.\n labels: collapsed.labels,\n links: collapsed.links,\n tags: collapsed.tags,\n description: collapsed.description,\n priority: collapsed.priority,\n properties: collapsed.properties,\n attachments: collapsed.attachments,\n };\n\n cy.task(TASK_REPORT_CASE, testCase, { log: false });\n}\n","import {\n MAX_ATTACHMENTS_PER_CASE,\n MAX_LABELS_PER_CASE,\n MAX_LINKS_PER_CASE,\n MAX_PARAMETERS_PER_STEP,\n MAX_STEPS_PER_TEST_ATTEMPT,\n MAX_TAGS_PER_CASE,\n MAX_TAG_LENGTH,\n} from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment, CasePriority, CaseStatus, Label, Link, LinkType, Parameter } from '../shared/types.js';\n\n/** One manually-declared step (`qualflare.step()`), tracked entirely\n * separately from `CommandLogBuffer`'s auto-captured command-log steps (see\n * `command-log-listener.ts`). Combining the two into one truly\n * chronologically-interleaved array would require this buffer and that one\n * to share mutable state — fragile for two independently-testable modules to\n * coordinate. Simpler, deliberate choice instead: manual steps get their own\n * flat array with their own nesting stack (supporting arbitrary depth, unlike\n * Cypress's own flat parent/child signal), and are appended AFTER all\n * auto-captured steps at the point the two arrays are combined\n * (`case-builder.ts`'s `collapseAttempts`), with `parentIndex` shifted by the\n * auto-steps' count so indices remain valid into the final combined array. */\nexport interface ManualStepRecord {\n name: string;\n status: CaseStatus;\n error?: string;\n parentIndex?: number;\n parameters?: Parameter[];\n /** Milliseconds — captured at the actual `cy.then()` execution point in\n * `metadata-api.ts`'s `step()`, i.e. real wall-clock time the wrapped\n * commands ran, not when `qualflare.step()` was textually called. */\n startedAt: number;\n /** Milliseconds — set by `endStep()`; `undefined` until then (e.g. if a\n * step's wrapped commands fail/throw before `endStep()` ever runs — see\n * `metadata-api.ts`'s `step()` doc comment). */\n durationMs?: number;\n}\n\n/** Everything one test-ATTEMPT's `qualflare.*` calls accumulated, drained at\n * the end of that attempt (mirrors `CommandLogBuffer.drain()`'s per-attempt\n * lifecycle — see `mocha-listener.ts`). `manualSteps` is intentionally kept\n * separate from wire-shaped `Step[]` here (its `parentIndex` values are only\n * valid within this array, not yet offset into a combined steps array) —\n * `case-builder.ts` does that combination. */\nexport interface TestMetadataSnapshot {\n labels?: Label[];\n links?: Link[];\n tags?: string[];\n description?: string;\n priority?: CasePriority;\n properties?: Record<string, string>;\n attachments?: Attachment[];\n manualSteps?: ManualStepRecord[];\n}\n\n/**\n * Accumulates one test-attempt's worth of author-facing `qualflare.*` API\n * calls (see `metadata-api.ts`). Attempt-scoped, exactly like\n * `CommandLogBuffer`: `reset()` at the start of every attempt (including\n * retries), `drain()` at the end — so an abandoned/retried attempt's\n * labels/tags/attachments/etc. are discarded, never merged into the next\n * attempt's data, matching how steps already behave (`case-builder.ts`'s\n * \"final attempt wins\" rule).\n *\n * `active` distinguishes \"a test is currently running\" from \"no test is in\n * progress\" (e.g. a `qualflare.*` call made in a `before`/`after` hook, or at\n * spec-file module-load time) — every public method checks it and warns\n * instead of silently recording into data nothing will ever drain, per this\n * codebase's established \"never let incidental misuse abort the run\"\n * philosophy (see `command-log-listener.ts`/`case-builder.ts`).\n */\nexport class TestMetadataBuffer {\n private active = false;\n private labels: Label[] = [];\n private links: Link[] = [];\n private tags: string[] = [];\n private descriptionText: string | undefined;\n private priorityValue: CasePriority | undefined;\n private properties: Record<string, string> = {};\n private attachments: Attachment[] = [];\n private manualSteps: ManualStepRecord[] = [];\n private manualStepStack: number[] = [];\n private cappedWarnings = new Set<string>();\n\n isActive(): boolean {\n return this.active;\n }\n\n /** Starts a fresh attempt: clears all accumulated data and marks the\n * buffer active. Called from `mocha-listener.ts`'s `runner.on('test', ...)`\n * — fired once per attempt, including retries. */\n reset(): void {\n this.active = true;\n this.labels = [];\n this.links = [];\n this.tags = [];\n this.descriptionText = undefined;\n this.priorityValue = undefined;\n this.properties = {};\n this.attachments = [];\n this.manualSteps = [];\n this.manualStepStack = [];\n this.cappedWarnings.clear();\n }\n\n /** Ends the current attempt: returns everything accumulated (undefined for\n * any field with nothing recorded, matching this codebase's\n * omit-rather-than-empty-array convention elsewhere), marks the buffer\n * inactive, and clears state. */\n drain(): TestMetadataSnapshot {\n const snapshot: TestMetadataSnapshot = {\n labels: this.labels.length > 0 ? this.labels : undefined,\n links: this.links.length > 0 ? this.links : undefined,\n tags: this.tags.length > 0 ? this.tags : undefined,\n description: this.descriptionText,\n priority: this.priorityValue,\n properties: Object.keys(this.properties).length > 0 ? this.properties : undefined,\n attachments: this.attachments.length > 0 ? this.attachments : undefined,\n manualSteps: this.manualSteps.length > 0 ? this.manualSteps : undefined,\n };\n this.active = false;\n this.labels = [];\n this.links = [];\n this.tags = [];\n this.descriptionText = undefined;\n this.priorityValue = undefined;\n this.properties = {};\n this.attachments = [];\n this.manualSteps = [];\n this.manualStepStack = [];\n return snapshot;\n }\n\n private warnInactive(fnName: string): void {\n logger.warn(\n `qualflare.${fnName}() was called while no test is currently running (e.g. from a before/after ` +\n 'hook, or at module-load time) — this call has no effect.',\n );\n }\n\n /** Warns at most once per (buffer lifetime, cap-name) pair, so a loop that\n * blows through a cap doesn't spam the log once per iteration. */\n private warnCappedOnce(capName: string, message: string): void {\n if (this.cappedWarnings.has(capName)) return;\n this.cappedWarnings.add(capName);\n logger.warn(message);\n }\n\n label(name: string, value: string): void {\n if (!this.active) return this.warnInactive('label');\n if (this.labels.length >= MAX_LABELS_PER_CASE) {\n return this.warnCappedOnce(\n 'labels',\n `reached the ${MAX_LABELS_PER_CASE}-label-per-case cap — further qualflare.label() calls this test will be dropped.`,\n );\n }\n this.labels.push({ name, value });\n }\n\n link(url: string, opts?: { type?: LinkType; name?: string }): void {\n if (!this.active) return this.warnInactive('link');\n if (this.links.length >= MAX_LINKS_PER_CASE) {\n return this.warnCappedOnce(\n 'links',\n `reached the ${MAX_LINKS_PER_CASE}-link-per-case cap — further qualflare.link() calls this test will be dropped.`,\n );\n }\n const link: Link = { type: opts?.type ?? 'custom', url };\n if (opts?.name) link.name = opts.name;\n this.links.push(link);\n }\n\n /** `Case.tags` is a REJECT-not-truncate field server-side (`max=64` items,\n * `max=255` chars each) — unlike most other caps in this file, exceeding\n * it 400s the whole launch, not just this one test's tags. Count is\n * enforced the same warn-and-drop-excess way as `label()`/`link()`; an\n * individual over-length tag is truncated (not dropped) instead, since a\n * single long string is a shortenable formatting issue, not a structural\n * one — matching how other length-only limits elsewhere in this codebase\n * (e.g. `console-props.ts`'s `truncate()`) are handled. */\n tag(...tags: string[]): void {\n if (!this.active) return this.warnInactive('tag');\n for (const rawTag of tags) {\n if (this.tags.length >= MAX_TAGS_PER_CASE) {\n this.warnCappedOnce(\n 'tags',\n `reached the ${MAX_TAGS_PER_CASE}-tag-per-case cap — further qualflare.tag() calls this test will be dropped.`,\n );\n return;\n }\n let tag = rawTag;\n if (tag.length > MAX_TAG_LENGTH) {\n this.warnCappedOnce('tag-length', `a tag exceeded ${MAX_TAG_LENGTH} characters and was truncated.`);\n tag = tag.slice(0, MAX_TAG_LENGTH);\n }\n this.tags.push(tag);\n }\n }\n\n /** Last-write-wins if called more than once in one test — simpler than\n * concatenation, and matches how most comparable metadata APIs (a single\n * \"set the description\" call, not an accumulating log) behave. */\n description(text: string): void {\n if (!this.active) return this.warnInactive('description');\n this.descriptionText = text;\n }\n\n /** Last-write-wins if called more than once in one test, same as\n * `description()`. Server-side, an unrecognized value is normalized/\n * dropped rather than rejecting the request (`shared/types.ts`), so —\n * like `link()`'s `type` option — this does no runtime validation of\n * its own and simply takes the caller's word for it. */\n priority(value: CasePriority): void {\n if (!this.active) return this.warnInactive('priority');\n this.priorityValue = value;\n }\n\n /**\n * Placement decision (the wire contract has no top-level `Parameter[]` on\n * `Case` — only `Step.parameters` exists, see `shared/types.ts`): a call\n * made while a `qualflare.step()` is currently open attaches to that\n * step's `parameters[]` (capped at `MAX_PARAMETERS_PER_STEP`, shared with\n * whatever `consoleProps`-derived parameters that step might separately\n * accumulate — no, actually a manual step never has consoleProps, only\n * auto-captured command-log steps do, so no sharing/collision is possible\n * here). A call made OUTSIDE any step becomes a `Case.properties` entry\n * instead, since that's the only test-level key/value bag the wire\n * contract offers. `opts.masked` has no analog on `properties` (a plain\n * `Record<string,string>`) and is silently ignored in that branch — real,\n * documented limitation, not a bug: masking only has meaning for a\n * step-level `Parameter`.\n */\n parameter(name: string, value?: string, opts?: { masked?: boolean }): void {\n if (!this.active) return this.warnInactive('parameter');\n const openStepIndex = this.manualStepStack[this.manualStepStack.length - 1];\n if (openStepIndex !== undefined) {\n const step = this.manualSteps[openStepIndex];\n if (!step) return;\n step.parameters ??= [];\n if (step.parameters.length >= MAX_PARAMETERS_PER_STEP) {\n return this.warnCappedOnce(\n 'step-parameters',\n `reached the ${MAX_PARAMETERS_PER_STEP}-parameter-per-step cap — further qualflare.parameter() ` +\n 'calls within this step will be dropped.',\n );\n }\n const parameter: Parameter = { name };\n if (value !== undefined) parameter.value = value;\n if (opts?.masked) parameter.masked = true;\n step.parameters.push(parameter);\n return;\n }\n this.properties[name] = value ?? '';\n }\n\n /** `encoding` defaults to `'utf8'`: `content` is treated as plain text and\n * base64-encoded before being placed into the wire format's\n * always-base64 `Attachment.content` (see `shared/types.ts`). `'base64'`\n * means the caller already has base64 text and it's passed through\n * unencoded. Uses `TextEncoder`/`btoa` rather than Node's `Buffer` — this\n * module runs in the browser (Cypress's actual browser context, not\n * Node), where `Buffer` does not exist; both are standard Web APIs\n * available in every browser Cypress supports. */\n attachment(name: string, content: string, opts?: { encoding?: 'utf8' | 'base64'; mimeType?: string }): void {\n if (!this.active) return this.warnInactive('attachment');\n if (this.attachments.length >= MAX_ATTACHMENTS_PER_CASE) {\n return this.warnCappedOnce(\n 'attachments',\n `reached the ${MAX_ATTACHMENTS_PER_CASE}-attachment-per-case cap — further qualflare.attachment()/` +\n 'attachmentFromFile() calls this test will be dropped. Note this cap is enforced independently ' +\n 'of any screenshots captured during the same test, which are merged in Node-side — the combined ' +\n 'total is not currently capped.',\n );\n }\n const base64 = opts?.encoding === 'base64' ? content : utf8ToBase64(content);\n const attachment: Attachment = { name, content: base64 };\n if (opts?.mimeType) attachment.mimeType = opts.mimeType;\n this.attachments.push(attachment);\n }\n\n /** Mirrors the screenshot flow (`plugin/attachment-reader.ts`): the file's\n * bytes are never read here (this runs browser-side, with no filesystem\n * access) — a path-only `Attachment{name, path, mimeType}` is queued, and\n * the EXISTING Node-side `resolveAttachments()` pipeline (already\n * generic — it reads and size-guards any attachment that has a `path` but\n * no `content`) resolves it once this test's `Case` reaches\n * `TASK_REPORT_CASE`. No new Node-side code needed for this to work. */\n attachmentFromFile(name: string, path: string, opts?: { mimeType?: string }): void {\n if (!this.active) return this.warnInactive('attachmentFromFile');\n if (this.attachments.length >= MAX_ATTACHMENTS_PER_CASE) {\n return this.warnCappedOnce(\n 'attachments',\n `reached the ${MAX_ATTACHMENTS_PER_CASE}-attachment-per-case cap — further qualflare.attachment()/` +\n 'attachmentFromFile() calls this test will be dropped.',\n );\n }\n const attachment: Attachment = { name, path };\n if (opts?.mimeType) attachment.mimeType = opts.mimeType;\n this.attachments.push(attachment);\n }\n\n /** Starts a manually-declared step, nested under whatever manual step (if\n * any) is currently open — an independent nesting stack from\n * `CommandLogBuffer`'s command-log-derived parent/child tracking (see this\n * file's header comment for why the two aren't unified). Returns an index\n * to pass back to `endStep()`. Soft-capped at `MAX_STEPS_PER_TEST_ATTEMPT`,\n * same limit `CommandLogBuffer` uses (the two counts aren't combined\n * against a single shared budget — a deliberate, documented simplification;\n * either buffer alone can reach its own cap independently). Returns\n * `undefined` if inactive or capped — `endStep(undefined, ...)` is a\n * documented no-op, so callers don't need to branch on this themselves. */\n beginStep(name: string, now: number = Date.now()): number | undefined {\n if (!this.active) {\n this.warnInactive('step');\n return undefined;\n }\n if (this.manualSteps.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n this.warnCappedOnce(\n 'manual-steps',\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-test soft cap — further qualflare.step() calls ` +\n 'this test attempt will still run their wrapped commands, but will not be recorded as steps.',\n );\n return undefined;\n }\n const index = this.manualSteps.length;\n const parentIndex = this.manualStepStack[this.manualStepStack.length - 1];\n const record: ManualStepRecord = { name, status: 'pending', startedAt: now };\n if (parentIndex !== undefined) record.parentIndex = parentIndex;\n this.manualSteps.push(record);\n this.manualStepStack.push(index);\n return index;\n }\n\n /** Finalizes a step started by `beginStep()`, recording its real\n * wall-clock duration. A no-op if `index` is `undefined` (the documented\n * signal from `beginStep()` that nothing was actually recorded —\n * inactive buffer or step-count cap reached). If a step's wrapped\n * commands fail/throw, this never runs — see `metadata-api.ts`'s\n * `step()` doc comment — so `durationMs` stays `undefined` and\n * `combineSteps` (`case-builder.ts`) falls back to 0 for that step. */\n endStep(index: number | undefined, status: CaseStatus, error?: string, now: number = Date.now()): void {\n if (index === undefined) return;\n const record = this.manualSteps[index];\n if (record) {\n record.status = status;\n if (error) record.error = error;\n record.durationMs = Math.max(0, now - record.startedAt);\n }\n // Pop defensively rather than assuming `index` is the stack's current\n // top: if steps somehow end out of order (shouldn't happen given the\n // cy.then()-interleaved design in metadata-api.ts, but this keeps the\n // stack from getting stuck open if it ever does), remove this specific\n // index wherever it sits rather than only ever popping the tail.\n const stackPos = this.manualStepStack.lastIndexOf(index);\n if (stackPos !== -1) {\n this.manualStepStack.splice(stackPos, 1);\n }\n }\n}\n\nfunction utf8ToBase64(text: string): string {\n const bytes = new TextEncoder().encode(text);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n\n/**\n * One instance per spec-file load, shared between `metadata-api.ts` (the\n * `qualflare` object a test imports and calls directly) and\n * `mocha-listener.ts` (which drives its reset/drain lifecycle).\n *\n * Deliberately NOT a plain `export const ... = new TestMetadataBuffer()`\n * module-level singleton — verified by running a real `cypress run` (see\n * `test/integration/`) that this breaks in practice: Cypress compiles the\n * support file and each spec file as SEPARATE webpack bundles, and a spec\n * file importing this module gets its own independently-evaluated copy of\n * it (a fresh `TestMetadataBuffer` instance), not the SAME instance the\n * support file's `mocha-listener.ts` is reading from — so every\n * `qualflare.label()`/`.tag()`/etc. call from a spec silently wrote into a\n * buffer nothing ever drained. Anchoring the singleton to a property on the\n * `Cypress` object instead works because `Cypress` itself is injected once,\n * genuinely shared across every bundle running in the same spec-runner page\n * — unlike ES module state, which is NOT guaranteed shared across separate\n * webpack compilations even when they resolve to the identical source file.\n */\ninterface CypressWithMetadataBuffer {\n __qualflareMetadataBuffer?: TestMetadataBuffer;\n}\n\n/** A module-level fallback used only when `Cypress` isn't defined (e.g. this\n * module loaded under Vitest for unit testing, not inside a real Cypress\n * browser) — plain module-singleton semantics are fine there, since a unit\n * test never spans multiple webpack bundles. */\nlet fallbackBuffer: TestMetadataBuffer | undefined;\n\n/** Returns the one shared `TestMetadataBuffer` instance, creating it on\n * first call. A function (not a top-level `const`) specifically so that\n * merely importing this module never touches the `Cypress` global at\n * module-evaluation time — every call site (`metadata-api.ts`,\n * `mocha-listener.ts`) calls this fresh rather than closing over a\n * module-level object reference, avoiding any `this`-binding subtlety a\n * Proxy-based \"looks like a plain object\" wrapper would introduce. */\nexport function getDefaultMetadataBuffer(): TestMetadataBuffer {\n if (typeof Cypress === 'undefined') {\n fallbackBuffer ??= new TestMetadataBuffer();\n return fallbackBuffer;\n }\n const target = Cypress as unknown as CypressWithMetadataBuffer;\n target.__qualflareMetadataBuffer ??= new TestMetadataBuffer();\n return target.__qualflareMetadataBuffer;\n}\n","import type { CaseStatus } from '../shared/types.js';\nimport type { AttemptSnapshot } from './case-builder.js';\nimport { CommandLogBuffer, registerCommandLogListener } from './command-log-listener.js';\nimport { flushCase } from './queue.js';\nimport { getDefaultMetadataBuffer } from './test-metadata-buffer.js';\n\n/**\n * `Cypress.mocha` is not part of Cypress's public TypeScript surface, but\n * `Cypress.mocha.getRunner()` is the documented-by-convention way every\n * existing Cypress reporter (allure-cypress, cypress-mochawesome-reporter,\n * etc.) accesses the live Mocha runner — there is no public/typed\n * alternative. Declared locally rather than widened globally so the\n * `any`-shaped access stays contained to this one file.\n */\ninterface CypressWithMocha {\n mocha: {\n getRunner(): Mocha.Runner;\n };\n}\n\n/** `Runnable._currentRetry`/`_retries` are declared `private` on Mocha's\n * `Runnable` class in the bundled type definitions (compile-time only —\n * they're ordinary properties at runtime). A standalone (non-intersected)\n * shape cast through `unknown` is required to read them — intersecting\n * directly with `Mocha.Test` collapses to `never`, since TypeScript treats\n * a private member of the same name as incompatible with any other\n * declaration of it, public or not. Also documented-by-convention: every\n * existing Cypress reporter reads these two fields the same way, since\n * Mocha exposes no public accessor. */\ninterface RetryFields {\n _currentRetry: number;\n}\n\n/** The runtime shape of a Mocha `Hook` runnable relevant to detecting a\n * `beforeEach` hook failure — verified directly against Cypress 14.5.4's\n * bundled runner source (`packages/runner/dist/cypress_runner.js`), not\n * assumed:\n * - `Runner.prototype.hook`'s `next(i)` closure sets `hook.ctx.currentTest\n * = self.test` before running a `beforeEach`/`afterEach` hook (a\n * DIFFERENT assignment applies for `before all`/`after all`, see below),\n * and only `delete`s it on the hook's SUCCESS path — on failure it\n * remains set, which is exactly what lets a failure handler recover\n * which test the hook was guarding.\n * - `Runner.prototype.failHook` runs `hook.originalTitle = hook.originalTitle\n * || hook.title` BEFORE emitting `'fail'`, and Mocha's own\n * `Suite.prototype.beforeEach` always seeds a fresh hook's title as the\n * literal string `'\"before each\" hook'` (optionally suffixed with\n * `: <name>`) — so `originalTitle` reliably starts with that exact\n * prefix for (and only for) a beforeEach-flavored hook, regardless of\n * Cypress-driver-internal properties (like `hookName`) whose presence on\n * the raw Mocha object at this exact point wasn't verifiable the same\n * way.\n */\ninterface HookRunnable {\n type?: string;\n originalTitle?: string;\n title?: string;\n ctx?: { currentTest?: Mocha.Test };\n}\n\nfunction retryIndex(test: Mocha.Test): number {\n return (test as unknown as RetryFields)._currentRetry ?? 0;\n}\n\nfunction formatError(err: unknown): string | undefined {\n if (err === undefined || err === null) {\n return undefined;\n }\n if (err instanceof Error) {\n return err.stack ? `${err.message}\\n${err.stack}` : err.message;\n }\n return String(err);\n}\n\ninterface AttemptTrackerEntry {\n test: Mocha.Test;\n attempts: AttemptSnapshot[];\n lastRecordedRetryIndex?: number;\n willRetry: boolean;\n}\n\n/**\n * Pure, Cypress/Mocha-independent bookkeeping for collapsing one logical\n * test's retry attempts into the array `case-builder.ts`'s\n * `collapseAttempts` expects — extracted so it's unit-testable without a\n * real Mocha runtime (mirrors `CommandLogBuffer`'s pure-class/thin-adapter\n * split in `command-log-listener.ts`).\n *\n * Keyed by `test.fullTitle()` — Mocha permits duplicate/colliding titles\n * across different `describe` blocks — but WITHOUT ever leaking dedup state\n * across two different tests that happen to share a key: every piece of\n * per-attempt dedup bookkeeping (`lastRecordedRetryIndex`) lives INSIDE the\n * same entry as the attempts it protects, and both are deleted together the\n * instant that test is finalized (`takeIfFinal`/`drainOrphaned`). A LATER,\n * unrelated test that reuses the same key after the first one is finalized\n * starts from a brand-new entry with no memory of the first test's\n * attempts. This replaces an earlier design (a single spec-lifetime-global,\n * never-cleared `Set` of dedup keys) that caused a title-colliding test's\n * data to be silently dropped forever, independent of retries — found via\n * a deep adversarial self-review, never by any automated test (this file\n * previously had zero unit coverage).\n *\n * Each entry also retains the `Mocha.Test` reference it was recorded\n * against, not just its attempts — needed so `drainOrphaned` can hand back\n * something `queue.ts`'s `flushCase` can actually use, for entries whose\n * own test will never get a normal `afterEach` (see that method's doc\n * comment for why immediate flushing from other call sites was tried and\n * found unsafe).\n */\nexport class MochaAttemptTracker {\n private entries = new Map<string, AttemptTrackerEntry>();\n\n private entryFor(key: string, test: Mocha.Test): AttemptTrackerEntry {\n let entry = this.entries.get(key);\n if (!entry) {\n entry = { test, attempts: [], willRetry: false };\n this.entries.set(key, entry);\n }\n return entry;\n }\n\n /** Records one attempt, deduped against only the immediately-preceding\n * record for THIS test (never against any other test's history). More\n * than one Mocha/Cypress event can legitimately fire for the same\n * physical attempt (verified empirically: `runner.on('fail', ...)` AND\n * `runner.on('retry', ...)` both fire for one failing-and-retried\n * attempt) — a second call with the same `retryIndex` for the same key\n * is a no-op rather than double-recording. */\n record(key: string, retryIdx: number, test: Mocha.Test, snapshot: AttemptSnapshot): void {\n const entry = this.entryFor(key, test);\n if (entry.lastRecordedRetryIndex === retryIdx) {\n return;\n }\n entry.lastRecordedRetryIndex = retryIdx;\n entry.attempts.push(snapshot);\n }\n\n /** Marks this test's most-recently-recorded attempt as non-final — more\n * attempts are coming, so `takeIfFinal`/`drainOrphaned` must not flush\n * yet. Only meaningful immediately after a `record()` call for the same\n * attempt (Cypress's retry mechanism always records the failing attempt\n * before emitting `'retry'`). A no-op if no entry exists yet for `key`. */\n markWillRetry(key: string): void {\n const entry = this.entries.get(key);\n if (entry) {\n entry.willRetry = true;\n }\n }\n\n /** The `afterEach`-driven path for the CURRENTLY-ending test: returns its\n * attempts and forgets them — UNLESS `markWillRetry` was called for the\n * attempt just recorded, in which case this consumes that flag and\n * returns `undefined`, leaving the entry in place so the next attempt\n * appends to the same array instead of starting fresh. Returns\n * `undefined` (nothing to do) if no entry exists for `key` at all. */\n takeIfFinal(key: string): AttemptSnapshot[] | undefined {\n const entry = this.entries.get(key);\n if (!entry) {\n return undefined;\n }\n if (entry.willRetry) {\n entry.willRetry = false;\n return undefined;\n }\n this.entries.delete(key);\n return entry.attempts;\n }\n\n /**\n * Sweeps every OTHER finalized-but-never-collected entry (excluding\n * `excludeKey`, the test this `afterEach` firing is already handling via\n * `takeIfFinal`), skipping anything still mid-retry. This exists for\n * exactly one real scenario: a statically-skipped test (`it.skip(...)` or\n * an inherited `.skip`) fires `'pending'` and gets `record()`ed, but\n * Mocha's skip path never runs `afterEach` for it at all — so nothing\n * else will ever collect it.\n *\n * The obvious-looking alternative — flush a skipped test immediately,\n * right in the `'pending'` handler — was tried and is UNSAFE: verified\n * empirically (a real `cypress run` against a fixture spec containing\n * `it.skip(...)`) that calling `cy.task()` synchronously from that\n * handler doesn't just silently no-op, it HANGS the entire run\n * indefinitely (Cypress's command-queue machinery for a test whose body\n * never executes at all appears to never reach a state where a\n * newly-enqueued command can be processed). `drainOrphaned` instead waits\n * until the NEXT real `afterEach` fires (a call site independently\n * proven safe for `cy.task()`, both here and by every other flush in this\n * file) and sweeps anything left over at that point.\n *\n * Residual, honestly-documented limitation: if EVERY test in a spec file\n * is statically skipped, no real `afterEach` ever fires at all in that\n * spec, so a lingering skip entry is never swept — the same outcome as\n * before this fix, for that one narrower sub-case. The common case (at\n * least one non-skipped test in the spec) is fully fixed.\n */\n drainOrphaned(excludeKey: string): Array<{ test: Mocha.Test; attempts: AttemptSnapshot[] }> {\n const drained: Array<{ test: Mocha.Test; attempts: AttemptSnapshot[] }> = [];\n for (const [key, entry] of this.entries) {\n if (key === excludeKey || entry.willRetry) {\n continue;\n }\n drained.push({ test: entry.test, attempts: entry.attempts });\n this.entries.delete(key);\n }\n return drained;\n }\n}\n\n/**\n * Wires the browser-side Mocha listener: accumulates one `AttemptSnapshot`\n * per test-attempt (Cypress's built-in retry re-runs the same `Test`\n * object in place, firing one 'pass'/'fail'/'retry'/'pending' per attempt —\n * not per logical test) into a `MochaAttemptTracker`, and flushes each\n * test's collapsed result to the Node side once its outcome is final.\n *\n * CRITICAL, verified-by-running-real-Cypress detail: Mocha's `afterEach`\n * fires after EVERY physical attempt of a retried test — including ones\n * that will be retried again — not just once after retries are exhausted.\n * The fix is `runner.on('retry', ...)`: Mocha fires this (not 'fail')\n * specifically for an attempt that has retries remaining — 'fail' is\n * reserved for a truly final failure. `MochaAttemptTracker.markWillRetry`\n * tracks which test is mid-retry so `takeIfFinal` (called from the root\n * `afterEach` below) can skip flushing for that attempt and wait for the\n * actual final one.\n *\n * Also verified directly against Cypress's bundled runner source\n * (`Runner.prototype.hook`'s retry-emission block): `runner.on('retry', ...)`\n * can fire for an attempt that actually PASSED, under Cypress's\n * experimental flake-detection retry strategies (`test.hasAttemptPassed`) —\n * in that case `err` is guaranteed falsy (the source's own comment: \"we can\n * assume the test attempt failed as 'err' would have to be present here\"\n * otherwise), so the handler below records `'passed'`, not `'failed'`, when\n * `err` is absent. This package doesn't advertise/configure that\n * experimental strategy today, so this is defensive, not exercised by any\n * current caller.\n *\n * Every flush in this file (`flushCase`, which calls `cy.task()`) happens\n * ONLY from the root-level `afterEach` below — verified the hard way (a\n * real hung `cypress run`) that calling it directly from other Mocha\n * runner event handlers (`'pending'`, a hook-failure `'fail'`) is not\n * reliably safe. Both `'pending'` and the hook-failure branch below only\n * `record()`; `afterEach` is what actually collects and uploads.\n *\n * A root-level `afterEach`, registered here at support-file load time,\n * flushes each test's accumulated attempts once confirmed final — Mocha's\n * innermost-first `afterEach` ordering guarantees this also runs after any\n * `afterEach` the spec itself authored.\n */\nexport function registerMochaListener(): void {\n const runner = (Cypress as unknown as CypressWithMocha).mocha.getRunner();\n const tracker = new MochaAttemptTracker();\n\n // One buffer, shared across the whole spec file's lifetime; reset per\n // attempt (below) and drained into each AttemptSnapshot at record() time —\n // see command-log-listener.ts for why steps are attempt-scoped, not\n // test-scoped (an abandoned/retried attempt's steps must never survive\n // into the next attempt's buffer).\n const stepBuffer = new CommandLogBuffer();\n registerCommandLogListener(stepBuffer);\n\n function buildSnapshot(test: Mocha.Test, status: CaseStatus, err?: unknown): AttemptSnapshot {\n const steps = stepBuffer.drain();\n const metadata = getDefaultMetadataBuffer().drain();\n return {\n status,\n duration: test.duration ?? 0,\n error: formatError(err),\n steps: steps.length > 0 ? steps : undefined,\n ...metadata,\n };\n }\n\n // Fires once per ATTEMPT (Cypress's retry mechanism re-runs the same Test\n // object in place, so this fires again on each retry) — resets both the\n // command-log step buffer AND the qualflare.* metadata buffer, so a fresh\n // attempt starts with empty state rather than inheriting entries from\n // whatever attempt (if any) just finished. The shared metadata buffer also\n // becomes \"active\" here (see `test-metadata-buffer.ts`) — a qualflare.*\n // call made before the first 'test' event of a spec (e.g. module-load\n // time, or inside a `before()` hook) correctly warns-and-no-ops instead of\n // silently writing into a buffer nothing has activated yet.\n runner.on('test', () => {\n stepBuffer.reset();\n getDefaultMetadataBuffer().reset();\n });\n\n runner.on('pass', (test) => {\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'passed'));\n });\n\n runner.on('fail', (test, err) => {\n const runnable = test as unknown as HookRunnable;\n if (runnable.type === 'test') {\n // Reached for a truly final failure (no retries remaining) OR —\n // verified empirically — sometimes ALSO for an attempt that 'retry'\n // below just handled; `tracker.record`'s per-attempt dedup covers\n // both cases.\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'failed', err));\n return;\n }\n\n // A Hook-typed failure. Only a `beforeEach` hook failure is handled\n // here: it fires BEFORE the guarded test's own body has run, so\n // nothing else will ever record that test's result — without this, the\n // test simply vanishes from the report (found via deep self-review,\n // verified against Cypress's actual runner source — see the\n // `HookRunnable` doc comment above for exactly how `ctx.currentTest`\n // and the `'\"before each\" hook'` title prefix were confirmed).\n // `before`/`afterEach`/`after` hook failures are deliberately NOT\n // synthesized here: `before` guards a whole suite, not one identifiable\n // test, and by the time an `afterEach`/`after` hook fails, the guarded\n // test's own pass/fail/pending has already been recorded normally —\n // re-recording it here would incorrectly overwrite a real result with\n // an unrelated hook-cleanup failure. This is a documented, deliberate\n // scope limitation, not an oversight.\n //\n // Deliberately only `record()`s here — does NOT call `flushCase`\n // directly (see the file header comment on why). Mocha's own\n // hook-failure handling still runs the suite's `afterEach` hooks\n // (\"jumps to corresponding after each hook\" — confirmed both in\n // Cypress's runner source comments and by observing this codebase's\n // own root-level `afterEach` correctly receiving\n // `this.currentTest === guardedTest` in a real run), so the normal\n // `afterEach` flush below picks this up via `takeIfFinal` exactly like\n // any other final attempt.\n const guardedTest = runnable.ctx?.currentTest;\n const isBeforeEachHook = (runnable.originalTitle ?? runnable.title ?? '').startsWith('\"before each\" hook');\n if (guardedTest && isBeforeEachHook) {\n tracker.record(guardedTest.fullTitle(), retryIndex(guardedTest), guardedTest, buildSnapshot(guardedTest, 'failed', err));\n }\n });\n\n // Fires for an attempt that has retries remaining — NOT necessarily a\n // failure (see the file header comment re: experimental flake-detection\n // strategies re-running a PASSED attempt). Still record its data\n // (steps/error/duration all feed `collapseAttempts`), and mark it so the\n // afterEach below skips flushing this attempt and waits for the actual\n // final one.\n runner.on('retry', (test: Mocha.Test, err: unknown) => {\n const status: CaseStatus = err ? 'failed' : 'passed';\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, status, err));\n tracker.markWillRetry(test.fullTitle());\n });\n\n runner.on('pending', (test) => {\n // A pending/skipped test never retries, so its outcome is always final\n // the moment 'pending' fires — but do NOT flush here (see the file\n // header comment: calling cy.task() from this handler was found, via a\n // real hung cypress run, to hang the whole process for a statically\n // skipped test). Just record; `drainOrphaned` (called from the next\n // real afterEach) sweeps this up from a call site proven safe.\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'skipped'));\n });\n\n // Fallback for failures that don't reach the Runner's own 'fail'/'retry'\n // events at all (e.g. some uncaught-exception paths) — `tracker.record`'s\n // dedup guard means this never double-counts an attempt already captured\n // through the normal path above.\n Cypress.on('fail', (err, runnable) => {\n // `Mocha.Runnable` (the base class) doesn't declare `type` — only its\n // `Test`/`Hook` subclasses do — so this is checked via a loose cast\n // rather than the type system, matching runner.on('fail')'s own\n // runtime-vs-declared-type mismatch above.\n if ((runnable as unknown as { type?: string }).type !== 'test') {\n throw err;\n }\n const test = runnable as Mocha.Test;\n tracker.record(test.fullTitle(), retryIndex(test), test, buildSnapshot(test, 'failed', err));\n throw err;\n });\n\n afterEach(function flushCurrentTest(this: Mocha.Context) {\n const test = this.currentTest;\n if (!test) {\n return;\n }\n const key = test.fullTitle();\n const attempts = tracker.takeIfFinal(key);\n if (attempts) {\n flushCase(test, attempts);\n }\n // Sweep any orphaned entries (statically-skipped tests, whose own\n // afterEach never runs) now that we know cy.task() is safe — we're\n // inside a real afterEach for a real test.\n for (const orphan of tracker.drainOrphaned(key)) {\n flushCase(orphan.test, orphan.attempts);\n }\n });\n}\n","import { TASK_MARK_TEST_PHASE_STARTED } from '../shared/constants.js';\n\n/**\n * Registers a one-shot, root-level `beforeEach` that tells the Node side\n * \"the first test of this spec is about to run\" — see\n * `src/plugin/state.ts`'s `TestPhaseGate` for the full rationale (letting\n * `events.ts` distinguish a screenshot taken in a root `before()` hook,\n * which cannot be attributed to any test, from one taken during a real\n * test's own execution).\n *\n * Deliberately a real, global `beforeEach()` — NOT a raw\n * `Cypress.mocha.getRunner().on('test', ...)` listener like\n * `mocha-listener.ts` uses for its own bookkeeping. A `beforeEach()`\n * function runs as part of Cypress's normal command-queue processing for\n * that test (the same mechanism `queue.ts`'s already-proven-safe\n * `flushCase`, called from a real `afterEach()`, relies on) — calling\n * `cy.task()` from within it is exactly as safe as calling it from a test\n * body itself. This is a DIFFERENT, safer call site than the raw Mocha\n * runner-event listeners Tier 1 of this remediation effort found (by\n * actually running a real `cypress run`) could hang the whole process.\n *\n * Runs once per spec file (module-eval time registers this one\n * `beforeEach`), and only actually sends the task on the FIRST test's\n * `beforeEach` firing — Mocha runs root-level `beforeEach` hooks\n * outermost-first, so this is guaranteed to run after every applicable\n * `before()` hook (root or nested) has already completed for that test,\n * which is exactly the \"before-hook phase is over\" signal needed. Every\n * subsequent test's `beforeEach` firing is a no-op (the gate only needs to\n * flip once per spec).\n */\nexport function registerTestPhaseSignal(): void {\n let signaled = false;\n beforeEach(function qualflareMarkTestPhaseStarted() {\n if (signaled) {\n return;\n }\n signaled = true;\n cy.task(TASK_MARK_TEST_PHASE_STARTED, null, { log: false });\n });\n}\n","import { initializeBrowserIntegration } from './browser-integration-guard.js';\nimport { registerMochaListener } from './mocha-listener.js';\nimport { registerTestPhaseSignal } from './test-phase-signal.js';\n\n// See browser-integration-guard.ts for the full rationale: this guards\n// against registerMochaListener()/registerTestPhaseSignal() double-firing\n// when a spec file also imports '@qualflare/cypress' directly (Cypress\n// evaluates the support file and each spec file as separate webpack\n// bundles). Deliberately thin/untested, like registerMochaListener itself —\n// it unconditionally needs a real Cypress page to do anything meaningful,\n// so it can't be imported under a plain Node/Vitest environment; the actual\n// guard logic lives in browser-integration-guard.ts, which can be.\ninitializeBrowserIntegration(() => {\n registerMochaListener();\n registerTestPhaseSignal();\n});\n","import type { CasePriority, LinkType } from '../shared/types.js';\nimport { getDefaultMetadataBuffer } from './test-metadata-buffer.js';\n\n/**\n * The author-facing metadata API, imported by test code as:\n *\n * ```ts\n * import { qualflare } from '@qualflare/cypress';\n *\n * it('logs in', () => {\n * qualflare.label('epic', 'Authentication');\n * qualflare.step('fill in credentials', () => {\n * cy.get('#user').type('a@example.com');\n * cy.get('#pass').type('secret');\n * });\n * });\n * ```\n *\n * `label`/`link`/`tag`/`description`/`priority`/`parameter`/`attachment`/\n * `attachmentFromFile` are PLAIN SYNCHRONOUS FUNCTIONS, not Cypress\n * commands — safe because Mocha runs a test's function body synchronously\n * to completion before any of ITS queued `cy.*()` commands actually\n * execute, so exactly one test is ever \"current\" (tracked by the shared\n * metadata buffer's — see `test-metadata-buffer.ts` — active/reset/drain\n * lifecycle, driven by `mocha-listener.ts`) regardless of where these are textually written\n * relative to `cy.*()` calls in the same test body. They append directly\n * into the buffer and return immediately.\n *\n * `step()` is different and MUST interleave into the actual Cypress command\n * queue — see its own doc comment below.\n */\nexport const qualflare = {\n label(name: string, value: string): void {\n getDefaultMetadataBuffer().label(name, value);\n },\n\n link(url: string, opts?: { type?: LinkType; name?: string }): void {\n getDefaultMetadataBuffer().link(url, opts);\n },\n\n tag(...tags: string[]): void {\n getDefaultMetadataBuffer().tag(...tags);\n },\n\n description(text: string): void {\n getDefaultMetadataBuffer().description(text);\n },\n\n priority(value: CasePriority): void {\n getDefaultMetadataBuffer().priority(value);\n },\n\n parameter(name: string, value?: string, opts?: { masked?: boolean }): void {\n getDefaultMetadataBuffer().parameter(name, value, opts);\n },\n\n attachment(name: string, content: string, opts?: { encoding?: 'utf8' | 'base64'; mimeType?: string }): void {\n getDefaultMetadataBuffer().attachment(name, content, opts);\n },\n\n attachmentFromFile(name: string, path: string, opts?: { mimeType?: string }): void {\n getDefaultMetadataBuffer().attachmentFromFile(name, path, opts);\n },\n\n /**\n * Wraps `fn` as a named, reportable step. Unlike every other function on\n * this object, a step's start/end only has meaning relative to when its\n * wrapped `cy.*()` commands actually EXECUTE — not when `step()` is\n * textually called, which happens synchronously, before any queued\n * command has run. So this interleaves `beginStep`/`endStep` INTO the\n * command queue itself via `cy.then()`, at the exact point the wrapped\n * commands run, rather than calling them eagerly.\n *\n * Verified directly against `node_modules/cypress/types/cypress.d.ts`\n * (Cypress 14.5.4) before relying on any of this, rather than assumed:\n * - `Cypress.isCy(obj: any): obj is Chainable` exists exactly as the\n * plan's sketch expected.\n * - `cy.wrap<S>(object: S, options?: Partial<Loggable & Timeoutable>)`\n * accepts `{ log: false }` — this is properly typed.\n * - `cy.then<S>(options: Partial<Timeoutable>, fn): ...` does NOT accept\n * `Loggable` in its options type (only `wrap()` does) — passing\n * `{ log: false }` to `.then()` is a genuine type error under this\n * version's declarations. Cypress's own runtime DOES honor `log: false`\n * on `.then()` in practice (a long-documented, widely-relied-upon\n * behavior across the Cypress plugin ecosystem — every reporter that\n * injects bookkeeping commands into the queue uses this), so `.then()`\n * calls below pass it via a narrow, explicitly-commented type\n * assertion rather than omitting it and cluttering every test's\n * Command Log with reporter-internal entries.\n */\n step<T = void>(name: string, fn: () => T | Cypress.Chainable<T>): Cypress.Chainable<T> {\n // `beginStep()` runs SYNCHRONOUSLY, immediately — not deferred into a\n // queued `cy.then()` as an earlier version of this function did. Verified\n // wrong by running a real Cypress spec (see `test/integration/`): `fn()`\n // below executes synchronously right after this call, but a `cy.then()`\n // callback only runs later, once the Cypress command queue reaches it —\n // so any `qualflare.parameter()`/nested `qualflare.step()` call made\n // directly in `fn()`'s synchronous body would run BEFORE the deferred\n // `beginStep()` ever pushed onto the nesting stack, and would incorrectly\n // see \"no step is open.\" Pushing synchronously here means every call\n // made during `fn()`'s own synchronous execution sees the correct,\n // currently-open step — the tradeoff is that `startedAt` (used for this\n // step's duration) reflects when `step()` was JS-called, not the actual\n // Cypress-queue moment its first wrapped command executes; a documented\n // approximation, consistent with the auto-captured command-log steps'\n // own timing caveat (see `command-log-listener.ts`).\n const stepIndex = getDefaultMetadataBuffer().beginStep(name);\n\n // `fn()` itself can throw SYNCHRONOUSLY (a plain `throw` in the step\n // body, not a failing `cy.*()` command — those fail asynchronously,\n // later in the queue, and are unaffected by this catch). Without this,\n // `endStep()` would never run for this step (only reachable via the\n // `.then()` success path below), leaving it `'pending'` forever with no\n // captured error — found via deep adversarial self-review. Record the\n // failure, then re-throw unchanged: the test itself must still fail\n // normally, this only makes sure the step record reflects what actually\n // happened before that propagates.\n let result: T | Cypress.Chainable<T>;\n try {\n result = fn();\n } catch (err) {\n getDefaultMetadataBuffer().endStep(stepIndex, 'failed', formatSyncStepError(err));\n throw err;\n }\n\n // `Cypress.isCy(obj: any): obj is Chainable` narrows to the untyped\n // generic `Chainable`, not `Chainable<T>` — TS can't prove a runtime\n // check on an `any`-typed parameter preserves a specific type argument,\n // so the branch still needs an explicit assertion back to `Chainable<T>`\n // (safe: `fn`'s own declared return type guarantees this at the call site).\n const chained: Cypress.Chainable<T> = Cypress.isCy(result)\n ? (result as Cypress.Chainable<T>)\n : cy.wrap(result as T, { log: false });\n\n return chained.then(withoutLog(), (value: T) => {\n getDefaultMetadataBuffer().endStep(stepIndex, 'passed');\n return value;\n });\n },\n};\n\n/** Mirrors `mocha-listener.ts`'s `formatError` — kept as a small local copy\n * rather than a shared import, since this file is deliberately independent\n * of that one (see its own module boundary reasoning elsewhere in this\n * codebase). */\nfunction formatSyncStepError(err: unknown): string {\n if (err instanceof Error) {\n return err.stack ? `${err.message}\\n${err.stack}` : err.message;\n }\n return String(err);\n}\n\n/**\n * `{ log: false }`, typed as `Partial<Cypress.Timeoutable>` to satisfy\n * `.then()`'s declared signature — see the long comment on `step()` above\n * for why this is a deliberate, verified-safe type assertion rather than an\n * oversight. Factored into one helper so the justification lives in exactly\n * one place instead of being repeated at each `.then()` call site.\n */\nfunction withoutLog(): Partial<Cypress.Timeoutable> {\n return { log: false } as Partial<Cypress.Timeoutable>;\n}\n"],"mappings":"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","names":[]}
|
package/dist/plugin/index.cjs
CHANGED