@qualflare/playwright 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/reporter/index.cjs +94 -1
- package/dist/reporter/index.cjs.map +1 -1
- package/dist/reporter/index.d.cts +2 -2
- package/dist/reporter/index.d.ts +2 -2
- package/dist/reporter/index.js +94 -1
- package/dist/reporter/index.js.map +1 -1
- package/dist/{resolve-config-CQe-oDpg.d.cts → resolve-config-N04M1Avn.d.cts} +50 -0
- package/dist/{resolve-config-CQe-oDpg.d.ts → resolve-config-N04M1Avn.d.ts} +50 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/reporter/index.ts","../../src/reporter/reporter.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/reporter/attachment-reader.ts","../../src/reporter/video-writer.ts","../../src/shared/duration.ts","../../src/reporter/step-mapper.ts","../../src/reporter/case-builder.ts","../../src/reporter/collect-builder.ts","../../src/config/version.ts","../../src/reporter/suite-builder.ts"],"sourcesContent":["export { default } from './reporter.js';\nexport type { QualflarePlaywrightOptions, ResolvedReporterConfig } from '../config/resolve-config.js';\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type {\n FullConfig,\n FullResult,\n Reporter,\n Suite as PwSuite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\nimport { resolveConfig, type QualflarePlaywrightOptions, type ResolvedReporterConfig } from '../config/resolve-config.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport { AttachmentBudget, resolveAttachments } from './attachment-reader.js';\nimport { buildCase } from './case-builder.js';\nimport { buildCollectPayload } from './collect-builder.js';\nimport { groupIntoSuites, relativizeFile, type CaseWithFile } from './suite-builder.js';\n\n/** Options Playwright injects on top of the user's own, for every reporter.\n * It also injects internal `_mode`/`_commandHash` fields; this package\n * deliberately reads neither, since both are undocumented internals. */\ninterface InjectedOptions {\n configDir?: string;\n}\n\n/**\n * The Qualflare Playwright reporter.\n *\n * Writes ONE uniquely-named JSON report per process into `outputDir`, plus\n * any videos copied alongside it, and makes zero network calls.\n * `qualflare-cli collect <outputDir>` uploads the result — which is what lets\n * any number of sharded jobs write into one directory and merge into a single\n * Launch.\n *\n * Registered in `playwright.config.ts`:\n *\n * ```ts\n * export default defineConfig({\n * reporter: [['list'], ['@qualflare/playwright/reporter', { environment: 'staging' }]],\n * });\n * ```\n */\nexport default class QualflareReporter implements Reporter {\n private readonly options: QualflarePlaywrightOptions & InjectedOptions;\n private config?: ResolvedReporterConfig;\n private rootDir = process.cwd();\n private readonly cases: CaseWithFile[] = [];\n private readonly browsers = new Set<string>();\n private budget = new AttachmentBudget(0);\n private rootSuite?: PwSuite;\n private readonly attachmentsByResult = new Map<string, Attachment[]>();\n private readonly latestAttemptByTest = new Map<string, number>();\n\n constructor(options: QualflarePlaywrightOptions & InjectedOptions = {}) {\n this.options = options;\n }\n\n /** Returning false tells Playwright to auto-inject a terminal reporter\n * (`line` locally, `dot` on CI) so a user who registers only this one is\n * not left staring at a blank console. This reporter prints nothing but\n * warnings and a single completion line. */\n printsToStdio(): boolean {\n return false;\n }\n\n onBegin(config: FullConfig, suite: PwSuite): void {\n this.guard('onBegin', () => {\n this.rootSuite = suite;\n this.rootDir = config.rootDir || process.cwd();\n\n // Playwright's shard index is 1-BASED (\"--shard=1/3\" is the first\n // shard); ours is 0-based, matching every other Qualflare reporter.\n const detectedShardIndex = config.shard ? config.shard.current - 1 : undefined;\n\n this.config = resolveConfig(this.options, { detectedShardIndex });\n this.budget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);\n\n for (const project of config.projects) {\n const browserName = project.use?.browserName;\n if (browserName) {\n this.browsers.add(browserName);\n }\n }\n });\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n this.guard('onTestEnd', () => {\n const config = this.config;\n if (!config || !config.enabled) {\n return;\n }\n // Attachment FILES are read here, not in onEnd, and this ordering is\n // load-bearing: `use.preserveOutput` deletes artifacts once a test\n // finishes, and a passing retry cleans up the previous attempt's output\n // directory. By onEnd the screenshots and videos may simply be gone.\n //\n // The Case itself is NOT assembled here — test.outcome() is not final\n // until every retry has run, so a to-be-retried failure would be\n // reported as a plain failure and nothing would ever be flaky.\n // A retried test reaches onTestEnd once per ATTEMPT, but only the FINAL\n // attempt's attachments are ever reported (see buildCase). Discard the\n // superseded attempt's work as soon as a later one arrives, or its\n // copied video is orphaned in outputDir forever and its bytes stay\n // reserved against a budget that a later test still needs.\n this.discardSupersededAttempt(test.id, result.retry, config.outputDir);\n this.attachmentsByResult.set(`${test.id}:${result.retry}`, resolveAttachments(result, config, this.budget));\n this.latestAttemptByTest.set(test.id, result.retry);\n });\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n await Promise.resolve();\n this.guard('onEnd', () => {\n const config = this.config;\n if (!config || !config.enabled) {\n return;\n }\n this.writeReport(config);\n });\n }\n\n /** Collects every test from the (possibly nested) suite tree. */\n private collectCases(root: PwSuite, config: ResolvedReporterConfig): void {\n for (const test of root.allTests()) {\n const built = buildCase(test, config, this.attachmentsByResult, this.budget);\n if (!built) {\n continue;\n }\n const file = relativizeFile(test.location.file, this.rootDir);\n built.className = file;\n if (built.properties) {\n built.properties['file'] = file;\n }\n const browserName = test.parent.project()?.use?.browserName;\n this.cases.push({\n file,\n ...(browserName ? { browser: browserName } : {}),\n testCase: built,\n });\n }\n }\n\n private writeReport(config: ResolvedReporterConfig): void {\n if (this.rootSuite) {\n this.collectCases(this.rootSuite, config);\n }\n\n const suites = groupIntoSuites(this.cases);\n if (suites.length === 0) {\n logger.info('no test results were captured this run — skipping file write.');\n return;\n }\n\n const collect = buildCollectPayload(suites, config, [...this.browsers]);\n\n if (config.shardIndex !== undefined) {\n for (const suite of collect.suites) {\n for (const testCase of suite.cases) {\n testCase.shardIndex = config.shardIndex;\n }\n }\n }\n\n const outputDir = this.resolveOutputDir(config.outputDir);\n\n fs.mkdirSync(outputDir, { recursive: true });\n const outputPath = path.join(outputDir, `${randomUUID()}.json`);\n fs.writeFileSync(outputPath, JSON.stringify(collect));\n logger.info(`wrote Collect payload to ${outputPath} — run \\`qualflare-cli collect ${outputDir}\\` to upload it.`);\n }\n\n /** Relative `outputDir` resolves against the Playwright config's own\n * directory, not the shell's cwd — a user running `npx playwright test`\n * from a monorepo root should still write next to their config. */\n private resolveOutputDir(outputDir: string): string {\n return path.isAbsolute(outputDir) ? outputDir : path.resolve(this.options.configDir ?? this.rootDir, outputDir);\n }\n\n /** Drops everything an earlier, now-superseded attempt produced: deletes the\n * video copied into outputDir and refunds its bytes to the run budget. */\n private discardSupersededAttempt(testId: string, retry: number, outputDir: string): void {\n const previous = this.latestAttemptByTest.get(testId);\n if (previous === undefined || previous >= retry) {\n return;\n }\n const key = `${testId}:${previous}`;\n for (const attachment of this.attachmentsByResult.get(key) ?? []) {\n if (attachment.localVideoPath) {\n try {\n fs.rmSync(path.join(this.resolveOutputDir(outputDir), attachment.localVideoPath), { force: true });\n } catch {\n // Best effort: an orphan left on disk is untidy, never incorrect.\n }\n }\n if (attachment.fileSize && attachment.content) {\n this.budget.release(attachment.fileSize);\n }\n }\n this.attachmentsByResult.delete(key);\n }\n\n /**\n * Playwright SWALLOWS anything a reporter throws (Multiplexer._wrap catches\n * it and re-dispatches as onError), so an unguarded bug here vanishes\n * silently and the user just gets no report. Every hook body runs through\n * this instead, which at least says what broke and where.\n */\n private guard(hook: string, fn: () => void): void {\n try {\n fn();\n } catch (err) {\n logger.error(`${hook} failed: ${(err as Error).message}`);\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 for the reporter, passed as the second element of its entry in\n * `playwright.config.ts`'s `reporter` array:\n * `['@qualflare/playwright/reporter', { ... }]`. Every field here also has an\n * environment-variable override — see the precedence table in\n * `docs/CONFIGURATION.md`. */\nexport interface QualflarePlaywrightOptions {\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 Playwright's runner-internal steps — `pw:api` (every\n * `page.click()`, `locator.fill()`, ...) and `fixture` (the implicit\n * `browser`/`context`/`page` setup every browser test opens with) — as\n * reported Steps.\n *\n * Off by default: a single browser test routinely produces hundreds of\n * them, which buries the user-authored `test.step()`/`expect` boundaries\n * that are actually legible in a report and blows through\n * MAX_STEPS_PER_TEST_ATTEMPT on noise. A step that FAILED is always kept\n * regardless of this setting, since a failing API call or fixture is\n * usually the single most useful line in the trace. */\n includeApiSteps?: 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 * reporter still no-ops cleanly rather than throwing. */\n enabled?: boolean;\n /** Directory `onEnd()` writes this process's report file (and any\n * video attachments) into. Default `./qualflare-results`. Always active —\n * this reporter 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 Playwright's own\n * `--shard i/N`, which it exposes to reporters as `FullConfig.shard`\n * ({ current, total }). Playwright's `current` is 1-BASED, so the reporter\n * converts it before passing it here as `deps.detectedShardIndex`.\n *\n * This is the one place Playwright is markedly better than its siblings:\n * Cypress has no shard concept at all, and cucumber-js hides its `--shard`\n * from formatters entirely (forcing an argv scrape). Here the runner just\n * tells us. */\n shardIndex?: number;\n}\n\nexport interface ResolvedReporterConfig {\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 includeApiSteps: 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\n/** Resolves the full reporter configuration from, in order: the explicit\n * `options` (the second element of the `playwright.config.ts` reporter\n * tuple, `['@qualflare/playwright', { ... }]`), 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 reporter's constructor calls\n * `resolveConfig(options)` with no second argument) is unaffected.\n */\nexport function resolveConfig(\n options: QualflarePlaywrightOptions,\n deps: { detectGit?: () => GitInfo; detectCi?: () => CiMetadata;\n /** Playwright's `FullConfig.shard`, already converted from its 1-based\n * `current` to our 0-based index by the reporter. */\n detectedShardIndex?: number;\n } = {},\n): ResolvedReporterConfig {\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') ?? deps.detectedShardIndex;\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 || 'playwright',\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 includeApiSteps: options.includeApiSteps ?? envBool('QUALFLARE_INCLUDE_API_STEPS') ?? 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 reporter and the author-facing runtime\n * API.\n */\n\n/** Reserved `testInfo.attach()` content type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from test and hook code back to\n * the reporter — the only channel Playwright gives user code back to a\n * running reporter. The reporter recognizes this exact content type and\n * replays the message as a model mutation instead of reporting it as a\n * 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 Playwright 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 Playwright's own reporter output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-playwright]';\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';\n\nimport type { TestResult } from '@playwright/test/reporter';\n\nimport { MAX_ATTACHMENTS_PER_CASE, RESERVED_MESSAGE_MEDIA_TYPE } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport type { ResolvedReporterConfig } from '../config/resolve-config.js';\nimport { copyVideoAttachment } from './video-writer.js';\n\n/** Running total of inline attachment bytes for one reporter process, so a\n * single pathological run can't push a launch past the server's body limit.\n * Identical to the class both sibling packages use. */\nexport class AttachmentBudget {\n private used = 0;\n\n constructor(private readonly maxTotalBytes: number) {}\n\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 /** Returns bytes to the budget when the attachment they were reserved for\n * turns out to be discarded — a retried test's superseded attempt. Without\n * this, a flaky test consumes budget twice and a LATER test silently loses\n * its screenshot to an attachment nobody will ever see. */\n release(bytes: number): void {\n this.used = Math.max(0, this.used - bytes);\n }\n\n get usedBytes(): number {\n return this.used;\n }\n}\n\n/** Playwright's own auto-attachment names (`use.video`/`use.screenshot`/\n * `use.trace` produce exactly these). */\nconst NAME_VIDEO = 'video';\nconst NAME_TRACE = 'trace';\n\nfunction isVideo(a: TestResult['attachments'][number]): boolean {\n return a.name === NAME_VIDEO || (a.contentType?.startsWith('video/') ?? false);\n}\n\n/**\n * Resolves one test attempt's Playwright attachments into wire `Attachment`s.\n *\n * Three routes, and which one an attachment takes is decided entirely by what\n * the CLI and server can actually do with it:\n *\n * - **video** -> copied into `outputDir`, referenced by `localVideoPath`.\n * This is the ONLY path `qualflare-cli` uploads to blob storage.\n * - **everything else with bytes** -> inlined as base64 `content`, subject to\n * the per-attachment and per-run budgets.\n * - **trace** -> dropped, deliberately. Traces are `application/zip`, which\n * the upload endpoint's MIME allowlist rejects, and they are far too large\n * to inline. Attaching one would produce a row pointing at nothing. See\n * docs/LIMITATIONS.md.\n *\n * A bare `path` is never emitted on its own: the server treats `path` as\n * informational and never fetches it, so a path-only attachment is a row the\n * user can see but never open. Dropping is more honest than that.\n */\nexport function resolveAttachments(\n result: TestResult,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment[] {\n if (!config.attachScreenshots) {\n return [];\n }\n\n const out: Attachment[] = [];\n let capWarned = false;\n\n for (const a of result.attachments) {\n // Runtime messages from the metadata API travel as attachments; they are\n // consumed by the reporter, never reported as one.\n if (a.contentType === RESERVED_MESSAGE_MEDIA_TYPE) {\n continue;\n }\n\n if (out.length >= MAX_ATTACHMENTS_PER_CASE) {\n if (!capWarned) {\n capWarned = true;\n logger.warn(`a test produced more than ${MAX_ATTACHMENTS_PER_CASE} attachments; the rest were dropped.`);\n }\n break;\n }\n\n if (a.name === NAME_TRACE || a.contentType === 'application/zip') {\n continue;\n }\n\n if (isVideo(a)) {\n if (!a.path) {\n // An in-memory video is not something Playwright produces on its own\n // and cannot be routed through localVideoPath without writing it out;\n // inlining a video would blow the budget instantly.\n logger.warn(`skipping in-memory video attachment \"${a.name}\": only file-backed videos are supported.`);\n continue;\n }\n const copied = copyVideoAttachment(a.path, config.outputDir, config.maxVideoBytes);\n if (copied) {\n out.push({\n name: a.name,\n mimeType: copied.mimeType,\n localVideoPath: copied.localVideoPath,\n fileSize: copied.fileSize,\n });\n }\n continue;\n }\n\n const inlined = inlineAttachment(a, config, budget);\n if (inlined) {\n out.push(inlined);\n }\n }\n\n return out;\n}\n\n/**\n * Turns raw bytes into a wire `Attachment`, enforcing BOTH caps.\n *\n * Every path that inlines content must go through here. `/collect` rejects a\n * body over 10MB outright (api-service `launch_controller.go`'s\n * `BodyLimit(10<<20)`), and a rejected request loses the ENTIRE launch — not\n * just the oversized attachment. `maxTotalAttachmentBytes` defaults to 750KB\n * precisely to stay clear of that, so any path that skips the budget can\n * silently destroy a whole run's results.\n */\nexport function inlineFromBuffer(\n name: string,\n bytes: Buffer,\n mimeType: string | undefined,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (bytes.byteLength > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${name}\": ${bytes.byteLength} bytes exceeds the configured maxAttachmentBytes cap of ${config.maxAttachmentBytes} bytes.`,\n );\n return undefined;\n }\n if (!budget.tryReserve(bytes.byteLength)) {\n logger.warn(\n `skipping attachment \"${name}\": this run's total inline-attachment budget of ${config.maxTotalAttachmentBytes} bytes is exhausted.`,\n );\n return undefined;\n }\n\n return {\n name,\n ...(mimeType ? { mimeType } : {}),\n content: bytes.toString('base64'),\n fileSize: bytes.byteLength,\n };\n}\n\n/**\n * Reads a file from disk and inlines it, subject to the same caps.\n *\n * `stat`s before reading so an oversized file is rejected without ever being\n * pulled into memory. Every failure warns and returns `undefined`; an\n * attachment must never fail a run.\n */\nexport function inlineFromFile(\n name: string,\n filePath: string,\n mimeType: string | undefined,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n let size: number;\n try {\n size = fs.statSync(filePath).size;\n } catch (err) {\n logger.warn(`skipping attachment \"${name}\": could not stat ${filePath}: ${(err as Error).message}`);\n return undefined;\n }\n if (size > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${name}\": ${size} bytes exceeds the configured maxAttachmentBytes cap of ${config.maxAttachmentBytes} bytes.`,\n );\n return undefined;\n }\n\n let bytes: Buffer;\n try {\n bytes = fs.readFileSync(filePath);\n } catch (err) {\n logger.warn(`skipping attachment \"${name}\": could not read ${filePath}: ${(err as Error).message}`);\n return undefined;\n }\n return inlineFromBuffer(name, bytes, mimeType, config, budget);\n}\n\nfunction inlineAttachment(\n a: TestResult['attachments'][number],\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (a.body) {\n return inlineFromBuffer(a.name, a.body, a.contentType, config, budget);\n }\n if (a.path) {\n return inlineFromFile(a.name, a.path, a.contentType, config, budget);\n }\n return undefined;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nimport { logger } from '../shared/logger.js';\n\n/** Extension -> MIME type for the video formats the server accepts (see\n * the upload endpoint's own allowlist server-side). Playwright records `.webm`\n * by default; `.mp4`/`.mov` are listed for parity with the server's allowlist\n * and because a user can attach either via `testInfo.attach()`. An extension not\n * in this map (a user could point `qualflare.attachmentFromFile()` at an\n * arbitrary file) is skipped — see `copyVideoAttachment`'s doc comment. */\nconst VIDEO_MIME_TYPES_BY_EXTENSION: Record<string, string> = {\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.mov': 'video/quicktime',\n};\n\nexport interface VideoCopyResult {\n /** Filename relative to the `outputDir` this was copied into — never an\n * absolute path, since the whole directory travels together as one CI\n * artifact bundle (see the design spec's \"Why no backend changes\"\n * section). */\n localVideoPath: string;\n fileSize: number;\n mimeType: string;\n}\n\n/**\n * Copies one video file into `outputDir` under a unique filename (Allure's\n * `FileSystemWriter.writeAttachmentFromPath` pattern: `fs.copyFileSync`,\n * never read into memory) and returns enough to build that `Attachment`\n * entry's `localVideoPath`. `qualflare-cli` is what actually uploads this\n * file later, once it has a real auth token — see the design spec.\n *\n * Best-effort, like the rest of this reporter's attachment handling\n * (`attachment-reader.ts`'s oversized/unreadable-file skip): any failure —\n * oversized file, unsupported extension, an unreadable source file — is\n * logged as a warning and resolves to `undefined` rather than throwing, so a\n * video problem never fails the whole run.\n */\nexport function copyVideoAttachment(\n filePath: string,\n outputDir: string,\n maxVideoBytes: number,\n): VideoCopyResult | undefined {\n const ext = path.extname(filePath).toLowerCase();\n const mimeType = VIDEO_MIME_TYPES_BY_EXTENSION[ext];\n if (!mimeType) {\n logger.warn(`skipping video attachment \"${filePath}\": unsupported video format.`);\n return undefined;\n }\n\n let fileSize: number;\n try {\n // Stat BEFORE copying — an oversized file must never be copied just to\n // discover it should be skipped.\n fileSize = fs.statSync(filePath).size;\n } catch (err) {\n logger.warn(`skipping video attachment \"${filePath}\": could not stat file: ${(err as Error).message}`);\n return undefined;\n }\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${filePath}\": ${fileSize} bytes exceeds the configured ` +\n `maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n\n const localVideoPath = `${randomUUID()}${ext}`;\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.copyFileSync(filePath, path.join(outputDir, localVideoPath));\n } catch (err) {\n logger.warn(`skipping video attachment \"${filePath}\": could not copy file: ${(err as Error).message}`);\n return undefined;\n }\n\n return { localVideoPath, fileSize, mimeType };\n}\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_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","import type { TestStep } from '@playwright/test/reporter';\n\nimport { MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { Step } from '../shared/types.js';\n\n/** Playwright's built-in step categories, as of 1.62. `category` is typed as\n * a plain `string`, not a union — third-party integrations add their own — so\n * nothing here may switch exhaustively on it. */\nconst CATEGORY_TEST_STEP = 'test.step';\nconst CATEGORY_EXPECT = 'expect';\nconst CATEGORY_HOOK = 'hook';\nconst CATEGORY_FIXTURE = 'fixture';\nconst CATEGORY_PW_API = 'pw:api';\n\n/** Depth beyond which nesting is flattened rather than followed.\n *\n * Playwright imposes no nesting limit: a `test.step()` inside a `test.step()`\n * around a `page.getByRole().click()` already reaches three levels before any\n * user intent, and a recursive helper can go arbitrarily deep. Real suites sit\n * at 3-6, so this is a runaway guard, not a product limit — steps past it are\n * still reported, just re-parented to the deepest ancestor within the cap\n * rather than dropped, since losing a failing assertion to a depth rule would\n * be far worse than showing it one level too shallow. */\nconst MAX_STEP_DEPTH = 10;\n\n/** True when a step is worth reporting at all.\n *\n * `pw:api` and `fixture` are excluded by default and this is the single most\n * important filter in the mapper: one `page.goto()` plus a handful of\n * assertions can emit hundreds of `pw:api` steps, and every browser test\n * opens with `Fixture \"browser\"`/`\"context\"`/`\"page\"` before reaching a line\n * of user code. Together they bury the user-authored `test.step()`\n * boundaries and exhaust MAX_STEPS_PER_TEST_ATTEMPT on noise long before\n * reaching anything a human wants to read.\n *\n * A step that FAILED is always kept, whatever its category — the failing\n * `pw:api` call is usually the single most useful line in the whole trace,\n * and dropping it to a volume heuristic would defeat the point of reporting\n * steps at all. */\nfunction isReportable(step: TestStep, includeApiSteps: boolean): boolean {\n if (step.error) {\n return true;\n }\n switch (step.category) {\n case CATEGORY_TEST_STEP:\n case CATEGORY_EXPECT:\n case CATEGORY_HOOK:\n return true;\n case CATEGORY_PW_API:\n case CATEGORY_FIXTURE:\n // Both are runner internals rather than anything the test author\n // wrote. Every browser test emits `Fixture \"browser\"` / `\"context\"` /\n // `\"page\"` before reaching a single line of user code, and `pw:api`\n // runs to hundreds of entries — in a test-management report that is\n // noise ahead of signal. A FAILED fixture is still kept, by the check\n // above: a fixture that throws is a genuine failure and usually the\n // most useful line in the trace.\n return includeApiSteps;\n default:\n // An unrecognized category is most likely a third-party integration's\n // own step (Playwright allows any string). Keep it: an unknown step is\n // more likely signal than the `pw:api` firehose this filter exists for.\n return true;\n }\n}\n\n/** `file:line`, relative paths left as Playwright reports them. */\nfunction formatLocation(step: TestStep): string | undefined {\n if (!step.location) {\n return undefined;\n }\n return `${step.location.file}:${step.location.line}`;\n}\n\n/**\n * Flattens Playwright's nested `TestStep` tree into the wire format's flat\n * `Step[]`, preserving the shape via `parentIndex` (a 0-based index into the\n * same array). The server reconstructs the tree from that — see\n * `ResolveStepParents` in api-service, which drops out-of-range or cyclic\n * values rather than rejecting the case.\n *\n * Read the tree in `onTestEnd`, never in `onStepBegin`/`onStepEnd`: Playwright\n * MUTATES the same step object when a step finishes (`step.duration` and\n * `step.error` are assigned in place), so anything captured at begin-time is a\n * live reference whose duration is still unset.\n */\nexport function mapSteps(steps: readonly TestStep[], includeApiSteps: boolean): Step[] {\n const out: Step[] = [];\n let capWarned = false;\n\n const walk = (nodes: readonly TestStep[], parentIndex: number | undefined, depth: number): void => {\n for (const node of nodes) {\n if (!isReportable(node, includeApiSteps)) {\n // Skipped, but still descend: a filtered-out `pw:api` wrapper can\n // contain a reportable child (an assertion, or anything that failed).\n // Those children re-parent to this node's own parent, which keeps the\n // tree connected instead of orphaning them at the root.\n walk(node.steps, parentIndex, depth);\n continue;\n }\n\n if (out.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!capWarned) {\n capWarned = true;\n logger.warn(\n `a test produced more than ${MAX_STEPS_PER_TEST_ATTEMPT} reportable steps; the rest were dropped. ` +\n 'Set `includeApiSteps: false` (the default) or reduce step nesting if this is unexpected.',\n );\n }\n return;\n }\n\n const index = out.length;\n out.push({\n name: node.title,\n keyword: node.category,\n status: node.error ? 'failed' : 'passed',\n duration: msToNs(node.duration),\n ...(node.error ? { error: formatStepError(node) } : {}),\n ...(formatLocation(node) ? { location: formatLocation(node) } : {}),\n ...(parentIndex !== undefined ? { parentIndex } : {}),\n });\n\n // Past the depth cap, keep reporting but stop deepening: children are\n // attached to the last in-cap ancestor rather than dropped.\n const nextParent = depth + 1 >= MAX_STEP_DEPTH ? parentIndex : index;\n walk(node.steps, nextParent, depth + 1);\n }\n };\n\n walk(steps, undefined, 0);\n return out;\n}\n\n/** Playwright's step errors carry the same shape as test errors; `message`\n * is set for thrown Errors and `value` for non-Error throws (`throw 'x'`). */\nfunction formatStepError(step: TestStep): string {\n const err = step.error;\n if (!err) {\n return '';\n }\n return err.message ?? err.value ?? 'step failed';\n}\n","import * as fs from 'node:fs';\n\nimport type { TestCase, TestResult } from '@playwright/test/reporter';\n\nimport type { ResolvedReporterConfig } from '../config/resolve-config.js';\nimport {\n MAX_LABELS_PER_CASE,\n MAX_LINKS_PER_CASE,\n MAX_TAGS_PER_CASE,\n MAX_TAG_LENGTH,\n RESERVED_MESSAGE_MEDIA_TYPE,\n} from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment, Case, CaseStatus, Label, Link, Parameter, Step } from '../shared/types.js';\nimport type { RuntimeMessage } from '../runtime/message-types.js';\nimport { AttachmentBudget, inlineFromBuffer, inlineFromFile } from './attachment-reader.js';\nimport { mapSteps } from './step-mapper.js';\n\n/**\n * Maps Playwright's 5 result statuses onto the wire contract's vocabulary.\n *\n * `qualflare-cli` accepts exactly 7 values and turns anything it does not\n * recognize into `error` — NOT into a pass — so every Playwright status is\n * mapped explicitly here rather than passed through and hoped for.\n */\nfunction mapStatus(status: TestResult['status']): CaseStatus {\n switch (status) {\n case 'passed':\n return 'passed';\n case 'failed':\n return 'failed';\n case 'timedOut':\n return 'timeout';\n case 'interrupted':\n return 'aborted';\n case 'skipped':\n return 'skipped';\n default:\n return 'error';\n }\n}\n\n// Matches ANSI SGR escapes. Written as a unicode escape rather than a literal\n// control character, so this source stays copy-pasteable and greppable.\n// eslint-disable-next-line no-control-regex -- matching ANSI escapes requires the escape byte itself\nconst ANSI_PATTERN = /\\u001b\\[[0-9;]*m/g;\n\nfunction stripAnsi(text: string): string {\n return text.replace(ANSI_PATTERN, '');\n}\n\n/**\n * Playwright's TestError carries `message` for thrown Errors and `value` for\n * non-Error throws (`throw 'boom'`). `snippet` is the rendered code frame\n * with the failing line highlighted — genuinely the most useful part of a\n * Playwright failure, and something the built-in JSON reporter flattens — but\n * it arrives ANSI-colored, which would render as escape soup in a web UI.\n */\nfunction formatError(result: TestResult): string | undefined {\n const err = result.error;\n if (!err) {\n return undefined;\n }\n const head = err.message ?? err.value ?? 'test failed';\n const parts = [stripAnsi(head)];\n if (err.snippet) {\n parts.push('', stripAnsi(err.snippet));\n }\n if (err.stack && !head.includes(err.stack)) {\n parts.push('', stripAnsi(err.stack));\n }\n return parts.join('\\n');\n}\n\ninterface ReplayedMetadata {\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: Case['priority'];\n caseParameters: Parameter[];\n stepParameters: Map<string, Parameter[]>;\n attachments: Attachment[];\n}\n\n/**\n * Replays the `qualflare.*()` calls a test made, which reach the reporter as\n * attachments under a reserved content type (see runtime/qualflare-api.ts).\n *\n * `parameter()` placement follows the rule shared with the sibling packages:\n * inside an open `step()` it belongs to that step, outside any step it\n * belongs to the case's properties. The step_start/step_stop pair exists only\n * to establish that bracket — the step ITSELF is already captured natively,\n * because `qualflare.step()` delegates to `test.step()`, so synthesizing a\n * second step from these messages would double-report every manual step.\n */\nfunction replayMetadata(\n result: TestResult,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): ReplayedMetadata {\n const meta: ReplayedMetadata = {\n labels: [],\n links: [],\n tags: [],\n caseParameters: [],\n stepParameters: new Map(),\n attachments: [],\n };\n const openSteps: string[] = [];\n\n for (const a of result.attachments) {\n if (a.contentType !== RESERVED_MESSAGE_MEDIA_TYPE || !a.body) {\n continue;\n }\n let message: RuntimeMessage;\n try {\n message = JSON.parse(a.body.toString('utf8')) as RuntimeMessage;\n } catch {\n logger.warn('ignoring an unparseable qualflare runtime message.');\n continue;\n }\n\n switch (message.type) {\n case 'label':\n meta.labels.push({ name: message.name, value: message.value });\n break;\n case 'link':\n meta.links.push({\n type: message.linkType ?? 'custom',\n ...(message.name ? { name: message.name } : {}),\n url: message.url,\n });\n break;\n case 'tag':\n meta.tags.push(...message.tags);\n break;\n case 'description':\n meta.description = message.text;\n break;\n case 'priority':\n meta.priority = message.value;\n break;\n case 'parameter': {\n const param: Parameter = {\n name: message.name,\n ...(message.value !== undefined ? { value: message.value } : {}),\n ...(message.masked ? { masked: true } : {}),\n };\n const openStep = openSteps[openSteps.length - 1];\n if (openStep === undefined) {\n meta.caseParameters.push(param);\n } else {\n const existing = meta.stepParameters.get(openStep) ?? [];\n existing.push(param);\n meta.stepParameters.set(openStep, existing);\n }\n break;\n }\n case 'attachment': {\n // Decoded back to bytes rather than trusting the base64 length, so the\n // cap is applied to the real payload size the server will receive.\n const inlined = inlineFromBuffer(\n message.name,\n Buffer.from(message.contentBase64, 'base64'),\n message.mimeType,\n config,\n budget,\n );\n if (inlined) {\n meta.attachments.push(inlined);\n }\n break;\n }\n case 'attachment_from_file': {\n const fromFile = inlineFromFile(message.name, message.path, message.mimeType, config, budget);\n if (fromFile) {\n meta.attachments.push(fromFile);\n }\n break;\n }\n case 'step_start':\n openSteps.push(message.name);\n break;\n case 'step_stop':\n openSteps.pop();\n break;\n }\n }\n\n return meta;\n}\n\n\n/** Truncates and caps tags to the server's limits, so a runaway loop in a\n * test can't get a whole launch rejected at validation. */\nfunction capTags(tags: string[]): string[] {\n const unique = [...new Set(tags.map((t) => t.slice(0, MAX_TAG_LENGTH)))];\n return unique.slice(0, MAX_TAGS_PER_CASE);\n}\n\n/**\n * Builds one wire `Case` from a Playwright test and all of its attempts.\n *\n * Called from `onEnd`, never `onTestEnd`: `test.outcome()` is only meaningful\n * once every retry has finished, so asking mid-flight would report a\n * to-be-retried failure as a plain failure and never mark anything flaky.\n */\nexport function buildCase(\n test: TestCase,\n config: ResolvedReporterConfig,\n /** Attachments already resolved in onTestEnd, keyed `${test.id}:${retry}`.\n * Resolution cannot be deferred to onEnd: `use.preserveOutput` and a\n * passing retry both delete a previous attempt's output directory, so by\n * onEnd the screenshot/video files may no longer exist. */\n attachmentsByResult: ReadonlyMap<string, Attachment[]>,\n /** The run-wide inline budget. Needed here because the metadata API's own\n * attachments (`qualflare.attachment()` / `attachmentFromFile()`) are\n * resolved at case-build time, and they must draw on the SAME budget as\n * Playwright's attachments — an uncapped path can push the request past\n * `/collect`'s 10MB body limit and lose the entire launch. */\n budget: AttachmentBudget,\n): Case | undefined {\n // workerIndex === -1 means the test never actually ran (the run was\n // interrupted before it started); there is no result worth reporting.\n const results = test.results.filter((r) => r.workerIndex !== -1);\n if (results.length === 0) {\n return undefined;\n }\n\n const final = results[results.length - 1]!;\n const outcome = test.outcome();\n\n // A `test.fail()` test that failed is a PASS: the author declared the\n // failure expected, and Playwright reports that as outcome 'expected'. The\n // error text is kept so the report still shows what actually happened.\n const expectedFailure = outcome === 'expected' && final.status === 'failed';\n const status: CaseStatus = expectedFailure ? 'passed' : mapStatus(final.status);\n\n const meta = replayMetadata(final, config, budget);\n const steps: Step[] = mapSteps(final.steps, config.includeApiSteps);\n\n // Attach parameters recorded between a step_start/step_stop bracket to the\n // matching native step. Matched by title, last occurrence wins — a repeated\n // step title is rare, and mis-attributing a parameter is a much smaller\n // problem than dropping it.\n for (const [stepName, params] of meta.stepParameters) {\n for (let i = steps.length - 1; i >= 0; i -= 1) {\n if (steps[i]!.name === stepName) {\n steps[i]!.parameters = [...(steps[i]!.parameters ?? []), ...params];\n break;\n }\n }\n }\n\n const projectName = test.parent.project()?.name;\n const properties: Record<string, string> = {\n file: test.location.file,\n ...(projectName ? { project: projectName } : {}),\n };\n for (const p of meta.caseParameters) {\n properties[p.name] = p.value ?? '';\n }\n\n const attachments = [...(attachmentsByResult.get(`${test.id}:${final.retry}`) ?? []), ...meta.attachments];\n\n // Playwright's own tags (@-tokens in titles, plus describe/test `tag`\n // options) merged with anything qualflare.tag() added.\n //\n // Read defensively because `TestCase.tags` only exists from Playwright\n // 1.42; on 1.40/1.41 it is undefined and spreading it would throw. Rather\n // than raise the peer floor and hard-block those users, they simply get no\n // native tags — a concept their Playwright does not have anyway — while\n // qualflare.tag() keeps working. Verified against 1.40.0's shipped types.\n const nativeTags = (test as { tags?: string[] }).tags ?? [];\n const tags = capTags([...nativeTags, ...meta.tags]);\n const error = formatError(final);\n\n return {\n id: test.id,\n name: test.title,\n className: test.location.file,\n status,\n duration: msToNs(final.duration),\n retryCount: results.length - 1,\n isFlaky: outcome === 'flaky',\n ...(error ? { error } : {}),\n ...(meta.priority ? { priority: meta.priority } : {}),\n ...(meta.description ? { description: meta.description } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n properties,\n ...(attachments.length > 0 ? { attachments } : {}),\n ...(steps.length > 0 ? { steps } : {}),\n ...(meta.labels.length > 0 ? { labels: meta.labels.slice(0, MAX_LABELS_PER_CASE) } : {}),\n ...(meta.links.length > 0 ? { links: meta.links.slice(0, MAX_LINKS_PER_CASE) } : {}),\n startedAt: final.startTime.toISOString(),\n };\n}\n","import * as os from 'node:os';\n\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport type { ResolvedReporterConfig } from '../config/resolve-config.js';\nimport type { Collect, Suite } from '../shared/types.js';\n\nfunction resolveOs(config: ResolvedReporterConfig): string {\n if (config.os) {\n return config.os;\n }\n return `${os.type()} ${os.release()}`;\n}\n\n/**\n * Launch-level browser. Playwright is the only one of the three reporters\n * that genuinely knows this: `FullProject.use.browserName` is real, whereas\n * `qualflare-cli`'s existing Playwright parser reports the PROJECT NAME here\n * (so a project called `smoke` or `mobile-safari` becomes the \"browser\").\n *\n * A multi-project run has no single browser, so the distinct set is joined\n * rather than picking one arbitrarily; per-suite attribution is finer-grained\n * and lives on `Suite.browser`.\n */\nfunction resolveBrowser(config: ResolvedReporterConfig, browsers: readonly string[]): string {\n if (config.browser) {\n return config.browser;\n }\n return [...new Set(browsers)].sort().join(', ');\n}\n\n/**\n * Assembles the final `Collect` payload at `onEnd`.\n *\n * CI metadata and branch/commit detection are already fully resolved by\n * `resolve-config.ts` — this reads the resolved config through and does NOT\n * call `ci-detect`/`git-detect` itself, matching both sibling packages.\n *\n * `metadata` is not optional decoration: `qualflare-cli` identifies this\n * format by the presence of `framework` + `metadata` + `suites` together.\n * Omitting it makes the CLI fall back to filename matching, where a file\n * whose name contains \"playwright\" is routed to the built-in-JSON parser and\n * fails to parse. For the same reason this payload must never grow a\n * top-level `config` key — that is the Playwright-JSON detector's signature.\n */\nexport function buildCollectPayload(\n suites: Suite[],\n config: ResolvedReporterConfig,\n browsers: readonly string[] = [],\n): Collect {\n return {\n framework: config.framework,\n platform: config.platform,\n os: resolveOs(config),\n browser: resolveBrowser(config, browsers),\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-playwright',\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 * 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';\n\n/**\n * Makes a spec path stable and portable: relative to the Playwright project\n * root, with POSIX separators regardless of the OS that produced it.\n *\n * Without this, the same suite reported from a Windows runner and a Linux\n * runner would be two different suites server-side, and an absolute path\n * would leak a CI agent's directory layout into the report.\n */\nexport function relativizeFile(file: string, rootDir: string): string {\n const relative = path.isAbsolute(file) ? path.relative(rootDir, file) : file;\n return relative.split(path.sep).join('/');\n}\n\n/** One case plus the spec file it came from. */\nexport interface CaseWithFile {\n file: string;\n browser?: string;\n testCase: Case;\n}\n\n/**\n * Groups finished cases into one Suite per spec file.\n *\n * Grouping happens once at `onEnd` rather than incrementally, because\n * Playwright interleaves results across workers — with `fullyParallel` and N\n * workers there is no point during the run at which one file's cases are\n * known to be complete.\n */\nexport function groupIntoSuites(cases: readonly CaseWithFile[]): Suite[] {\n const byFile = new Map<string, CaseWithFile[]>();\n for (const entry of cases) {\n const existing = byFile.get(entry.file);\n if (existing) {\n existing.push(entry);\n } else {\n byFile.set(entry.file, [entry]);\n }\n }\n\n const suites: Suite[] = [];\n for (const [file, entries] of byFile) {\n let kept = entries;\n if (kept.length > MAX_CASES_PER_SUITE) {\n logger.warn(\n `suite \"${file}\" produced ${kept.length} cases, over the server's limit of ${MAX_CASES_PER_SUITE}; the rest were dropped.`,\n );\n kept = kept.slice(0, MAX_CASES_PER_SUITE);\n }\n\n // Browsers are per-project, and one spec file can run under several\n // projects (chromium + firefox + webkit). Report the distinct set rather\n // than whichever happened to finish last.\n const browsers = [...new Set(kept.map((e) => e.browser).filter((b): b is string => Boolean(b)))].sort();\n\n suites.push({\n name: file,\n category: 'playwright',\n duration: kept.reduce((sum, e) => sum + e.testCase.duration, 0),\n ...(browsers.length > 0 ? { browser: browsers.join(', ') } : {}),\n cases: kept.map((e) => e.testCase),\n });\n }\n\n if (suites.length > MAX_SUITES_PER_LAUNCH) {\n logger.warn(\n `this run produced ${suites.length} suites, over the server's limit of ${MAX_SUITES_PER_LAUNCH}; the rest were dropped.`,\n );\n return suites.slice(0, MAX_SUITES_PER_LAUNCH);\n }\n return suites;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,sBAA2B;AAC3B,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;;;ACFtB,yBAA2B;;;ACWpB,IAAM,8BAA8B;AAIpC,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB;AAG5B,IAAM,2BAA2B;AACjC,IAAM,sBAAsB;AAC5B,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AAIvB,IAAM,yBAAyB,KAAK,OAAO;AAM3C,IAAM,6BAA6B;;;ACjC1C,aAAwB;AA2BxB,SAAS,iBAAiB,KAA6C;AACrE,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AACA,QAAM,IAAI,OAAO,SAAS,KAAK,EAAE;AACjC,SAAO,OAAO,SAAS,CAAC,KAAK,KAAK,IAAI,IAAI;AAC5C;AAEA,SAAS,SAAS,KAA6C;AAC7D,SAAO,OAAO,IAAI,SAAS,IAAI,MAAM;AACvC;AASA,IAAM,YAAiC;AAAA,EACrC;AAAA,IACE,QAAQ,CAAC,QAAQ,IAAI,mBAAmB;AAAA,IACxC,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,iBAAiB;AAAA,IACpD,OAAO,CAAC,QAAQ,SAAS,IAAI,aAAa;AAAA,IAC1C,QAAQ,CAAC,QACP,IAAI,qBAAqB,IAAI,qBAAqB,IAAI,gBAClD,GAAG,IAAI,iBAAiB,IAAI,IAAI,iBAAiB,iBAAiB,IAAI,aAAa,KACnF;AAAA,IACN,UAAU,CAAC,QAAQ;AACjB,YAAM,QAAQ,6BAA6B,KAAK,IAAI,cAAc,EAAE;AACpE,aAAO,QAAQ,iBAAiB,MAAM,CAAC,CAAC,IAAI;AAAA,IAC9C;AAAA,EACF;AAAA,EACA;AAAA,IACE,QAAQ,CAAC,QAAQ,IAAI,cAAc;AAAA,IACnC,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,eAAe;AAAA,IAClD,OAAO,CAAC,QAAQ,SAAS,IAAI,cAAc;AAAA,IAC3C,QAAQ,CAAC,QAAQ,SAAS,IAAI,eAAe;AAAA,IAC7C,UAAU,CAAC,QAAQ,iBAAiB,IAAI,oBAAoB;AAAA,EAC9D;AAAA,EACA;AAAA,IACE,QAAQ,CAAC,QAAQ,IAAI,aAAa;AAAA,IAClC,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,gBAAgB;AAAA,IACnD,OAAO,CAAC,QAAQ,SAAS,IAAI,sBAAsB,IAAI,gBAAgB;AAAA,IACvE,QAAQ,CAAC,QAAQ,SAAS,IAAI,gBAAgB;AAAA,IAC9C,UAAU,CAAC,QAAQ,iBAAiB,IAAI,gBAAgB;AAAA,EAC1D;AAAA,EACA;AAAA,IACE,QAAQ,CAAC,QAAQ,IAAI,cAAc;AAAA,IACnC,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,sBAAsB;AAAA,IACzD,OAAO,CAAC,QAAQ,SAAS,IAAI,kBAAkB;AAAA,IAC/C,QAAQ,CAAC,QAAQ,SAAS,IAAI,mBAAmB;AAAA,IACjD,UAAU,CAAC,QAAQ;AACjB,YAAM,MAAM,IAAI;AAChB,UAAI,CAAC,OAAO,QAAQ,SAAS;AAC3B,eAAO;AAAA,MACT;AACA,aAAO,iBAAiB,GAAG;AAAA,IAC7B;AAAA,EACF;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,QAAQ,CAAC,QAAQ,QAAQ,IAAI,WAAW;AAAA,IACxC,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,YAAY;AAAA,IAC/C,OAAO,CAAC,QAAQ,SAAS,IAAI,aAAa,IAAI,YAAY;AAAA,IAC1D,QAAQ,CAAC,QAAQ,SAAS,IAAI,SAAS;AAAA;AAAA;AAAA;AAAA,EAIzC;AAAA,EACA;AAAA,IACE,QAAQ,CAAC,QAAQ,IAAI,aAAa,UAAU,IAAI,aAAa;AAAA,IAC7D,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,aAAa;AAAA,IAChD,OAAO,CAAC,QAAQ,SAAS,IAAI,aAAa;AAAA,IAC1C,QAAQ,CAAC,QAAQ;AACf,YAAM,gBAAgB,IAAI;AAC1B,YAAM,UAAU,IAAI;AACpB,YAAM,UAAU,IAAI;AACpB,UAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,SAAS;AAC1C,eAAO;AAAA,MACT;AACA,aAAO,GAAG,cAAc,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,OAAO,CAAC,2BAA2B,OAAO;AAAA,IAC9G;AAAA,IACA,UAAU,CAAC,QAAQ,iBAAiB,IAAI,oCAAoC;AAAA,EAC9E;AAAA,EACA;AAAA,IACE,QAAQ,CAAC,QAAQ,QAAQ,IAAI,sBAAsB;AAAA,IACnD,cAAc;AAAA,IACd,aAAa,CAAC,QAAQ,SAAS,IAAI,sBAAsB;AAAA,IACzD,OAAO,CAAC,QAAQ,SAAS,IAAI,sBAAsB;AAAA,IACnD,QAAQ,CAAC,QAAQ;AACf,YAAM,SAAS,IAAI;AACnB,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AACA,YAAM,YAAY,IAAI,2BAA2B,IAAI;AACrD,aAAO,YAAY,GAAG,MAAM,mCAAmC,SAAS,KAAK;AAAA,IAC/E;AAAA,IACA,UAAU,CAAC,QAAQ,iBAAiB,IAAI,eAAe;AAAA,EACzD;AACF;AAiBO,SAAS,SAAS,MAAyB,QAAQ,KAAiB;AACzE,QAAM,WAAW,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC;AACpD,MAAI,UAAU;AACZ,UAAM,SAAqB,EAAE,YAAY,SAAS,aAAa;AAC/D,UAAM,cAAc,SAAS,cAAc,GAAG;AAC9C,QAAI,gBAAgB,OAAW,QAAO,gBAAgB;AACtD,UAAM,SAAS,SAAS,SAAS,GAAG;AACpC,QAAI,WAAW,OAAW,QAAO,WAAW;AAC5C,UAAM,WAAW,SAAS,WAAW,GAAG;AACxC,QAAI,aAAa,OAAW,QAAO,aAAa;AAChD,UAAM,QAAQ,SAAS,QAAQ,GAAG;AAClC,QAAI,UAAU,OAAW,QAAO,UAAU;AAC1C,WAAO;AAAA,EACT;AAOA,MAAW,aAAM;AACf,WAAO,EAAE,YAAmB,YAAK;AAAA,EACnC;AACA,SAAO,CAAC;AACV;;;AC/KA,gCAA6B;AAkB7B,IAAM,iBAA0B,CAAC,MAAM,YACrC,wCAAa,OAAO,MAAM,EAAE,KAAK,UAAU,QAAQ,OAAO,CAAC,UAAU,QAAQ,QAAQ,EAAE,CAAC;AAE1F,SAAS,SAAS,QAA2B,OAAqC;AAChF,aAAWC,SAAQ,OAAO;AACxB,UAAM,QAAQ,IAAIA,KAAI;AACtB,QAAI,OAAO;AACT,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAe,KAAiC;AAC3E,MAAI;AAKF,UAAM,MAAM,KAAK,CAAC,gBAAgB,WAAW,MAAM,MAAM,GAAG,GAAG,EAAE,KAAK;AACtE,WAAO,OAAO;AAAA,EAChB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oBAAoB,MAAe,KAAiC;AAC3E,MAAI;AACF,UAAM,MAAM,KAAK,CAAC,aAAa,MAAM,GAAG,GAAG,EAAE,KAAK;AAClD,WAAO,OAAO;AAAA,EAChB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,UACd,MAAyB,QAAQ,KACjC,MAAc,QAAQ,IAAI,GAC1B,OAAgB,gBACP;AACT,QAAM,SACJ,SAAS,KAAK,cAAc,mBAAmB,sBAAsB,kBAAkB,KACvF,oBAAoB,MAAM,GAAG;AAC/B,QAAM,SACJ,SAAS,KAAK,cAAc,cAAc,iBAAiB,kBAAkB,KAC7E,oBAAoB,MAAM,GAAG;AAE/B,QAAM,SAAkB,CAAC;AACzB,MAAI,WAAW,OAAW,QAAO,SAAS;AAC1C,MAAI,WAAW,OAAW,QAAO,SAAS;AAC1C,SAAO;AACT;;;AH2BA,SAASC,aAAY,OAAqC;AACxD,aAAWC,SAAQ,OAAO;AACxB,UAAM,QAAQ,QAAQ,IAAIA,KAAI;AAC9B,QAAI,UAAU,UAAa,UAAU,IAAI;AACvC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAsC;AACxD,QAAM,MAAMD,UAAS,GAAG,KAAK;AAC7B,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,UAAU,QAAQ;AACnC;AAEA,SAAS,UAAU,OAAqC;AACtD,QAAM,MAAMA,UAAS,GAAG,KAAK;AAC7B,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,QAAM,SAAS,OAAO,SAAS,KAAK,EAAE;AACtC,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AA6BO,SAAS,cACd,SACA,OAII,CAAC,GACmB;AACxB,QAAM,cAAc,KAAK,aAAa;AACtC,QAAM,aAAa,KAAK,YAAY;AAEpC,QAAM,UAAU,QAAQ,WAAW,QAAQ,mBAAmB,KAAK;AAEnE,QAAM,YAAY,QAAQ,aAAaA,UAAS,sBAAsB,KAAK;AAC3E,QAAM,aAAa,QAAQ,cAAc,OAAO,uBAAuB,KAAK,KAAK;AAEjF,QAAM,eAAe,QAAQ,cAAc,SAAY,QAAQ,YAAY,OAAO,uBAAuB,cAAc;AACvH,QAAM,YAAY,iBAAiB,UAAa,iBAAiB,QAAQ,gBAAgB,IAAI,eAAe;AAE5G,QAAM,YAAYA,UAAS,oBAAoB,WAAW;AAC1D,QAAM,YAAYA,UAAS,oBAAoB,WAAW;AAC1D,QAAM,oBACH,QAAQ,WAAW,UAAa,cAAc,UAC9C,QAAQ,WAAW,UAAa,cAAc;AACjD,QAAM,cAAc,oBAAoB,YAAY,IAAI,CAAC;AAEzD,QAAM,SAAS,QAAQ,WAAW,SAAY,QAAQ,SAAU,aAAa,YAAY,UAAU;AACnG,QAAM,SAAS,QAAQ,WAAW,SAAY,QAAQ,SAAU,aAAa,YAAY,UAAU;AAEnG,QAAM,aAAa,WAAW;AAC9B,QAAM,aAAa,QAAQ,cAAc,WAAW;AACpD,QAAM,gBAAgB,QAAQ,iBAAiB,WAAW;AAC1D,QAAM,WAAW,QAAQ,YAAY,WAAW;AAChD,QAAM,aAAa,QAAQ,cAAc,WAAW;AAKpD,QAAM,QAAQ,QAAQ,SAASA,UAAS,kBAAkB,KAAK,WAAW,eAAW,+BAAW;AAEhG,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,cAAc,QAAQ,eAAe,WAAcA,UAAS,yBAAyB,gBAAgB,KAAK;AAAA,IAC1G,WAAW,QAAQ,YAAY,WAAcA,UAAS,sBAAsB,aAAa,KAAK;AAAA,IAC9F;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,WAAW,QAAQ,aAAa;AAAA,IAChC,IAAI,QAAQ;AAAA,IACZ,SAAS,QAAQ;AAAA,IACjB,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB,QAAQ,qBAAqB,QAAQ,8BAA8B,KAAK;AAAA,IAC3F,iBAAiB,QAAQ,mBAAmB,QAAQ,6BAA6B,KAAK;AAAA,IACtF,oBAAoB,QAAQ,sBAAsB,OAAO,gCAAgC,KAAK;AAAA,IAC9F,yBACE,QAAQ,2BAA2B,OAAO,sCAAsC,KAAK;AAAA,IACvF,eAAe,QAAQ,iBAAiB,OAAO,2BAA2B,KAAK;AAAA,IAC/E,OAAO,QAAQ,SAAS,QAAQ,mBAAmB,UAAU,KAAK;AAAA,IAClE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AIrOA,IAAM,SAAS;AAER,IAAM,SAAS;AAAA,EACpB,SAAS,MAAuB;AAC9B,YAAQ,MAAM,QAAQ,GAAG,IAAI;AAAA,EAC/B;AAAA,EACA,QAAQ,MAAuB;AAC7B,YAAQ,IAAI,QAAQ,GAAG,IAAI;AAAA,EAC7B;AAAA,EACA,QAAQ,MAAuB;AAC7B,YAAQ,KAAK,QAAQ,GAAG,IAAI;AAAA,EAC9B;AAAA,EACA,SAAS,MAAuB;AAC9B,YAAQ,MAAM,QAAQ,GAAG,IAAI;AAAA,EAC/B;AACF;;;ACrBA,IAAAE,MAAoB;;;ACApB,SAAoB;AACpB,WAAsB;AACtB,IAAAC,sBAA2B;AAU3B,IAAM,gCAAwD;AAAA,EAC5D,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AACV;AAyBO,SAAS,oBACd,UACA,WACA,eAC6B;AAC7B,QAAM,MAAW,aAAQ,QAAQ,EAAE,YAAY;AAC/C,QAAM,WAAW,8BAA8B,GAAG;AAClD,MAAI,CAAC,UAAU;AACb,WAAO,KAAK,8BAA8B,QAAQ,8BAA8B;AAChF,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AAGF,eAAc,YAAS,QAAQ,EAAE;AAAA,EACnC,SAAS,KAAK;AACZ,WAAO,KAAK,8BAA8B,QAAQ,2BAA4B,IAAc,OAAO,EAAE;AACrG,WAAO;AAAA,EACT;AACA,MAAI,WAAW,eAAe;AAC5B,WAAO;AAAA,MACL,8BAA8B,QAAQ,MAAM,QAAQ,sDAC1B,aAAa;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,OAAG,gCAAW,CAAC,GAAG,GAAG;AAC5C,MAAI;AACF,IAAG,aAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,IAAG,gBAAa,UAAe,UAAK,WAAW,cAAc,CAAC;AAAA,EAChE,SAAS,KAAK;AACZ,WAAO,KAAK,8BAA8B,QAAQ,2BAA4B,IAAc,OAAO,EAAE;AACrG,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,gBAAgB,UAAU,SAAS;AAC9C;;;ADnEO,IAAM,mBAAN,MAAuB;AAAA,EAG5B,YAA6B,eAAuB;AAAvB;AAAA,EAAwB;AAAA,EAAxB;AAAA,EAFrB,OAAO;AAAA,EAIf,WAAW,OAAwB;AACjC,QAAI,KAAK,OAAO,QAAQ,KAAK,eAAe;AAC1C,aAAO;AAAA,IACT;AACA,SAAK,QAAQ;AACb,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,OAAqB;AAC3B,SAAK,OAAO,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK;AAAA,EAC3C;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AACF;AAIA,IAAM,aAAa;AACnB,IAAM,aAAa;AAEnB,SAAS,QAAQ,GAA+C;AAC9D,SAAO,EAAE,SAAS,eAAe,EAAE,aAAa,WAAW,QAAQ,KAAK;AAC1E;AAqBO,SAAS,mBACd,QACA,QACA,QACc;AACd,MAAI,CAAC,OAAO,mBAAmB;AAC7B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,MAAoB,CAAC;AAC3B,MAAI,YAAY;AAEhB,aAAW,KAAK,OAAO,aAAa;AAGlC,QAAI,EAAE,gBAAgB,6BAA6B;AACjD;AAAA,IACF;AAEA,QAAI,IAAI,UAAU,0BAA0B;AAC1C,UAAI,CAAC,WAAW;AACd,oBAAY;AACZ,eAAO,KAAK,6BAA6B,wBAAwB,sCAAsC;AAAA,MACzG;AACA;AAAA,IACF;AAEA,QAAI,EAAE,SAAS,cAAc,EAAE,gBAAgB,mBAAmB;AAChE;AAAA,IACF;AAEA,QAAI,QAAQ,CAAC,GAAG;AACd,UAAI,CAAC,EAAE,MAAM;AAIX,eAAO,KAAK,wCAAwC,EAAE,IAAI,2CAA2C;AACrG;AAAA,MACF;AACA,YAAM,SAAS,oBAAoB,EAAE,MAAM,OAAO,WAAW,OAAO,aAAa;AACjF,UAAI,QAAQ;AACV,YAAI,KAAK;AAAA,UACP,MAAM,EAAE;AAAA,UACR,UAAU,OAAO;AAAA,UACjB,gBAAgB,OAAO;AAAA,UACvB,UAAU,OAAO;AAAA,QACnB,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAEA,UAAM,UAAU,iBAAiB,GAAG,QAAQ,MAAM;AAClD,QAAI,SAAS;AACX,UAAI,KAAK,OAAO;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,iBACdC,OACA,OACA,UACA,QACA,QACwB;AACxB,MAAI,MAAM,aAAa,OAAO,oBAAoB;AAChD,WAAO;AAAA,MACL,wBAAwBA,KAAI,MAAM,MAAM,UAAU,2DAA2D,OAAO,kBAAkB;AAAA,IACxI;AACA,WAAO;AAAA,EACT;AACA,MAAI,CAAC,OAAO,WAAW,MAAM,UAAU,GAAG;AACxC,WAAO;AAAA,MACL,wBAAwBA,KAAI,mDAAmD,OAAO,uBAAuB;AAAA,IAC/G;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAAA;AAAA,IACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,SAAS,MAAM,SAAS,QAAQ;AAAA,IAChC,UAAU,MAAM;AAAA,EAClB;AACF;AASO,SAAS,eACdA,OACA,UACA,UACA,QACA,QACwB;AACxB,MAAI;AACJ,MAAI;AACF,WAAU,aAAS,QAAQ,EAAE;AAAA,EAC/B,SAAS,KAAK;AACZ,WAAO,KAAK,wBAAwBA,KAAI,qBAAqB,QAAQ,KAAM,IAAc,OAAO,EAAE;AAClG,WAAO;AAAA,EACT;AACA,MAAI,OAAO,OAAO,oBAAoB;AACpC,WAAO;AAAA,MACL,wBAAwBA,KAAI,MAAM,IAAI,2DAA2D,OAAO,kBAAkB;AAAA,IAC5H;AACA,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACF,YAAW,iBAAa,QAAQ;AAAA,EAClC,SAAS,KAAK;AACZ,WAAO,KAAK,wBAAwBA,KAAI,qBAAqB,QAAQ,KAAM,IAAc,OAAO,EAAE;AAClG,WAAO;AAAA,EACT;AACA,SAAO,iBAAiBA,OAAM,OAAO,UAAU,QAAQ,MAAM;AAC/D;AAEA,SAAS,iBACP,GACA,QACA,QACwB;AACxB,MAAI,EAAE,MAAM;AACV,WAAO,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,QAAQ,MAAM;AAAA,EACvE;AACA,MAAI,EAAE,MAAM;AACV,WAAO,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,QAAQ,MAAM;AAAA,EACrE;AACA,SAAO;AACT;;;AErNA,IAAM,YAAY;AAYX,SAAS,OAAO,IAAgC;AACrD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,MAAM,GAAG;AACnC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,MAAM,KAAK,SAAS;AAClC;;;ACTA,IAAM,qBAAqB;AAC3B,IAAM,kBAAkB;AACxB,IAAM,gBAAgB;AACtB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AAWxB,IAAM,iBAAiB;AAgBvB,SAAS,aAAa,MAAgB,iBAAmC;AACvE,MAAI,KAAK,OAAO;AACd,WAAO;AAAA,EACT;AACA,UAAQ,KAAK,UAAU;AAAA,IACrB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAQH,aAAO;AAAA,IACT;AAIE,aAAO;AAAA,EACX;AACF;AAGA,SAAS,eAAe,MAAoC;AAC1D,MAAI,CAAC,KAAK,UAAU;AAClB,WAAO;AAAA,EACT;AACA,SAAO,GAAG,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI;AACpD;AAcO,SAAS,SAAS,OAA4B,iBAAkC;AACrF,QAAM,MAAc,CAAC;AACrB,MAAI,YAAY;AAEhB,QAAM,OAAO,CAAC,OAA4B,aAAiC,UAAwB;AACjG,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,aAAa,MAAM,eAAe,GAAG;AAKxC,aAAK,KAAK,OAAO,aAAa,KAAK;AACnC;AAAA,MACF;AAEA,UAAI,IAAI,UAAU,4BAA4B;AAC5C,YAAI,CAAC,WAAW;AACd,sBAAY;AACZ,iBAAO;AAAA,YACL,6BAA6B,0BAA0B;AAAA,UAEzD;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,QAAQ,IAAI;AAClB,UAAI,KAAK;AAAA,QACP,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK,QAAQ,WAAW;AAAA,QAChC,UAAU,OAAO,KAAK,QAAQ;AAAA,QAC9B,GAAI,KAAK,QAAQ,EAAE,OAAO,gBAAgB,IAAI,EAAE,IAAI,CAAC;AAAA,QACrD,GAAI,eAAe,IAAI,IAAI,EAAE,UAAU,eAAe,IAAI,EAAE,IAAI,CAAC;AAAA,QACjE,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACrD,CAAC;AAID,YAAM,aAAa,QAAQ,KAAK,iBAAiB,cAAc;AAC/D,WAAK,KAAK,OAAO,YAAY,QAAQ,CAAC;AAAA,IACxC;AAAA,EACF;AAEA,OAAK,OAAO,QAAW,CAAC;AACxB,SAAO;AACT;AAIA,SAAS,gBAAgB,MAAwB;AAC/C,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AACA,SAAO,IAAI,WAAW,IAAI,SAAS;AACrC;;;ACtHA,SAAS,UAAU,QAA0C;AAC3D,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKA,IAAM,eAAe;AAErB,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,cAAc,EAAE;AACtC;AASA,SAAS,YAAY,QAAwC;AAC3D,QAAM,MAAM,OAAO;AACnB,MAAI,CAAC,KAAK;AACR,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI,WAAW,IAAI,SAAS;AACzC,QAAM,QAAQ,CAAC,UAAU,IAAI,CAAC;AAC9B,MAAI,IAAI,SAAS;AACf,UAAM,KAAK,IAAI,UAAU,IAAI,OAAO,CAAC;AAAA,EACvC;AACA,MAAI,IAAI,SAAS,CAAC,KAAK,SAAS,IAAI,KAAK,GAAG;AAC1C,UAAM,KAAK,IAAI,UAAU,IAAI,KAAK,CAAC;AAAA,EACrC;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAwBA,SAAS,eACP,QACA,QACA,QACkB;AAClB,QAAM,OAAyB;AAAA,IAC7B,QAAQ,CAAC;AAAA,IACT,OAAO,CAAC;AAAA,IACR,MAAM,CAAC;AAAA,IACP,gBAAgB,CAAC;AAAA,IACjB,gBAAgB,oBAAI,IAAI;AAAA,IACxB,aAAa,CAAC;AAAA,EAChB;AACA,QAAM,YAAsB,CAAC;AAE7B,aAAW,KAAK,OAAO,aAAa;AAClC,QAAI,EAAE,gBAAgB,+BAA+B,CAAC,EAAE,MAAM;AAC5D;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,EAAE,KAAK,SAAS,MAAM,CAAC;AAAA,IAC9C,QAAQ;AACN,aAAO,KAAK,oDAAoD;AAChE;AAAA,IACF;AAEA,YAAQ,QAAQ,MAAM;AAAA,MACpB,KAAK;AACH,aAAK,OAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,OAAO,QAAQ,MAAM,CAAC;AAC7D;AAAA,MACF,KAAK;AACH,aAAK,MAAM,KAAK;AAAA,UACd,MAAM,QAAQ,YAAY;AAAA,UAC1B,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,UAC7C,KAAK,QAAQ;AAAA,QACf,CAAC;AACD;AAAA,MACF,KAAK;AACH,aAAK,KAAK,KAAK,GAAG,QAAQ,IAAI;AAC9B;AAAA,MACF,KAAK;AACH,aAAK,cAAc,QAAQ;AAC3B;AAAA,MACF,KAAK;AACH,aAAK,WAAW,QAAQ;AACxB;AAAA,MACF,KAAK,aAAa;AAChB,cAAM,QAAmB;AAAA,UACvB,MAAM,QAAQ;AAAA,UACd,GAAI,QAAQ,UAAU,SAAY,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC;AAAA,UAC9D,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,QAC3C;AACA,cAAM,WAAW,UAAU,UAAU,SAAS,CAAC;AAC/C,YAAI,aAAa,QAAW;AAC1B,eAAK,eAAe,KAAK,KAAK;AAAA,QAChC,OAAO;AACL,gBAAM,WAAW,KAAK,eAAe,IAAI,QAAQ,KAAK,CAAC;AACvD,mBAAS,KAAK,KAAK;AACnB,eAAK,eAAe,IAAI,UAAU,QAAQ;AAAA,QAC5C;AACA;AAAA,MACF;AAAA,MACA,KAAK,cAAc;AAGjB,cAAM,UAAU;AAAA,UACd,QAAQ;AAAA,UACR,OAAO,KAAK,QAAQ,eAAe,QAAQ;AAAA,UAC3C,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,QACF;AACA,YAAI,SAAS;AACX,eAAK,YAAY,KAAK,OAAO;AAAA,QAC/B;AACA;AAAA,MACF;AAAA,MACA,KAAK,wBAAwB;AAC3B,cAAM,WAAW,eAAe,QAAQ,MAAM,QAAQ,MAAM,QAAQ,UAAU,QAAQ,MAAM;AAC5F,YAAI,UAAU;AACZ,eAAK,YAAY,KAAK,QAAQ;AAAA,QAChC;AACA;AAAA,MACF;AAAA,MACA,KAAK;AACH,kBAAU,KAAK,QAAQ,IAAI;AAC3B;AAAA,MACF,KAAK;AACH,kBAAU,IAAI;AACd;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,QAAQ,MAA0B;AACzC,QAAM,SAAS,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC;AACvE,SAAO,OAAO,MAAM,GAAG,iBAAiB;AAC1C;AASO,SAAS,UACd,MACA,QAKA,qBAMA,QACkB;AAGlB,QAAM,UAAU,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE;AAC/D,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AACxC,QAAM,UAAU,KAAK,QAAQ;AAK7B,QAAM,kBAAkB,YAAY,cAAc,MAAM,WAAW;AACnE,QAAM,SAAqB,kBAAkB,WAAW,UAAU,MAAM,MAAM;AAE9E,QAAM,OAAO,eAAe,OAAO,QAAQ,MAAM;AACjD,QAAM,QAAgB,SAAS,MAAM,OAAO,OAAO,eAAe;AAMlE,aAAW,CAAC,UAAU,MAAM,KAAK,KAAK,gBAAgB;AACpD,aAAS,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;AAC7C,UAAI,MAAM,CAAC,EAAG,SAAS,UAAU;AAC/B,cAAM,CAAC,EAAG,aAAa,CAAC,GAAI,MAAM,CAAC,EAAG,cAAc,CAAC,GAAI,GAAG,MAAM;AAClE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,KAAK,OAAO,QAAQ,GAAG;AAC3C,QAAM,aAAqC;AAAA,IACzC,MAAM,KAAK,SAAS;AAAA,IACpB,GAAI,cAAc,EAAE,SAAS,YAAY,IAAI,CAAC;AAAA,EAChD;AACA,aAAW,KAAK,KAAK,gBAAgB;AACnC,eAAW,EAAE,IAAI,IAAI,EAAE,SAAS;AAAA,EAClC;AAEA,QAAM,cAAc,CAAC,GAAI,oBAAoB,IAAI,GAAG,KAAK,EAAE,IAAI,MAAM,KAAK,EAAE,KAAK,CAAC,GAAI,GAAG,KAAK,WAAW;AAUzG,QAAM,aAAc,KAA6B,QAAQ,CAAC;AAC1D,QAAM,OAAO,QAAQ,CAAC,GAAG,YAAY,GAAG,KAAK,IAAI,CAAC;AAClD,QAAM,QAAQ,YAAY,KAAK;AAE/B,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,MAAM,KAAK;AAAA,IACX,WAAW,KAAK,SAAS;AAAA,IACzB;AAAA,IACA,UAAU,OAAO,MAAM,QAAQ;AAAA,IAC/B,YAAY,QAAQ,SAAS;AAAA,IAC7B,SAAS,YAAY;AAAA,IACrB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IACzB,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACnD,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,IAC5D,GAAI,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IAClC;AAAA,IACA,GAAI,YAAY,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;AAAA,IAChD,GAAI,MAAM,SAAS,IAAI,EAAE,MAAM,IAAI,CAAC;AAAA,IACpC,GAAI,KAAK,OAAO,SAAS,IAAI,EAAE,QAAQ,KAAK,OAAO,MAAM,GAAG,mBAAmB,EAAE,IAAI,CAAC;AAAA,IACtF,GAAI,KAAK,MAAM,SAAS,IAAI,EAAE,OAAO,KAAK,MAAM,MAAM,GAAG,kBAAkB,EAAE,IAAI,CAAC;AAAA,IAClF,WAAW,MAAM,UAAU,YAAY;AAAA,EACzC;AACF;;;AC1SA,SAAoB;;;ACUb,IAAM,kBAA0B;;;ADJvC,SAAS,UAAU,QAAwC;AACzD,MAAI,OAAO,IAAI;AACb,WAAO,OAAO;AAAA,EAChB;AACA,SAAO,GAAM,QAAK,CAAC,IAAO,WAAQ,CAAC;AACrC;AAYA,SAAS,eAAe,QAAgC,UAAqC;AAC3F,MAAI,OAAO,SAAS;AAClB,WAAO,OAAO;AAAA,EAChB;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI;AAChD;AAgBO,SAAS,oBACd,QACA,QACA,WAA8B,CAAC,GACtB;AACT,SAAO;AAAA,IACL,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,IAAI,UAAU,MAAM;AAAA,IACpB,SAAS,eAAe,QAAQ,QAAQ;AAAA,IACxC,QAAQ,OAAO;AAAA,IACf,QAAQ,OAAO;AAAA,IACf,aAAa,OAAO;AAAA,IACpB,UAAU,OAAO;AAAA,IACjB,WAAW,OAAO;AAAA,IAClB,UAAU;AAAA,MACR,SAAS;AAAA,MACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,SAAS;AAAA,MACT,OAAO,OAAO;AAAA,IAChB;AAAA,IACA,YAAY,OAAO;AAAA,IACnB;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,EACrB;AACF;;;AExEA,IAAAC,QAAsB;AAcf,SAAS,eAAe,MAAc,SAAyB;AACpE,QAAMC,YAAgB,iBAAW,IAAI,IAAS,eAAS,SAAS,IAAI,IAAI;AACxE,SAAOA,UAAS,MAAW,SAAG,EAAE,KAAK,GAAG;AAC1C;AAiBO,SAAS,gBAAgB,OAAyC;AACvE,QAAM,SAAS,oBAAI,IAA4B;AAC/C,aAAW,SAAS,OAAO;AACzB,UAAM,WAAW,OAAO,IAAI,MAAM,IAAI;AACtC,QAAI,UAAU;AACZ,eAAS,KAAK,KAAK;AAAA,IACrB,OAAO;AACL,aAAO,IAAI,MAAM,MAAM,CAAC,KAAK,CAAC;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,SAAkB,CAAC;AACzB,aAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACpC,QAAI,OAAO;AACX,QAAI,KAAK,SAAS,qBAAqB;AACrC,aAAO;AAAA,QACL,UAAU,IAAI,cAAc,KAAK,MAAM,sCAAsC,mBAAmB;AAAA,MAClG;AACA,aAAO,KAAK,MAAM,GAAG,mBAAmB;AAAA,IAC1C;AAKA,UAAM,WAAW,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAmB,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK;AAEtG,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU,KAAK,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,SAAS,UAAU,CAAC;AAAA,MAC9D,GAAI,SAAS,SAAS,IAAI,EAAE,SAAS,SAAS,KAAK,IAAI,EAAE,IAAI,CAAC;AAAA,MAC9D,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,IACnC,CAAC;AAAA,EACH;AAEA,MAAI,OAAO,SAAS,uBAAuB;AACzC,WAAO;AAAA,MACL,qBAAqB,OAAO,MAAM,uCAAuC,qBAAqB;AAAA,IAChG;AACA,WAAO,OAAO,MAAM,GAAG,qBAAqB;AAAA,EAC9C;AACA,SAAO;AACT;;;Ab/BA,IAAqB,oBAArB,MAA2D;AAAA,EACxC;AAAA,EACT;AAAA,EACA,UAAU,QAAQ,IAAI;AAAA,EACb,QAAwB,CAAC;AAAA,EACzB,WAAW,oBAAI,IAAY;AAAA,EACpC,SAAS,IAAI,iBAAiB,CAAC;AAAA,EAC/B;AAAA,EACS,sBAAsB,oBAAI,IAA0B;AAAA,EACpD,sBAAsB,oBAAI,IAAoB;AAAA,EAE/D,YAAY,UAAwD,CAAC,GAAG;AACtE,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAyB;AACvB,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,QAAoB,OAAsB;AAChD,SAAK,MAAM,WAAW,MAAM;AAC1B,WAAK,YAAY;AACjB,WAAK,UAAU,OAAO,WAAW,QAAQ,IAAI;AAI7C,YAAM,qBAAqB,OAAO,QAAQ,OAAO,MAAM,UAAU,IAAI;AAErE,WAAK,SAAS,cAAc,KAAK,SAAS,EAAE,mBAAmB,CAAC;AAChE,WAAK,SAAS,IAAI,iBAAiB,KAAK,OAAO,uBAAuB;AAEtE,iBAAW,WAAW,OAAO,UAAU;AACrC,cAAM,cAAc,QAAQ,KAAK;AACjC,YAAI,aAAa;AACf,eAAK,SAAS,IAAI,WAAW;AAAA,QAC/B;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,UAAU,MAAgB,QAA0B;AAClD,SAAK,MAAM,aAAa,MAAM;AAC5B,YAAM,SAAS,KAAK;AACpB,UAAI,CAAC,UAAU,CAAC,OAAO,SAAS;AAC9B;AAAA,MACF;AAcA,WAAK,yBAAyB,KAAK,IAAI,OAAO,OAAO,OAAO,SAAS;AACrE,WAAK,oBAAoB,IAAI,GAAG,KAAK,EAAE,IAAI,OAAO,KAAK,IAAI,mBAAmB,QAAQ,QAAQ,KAAK,MAAM,CAAC;AAC1G,WAAK,oBAAoB,IAAI,KAAK,IAAI,OAAO,KAAK;AAAA,IACpD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,SAAoC;AAC9C,UAAM,QAAQ,QAAQ;AACtB,SAAK,MAAM,SAAS,MAAM;AACxB,YAAM,SAAS,KAAK;AACpB,UAAI,CAAC,UAAU,CAAC,OAAO,SAAS;AAC9B;AAAA,MACF;AACA,WAAK,YAAY,MAAM;AAAA,IACzB,CAAC;AAAA,EACH;AAAA;AAAA,EAGQ,aAAa,MAAe,QAAsC;AACxE,eAAW,QAAQ,KAAK,SAAS,GAAG;AAClC,YAAM,QAAQ,UAAU,MAAM,QAAQ,KAAK,qBAAqB,KAAK,MAAM;AAC3E,UAAI,CAAC,OAAO;AACV;AAAA,MACF;AACA,YAAM,OAAO,eAAe,KAAK,SAAS,MAAM,KAAK,OAAO;AAC5D,YAAM,YAAY;AAClB,UAAI,MAAM,YAAY;AACpB,cAAM,WAAW,MAAM,IAAI;AAAA,MAC7B;AACA,YAAM,cAAc,KAAK,OAAO,QAAQ,GAAG,KAAK;AAChD,WAAK,MAAM,KAAK;AAAA,QACd;AAAA,QACA,GAAI,cAAc,EAAE,SAAS,YAAY,IAAI,CAAC;AAAA,QAC9C,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,YAAY,QAAsC;AACxD,QAAI,KAAK,WAAW;AAClB,WAAK,aAAa,KAAK,WAAW,MAAM;AAAA,IAC1C;AAEA,UAAM,SAAS,gBAAgB,KAAK,KAAK;AACzC,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,KAAK,oEAA+D;AAC3E;AAAA,IACF;AAEA,UAAM,UAAU,oBAAoB,QAAQ,QAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC;AAEtE,QAAI,OAAO,eAAe,QAAW;AACnC,iBAAW,SAAS,QAAQ,QAAQ;AAClC,mBAAW,YAAY,MAAM,OAAO;AAClC,mBAAS,aAAa,OAAO;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,iBAAiB,OAAO,SAAS;AAExD,IAAG,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,UAAM,aAAkB,WAAK,WAAW,OAAG,gCAAW,CAAC,OAAO;AAC9D,IAAG,kBAAc,YAAY,KAAK,UAAU,OAAO,CAAC;AACpD,WAAO,KAAK,4BAA4B,UAAU,uCAAkC,SAAS,kBAAkB;AAAA,EACjH;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,WAA2B;AAClD,WAAY,iBAAW,SAAS,IAAI,YAAiB,cAAQ,KAAK,QAAQ,aAAa,KAAK,SAAS,SAAS;AAAA,EAChH;AAAA;AAAA;AAAA,EAIQ,yBAAyB,QAAgB,OAAe,WAAyB;AACvF,UAAM,WAAW,KAAK,oBAAoB,IAAI,MAAM;AACpD,QAAI,aAAa,UAAa,YAAY,OAAO;AAC/C;AAAA,IACF;AACA,UAAM,MAAM,GAAG,MAAM,IAAI,QAAQ;AACjC,eAAW,cAAc,KAAK,oBAAoB,IAAI,GAAG,KAAK,CAAC,GAAG;AAChE,UAAI,WAAW,gBAAgB;AAC7B,YAAI;AACF,UAAG,WAAY,WAAK,KAAK,iBAAiB,SAAS,GAAG,WAAW,cAAc,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,QACnG,QAAQ;AAAA,QAER;AAAA,MACF;AACA,UAAI,WAAW,YAAY,WAAW,SAAS;AAC7C,aAAK,OAAO,QAAQ,WAAW,QAAQ;AAAA,MACzC;AAAA,IACF;AACA,SAAK,oBAAoB,OAAO,GAAG;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,MAAM,MAAc,IAAsB;AAChD,QAAI;AACF,SAAG;AAAA,IACL,SAAS,KAAK;AACZ,aAAO,MAAM,GAAG,IAAI,YAAa,IAAc,OAAO,EAAE;AAAA,IAC1D;AAAA,EACF;AACF;","names":["import_node_crypto","fs","path","name","firstEnv","name","fs","import_node_crypto","name","path","relative"]}
|
|
1
|
+
{"version":3,"sources":["../../src/reporter/index.ts","../../src/reporter/reporter.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/reporter/attachment-reader.ts","../../src/reporter/video-writer.ts","../../src/shared/duration.ts","../../src/shared/text.ts","../../src/reporter/step-mapper.ts","../../src/reporter/case-builder.ts","../../src/reporter/collect-builder.ts","../../src/config/version.ts","../../src/reporter/suite-builder.ts"],"sourcesContent":["export { default } from './reporter.js';\nexport type { QualflarePlaywrightOptions, ResolvedReporterConfig } from '../config/resolve-config.js';\n","import { randomUUID } from 'node:crypto';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport type {\n FullConfig,\n FullResult,\n Reporter,\n Suite as PwSuite,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\n\nimport { resolveConfig, type QualflarePlaywrightOptions, type ResolvedReporterConfig } from '../config/resolve-config.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport { AttachmentBudget, resolveAttachments } from './attachment-reader.js';\nimport { buildCase } from './case-builder.js';\nimport { buildCollectPayload } from './collect-builder.js';\nimport { groupIntoSuites, relativizeFile, type CaseWithFile } from './suite-builder.js';\n\n/** Options Playwright injects on top of the user's own, for every reporter.\n * It also injects internal `_mode`/`_commandHash` fields; this package\n * deliberately reads neither, since both are undocumented internals. */\ninterface InjectedOptions {\n configDir?: string;\n}\n\n/**\n * The Qualflare Playwright reporter.\n *\n * Writes ONE uniquely-named JSON report per process into `outputDir`, plus\n * any videos copied alongside it, and makes zero network calls.\n * `qualflare-cli collect <outputDir>` uploads the result — which is what lets\n * any number of sharded jobs write into one directory and merge into a single\n * Launch.\n *\n * Registered in `playwright.config.ts`:\n *\n * ```ts\n * export default defineConfig({\n * reporter: [['list'], ['@qualflare/playwright/reporter', { environment: 'staging' }]],\n * });\n * ```\n */\nexport default class QualflareReporter implements Reporter {\n private readonly options: QualflarePlaywrightOptions & InjectedOptions;\n private config?: ResolvedReporterConfig;\n private rootDir = process.cwd();\n private readonly cases: CaseWithFile[] = [];\n private readonly browsers = new Set<string>();\n private budget = new AttachmentBudget(0);\n private rootSuite?: PwSuite;\n private readonly attachmentsByResult = new Map<string, Attachment[]>();\n private readonly latestAttemptByTest = new Map<string, number>();\n\n constructor(options: QualflarePlaywrightOptions & InjectedOptions = {}) {\n this.options = options;\n }\n\n /** Returning false tells Playwright to auto-inject a terminal reporter\n * (`line` locally, `dot` on CI) so a user who registers only this one is\n * not left staring at a blank console. This reporter prints nothing but\n * warnings and a single completion line. */\n printsToStdio(): boolean {\n return false;\n }\n\n onBegin(config: FullConfig, suite: PwSuite): void {\n this.guard('onBegin', () => {\n this.rootSuite = suite;\n this.rootDir = config.rootDir || process.cwd();\n\n // Playwright's shard index is 1-BASED (\"--shard=1/3\" is the first\n // shard); ours is 0-based, matching every other Qualflare reporter.\n const detectedShardIndex = config.shard ? config.shard.current - 1 : undefined;\n\n this.config = resolveConfig(this.options, { detectedShardIndex });\n this.budget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);\n\n for (const project of config.projects) {\n const browserName = project.use?.browserName;\n if (browserName) {\n this.browsers.add(browserName);\n }\n }\n });\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n this.guard('onTestEnd', () => {\n const config = this.config;\n if (!config || !config.enabled) {\n return;\n }\n // Attachment FILES are read here, not in onEnd, and this ordering is\n // load-bearing: `use.preserveOutput` deletes artifacts once a test\n // finishes, and a passing retry cleans up the previous attempt's output\n // directory. By onEnd the screenshots and videos may simply be gone.\n //\n // The Case itself is NOT assembled here — test.outcome() is not final\n // until every retry has run, so a to-be-retried failure would be\n // reported as a plain failure and nothing would ever be flaky.\n // A retried test reaches onTestEnd once per ATTEMPT, but only the FINAL\n // attempt's attachments are ever reported (see buildCase). Discard the\n // superseded attempt's work as soon as a later one arrives, or its\n // copied video is orphaned in outputDir forever and its bytes stay\n // reserved against a budget that a later test still needs.\n this.discardSupersededAttempt(test.id, result.retry, config.outputDir);\n this.attachmentsByResult.set(`${test.id}:${result.retry}`, resolveAttachments(result, config, this.budget));\n this.latestAttemptByTest.set(test.id, result.retry);\n });\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n await Promise.resolve();\n this.guard('onEnd', () => {\n const config = this.config;\n if (!config || !config.enabled) {\n return;\n }\n this.writeReport(config);\n });\n }\n\n /** Collects every test from the (possibly nested) suite tree. */\n private collectCases(root: PwSuite, config: ResolvedReporterConfig): void {\n for (const test of root.allTests()) {\n const built = buildCase(test, config, this.attachmentsByResult, this.budget);\n if (!built) {\n continue;\n }\n const file = relativizeFile(test.location.file, this.rootDir);\n built.className = file;\n if (built.properties) {\n built.properties['file'] = file;\n }\n const browserName = test.parent.project()?.use?.browserName;\n this.cases.push({\n file,\n ...(browserName ? { browser: browserName } : {}),\n testCase: built,\n });\n }\n }\n\n private writeReport(config: ResolvedReporterConfig): void {\n if (this.rootSuite) {\n this.collectCases(this.rootSuite, config);\n }\n\n const suites = groupIntoSuites(this.cases);\n if (suites.length === 0) {\n logger.info('no test results were captured this run — skipping file write.');\n return;\n }\n\n const collect = buildCollectPayload(suites, config, [...this.browsers]);\n\n if (config.shardIndex !== undefined) {\n for (const suite of collect.suites) {\n for (const testCase of suite.cases) {\n testCase.shardIndex = config.shardIndex;\n }\n }\n }\n\n const outputDir = this.resolveOutputDir(config.outputDir);\n\n fs.mkdirSync(outputDir, { recursive: true });\n const outputPath = path.join(outputDir, `${randomUUID()}.json`);\n fs.writeFileSync(outputPath, JSON.stringify(collect));\n logger.info(`wrote Collect payload to ${outputPath} — run \\`qualflare-cli collect ${outputDir}\\` to upload it.`);\n }\n\n /** Relative `outputDir` resolves against the Playwright config's own\n * directory, not the shell's cwd — a user running `npx playwright test`\n * from a monorepo root should still write next to their config. */\n private resolveOutputDir(outputDir: string): string {\n return path.isAbsolute(outputDir) ? outputDir : path.resolve(this.options.configDir ?? this.rootDir, outputDir);\n }\n\n /** Drops everything an earlier, now-superseded attempt produced: deletes the\n * video copied into outputDir and refunds its bytes to the run budget. */\n private discardSupersededAttempt(testId: string, retry: number, outputDir: string): void {\n const previous = this.latestAttemptByTest.get(testId);\n if (previous === undefined || previous >= retry) {\n return;\n }\n const key = `${testId}:${previous}`;\n for (const attachment of this.attachmentsByResult.get(key) ?? []) {\n if (attachment.localVideoPath) {\n try {\n fs.rmSync(path.join(this.resolveOutputDir(outputDir), attachment.localVideoPath), { force: true });\n } catch {\n // Best effort: an orphan left on disk is untidy, never incorrect.\n }\n }\n if (attachment.fileSize && attachment.content) {\n this.budget.release(attachment.fileSize);\n }\n }\n this.attachmentsByResult.delete(key);\n }\n\n /**\n * Playwright SWALLOWS anything a reporter throws (Multiplexer._wrap catches\n * it and re-dispatches as onError), so an unguarded bug here vanishes\n * silently and the user just gets no report. Every hook body runs through\n * this instead, which at least says what broke and where.\n */\n private guard(hook: string, fn: () => void): void {\n try {\n fn();\n } catch (err) {\n logger.error(`${hook} failed: ${(err as Error).message}`);\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 for the reporter, passed as the second element of its entry in\n * `playwright.config.ts`'s `reporter` array:\n * `['@qualflare/playwright/reporter', { ... }]`. Every field here also has an\n * environment-variable override — see the precedence table in\n * `docs/CONFIGURATION.md`. */\nexport interface QualflarePlaywrightOptions {\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 Playwright's runner-internal steps — `pw:api` (every\n * `page.click()`, `locator.fill()`, ...) and `fixture` (the implicit\n * `browser`/`context`/`page` setup every browser test opens with) — as\n * reported Steps.\n *\n * Off by default: a single browser test routinely produces hundreds of\n * them, which buries the user-authored `test.step()`/`expect` boundaries\n * that are actually legible in a report and blows through\n * MAX_STEPS_PER_TEST_ATTEMPT on noise. A step that FAILED is always kept\n * regardless of this setting, since a failing API call or fixture is\n * usually the single most useful line in the trace. */\n includeApiSteps?: 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 * reporter still no-ops cleanly rather than throwing. */\n enabled?: boolean;\n /** Directory `onEnd()` writes this process's report file (and any\n * video attachments) into. Default `./qualflare-results`. Always active —\n * this reporter 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 Playwright's own\n * `--shard i/N`, which it exposes to reporters as `FullConfig.shard`\n * ({ current, total }). Playwright's `current` is 1-BASED, so the reporter\n * converts it before passing it here as `deps.detectedShardIndex`.\n *\n * This is the one place Playwright is markedly better than its siblings:\n * Cypress has no shard concept at all, and cucumber-js hides its `--shard`\n * from formatters entirely (forcing an argv scrape). Here the runner just\n * tells us. */\n shardIndex?: number;\n}\n\nexport interface ResolvedReporterConfig {\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 includeApiSteps: 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\n/** Resolves the full reporter configuration from, in order: the explicit\n * `options` (the second element of the `playwright.config.ts` reporter\n * tuple, `['@qualflare/playwright', { ... }]`), 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 reporter's constructor calls\n * `resolveConfig(options)` with no second argument) is unaffected.\n */\nexport function resolveConfig(\n options: QualflarePlaywrightOptions,\n deps: { detectGit?: () => GitInfo; detectCi?: () => CiMetadata;\n /** Playwright's `FullConfig.shard`, already converted from its 1-based\n * `current` to our 0-based index by the reporter. */\n detectedShardIndex?: number;\n } = {},\n): ResolvedReporterConfig {\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') ?? deps.detectedShardIndex;\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 || 'playwright',\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 includeApiSteps: options.includeApiSteps ?? envBool('QUALFLARE_INCLUDE_API_STEPS') ?? 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 reporter and the author-facing runtime\n * API.\n */\n\n/** Reserved `testInfo.attach()` content type used to smuggle structured\n * `qualflare.*()` calls (label/tag/step/etc.) from test and hook code back to\n * the reporter — the only channel Playwright gives user code back to a\n * running reporter. The reporter recognizes this exact content type and\n * replays the message as a model mutation instead of reporting it as a\n * 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 Playwright 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 Playwright's own reporter output stream and shouldn't be\n * polluted with reporter diagnostics.\n */\n\nconst PREFIX = '[qualflare-playwright]';\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';\n\nimport type { TestResult } from '@playwright/test/reporter';\n\nimport { MAX_ATTACHMENTS_PER_CASE, RESERVED_MESSAGE_MEDIA_TYPE } from '../shared/constants.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment } from '../shared/types.js';\nimport type { ResolvedReporterConfig } from '../config/resolve-config.js';\nimport { copyVideoAttachment } from './video-writer.js';\n\n/** Running total of inline attachment bytes for one reporter process, so a\n * single pathological run can't push a launch past the server's body limit.\n * Identical to the class both sibling packages use. */\nexport class AttachmentBudget {\n private used = 0;\n\n constructor(private readonly maxTotalBytes: number) {}\n\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 /** Returns bytes to the budget when the attachment they were reserved for\n * turns out to be discarded — a retried test's superseded attempt. Without\n * this, a flaky test consumes budget twice and a LATER test silently loses\n * its screenshot to an attachment nobody will ever see. */\n release(bytes: number): void {\n this.used = Math.max(0, this.used - bytes);\n }\n\n get usedBytes(): number {\n return this.used;\n }\n}\n\n/** Playwright's own auto-attachment names (`use.video`/`use.screenshot`/\n * `use.trace` produce exactly these). */\nconst NAME_VIDEO = 'video';\nconst NAME_TRACE = 'trace';\n\nfunction isVideo(a: TestResult['attachments'][number]): boolean {\n return a.name === NAME_VIDEO || (a.contentType?.startsWith('video/') ?? false);\n}\n\n/**\n * Resolves one test attempt's Playwright attachments into wire `Attachment`s.\n *\n * Three routes, and which one an attachment takes is decided entirely by what\n * the CLI and server can actually do with it:\n *\n * - **video** -> copied into `outputDir`, referenced by `localVideoPath`.\n * This is the ONLY path `qualflare-cli` uploads to blob storage.\n * - **everything else with bytes** -> inlined as base64 `content`, subject to\n * the per-attachment and per-run budgets.\n * - **trace** -> dropped, deliberately. Traces are `application/zip`, which\n * the upload endpoint's MIME allowlist rejects, and they are far too large\n * to inline. Attaching one would produce a row pointing at nothing. See\n * docs/LIMITATIONS.md.\n *\n * A bare `path` is never emitted on its own: the server treats `path` as\n * informational and never fetches it, so a path-only attachment is a row the\n * user can see but never open. Dropping is more honest than that.\n */\nexport function resolveAttachments(\n result: TestResult,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment[] {\n if (!config.attachScreenshots) {\n return [];\n }\n\n const out: Attachment[] = [];\n let capWarned = false;\n\n for (const a of result.attachments) {\n // Runtime messages from the metadata API travel as attachments; they are\n // consumed by the reporter, never reported as one.\n if (a.contentType === RESERVED_MESSAGE_MEDIA_TYPE) {\n continue;\n }\n\n if (out.length >= MAX_ATTACHMENTS_PER_CASE) {\n if (!capWarned) {\n capWarned = true;\n logger.warn(`a test produced more than ${MAX_ATTACHMENTS_PER_CASE} attachments; the rest were dropped.`);\n }\n break;\n }\n\n if (a.name === NAME_TRACE || a.contentType === 'application/zip') {\n continue;\n }\n\n if (isVideo(a)) {\n if (!a.path) {\n // An in-memory video is not something Playwright produces on its own\n // and cannot be routed through localVideoPath without writing it out;\n // inlining a video would blow the budget instantly.\n logger.warn(`skipping in-memory video attachment \"${a.name}\": only file-backed videos are supported.`);\n continue;\n }\n const copied = copyVideoAttachment(a.path, config.outputDir, config.maxVideoBytes);\n if (copied) {\n out.push({\n name: a.name,\n mimeType: copied.mimeType,\n localVideoPath: copied.localVideoPath,\n fileSize: copied.fileSize,\n });\n }\n continue;\n }\n\n const inlined = inlineAttachment(a, config, budget);\n if (inlined) {\n out.push(inlined);\n }\n }\n\n return out;\n}\n\n/**\n * Turns raw bytes into a wire `Attachment`, enforcing BOTH caps.\n *\n * Every path that inlines content must go through here. `/collect` rejects a\n * body over 10MB outright (api-service `launch_controller.go`'s\n * `BodyLimit(10<<20)`), and a rejected request loses the ENTIRE launch — not\n * just the oversized attachment. `maxTotalAttachmentBytes` defaults to 750KB\n * precisely to stay clear of that, so any path that skips the budget can\n * silently destroy a whole run's results.\n */\nexport function inlineFromBuffer(\n name: string,\n bytes: Buffer,\n mimeType: string | undefined,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (bytes.byteLength > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${name}\": ${bytes.byteLength} bytes exceeds the configured maxAttachmentBytes cap of ${config.maxAttachmentBytes} bytes.`,\n );\n return undefined;\n }\n if (!budget.tryReserve(bytes.byteLength)) {\n logger.warn(\n `skipping attachment \"${name}\": this run's total inline-attachment budget of ${config.maxTotalAttachmentBytes} bytes is exhausted.`,\n );\n return undefined;\n }\n\n return {\n name,\n ...(mimeType ? { mimeType } : {}),\n content: bytes.toString('base64'),\n fileSize: bytes.byteLength,\n };\n}\n\n/**\n * Reads a file from disk and inlines it, subject to the same caps.\n *\n * `stat`s before reading so an oversized file is rejected without ever being\n * pulled into memory. Every failure warns and returns `undefined`; an\n * attachment must never fail a run.\n */\nexport function inlineFromFile(\n name: string,\n filePath: string,\n mimeType: string | undefined,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n let size: number;\n try {\n size = fs.statSync(filePath).size;\n } catch (err) {\n logger.warn(`skipping attachment \"${name}\": could not stat ${filePath}: ${(err as Error).message}`);\n return undefined;\n }\n if (size > config.maxAttachmentBytes) {\n logger.warn(\n `skipping attachment \"${name}\": ${size} bytes exceeds the configured maxAttachmentBytes cap of ${config.maxAttachmentBytes} bytes.`,\n );\n return undefined;\n }\n\n let bytes: Buffer;\n try {\n bytes = fs.readFileSync(filePath);\n } catch (err) {\n logger.warn(`skipping attachment \"${name}\": could not read ${filePath}: ${(err as Error).message}`);\n return undefined;\n }\n return inlineFromBuffer(name, bytes, mimeType, config, budget);\n}\n\nfunction inlineAttachment(\n a: TestResult['attachments'][number],\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): Attachment | undefined {\n if (a.body) {\n return inlineFromBuffer(a.name, a.body, a.contentType, config, budget);\n }\n if (a.path) {\n return inlineFromFile(a.name, a.path, a.contentType, config, budget);\n }\n return undefined;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nimport { logger } from '../shared/logger.js';\n\n/** Extension -> MIME type for the video formats the server accepts (see\n * the upload endpoint's own allowlist server-side). Playwright records `.webm`\n * by default; `.mp4`/`.mov` are listed for parity with the server's allowlist\n * and because a user can attach either via `testInfo.attach()`. An extension not\n * in this map (a user could point `qualflare.attachmentFromFile()` at an\n * arbitrary file) is skipped — see `copyVideoAttachment`'s doc comment. */\nconst VIDEO_MIME_TYPES_BY_EXTENSION: Record<string, string> = {\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.mov': 'video/quicktime',\n};\n\nexport interface VideoCopyResult {\n /** Filename relative to the `outputDir` this was copied into — never an\n * absolute path, since the whole directory travels together as one CI\n * artifact bundle (see the design spec's \"Why no backend changes\"\n * section). */\n localVideoPath: string;\n fileSize: number;\n mimeType: string;\n}\n\n/**\n * Copies one video file into `outputDir` under a unique filename (Allure's\n * `FileSystemWriter.writeAttachmentFromPath` pattern: `fs.copyFileSync`,\n * never read into memory) and returns enough to build that `Attachment`\n * entry's `localVideoPath`. `qualflare-cli` is what actually uploads this\n * file later, once it has a real auth token — see the design spec.\n *\n * Best-effort, like the rest of this reporter's attachment handling\n * (`attachment-reader.ts`'s oversized/unreadable-file skip): any failure —\n * oversized file, unsupported extension, an unreadable source file — is\n * logged as a warning and resolves to `undefined` rather than throwing, so a\n * video problem never fails the whole run.\n */\nexport function copyVideoAttachment(\n filePath: string,\n outputDir: string,\n maxVideoBytes: number,\n): VideoCopyResult | undefined {\n const ext = path.extname(filePath).toLowerCase();\n const mimeType = VIDEO_MIME_TYPES_BY_EXTENSION[ext];\n if (!mimeType) {\n logger.warn(`skipping video attachment \"${filePath}\": unsupported video format.`);\n return undefined;\n }\n\n let fileSize: number;\n try {\n // Stat BEFORE copying — an oversized file must never be copied just to\n // discover it should be skipped.\n fileSize = fs.statSync(filePath).size;\n } catch (err) {\n logger.warn(`skipping video attachment \"${filePath}\": could not stat file: ${(err as Error).message}`);\n return undefined;\n }\n if (fileSize > maxVideoBytes) {\n logger.warn(\n `skipping video attachment \"${filePath}\": ${fileSize} bytes exceeds the configured ` +\n `maxVideoBytes cap of ${maxVideoBytes} bytes.`,\n );\n return undefined;\n }\n\n const localVideoPath = `${randomUUID()}${ext}`;\n try {\n fs.mkdirSync(outputDir, { recursive: true });\n fs.copyFileSync(filePath, path.join(outputDir, localVideoPath));\n } catch (err) {\n logger.warn(`skipping video attachment \"${filePath}\": could not copy file: ${(err as Error).message}`);\n return undefined;\n }\n\n return { localVideoPath, fileSize, mimeType };\n}\n","import type { NanosecondDuration } from './types.js';\n\nconst NS_PER_MS = 1_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 * 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 { TestStep } from '@playwright/test/reporter';\n\nimport { MAX_STEPS_PER_TEST_ATTEMPT } from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport { logger } from '../shared/logger.js';\nimport type { Step } from '../shared/types.js';\n\n/** Playwright's built-in step categories, as of 1.62. `category` is typed as\n * a plain `string`, not a union — third-party integrations add their own — so\n * nothing here may switch exhaustively on it. */\nconst CATEGORY_TEST_STEP = 'test.step';\nconst CATEGORY_EXPECT = 'expect';\nconst CATEGORY_HOOK = 'hook';\nconst CATEGORY_FIXTURE = 'fixture';\nconst CATEGORY_PW_API = 'pw:api';\n\n/** Depth beyond which nesting is flattened rather than followed.\n *\n * Playwright imposes no nesting limit: a `test.step()` inside a `test.step()`\n * around a `page.getByRole().click()` already reaches three levels before any\n * user intent, and a recursive helper can go arbitrarily deep. Real suites sit\n * at 3-6, so this is a runaway guard, not a product limit — steps past it are\n * still reported, just re-parented to the deepest ancestor within the cap\n * rather than dropped, since losing a failing assertion to a depth rule would\n * be far worse than showing it one level too shallow. */\nconst MAX_STEP_DEPTH = 10;\n\n/** True when a step is worth reporting at all.\n *\n * `pw:api` and `fixture` are excluded by default and this is the single most\n * important filter in the mapper: one `page.goto()` plus a handful of\n * assertions can emit hundreds of `pw:api` steps, and every browser test\n * opens with `Fixture \"browser\"`/`\"context\"`/`\"page\"` before reaching a line\n * of user code. Together they bury the user-authored `test.step()`\n * boundaries and exhaust MAX_STEPS_PER_TEST_ATTEMPT on noise long before\n * reaching anything a human wants to read.\n *\n * A step that FAILED is always kept, whatever its category — the failing\n * `pw:api` call is usually the single most useful line in the whole trace,\n * and dropping it to a volume heuristic would defeat the point of reporting\n * steps at all. */\nfunction isReportable(step: TestStep, includeApiSteps: boolean): boolean {\n if (step.error) {\n return true;\n }\n switch (step.category) {\n case CATEGORY_TEST_STEP:\n case CATEGORY_EXPECT:\n case CATEGORY_HOOK:\n return true;\n case CATEGORY_PW_API:\n case CATEGORY_FIXTURE:\n // Both are runner internals rather than anything the test author\n // wrote. Every browser test emits `Fixture \"browser\"` / `\"context\"` /\n // `\"page\"` before reaching a single line of user code, and `pw:api`\n // runs to hundreds of entries — in a test-management report that is\n // noise ahead of signal. A FAILED fixture is still kept, by the check\n // above: a fixture that throws is a genuine failure and usually the\n // most useful line in the trace.\n return includeApiSteps;\n default:\n // An unrecognized category is most likely a third-party integration's\n // own step (Playwright allows any string). Keep it: an unknown step is\n // more likely signal than the `pw:api` firehose this filter exists for.\n return true;\n }\n}\n\n/** `file:line`, relative paths left as Playwright reports them. */\nfunction formatLocation(step: TestStep): string | undefined {\n if (!step.location) {\n return undefined;\n }\n return `${step.location.file}:${step.location.line}`;\n}\n\n/**\n * Flattens Playwright's nested `TestStep` tree into the wire format's flat\n * `Step[]`, preserving the shape via `parentIndex` (a 0-based index into the\n * same array). The server reconstructs the tree from that — see\n * `ResolveStepParents` in api-service, which drops out-of-range or cyclic\n * values rather than rejecting the case.\n *\n * Read the tree in `onTestEnd`, never in `onStepBegin`/`onStepEnd`: Playwright\n * MUTATES the same step object when a step finishes (`step.duration` and\n * `step.error` are assigned in place), so anything captured at begin-time is a\n * live reference whose duration is still unset.\n */\nexport function mapSteps(steps: readonly TestStep[], includeApiSteps: boolean): Step[] {\n const out: Step[] = [];\n let capWarned = false;\n\n const walk = (nodes: readonly TestStep[], parentIndex: number | undefined, depth: number): void => {\n for (const node of nodes) {\n if (!isReportable(node, includeApiSteps)) {\n // Skipped, but still descend: a filtered-out `pw:api` wrapper can\n // contain a reportable child (an assertion, or anything that failed).\n // Those children re-parent to this node's own parent, which keeps the\n // tree connected instead of orphaning them at the root.\n walk(node.steps, parentIndex, depth);\n continue;\n }\n\n if (out.length >= MAX_STEPS_PER_TEST_ATTEMPT) {\n if (!capWarned) {\n capWarned = true;\n logger.warn(\n `a test produced more than ${MAX_STEPS_PER_TEST_ATTEMPT} reportable steps; the rest were dropped. ` +\n 'Set `includeApiSteps: false` (the default) or reduce step nesting if this is unexpected.',\n );\n }\n return;\n }\n\n const index = out.length;\n out.push({\n name: node.title,\n keyword: node.category,\n status: node.error ? 'failed' : 'passed',\n duration: msToNs(node.duration),\n ...(node.error ? { error: formatStepError(node) } : {}),\n ...(formatLocation(node) ? { location: formatLocation(node) } : {}),\n ...(parentIndex !== undefined ? { parentIndex } : {}),\n });\n\n // Past the depth cap, keep reporting but stop deepening: children are\n // attached to the last in-cap ancestor rather than dropped.\n const nextParent = depth + 1 >= MAX_STEP_DEPTH ? parentIndex : index;\n walk(node.steps, nextParent, depth + 1);\n }\n };\n\n walk(steps, undefined, 0);\n return out;\n}\n\n/** Playwright's step errors carry the same shape as test errors; `message`\n * is set for thrown Errors and `value` for non-Error throws (`throw 'x'`). */\nfunction formatStepError(step: TestStep): string {\n const err = step.error;\n if (!err) {\n return '';\n }\n return err.message ?? err.value ?? 'step failed';\n}\n","import * as fs from 'node:fs';\n\nimport type { TestCase, TestResult } from '@playwright/test/reporter';\n\nimport type { ResolvedReporterConfig } from '../config/resolve-config.js';\nimport {\n MAX_ATTEMPTS_PER_CASE,\n MAX_ATTEMPT_MESSAGE_RUNES,\n MAX_ATTEMPT_OUTPUT_LINES,\n MAX_ATTEMPT_OUTPUT_RUNES,\n MAX_ATTEMPT_SNIPPET_RUNES,\n MAX_ATTEMPT_TRACE_RUNES,\n MAX_LABELS_PER_CASE,\n MAX_LINKS_PER_CASE,\n MAX_TAGS_PER_CASE,\n MAX_TAG_LENGTH,\n RESERVED_MESSAGE_MEDIA_TYPE,\n} from '../shared/constants.js';\nimport { msToNs } from '../shared/duration.js';\nimport { clampOutputLines, truncateRunes } from '../shared/text.js';\nimport { logger } from '../shared/logger.js';\nimport type { Attachment, Attempt, Case, CaseStatus, Label, Link, Parameter, Step } from '../shared/types.js';\nimport type { RuntimeMessage } from '../runtime/message-types.js';\nimport { AttachmentBudget, inlineFromBuffer, inlineFromFile } from './attachment-reader.js';\nimport { mapSteps } from './step-mapper.js';\n\n/**\n * Maps Playwright's 5 result statuses onto the wire contract's vocabulary.\n *\n * `qualflare-cli` accepts exactly 7 values and turns anything it does not\n * recognize into `error` — NOT into a pass — so every Playwright status is\n * mapped explicitly here rather than passed through and hoped for.\n */\nfunction mapStatus(status: TestResult['status']): CaseStatus {\n switch (status) {\n case 'passed':\n return 'passed';\n case 'failed':\n return 'failed';\n case 'timedOut':\n return 'timeout';\n case 'interrupted':\n return 'aborted';\n case 'skipped':\n return 'skipped';\n default:\n return 'error';\n }\n}\n\n// Matches ANSI SGR escapes. Written as a unicode escape rather than a literal\n// control character, so this source stays copy-pasteable and greppable.\n// eslint-disable-next-line no-control-regex -- matching ANSI escapes requires the escape byte itself\nconst ANSI_PATTERN = /\\u001b\\[[0-9;]*m/g;\n\nfunction stripAnsi(text: string): string {\n return text.replace(ANSI_PATTERN, '');\n}\n\n/**\n * Playwright's TestError carries `message` for thrown Errors and `value` for\n * non-Error throws (`throw 'boom'`). `snippet` is the rendered code frame\n * with the failing line highlighted — genuinely the most useful part of a\n * Playwright failure, and something the built-in JSON reporter flattens — but\n * it arrives ANSI-colored, which would render as escape soup in a web UI.\n */\nfunction formatError(result: TestResult): string | undefined {\n const err = result.error;\n if (!err) {\n return undefined;\n }\n const head = err.message ?? err.value ?? 'test failed';\n const parts = [stripAnsi(head)];\n if (err.snippet) {\n parts.push('', stripAnsi(err.snippet));\n }\n if (err.stack && !head.includes(err.stack)) {\n parts.push('', stripAnsi(err.stack));\n }\n return parts.join('\\n');\n}\n\n/**\n * Captured process output as the wire wants it: one array entry per line.\n *\n * Playwright hands back `(string | Buffer)[]` where each entry is whatever\n * chunk the stream happened to flush, so a single entry may hold many lines or\n * a partial one. Splitting on newlines here means the server's 200-LINE cap\n * counts real lines rather than arbitrary flush boundaries.\n */\nfunction outputLines(chunks: ReadonlyArray<string | Buffer> | undefined): string[] | undefined {\n if (!chunks || chunks.length === 0) {\n return undefined;\n }\n const text = chunks.map((c) => (typeof c === 'string' ? c : c.toString('utf8'))).join('');\n const lines = stripAnsi(text).split('\\n');\n // A trailing newline yields a final empty element that is not a real line.\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n // Bounded to what the server actually stores. Playwright captures everything\n // a test prints, so this is the field that makes an attempt unboundedly large.\n return clampOutputLines(lines, MAX_ATTEMPT_OUTPUT_LINES, MAX_ATTEMPT_OUTPUT_RUNES);\n}\n\n/**\n * Builds the per-attempt history for one test, or `undefined` when there is\n * nothing worth sending.\n *\n * # Why every attempt is sent, including the last\n *\n * The server treats the highest-numbered attempt as the final execution and\n * overwrites its `status`/`duration` from the Case itself, so a client cannot\n * make the case row and its final attempt row disagree. It keeps that\n * attempt's own `message`/`trace` though — which is exactly why the final\n * attempt has to be sent rather than left to be inferred.\n *\n * # Why a single attempt sends nothing\n *\n * A test that ran once has no history: the Case already carries that status,\n * duration and error. The server discards a one-element array, so sending it\n * would be payload spent against the 10MB body limit on a row that is dropped.\n *\n * # Why the error is split rather than reused\n *\n * `formatError` flattens message + snippet + stack into the Case's single\n * `error` string, because that is all the Case has room for. An Attempt has\n * separate `message`/`trace`/`snippet`/`line` fields, so they are mapped\n * individually — the attempt history is strictly richer than a per-attempt\n * copy of `error` would be.\n */\nexport function buildAttempts(results: readonly TestResult[]): Attempt[] | undefined {\n if (results.length < 2) {\n return undefined;\n }\n\n // Beyond the server's cap it keeps the first 49 and the final one, dropping\n // the middle. Doing the same here means the bytes are never sent at all,\n // and — crucially — that the FINAL attempt survives the trim, which a plain\n // `slice(0, 50)` would discard.\n let kept: readonly TestResult[] = results;\n if (results.length > MAX_ATTEMPTS_PER_CASE) {\n kept = [...results.slice(0, MAX_ATTEMPTS_PER_CASE - 1), results[results.length - 1]!];\n }\n\n return kept.map((r, i) => {\n const err = r.error;\n const attempt: Attempt = {\n // 1-based and contiguous. Deliberately the index rather than\n // `r.retry`: results are already ordered by attempt, and a filtered-out\n // never-ran result would leave a hole in the retry numbering that the\n // server reads as a truncated history.\n attempt: i + 1,\n status: mapStatus(r.status),\n duration: msToNs(r.duration),\n startedAt: r.startTime.toISOString(),\n };\n\n if (err) {\n const message = err.message ?? err.value;\n if (message) {\n attempt.message = truncateRunes(stripAnsi(message), MAX_ATTEMPT_MESSAGE_RUNES);\n }\n if (err.stack) {\n attempt.trace = truncateRunes(stripAnsi(err.stack), MAX_ATTEMPT_TRACE_RUNES);\n }\n if (err.snippet) {\n attempt.snippet = truncateRunes(stripAnsi(err.snippet), MAX_ATTEMPT_SNIPPET_RUNES);\n }\n if (typeof err.location?.line === 'number') {\n attempt.line = err.location.line;\n }\n }\n\n const stdout = outputLines(r.stdout);\n if (stdout) {\n attempt.stdout = stdout;\n }\n const stderr = outputLines(r.stderr);\n if (stderr) {\n attempt.stderr = stderr;\n }\n\n return attempt;\n });\n}\n\ninterface ReplayedMetadata {\n labels: Label[];\n links: Link[];\n tags: string[];\n description?: string;\n priority?: Case['priority'];\n caseParameters: Parameter[];\n stepParameters: Map<string, Parameter[]>;\n attachments: Attachment[];\n}\n\n/**\n * Replays the `qualflare.*()` calls a test made, which reach the reporter as\n * attachments under a reserved content type (see runtime/qualflare-api.ts).\n *\n * `parameter()` placement follows the rule shared with the sibling packages:\n * inside an open `step()` it belongs to that step, outside any step it\n * belongs to the case's properties. The step_start/step_stop pair exists only\n * to establish that bracket — the step ITSELF is already captured natively,\n * because `qualflare.step()` delegates to `test.step()`, so synthesizing a\n * second step from these messages would double-report every manual step.\n */\nfunction replayMetadata(\n result: TestResult,\n config: ResolvedReporterConfig,\n budget: AttachmentBudget,\n): ReplayedMetadata {\n const meta: ReplayedMetadata = {\n labels: [],\n links: [],\n tags: [],\n caseParameters: [],\n stepParameters: new Map(),\n attachments: [],\n };\n const openSteps: string[] = [];\n\n for (const a of result.attachments) {\n if (a.contentType !== RESERVED_MESSAGE_MEDIA_TYPE || !a.body) {\n continue;\n }\n let message: RuntimeMessage;\n try {\n message = JSON.parse(a.body.toString('utf8')) as RuntimeMessage;\n } catch {\n logger.warn('ignoring an unparseable qualflare runtime message.');\n continue;\n }\n\n switch (message.type) {\n case 'label':\n meta.labels.push({ name: message.name, value: message.value });\n break;\n case 'link':\n meta.links.push({\n type: message.linkType ?? 'custom',\n ...(message.name ? { name: message.name } : {}),\n url: message.url,\n });\n break;\n case 'tag':\n meta.tags.push(...message.tags);\n break;\n case 'description':\n meta.description = message.text;\n break;\n case 'priority':\n meta.priority = message.value;\n break;\n case 'parameter': {\n const param: Parameter = {\n name: message.name,\n ...(message.value !== undefined ? { value: message.value } : {}),\n ...(message.masked ? { masked: true } : {}),\n };\n const openStep = openSteps[openSteps.length - 1];\n if (openStep === undefined) {\n meta.caseParameters.push(param);\n } else {\n const existing = meta.stepParameters.get(openStep) ?? [];\n existing.push(param);\n meta.stepParameters.set(openStep, existing);\n }\n break;\n }\n case 'attachment': {\n // Decoded back to bytes rather than trusting the base64 length, so the\n // cap is applied to the real payload size the server will receive.\n const inlined = inlineFromBuffer(\n message.name,\n Buffer.from(message.contentBase64, 'base64'),\n message.mimeType,\n config,\n budget,\n );\n if (inlined) {\n meta.attachments.push(inlined);\n }\n break;\n }\n case 'attachment_from_file': {\n const fromFile = inlineFromFile(message.name, message.path, message.mimeType, config, budget);\n if (fromFile) {\n meta.attachments.push(fromFile);\n }\n break;\n }\n case 'step_start':\n openSteps.push(message.name);\n break;\n case 'step_stop':\n openSteps.pop();\n break;\n }\n }\n\n return meta;\n}\n\n\n/** Truncates and caps tags to the server's limits, so a runaway loop in a\n * test can't get a whole launch rejected at validation. */\nfunction capTags(tags: string[]): string[] {\n const unique = [...new Set(tags.map((t) => t.slice(0, MAX_TAG_LENGTH)))];\n return unique.slice(0, MAX_TAGS_PER_CASE);\n}\n\n/**\n * Builds one wire `Case` from a Playwright test and all of its attempts.\n *\n * Called from `onEnd`, never `onTestEnd`: `test.outcome()` is only meaningful\n * once every retry has finished, so asking mid-flight would report a\n * to-be-retried failure as a plain failure and never mark anything flaky.\n */\nexport function buildCase(\n test: TestCase,\n config: ResolvedReporterConfig,\n /** Attachments already resolved in onTestEnd, keyed `${test.id}:${retry}`.\n * Resolution cannot be deferred to onEnd: `use.preserveOutput` and a\n * passing retry both delete a previous attempt's output directory, so by\n * onEnd the screenshot/video files may no longer exist. */\n attachmentsByResult: ReadonlyMap<string, Attachment[]>,\n /** The run-wide inline budget. Needed here because the metadata API's own\n * attachments (`qualflare.attachment()` / `attachmentFromFile()`) are\n * resolved at case-build time, and they must draw on the SAME budget as\n * Playwright's attachments — an uncapped path can push the request past\n * `/collect`'s 10MB body limit and lose the entire launch. */\n budget: AttachmentBudget,\n): Case | undefined {\n // workerIndex === -1 means the test never actually ran (the run was\n // interrupted before it started); there is no result worth reporting.\n const results = test.results.filter((r) => r.workerIndex !== -1);\n if (results.length === 0) {\n return undefined;\n }\n\n const final = results[results.length - 1]!;\n const outcome = test.outcome();\n\n // A `test.fail()` test that failed is a PASS: the author declared the\n // failure expected, and Playwright reports that as outcome 'expected'. The\n // error text is kept so the report still shows what actually happened.\n const expectedFailure = outcome === 'expected' && final.status === 'failed';\n const status: CaseStatus = expectedFailure ? 'passed' : mapStatus(final.status);\n\n const meta = replayMetadata(final, config, budget);\n const steps: Step[] = mapSteps(final.steps, config.includeApiSteps);\n\n // Attach parameters recorded between a step_start/step_stop bracket to the\n // matching native step. Matched by title, last occurrence wins — a repeated\n // step title is rare, and mis-attributing a parameter is a much smaller\n // problem than dropping it.\n for (const [stepName, params] of meta.stepParameters) {\n for (let i = steps.length - 1; i >= 0; i -= 1) {\n if (steps[i]!.name === stepName) {\n steps[i]!.parameters = [...(steps[i]!.parameters ?? []), ...params];\n break;\n }\n }\n }\n\n const projectName = test.parent.project()?.name;\n const properties: Record<string, string> = {\n file: test.location.file,\n ...(projectName ? { project: projectName } : {}),\n };\n for (const p of meta.caseParameters) {\n properties[p.name] = p.value ?? '';\n }\n\n const attachments = [...(attachmentsByResult.get(`${test.id}:${final.retry}`) ?? []), ...meta.attachments];\n\n // Playwright's own tags (@-tokens in titles, plus describe/test `tag`\n // options) merged with anything qualflare.tag() added.\n //\n // Read defensively because `TestCase.tags` only exists from Playwright\n // 1.42; on 1.40/1.41 it is undefined and spreading it would throw. Rather\n // than raise the peer floor and hard-block those users, they simply get no\n // native tags — a concept their Playwright does not have anyway — while\n // qualflare.tag() keeps working. Verified against 1.40.0's shipped types.\n const nativeTags = (test as { tags?: string[] }).tags ?? [];\n const tags = capTags([...nativeTags, ...meta.tags]);\n const error = formatError(final);\n const attempts = buildAttempts(results);\n\n return {\n id: test.id,\n name: test.title,\n className: test.location.file,\n status,\n duration: msToNs(final.duration),\n retryCount: results.length - 1,\n isFlaky: outcome === 'flaky',\n ...(attempts ? { attempts } : {}),\n ...(error ? { error } : {}),\n ...(meta.priority ? { priority: meta.priority } : {}),\n ...(meta.description ? { description: meta.description } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n properties,\n ...(attachments.length > 0 ? { attachments } : {}),\n ...(steps.length > 0 ? { steps } : {}),\n ...(meta.labels.length > 0 ? { labels: meta.labels.slice(0, MAX_LABELS_PER_CASE) } : {}),\n ...(meta.links.length > 0 ? { links: meta.links.slice(0, MAX_LINKS_PER_CASE) } : {}),\n startedAt: final.startTime.toISOString(),\n };\n}\n","import * as os from 'node:os';\n\nimport { PACKAGE_VERSION } from '../config/version.js';\nimport type { ResolvedReporterConfig } from '../config/resolve-config.js';\nimport type { Collect, Suite } from '../shared/types.js';\n\nfunction resolveOs(config: ResolvedReporterConfig): string {\n if (config.os) {\n return config.os;\n }\n return `${os.type()} ${os.release()}`;\n}\n\n/**\n * Launch-level browser. Playwright is the only one of the three reporters\n * that genuinely knows this: `FullProject.use.browserName` is real, whereas\n * `qualflare-cli`'s existing Playwright parser reports the PROJECT NAME here\n * (so a project called `smoke` or `mobile-safari` becomes the \"browser\").\n *\n * A multi-project run has no single browser, so the distinct set is joined\n * rather than picking one arbitrarily; per-suite attribution is finer-grained\n * and lives on `Suite.browser`.\n */\nfunction resolveBrowser(config: ResolvedReporterConfig, browsers: readonly string[]): string {\n if (config.browser) {\n return config.browser;\n }\n return [...new Set(browsers)].sort().join(', ');\n}\n\n/**\n * Assembles the final `Collect` payload at `onEnd`.\n *\n * CI metadata and branch/commit detection are already fully resolved by\n * `resolve-config.ts` — this reads the resolved config through and does NOT\n * call `ci-detect`/`git-detect` itself, matching both sibling packages.\n *\n * `metadata` is not optional decoration: `qualflare-cli` identifies this\n * format by the presence of `framework` + `metadata` + `suites` together.\n * Omitting it makes the CLI fall back to filename matching, where a file\n * whose name contains \"playwright\" is routed to the built-in-JSON parser and\n * fails to parse. For the same reason this payload must never grow a\n * top-level `config` key — that is the Playwright-JSON detector's signature.\n */\nexport function buildCollectPayload(\n suites: Suite[],\n config: ResolvedReporterConfig,\n browsers: readonly string[] = [],\n): Collect {\n return {\n framework: config.framework,\n platform: config.platform,\n os: resolveOs(config),\n browser: resolveBrowser(config, browsers),\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-playwright',\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 * 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';\n\n/**\n * Makes a spec path stable and portable: relative to the Playwright project\n * root, with POSIX separators regardless of the OS that produced it.\n *\n * Without this, the same suite reported from a Windows runner and a Linux\n * runner would be two different suites server-side, and an absolute path\n * would leak a CI agent's directory layout into the report.\n */\nexport function relativizeFile(file: string, rootDir: string): string {\n const relative = path.isAbsolute(file) ? path.relative(rootDir, file) : file;\n return relative.split(path.sep).join('/');\n}\n\n/** One case plus the spec file it came from. */\nexport interface CaseWithFile {\n file: string;\n browser?: string;\n testCase: Case;\n}\n\n/**\n * Groups finished cases into one Suite per spec file.\n *\n * Grouping happens once at `onEnd` rather than incrementally, because\n * Playwright interleaves results across workers — with `fullyParallel` and N\n * workers there is no point during the run at which one file's cases are\n * known to be complete.\n */\nexport function groupIntoSuites(cases: readonly CaseWithFile[]): Suite[] {\n const byFile = new Map<string, CaseWithFile[]>();\n for (const entry of cases) {\n const existing = byFile.get(entry.file);\n if (existing) {\n existing.push(entry);\n } else {\n byFile.set(entry.file, [entry]);\n }\n }\n\n const suites: Suite[] = [];\n for (const [file, entries] of byFile) {\n let kept = entries;\n if (kept.length > MAX_CASES_PER_SUITE) {\n logger.warn(\n `suite \"${file}\" produced ${kept.length} cases, over the server's limit of ${MAX_CASES_PER_SUITE}; the rest were dropped.`,\n );\n kept = kept.slice(0, MAX_CASES_PER_SUITE);\n }\n\n // Browsers are per-project, and one spec file can run under several\n // projects (chromium + firefox + webkit). Report the distinct set rather\n // than whichever happened to finish last.\n const browsers = [...new Set(kept.map((e) => e.browser).filter((b): b is string => Boolean(b)))].sort();\n\n suites.push({\n name: file,\n category: 'playwright',\n duration: kept.reduce((sum, e) => sum + e.testCase.duration, 0),\n ...(browsers.length > 0 ? { browser: browsers.join(', ') } : {}),\n cases: kept.map((e) => e.testCase),\n });\n }\n\n if (suites.length > MAX_SUITES_PER_LAUNCH) {\n logger.warn(\n `this run produced ${suites.length} suites, over the server's limit of ${MAX_SUITES_PER_LAUNCH}; the rest were dropped.`,\n );\n return suites.slice(0, MAX_SUITES_PER_LAUNCH);\n }\n return suites;\n}\n"],"mappings":"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","names":["import_node_crypto","fs","path","name","firstEnv","name","fs","import_node_crypto","name","path","relative"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
-
import { Q as QualflarePlaywrightOptions } from '../resolve-config-
|
|
3
|
-
export { R as ResolvedReporterConfig } from '../resolve-config-
|
|
2
|
+
import { Q as QualflarePlaywrightOptions } from '../resolve-config-N04M1Avn.cjs';
|
|
3
|
+
export { R as ResolvedReporterConfig } from '../resolve-config-N04M1Avn.cjs';
|
|
4
4
|
|
|
5
5
|
/** Options Playwright injects on top of the user's own, for every reporter.
|
|
6
6
|
* It also injects internal `_mode`/`_commandHash` fields; this package
|
package/dist/reporter/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
-
import { Q as QualflarePlaywrightOptions } from '../resolve-config-
|
|
3
|
-
export { R as ResolvedReporterConfig } from '../resolve-config-
|
|
2
|
+
import { Q as QualflarePlaywrightOptions } from '../resolve-config-N04M1Avn.js';
|
|
3
|
+
export { R as ResolvedReporterConfig } from '../resolve-config-N04M1Avn.js';
|
|
4
4
|
|
|
5
5
|
/** Options Playwright injects on top of the user's own, for every reporter.
|
|
6
6
|
* It also injects internal `_mode`/`_commandHash` fields; this package
|