@qualflare/cucumberjs 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/formatter/index.cjs +38 -1
- package/dist/formatter/index.cjs.map +1 -1
- package/dist/formatter/index.js +38 -1
- package/dist/formatter/index.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/formatter/index.ts","../../src/formatter/formatter.ts","../../src/config/resolve-config.ts","../../src/shared/constants.ts","../../src/config/ci-detect.ts","../../src/config/git-detect.ts","../../src/shared/logger.ts","../../src/formatter/attachment-budget.ts","../../src/formatter/video-writer.ts","../../src/formatter/attempt-tracker.ts","../../src/shared/duration.ts","../../src/formatter/case-builder.ts","../../src/formatter/step-mapper.ts","../../src/formatter/collect-builder.ts","../../src/config/version.ts","../../src/formatter/gherkin-index.ts","../../src/formatter/hook-index.ts","../../src/formatter/run-hook-tracker.ts","../../src/formatter/suite-builder.ts"],"sourcesContent":["export { default } from './formatter.js';\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { Formatter, type IFormatterOptions } from '@cucumber/cucumber';\nimport type { Envelope, GherkinDocument, Pickle, TestCase } from '@cucumber/messages';\n\nimport { resolveConfig, type QualflareCucumberOptions, type ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport { logger } from '../shared/logger.js';\nimport { AttachmentBudget } from './attachment-budget.js';\nimport { AttemptTracker } from './attempt-tracker.js';\nimport { buildCase, type FinishedCase } from './case-builder.js';\nimport { buildCollectPayload } from './collect-builder.js';\nimport { GherkinIndex } from './gherkin-index.js';\nimport { buildHookIndex, type HookIndex } from './hook-index.js';\nimport { RunHookTracker } from './run-hook-tracker.js';\nimport { groupIntoSuites } from './suite-builder.js';\n\nexport default class QualflareCucumberFormatter extends Formatter {\n private readonly config: ResolvedFormatterConfig;\n private readonly gherkin = new GherkinIndex();\n private readonly hookIndex: HookIndex;\n private readonly pickleIndex = new Map<string, Pickle>();\n private readonly testCaseIndex = new Map<string, TestCase>();\n private readonly attachmentBudget: AttachmentBudget;\n private readonly attemptTracker: AttemptTracker;\n private readonly runHookTracker = new RunHookTracker();\n private readonly finishedCases: FinishedCase[] = [];\n /** One promise per `testCaseFinished` envelope, resolving once that\n * scenario's `AttemptTracker.finish()` (which itself awaits any pending\n * video uploads — see its doc comment) has settled and, if it produced a\n * result, been pushed into `finishedCases`. `finished()` awaits all of\n * these before building/uploading the Collect payload, so a scenario\n * whose only attachment is a still-uploading video is never silently\n * dropped from the report. */\n private readonly pendingCaseBuilds: Promise<void>[] = [];\n\n constructor(options: IFormatterOptions) {\n super(options);\n // cucumber-js's `formatOptions` is untyped (`FormatOptions` has only an\n // index signature) — the shape is a contract between the user's config\n // and this formatter, not something cucumber-js itself validates.\n this.config = resolveConfig(options.parsedArgvOptions as QualflareCucumberOptions);\n this.hookIndex = buildHookIndex(options.supportCodeLibrary);\n this.attachmentBudget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);\n this.attemptTracker = new AttemptTracker(\n this.hookIndex,\n this.gherkin,\n this.config,\n this.attachmentBudget,\n );\n\n if (!this.config.enabled) {\n return;\n }\n options.eventBroadcaster.on('envelope', (envelope: Envelope) => this.onEnvelope(envelope));\n }\n\n private onEnvelope(envelope: Envelope): void {\n try {\n this.dispatch(envelope);\n } catch (err) {\n logger.error('failed to process a cucumber-js event:', err);\n }\n }\n\n private dispatch(envelope: Envelope): void {\n if (envelope.gherkinDocument) {\n this.onGherkinDocument(envelope.gherkinDocument);\n return;\n }\n if (envelope.pickle) {\n this.pickleIndex.set(envelope.pickle.id, envelope.pickle);\n return;\n }\n if (envelope.testCase) {\n this.testCaseIndex.set(envelope.testCase.id, envelope.testCase);\n return;\n }\n if (envelope.testCaseStarted) {\n const testCase = this.testCaseIndex.get(envelope.testCaseStarted.testCaseId);\n const pickle = testCase ? this.pickleIndex.get(testCase.pickleId) : undefined;\n if (testCase && pickle) {\n this.attemptTracker.begin(envelope.testCaseStarted, testCase, pickle);\n } else {\n // Should never happen under cucumber-js's documented message\n // ordering (testCase/pickle always precede testCaseStarted) — this\n // scenario attempt would otherwise be silently dropped from the\n // report with no signal at all, so warn rather than swallow it.\n logger.warn(\n `could not resolve testCase/pickle for testCaseStarted \"${envelope.testCaseStarted.id}\" — this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n if (envelope.testStepStarted) {\n this.attemptTracker.stepStarted(envelope.testStepStarted);\n return;\n }\n if (envelope.testStepFinished) {\n this.attemptTracker.stepFinished(envelope.testStepFinished);\n return;\n }\n if (envelope.attachment) {\n this.attemptTracker.attachment(envelope.attachment);\n return;\n }\n if (envelope.testCaseFinished) {\n // finish() is async (it awaits any pending video upload for this\n // scenario before its attachments can be read — see its doc comment),\n // but dispatch() itself stays synchronous: cucumber-js's envelope\n // stream doesn't wait for one 'envelope' listener's returned promise\n // before emitting the next, so blocking here would just desync this\n // handler from the events actually arriving. Instead, track the\n // promise and await every one of them in finished(), before the\n // Collect payload is ever built.\n const pending = this.attemptTracker\n .finish(envelope.testCaseFinished)\n .then((finished) => {\n if (!finished) {\n return;\n }\n const pickle = this.pickleIndex.get(finished.pickleId);\n if (pickle) {\n this.finishedCases.push(buildCase(finished.uri, pickle, finished.collapsed, this.gherkin));\n } else {\n logger.warn(`could not resolve pickle \"${finished.pickleId}\" for a finished scenario — it will not be uploaded.`);\n }\n })\n .catch((err) => {\n // Mirrors onEnvelope's own catch — dispatch() itself can no longer\n // catch an error raised inside this deferred chain.\n logger.error('failed to process a cucumber-js event:', err);\n });\n this.pendingCaseBuilds.push(pending);\n return;\n }\n if (envelope.testRunHookStarted) {\n this.runHookTracker.start(envelope.testRunHookStarted);\n return;\n }\n if (envelope.testRunHookFinished) {\n this.runHookTracker.finish(envelope.testRunHookFinished, this.hookIndex);\n return;\n }\n }\n\n private onGherkinDocument(doc: GherkinDocument): void {\n this.gherkin.add(doc);\n }\n\n async finished(): Promise<void> {\n try {\n // Every scenario's Case must be fully built (attachments included,\n // any pending video write settled) before the Collect payload is\n // assembled — see the testCaseFinished dispatch branch above.\n await Promise.all(this.pendingCaseBuilds);\n if (this.config.enabled) {\n this.writeResults();\n }\n } finally {\n await super.finished();\n }\n }\n\n /** Writes this process's Collect payload into `outputDir` under a unique\n * filename. Never uploads: `qualflare-cli collect <outputDir>` does that,\n * merging every file it finds there into one Launch. Multiple shards can\n * therefore share one directory safely — the UUID filename is what keeps\n * them from overwriting each other. */\n private writeResults(): void {\n const suites = groupIntoSuites(this.finishedCases, this.cwd, this.runHookTracker.buildSuite());\n if (suites.length === 0) {\n if (this.config.debug) {\n logger.debug('no scenarios reported — skipping file write.');\n }\n return;\n }\n const payload = buildCollectPayload(suites, this.config);\n if (this.config.shardIndex !== undefined) {\n for (const suite of payload.suites) {\n for (const c of suite.cases) {\n c.shardIndex = this.config.shardIndex;\n }\n }\n }\n\n fs.mkdirSync(this.config.outputDir, { recursive: true });\n const outputPath = path.join(this.config.outputDir, `${randomUUID()}.json`);\n fs.writeFileSync(outputPath, JSON.stringify(payload));\n logger.info(\n `wrote Collect payload to ${outputPath} — run \\`qualflare-cli collect ${this.config.outputDir}\\` to upload it.`,\n );\n }\n}\n","import { randomUUID } from 'node:crypto';\n\nimport { MAX_VIDEO_UPLOAD_BYTES } from '../shared/constants.js';\nimport type { Platform } from '../shared/types.js';\nimport { detectCi, type CiMetadata } from './ci-detect.js';\nimport { detectGit, type GitInfo } from './git-detect.js';\n\n/** Options passed via the `--format-options`/`formatOptions` value the user\n * configures for `@qualflare/cucumberjs/formatter` (e.g. in `cucumber.js` /\n * `cucumber.json`). Every field here also has an environment-variable\n * override — see the precedence table in the README / plan. */\nexport interface QualflareCucumberOptions {\n environment?: string;\n language?: string;\n milestone?: number | null;\n branch?: string | null;\n commit?: string | null;\n platform?: Platform;\n framework?: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n /** Max 64 chars. Free text, no enum — an unrecognized CI provider must\n * never be rejected. Auto-detected via `ci-detect.ts` when omitted. */\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n /** Identifier shared by every shard of one run, written into the report as\n * `metadata.runId`. `qualflare-cli collect` groups files by it and refuses\n * to merge a stale report from an earlier run into this launch.\n *\n * Auto-detected from CI. Outside CI it falls back to a per-process UUID,\n * which is correct there: every local run is a distinct run, so a leftover\n * file is still caught. */\n runId?: string;\n attachScreenshots?: boolean;\n /** Include `BeforeStep`/`AfterStep` hook executions as synthetic steps.\n * Off by default — these run once per Gherkin step and can multiply the\n * step count several-fold for suites with global per-step instrumentation\n * hooks (e.g. a screenshot-after-every-step hook), which is noisy as a\n * default but valuable as an explicit opt-in. */\n includeStepHooks?: boolean;\n maxAttachmentBytes?: number;\n maxTotalAttachmentBytes?: number;\n /** Per-video byte cap, checked before the file is written. Default 50MB,\n * matching the server's own hard cap. */\n maxVideoBytes?: number;\n debug?: boolean;\n /** `false` fully disables accumulation/upload (a complete no-op) but the\n * formatter still no-ops cleanly rather than throwing. */\n enabled?: boolean;\n /** Directory `finished()` writes this process's report file (and any\n * video attachments) into. Default `./qualflare-results`. Always active —\n * this formatter never uploads anything itself; `qualflare-cli` reads\n * whatever ends up in this directory. Every JSON file this process writes\n * is uniquely named, so multiple shards can safely share one `outputDir`\n * without colliding — see docs/LIMITATIONS.md. */\n outputDir?: string;\n /** This process's 0-based position among parallel shards of the same CI\n * run, stamped onto every case it reports. Purely a label: `qualflare-cli`\n * merges by \"every file in the directory\", not by this value, so an\n * unset shardIndex costs attribution, never correctness.\n *\n * Auto-detected, in order: `QUALFLARE_SHARD_INDEX`, then a best-effort\n * scan of `process.argv` for cucumber-js's own `--shard INDEX/TOTAL`\n * (whose index is 1-based, so it is converted). cucumber-js routes that\n * flag to `configuration.sources.shard`, and a formatter is only ever\n * handed `configuration.options` — so argv is the only place a formatter\n * can observe it, and only when it was passed on the command line rather\n * than via a config file. */\n shardIndex?: number;\n}\n\nexport interface ResolvedFormatterConfig {\n environment: string;\n language: string;\n milestone: number | null;\n branch: string | null;\n commit: string | null;\n platform: Platform;\n framework: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n runId: string;\n attachScreenshots: boolean;\n includeStepHooks: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n maxVideoBytes: number;\n debug: boolean;\n enabled: boolean;\n outputDir: string;\n shardIndex?: number;\n}\n\nfunction firstEnv(...names: string[]): string | undefined {\n for (const name of names) {\n const value = process.env[name];\n if (value !== undefined && value !== '') {\n return value;\n }\n }\n return undefined;\n}\n\nfunction envBool(...names: string[]): boolean | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n return raw === 'true' || raw === '1';\n}\n\nfunction envInt(...names: string[]): number | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/** Best-effort read of cucumber-js's own `--shard INDEX/TOTAL` flag from\n * `process.argv`, returned 0-based.\n *\n * cucumber-js does parse this flag, but routes it to\n * `configuration.sources.shard`, while a formatter is only ever handed\n * `configuration.options` (see `api/formatters.js`) — so there is no\n * supported API for a formatter to read it. argv is the one place it is\n * observable, and only when the user passed it on the command line rather\n * than via a `cucumber.js` config file; that is why this sits BELOW\n * `QUALFLARE_SHARD_INDEX` in precedence rather than replacing it.\n *\n * cucumber documents the flag's index as 1-based (\"The index starts at 1\")\n * and normalizes it internally with `parseInt(idx) - 1`; we match that, so\n * `--shard 1/3` is shard 0. A malformed value yields `undefined` rather\n * than a wrong shard label — cucumber validates the same `<n>/<n>` shape\n * and would already have rejected it. */\nfunction argvShardIndex(argv: readonly string[] = process.argv): number | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === undefined) {\n continue;\n }\n const raw = arg === '--shard' ? argv[i + 1] : arg.startsWith('--shard=') ? arg.slice('--shard='.length) : undefined;\n if (raw === undefined) {\n continue;\n }\n if (!/^\\d+\\/\\d+$/.test(raw)) {\n return undefined;\n }\n const oneBased = Number.parseInt(raw.split('/')[0] ?? '', 10);\n return Number.isFinite(oneBased) && oneBased >= 1 ? oneBased - 1 : undefined;\n }\n return undefined;\n}\n\n/** Resolves the full formatter configuration from, in order: the explicit\n * `options` (the formatter's own `formatOptions`), then `QUALFLARE_*`\n * environment variables, then `QF_*` (compat alias with the existing Go\n * CLI, where an equivalent exists), then a hardcoded default.\n *\n * Branch/commit precedence: `options.branch`/`.commit` (including an\n * explicit `null`, which is respected as \"no auto-detection wanted\" rather\n * than triggering the fallback tiers below it) > `QUALFLARE_BRANCH`/\n * `QF_BRANCH` env (and the commit equivalent) > CI-provider env vars > a\n * local `git` subprocess (`git-detect.ts`) > `null`. The subprocess tier is\n * skipped entirely — no `git` process is forked — once both branch and\n * commit are already resolved from options/env, mirroring\n * `qualflare-cli/internal/config/config.go`'s `DetectGit`'s early return.\n *\n * CI-metadata precedence (`ciProvider`/`ciBuildNumber`/`ciRunUrl`/\n * `ciPrNumber`): the corresponding `options.ci*` field, else `ci-detect.ts`'s\n * auto-detection (per-provider extraction table, falling back to the\n * `ci-info` package's ~70-provider free-text name).\n *\n * `deps` lets tests inject fake `detectGit`/`detectCi` implementations\n * instead of the real ones (which shell out to `git` and read the real\n * `process.env`/`ci-info` module state) — defaults to the real detectors,\n * so every production call site (the formatter's constructor calls\n * `resolveConfig(options)` with no second argument) is unaffected.\n */\nexport function resolveConfig(\n options: QualflareCucumberOptions,\n deps: { detectGit?: () => GitInfo; detectCi?: () => CiMetadata } = {},\n): ResolvedFormatterConfig {\n const doDetectGit = deps.detectGit ?? detectGit;\n const doDetectCi = deps.detectCi ?? detectCi;\n\n const enabled = options.enabled ?? envBool('QUALFLARE_ENABLED') ?? true;\n // `||`, not `??` — matching `environment`/`language` below: an explicit\n const outputDir = options.outputDir || firstEnv('QUALFLARE_OUTPUT_DIR') || './qualflare-results';\n const shardIndex = options.shardIndex ?? envInt('QUALFLARE_SHARD_INDEX') ?? argvShardIndex();\n\n const milestoneRaw = options.milestone !== undefined ? options.milestone : envInt('QUALFLARE_MILESTONE', 'QF_MILESTONE');\n const milestone = milestoneRaw !== undefined && milestoneRaw !== null && milestoneRaw >= 1 ? milestoneRaw : null;\n\n const envBranch = firstEnv('QUALFLARE_BRANCH', 'QF_BRANCH');\n const envCommit = firstEnv('QUALFLARE_COMMIT', 'QF_COMMIT');\n const needsGitDetection =\n (options.branch === undefined && envBranch === undefined) ||\n (options.commit === undefined && envCommit === undefined);\n const detectedGit = needsGitDetection ? doDetectGit() : {};\n\n const branch = options.branch !== undefined ? options.branch : (envBranch ?? detectedGit.branch ?? null);\n const commit = options.commit !== undefined ? options.commit : (envCommit ?? detectedGit.commit ?? null);\n\n const detectedCi = doDetectCi();\n const ciProvider = options.ciProvider ?? detectedCi.ciProvider;\n const ciBuildNumber = options.ciBuildNumber ?? detectedCi.ciBuildNumber;\n const ciRunUrl = options.ciRunUrl ?? detectedCi.ciRunUrl;\n const ciPrNumber = options.ciPrNumber ?? detectedCi.ciPrNumber;\n\n // Never empty on purpose: `qf collect` treats a report with no runId as\n // \"unknown run\" and never lets it block a merge, so defaulting to '' would\n // quietly opt local runs out of the very check this exists for.\n const runId = options.runId ?? firstEnv('QUALFLARE_RUN_ID') ?? detectedCi.ciRunId ?? randomUUID();\n\n return {\n // `||` (truthy check), not `??`, for these three REQUIRED-non-empty wire\n // fields — an explicit `''` option must not silently win over the\n // default (the server rejects an empty `environment`). Ported verbatim from\n // qualflare-cypress, where this was found via deep adversarial review.\n environment: (options.environment || undefined) ?? firstEnv('QUALFLARE_ENVIRONMENT', 'QF_ENVIRONMENT') ?? 'development',\n language: (options.language || undefined) ?? firstEnv('QUALFLARE_LANGUAGE', 'QF_LANGUAGE') ?? 'en-US',\n milestone,\n branch,\n commit,\n platform: options.platform ?? 'web',\n framework: options.framework || 'cucumber',\n os: options.os,\n browser: options.browser,\n properties: options.properties,\n ciProvider,\n ciBuildNumber,\n ciRunUrl,\n ciPrNumber,\n runId,\n attachScreenshots: options.attachScreenshots ?? envBool('QUALFLARE_ATTACH_SCREENSHOTS') ?? true,\n includeStepHooks: options.includeStepHooks ?? envBool('QUALFLARE_INCLUDE_STEP_HOOKS') ?? false,\n maxAttachmentBytes: options.maxAttachmentBytes ?? envInt('QUALFLARE_MAX_ATTACHMENT_BYTES') ?? 1_500_000,\n maxTotalAttachmentBytes:\n options.maxTotalAttachmentBytes ?? envInt('QUALFLARE_MAX_TOTAL_ATTACHMENT_BYTES') ?? 750_000,\n maxVideoBytes: options.maxVideoBytes ?? envInt('QUALFLARE_MAX_VIDEO_BYTES') ?? MAX_VIDEO_UPLOAD_BYTES,\n debug: options.debug ?? envBool('QUALFLARE_DEBUG', 'QF_DEBUG') ?? false,\n enabled,\n outputDir,\n shardIndex,\n };\n}\n","/**\n * Shared constants used across the formatter and the author-facing runtime\n * API.\n */\n\n/** Reserved `World.attach()` media type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from step-definition and hook\n * code back to the formatter process — the only data channel CucumberJS\n * gives user code back to a running formatter. The formatter's attachment\n * handler recognizes this exact media type and replays the message as a\n * model mutation instead of rendering it as a literal attachment. */\nexport const RESERVED_MESSAGE_MEDIA_TYPE = 'application/vnd.qualflare.message+json';\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 scenario attempt — well under\n * the server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). Once hit,\n * further steps 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 * as ciInfo from 'ci-info';\n\n/** Detected CI pipeline metadata, matching the wire contract's `ciProvider`/\n * `ciBuildNumber`/`ciRunUrl`/`ciPrNumber` fields exactly (`src/shared/types.ts`). */\nexport interface CiMetadata {\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n /** Identifier every shard of ONE CI run shares, and which differs between\n * runs. Distinct from `ciBuildNumber`: a build number is the human-facing\n * counter (GitHub's run NUMBER repeats across re-runs of a workflow),\n * whereas this is the unique run id. `qualflare-cli collect` groups report\n * files by it to refuse merging a stale file from an earlier run into the\n * current launch. */\n ciRunId?: string;\n}\n\ninterface ProviderExtractor {\n detect: (env: NodeJS.ProcessEnv) => boolean;\n providerName: string;\n buildNumber?: (env: NodeJS.ProcessEnv) => string | undefined;\n runUrl?: (env: NodeJS.ProcessEnv) => string | undefined;\n runId?: (env: NodeJS.ProcessEnv) => string | undefined;\n prNumber?: (env: NodeJS.ProcessEnv) => number | undefined;\n}\n\nfunction parsePositiveInt(raw: string | undefined): number | undefined {\n if (!raw) {\n return undefined;\n }\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : undefined;\n}\n\nfunction nonEmpty(raw: string | undefined): string | undefined {\n return raw && raw.length > 0 ? raw : undefined;\n}\n\n/** Explicit per-provider extraction for the fields `ci-info` doesn't\n * standardize (build number / run URL / PR number). Each `detect` reads\n * directly off the passed-in `env` — deliberately NOT delegating to\n * `ci-info`'s own per-vendor booleans (e.g. `ciInfo.GITHUB_ACTIONS`), which\n * are computed once against the real `process.env` at module-import time\n * and can't be re-evaluated against an injected env — see the module-level\n * comment on `detectCi` below. Checked in order; first match wins. */\nconst PROVIDERS: ProviderExtractor[] = [\n {\n detect: (env) => env.GITHUB_ACTIONS === 'true',\n providerName: 'GitHub Actions',\n buildNumber: (env) => nonEmpty(env.GITHUB_RUN_NUMBER),\n runId: (env) => nonEmpty(env.GITHUB_RUN_ID),\n runUrl: (env) =>\n env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID\n ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`\n : undefined,\n prNumber: (env) => {\n const match = /^refs\\/pull\\/(\\d+)\\/merge$/.exec(env.GITHUB_REF ?? '');\n return match ? parsePositiveInt(match[1]) : undefined;\n },\n },\n {\n detect: (env) => env.GITLAB_CI === 'true',\n providerName: 'GitLab CI',\n buildNumber: (env) => nonEmpty(env.CI_PIPELINE_IID),\n runId: (env) => nonEmpty(env.CI_PIPELINE_ID),\n runUrl: (env) => nonEmpty(env.CI_PIPELINE_URL),\n prNumber: (env) => parsePositiveInt(env.CI_MERGE_REQUEST_IID),\n },\n {\n detect: (env) => env.CIRCLECI === 'true',\n providerName: 'CircleCI',\n buildNumber: (env) => nonEmpty(env.CIRCLE_BUILD_NUM),\n runId: (env) => nonEmpty(env.CIRCLE_WORKFLOW_ID ?? env.CIRCLE_BUILD_NUM),\n runUrl: (env) => nonEmpty(env.CIRCLE_BUILD_URL),\n prNumber: (env) => parsePositiveInt(env.CIRCLE_PR_NUMBER),\n },\n {\n detect: (env) => env.BUILDKITE === 'true',\n providerName: 'Buildkite',\n buildNumber: (env) => nonEmpty(env.BUILDKITE_BUILD_NUMBER),\n runId: (env) => nonEmpty(env.BUILDKITE_BUILD_ID),\n runUrl: (env) => nonEmpty(env.BUILDKITE_BUILD_URL),\n prNumber: (env) => {\n const raw = env.BUILDKITE_PULL_REQUEST;\n if (!raw || raw === 'false') {\n return undefined;\n }\n return parsePositiveInt(raw);\n },\n },\n {\n // Jenkins has no simple `JENKINS=true`-style flag; JENKINS_URL is always\n // set by the Jenkins agent and is the conventional detection signal.\n detect: (env) => Boolean(env.JENKINS_URL),\n providerName: 'Jenkins',\n buildNumber: (env) => nonEmpty(env.BUILD_NUMBER),\n runId: (env) => nonEmpty(env.BUILD_TAG ?? env.BUILD_NUMBER),\n runUrl: (env) => nonEmpty(env.BUILD_URL),\n // Jenkins has no standardized PR-number env var across its many PR\n // plugins (Multibranch, GitHub Branch Source, etc.) — deliberately\n // omitted rather than guessing at a plugin-specific variable.\n },\n {\n detect: (env) => env.TF_BUILD === 'True' || env.TF_BUILD === 'true',\n providerName: 'Azure Pipelines',\n buildNumber: (env) => nonEmpty(env.BUILD_BUILDID),\n runId: (env) => nonEmpty(env.BUILD_BUILDID),\n runUrl: (env) => {\n const collectionUri = env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;\n const project = env.SYSTEM_TEAMPROJECT;\n const buildId = env.BUILD_BUILDID;\n if (!collectionUri || !project || !buildId) {\n return undefined;\n }\n return `${collectionUri.replace(/\\/+$/, '')}/${encodeURIComponent(project)}/_build/results?buildId=${buildId}`;\n },\n prNumber: (env) => parsePositiveInt(env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER),\n },\n {\n detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),\n providerName: 'Bitbucket Pipelines',\n buildNumber: (env) => nonEmpty(env.BITBUCKET_BUILD_NUMBER),\n runId: (env) => nonEmpty(env.BITBUCKET_BUILD_NUMBER),\n runUrl: (env) => {\n const origin = env.BITBUCKET_GIT_HTTP_ORIGIN;\n if (!origin) {\n return undefined;\n }\n const resultsId = env.BITBUCKET_PIPELINE_UUID ?? env.BITBUCKET_BUILD_NUMBER;\n return resultsId ? `${origin}/addon/pipelines/home#!/results/${resultsId}` : undefined;\n },\n prNumber: (env) => parsePositiveInt(env.BITBUCKET_PR_ID),\n },\n];\n\n/**\n * Detects CI pipeline metadata for the `Collect.ciProvider`/`ciBuildNumber`/\n * `ciRunUrl`/`ciPrNumber` fields.\n *\n * IMPORTANT, non-obvious limitation: the `env` parameter only governs the\n * `PROVIDERS` table above (this module's own, directly-env-reading logic).\n * The `ci-info` fallback below does NOT honor it — `ci-info`'s package source\n * computes `exports.name`/`exports.isCI` exactly once, against the REAL\n * `process.env`, at module-import time (`const env = process.env` at its top\n * level) — there is no API to re-evaluate it against a different env object.\n * This is a non-issue in production (this function is always called against\n * the real `process.env` there); `ci-detect.test.ts` exercises the `ci-info`\n * fallback path via `vi.stubEnv` + `vi.resetModules()` (forcing a fresh\n * `ci-info` evaluation) rather than via this function's `env` parameter.\n */\nexport function detectCi(env: NodeJS.ProcessEnv = process.env): CiMetadata {\n const provider = PROVIDERS.find((p) => p.detect(env));\n if (provider) {\n const result: CiMetadata = { ciProvider: provider.providerName };\n const buildNumber = provider.buildNumber?.(env);\n if (buildNumber !== undefined) result.ciBuildNumber = buildNumber;\n const runUrl = provider.runUrl?.(env);\n if (runUrl !== undefined) result.ciRunUrl = runUrl;\n const prNumber = provider.prNumber?.(env);\n if (prNumber !== undefined) result.ciPrNumber = prNumber;\n const runId = provider.runId?.(env);\n if (runId !== undefined) result.ciRunId = runId;\n return result;\n }\n\n // Fallback: ci-info's ~70-provider detection gives us a free-text provider\n // name (the server's `ciProvider` field has no enum — an unrecognized\n // value is always accepted, per its doc comment in shared/types.ts) even\n // for providers our explicit table above doesn't cover a full\n // build-number/run-URL/PR-number extraction for.\n if (ciInfo.name) {\n return { ciProvider: ciInfo.name };\n }\n return {};\n}\n","import { execFileSync } from 'node:child_process';\n\n/** Auto-detected branch/commit — the bottom tier of `resolve-config.ts`'s\n * precedence chain (option > QUALFLARE_BRANCH/QF_BRANCH env > CI env vars >\n * local git subprocess > null). Does NOT include the QUALFLARE_BRANCH/\n * QF_BRANCH-style env aliases — those are resolved at a higher tier,\n * directly in `resolve-config.ts`, before this module is ever consulted. */\nexport interface GitInfo {\n branch?: string;\n commit?: string;\n}\n\n/** Injectable so tests never actually shell out. `stdio: ['ignore', 'pipe',\n * 'ignore']` suppresses git's own stderr chatter (e.g. \"fatal: not a git\n * repository\") from leaking into the CI log for what is, from this plugin's\n * perspective, an entirely expected, non-fatal outcome. */\nexport type ExecGit = (args: string[], cwd: string) => string;\n\nconst defaultExecGit: ExecGit = (args, cwd) =>\n execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n\nfunction firstEnv(env: NodeJS.ProcessEnv, ...names: string[]): string | undefined {\n for (const name of names) {\n const value = env[name];\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n\nfunction detectBranchFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n // Empty on detached HEAD (the `-q` flag suppresses the error and the\n // command still exits 0 with no output in that case on some git\n // versions, hence the explicit empty-string check as well as the\n // try/catch for versions that exit non-zero instead).\n const out = exec(['symbolic-ref', '--short', '-q', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction detectCommitFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n const out = exec(['rev-parse', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Auto-detects branch/commit: CI-provider env vars first (cheap, no\n * subprocess), falling back to a local `git` subprocess only for whichever\n * of branch/commit the env vars didn't resolve — mirroring\n * `qualflare-cli/internal/config/config.go`'s `LoadFromEnv`\n * (`getFirstEnv(\"GIT_BRANCH\", \"GITHUB_REF_NAME\", \"CI_COMMIT_REF_NAME\",\n * \"BITBUCKET_BRANCH\")` / the equivalent commit chain) and its `DetectGit`'s\n * \"only shell out for what's actually missing\" behavior (BUG-39 in that\n * file: forking `git` on every CLI invocation, even `--help`, was wasteful —\n * the same reasoning applies here, this reporter should not fork two `git`\n * processes on every `cucumber-js` run when CI env vars already cover both\n * values, or when the caller already resolved both from options/env at a\n * higher precedence tier and doesn't need this module at all).\n */\nexport function detectGit(\n env: NodeJS.ProcessEnv = process.env,\n cwd: string = process.cwd(),\n exec: ExecGit = defaultExecGit,\n): GitInfo {\n const branch =\n firstEnv(env, 'GIT_BRANCH', 'GITHUB_REF_NAME', 'CI_COMMIT_REF_NAME', 'BITBUCKET_BRANCH') ??\n detectBranchFromGit(exec, cwd);\n const commit =\n firstEnv(env, 'GIT_COMMIT', 'GITHUB_SHA', 'CI_COMMIT_SHA', 'BITBUCKET_COMMIT') ??\n detectCommitFromGit(exec, cwd);\n\n const result: GitInfo = {};\n if (branch !== undefined) result.branch = branch;\n if (commit !== undefined) result.commit = commit;\n return result;\n}\n","/**\n * A minimal logger writing to stderr. Deliberately avoids stdout, since\n * that's typically `cucumber-js`'s own test-output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-cucumberjs]';\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 * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport { writeVideoAttachment } from './video-writer.js';\n\n/** Extensions/mime-prefixes routed through the video-upload flow\n * (`resolveVideoAttachment`) instead of the inline-base64 path below.\n * Broader than the server's own MIME allowlist (`.avi`/`.mkv` included) so\n * this still correctly IDENTIFIES a video attachment even in a format the\n * server can't accept — `resolveVideoAttachment`/`resolveVideoMimeType` is\n * what actually enforces the narrower allowlist and warns/skips a format\n * outside it. */\nconst VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.mov', '.avi', '.mkv']);\n\nexport interface AttachmentBudgetConfig {\n attachScreenshots: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n maxVideoBytes: number;\n outputDir: string;\n}\n\n/** One `World.attach()` call (real user attachment, or `qualflare.attachment\n * ()`/`attachmentFromFile()`), not yet resolved into a wire `Attachment`.\n * Exactly one of `content`/`path` is set — `content` for cucumber-js's\n * native in-memory delivery (a Buffer/base64 string, the common case for\n * real `World.attach()` calls) or `qualflare.attachment()`; `path` only for\n * `qualflare.attachmentFromFile()`, cucumber-js itself never delivers a bare\n * file path. */\nexport interface PendingAttachment {\n name: string;\n mimeType?: string;\n stepIndex?: number;\n /** Base64-encoded. */\n content?: string;\n path?: string;\n}\n\n/**\n * Tracks cumulative attached bytes across the whole `cucumber-js` process\n * (one instance per formatter, reused across every attachment resolved),\n * so the final POST doesn't silently exceed the request body limit. Ported\n * verbatim in spirit from `@qualflare/cypress`'s `AttachmentBudget`.\n */\nexport class AttachmentBudget {\n private used = 0;\n\n constructor(private readonly maxTotalBytes: number) {}\n\n /** Atomically checks-and-reserves `bytes` against the remaining budget.\n * Returns false (reserving nothing) if it would exceed the total. */\n tryReserve(bytes: number): boolean {\n if (this.used + bytes > this.maxTotalBytes) {\n return false;\n }\n this.used += bytes;\n return true;\n }\n\n get usedBytes(): number {\n return this.used;\n }\n}\n\ntype ReadResult = { skipped: false; content: string } | { skipped: true; reason: string };\n\nexport function isVideoLike(mimeType: string | undefined, filePath: string | undefined): boolean {\n if (mimeType?.toLowerCase().startsWith('video/')) {\n return true;\n }\n if (filePath && VIDEO_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {\n return true;\n }\n return false;\n}\n\nfunction readAttachmentFile(filePath: string, maxAttachmentBytes: number, budget: AttachmentBudget): ReadResult {\n let size: number;\n try {\n // Stat BEFORE reading — an oversized file must never be loaded into\n // memory just to discover it should be skipped.\n size = fs.statSync(filePath).size;\n } catch (err) {\n return { skipped: true, reason: `could not stat file: ${(err as Error).message}` };\n }\n if (size > maxAttachmentBytes) {\n return {\n skipped: true,\n reason: `${size} bytes exceeds the configured per-attachment cap of ${maxAttachmentBytes} bytes`,\n };\n }\n if (!budget.tryReserve(size)) {\n return {\n skipped: true,\n reason: `would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n };\n }\n try {\n const content = fs.readFileSync(filePath).toString('base64');\n return { skipped: false, content };\n } catch (err) {\n return { skipped: true, reason: `could not read file: ${(err as Error).message}` };\n }\n}\n\n/**\n * Resolves one NON-video pending attachment into a wire `Attachment`, or\n * `undefined` if it should be skipped entirely (per the plan's resolved\n * decision — an oversized/over-budget attachment is dropped, not degraded\n * to a contentless stub, since the server's `path` field is explicitly\n * informational/never-fetched). Unlike `@qualflare/cypress`'s\n * `resolveAttachments()` (which batch-resolves a Case's whole array at\n * case-finish time), this resolves one attachment at a time as its\n * `attachment` envelope arrives — matching cucumber-js's per-envelope\n * event stream.\n *\n * Callers MUST check `isVideoLike()` first and route a video-like pending\n * attachment to `resolveVideoAttachment()` instead — this function assumes\n * it is not one (see `attempt-tracker.ts`'s call sites).\n */\nexport function resolvePendingAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (!config.attachScreenshots) {\n return undefined;\n }\n if (pending.content !== undefined) {\n const bytes = Buffer.byteLength(pending.content, 'base64');\n if (bytes > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${pending.name}\": ${bytes} bytes exceeds the configured per-attachment cap of ${config.maxAttachmentBytes} bytes`,\n );\n return undefined;\n }\n if (!budget.tryReserve(bytes)) {\n logger.warn(\n `skipping attachment \"${pending.name}\": would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n );\n return undefined;\n }\n return { name: pending.name, mimeType: pending.mimeType, content: pending.content, stepIndex: pending.stepIndex };\n }\n if (pending.path) {\n const result = readAttachmentFile(pending.path, config.maxAttachmentBytes, budget);\n if (result.skipped) {\n logger.warn(`skipping attachment \"${pending.name}\" (${pending.path}): ${result.reason}`);\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: pending.mimeType,\n content: result.content,\n path: pending.path,\n stepIndex: pending.stepIndex,\n };\n }\n return undefined;\n}\n\n/**\n * Resolves one video-like pending attachment (`isVideoLike()` already true)\n * into a wire `Attachment` carrying `storageKey`/`fileSize` instead of\n * `content`, via the presigned-upload-URL flow — or `undefined` if it should\n * be skipped (uploads disabled, unsupported format, oversized, or a\n * network/API error; each case logs why). Async, unlike\n * `resolvePendingAttachment` — see `attempt-tracker.ts`'s\n * `pendingVideoWrites` for how callers reconcile that with cucumber-js's\n * synchronous, per-envelope event stream.\n */\nexport async function resolveVideoAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n): Promise<Attachment | undefined> {\n if (!config.attachScreenshots) {\n return undefined;\n }\n const written = writeVideoAttachment(pending, config.outputDir, config.maxVideoBytes);\n if (!written) {\n // writeVideoAttachment already logged why.\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: written.mimeType,\n localVideoPath: written.localVideoPath,\n fileSize: written.fileSize,\n stepIndex: pending.stepIndex,\n };\n}\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\n\n\n/** Extension <-> MIME type for the video formats the server accepts (see\n * `launch.AllowedAttachmentUploadMimeTypes` server-side). */\nconst VIDEO_MIME_TYPES_BY_EXTENSION: Record<string, string> = {\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.mov': 'video/quicktime',\n};\nconst EXTENSION_BY_VIDEO_MIME_TYPE: Record<string, string> = {\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'video/quicktime': '.mov',\n};\n\nexport interface ResolvedVideoMimeType {\n mimeType: string;\n extension: string;\n}\n\n/**\n * Determines the server-accepted `{mimeType, extension}` pair for a pending\n * video attachment, or `undefined` if neither `filePath`'s extension nor\n * `mimeType` maps to one of the three formats the server allows.\n * `filePath` (a real local file, from `qualflare.attachmentFromFile()`)\n * takes priority when present — its extension is authoritative and cannot\n * disagree with itself the way a caller-supplied `mimeType` claim could.\n * Without a `filePath` (in-memory `World.attach()`/`qualflare.attachment()`\n * content), `mimeType` is the only signal available.\n */\nexport function resolveVideoMimeType(mimeType: string | undefined, filePath: string | undefined): ResolvedVideoMimeType | undefined {\n if (filePath) {\n const extension = path.extname(filePath).toLowerCase();\n const resolvedMimeType = VIDEO_MIME_TYPES_BY_EXTENSION[extension];\n return resolvedMimeType ? { mimeType: resolvedMimeType, extension } : undefined;\n }\n const normalized = mimeType?.toLowerCase();\n const extension = normalized ? EXTENSION_BY_VIDEO_MIME_TYPE[normalized] : undefined;\n return normalized && extension ? { mimeType: normalized, extension } : undefined;\n}\n\nexport interface VideoWriteResult {\n /** Filename relative to the `outputDir` this was written into. */\n localVideoPath: string;\n fileSize: number;\n mimeType: string;\n}\n\n/**\n * Writes one pending video attachment's bytes into `outputDir` under a\n * unique filename — copying (`fs.copyFileSync`) when it names a real local\n * file, or decoding+writing when it's in-memory base64 content (the\n * `World.attach()`/`qualflare.attachment()` path, which has no file to\n * copy). Unlike qualflare-cypress, where a video is always a file Cypress\n * recorded, this formatter has to handle both — cucumber-js has no\n * \"one recorded file per run\" concept.\n *\n * `qualflare-cli` uploads whatever lands here later, once it has a real\n * auth token; this process never makes a network call.\n *\n * Best-effort: any failure (unsupported format, oversized, unreadable\n * source, write failure) is logged as a warning and returns `undefined`\n * rather than throwing — a video is never worth failing a test run over.\n */\nexport function writeVideoAttachment(\n pending: { name: string; mimeType?: string; path?: string; content?: string },\n outputDir: string,\n maxVideoBytes: number,\n): VideoWriteResult | undefined {\n const resolved = resolveVideoMimeType(pending.mimeType, pending.path);\n if (!resolved) {\n logger.warn(`skipping video attachment \"${pending.name}\": unsupported video format.`);\n return undefined;\n }\n\n const localVideoPath = `${randomUUID()}${resolved.extension}`;\n const destination = path.join(outputDir, localVideoPath);\n\n if (pending.path !== undefined) {\n let fileSize: number;\n try {\n fileSize = fs.statSync(pending.path).size;\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.path}\": could not stat file: ${(err as Error).message}`);\n return undefined;\n }\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${pending.path}\": ${fileSize} bytes exceeds the configured maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.copyFileSync(pending.path, destination);\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.path}\": could not copy file: ${(err as Error).message}`);\n return undefined;\n }\n return { localVideoPath, fileSize, mimeType: resolved.mimeType };\n }\n\n if (pending.content !== undefined) {\n const fileSize = Buffer.byteLength(pending.content, 'base64');\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${pending.name}\": ${fileSize} bytes exceeds the configured maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.writeFileSync(destination, Buffer.from(pending.content, 'base64'));\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.name}\": could not write file: ${(err as Error).message}`);\n return undefined;\n }\n return { localVideoPath, fileSize, mimeType: resolved.mimeType };\n }\n\n return undefined;\n}\n","import {\n getWorstTestStepResult,\n type Attachment as MessageAttachment,\n type Pickle,\n type TestCase,\n type TestCaseFinished,\n type TestCaseStarted,\n type TestStepFinished,\n type TestStepResult,\n type TestStepStarted,\n} from '@cucumber/messages';\n\nimport { RESERVED_MESSAGE_MEDIA_TYPE, MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { messageDurationToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { ManualStepRecord, RuntimeMessage } from '../runtime/message-types.js';\nimport type { Attachment, CasePriority, Label, Link, Step } from '../shared/types.js';\nimport {\n AttachmentBudget,\n isVideoLike,\n resolvePendingAttachment,\n resolveVideoAttachment,\n type AttachmentBudgetConfig,\n type PendingAttachment,\n} from './attachment-budget.js';\nimport { collapseAttempts, type AttemptSnapshot, type CollapsedResult } from './case-builder.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapHookStep, mapPickleStep, mapStatus } from './step-mapper.js';\n\nexport interface FinishedAttempts {\n uri: string;\n pickleId: string;\n collapsed: CollapsedResult;\n}\n\ninterface AttemptRecord {\n testCase: TestCase;\n pickle: Pickle;\n attempt: number;\n startedAtMs: number;\n steps: Step[];\n stepResults: TestStepResult[];\n /** Maps a `TestStep.id` to its index in `steps`, once finished — used to\n * correlate a same-step attachment's `stepIndex` on the wire. */\n stepIndexByTestStepId: Map<string, number>;\n currentTestStepId?: string;\n manualSteps: ManualStepRecord[];\n manualStepStack: number[];\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n stepCapWarned: boolean;\n /** Not-yet-settled video writes (see `resolveVideoAttachment`) started\n * during this attempt. Each one, once settled, pushes its resulting\n * `Attachment` onto `attachments` above (mutating the array in place —\n * never reassigning it — so a reference already captured elsewhere still\n * sees the push; see `finish()`'s doc comment for why this matters).\n * `finish()` awaits every attempt's own queue before collapsing, so\n * `attachments` is always complete by the time it's read.\n *\n * Still genuinely needed even though writing a video is now synchronous\n * filesystem work rather than an upload: `resolveVideoAttachment` remains\n * an `async` function, so the `.then()` that performs the push runs on a\n * microtask, not inline. Dropping this queue would reintroduce exactly the\n * bug `finish()` documents. */\n pendingVideoWrites: Promise<void>[];\n}\n\n/**\n * The retry-safe core. Two lookup layers, both required because\n * cucumber-js's *grouping* key (a logical scenario, stable across retries —\n * `testCase.id`) and its *live-event-addressing* key (one specific attempt\n * — `testCaseStarted.id`) are genuinely different fields on the wire.\n * `testCaseFinished.willBeRetried === false` is the sole authoritative\n * \"this scenario is really done\" signal — no hash-based grouping anywhere\n * (the deliberate fix for `allure-framework/allure-js#625`/`#1502`, both\n * real bugs caused by Allure's content-hash retry/outline-row grouping).\n */\nexport class AttemptTracker {\n private readonly byTestCaseId = new Map<string, AttemptRecord[]>();\n private readonly byTestCaseStartedId = new Map<string, AttemptRecord>();\n\n constructor(\n private readonly hookIndex: HookIndex,\n private readonly gherkin: GherkinIndex,\n private readonly config: { includeStepHooks: boolean } & AttachmentBudgetConfig,\n private readonly attachmentBudget: AttachmentBudget,\n ) {}\n\n begin(e: TestCaseStarted, testCase: TestCase, pickle: Pickle): void {\n const record: AttemptRecord = {\n testCase,\n pickle,\n attempt: e.attempt,\n startedAtMs: timestampMs(e.timestamp),\n steps: [],\n stepResults: [],\n stepIndexByTestStepId: new Map(),\n manualSteps: [],\n manualStepStack: [],\n labels: [],\n links: [],\n tags: [],\n properties: {},\n attachments: [],\n stepCapWarned: false,\n pendingVideoWrites: [],\n };\n this.byTestCaseStartedId.set(e.id, record);\n const attempts = this.byTestCaseId.get(testCase.id) ?? [];\n attempts.push(record);\n this.byTestCaseId.set(testCase.id, attempts);\n }\n\n stepStarted(e: TestStepStarted): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = e.testStepId;\n }\n\n stepFinished(e: TestStepFinished): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = undefined;\n record.stepResults.push(e.testStepResult);\n\n const testStep = record.testCase.testSteps.find((s) => s.id === e.testStepId);\n if (!testStep) {\n return;\n }\n\n let step: Step | undefined;\n if (testStep.pickleStepId) {\n const pickleStep = record.pickle.steps.find((s) => s.id === testStep.pickleStepId);\n if (pickleStep) {\n step = mapPickleStep(record.pickle.uri, pickleStep, e.testStepResult, this.gherkin);\n }\n } else if (testStep.hookId) {\n const hook = this.hookIndex.get(testStep.hookId);\n if (hook && (hook.kind === 'before' || hook.kind === 'after')) {\n step = mapHookStep(hook, e.testStepResult);\n } else if (hook && (hook.kind === 'beforeStep' || hook.kind === 'afterStep') && this.config.includeStepHooks) {\n // Deliberately NOT nested under the pickle step it wraps: BeforeStep\n // fires (and is pushed here) BEFORE that step, so \"the most recently\n // pushed step\" would be the PREVIOUS, unrelated step at that point —\n // an earlier version of this nested BeforeStep under the wrong\n // parent. AfterStep could be nested correctly (its wrapped step is\n // already pushed by the time it fires), but nesting one and not the\n // other would be inconsistent, so both stay flat, root-level steps —\n // still informative via their position immediately adjacent to the\n // step they wrap in the flat, chronologically-ordered array.\n step = mapHookStep(hook, e.testStepResult);\n }\n }\n\n if (!step) {\n return;\n }\n if (record.steps.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!record.stepCapWarned) {\n record.stepCapWarned = true;\n logger.warn(\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-attempt cap — further steps in this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n record.stepIndexByTestStepId.set(e.testStepId, record.steps.length);\n record.steps.push(step);\n }\n\n /** Handles both a real user `World.attach()` call (becomes a wire\n * `Attachment`) and a `qualflare.*()` reserved-media-type message\n * (unwrapped and applied as a model mutation instead). */\n attachment(e: MessageAttachment): void {\n if (!e.testCaseStartedId) {\n // A BeforeAll/AfterAll-scoped attachment (`testRunHookStartedId` set\n // instead) — there is no Case to attach it to; see `run-hook-tracker\n // .ts`'s doc comment for why BeforeAll/AfterAll attachments are out of\n // scope for v1.\n return;\n }\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n\n if (e.mediaType === RESERVED_MESSAGE_MEDIA_TYPE) {\n let message: RuntimeMessage;\n try {\n message = JSON.parse(e.contentEncoding === 'BASE64' ? Buffer.from(e.body, 'base64').toString('utf8') : e.body);\n } catch {\n logger.warn('received a malformed qualflare runtime message — ignoring it.');\n return;\n }\n this.applyRuntimeMessage(record, message, e.testStepId);\n return;\n }\n\n const stepIndex = this.resolveStepIndex(record, e.testStepId);\n const content = e.contentEncoding === 'BASE64' ? e.body : Buffer.from(e.body, 'utf8').toString('base64');\n this.resolveAttachment(record, { name: e.fileName || 'attachment', mimeType: e.mediaType, content, stepIndex });\n }\n\n /** Resolves one pending attachment, routing a video-like one through the\n * write-to-`outputDir` flow (tracked in `record.pendingVideoWrites` so\n * `finish()` can wait for it) and everything else through the synchronous\n * inline path — shared by the real `World.attach()` handler above and both\n * `qualflare.attachment()`/`attachmentFromFile()` runtime-message cases\n * below. */\n private resolveAttachment(record: AttemptRecord, pending: PendingAttachment): void {\n if (isVideoLike(pending.mimeType, pending.path)) {\n const write = resolveVideoAttachment(pending, this.config).then((resolved) => {\n if (resolved) {\n record.attachments.push(resolved);\n }\n });\n record.pendingVideoWrites.push(write);\n return;\n }\n const resolved = resolvePendingAttachment(pending, this.config, this.attachmentBudget);\n if (resolved) {\n record.attachments.push(resolved);\n }\n }\n\n /** Resolves which step index an attachment (real or `qualflare.*()`\n * message) belongs to. `stepIndexByTestStepId` only gets an entry once a\n * step is FINISHED — but `World.attach()` (the only channel available,\n * used by both a real user attachment and every `qualflare.*()` call) is\n * always called from WITHIN a step's body, i.e. strictly BETWEEN that\n * step's `testStepStarted` and `testStepFinished`. So the map lookup\n * alone can never resolve the step that's currently attaching — this was\n * a real bug found in self-review, caught by a unit test that attaches\n * mid-step instead of only after it finishes. While a step is in flight,\n * `record.steps.length` (the array's CURRENT length, before that step has\n * been pushed) is exactly the index it will occupy once it does finish —\n * single-threaded, event-ordered execution guarantees nothing else can be\n * pushed in between. */\n private resolveStepIndex(record: AttemptRecord, testStepId: string | undefined): number | undefined {\n if (testStepId === undefined) {\n return undefined;\n }\n if (record.currentTestStepId === testStepId) {\n return record.steps.length;\n }\n return record.stepIndexByTestStepId.get(testStepId);\n }\n\n private applyRuntimeMessage(record: AttemptRecord, message: RuntimeMessage, testStepId: string | undefined): void {\n switch (message.type) {\n case 'label':\n record.labels.push({ name: message.name, value: message.value });\n return;\n case 'link':\n record.links.push({ type: message.linkType ?? 'custom', name: message.name, url: message.url });\n return;\n case 'tag':\n record.tags.push(...message.tags);\n return;\n case 'description':\n record.description = message.text;\n return;\n case 'priority':\n record.priority = message.value;\n return;\n case 'parameter': {\n const openStepIndex = record.manualStepStack[record.manualStepStack.length - 1];\n if (openStepIndex !== undefined) {\n const step = record.manualSteps[openStepIndex]!;\n step.parameters = step.parameters ?? [];\n step.parameters.push({ name: message.name, value: message.value, masked: message.masked });\n } else {\n record.properties[message.name] = message.value ?? '';\n }\n return;\n }\n case 'attachment': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n this.resolveAttachment(record, { name: message.name, mimeType: message.mimeType, content: message.contentBase64, stepIndex });\n return;\n }\n case 'attachment_from_file': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n this.resolveAttachment(record, { name: message.name, mimeType: message.mimeType, path: message.path, stepIndex });\n return;\n }\n case 'step_start': {\n const parentIndex = record.manualStepStack.length > 0 ? record.manualStepStack[record.manualStepStack.length - 1] : undefined;\n const step: ManualStepRecord = { name: message.name, status: 'passed', startedAt: message.timestamp, parentIndex };\n record.manualStepStack.push(record.manualSteps.length);\n record.manualSteps.push(step);\n return;\n }\n case 'step_stop': {\n const openIndex = record.manualStepStack.pop();\n if (openIndex === undefined) {\n return;\n }\n const step = record.manualSteps[openIndex]!;\n step.status = message.status;\n step.error = message.error;\n step.durationMs = Math.max(0, message.timestamp - step.startedAt);\n return;\n }\n }\n }\n\n /** Returns the collapsed result once all attempts of this logical\n * scenario have arrived, or `undefined` if more attempts are coming\n * (`willBeRetried === true`).\n *\n * Async because it must first await every attempt's own\n * `pendingVideoWrites` (any video attached anywhere across every retry\n * of this scenario). This is load-bearing, not just tidiness:\n * `collapseAttempts`/`buildCase` read `attachments` by REFERENCE, not by\n * copy, and `buildCase` runs synchronously right after this resolves — a\n * scenario whose ONLY attachment is a still-uploading video would have an\n * EMPTY `attachments` array at that instant, and `buildCase` captures\n * `attachments.length > 0 ? attachments : undefined` as a plain\n * `undefined` VALUE right then, permanently — a later push onto the\n * (still-live) array reference would no longer be visible through\n * `undefined`. Awaiting here first guarantees `attachments` is complete\n * before `buildCase` ever reads it. */\n async finish(e: TestCaseFinished): Promise<FinishedAttempts | undefined> {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n this.byTestCaseStartedId.delete(e.testCaseStartedId);\n if (!record) {\n return undefined;\n }\n if (e.willBeRetried) {\n return undefined;\n }\n\n const testCaseId = record.testCase.id;\n const attempts = this.byTestCaseId.get(testCaseId) ?? [record];\n this.byTestCaseId.delete(testCaseId);\n\n const pendingWrites = attempts.flatMap((a) => a.pendingVideoWrites);\n if (pendingWrites.length > 0) {\n await Promise.all(pendingWrites);\n }\n\n const snapshots: AttemptSnapshot[] = attempts.map((a) => {\n const worst = a.stepResults.length > 0 ? getWorstTestStepResult(a.stepResults) : undefined;\n const duration = a.stepResults.reduce((sum, r) => sum + messageDurationToNs(r.duration), 0);\n return {\n status: worst ? mapStatus(worst.status) : 'passed',\n duration,\n // `message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically across the peer-dependency\n // range; `exception.stackTrace` is not version-safe).\n error: worst?.message || worst?.exception?.stackTrace || worst?.exception?.message,\n steps: a.steps,\n manualSteps: a.manualSteps,\n labels: a.labels,\n links: a.links,\n tags: a.tags,\n description: a.description,\n priority: a.priority,\n properties: a.properties,\n attachments: a.attachments,\n };\n });\n\n return {\n uri: record.pickle.uri,\n pickleId: record.testCase.pickleId,\n collapsed: collapseAttempts(snapshots),\n };\n }\n}\n\nfunction timestampMs(ts: { seconds: number; nanos: number }): number {\n return ts.seconds * 1000 + Math.floor(ts.nanos / 1_000_000);\n}\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_000_000;\nconst NS_PER_SECOND = 1_000_000_000;\n\n/**\n * Converts a plain millisecond duration (e.g. from `Date.now()` deltas used\n * by manual `qualflare.step()` timing) into the wire format's raw-nanosecond\n * 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/**\n * Converts a `@cucumber/messages` structured `Duration` ({seconds, nanos})\n * into the wire format's raw-nanosecond integer, without round-tripping\n * through milliseconds (which would lose sub-millisecond precision Cucumber\n * already reports natively). Negative/malformed input is clamped to 0, same\n * defensive posture as `msToNs`.\n */\nexport function messageDurationToNs(duration: { seconds: number; nanos: number } | undefined): NanosecondDuration {\n if (!duration || !Number.isFinite(duration.seconds) || !Number.isFinite(duration.nanos)) {\n return 0;\n }\n const total = duration.seconds * NS_PER_SECOND + duration.nanos;\n return total > 0 ? Math.round(total) : 0;\n}\n","import type { Pickle } from '@cucumber/messages';\n\nimport { MAX_TAGS_PER_CASE } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport type { ManualStepRecord } from '../runtime/message-types.js';\nimport type { Attachment, Case, CasePriority, CaseStatus, Label, Link, NanosecondDuration, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\n\n/** One attempt of a scenario — cucumber-js's `--retry` re-runs the same\n * pickle from scratch (fresh World, all hooks/steps rerun); each attempt\n * (`testCaseStarted`→...→`testCaseFinished`) produces one of these. Ported\n * from `@qualflare/cypress`'s `AttemptSnapshot`/`collapseAttempts` — same\n * \"final attempt wins for content, but count+sum across all attempts\"\n * rules — fed by envelope-derived data here instead of Mocha-derived data. */\nexport interface AttemptSnapshot {\n status: CaseStatus;\n /** NANOSECONDS — already summed across this attempt's real step results;\n * see `attempt-tracker.ts`. No ms round-trip, unlike the Cypress version:\n * Cucumber's own `TestStepResult.duration` is nanosecond-precision. */\n duration: NanosecondDuration;\n error?: string;\n /** Real Gherkin + hook steps, already wire-shaped by `step-mapper.ts`. */\n steps: Step[];\n manualSteps: ManualStepRecord[];\n labels: Label[];\n links: Link[];\n /** Dynamically added via `qualflare.tag()` during this attempt — merged\n * with the pickle's own static `@tag`s separately, in `case-builder.ts`'s\n * `buildCase()`, since those are the same across every attempt. */\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n}\n\nexport interface CollapsedResult {\n status: CaseStatus;\n /** NANOSECONDS — sum of every attempt (reflects true CI wall-clock cost,\n * not just the final attempt's duration). */\n duration: NanosecondDuration;\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. */\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 `realSteps` (Gherkin + hook\n * steps, indices/`parentIndex` valid only relative to each other) into one\n * combined, correctly-indexed `Step[]`. Ported verbatim in spirit from\n * `@qualflare/cypress`'s `combineSteps()`. */\nfunction combineSteps(realSteps: Step[], manualSteps: ManualStepRecord[]): Step[] | undefined {\n if (realSteps.length === 0 && manualSteps.length === 0) {\n return undefined;\n }\n const offsetManual: Step[] = manualSteps.map((record) => {\n const step: Step = {\n name: record.name,\n status: record.status,\n duration: msToNs(record.durationMs ?? 0),\n };\n if (record.error) step.error = record.error;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex + realSteps.length;\n if (record.parameters && record.parameters.length > 0) step.parameters = record.parameters;\n return step;\n });\n return [...realSteps, ...offsetManual];\n}\n\n/**\n * Collapses every attempt of one logical scenario (grouped by cucumber-js's\n * own stable `testCase.id`, NOT a content hash — see `attempt-tracker.ts`)\n * into the single `Case` this reporter uploads. `willBeRetried === false` on\n * the final `testCaseFinished` is the caller's signal that all attempts have\n * arrived; 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\nexport interface FinishedCase {\n uri: string;\n case: Case;\n}\n\n/** Assembles the final wire `Case` for one finished (all-attempts-collapsed)\n * scenario. */\nexport function buildCase(uri: string, pickle: Pickle, collapsed: CollapsedResult, gherkin: GherkinIndex): FinishedCase {\n const entry = gherkin.get(uri);\n // `pickle.astNodeIds` is `[scenarioId]` for a plain Scenario, or\n // `[scenarioId, exampleRowId]` for one row of a Scenario Outline — the\n // scenario's own AST id is always first.\n const scenarioId = pickle.astNodeIds[0];\n const scenarioEntry = scenarioId ? entry?.scenarioById.get(scenarioId) : undefined;\n const featureName = entry?.featureName;\n const ruleName = scenarioEntry?.ruleName;\n const className = ruleName ? `${featureName ?? ''} > ${ruleName}` : featureName;\n\n const labels: Label[] = [...collapsed.labels];\n if (featureName) {\n labels.push({ name: 'feature', value: featureName });\n }\n if (ruleName) {\n labels.push({ name: 'rule', value: ruleName });\n }\n\n const properties: Record<string, string> = { ...examplesRowProperties(scenarioEntry, pickle), ...collapsed.properties };\n\n // `pickle.tags` already has Feature/Rule `@tag`s inherited/flattened onto\n // every scenario by cucumber-js itself — no manual inheritance needed.\n const staticTags = pickle.tags.map((t) => t.name.replace(/^@/, ''));\n const tags = [...new Set([...staticTags, ...collapsed.tags])].slice(0, MAX_TAGS_PER_CASE);\n\n const kase: Case = {\n // Stable across runs (unchanged unless the file is edited), and\n // includes the source line specifically so two identically-named\n // Scenarios in one feature file can never collide — the exact bug\n // that's open upstream in allure-js (allure-framework/allure-js#1502).\n id: `${uri}:${pickle.location?.line ?? 0}#${pickle.name}`,\n name: pickle.name,\n status: collapsed.status,\n duration: collapsed.duration,\n retryCount: collapsed.retryCount || undefined,\n isFlaky: collapsed.isFlaky || undefined,\n error: collapsed.error,\n tags: tags.length > 0 ? tags : undefined,\n steps: collapsed.steps,\n labels: labels.length > 0 ? labels : undefined,\n links: collapsed.links.length > 0 ? collapsed.links : undefined,\n // `||`, not `??`: cucumber-js's `Scenario.description` is always a\n // defined string, `''` when the Gherkin source has none — `??` would\n // never fall through and every scenario without one would send an\n // empty-string description instead of omitting the field (found via a\n // real tarball-installed smoke test, not just reasoning about types).\n description: collapsed.description || scenarioEntry?.scenario.description || undefined,\n priority: collapsed.priority,\n properties: Object.keys(properties).length > 0 ? properties : undefined,\n attachments: collapsed.attachments.length > 0 ? collapsed.attachments : undefined,\n };\n if (className) {\n kase.className = className;\n }\n return { uri, case: kase };\n}\n\n/** For a Scenario Outline row, folds that row's concrete Examples values\n * into `Case.properties` (`{columnName: cellValue}`) — the same AST\n * `tableBody`-correlation technique verified in `allure-cucumberjs`'s real\n * source. Every row's values show up on that row's Case automatically, with\n * zero extra author effort. */\nfunction examplesRowProperties(\n scenarioEntry: { scenario: { examples: readonly { tableHeader?: { cells: readonly { value: string }[] }; tableBody: readonly { id: string; cells: readonly { value: string }[] }[] }[] } } | undefined,\n pickle: Pickle,\n): Record<string, string> {\n if (!scenarioEntry) {\n return {};\n }\n const rowAstIds = new Set(pickle.astNodeIds);\n for (const examples of scenarioEntry.scenario.examples) {\n const header = examples.tableHeader?.cells;\n if (!header) {\n continue;\n }\n const row = examples.tableBody.find((r) => rowAstIds.has(r.id));\n if (!row) {\n continue;\n }\n const properties: Record<string, string> = {};\n header.forEach((cell, i) => {\n const value = row.cells[i]?.value;\n if (cell.value && value !== undefined) {\n properties[cell.value] = value;\n }\n });\n return properties;\n }\n return {};\n}\n","import { TestStepResultStatus, type PickleStep, type PickleStepArgument, type TestStepResult } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { CaseStatus, Parameter, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookInfo } from './hook-index.js';\n\n/** Maps cucumber-js's step/scenario result status onto the wire contract's\n * `CaseStatus` vocabulary. `UNDEFINED` (no matching step definition) and\n * `AMBIGUOUS` (more than one matching definition) are both authoring/config\n * problems rather than a real pass/fail outcome — `'error'` is the closest\n * semantic fit in the wire vocabulary, distinct from a genuine `'failed'`\n * assertion. */\nexport function mapStatus(status: TestStepResultStatus): CaseStatus {\n switch (status) {\n case TestStepResultStatus.PASSED:\n return 'passed';\n case TestStepResultStatus.FAILED:\n return 'failed';\n case TestStepResultStatus.SKIPPED:\n return 'skipped';\n case TestStepResultStatus.PENDING:\n return 'pending';\n case TestStepResultStatus.UNDEFINED:\n case TestStepResultStatus.AMBIGUOUS:\n case TestStepResultStatus.UNKNOWN:\n default:\n return 'error';\n }\n}\n\n/** `TestStepResult.message` is the version-safe field to prefer: verified\n * empirically (real spawned runs, not just docs) that it reliably contains\n * the full \"Error: <message>\\n at ...\" text on both the peer floor\n * (cucumber-js 10.9.0) and the latest (13.2.1) — `exception.stackTrace`\n * does NOT: on 10.9.0 it's stack-frames only, with no message text at all,\n * while on 13.2.1 it happens to duplicate the full combined text. Preferring\n * `stackTrace` first (an earlier version of this function did) silently\n * produced a message-less error on 10.9.0 — caught by running the real CI\n * version matrix locally before trusting it, not by reasoning about the\n * schema alone. */\nfunction formatError(result: TestStepResult): string | undefined {\n return result.message || result.exception?.stackTrace || result.exception?.message;\n}\n\n/** Doc Strings and Data Tables have no dedicated field on the wire `Step`\n * contract (confirmed against `launch.go` — the only structured-payload\n * slot on a step is the flat `parameters` list) — encoded as one Parameter\n * each, documented as a workaround in `docs/LIMITATIONS.md`. A Data Table is\n * JSON-stringified as one Parameter rather than exploded into one Parameter\n * per cell, to avoid risking `MAX_PARAMETERS_PER_STEP` on a large table. */\nexport function pickleStepArgumentToParameters(argument: PickleStepArgument | undefined): Parameter[] | undefined {\n if (!argument) {\n return undefined;\n }\n const params: Parameter[] = [];\n if (argument.docString) {\n params.push({ name: 'docString', value: argument.docString.content });\n }\n if (argument.dataTable) {\n const rows = argument.dataTable.rows.map((row) => row.cells.map((cell) => cell.value));\n params.push({ name: 'dataTable', value: JSON.stringify(rows) });\n }\n return params.length > 0 ? params : undefined;\n}\n\n/** Builds a wire `Step` for a real Gherkin (Given/When/Then/And/But) step. */\nexport function mapPickleStep(\n uri: string,\n pickleStep: PickleStep,\n result: TestStepResult,\n gherkin: GherkinIndex,\n): Step {\n const resolved = gherkin.resolveKeyword(uri, pickleStep.astNodeIds);\n const step: Step = {\n name: pickleStep.text,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n if (resolved?.keyword) {\n step.keyword = resolved.keyword.trim();\n }\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n const parameters = pickleStepArgumentToParameters(pickleStep.argument);\n if (parameters) {\n step.parameters = parameters;\n }\n return step;\n}\n\n/** Builds a synthetic wire `Step` for a `Before`/`After` (or, when enabled,\n * `BeforeStep`/`AfterStep`) hook execution — see design decision (a) in the\n * plan: hooks are folded directly into the flat/nested `Step[]` rather than\n * needing a separate \"fixture\" model concept the way Allure's richer model\n * requires, since our wire contract has no such concept anyway. */\nconst HOOK_LABELS: Record<HookInfo['kind'], string> = {\n before: 'Before',\n after: 'After',\n beforeStep: 'BeforeStep',\n afterStep: 'AfterStep',\n beforeAll: 'BeforeAll',\n afterAll: 'AfterAll',\n};\n\nexport function mapHookStep(hook: HookInfo, result: TestStepResult): Step {\n // A distinct label per hook kind (not just \"Before\"/\"After\" for\n // everything) so a step-level hook is distinguishable from a case-level\n // one in the uploaded data when `includeStepHooks` is enabled.\n const label = HOOK_LABELS[hook.kind];\n const step: Step = {\n name: hook.name || `${label} hook`,\n keyword: label,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n return step;\n}\n","import * as os from 'node:os';\n\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport type { ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport type { Collect, Suite } from '../shared/types.js';\n\nfunction resolveOs(config: ResolvedFormatterConfig): string {\n if (config.os) {\n return config.os;\n }\n return `${os.type()} ${os.release()}`;\n}\n\n/**\n * Assembles the final `Collect` payload from every finished `Suite`, at\n * `finished()`. CI metadata and branch/commit auto-detection are already\n * fully resolved by `resolve-config.ts` — this function just reads the\n * resolved config through, it does not call `ci-detect.ts`/`git-detect.ts`\n * itself. Unlike `@qualflare/cypress`'s version, there is no `BrowserInfo`\n * parameter — cucumber-js has no browser context of its own (unless a user\n * pairs it with a browser driver, which is outside this reporter's own\n * knowledge), so `browser` is config-only and `os` falls back to\n * `os.type()/os.release()`.\n */\nexport function buildCollectPayload(suites: Suite[], config: ResolvedFormatterConfig): Collect {\n return {\n framework: config.framework,\n platform: config.platform,\n os: resolveOs(config),\n browser: config.browser ?? '',\n branch: config.branch,\n commit: config.commit,\n environment: config.environment,\n language: config.language,\n milestone: config.milestone,\n metadata: {\n version: PACKAGE_VERSION,\n timestamp: new Date().toISOString(),\n cliName: 'qualflare-cucumberjs',\n runId: config.runId,\n },\n properties: config.properties,\n suites,\n ciProvider: config.ciProvider,\n ciBuildNumber: config.ciBuildNumber,\n ciRunUrl: config.ciRunUrl,\n ciPrNumber: config.ciPrNumber,\n };\n}\n","// `__PACKAGE_VERSION__` is injected at build time by tsup's `define` option\n// (see tsup.config.ts) from package.json's `version` field. Deliberately\n// NOT read at runtime via `import.meta.url` + `createRequire` — that breaks\n// the CJS build output (`import.meta` is empty/unavailable once esbuild\n// compiles to CommonJS), which is exactly the class of dual-CJS/ESM-package\n// bug a build-time constant sidesteps entirely. Under Vitest (which never\n// goes through tsup), `vitest.config.ts` defines the same constant so this\n// module behaves identically in tests and in the built package.\ndeclare const __PACKAGE_VERSION__: string;\n\nexport const PACKAGE_VERSION: string = __PACKAGE_VERSION__;\n","import type { GherkinDocument, Scenario } from '@cucumber/messages';\n\ninterface ScenarioEntry {\n scenario: Scenario;\n ruleName?: string;\n}\n\ninterface FeatureEntry {\n featureName?: string;\n /** AST step id -> literal Gherkin keyword (\"Given \"/\"When \"/\"Then \"/\"And \"/\n * \"But \", with cucumber-js's own trailing space) + step text. Background\n * steps are indexed here too — cucumber-js already merges Background\n * steps into every scenario's compiled `Pickle.steps[]` itself, so no\n * separate handling is needed; a Background step's AST id just needs to\n * resolve here like any other. */\n stepMap: Map<string, { keyword: string; text: string }>;\n scenarioById: Map<string, ScenarioEntry>;\n}\n\n/**\n * Indexes each `gherkinDocument` envelope (one per feature file) so\n * `case-builder.ts`/`step-mapper.ts` can resolve, per pickle: the Feature\n * name, an optional `Rule:` name, the Scenario AST node (for its\n * `examples[]`, used for Scenario Outline row correlation), and literal\n * Given/When/Then/And/But keyword text for each step (the compiled\n * `PickleStep.type` only gives a coarse Context/Action/Outcome/Unknown\n * classification, not the literal keyword).\n */\nexport class GherkinIndex {\n private readonly byUri = new Map<string, FeatureEntry>();\n\n add(doc: GherkinDocument): void {\n if (!doc.uri || !doc.feature) {\n return;\n }\n const entry: FeatureEntry = {\n featureName: doc.feature.name || undefined,\n stepMap: new Map(),\n scenarioById: new Map(),\n };\n for (const child of doc.feature.children) {\n if (child.background) {\n this.indexSteps(entry, child.background.steps);\n }\n if (child.scenario) {\n entry.scenarioById.set(child.scenario.id, { scenario: child.scenario });\n this.indexSteps(entry, child.scenario.steps);\n }\n if (child.rule) {\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n this.indexSteps(entry, ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n entry.scenarioById.set(ruleChild.scenario.id, {\n scenario: ruleChild.scenario,\n ruleName: child.rule.name || undefined,\n });\n this.indexSteps(entry, ruleChild.scenario.steps);\n }\n }\n }\n }\n this.byUri.set(doc.uri, entry);\n }\n\n private indexSteps(entry: FeatureEntry, steps: readonly { id: string; keyword: string; text: string }[]): void {\n for (const step of steps) {\n entry.stepMap.set(step.id, { keyword: step.keyword, text: step.text });\n }\n }\n\n get(uri: string): FeatureEntry | undefined {\n return this.byUri.get(uri);\n }\n\n /** Resolves the literal Given/When/Then/And/But keyword text for a\n * compiled `PickleStep`, by walking its `astNodeIds` back to the first\n * one present in this feature's `stepMap` (a Background step referenced\n * by a scenario has exactly one AST id; a step reusing a parameter type\n * from an outline row can have more than one — the first match is always\n * the step's own defining AST node). */\n resolveKeyword(uri: string, astNodeIds: readonly string[]): { keyword: string; text: string } | undefined {\n const entry = this.byUri.get(uri);\n if (!entry) {\n return undefined;\n }\n for (const id of astNodeIds) {\n const found = entry.stepMap.get(id);\n if (found) {\n return found;\n }\n }\n return undefined;\n }\n}\n","import type { IFormatterOptions } from '@cucumber/cucumber';\n\n// `SupportCodeLibrary` itself isn't re-exported from the package's public\n// entry point (only reachable via a deep `lib/**` import, which the\n// package's exports map only allows for `require`, not `import` — this\n// package is ESM-first). Deriving the type via indexed access on the\n// already-public `IFormatterOptions` avoids that deep import entirely.\ntype SupportCodeLibrary = IFormatterOptions['supportCodeLibrary'];\n\nexport type HookKind = 'before' | 'after' | 'beforeStep' | 'afterStep' | 'beforeAll' | 'afterAll';\n\nexport interface HookInfo {\n kind: HookKind;\n name?: string;\n}\n\nexport type HookIndex = ReadonlyMap<string, HookInfo>;\n\n/**\n * Resolves a hook's kind from `supportCodeLibrary`'s six definition-array\n * fields, NOT the envelope's `Hook.type` — that field was only added in\n * cucumber-js 11.2.0 (confirmed via the project's own CHANGELOG), which is\n * after this package's peer floor (`>=10.8.0`). This is also exactly the\n * mechanism `allure-cucumberjs`'s real source uses for `Before`/`After`\n * (though it never indexes the `TestStep`/`TestRunHook` collections at all,\n * which is why it silently drops `BeforeStep`/`AfterStep` and never handles\n * `BeforeAll`/`AfterAll` — this package intentionally indexes all six).\n */\nexport function buildHookIndex(supportCodeLibrary: SupportCodeLibrary): HookIndex {\n const index = new Map<string, HookInfo>();\n for (const def of supportCodeLibrary.beforeTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'before', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.afterTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'after', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.beforeTestStepHookDefinitions) {\n index.set(def.id, { kind: 'beforeStep' });\n }\n for (const def of supportCodeLibrary.afterTestStepHookDefinitions) {\n index.set(def.id, { kind: 'afterStep' });\n }\n for (const def of supportCodeLibrary.beforeTestRunHookDefinitions) {\n index.set(def.id, { kind: 'beforeAll' });\n }\n for (const def of supportCodeLibrary.afterTestRunHookDefinitions) {\n index.set(def.id, { kind: 'afterAll' });\n }\n return index;\n}\n","import type { TestRunHookFinished, TestRunHookStarted } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { Case } from '../shared/types.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapStatus } from './step-mapper.js';\n\n/**\n * `BeforeAll`/`AfterAll` run outside any test case (`testRunHookStarted`/\n * `Finished`, never a `Case`) — `allure-cucumberjs` drops these entirely\n * (confirmed by reading its real source: no case for them anywhere in its\n * envelope-dispatch switch). This tracker does not: a FAILED run-hook\n * becomes a synthetic `Case` in a `(global hooks)` Suite (design decision\n * (b) in the plan); a passing one produces nothing — a passing `BeforeAll`\n * has no useful signal to report, and would be pure noise on every run.\n */\nexport class RunHookTracker {\n /** `testRunHookStartedId` -> the hook it started, so `finish()` can look\n * up its kind/name. `TestRunHookFinished.result.duration` already gives\n * an accurate duration directly — no start/finish timestamp delta needed. */\n private readonly started = new Map<string, string>();\n private readonly failed: Case[] = [];\n\n start(e: TestRunHookStarted): void {\n this.started.set(e.id, e.hookId);\n }\n\n finish(e: TestRunHookFinished, hookIndex: HookIndex): void {\n const hookId = this.started.get(e.testRunHookStartedId);\n this.started.delete(e.testRunHookStartedId);\n const status = mapStatus(e.result.status);\n if (status === 'passed' || status === 'skipped') {\n return;\n }\n const hook = hookId ? hookIndex.get(hookId) : undefined;\n const label = hook?.kind === 'afterAll' ? 'AfterAll hook' : 'BeforeAll hook';\n this.failed.push({\n id: `global-hook:${e.testRunHookStartedId}`,\n name: hook?.name || label,\n status,\n duration: messageDurationToNs(e.result.duration),\n // `result.message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically to be the version-safe field\n // across the peer-dependency range; `exception.stackTrace` is not).\n error: e.result.message || e.result.exception?.stackTrace || e.result.exception?.message,\n });\n }\n\n /** Returns `undefined` if no run-hook failed — see the class doc comment\n * for why a passing BeforeAll/AfterAll produces no Case at all. */\n buildSuite(): { name: string; category: 'cucumber'; duration: number; cases: Case[] } | undefined {\n if (this.failed.length === 0) {\n return undefined;\n }\n return {\n name: '(global hooks)',\n category: 'cucumber',\n duration: this.failed.reduce((sum, c) => sum + c.duration, 0),\n cases: this.failed,\n };\n }\n}\n","import * as path from 'node:path';\n\nimport { MAX_CASES_PER_SUITE, MAX_SUITES_PER_LAUNCH } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Case, Suite } from '../shared/types.js';\nimport type { FinishedCase } from './case-builder.js';\n\n/**\n * Groups every finished Case into one `Suite` per `.feature` file. Unlike\n * `@qualflare/cypress` (which can batch incrementally, one spec file at a\n * time, since Cypress runs specs sequentially in one process),\n * cucumber-js can interleave scenarios from different feature files even\n * without `--parallel`, and always runs the formatter in the coordinator\n * process either way (worker *threads* under `--parallel` ship envelopes\n * back to it) — so grouping happens once, at `finished()`, over the whole\n * run's flat list of finished cases.\n */\nexport function groupIntoSuites(cases: FinishedCase[], cwd: string, extraSuite?: Suite): Suite[] {\n const byUri = new Map<string, Case[]>();\n for (const { uri, case: kase } of cases) {\n const bucket = byUri.get(uri);\n if (bucket) {\n bucket.push(kase);\n } else {\n byUri.set(uri, [kase]);\n }\n }\n\n const suites: Suite[] = [];\n for (const [uri, kases] of byUri) {\n if (kases.length > MAX_CASES_PER_SUITE) {\n logger.warn(\n `feature file \"${uri}\" reported ${kases.length} scenarios — only the first ${MAX_CASES_PER_SUITE} will be uploaded (server cap).`,\n );\n }\n suites.push({\n name: relativizeUri(uri, cwd),\n category: 'cucumber',\n duration: kases.reduce((sum, c) => sum + c.duration, 0),\n cases: kases.slice(0, MAX_CASES_PER_SUITE),\n });\n }\n\n if (extraSuite) {\n suites.push(extraSuite);\n }\n\n if (suites.length > MAX_SUITES_PER_LAUNCH) {\n logger.warn(\n `this run reported ${suites.length} feature-file suites — only the first ${MAX_SUITES_PER_LAUNCH} will be uploaded (server cap).`,\n );\n }\n return suites.slice(0, MAX_SUITES_PER_LAUNCH);\n}\n\n/** cucumber-js's own `pickle.uri` is already relative to the invocation cwd\n * in the common case (confirmed empirically: a real run reports e.g.\n * `\"features/passing.feature\"`, not an absolute path or a `file://` URL) —\n * this only normalizes the rarer absolute-path/URL forms down to the same\n * shape. */\nfunction relativizeUri(uri: string, cwd: string): string {\n let normalized = uri;\n if (normalized.startsWith('file://')) {\n normalized = new URL(normalized).pathname;\n }\n if (path.isAbsolute(normalized)) {\n normalized = path.relative(cwd, normalized);\n }\n return normalized.split(path.sep).join('/');\n}\n"],"mappings":"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","names":["import_node_crypto","fs","path","name","firstEnv","name","fs","path","import_node_crypto","extension","import_messages","resolved","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/formatter/index.ts","../../src/formatter/formatter.ts","../../src/config/resolve-config.ts","../../src/shared/constants.ts","../../src/config/ci-detect.ts","../../src/config/git-detect.ts","../../src/shared/logger.ts","../../src/formatter/attachment-budget.ts","../../src/formatter/video-writer.ts","../../src/formatter/attempt-tracker.ts","../../src/shared/duration.ts","../../src/shared/text.ts","../../src/formatter/case-builder.ts","../../src/formatter/step-mapper.ts","../../src/formatter/collect-builder.ts","../../src/config/version.ts","../../src/formatter/gherkin-index.ts","../../src/formatter/hook-index.ts","../../src/formatter/run-hook-tracker.ts","../../src/formatter/suite-builder.ts"],"sourcesContent":["export { default } from './formatter.js';\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { Formatter, type IFormatterOptions } from '@cucumber/cucumber';\nimport type { Envelope, GherkinDocument, Pickle, TestCase } from '@cucumber/messages';\n\nimport { resolveConfig, type QualflareCucumberOptions, type ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport { logger } from '../shared/logger.js';\nimport { AttachmentBudget } from './attachment-budget.js';\nimport { AttemptTracker } from './attempt-tracker.js';\nimport { buildCase, type FinishedCase } from './case-builder.js';\nimport { buildCollectPayload } from './collect-builder.js';\nimport { GherkinIndex } from './gherkin-index.js';\nimport { buildHookIndex, type HookIndex } from './hook-index.js';\nimport { RunHookTracker } from './run-hook-tracker.js';\nimport { groupIntoSuites } from './suite-builder.js';\n\nexport default class QualflareCucumberFormatter extends Formatter {\n private readonly config: ResolvedFormatterConfig;\n private readonly gherkin = new GherkinIndex();\n private readonly hookIndex: HookIndex;\n private readonly pickleIndex = new Map<string, Pickle>();\n private readonly testCaseIndex = new Map<string, TestCase>();\n private readonly attachmentBudget: AttachmentBudget;\n private readonly attemptTracker: AttemptTracker;\n private readonly runHookTracker = new RunHookTracker();\n private readonly finishedCases: FinishedCase[] = [];\n /** One promise per `testCaseFinished` envelope, resolving once that\n * scenario's `AttemptTracker.finish()` (which itself awaits any pending\n * video uploads — see its doc comment) has settled and, if it produced a\n * result, been pushed into `finishedCases`. `finished()` awaits all of\n * these before building/uploading the Collect payload, so a scenario\n * whose only attachment is a still-uploading video is never silently\n * dropped from the report. */\n private readonly pendingCaseBuilds: Promise<void>[] = [];\n\n constructor(options: IFormatterOptions) {\n super(options);\n // cucumber-js's `formatOptions` is untyped (`FormatOptions` has only an\n // index signature) — the shape is a contract between the user's config\n // and this formatter, not something cucumber-js itself validates.\n this.config = resolveConfig(options.parsedArgvOptions as QualflareCucumberOptions);\n this.hookIndex = buildHookIndex(options.supportCodeLibrary);\n this.attachmentBudget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);\n this.attemptTracker = new AttemptTracker(\n this.hookIndex,\n this.gherkin,\n this.config,\n this.attachmentBudget,\n );\n\n if (!this.config.enabled) {\n return;\n }\n options.eventBroadcaster.on('envelope', (envelope: Envelope) => this.onEnvelope(envelope));\n }\n\n private onEnvelope(envelope: Envelope): void {\n try {\n this.dispatch(envelope);\n } catch (err) {\n logger.error('failed to process a cucumber-js event:', err);\n }\n }\n\n private dispatch(envelope: Envelope): void {\n if (envelope.gherkinDocument) {\n this.onGherkinDocument(envelope.gherkinDocument);\n return;\n }\n if (envelope.pickle) {\n this.pickleIndex.set(envelope.pickle.id, envelope.pickle);\n return;\n }\n if (envelope.testCase) {\n this.testCaseIndex.set(envelope.testCase.id, envelope.testCase);\n return;\n }\n if (envelope.testCaseStarted) {\n const testCase = this.testCaseIndex.get(envelope.testCaseStarted.testCaseId);\n const pickle = testCase ? this.pickleIndex.get(testCase.pickleId) : undefined;\n if (testCase && pickle) {\n this.attemptTracker.begin(envelope.testCaseStarted, testCase, pickle);\n } else {\n // Should never happen under cucumber-js's documented message\n // ordering (testCase/pickle always precede testCaseStarted) — this\n // scenario attempt would otherwise be silently dropped from the\n // report with no signal at all, so warn rather than swallow it.\n logger.warn(\n `could not resolve testCase/pickle for testCaseStarted \"${envelope.testCaseStarted.id}\" — this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n if (envelope.testStepStarted) {\n this.attemptTracker.stepStarted(envelope.testStepStarted);\n return;\n }\n if (envelope.testStepFinished) {\n this.attemptTracker.stepFinished(envelope.testStepFinished);\n return;\n }\n if (envelope.attachment) {\n this.attemptTracker.attachment(envelope.attachment);\n return;\n }\n if (envelope.testCaseFinished) {\n // finish() is async (it awaits any pending video upload for this\n // scenario before its attachments can be read — see its doc comment),\n // but dispatch() itself stays synchronous: cucumber-js's envelope\n // stream doesn't wait for one 'envelope' listener's returned promise\n // before emitting the next, so blocking here would just desync this\n // handler from the events actually arriving. Instead, track the\n // promise and await every one of them in finished(), before the\n // Collect payload is ever built.\n const pending = this.attemptTracker\n .finish(envelope.testCaseFinished)\n .then((finished) => {\n if (!finished) {\n return;\n }\n const pickle = this.pickleIndex.get(finished.pickleId);\n if (pickle) {\n this.finishedCases.push(buildCase(finished.uri, pickle, finished.collapsed, this.gherkin));\n } else {\n logger.warn(`could not resolve pickle \"${finished.pickleId}\" for a finished scenario — it will not be uploaded.`);\n }\n })\n .catch((err) => {\n // Mirrors onEnvelope's own catch — dispatch() itself can no longer\n // catch an error raised inside this deferred chain.\n logger.error('failed to process a cucumber-js event:', err);\n });\n this.pendingCaseBuilds.push(pending);\n return;\n }\n if (envelope.testRunHookStarted) {\n this.runHookTracker.start(envelope.testRunHookStarted);\n return;\n }\n if (envelope.testRunHookFinished) {\n this.runHookTracker.finish(envelope.testRunHookFinished, this.hookIndex);\n return;\n }\n }\n\n private onGherkinDocument(doc: GherkinDocument): void {\n this.gherkin.add(doc);\n }\n\n async finished(): Promise<void> {\n try {\n // Every scenario's Case must be fully built (attachments included,\n // any pending video write settled) before the Collect payload is\n // assembled — see the testCaseFinished dispatch branch above.\n await Promise.all(this.pendingCaseBuilds);\n if (this.config.enabled) {\n this.writeResults();\n }\n } finally {\n await super.finished();\n }\n }\n\n /** Writes this process's Collect payload into `outputDir` under a unique\n * filename. Never uploads: `qualflare-cli collect <outputDir>` does that,\n * merging every file it finds there into one Launch. Multiple shards can\n * therefore share one directory safely — the UUID filename is what keeps\n * them from overwriting each other. */\n private writeResults(): void {\n const suites = groupIntoSuites(this.finishedCases, this.cwd, this.runHookTracker.buildSuite());\n if (suites.length === 0) {\n if (this.config.debug) {\n logger.debug('no scenarios reported — skipping file write.');\n }\n return;\n }\n const payload = buildCollectPayload(suites, this.config);\n if (this.config.shardIndex !== undefined) {\n for (const suite of payload.suites) {\n for (const c of suite.cases) {\n c.shardIndex = this.config.shardIndex;\n }\n }\n }\n\n fs.mkdirSync(this.config.outputDir, { recursive: true });\n const outputPath = path.join(this.config.outputDir, `${randomUUID()}.json`);\n fs.writeFileSync(outputPath, JSON.stringify(payload));\n logger.info(\n `wrote Collect payload to ${outputPath} — run \\`qualflare-cli collect ${this.config.outputDir}\\` to upload it.`,\n );\n }\n}\n","import { randomUUID } from 'node:crypto';\n\nimport { MAX_VIDEO_UPLOAD_BYTES } from '../shared/constants.js';\nimport type { Platform } from '../shared/types.js';\nimport { detectCi, type CiMetadata } from './ci-detect.js';\nimport { detectGit, type GitInfo } from './git-detect.js';\n\n/** Options passed via the `--format-options`/`formatOptions` value the user\n * configures for `@qualflare/cucumberjs/formatter` (e.g. in `cucumber.js` /\n * `cucumber.json`). Every field here also has an environment-variable\n * override — see the precedence table in the README / plan. */\nexport interface QualflareCucumberOptions {\n environment?: string;\n language?: string;\n milestone?: number | null;\n branch?: string | null;\n commit?: string | null;\n platform?: Platform;\n framework?: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n /** Max 64 chars. Free text, no enum — an unrecognized CI provider must\n * never be rejected. Auto-detected via `ci-detect.ts` when omitted. */\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n /** Identifier shared by every shard of one run, written into the report as\n * `metadata.runId`. `qualflare-cli collect` groups files by it and refuses\n * to merge a stale report from an earlier run into this launch.\n *\n * Auto-detected from CI. Outside CI it falls back to a per-process UUID,\n * which is correct there: every local run is a distinct run, so a leftover\n * file is still caught. */\n runId?: string;\n attachScreenshots?: boolean;\n /** Include `BeforeStep`/`AfterStep` hook executions as synthetic steps.\n * Off by default — these run once per Gherkin step and can multiply the\n * step count several-fold for suites with global per-step instrumentation\n * hooks (e.g. a screenshot-after-every-step hook), which is noisy as a\n * default but valuable as an explicit opt-in. */\n includeStepHooks?: boolean;\n maxAttachmentBytes?: number;\n maxTotalAttachmentBytes?: number;\n /** Per-video byte cap, checked before the file is written. Default 50MB,\n * matching the server's own hard cap. */\n maxVideoBytes?: number;\n debug?: boolean;\n /** `false` fully disables accumulation/upload (a complete no-op) but the\n * formatter still no-ops cleanly rather than throwing. */\n enabled?: boolean;\n /** Directory `finished()` writes this process's report file (and any\n * video attachments) into. Default `./qualflare-results`. Always active —\n * this formatter never uploads anything itself; `qualflare-cli` reads\n * whatever ends up in this directory. Every JSON file this process writes\n * is uniquely named, so multiple shards can safely share one `outputDir`\n * without colliding — see docs/LIMITATIONS.md. */\n outputDir?: string;\n /** This process's 0-based position among parallel shards of the same CI\n * run, stamped onto every case it reports. Purely a label: `qualflare-cli`\n * merges by \"every file in the directory\", not by this value, so an\n * unset shardIndex costs attribution, never correctness.\n *\n * Auto-detected, in order: `QUALFLARE_SHARD_INDEX`, then a best-effort\n * scan of `process.argv` for cucumber-js's own `--shard INDEX/TOTAL`\n * (whose index is 1-based, so it is converted). cucumber-js routes that\n * flag to `configuration.sources.shard`, and a formatter is only ever\n * handed `configuration.options` — so argv is the only place a formatter\n * can observe it, and only when it was passed on the command line rather\n * than via a config file. */\n shardIndex?: number;\n}\n\nexport interface ResolvedFormatterConfig {\n environment: string;\n language: string;\n milestone: number | null;\n branch: string | null;\n commit: string | null;\n platform: Platform;\n framework: string;\n os?: string;\n browser?: string;\n properties?: Record<string, string>;\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n runId: string;\n attachScreenshots: boolean;\n includeStepHooks: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n maxVideoBytes: number;\n debug: boolean;\n enabled: boolean;\n outputDir: string;\n shardIndex?: number;\n}\n\nfunction firstEnv(...names: string[]): string | undefined {\n for (const name of names) {\n const value = process.env[name];\n if (value !== undefined && value !== '') {\n return value;\n }\n }\n return undefined;\n}\n\nfunction envBool(...names: string[]): boolean | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n return raw === 'true' || raw === '1';\n}\n\nfunction envInt(...names: string[]): number | undefined {\n const raw = firstEnv(...names);\n if (raw === undefined) {\n return undefined;\n }\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/** Best-effort read of cucumber-js's own `--shard INDEX/TOTAL` flag from\n * `process.argv`, returned 0-based.\n *\n * cucumber-js does parse this flag, but routes it to\n * `configuration.sources.shard`, while a formatter is only ever handed\n * `configuration.options` (see `api/formatters.js`) — so there is no\n * supported API for a formatter to read it. argv is the one place it is\n * observable, and only when the user passed it on the command line rather\n * than via a `cucumber.js` config file; that is why this sits BELOW\n * `QUALFLARE_SHARD_INDEX` in precedence rather than replacing it.\n *\n * cucumber documents the flag's index as 1-based (\"The index starts at 1\")\n * and normalizes it internally with `parseInt(idx) - 1`; we match that, so\n * `--shard 1/3` is shard 0. A malformed value yields `undefined` rather\n * than a wrong shard label — cucumber validates the same `<n>/<n>` shape\n * and would already have rejected it. */\nfunction argvShardIndex(argv: readonly string[] = process.argv): number | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === undefined) {\n continue;\n }\n const raw = arg === '--shard' ? argv[i + 1] : arg.startsWith('--shard=') ? arg.slice('--shard='.length) : undefined;\n if (raw === undefined) {\n continue;\n }\n if (!/^\\d+\\/\\d+$/.test(raw)) {\n return undefined;\n }\n const oneBased = Number.parseInt(raw.split('/')[0] ?? '', 10);\n return Number.isFinite(oneBased) && oneBased >= 1 ? oneBased - 1 : undefined;\n }\n return undefined;\n}\n\n/** Resolves the full formatter configuration from, in order: the explicit\n * `options` (the formatter's own `formatOptions`), then `QUALFLARE_*`\n * environment variables, then `QF_*` (compat alias with the existing Go\n * CLI, where an equivalent exists), then a hardcoded default.\n *\n * Branch/commit precedence: `options.branch`/`.commit` (including an\n * explicit `null`, which is respected as \"no auto-detection wanted\" rather\n * than triggering the fallback tiers below it) > `QUALFLARE_BRANCH`/\n * `QF_BRANCH` env (and the commit equivalent) > CI-provider env vars > a\n * local `git` subprocess (`git-detect.ts`) > `null`. The subprocess tier is\n * skipped entirely — no `git` process is forked — once both branch and\n * commit are already resolved from options/env, mirroring\n * `qualflare-cli/internal/config/config.go`'s `DetectGit`'s early return.\n *\n * CI-metadata precedence (`ciProvider`/`ciBuildNumber`/`ciRunUrl`/\n * `ciPrNumber`): the corresponding `options.ci*` field, else `ci-detect.ts`'s\n * auto-detection (per-provider extraction table, falling back to the\n * `ci-info` package's ~70-provider free-text name).\n *\n * `deps` lets tests inject fake `detectGit`/`detectCi` implementations\n * instead of the real ones (which shell out to `git` and read the real\n * `process.env`/`ci-info` module state) — defaults to the real detectors,\n * so every production call site (the formatter's constructor calls\n * `resolveConfig(options)` with no second argument) is unaffected.\n */\nexport function resolveConfig(\n options: QualflareCucumberOptions,\n deps: { detectGit?: () => GitInfo; detectCi?: () => CiMetadata } = {},\n): ResolvedFormatterConfig {\n const doDetectGit = deps.detectGit ?? detectGit;\n const doDetectCi = deps.detectCi ?? detectCi;\n\n const enabled = options.enabled ?? envBool('QUALFLARE_ENABLED') ?? true;\n // `||`, not `??` — matching `environment`/`language` below: an explicit\n const outputDir = options.outputDir || firstEnv('QUALFLARE_OUTPUT_DIR') || './qualflare-results';\n const shardIndex = options.shardIndex ?? envInt('QUALFLARE_SHARD_INDEX') ?? argvShardIndex();\n\n const milestoneRaw = options.milestone !== undefined ? options.milestone : envInt('QUALFLARE_MILESTONE', 'QF_MILESTONE');\n const milestone = milestoneRaw !== undefined && milestoneRaw !== null && milestoneRaw >= 1 ? milestoneRaw : null;\n\n const envBranch = firstEnv('QUALFLARE_BRANCH', 'QF_BRANCH');\n const envCommit = firstEnv('QUALFLARE_COMMIT', 'QF_COMMIT');\n const needsGitDetection =\n (options.branch === undefined && envBranch === undefined) ||\n (options.commit === undefined && envCommit === undefined);\n const detectedGit = needsGitDetection ? doDetectGit() : {};\n\n const branch = options.branch !== undefined ? options.branch : (envBranch ?? detectedGit.branch ?? null);\n const commit = options.commit !== undefined ? options.commit : (envCommit ?? detectedGit.commit ?? null);\n\n const detectedCi = doDetectCi();\n const ciProvider = options.ciProvider ?? detectedCi.ciProvider;\n const ciBuildNumber = options.ciBuildNumber ?? detectedCi.ciBuildNumber;\n const ciRunUrl = options.ciRunUrl ?? detectedCi.ciRunUrl;\n const ciPrNumber = options.ciPrNumber ?? detectedCi.ciPrNumber;\n\n // Never empty on purpose: `qf collect` treats a report with no runId as\n // \"unknown run\" and never lets it block a merge, so defaulting to '' would\n // quietly opt local runs out of the very check this exists for.\n const runId = options.runId ?? firstEnv('QUALFLARE_RUN_ID') ?? detectedCi.ciRunId ?? randomUUID();\n\n return {\n // `||` (truthy check), not `??`, for these three REQUIRED-non-empty wire\n // fields — an explicit `''` option must not silently win over the\n // default (the server rejects an empty `environment`). Ported verbatim from\n // qualflare-cypress, where this was found via deep adversarial review.\n environment: (options.environment || undefined) ?? firstEnv('QUALFLARE_ENVIRONMENT', 'QF_ENVIRONMENT') ?? 'development',\n language: (options.language || undefined) ?? firstEnv('QUALFLARE_LANGUAGE', 'QF_LANGUAGE') ?? 'en-US',\n milestone,\n branch,\n commit,\n platform: options.platform ?? 'web',\n framework: options.framework || 'cucumber',\n os: options.os,\n browser: options.browser,\n properties: options.properties,\n ciProvider,\n ciBuildNumber,\n ciRunUrl,\n ciPrNumber,\n runId,\n attachScreenshots: options.attachScreenshots ?? envBool('QUALFLARE_ATTACH_SCREENSHOTS') ?? true,\n includeStepHooks: options.includeStepHooks ?? envBool('QUALFLARE_INCLUDE_STEP_HOOKS') ?? false,\n maxAttachmentBytes: options.maxAttachmentBytes ?? envInt('QUALFLARE_MAX_ATTACHMENT_BYTES') ?? 1_500_000,\n maxTotalAttachmentBytes:\n options.maxTotalAttachmentBytes ?? envInt('QUALFLARE_MAX_TOTAL_ATTACHMENT_BYTES') ?? 750_000,\n maxVideoBytes: options.maxVideoBytes ?? envInt('QUALFLARE_MAX_VIDEO_BYTES') ?? MAX_VIDEO_UPLOAD_BYTES,\n debug: options.debug ?? envBool('QUALFLARE_DEBUG', 'QF_DEBUG') ?? false,\n enabled,\n outputDir,\n shardIndex,\n };\n}\n","/**\n * Shared constants used across the formatter and the author-facing runtime\n * API.\n */\n\n/** Reserved `World.attach()` media type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from step-definition and hook\n * code back to the formatter process — the only data channel CucumberJS\n * gives user code back to a running formatter. The formatter's attachment\n * handler recognizes this exact media type and replays the message as a\n * model mutation instead of rendering it as a literal attachment. */\nexport const RESERVED_MESSAGE_MEDIA_TYPE = 'application/vnd.qualflare.message+json';\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 scenario attempt — well under\n * the server's 1000-per-case hard cap (`MAX_STEPS_PER_CASE`). Once hit,\n * further steps 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 * as ciInfo from 'ci-info';\n\n/** Detected CI pipeline metadata, matching the wire contract's `ciProvider`/\n * `ciBuildNumber`/`ciRunUrl`/`ciPrNumber` fields exactly (`src/shared/types.ts`). */\nexport interface CiMetadata {\n ciProvider?: string;\n ciBuildNumber?: string;\n ciRunUrl?: string;\n ciPrNumber?: number;\n /** Identifier every shard of ONE CI run shares, and which differs between\n * runs. Distinct from `ciBuildNumber`: a build number is the human-facing\n * counter (GitHub's run NUMBER repeats across re-runs of a workflow),\n * whereas this is the unique run id. `qualflare-cli collect` groups report\n * files by it to refuse merging a stale file from an earlier run into the\n * current launch. */\n ciRunId?: string;\n}\n\ninterface ProviderExtractor {\n detect: (env: NodeJS.ProcessEnv) => boolean;\n providerName: string;\n buildNumber?: (env: NodeJS.ProcessEnv) => string | undefined;\n runUrl?: (env: NodeJS.ProcessEnv) => string | undefined;\n runId?: (env: NodeJS.ProcessEnv) => string | undefined;\n prNumber?: (env: NodeJS.ProcessEnv) => number | undefined;\n}\n\nfunction parsePositiveInt(raw: string | undefined): number | undefined {\n if (!raw) {\n return undefined;\n }\n const n = Number.parseInt(raw, 10);\n return Number.isFinite(n) && n >= 1 ? n : undefined;\n}\n\nfunction nonEmpty(raw: string | undefined): string | undefined {\n return raw && raw.length > 0 ? raw : undefined;\n}\n\n/** Explicit per-provider extraction for the fields `ci-info` doesn't\n * standardize (build number / run URL / PR number). Each `detect` reads\n * directly off the passed-in `env` — deliberately NOT delegating to\n * `ci-info`'s own per-vendor booleans (e.g. `ciInfo.GITHUB_ACTIONS`), which\n * are computed once against the real `process.env` at module-import time\n * and can't be re-evaluated against an injected env — see the module-level\n * comment on `detectCi` below. Checked in order; first match wins. */\nconst PROVIDERS: ProviderExtractor[] = [\n {\n detect: (env) => env.GITHUB_ACTIONS === 'true',\n providerName: 'GitHub Actions',\n buildNumber: (env) => nonEmpty(env.GITHUB_RUN_NUMBER),\n runId: (env) => nonEmpty(env.GITHUB_RUN_ID),\n runUrl: (env) =>\n env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID\n ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`\n : undefined,\n prNumber: (env) => {\n const match = /^refs\\/pull\\/(\\d+)\\/merge$/.exec(env.GITHUB_REF ?? '');\n return match ? parsePositiveInt(match[1]) : undefined;\n },\n },\n {\n detect: (env) => env.GITLAB_CI === 'true',\n providerName: 'GitLab CI',\n buildNumber: (env) => nonEmpty(env.CI_PIPELINE_IID),\n runId: (env) => nonEmpty(env.CI_PIPELINE_ID),\n runUrl: (env) => nonEmpty(env.CI_PIPELINE_URL),\n prNumber: (env) => parsePositiveInt(env.CI_MERGE_REQUEST_IID),\n },\n {\n detect: (env) => env.CIRCLECI === 'true',\n providerName: 'CircleCI',\n buildNumber: (env) => nonEmpty(env.CIRCLE_BUILD_NUM),\n runId: (env) => nonEmpty(env.CIRCLE_WORKFLOW_ID ?? env.CIRCLE_BUILD_NUM),\n runUrl: (env) => nonEmpty(env.CIRCLE_BUILD_URL),\n prNumber: (env) => parsePositiveInt(env.CIRCLE_PR_NUMBER),\n },\n {\n detect: (env) => env.BUILDKITE === 'true',\n providerName: 'Buildkite',\n buildNumber: (env) => nonEmpty(env.BUILDKITE_BUILD_NUMBER),\n runId: (env) => nonEmpty(env.BUILDKITE_BUILD_ID),\n runUrl: (env) => nonEmpty(env.BUILDKITE_BUILD_URL),\n prNumber: (env) => {\n const raw = env.BUILDKITE_PULL_REQUEST;\n if (!raw || raw === 'false') {\n return undefined;\n }\n return parsePositiveInt(raw);\n },\n },\n {\n // Jenkins has no simple `JENKINS=true`-style flag; JENKINS_URL is always\n // set by the Jenkins agent and is the conventional detection signal.\n detect: (env) => Boolean(env.JENKINS_URL),\n providerName: 'Jenkins',\n buildNumber: (env) => nonEmpty(env.BUILD_NUMBER),\n runId: (env) => nonEmpty(env.BUILD_TAG ?? env.BUILD_NUMBER),\n runUrl: (env) => nonEmpty(env.BUILD_URL),\n // Jenkins has no standardized PR-number env var across its many PR\n // plugins (Multibranch, GitHub Branch Source, etc.) — deliberately\n // omitted rather than guessing at a plugin-specific variable.\n },\n {\n detect: (env) => env.TF_BUILD === 'True' || env.TF_BUILD === 'true',\n providerName: 'Azure Pipelines',\n buildNumber: (env) => nonEmpty(env.BUILD_BUILDID),\n runId: (env) => nonEmpty(env.BUILD_BUILDID),\n runUrl: (env) => {\n const collectionUri = env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;\n const project = env.SYSTEM_TEAMPROJECT;\n const buildId = env.BUILD_BUILDID;\n if (!collectionUri || !project || !buildId) {\n return undefined;\n }\n return `${collectionUri.replace(/\\/+$/, '')}/${encodeURIComponent(project)}/_build/results?buildId=${buildId}`;\n },\n prNumber: (env) => parsePositiveInt(env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER),\n },\n {\n detect: (env) => Boolean(env.BITBUCKET_BUILD_NUMBER),\n providerName: 'Bitbucket Pipelines',\n buildNumber: (env) => nonEmpty(env.BITBUCKET_BUILD_NUMBER),\n runId: (env) => nonEmpty(env.BITBUCKET_BUILD_NUMBER),\n runUrl: (env) => {\n const origin = env.BITBUCKET_GIT_HTTP_ORIGIN;\n if (!origin) {\n return undefined;\n }\n const resultsId = env.BITBUCKET_PIPELINE_UUID ?? env.BITBUCKET_BUILD_NUMBER;\n return resultsId ? `${origin}/addon/pipelines/home#!/results/${resultsId}` : undefined;\n },\n prNumber: (env) => parsePositiveInt(env.BITBUCKET_PR_ID),\n },\n];\n\n/**\n * Detects CI pipeline metadata for the `Collect.ciProvider`/`ciBuildNumber`/\n * `ciRunUrl`/`ciPrNumber` fields.\n *\n * IMPORTANT, non-obvious limitation: the `env` parameter only governs the\n * `PROVIDERS` table above (this module's own, directly-env-reading logic).\n * The `ci-info` fallback below does NOT honor it — `ci-info`'s package source\n * computes `exports.name`/`exports.isCI` exactly once, against the REAL\n * `process.env`, at module-import time (`const env = process.env` at its top\n * level) — there is no API to re-evaluate it against a different env object.\n * This is a non-issue in production (this function is always called against\n * the real `process.env` there); `ci-detect.test.ts` exercises the `ci-info`\n * fallback path via `vi.stubEnv` + `vi.resetModules()` (forcing a fresh\n * `ci-info` evaluation) rather than via this function's `env` parameter.\n */\nexport function detectCi(env: NodeJS.ProcessEnv = process.env): CiMetadata {\n const provider = PROVIDERS.find((p) => p.detect(env));\n if (provider) {\n const result: CiMetadata = { ciProvider: provider.providerName };\n const buildNumber = provider.buildNumber?.(env);\n if (buildNumber !== undefined) result.ciBuildNumber = buildNumber;\n const runUrl = provider.runUrl?.(env);\n if (runUrl !== undefined) result.ciRunUrl = runUrl;\n const prNumber = provider.prNumber?.(env);\n if (prNumber !== undefined) result.ciPrNumber = prNumber;\n const runId = provider.runId?.(env);\n if (runId !== undefined) result.ciRunId = runId;\n return result;\n }\n\n // Fallback: ci-info's ~70-provider detection gives us a free-text provider\n // name (the server's `ciProvider` field has no enum — an unrecognized\n // value is always accepted, per its doc comment in shared/types.ts) even\n // for providers our explicit table above doesn't cover a full\n // build-number/run-URL/PR-number extraction for.\n if (ciInfo.name) {\n return { ciProvider: ciInfo.name };\n }\n return {};\n}\n","import { execFileSync } from 'node:child_process';\n\n/** Auto-detected branch/commit — the bottom tier of `resolve-config.ts`'s\n * precedence chain (option > QUALFLARE_BRANCH/QF_BRANCH env > CI env vars >\n * local git subprocess > null). Does NOT include the QUALFLARE_BRANCH/\n * QF_BRANCH-style env aliases — those are resolved at a higher tier,\n * directly in `resolve-config.ts`, before this module is ever consulted. */\nexport interface GitInfo {\n branch?: string;\n commit?: string;\n}\n\n/** Injectable so tests never actually shell out. `stdio: ['ignore', 'pipe',\n * 'ignore']` suppresses git's own stderr chatter (e.g. \"fatal: not a git\n * repository\") from leaking into the CI log for what is, from this plugin's\n * perspective, an entirely expected, non-fatal outcome. */\nexport type ExecGit = (args: string[], cwd: string) => string;\n\nconst defaultExecGit: ExecGit = (args, cwd) =>\n execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });\n\nfunction firstEnv(env: NodeJS.ProcessEnv, ...names: string[]): string | undefined {\n for (const name of names) {\n const value = env[name];\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n\nfunction detectBranchFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n // Empty on detached HEAD (the `-q` flag suppresses the error and the\n // command still exits 0 with no output in that case on some git\n // versions, hence the explicit empty-string check as well as the\n // try/catch for versions that exit non-zero instead).\n const out = exec(['symbolic-ref', '--short', '-q', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction detectCommitFromGit(exec: ExecGit, cwd: string): string | undefined {\n try {\n const out = exec(['rev-parse', 'HEAD'], cwd).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Auto-detects branch/commit: CI-provider env vars first (cheap, no\n * subprocess), falling back to a local `git` subprocess only for whichever\n * of branch/commit the env vars didn't resolve — mirroring\n * `qualflare-cli/internal/config/config.go`'s `LoadFromEnv`\n * (`getFirstEnv(\"GIT_BRANCH\", \"GITHUB_REF_NAME\", \"CI_COMMIT_REF_NAME\",\n * \"BITBUCKET_BRANCH\")` / the equivalent commit chain) and its `DetectGit`'s\n * \"only shell out for what's actually missing\" behavior (BUG-39 in that\n * file: forking `git` on every CLI invocation, even `--help`, was wasteful —\n * the same reasoning applies here, this reporter should not fork two `git`\n * processes on every `cucumber-js` run when CI env vars already cover both\n * values, or when the caller already resolved both from options/env at a\n * higher precedence tier and doesn't need this module at all).\n */\nexport function detectGit(\n env: NodeJS.ProcessEnv = process.env,\n cwd: string = process.cwd(),\n exec: ExecGit = defaultExecGit,\n): GitInfo {\n const branch =\n firstEnv(env, 'GIT_BRANCH', 'GITHUB_REF_NAME', 'CI_COMMIT_REF_NAME', 'BITBUCKET_BRANCH') ??\n detectBranchFromGit(exec, cwd);\n const commit =\n firstEnv(env, 'GIT_COMMIT', 'GITHUB_SHA', 'CI_COMMIT_SHA', 'BITBUCKET_COMMIT') ??\n detectCommitFromGit(exec, cwd);\n\n const result: GitInfo = {};\n if (branch !== undefined) result.branch = branch;\n if (commit !== undefined) result.commit = commit;\n return result;\n}\n","/**\n * A minimal logger writing to stderr. Deliberately avoids stdout, since\n * that's typically `cucumber-js`'s own test-output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-cucumberjs]';\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 * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport { writeVideoAttachment } from './video-writer.js';\n\n/** Extensions/mime-prefixes routed through the video-upload flow\n * (`resolveVideoAttachment`) instead of the inline-base64 path below.\n * Broader than the server's own MIME allowlist (`.avi`/`.mkv` included) so\n * this still correctly IDENTIFIES a video attachment even in a format the\n * server can't accept — `resolveVideoAttachment`/`resolveVideoMimeType` is\n * what actually enforces the narrower allowlist and warns/skips a format\n * outside it. */\nconst VIDEO_EXTENSIONS = new Set(['.mp4', '.webm', '.mov', '.avi', '.mkv']);\n\nexport interface AttachmentBudgetConfig {\n attachScreenshots: boolean;\n maxAttachmentBytes: number;\n maxTotalAttachmentBytes: number;\n maxVideoBytes: number;\n outputDir: string;\n}\n\n/** One `World.attach()` call (real user attachment, or `qualflare.attachment\n * ()`/`attachmentFromFile()`), not yet resolved into a wire `Attachment`.\n * Exactly one of `content`/`path` is set — `content` for cucumber-js's\n * native in-memory delivery (a Buffer/base64 string, the common case for\n * real `World.attach()` calls) or `qualflare.attachment()`; `path` only for\n * `qualflare.attachmentFromFile()`, cucumber-js itself never delivers a bare\n * file path. */\nexport interface PendingAttachment {\n name: string;\n mimeType?: string;\n stepIndex?: number;\n /** Base64-encoded. */\n content?: string;\n path?: string;\n}\n\n/**\n * Tracks cumulative attached bytes across the whole `cucumber-js` process\n * (one instance per formatter, reused across every attachment resolved),\n * so the final POST doesn't silently exceed the request body limit. Ported\n * verbatim in spirit from `@qualflare/cypress`'s `AttachmentBudget`.\n */\nexport class AttachmentBudget {\n private used = 0;\n\n constructor(private readonly maxTotalBytes: number) {}\n\n /** Atomically checks-and-reserves `bytes` against the remaining budget.\n * Returns false (reserving nothing) if it would exceed the total. */\n tryReserve(bytes: number): boolean {\n if (this.used + bytes > this.maxTotalBytes) {\n return false;\n }\n this.used += bytes;\n return true;\n }\n\n get usedBytes(): number {\n return this.used;\n }\n}\n\ntype ReadResult = { skipped: false; content: string } | { skipped: true; reason: string };\n\nexport function isVideoLike(mimeType: string | undefined, filePath: string | undefined): boolean {\n if (mimeType?.toLowerCase().startsWith('video/')) {\n return true;\n }\n if (filePath && VIDEO_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {\n return true;\n }\n return false;\n}\n\nfunction readAttachmentFile(filePath: string, maxAttachmentBytes: number, budget: AttachmentBudget): ReadResult {\n let size: number;\n try {\n // Stat BEFORE reading — an oversized file must never be loaded into\n // memory just to discover it should be skipped.\n size = fs.statSync(filePath).size;\n } catch (err) {\n return { skipped: true, reason: `could not stat file: ${(err as Error).message}` };\n }\n if (size > maxAttachmentBytes) {\n return {\n skipped: true,\n reason: `${size} bytes exceeds the configured per-attachment cap of ${maxAttachmentBytes} bytes`,\n };\n }\n if (!budget.tryReserve(size)) {\n return {\n skipped: true,\n reason: `would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n };\n }\n try {\n const content = fs.readFileSync(filePath).toString('base64');\n return { skipped: false, content };\n } catch (err) {\n return { skipped: true, reason: `could not read file: ${(err as Error).message}` };\n }\n}\n\n/**\n * Resolves one NON-video pending attachment into a wire `Attachment`, or\n * `undefined` if it should be skipped entirely (per the plan's resolved\n * decision — an oversized/over-budget attachment is dropped, not degraded\n * to a contentless stub, since the server's `path` field is explicitly\n * informational/never-fetched). Unlike `@qualflare/cypress`'s\n * `resolveAttachments()` (which batch-resolves a Case's whole array at\n * case-finish time), this resolves one attachment at a time as its\n * `attachment` envelope arrives — matching cucumber-js's per-envelope\n * event stream.\n *\n * Callers MUST check `isVideoLike()` first and route a video-like pending\n * attachment to `resolveVideoAttachment()` instead — this function assumes\n * it is not one (see `attempt-tracker.ts`'s call sites).\n */\nexport function resolvePendingAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (!config.attachScreenshots) {\n return undefined;\n }\n if (pending.content !== undefined) {\n const bytes = Buffer.byteLength(pending.content, 'base64');\n if (bytes > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${pending.name}\": ${bytes} bytes exceeds the configured per-attachment cap of ${config.maxAttachmentBytes} bytes`,\n );\n return undefined;\n }\n if (!budget.tryReserve(bytes)) {\n logger.warn(\n `skipping attachment \"${pending.name}\": would exceed this run's total attachment budget (${budget.usedBytes} bytes already used)`,\n );\n return undefined;\n }\n return { name: pending.name, mimeType: pending.mimeType, content: pending.content, stepIndex: pending.stepIndex };\n }\n if (pending.path) {\n const result = readAttachmentFile(pending.path, config.maxAttachmentBytes, budget);\n if (result.skipped) {\n logger.warn(`skipping attachment \"${pending.name}\" (${pending.path}): ${result.reason}`);\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: pending.mimeType,\n content: result.content,\n path: pending.path,\n stepIndex: pending.stepIndex,\n };\n }\n return undefined;\n}\n\n/**\n * Resolves one video-like pending attachment (`isVideoLike()` already true)\n * into a wire `Attachment` carrying `storageKey`/`fileSize` instead of\n * `content`, via the presigned-upload-URL flow — or `undefined` if it should\n * be skipped (uploads disabled, unsupported format, oversized, or a\n * network/API error; each case logs why). Async, unlike\n * `resolvePendingAttachment` — see `attempt-tracker.ts`'s\n * `pendingVideoWrites` for how callers reconcile that with cucumber-js's\n * synchronous, per-envelope event stream.\n */\nexport async function resolveVideoAttachment(\n pending: PendingAttachment,\n config: AttachmentBudgetConfig,\n): Promise<Attachment | undefined> {\n if (!config.attachScreenshots) {\n return undefined;\n }\n const written = writeVideoAttachment(pending, config.outputDir, config.maxVideoBytes);\n if (!written) {\n // writeVideoAttachment already logged why.\n return undefined;\n }\n return {\n name: pending.name,\n mimeType: written.mimeType,\n localVideoPath: written.localVideoPath,\n fileSize: written.fileSize,\n stepIndex: pending.stepIndex,\n };\n}\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { logger } from '../shared/logger.js';\n\n\n/** Extension <-> MIME type for the video formats the server accepts (see\n * `launch.AllowedAttachmentUploadMimeTypes` server-side). */\nconst VIDEO_MIME_TYPES_BY_EXTENSION: Record<string, string> = {\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.mov': 'video/quicktime',\n};\nconst EXTENSION_BY_VIDEO_MIME_TYPE: Record<string, string> = {\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'video/quicktime': '.mov',\n};\n\nexport interface ResolvedVideoMimeType {\n mimeType: string;\n extension: string;\n}\n\n/**\n * Determines the server-accepted `{mimeType, extension}` pair for a pending\n * video attachment, or `undefined` if neither `filePath`'s extension nor\n * `mimeType` maps to one of the three formats the server allows.\n * `filePath` (a real local file, from `qualflare.attachmentFromFile()`)\n * takes priority when present — its extension is authoritative and cannot\n * disagree with itself the way a caller-supplied `mimeType` claim could.\n * Without a `filePath` (in-memory `World.attach()`/`qualflare.attachment()`\n * content), `mimeType` is the only signal available.\n */\nexport function resolveVideoMimeType(mimeType: string | undefined, filePath: string | undefined): ResolvedVideoMimeType | undefined {\n if (filePath) {\n const extension = path.extname(filePath).toLowerCase();\n const resolvedMimeType = VIDEO_MIME_TYPES_BY_EXTENSION[extension];\n return resolvedMimeType ? { mimeType: resolvedMimeType, extension } : undefined;\n }\n const normalized = mimeType?.toLowerCase();\n const extension = normalized ? EXTENSION_BY_VIDEO_MIME_TYPE[normalized] : undefined;\n return normalized && extension ? { mimeType: normalized, extension } : undefined;\n}\n\nexport interface VideoWriteResult {\n /** Filename relative to the `outputDir` this was written into. */\n localVideoPath: string;\n fileSize: number;\n mimeType: string;\n}\n\n/**\n * Writes one pending video attachment's bytes into `outputDir` under a\n * unique filename — copying (`fs.copyFileSync`) when it names a real local\n * file, or decoding+writing when it's in-memory base64 content (the\n * `World.attach()`/`qualflare.attachment()` path, which has no file to\n * copy). Unlike qualflare-cypress, where a video is always a file Cypress\n * recorded, this formatter has to handle both — cucumber-js has no\n * \"one recorded file per run\" concept.\n *\n * `qualflare-cli` uploads whatever lands here later, once it has a real\n * auth token; this process never makes a network call.\n *\n * Best-effort: any failure (unsupported format, oversized, unreadable\n * source, write failure) is logged as a warning and returns `undefined`\n * rather than throwing — a video is never worth failing a test run over.\n */\nexport function writeVideoAttachment(\n pending: { name: string; mimeType?: string; path?: string; content?: string },\n outputDir: string,\n maxVideoBytes: number,\n): VideoWriteResult | undefined {\n const resolved = resolveVideoMimeType(pending.mimeType, pending.path);\n if (!resolved) {\n logger.warn(`skipping video attachment \"${pending.name}\": unsupported video format.`);\n return undefined;\n }\n\n const localVideoPath = `${randomUUID()}${resolved.extension}`;\n const destination = path.join(outputDir, localVideoPath);\n\n if (pending.path !== undefined) {\n let fileSize: number;\n try {\n fileSize = fs.statSync(pending.path).size;\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.path}\": could not stat file: ${(err as Error).message}`);\n return undefined;\n }\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${pending.path}\": ${fileSize} bytes exceeds the configured maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.copyFileSync(pending.path, destination);\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.path}\": could not copy file: ${(err as Error).message}`);\n return undefined;\n }\n return { localVideoPath, fileSize, mimeType: resolved.mimeType };\n }\n\n if (pending.content !== undefined) {\n const fileSize = Buffer.byteLength(pending.content, 'base64');\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${pending.name}\": ${fileSize} bytes exceeds the configured maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.writeFileSync(destination, Buffer.from(pending.content, 'base64'));\n } catch (err) {\n logger.warn(`skipping video attachment \"${pending.name}\": could not write file: ${(err as Error).message}`);\n return undefined;\n }\n return { localVideoPath, fileSize, mimeType: resolved.mimeType };\n }\n\n return undefined;\n}\n","import {\n getWorstTestStepResult,\n type Attachment as MessageAttachment,\n type Pickle,\n type TestCase,\n type TestCaseFinished,\n type TestCaseStarted,\n type TestStepFinished,\n type TestStepResult,\n type TestStepStarted,\n} from '@cucumber/messages';\n\nimport { RESERVED_MESSAGE_MEDIA_TYPE, MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { messageDurationToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { ManualStepRecord, RuntimeMessage } from '../runtime/message-types.js';\nimport type { Attachment, CasePriority, Label, Link, Step } from '../shared/types.js';\nimport {\n AttachmentBudget,\n isVideoLike,\n resolvePendingAttachment,\n resolveVideoAttachment,\n type AttachmentBudgetConfig,\n type PendingAttachment,\n} from './attachment-budget.js';\nimport { collapseAttempts, type AttemptSnapshot, type CollapsedResult } from './case-builder.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapHookStep, mapPickleStep, mapStatus } from './step-mapper.js';\n\nexport interface FinishedAttempts {\n uri: string;\n pickleId: string;\n collapsed: CollapsedResult;\n}\n\ninterface AttemptRecord {\n testCase: TestCase;\n pickle: Pickle;\n attempt: number;\n startedAtMs: number;\n steps: Step[];\n stepResults: TestStepResult[];\n /** Maps a `TestStep.id` to its index in `steps`, once finished — used to\n * correlate a same-step attachment's `stepIndex` on the wire. */\n stepIndexByTestStepId: Map<string, number>;\n currentTestStepId?: string;\n manualSteps: ManualStepRecord[];\n manualStepStack: number[];\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n stepCapWarned: boolean;\n /** Not-yet-settled video writes (see `resolveVideoAttachment`) started\n * during this attempt. Each one, once settled, pushes its resulting\n * `Attachment` onto `attachments` above (mutating the array in place —\n * never reassigning it — so a reference already captured elsewhere still\n * sees the push; see `finish()`'s doc comment for why this matters).\n * `finish()` awaits every attempt's own queue before collapsing, so\n * `attachments` is always complete by the time it's read.\n *\n * Still genuinely needed even though writing a video is now synchronous\n * filesystem work rather than an upload: `resolveVideoAttachment` remains\n * an `async` function, so the `.then()` that performs the push runs on a\n * microtask, not inline. Dropping this queue would reintroduce exactly the\n * bug `finish()` documents. */\n pendingVideoWrites: Promise<void>[];\n}\n\n/**\n * The retry-safe core. Two lookup layers, both required because\n * cucumber-js's *grouping* key (a logical scenario, stable across retries —\n * `testCase.id`) and its *live-event-addressing* key (one specific attempt\n * — `testCaseStarted.id`) are genuinely different fields on the wire.\n * `testCaseFinished.willBeRetried === false` is the sole authoritative\n * \"this scenario is really done\" signal — no hash-based grouping anywhere\n * (the deliberate fix for `allure-framework/allure-js#625`/`#1502`, both\n * real bugs caused by Allure's content-hash retry/outline-row grouping).\n */\nexport class AttemptTracker {\n private readonly byTestCaseId = new Map<string, AttemptRecord[]>();\n private readonly byTestCaseStartedId = new Map<string, AttemptRecord>();\n\n constructor(\n private readonly hookIndex: HookIndex,\n private readonly gherkin: GherkinIndex,\n private readonly config: { includeStepHooks: boolean } & AttachmentBudgetConfig,\n private readonly attachmentBudget: AttachmentBudget,\n ) {}\n\n begin(e: TestCaseStarted, testCase: TestCase, pickle: Pickle): void {\n const record: AttemptRecord = {\n testCase,\n pickle,\n attempt: e.attempt,\n startedAtMs: timestampMs(e.timestamp),\n steps: [],\n stepResults: [],\n stepIndexByTestStepId: new Map(),\n manualSteps: [],\n manualStepStack: [],\n labels: [],\n links: [],\n tags: [],\n properties: {},\n attachments: [],\n stepCapWarned: false,\n pendingVideoWrites: [],\n };\n this.byTestCaseStartedId.set(e.id, record);\n const attempts = this.byTestCaseId.get(testCase.id) ?? [];\n attempts.push(record);\n this.byTestCaseId.set(testCase.id, attempts);\n }\n\n stepStarted(e: TestStepStarted): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = e.testStepId;\n }\n\n stepFinished(e: TestStepFinished): void {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n record.currentTestStepId = undefined;\n record.stepResults.push(e.testStepResult);\n\n const testStep = record.testCase.testSteps.find((s) => s.id === e.testStepId);\n if (!testStep) {\n return;\n }\n\n let step: Step | undefined;\n if (testStep.pickleStepId) {\n const pickleStep = record.pickle.steps.find((s) => s.id === testStep.pickleStepId);\n if (pickleStep) {\n step = mapPickleStep(record.pickle.uri, pickleStep, e.testStepResult, this.gherkin);\n }\n } else if (testStep.hookId) {\n const hook = this.hookIndex.get(testStep.hookId);\n if (hook && (hook.kind === 'before' || hook.kind === 'after')) {\n step = mapHookStep(hook, e.testStepResult);\n } else if (hook && (hook.kind === 'beforeStep' || hook.kind === 'afterStep') && this.config.includeStepHooks) {\n // Deliberately NOT nested under the pickle step it wraps: BeforeStep\n // fires (and is pushed here) BEFORE that step, so \"the most recently\n // pushed step\" would be the PREVIOUS, unrelated step at that point —\n // an earlier version of this nested BeforeStep under the wrong\n // parent. AfterStep could be nested correctly (its wrapped step is\n // already pushed by the time it fires), but nesting one and not the\n // other would be inconsistent, so both stay flat, root-level steps —\n // still informative via their position immediately adjacent to the\n // step they wrap in the flat, chronologically-ordered array.\n step = mapHookStep(hook, e.testStepResult);\n }\n }\n\n if (!step) {\n return;\n }\n if (record.steps.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!record.stepCapWarned) {\n record.stepCapWarned = true;\n logger.warn(\n `reached the ${MAX_STEPS_PER_TEST_ATTEMPT}-step-per-attempt cap — further steps in this scenario attempt will not be uploaded.`,\n );\n }\n return;\n }\n record.stepIndexByTestStepId.set(e.testStepId, record.steps.length);\n record.steps.push(step);\n }\n\n /** Handles both a real user `World.attach()` call (becomes a wire\n * `Attachment`) and a `qualflare.*()` reserved-media-type message\n * (unwrapped and applied as a model mutation instead). */\n attachment(e: MessageAttachment): void {\n if (!e.testCaseStartedId) {\n // A BeforeAll/AfterAll-scoped attachment (`testRunHookStartedId` set\n // instead) — there is no Case to attach it to; see `run-hook-tracker\n // .ts`'s doc comment for why BeforeAll/AfterAll attachments are out of\n // scope for v1.\n return;\n }\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n if (!record) {\n return;\n }\n\n if (e.mediaType === RESERVED_MESSAGE_MEDIA_TYPE) {\n let message: RuntimeMessage;\n try {\n message = JSON.parse(e.contentEncoding === 'BASE64' ? Buffer.from(e.body, 'base64').toString('utf8') : e.body);\n } catch {\n logger.warn('received a malformed qualflare runtime message — ignoring it.');\n return;\n }\n this.applyRuntimeMessage(record, message, e.testStepId);\n return;\n }\n\n const stepIndex = this.resolveStepIndex(record, e.testStepId);\n const content = e.contentEncoding === 'BASE64' ? e.body : Buffer.from(e.body, 'utf8').toString('base64');\n this.resolveAttachment(record, { name: e.fileName || 'attachment', mimeType: e.mediaType, content, stepIndex });\n }\n\n /** Resolves one pending attachment, routing a video-like one through the\n * write-to-`outputDir` flow (tracked in `record.pendingVideoWrites` so\n * `finish()` can wait for it) and everything else through the synchronous\n * inline path — shared by the real `World.attach()` handler above and both\n * `qualflare.attachment()`/`attachmentFromFile()` runtime-message cases\n * below. */\n private resolveAttachment(record: AttemptRecord, pending: PendingAttachment): void {\n if (isVideoLike(pending.mimeType, pending.path)) {\n const write = resolveVideoAttachment(pending, this.config).then((resolved) => {\n if (resolved) {\n record.attachments.push(resolved);\n }\n });\n record.pendingVideoWrites.push(write);\n return;\n }\n const resolved = resolvePendingAttachment(pending, this.config, this.attachmentBudget);\n if (resolved) {\n record.attachments.push(resolved);\n }\n }\n\n /** Resolves which step index an attachment (real or `qualflare.*()`\n * message) belongs to. `stepIndexByTestStepId` only gets an entry once a\n * step is FINISHED — but `World.attach()` (the only channel available,\n * used by both a real user attachment and every `qualflare.*()` call) is\n * always called from WITHIN a step's body, i.e. strictly BETWEEN that\n * step's `testStepStarted` and `testStepFinished`. So the map lookup\n * alone can never resolve the step that's currently attaching — this was\n * a real bug found in self-review, caught by a unit test that attaches\n * mid-step instead of only after it finishes. While a step is in flight,\n * `record.steps.length` (the array's CURRENT length, before that step has\n * been pushed) is exactly the index it will occupy once it does finish —\n * single-threaded, event-ordered execution guarantees nothing else can be\n * pushed in between. */\n private resolveStepIndex(record: AttemptRecord, testStepId: string | undefined): number | undefined {\n if (testStepId === undefined) {\n return undefined;\n }\n if (record.currentTestStepId === testStepId) {\n return record.steps.length;\n }\n return record.stepIndexByTestStepId.get(testStepId);\n }\n\n private applyRuntimeMessage(record: AttemptRecord, message: RuntimeMessage, testStepId: string | undefined): void {\n switch (message.type) {\n case 'label':\n record.labels.push({ name: message.name, value: message.value });\n return;\n case 'link':\n record.links.push({ type: message.linkType ?? 'custom', name: message.name, url: message.url });\n return;\n case 'tag':\n record.tags.push(...message.tags);\n return;\n case 'description':\n record.description = message.text;\n return;\n case 'priority':\n record.priority = message.value;\n return;\n case 'parameter': {\n const openStepIndex = record.manualStepStack[record.manualStepStack.length - 1];\n if (openStepIndex !== undefined) {\n const step = record.manualSteps[openStepIndex]!;\n step.parameters = step.parameters ?? [];\n step.parameters.push({ name: message.name, value: message.value, masked: message.masked });\n } else {\n record.properties[message.name] = message.value ?? '';\n }\n return;\n }\n case 'attachment': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n this.resolveAttachment(record, { name: message.name, mimeType: message.mimeType, content: message.contentBase64, stepIndex });\n return;\n }\n case 'attachment_from_file': {\n const stepIndex = testStepId !== undefined ? record.stepIndexByTestStepId.get(testStepId) : undefined;\n this.resolveAttachment(record, { name: message.name, mimeType: message.mimeType, path: message.path, stepIndex });\n return;\n }\n case 'step_start': {\n const parentIndex = record.manualStepStack.length > 0 ? record.manualStepStack[record.manualStepStack.length - 1] : undefined;\n const step: ManualStepRecord = { name: message.name, status: 'passed', startedAt: message.timestamp, parentIndex };\n record.manualStepStack.push(record.manualSteps.length);\n record.manualSteps.push(step);\n return;\n }\n case 'step_stop': {\n const openIndex = record.manualStepStack.pop();\n if (openIndex === undefined) {\n return;\n }\n const step = record.manualSteps[openIndex]!;\n step.status = message.status;\n step.error = message.error;\n step.durationMs = Math.max(0, message.timestamp - step.startedAt);\n return;\n }\n }\n }\n\n /** Returns the collapsed result once all attempts of this logical\n * scenario have arrived, or `undefined` if more attempts are coming\n * (`willBeRetried === true`).\n *\n * Async because it must first await every attempt's own\n * `pendingVideoWrites` (any video attached anywhere across every retry\n * of this scenario). This is load-bearing, not just tidiness:\n * `collapseAttempts`/`buildCase` read `attachments` by REFERENCE, not by\n * copy, and `buildCase` runs synchronously right after this resolves — a\n * scenario whose ONLY attachment is a still-uploading video would have an\n * EMPTY `attachments` array at that instant, and `buildCase` captures\n * `attachments.length > 0 ? attachments : undefined` as a plain\n * `undefined` VALUE right then, permanently — a later push onto the\n * (still-live) array reference would no longer be visible through\n * `undefined`. Awaiting here first guarantees `attachments` is complete\n * before `buildCase` ever reads it. */\n async finish(e: TestCaseFinished): Promise<FinishedAttempts | undefined> {\n const record = this.byTestCaseStartedId.get(e.testCaseStartedId);\n this.byTestCaseStartedId.delete(e.testCaseStartedId);\n if (!record) {\n return undefined;\n }\n if (e.willBeRetried) {\n return undefined;\n }\n\n const testCaseId = record.testCase.id;\n const attempts = this.byTestCaseId.get(testCaseId) ?? [record];\n this.byTestCaseId.delete(testCaseId);\n\n const pendingWrites = attempts.flatMap((a) => a.pendingVideoWrites);\n if (pendingWrites.length > 0) {\n await Promise.all(pendingWrites);\n }\n\n const snapshots: AttemptSnapshot[] = attempts.map((a) => {\n const worst = a.stepResults.length > 0 ? getWorstTestStepResult(a.stepResults) : undefined;\n const duration = a.stepResults.reduce((sum, r) => sum + messageDurationToNs(r.duration), 0);\n return {\n status: worst ? mapStatus(worst.status) : 'passed',\n duration,\n // `message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically across the peer-dependency\n // range; `exception.stackTrace` is not version-safe).\n error: worst?.message || worst?.exception?.stackTrace || worst?.exception?.message,\n steps: a.steps,\n manualSteps: a.manualSteps,\n labels: a.labels,\n links: a.links,\n tags: a.tags,\n description: a.description,\n priority: a.priority,\n properties: a.properties,\n attachments: a.attachments,\n };\n });\n\n return {\n uri: record.pickle.uri,\n pickleId: record.testCase.pickleId,\n collapsed: collapseAttempts(snapshots),\n };\n }\n}\n\nfunction timestampMs(ts: { seconds: number; nanos: number }): number {\n return ts.seconds * 1000 + Math.floor(ts.nanos / 1_000_000);\n}\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_000_000;\nconst NS_PER_SECOND = 1_000_000_000;\n\n/**\n * Converts a plain millisecond duration (e.g. from `Date.now()` deltas used\n * by manual `qualflare.step()` timing) into the wire format's raw-nanosecond\n * 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/**\n * Converts a `@cucumber/messages` structured `Duration` ({seconds, nanos})\n * into the wire format's raw-nanosecond integer, without round-tripping\n * through milliseconds (which would lose sub-millisecond precision Cucumber\n * already reports natively). Negative/malformed input is clamped to 0, same\n * defensive posture as `msToNs`.\n */\nexport function messageDurationToNs(duration: { seconds: number; nanos: number } | undefined): NanosecondDuration {\n if (!duration || !Number.isFinite(duration.seconds) || !Number.isFinite(duration.nanos)) {\n return 0;\n }\n const total = duration.seconds * NS_PER_SECOND + duration.nanos;\n return total > 0 ? Math.round(total) : 0;\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 type { Pickle } from '@cucumber/messages';\n\nimport { MAX_ATTEMPTS_PER_CASE, MAX_ATTEMPT_MESSAGE_RUNES, MAX_TAGS_PER_CASE } from '../shared/constants.js';\nimport { truncateRunes } from '../shared/text.js';\nimport { msToNs } from '../shared/duration.js';\nimport type { ManualStepRecord } from '../runtime/message-types.js';\nimport type { Attachment, Attempt, Case, CasePriority, CaseStatus, Label, Link, NanosecondDuration, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\n\n/** One attempt of a scenario — cucumber-js's `--retry` re-runs the same\n * pickle from scratch (fresh World, all hooks/steps rerun); each attempt\n * (`testCaseStarted`→...→`testCaseFinished`) produces one of these. Ported\n * from `@qualflare/cypress`'s `AttemptSnapshot`/`collapseAttempts` — same\n * \"final attempt wins for content, but count+sum across all attempts\"\n * rules — fed by envelope-derived data here instead of Mocha-derived data. */\nexport interface AttemptSnapshot {\n status: CaseStatus;\n /** NANOSECONDS — already summed across this attempt's real step results;\n * see `attempt-tracker.ts`. No ms round-trip, unlike the Cypress version:\n * Cucumber's own `TestStepResult.duration` is nanosecond-precision. */\n duration: NanosecondDuration;\n error?: string;\n /** Real Gherkin + hook steps, already wire-shaped by `step-mapper.ts`. */\n steps: Step[];\n manualSteps: ManualStepRecord[];\n labels: Label[];\n links: Link[];\n /** Dynamically added via `qualflare.tag()` during this attempt — merged\n * with the pickle's own static `@tag`s separately, in `case-builder.ts`'s\n * `buildCase()`, since those are the same across every attempt. */\n tags: string[];\n description?: string;\n priority?: CasePriority;\n properties: Record<string, string>;\n attachments: Attachment[];\n}\n\nexport interface CollapsedResult {\n status: CaseStatus;\n /** NANOSECONDS — sum of every attempt (reflects true CI wall-clock cost,\n * not just the final attempt's duration). */\n duration: NanosecondDuration;\n retryCount: number;\n isFlaky: boolean;\n /** Per-attempt history, present only when the scenario actually retried\n * (>= 2 attempts). Durations are NANOSECONDS, as everywhere in this module\n * — cucumber-js reports nanosecond-precision natively, so unlike the Cypress\n * port there is no ms round-trip anywhere on this path. */\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. */\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 `realSteps` (Gherkin + hook\n * steps, indices/`parentIndex` valid only relative to each other) into one\n * combined, correctly-indexed `Step[]`. Ported verbatim in spirit from\n * `@qualflare/cypress`'s `combineSteps()`. */\nfunction combineSteps(realSteps: Step[], manualSteps: ManualStepRecord[]): Step[] | undefined {\n if (realSteps.length === 0 && manualSteps.length === 0) {\n return undefined;\n }\n const offsetManual: Step[] = manualSteps.map((record) => {\n const step: Step = {\n name: record.name,\n status: record.status,\n duration: msToNs(record.durationMs ?? 0),\n };\n if (record.error) step.error = record.error;\n if (record.parentIndex !== undefined) step.parentIndex = record.parentIndex + realSteps.length;\n if (record.parameters && record.parameters.length > 0) step.parameters = record.parameters;\n return step;\n });\n return [...realSteps, ...offsetManual];\n}\n\n/**\n * Collapses every attempt of one logical scenario (grouped by cucumber-js's\n * own stable `testCase.id`, NOT a content hash — see `attempt-tracker.ts`)\n * into the single `Case` this reporter uploads. `willBeRetried === false` on\n * the final `testCaseFinished` is the caller's signal that all attempts have\n * arrived; 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 * The rest of `collapseAttempts` keeps only the final attempt's data — steps,\n * labels, attachments — because an abandoned attempt's step trace would\n * misrepresent one execution as if the same commands ran twice. That reasoning\n * does not apply here: these ARE separate executions, and saying so is the\n * whole point. `retryCount` says a scenario retried; this says what failed\n * each time.\n *\n * # Why a single attempt sends nothing\n *\n * The server discards a one-element array — a scenario that ran once has no\n * history beyond what the Case already carries — so sending one spends payload\n * against the 10MB body limit for a row that is dropped.\n *\n * # Why the error goes to `message`\n *\n * `AttemptSnapshot.error` is already a single formatted string; cucumber-js\n * does not hand back a separate stack. Splitting it to fill `trace` would need\n * to guess where the message ends, and a multiline Gherkin assertion message\n * would be corrupted by that guess. The server truncates `message` at 8192\n * runes rather than rejecting, and the Case's own `error` still carries the\n * final attempt's full text.\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, dropping 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: 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\nexport interface FinishedCase {\n uri: string;\n case: Case;\n}\n\n/** Assembles the final wire `Case` for one finished (all-attempts-collapsed)\n * scenario. */\nexport function buildCase(uri: string, pickle: Pickle, collapsed: CollapsedResult, gherkin: GherkinIndex): FinishedCase {\n const entry = gherkin.get(uri);\n // `pickle.astNodeIds` is `[scenarioId]` for a plain Scenario, or\n // `[scenarioId, exampleRowId]` for one row of a Scenario Outline — the\n // scenario's own AST id is always first.\n const scenarioId = pickle.astNodeIds[0];\n const scenarioEntry = scenarioId ? entry?.scenarioById.get(scenarioId) : undefined;\n const featureName = entry?.featureName;\n const ruleName = scenarioEntry?.ruleName;\n const className = ruleName ? `${featureName ?? ''} > ${ruleName}` : featureName;\n\n const labels: Label[] = [...collapsed.labels];\n if (featureName) {\n labels.push({ name: 'feature', value: featureName });\n }\n if (ruleName) {\n labels.push({ name: 'rule', value: ruleName });\n }\n\n const properties: Record<string, string> = { ...examplesRowProperties(scenarioEntry, pickle), ...collapsed.properties };\n\n // `pickle.tags` already has Feature/Rule `@tag`s inherited/flattened onto\n // every scenario by cucumber-js itself — no manual inheritance needed.\n const staticTags = pickle.tags.map((t) => t.name.replace(/^@/, ''));\n const tags = [...new Set([...staticTags, ...collapsed.tags])].slice(0, MAX_TAGS_PER_CASE);\n\n const kase: Case = {\n // Stable across runs (unchanged unless the file is edited), and\n // includes the source line specifically so two identically-named\n // Scenarios in one feature file can never collide — the exact bug\n // that's open upstream in allure-js (allure-framework/allure-js#1502).\n id: `${uri}:${pickle.location?.line ?? 0}#${pickle.name}`,\n name: pickle.name,\n status: collapsed.status,\n duration: collapsed.duration,\n retryCount: collapsed.retryCount || undefined,\n isFlaky: collapsed.isFlaky || undefined,\n attempts: collapsed.attempts,\n error: collapsed.error,\n tags: tags.length > 0 ? tags : undefined,\n steps: collapsed.steps,\n labels: labels.length > 0 ? labels : undefined,\n links: collapsed.links.length > 0 ? collapsed.links : undefined,\n // `||`, not `??`: cucumber-js's `Scenario.description` is always a\n // defined string, `''` when the Gherkin source has none — `??` would\n // never fall through and every scenario without one would send an\n // empty-string description instead of omitting the field (found via a\n // real tarball-installed smoke test, not just reasoning about types).\n description: collapsed.description || scenarioEntry?.scenario.description || undefined,\n priority: collapsed.priority,\n properties: Object.keys(properties).length > 0 ? properties : undefined,\n attachments: collapsed.attachments.length > 0 ? collapsed.attachments : undefined,\n };\n if (className) {\n kase.className = className;\n }\n return { uri, case: kase };\n}\n\n/** For a Scenario Outline row, folds that row's concrete Examples values\n * into `Case.properties` (`{columnName: cellValue}`) — the same AST\n * `tableBody`-correlation technique verified in `allure-cucumberjs`'s real\n * source. Every row's values show up on that row's Case automatically, with\n * zero extra author effort. */\nfunction examplesRowProperties(\n scenarioEntry: { scenario: { examples: readonly { tableHeader?: { cells: readonly { value: string }[] }; tableBody: readonly { id: string; cells: readonly { value: string }[] }[] }[] } } | undefined,\n pickle: Pickle,\n): Record<string, string> {\n if (!scenarioEntry) {\n return {};\n }\n const rowAstIds = new Set(pickle.astNodeIds);\n for (const examples of scenarioEntry.scenario.examples) {\n const header = examples.tableHeader?.cells;\n if (!header) {\n continue;\n }\n const row = examples.tableBody.find((r) => rowAstIds.has(r.id));\n if (!row) {\n continue;\n }\n const properties: Record<string, string> = {};\n header.forEach((cell, i) => {\n const value = row.cells[i]?.value;\n if (cell.value && value !== undefined) {\n properties[cell.value] = value;\n }\n });\n return properties;\n }\n return {};\n}\n","import { TestStepResultStatus, type PickleStep, type PickleStepArgument, type TestStepResult } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { CaseStatus, Parameter, Step } from '../shared/types.js';\nimport type { GherkinIndex } from './gherkin-index.js';\nimport type { HookInfo } from './hook-index.js';\n\n/** Maps cucumber-js's step/scenario result status onto the wire contract's\n * `CaseStatus` vocabulary. `UNDEFINED` (no matching step definition) and\n * `AMBIGUOUS` (more than one matching definition) are both authoring/config\n * problems rather than a real pass/fail outcome — `'error'` is the closest\n * semantic fit in the wire vocabulary, distinct from a genuine `'failed'`\n * assertion. */\nexport function mapStatus(status: TestStepResultStatus): CaseStatus {\n switch (status) {\n case TestStepResultStatus.PASSED:\n return 'passed';\n case TestStepResultStatus.FAILED:\n return 'failed';\n case TestStepResultStatus.SKIPPED:\n return 'skipped';\n case TestStepResultStatus.PENDING:\n return 'pending';\n case TestStepResultStatus.UNDEFINED:\n case TestStepResultStatus.AMBIGUOUS:\n case TestStepResultStatus.UNKNOWN:\n default:\n return 'error';\n }\n}\n\n/** `TestStepResult.message` is the version-safe field to prefer: verified\n * empirically (real spawned runs, not just docs) that it reliably contains\n * the full \"Error: <message>\\n at ...\" text on both the peer floor\n * (cucumber-js 10.9.0) and the latest (13.2.1) — `exception.stackTrace`\n * does NOT: on 10.9.0 it's stack-frames only, with no message text at all,\n * while on 13.2.1 it happens to duplicate the full combined text. Preferring\n * `stackTrace` first (an earlier version of this function did) silently\n * produced a message-less error on 10.9.0 — caught by running the real CI\n * version matrix locally before trusting it, not by reasoning about the\n * schema alone. */\nfunction formatError(result: TestStepResult): string | undefined {\n return result.message || result.exception?.stackTrace || result.exception?.message;\n}\n\n/** Doc Strings and Data Tables have no dedicated field on the wire `Step`\n * contract (confirmed against `launch.go` — the only structured-payload\n * slot on a step is the flat `parameters` list) — encoded as one Parameter\n * each, documented as a workaround in `docs/LIMITATIONS.md`. A Data Table is\n * JSON-stringified as one Parameter rather than exploded into one Parameter\n * per cell, to avoid risking `MAX_PARAMETERS_PER_STEP` on a large table. */\nexport function pickleStepArgumentToParameters(argument: PickleStepArgument | undefined): Parameter[] | undefined {\n if (!argument) {\n return undefined;\n }\n const params: Parameter[] = [];\n if (argument.docString) {\n params.push({ name: 'docString', value: argument.docString.content });\n }\n if (argument.dataTable) {\n const rows = argument.dataTable.rows.map((row) => row.cells.map((cell) => cell.value));\n params.push({ name: 'dataTable', value: JSON.stringify(rows) });\n }\n return params.length > 0 ? params : undefined;\n}\n\n/** Builds a wire `Step` for a real Gherkin (Given/When/Then/And/But) step. */\nexport function mapPickleStep(\n uri: string,\n pickleStep: PickleStep,\n result: TestStepResult,\n gherkin: GherkinIndex,\n): Step {\n const resolved = gherkin.resolveKeyword(uri, pickleStep.astNodeIds);\n const step: Step = {\n name: pickleStep.text,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n if (resolved?.keyword) {\n step.keyword = resolved.keyword.trim();\n }\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n const parameters = pickleStepArgumentToParameters(pickleStep.argument);\n if (parameters) {\n step.parameters = parameters;\n }\n return step;\n}\n\n/** Builds a synthetic wire `Step` for a `Before`/`After` (or, when enabled,\n * `BeforeStep`/`AfterStep`) hook execution — see design decision (a) in the\n * plan: hooks are folded directly into the flat/nested `Step[]` rather than\n * needing a separate \"fixture\" model concept the way Allure's richer model\n * requires, since our wire contract has no such concept anyway. */\nconst HOOK_LABELS: Record<HookInfo['kind'], string> = {\n before: 'Before',\n after: 'After',\n beforeStep: 'BeforeStep',\n afterStep: 'AfterStep',\n beforeAll: 'BeforeAll',\n afterAll: 'AfterAll',\n};\n\nexport function mapHookStep(hook: HookInfo, result: TestStepResult): Step {\n // A distinct label per hook kind (not just \"Before\"/\"After\" for\n // everything) so a step-level hook is distinguishable from a case-level\n // one in the uploaded data when `includeStepHooks` is enabled.\n const label = HOOK_LABELS[hook.kind];\n const step: Step = {\n name: hook.name || `${label} hook`,\n keyword: label,\n status: mapStatus(result.status),\n duration: messageDurationToNs(result.duration),\n };\n const error = formatError(result);\n if (error) {\n step.error = error;\n }\n return step;\n}\n","import * as os from 'node:os';\n\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport type { ResolvedFormatterConfig } from '../config/resolve-config.js';\nimport type { Collect, Suite } from '../shared/types.js';\n\nfunction resolveOs(config: ResolvedFormatterConfig): string {\n if (config.os) {\n return config.os;\n }\n return `${os.type()} ${os.release()}`;\n}\n\n/**\n * Assembles the final `Collect` payload from every finished `Suite`, at\n * `finished()`. CI metadata and branch/commit auto-detection are already\n * fully resolved by `resolve-config.ts` — this function just reads the\n * resolved config through, it does not call `ci-detect.ts`/`git-detect.ts`\n * itself. Unlike `@qualflare/cypress`'s version, there is no `BrowserInfo`\n * parameter — cucumber-js has no browser context of its own (unless a user\n * pairs it with a browser driver, which is outside this reporter's own\n * knowledge), so `browser` is config-only and `os` falls back to\n * `os.type()/os.release()`.\n */\nexport function buildCollectPayload(suites: Suite[], config: ResolvedFormatterConfig): Collect {\n return {\n framework: config.framework,\n platform: config.platform,\n os: resolveOs(config),\n browser: config.browser ?? '',\n branch: config.branch,\n commit: config.commit,\n environment: config.environment,\n language: config.language,\n milestone: config.milestone,\n metadata: {\n version: PACKAGE_VERSION,\n timestamp: new Date().toISOString(),\n cliName: 'qualflare-cucumberjs',\n runId: config.runId,\n },\n properties: config.properties,\n suites,\n ciProvider: config.ciProvider,\n ciBuildNumber: config.ciBuildNumber,\n ciRunUrl: config.ciRunUrl,\n ciPrNumber: config.ciPrNumber,\n };\n}\n","// `__PACKAGE_VERSION__` is injected at build time by tsup's `define` option\n// (see tsup.config.ts) from package.json's `version` field. Deliberately\n// NOT read at runtime via `import.meta.url` + `createRequire` — that breaks\n// the CJS build output (`import.meta` is empty/unavailable once esbuild\n// compiles to CommonJS), which is exactly the class of dual-CJS/ESM-package\n// bug a build-time constant sidesteps entirely. Under Vitest (which never\n// goes through tsup), `vitest.config.ts` defines the same constant so this\n// module behaves identically in tests and in the built package.\ndeclare const __PACKAGE_VERSION__: string;\n\nexport const PACKAGE_VERSION: string = __PACKAGE_VERSION__;\n","import type { GherkinDocument, Scenario } from '@cucumber/messages';\n\ninterface ScenarioEntry {\n scenario: Scenario;\n ruleName?: string;\n}\n\ninterface FeatureEntry {\n featureName?: string;\n /** AST step id -> literal Gherkin keyword (\"Given \"/\"When \"/\"Then \"/\"And \"/\n * \"But \", with cucumber-js's own trailing space) + step text. Background\n * steps are indexed here too — cucumber-js already merges Background\n * steps into every scenario's compiled `Pickle.steps[]` itself, so no\n * separate handling is needed; a Background step's AST id just needs to\n * resolve here like any other. */\n stepMap: Map<string, { keyword: string; text: string }>;\n scenarioById: Map<string, ScenarioEntry>;\n}\n\n/**\n * Indexes each `gherkinDocument` envelope (one per feature file) so\n * `case-builder.ts`/`step-mapper.ts` can resolve, per pickle: the Feature\n * name, an optional `Rule:` name, the Scenario AST node (for its\n * `examples[]`, used for Scenario Outline row correlation), and literal\n * Given/When/Then/And/But keyword text for each step (the compiled\n * `PickleStep.type` only gives a coarse Context/Action/Outcome/Unknown\n * classification, not the literal keyword).\n */\nexport class GherkinIndex {\n private readonly byUri = new Map<string, FeatureEntry>();\n\n add(doc: GherkinDocument): void {\n if (!doc.uri || !doc.feature) {\n return;\n }\n const entry: FeatureEntry = {\n featureName: doc.feature.name || undefined,\n stepMap: new Map(),\n scenarioById: new Map(),\n };\n for (const child of doc.feature.children) {\n if (child.background) {\n this.indexSteps(entry, child.background.steps);\n }\n if (child.scenario) {\n entry.scenarioById.set(child.scenario.id, { scenario: child.scenario });\n this.indexSteps(entry, child.scenario.steps);\n }\n if (child.rule) {\n for (const ruleChild of child.rule.children) {\n if (ruleChild.background) {\n this.indexSteps(entry, ruleChild.background.steps);\n }\n if (ruleChild.scenario) {\n entry.scenarioById.set(ruleChild.scenario.id, {\n scenario: ruleChild.scenario,\n ruleName: child.rule.name || undefined,\n });\n this.indexSteps(entry, ruleChild.scenario.steps);\n }\n }\n }\n }\n this.byUri.set(doc.uri, entry);\n }\n\n private indexSteps(entry: FeatureEntry, steps: readonly { id: string; keyword: string; text: string }[]): void {\n for (const step of steps) {\n entry.stepMap.set(step.id, { keyword: step.keyword, text: step.text });\n }\n }\n\n get(uri: string): FeatureEntry | undefined {\n return this.byUri.get(uri);\n }\n\n /** Resolves the literal Given/When/Then/And/But keyword text for a\n * compiled `PickleStep`, by walking its `astNodeIds` back to the first\n * one present in this feature's `stepMap` (a Background step referenced\n * by a scenario has exactly one AST id; a step reusing a parameter type\n * from an outline row can have more than one — the first match is always\n * the step's own defining AST node). */\n resolveKeyword(uri: string, astNodeIds: readonly string[]): { keyword: string; text: string } | undefined {\n const entry = this.byUri.get(uri);\n if (!entry) {\n return undefined;\n }\n for (const id of astNodeIds) {\n const found = entry.stepMap.get(id);\n if (found) {\n return found;\n }\n }\n return undefined;\n }\n}\n","import type { IFormatterOptions } from '@cucumber/cucumber';\n\n// `SupportCodeLibrary` itself isn't re-exported from the package's public\n// entry point (only reachable via a deep `lib/**` import, which the\n// package's exports map only allows for `require`, not `import` — this\n// package is ESM-first). Deriving the type via indexed access on the\n// already-public `IFormatterOptions` avoids that deep import entirely.\ntype SupportCodeLibrary = IFormatterOptions['supportCodeLibrary'];\n\nexport type HookKind = 'before' | 'after' | 'beforeStep' | 'afterStep' | 'beforeAll' | 'afterAll';\n\nexport interface HookInfo {\n kind: HookKind;\n name?: string;\n}\n\nexport type HookIndex = ReadonlyMap<string, HookInfo>;\n\n/**\n * Resolves a hook's kind from `supportCodeLibrary`'s six definition-array\n * fields, NOT the envelope's `Hook.type` — that field was only added in\n * cucumber-js 11.2.0 (confirmed via the project's own CHANGELOG), which is\n * after this package's peer floor (`>=10.8.0`). This is also exactly the\n * mechanism `allure-cucumberjs`'s real source uses for `Before`/`After`\n * (though it never indexes the `TestStep`/`TestRunHook` collections at all,\n * which is why it silently drops `BeforeStep`/`AfterStep` and never handles\n * `BeforeAll`/`AfterAll` — this package intentionally indexes all six).\n */\nexport function buildHookIndex(supportCodeLibrary: SupportCodeLibrary): HookIndex {\n const index = new Map<string, HookInfo>();\n for (const def of supportCodeLibrary.beforeTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'before', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.afterTestCaseHookDefinitions) {\n index.set(def.id, { kind: 'after', name: def.name || undefined });\n }\n for (const def of supportCodeLibrary.beforeTestStepHookDefinitions) {\n index.set(def.id, { kind: 'beforeStep' });\n }\n for (const def of supportCodeLibrary.afterTestStepHookDefinitions) {\n index.set(def.id, { kind: 'afterStep' });\n }\n for (const def of supportCodeLibrary.beforeTestRunHookDefinitions) {\n index.set(def.id, { kind: 'beforeAll' });\n }\n for (const def of supportCodeLibrary.afterTestRunHookDefinitions) {\n index.set(def.id, { kind: 'afterAll' });\n }\n return index;\n}\n","import type { TestRunHookFinished, TestRunHookStarted } from '@cucumber/messages';\n\nimport { messageDurationToNs } from '../shared/duration.js';\nimport type { Case } from '../shared/types.js';\nimport type { HookIndex } from './hook-index.js';\nimport { mapStatus } from './step-mapper.js';\n\n/**\n * `BeforeAll`/`AfterAll` run outside any test case (`testRunHookStarted`/\n * `Finished`, never a `Case`) — `allure-cucumberjs` drops these entirely\n * (confirmed by reading its real source: no case for them anywhere in its\n * envelope-dispatch switch). This tracker does not: a FAILED run-hook\n * becomes a synthetic `Case` in a `(global hooks)` Suite (design decision\n * (b) in the plan); a passing one produces nothing — a passing `BeforeAll`\n * has no useful signal to report, and would be pure noise on every run.\n */\nexport class RunHookTracker {\n /** `testRunHookStartedId` -> the hook it started, so `finish()` can look\n * up its kind/name. `TestRunHookFinished.result.duration` already gives\n * an accurate duration directly — no start/finish timestamp delta needed. */\n private readonly started = new Map<string, string>();\n private readonly failed: Case[] = [];\n\n start(e: TestRunHookStarted): void {\n this.started.set(e.id, e.hookId);\n }\n\n finish(e: TestRunHookFinished, hookIndex: HookIndex): void {\n const hookId = this.started.get(e.testRunHookStartedId);\n this.started.delete(e.testRunHookStartedId);\n const status = mapStatus(e.result.status);\n if (status === 'passed' || status === 'skipped') {\n return;\n }\n const hook = hookId ? hookIndex.get(hookId) : undefined;\n const label = hook?.kind === 'afterAll' ? 'AfterAll hook' : 'BeforeAll hook';\n this.failed.push({\n id: `global-hook:${e.testRunHookStartedId}`,\n name: hook?.name || label,\n status,\n duration: messageDurationToNs(e.result.duration),\n // `result.message` first — see `step-mapper.ts`'s `formatError()` doc\n // comment for why (verified empirically to be the version-safe field\n // across the peer-dependency range; `exception.stackTrace` is not).\n error: e.result.message || e.result.exception?.stackTrace || e.result.exception?.message,\n });\n }\n\n /** Returns `undefined` if no run-hook failed — see the class doc comment\n * for why a passing BeforeAll/AfterAll produces no Case at all. */\n buildSuite(): { name: string; category: 'cucumber'; duration: number; cases: Case[] } | undefined {\n if (this.failed.length === 0) {\n return undefined;\n }\n return {\n name: '(global hooks)',\n category: 'cucumber',\n duration: this.failed.reduce((sum, c) => sum + c.duration, 0),\n cases: this.failed,\n };\n }\n}\n","import * as path from 'node:path';\n\nimport { MAX_CASES_PER_SUITE, MAX_SUITES_PER_LAUNCH } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Case, Suite } from '../shared/types.js';\nimport type { FinishedCase } from './case-builder.js';\n\n/**\n * Groups every finished Case into one `Suite` per `.feature` file. Unlike\n * `@qualflare/cypress` (which can batch incrementally, one spec file at a\n * time, since Cypress runs specs sequentially in one process),\n * cucumber-js can interleave scenarios from different feature files even\n * without `--parallel`, and always runs the formatter in the coordinator\n * process either way (worker *threads* under `--parallel` ship envelopes\n * back to it) — so grouping happens once, at `finished()`, over the whole\n * run's flat list of finished cases.\n */\nexport function groupIntoSuites(cases: FinishedCase[], cwd: string, extraSuite?: Suite): Suite[] {\n const byUri = new Map<string, Case[]>();\n for (const { uri, case: kase } of cases) {\n const bucket = byUri.get(uri);\n if (bucket) {\n bucket.push(kase);\n } else {\n byUri.set(uri, [kase]);\n }\n }\n\n const suites: Suite[] = [];\n for (const [uri, kases] of byUri) {\n if (kases.length > MAX_CASES_PER_SUITE) {\n logger.warn(\n `feature file \"${uri}\" reported ${kases.length} scenarios — only the first ${MAX_CASES_PER_SUITE} will be uploaded (server cap).`,\n );\n }\n suites.push({\n name: relativizeUri(uri, cwd),\n category: 'cucumber',\n duration: kases.reduce((sum, c) => sum + c.duration, 0),\n cases: kases.slice(0, MAX_CASES_PER_SUITE),\n });\n }\n\n if (extraSuite) {\n suites.push(extraSuite);\n }\n\n if (suites.length > MAX_SUITES_PER_LAUNCH) {\n logger.warn(\n `this run reported ${suites.length} feature-file suites — only the first ${MAX_SUITES_PER_LAUNCH} will be uploaded (server cap).`,\n );\n }\n return suites.slice(0, MAX_SUITES_PER_LAUNCH);\n}\n\n/** cucumber-js's own `pickle.uri` is already relative to the invocation cwd\n * in the common case (confirmed empirically: a real run reports e.g.\n * `\"features/passing.feature\"`, not an absolute path or a `file://` URL) —\n * this only normalizes the rarer absolute-path/URL forms down to the same\n * shape. */\nfunction relativizeUri(uri: string, cwd: string): string {\n let normalized = uri;\n if (normalized.startsWith('file://')) {\n normalized = new URL(normalized).pathname;\n }\n if (path.isAbsolute(normalized)) {\n normalized = path.relative(cwd, normalized);\n }\n return normalized.split(path.sep).join('/');\n}\n"],"mappings":"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","names":["import_node_crypto","fs","path","name","firstEnv","name","fs","path","import_node_crypto","extension","import_messages","resolved","path"]}
|