@testrelic/playwright-analytics 2.16.0 → 2.16.1-next.132
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/api-fixture.cjs +4 -4
- package/dist/api-fixture.cjs.map +1 -1
- package/dist/api-fixture.js +4 -4
- package/dist/api-fixture.js.map +1 -1
- package/dist/fixture.cjs +4 -4
- package/dist/fixture.cjs.map +1 -1
- package/dist/fixture.js +4 -4
- package/dist/fixture.js.map +1 -1
- package/dist/index.cjs +43 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +43 -43
- package/dist/index.js.map +1 -1
- package/dist/reporter-entry.cjs +10 -10
- package/dist/reporter-entry.cjs.map +1 -1
- package/dist/reporter-entry.js +10 -10
- package/dist/reporter-entry.js.map +1 -1
- package/dist/visual.cjs +2 -2
- package/dist/visual.cjs.map +1 -1
- package/dist/visual.js +2 -2
- package/dist/visual.js.map +1 -1
- package/package.json +2 -2
package/dist/api-fixture.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/jsonl-stream.ts","../src/ansi.ts","../src/assertion-tracker.ts","../src/api-redactor.ts","../src/api-url-filter.ts","../src/api-request-tracker.ts","../src/navigation-tracker.ts","../src/visual-metrics.ts","../src/dom-capture.ts","../src/dom-snapshot.ts","../src/dom-diff.ts","../src/snapshot-path-support.ts","../src/visual-expect.ts","../src/visual-capture.ts","../src/visual-matcher.ts","../src/visual.ts","../src/fixture.ts","../src/api-fixture.ts"],"names":["TESTRELIC_TMP_DIR","join","tmpdir","JsonlFileWriter","prefix","mkdirSync","randomUUID","openSync","obj","line","writeSync","closeSync","unlinkSync","ANSI_PATTERN","stripAnsi","text","CALL_ID_SYMBOL","ASSERTION_ANNOTATION_TYPE","valueCallIdMap","AssertionTracker","assertion","callId","testInfo","sourceCache","getSourceLines","filePath","cached","path","fileURLToPath","lines","readFileSync","OWN_STACK_DIR","stack","m","cut","toPlainPath","file","captureAssertionLocation","findFrame","excludeOwnDir","match","inferAssertionType","expression","_value","expr","buildExpectWrapper","getTracker","getApiTracker","originalExpect","wrappedExpect","received","rest","tracker","apiTracker","tagged","location","matchers","createMatcherProxy","statics","target","key","ACTIVE_CONTEXT_KEY","contextSlot","g","slot","setActiveAssertionContext","createActiveAssertionAwareExpect","ctx","prop","receiver","value","args","expected","status","actual","thrownError","result","recordAssertionResult","err","receivedMatch","matcherName","assertionType","safeSerializable","REDACTED","redactHeaders","headers","redactList","lowerList","h","redactBodyFields","body","parsed","fieldSet","f","redacted","walkAndRedact","fields","item","val","SECRET_QUERY_PARAMS","stripSecretQueryParams","rawUrl","url","changed","shouldTrackApiUrl","include","exclude","pattern","matchesPattern","globToRegex","glob","i","char","HTTP_METHODS","TEXT_CONTENT_TYPES","isTextContentType","contentType","lower","extractRequestBody","options","data","multipart","descriptor","maskCookieValue","parseRequestCookies","raw","k","v","out","part","seg","eq","name","parseResponseCookies","setCookieValues","segRaw","parts","cookieValue","attributes","p","_ApiRequestTracker","context","assertionTracker","apiConfig","method","original","origDispose","code","_testInfo","response","self","origHeaders","origHeadersArray","origJson","origStatus","origStatusText","origOk","origText","origBody","id","timestamp","httpMethod","requestBody","startTime","performance","endTime","errorRequestBody","errorRecord","rawResponseHeaders","binary","capturedRequestHeaders","capturedResponseHeaders","capturedRequestBody","capturedResponseBody","requestCookies","responseCookies","record","ApiRequestTracker","NavigationTracker","_NavigationTracker","page","pending","type","navigations","patchable","event","handler","onDomContentLoaded","onFrameNavigated","frame","frameObj","lastRecord","onConsoleMessage","msg","msgObj","loc","level","onRequest","request","navIndex","counter","req","reqId","postData","onResponse","resp","contentLength","resourceType","typeKey","responseTimeMs","respHeaders","responseSize","bodyPromise","responseBody","captured","onRequestFailed","origPush","origReplace","index","SIZE_MISMATCH_RE","isSizeMismatchFailure","message","captureDomSnapshot","styleProps","defaults","inherited","maxNodes","maxText","maskBoxes","viewportBox","fullPage","inheritedSet","scrollX","scrollY","nodes","truncatedNodes","docMasks","mask","isMasked","x","y","w","b","indexAmongSiblings","el","parent","siblings","sibling","directText","children","node","collectStyles","computed","parentComputed","styles","tag","box","className","contentWidth","contentHeight","captureBox","STYLE_PROPS","STYLE_DEFAULTS","INHERITED_PROPS","DEFAULT_DOM_MAX_BYTES","DEFAULT_DOM_MAX_NODES","DEFAULT_DOM_MAX_TEXT","resolveMaskBoxes","locators","boxes","locator","captureDom","config","serializeSnapshot","snapshot","maxBytes","json","dropped","block","domBaselinePath","screenshotPath","readDomBaseline","existsSync","isDomSnapshot","writeDomBaseline","dirname","writeFileSync","MOVEMENT_ONLY","isRootCause","kinds","kind","labelOf","label","classes","movedOrResized","a","dx","dy","dw","dh","diffStyles","from","to","props","changes","before","after","identityKey","pairNodes","baseline","byPath","pairs","unmatchedActual","matchedBaseline","unmatchedBaseline","byIdentity","bucket","added","claimed","candidate","removed","diffAttrs","areaShare","total","diffDomSnapshots","maxChanges","includeText","includeStyles","baseNodes","actualNodes","scored","attrs","textChanged","share","rootCauses","entry","aRoot","bRoot","PROBE_NAME","WARNED_KEY","warnSlot","supportsSnapshotPathOptions","resolved","warnSnapshotPathUnsupported","feature","VISUAL_DOM_ATTACHMENT","VISUAL_MATCHERS","isLocatorLike","isPageLike","snapshotNameOf","first","optionsOf","arg","maskLocatorsOf","withPngExtension","ROLE_SUFFIX_RE","resolveStem","attachedBefore","fallback","attachments","capture","input","isUpdatingSnapshots","mode","recordDomComparison","baselinePath","stem","basename","extname","serialized","actualDomPath","withVisualCapture","baseExpect","getTestInfo","wrapped","VISUAL_META_ATTACHMENT","IMAGE_EXTENSIONS","normalizeSnapshotName","ext","currentTestInfo","playwrightTest","stemOfNewAttachments","addedAfter","attachPassingBaseline","snapshotName","attachMeta","toMatchVisualBaseline","screenshotOptions","pass","playwrightExpect","error","wroteNewBaseline","base","API_CONFIG_ANNOTATION","LEGACY_TRACK_API_CALLS_ANNOTATION","readApiConfig","configAnnotation","deserializeRegExp","legacyAnnotation","_key","source","flags","testRelicFixture","use","networkRequestsFile","networkRequestsCount","consoleLogsFile","consoleLogsCount","payload","PAYLOAD_VERSION","ATTACHMENT_NAME","ATTACHMENT_CONTENT_TYPE","apiCallsFile","apiCallsCount","apiStatusMix","apiAssertions","expect","testRelicApiFixture","consoleWriter","consoleLogCount","origStdoutWrite","origStderrWrite","chunk"],"mappings":"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","file":"api-fixture.cjs","sourcesContent":["/**\n * JSONL (JSON Lines) streaming writer and reader.\n *\n * Writer: Appends one JSON object per line to a file using a persistent fd.\n * Used in worker processes to stream network requests, console logs,\n * and API calls to disk instead of accumulating in memory.\n *\n * Reader: Reads a specific page of lines from a JSONL file without loading\n * the entire file into memory.\n */\n\nimport { openSync, writeSync, closeSync, unlinkSync, mkdirSync, createReadStream } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { createInterface } from 'node:readline';\n\nconst TESTRELIC_TMP_DIR = join(tmpdir(), 'testrelic-data');\n\n/**\n * Append-only JSONL file writer.\n *\n * Keeps a file descriptor open for the lifetime of the writer.\n * Each `append()` call writes one JSON-serialized line synchronously,\n * so data hits disk immediately and is eligible for GC.\n */\nexport class JsonlFileWriter {\n private readonly filePath: string;\n private readonly fd: number;\n private count = 0;\n private closed = false;\n\n constructor(prefix: string) {\n mkdirSync(TESTRELIC_TMP_DIR, { recursive: true });\n this.filePath = join(TESTRELIC_TMP_DIR, `${prefix}-${randomUUID().substring(0, 8)}.jsonl`);\n this.fd = openSync(this.filePath, 'w');\n }\n\n /** Serialize `obj` as a single JSON line and write to disk. */\n append(obj: unknown): void {\n if (this.closed) return;\n const line = JSON.stringify(obj) + '\\n';\n writeSync(this.fd, line);\n this.count++;\n }\n\n /** Absolute path to the JSONL file. */\n getPath(): string {\n return this.filePath;\n }\n\n /** Number of lines written so far. */\n getCount(): number {\n return this.count;\n }\n\n /** Close the file descriptor. Must be called before the file is moved. */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n try {\n closeSync(this.fd);\n } catch {\n // Ignore close errors (fd may already be closed)\n }\n }\n\n /** Remove the temporary file from disk. */\n cleanup(): void {\n try {\n unlinkSync(this.filePath);\n } catch {\n // File may already be moved/deleted\n }\n }\n}\n\n/**\n * Read a page of items from a JSONL file.\n *\n * Uses `readline` to stream through the file line by line.\n * Only parses lines within the requested page range.\n * Skips lines before the page start without parsing them.\n *\n * @param filePath — Absolute path to the JSONL file\n * @param page — 1-based page number\n * @param pageSize — Number of items per page\n * @param knownTotal — If provided, avoids counting remaining lines after the page\n */\nexport async function readJsonlPage<T>(\n filePath: string,\n page: number,\n pageSize: number,\n knownTotal?: number,\n): Promise<{ items: T[]; total: number; page: number; pageSize: number; totalPages: number }> {\n const skip = (page - 1) * pageSize;\n const items: T[] = [];\n let lineCount = 0;\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: 'utf-8' }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (line.length === 0) continue;\n if (lineCount >= skip && items.length < pageSize) {\n try {\n items.push(JSON.parse(line) as T);\n } catch {\n // Skip malformed lines\n }\n }\n lineCount++;\n // Early exit if we have all items and know the total\n if (items.length >= pageSize && knownTotal !== undefined) {\n break;\n }\n }\n\n const total = knownTotal ?? lineCount;\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n return { items, total, page, pageSize, totalPages };\n}\n","/**\n * Strip terminal colour codes from text bound for the HTML report.\n *\n * Playwright formats assertion failures for a terminal, so the message a\n * reporter receives carries SGR escape sequences. The report rendered them\n * verbatim, which is why a failure panel read:\n *\n * Error: ESC[2mexpect(ESC[22mESC[31mpageESC[39m...\n *\n * and the Playwright call log came out sprayed with `[2m` / `[22m`. The escape\n * character survives JSON and `textContent` untouched — only a terminal ever\n * consumed it.\n */\n\n/** ESC (0x1B) and the single-byte CSI introducer (0x9B). */\nconst ESC = '\\u001B';\nconst CSI = '\\u009B';\n\n/**\n * ANSI escape sequences, both the modern CSI form and the older two-byte one.\n *\n * Deliberately broader than colour codes alone: cursor moves and erase\n * sequences turn up in wrapped tool output too, and none of them mean anything\n * in HTML.\n */\nconst ANSI_PATTERN = new RegExp(\n `[${ESC}${CSI}][[\\\\]()#;?]*(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[0-9A-PR-TZcf-nqry=><]`,\n 'g',\n);\n\n/**\n * Remove ANSI escape sequences from a string.\n *\n * Returns the input unchanged when there is nothing to strip. That fast path\n * matters: this runs on every captured step title in a large suite, and the\n * overwhelming majority contain no escapes at all.\n */\nexport function stripAnsi(text: string): string {\n if (!text) return text;\n if (text.indexOf(ESC) === -1 && text.indexOf(CSI) === -1) return text;\n return text.replace(ANSI_PATTERN, '');\n}\n\n/** Strip, tolerating null and undefined so call sites stay uncluttered. */\nexport function stripAnsiSafe(text: string | null | undefined): string | null {\n if (text === null || text === undefined) return null;\n return stripAnsi(text);\n}\n","/**\n * AssertionTracker — Captures and links assertions to API calls\n *\n * Records every assertion made on API response data (both passing and failing),\n * links each to the originating API call by call ID, and flushes the data to\n * testInfo.annotations for the reporter to consume.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { ApiAssertion, AssertionType, AssertionLocation } from '@testrelic/core';\nimport { stripAnsi } from './ansi.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Symbol used to tag APIResponse objects with their call ID. */\nexport const CALL_ID_SYMBOL: unique symbol = Symbol.for('__testrelic_call_id');\n\n/** Annotation type for assertion data transport (fixture → reporter). */\nexport const ASSERTION_ANNOTATION_TYPE = '__testrelic_api_assertions';\n\n/** WeakMap to track call IDs for object values extracted from responses. */\nexport const valueCallIdMap = new WeakMap<object, string>();\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// AssertionTracker\n// ---------------------------------------------------------------------------\n\nexport class AssertionTracker {\n private assertions: ApiAssertion[] = [];\n private currentCallId: string | null = null;\n\n /** Record a captured assertion. */\n recordAssertion(assertion: ApiAssertion): void {\n this.assertions.push(assertion);\n }\n\n /** Get all recorded assertions. */\n getAssertions(): readonly ApiAssertion[] {\n return this.assertions;\n }\n\n /** Set the most recent API call ID for temporal fallback linking. */\n setCurrentCallId(callId: string): void {\n this.currentCallId = callId;\n }\n\n /** Get the most recent API call ID. */\n getCurrentCallId(): string | null {\n return this.currentCallId;\n }\n\n /** Return assertion data for consolidated payload. */\n getData(): ApiAssertion[] {\n return [...this.assertions];\n }\n\n /** Flush assertion data to testInfo annotations (legacy). */\n flushLegacyAnnotations(testInfo: TestInfoLike): void {\n if (this.assertions.length === 0) return;\n\n testInfo.annotations.push({\n type: ASSERTION_ANNOTATION_TYPE,\n description: JSON.stringify(this.assertions),\n });\n }\n\n /** Clear all state for garbage collection. */\n dispose(): void {\n this.assertions = [];\n this.currentCallId = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Location Capture\n// ---------------------------------------------------------------------------\n\n/** Cache for source file lines to avoid repeated reads. */\nconst sourceCache = new Map<string, string[]>();\n\nfunction getSourceLines(filePath: string): string[] | null {\n const cached = sourceCache.get(filePath);\n if (cached) return cached;\n\n try {\n // V8 stack frames are file:// URLs under ESM (and always on Windows, e.g.\n // `file:///C:/…`). readFileSync needs a filesystem path, so normalize first\n // — otherwise the source line (and thus the assertion expression + type)\n // can't be read and every assertion degrades to a generic \"custom\" one.\n const path = filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath;\n const content = readFileSync(path, 'utf-8');\n const lines = content.split('\\n');\n sourceCache.set(filePath, lines);\n return lines;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the call stack to find the assertion location in user test code.\n * Returns the first stack frame that is NOT inside the SDK's own source files.\n */\n/**\n * The directory this module ended up in.\n *\n * The package is bundled per entry point with `splitting: false`, so at\n * runtime this code lives in `fixture.cjs` / `api-fixture.cjs` / etc., never\n * in a file called `assertion-tracker`. Matching on the source filename\n * therefore skipped nothing, and every captured location pointed at a line of\n * our own wrapper instead of the user's test.\n */\nconst OWN_STACK_DIR: string | null = (() => {\n const stack = new Error().stack;\n if (!stack) return null;\n for (const line of stack.split('\\n')) {\n const m = line.match(/at\\s+(?:.*?\\s+\\()?(.+?):\\d+:\\d+\\)?$/);\n if (!m || m[1].startsWith('node:')) continue;\n // Source maps rewrite bundled frames back to their ORIGINAL source\n // paths, so the frame to skip can name any module in this package, not\n // only this one. The directory they share is the reliable marker.\n const cut = Math.max(m[1].lastIndexOf('/'), m[1].lastIndexOf('\\\\'));\n return cut > 0 ? m[1].slice(0, cut + 1) : null;\n }\n return null;\n})();\n\n/** A stack-frame path as a plain filesystem path. */\nfunction toPlainPath(file: string): string {\n if (!file.startsWith('file:')) return file;\n try { return fileURLToPath(file); } catch { return file; }\n}\n\nfunction captureAssertionLocation(): AssertionLocation | null {\n const err = new Error();\n // SAFETY: Error.stack is V8-specific but guaranteed on Node.js 18+\n const stack = err.stack;\n if (!stack) return null;\n\n const lines = stack.split('\\n');\n\n // Two passes. The first skips our own code; the second skips only the\n // machinery that is ours by name, so a stack the first pass rejects\n // entirely still yields a real file and line rather than `unknown:0`.\n return findFrame(lines, true) ?? findFrame(lines, false);\n}\n\n/**\n * First frame that is not ours.\n *\n * `excludeOwnDir` is what separates the two passes. Note what is deliberately\n * NOT here: a filter on our own source filenames. `'/assertion-tracker.'`\n * used to be in this list, and it matches the path of any file whose name\n * merely STARTS that way — including this module's own test,\n * `assertion-tracker.test.ts`. On Windows the separator is a backslash so the\n * substring never matched and the filter looked harmless; on POSIX it dropped\n * the very frame being looked for, every frame was skipped, and the location\n * came back as the `unknown` fallback. `OWN_STACK_DIR` identifies our code by\n * the directory it actually runs from, which is both correct and not\n * defeatable by what a user names a test file.\n */\nfunction findFrame(lines: readonly string[], excludeOwnDir: boolean): AssertionLocation | null {\n for (const line of lines) {\n if (\n (excludeOwnDir && OWN_STACK_DIR !== null && line.includes(OWN_STACK_DIR)) ||\n line.includes('node_modules') ||\n line.includes('node:internal') ||\n line.includes(' at new Error') ||\n line.includes(' at captureAssertionLocation') ||\n line.includes(' at findFrame') ||\n line.includes(' at expectWrapper') ||\n line.includes(' at wrappedMatcher')\n ) {\n continue;\n }\n\n // Match V8 stack frame: \" at functionName (file:line:column)\"\n // or \" at file:line:column\"\n const match = line.match(/at\\s+(?:.*?\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/);\n if (match) {\n return {\n // ESM frames are file:// URLs and CJS frames are plain paths. Callers\n // compare this against paths from Playwright, so normalise here.\n file: toPlainPath(match[1]),\n line: parseInt(match[2], 10),\n column: parseInt(match[3], 10),\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Type Detection\n// ---------------------------------------------------------------------------\n\n/**\n * Infer the assertion type from the source expression and the value being asserted.\n */\nfunction inferAssertionType(\n expression: string | undefined,\n // SAFETY: value is the raw asserted value from user code — may be any type\n _value: unknown,\n): AssertionType {\n if (!expression) return 'custom';\n\n const expr = expression.toLowerCase();\n\n if (expr.includes('.status()') && !expr.includes('.statustext()')) {\n return expr.includes('.ok()') ? 'statusOk' : 'status';\n }\n if (expr.includes('.ok()')) return 'statusOk';\n if (expr.includes('.headers()') || expr.includes('.headersarray()')) return 'header';\n if (expr.includes('tomatchobject') || expr.includes('toequal')) return 'bodyMatch';\n if (expr.includes('tocontain') || expr.includes('tostringcontaining')) return 'bodyContains';\n\n // If expression references json() or text() it's a body field\n if (expr.includes('.json()') || expr.includes('.text()')) return 'bodyField';\n\n // If none of the above match, default to bodyField for parsed data, or custom\n return 'custom';\n}\n\n// ---------------------------------------------------------------------------\n// Assertion-Aware Expect Wrapper\n// ---------------------------------------------------------------------------\n\n/** Minimal interface for ApiRequestTracker to avoid circular imports. */\ninterface ApiTrackerLike {\n readonly lastCallId: string | null;\n getCallIdForValue(value: unknown): string | null;\n}\n\n/**\n * Creates a wrapped `expect` that captures assertion metadata and records it\n * via the AssertionTracker. Preserves all original Playwright expect behavior.\n */\nfunction buildExpectWrapper(\n getTracker: () => AssertionTracker | null,\n getApiTracker: () => ApiTrackerLike | null,\n // SAFETY: originalExpect is the Playwright expect — typed as Function to avoid\n // importing @playwright/test in this module's runtime\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n const wrappedExpect = function expectWrapper(received: unknown, ...rest: unknown[]): unknown {\n // Resolve the assertion context (explicit per-instance, or the active\n // per-test context set by the fixture). With no context, this is a\n // transparent pass-through to the raw Playwright expect.\n const tracker = getTracker();\n const apiTracker = getApiTracker();\n if (!tracker || !apiTracker) {\n return (originalExpect as Function)(received, ...rest);\n }\n\n // Determine call ID from the value being asserted\n let callId: string | null = null;\n\n // Strategy 1: Check if the value itself is tagged (response object)\n if (received !== null && received !== undefined && typeof received === 'object') {\n const tagged = (received as Record<symbol, string>)[CALL_ID_SYMBOL];\n if (typeof tagged === 'string') {\n callId = tagged;\n }\n }\n\n // Strategy 2: Check if value is in the WeakMap (extracted from response)\n if (!callId && received !== null && received !== undefined && typeof received === 'object') {\n callId = valueCallIdMap.get(received as object) ?? null;\n }\n\n // Strategy 3: Check via the API tracker's value-to-callId mapping\n if (!callId) {\n callId = apiTracker.getCallIdForValue(received);\n }\n\n // Strategy 4: Temporal fallback — use the most recent API call\n if (!callId) {\n callId = tracker.getCurrentCallId();\n }\n\n // If no call ID found, this assertion is not related to an API call — skip capture\n if (!callId) {\n return (originalExpect as Function)(received, ...rest);\n }\n\n // Capture location before the assertion runs\n const location = captureAssertionLocation();\n\n // Read the source expression (best-effort)\n let expression: string | undefined;\n if (location) {\n const lines = getSourceLines(location.file);\n if (lines && location.line > 0 && location.line <= lines.length) {\n expression = lines[location.line - 1].trim();\n }\n }\n\n // Get the matchers object from the original expect\n const matchers = (originalExpect as Function)(received, ...rest);\n\n // If no callId at this point, just return the matchers directly\n if (!callId) return matchers;\n\n // Wrap the matchers to intercept assertion calls\n return createMatcherProxy(matchers as object, {\n tracker,\n callId,\n received,\n location: location ?? { file: 'unknown', line: 0 },\n expression,\n });\n };\n\n // Copy static methods from originalExpect (e.g., expect.soft, expect.configure, etc.)\n //\n // SAFETY: both sides are functions, which have no index signature, so\n // reading and writing arbitrary statics needs a widening step. The keys come\n // from `Object.keys` of the original, never from user input.\n const statics = originalExpect as unknown as Record<string, unknown>;\n const target = wrappedExpect as unknown as Record<string, unknown>;\n for (const key of Object.keys(statics)) {\n target[key] = statics[key];\n }\n\n return wrappedExpect;\n}\n\n/**\n * Per-instance assertion-aware expect — records into the explicitly-provided\n * tracker. Used by unit tests and any caller holding tracker references.\n */\nexport function createAssertionAwareExpect(\n tracker: AssertionTracker,\n apiTracker: ApiTrackerLike,\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n return buildExpectWrapper(() => tracker, () => apiTracker, originalExpect);\n}\n\n// ---------------------------------------------------------------------------\n// Active assertion context (fixture → exported `expect`)\n//\n// Fixtures export a single module-level `expect`, but the trackers are created\n// per-test inside the fixture. Playwright runs tests serially within a worker,\n// so the fixture sets an \"active context\" at test start and clears it at test\n// end; the exported `expect` records into whichever test is currently running.\n// ---------------------------------------------------------------------------\n\n/**\n * The active context lives on `globalThis`, not in a module variable.\n *\n * The package ships several entry points (`/fixture`, `/api-fixture`,\n * `/visual`) built with `splitting: false`, so each bundle carries its OWN\n * copy of this module — and its own module-level variables. The documented\n * setup imports `test` from one entry and `expect` from another, which meant\n * the fixture set the context in one copy while `expect` read `null` from\n * another and silently passed every assertion straight through. A single\n * process-wide slot is what makes the two halves meet.\n */\ninterface AssertionContextSlot {\n tracker: AssertionTracker | null;\n apiTracker: ApiTrackerLike | null;\n}\n\nconst ACTIVE_CONTEXT_KEY = Symbol.for('testrelic.activeAssertionContext');\n\nfunction contextSlot(): AssertionContextSlot {\n const g = globalThis as unknown as Record<symbol, AssertionContextSlot | undefined>;\n let slot = g[ACTIVE_CONTEXT_KEY];\n if (!slot) {\n slot = { tracker: null, apiTracker: null };\n g[ACTIVE_CONTEXT_KEY] = slot;\n }\n return slot;\n}\n\n/** Set (or clear, with nulls) the active per-test assertion context. */\nexport function setActiveAssertionContext(\n tracker: AssertionTracker | null,\n apiTracker: ApiTrackerLike | null,\n): void {\n const slot = contextSlot();\n slot.tracker = tracker;\n slot.apiTracker = apiTracker;\n}\n\n/**\n * Assertion-aware expect that records into whichever tracker is currently\n * active (see setActiveAssertionContext). Falls back to raw expect when none —\n * so it is safe to construct once at module load and import everywhere.\n */\nexport function createActiveAssertionAwareExpect(\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n return buildExpectWrapper(\n () => contextSlot().tracker,\n () => contextSlot().apiTracker,\n originalExpect,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Matcher Proxy\n// ---------------------------------------------------------------------------\n\ninterface MatcherContext {\n tracker: AssertionTracker;\n callId: string;\n received: unknown;\n location: AssertionLocation;\n expression: string | undefined;\n}\n\n/**\n * Creates a Proxy around the matchers object returned by expect().\n * Intercepts matcher method calls (toBe, toEqual, etc.) to record assertions.\n */\nfunction createMatcherProxy(matchers: object, ctx: MatcherContext): object {\n return new Proxy(matchers, {\n get(target: object, prop: string | symbol, receiver: unknown): unknown {\n const value = Reflect.get(target, prop, receiver);\n\n // Only intercept function calls (matcher methods)\n if (typeof value !== 'function') return value;\n\n // Skip internal Playwright properties\n if (typeof prop === 'symbol') return value;\n\n // Handle .not — return a new proxy with negation\n if (prop === 'not') {\n const negated = value;\n return createMatcherProxy(\n negated as object,\n ctx,\n );\n }\n\n // Wrap the matcher function to capture the assertion\n return function wrappedMatcher(this: unknown, ...args: unknown[]): unknown {\n const expected = args[0];\n let status: 'passed' | 'failed' = 'passed';\n let actual: unknown = ctx.received;\n let thrownError: unknown = null;\n\n try {\n const result = (value as Function).apply(this ?? target, args);\n\n // If result is a Promise (async matchers), handle it\n if (result && typeof result === 'object' && typeof (result as { then?: unknown }).then === 'function') {\n return (result as Promise<unknown>).then(\n () => {\n recordAssertionResult(ctx, prop, expected, actual, 'passed');\n },\n (err: unknown) => {\n recordAssertionResult(ctx, prop, expected, actual, 'failed');\n throw err;\n },\n );\n }\n\n return result;\n } catch (err: unknown) {\n status = 'failed';\n thrownError = err;\n\n // Try to extract actual value from the error message\n if (err instanceof Error && err.message) {\n const receivedMatch = err.message.match(/Received:\\s*(.+)/);\n if (receivedMatch) {\n actual = receivedMatch[1];\n }\n }\n\n throw err;\n } finally {\n // Only record sync assertions here; async assertions are handled above\n if (thrownError !== null || status === 'passed') {\n recordAssertionResult(ctx, prop, expected, actual, status);\n }\n }\n };\n },\n });\n}\n\nfunction recordAssertionResult(\n ctx: MatcherContext,\n matcherName: string,\n expected: unknown,\n actual: unknown,\n status: 'passed' | 'failed',\n): void {\n const assertionType = inferAssertionType(ctx.expression, ctx.received);\n\n const assertion: ApiAssertion = {\n callId: ctx.callId,\n type: assertionType,\n expected: safeSerializable(expected),\n actual: safeSerializable(actual),\n status,\n location: ctx.location,\n expression: ctx.expression,\n };\n\n ctx.tracker.recordAssertion(assertion);\n}\n\n/**\n * Ensure a value is safely serializable to JSON.\n * Converts non-serializable values to string representations.\n */\nfunction safeSerializable(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n // Values scraped out of a matcher's error message carry Playwright's ANSI\n // colour codes. They are invisible in a terminal and garbage everywhere\n // else — the HTML report, the JSON file and the cloud payload all showed\n // \"\\u001b[31m100\\u001b[39m\" where the received value should be.\n if (typeof value === 'string') return stripAnsi(value);\n if (typeof value === 'number' || typeof value === 'boolean') return value;\n try {\n JSON.stringify(value);\n return value;\n } catch {\n return String(value);\n }\n}\n","/**\n * API data redaction — header values and recursive body field redaction.\n *\n * Separate from redaction.ts (pattern-based string redaction).\n * This module operates on structured data: header name→value pairs\n * and JSON object field names at any nesting depth.\n */\n\nconst REDACTED = '[REDACTED]';\n\n/**\n * Redact header values by name (case-insensitive).\n * Returns a new object with matching header values replaced by \"[REDACTED]\".\n */\nexport function redactHeaders(\n headers: Record<string, string> | null,\n redactList: readonly string[],\n): Record<string, string> | null {\n if (headers === null || redactList.length === 0) return headers;\n\n const lowerList = new Set(redactList.map((h) => h.toLowerCase()));\n const result: Record<string, string> = {};\n\n for (const key of Object.keys(headers)) {\n // `Object.hasOwn` is ES2022 and this package pins `lib: [\"ES2020\"]` on\n // purpose. The prototype call is the same check with no lib requirement.\n if (Object.prototype.hasOwnProperty.call(headers, key)) {\n result[key] = lowerList.has(key.toLowerCase()) ? REDACTED : headers[key];\n }\n }\n\n return result;\n}\n\n/**\n * Redact body field values by name (recursive, any depth).\n * Parses JSON, walks object/array, replaces matching field values with \"[REDACTED]\".\n * Non-JSON bodies are returned as-is.\n */\nexport function redactBodyFields(\n body: string | null,\n redactList: readonly string[],\n): string | null {\n if (body === null || redactList.length === 0) return body;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n // Not JSON — return as-is (FR-019)\n return body;\n }\n\n if (typeof parsed !== 'object' || parsed === null) return body;\n\n // Field matching is case-insensitive: a config entry of `token` must also\n // catch `Token`, `TOKEN`, `Access_Token`, etc. so casing can't smuggle a\n // secret past the redactor.\n const fieldSet = new Set(redactList.map((f) => f.toLowerCase()));\n const redacted = walkAndRedact(parsed, fieldSet);\n return JSON.stringify(redacted);\n}\n\n/**\n * Recursively walk an object/array and redact matching field values.\n * Field names are compared case-insensitively (lowercased).\n */\nfunction walkAndRedact(value: unknown, fields: Set<string>): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => walkAndRedact(item, fields));\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (!Object.prototype.hasOwnProperty.call(value, key)) continue;\n const val = (value as Record<string, unknown>)[key];\n if (fields.has(key.toLowerCase())) {\n result[key] = REDACTED;\n } else {\n result[key] = walkAndRedact(val, fields);\n }\n }\n return result;\n }\n\n return value;\n}\n\n/**\n * Query-string parameters whose values are commonly secrets. Their values are\n * stripped from any URL we store so tokens don't leak via the request URL\n * (which is persisted and rendered verbatim in the report/cloud).\n */\nconst SECRET_QUERY_PARAMS = new Set(['access_token', 'api_key', 'token', 'sig']);\n\n/**\n * Remove known secret query parameters (access_token, api_key, token, sig)\n * from a URL. Matching is case-insensitive on the parameter name. Returns the\n * original string unchanged when it isn't a parseable absolute URL or when no\n * secret params are present (so URLs aren't needlessly re-serialized).\n */\nexport function stripSecretQueryParams(rawUrl: string): string {\n if (!rawUrl) return rawUrl;\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n // Not an absolute URL — leave it untouched.\n return rawUrl;\n }\n let changed = false;\n for (const key of [...url.searchParams.keys()]) {\n if (SECRET_QUERY_PARAMS.has(key.toLowerCase())) {\n url.searchParams.delete(key);\n changed = true;\n }\n }\n return changed ? url.toString() : rawUrl;\n}\n","/**\n * API URL filtering — include/exclude patterns for API call tracking.\n *\n * Supports glob strings and RegExp objects. Exclude takes precedence over include.\n */\n\n/**\n * Determines whether an API call URL should be tracked.\n *\n * - No filters → track all\n * - Include set → URL must match at least one include pattern\n * - Exclude set → URL must not match any exclude pattern\n * - Both set → exclude takes precedence\n */\nexport function shouldTrackApiUrl(\n url: string,\n include: readonly (string | RegExp)[],\n exclude: readonly (string | RegExp)[],\n): boolean {\n // Check exclude first (takes precedence)\n if (exclude.length > 0) {\n for (const pattern of exclude) {\n if (matchesPattern(url, pattern)) return false;\n }\n }\n\n // Check include\n if (include.length > 0) {\n for (const pattern of include) {\n if (matchesPattern(url, pattern)) return true;\n }\n return false; // Include set but no match → don't track\n }\n\n return true; // No filters → track all\n}\n\n/**\n * Test a URL against a single pattern (glob string or RegExp).\n */\nfunction matchesPattern(url: string, pattern: string | RegExp): boolean {\n try {\n if (pattern instanceof RegExp) {\n return pattern.test(url);\n }\n // Convert glob to regex\n const regex = globToRegex(pattern);\n return regex.test(url);\n } catch {\n // Invalid pattern — skip with warning\n console.warn(`[testrelic] Invalid URL filter pattern: ${String(pattern)}`);\n return false;\n }\n}\n\n/**\n * Convert a glob pattern to a RegExp.\n * Handles `**` (match anything including /) and `*` (match anything except /).\n */\nfunction globToRegex(glob: string): RegExp {\n let result = '';\n let i = 0;\n while (i < glob.length) {\n const char = glob[i];\n if (char === '*' && glob[i + 1] === '*') {\n result += '.*';\n i += 2;\n // Skip optional trailing /\n if (glob[i] === '/') i++;\n } else if (char === '*') {\n result += '[^/]*';\n i++;\n } else if (char === '?') {\n result += '[^/]';\n i++;\n } else if ('.+^${}()|[]\\\\'.includes(char)) {\n result += '\\\\' + char;\n i++;\n } else {\n result += char;\n i++;\n }\n }\n return new RegExp(result);\n}\n","/**\n * ApiRequestTracker — Proxy wrapper for Playwright's APIRequestContext\n *\n * Intercepts all HTTP method calls (.get, .post, .put, .patch, .delete,\n * .head, .fetch, .dispose) to record detailed API call analytics.\n * Data is flushed to testInfo.annotations for the reporter to consume.\n */\n\nimport { performance } from 'node:perf_hooks';\nimport type { APIRequestContext, APIResponse } from '@playwright/test';\nimport type { ApiCallRecord, ApiCallsByStatusRange, CapturedCookie } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\nimport { CALL_ID_SYMBOL, valueCallIdMap } from './assertion-tracker.js';\nimport type { AssertionTracker } from './assertion-tracker.js';\nimport type { ResolvedApiConfig } from './config.js';\nimport { redactHeaders, redactBodyFields, stripSecretQueryParams } from './api-redactor.js';\nimport { shouldTrackApiUrl } from './api-url-filter.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'fetch'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nconst TEXT_CONTENT_TYPES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\nconst ANNOTATION_TYPE = '__testrelic_api_calls';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isTextContentType(contentType: string): boolean {\n const lower = contentType.toLowerCase();\n return TEXT_CONTENT_TYPES.some((prefix) => lower.includes(prefix));\n}\n\n/**\n * Extract request body from Playwright request options.\n * Handles `data` (string/object/Buffer), `form` (key-value), and\n * `multipart` (field descriptors) cases.\n */\nfunction extractRequestBody(options?: Record<string, unknown> | null): string | null {\n if (!options) return null;\n\n if (options.data !== undefined && options.data !== null) {\n const data = options.data;\n if (typeof data === 'string') return data;\n if (Buffer.isBuffer(data)) return data.toString('base64');\n return JSON.stringify(data);\n }\n\n if (options.form !== undefined && options.form !== null) {\n return JSON.stringify(options.form);\n }\n\n if (options.multipart !== undefined && options.multipart !== null) {\n // Capture field names and metadata, not full binary contents\n const multipart = options.multipart as Record<string, unknown>;\n const descriptor: Record<string, string> = {};\n for (const [key, value] of Object.entries(multipart)) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n descriptor[key] = String(value);\n } else if (value && typeof value === 'object' && 'name' in value) {\n descriptor[key] = `[file: ${(value as { name: string }).name}]`;\n } else {\n descriptor[key] = '[binary]';\n }\n }\n return JSON.stringify(descriptor);\n }\n\n return null;\n}\n\n/** Mask a cookie value so raw session tokens never reach the cloud. */\nfunction maskCookieValue(value: string): string {\n return value ? '••••' : '';\n}\n\n/** Parse a request `Cookie` header (`a=1; b=2`) into masked cookies. */\nfunction parseRequestCookies(headers: Record<string, string> | null | undefined): CapturedCookie[] {\n if (!headers) return [];\n let raw: string | undefined;\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === 'cookie') {\n raw = v;\n break;\n }\n }\n if (!raw) return [];\n const out: CapturedCookie[] = [];\n for (const part of raw.split(';')) {\n const seg = part.trim();\n if (!seg) continue;\n const eq = seg.indexOf('=');\n const name = eq === -1 ? seg : seg.slice(0, eq).trim();\n const value = eq === -1 ? '' : seg.slice(eq + 1).trim();\n if (name) out.push({ name, value: maskCookieValue(value) });\n }\n return out;\n}\n\n/** Parse `Set-Cookie` response header values into masked cookies + attributes. */\nfunction parseResponseCookies(setCookieValues: string[]): CapturedCookie[] {\n const out: CapturedCookie[] = [];\n for (const value of setCookieValues) {\n // Playwright's headersArray() usually returns one entry per Set-Cookie, but\n // some runtimes join multiple cookies into one value — split on the\n // `, <token>=` boundary so commas inside Expires=... don't break the parse.\n for (const segRaw of value.split(/,(?=\\s*[A-Za-z0-9_.\\-]+=)/)) {\n const seg = segRaw.trim();\n if (!seg) continue;\n const parts = seg.split(';');\n const eq = parts[0].indexOf('=');\n if (eq === -1) continue;\n const name = parts[0].slice(0, eq).trim();\n const cookieValue = parts[0].slice(eq + 1).trim();\n const attributes = parts.slice(1).map((p) => p.trim()).filter(Boolean).join(' · ') || undefined;\n if (name) out.push({ name, value: maskCookieValue(cookieValue), attributes });\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// ApiRequestTracker\n// ---------------------------------------------------------------------------\n\nexport class ApiRequestTracker {\n private readonly context: APIRequestContext;\n private readonly originals: Map<string, (...args: never[]) => unknown> = new Map();\n private apiCallWriter: JsonlFileWriter | null = null;\n /**\n * Running tally of response-status ranges.\n *\n * The records go straight to a JSONL file and are never read back in\n * streaming mode, so anything the grid needs about them has to be counted\n * here, while we still hold the record.\n */\n private readonly statusMix = { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 };\n private callCounter = 0;\n private apiCallCount = 0;\n private disposed = false;\n private readonly assertionTracker: AssertionTracker | null;\n private readonly apiConfig: ResolvedApiConfig;\n private _lastCallId: string | null = null;\n\n /** Map of primitive values to their originating call IDs (for status, ok, etc.). */\n private readonly primitiveCallIds = new Map<unknown, string>();\n\n /** Default API config when none is provided (all capture enabled, default redaction). */\n private static readonly DEFAULT_API_CONFIG: ResolvedApiConfig = Object.freeze({\n trackApiCalls: true,\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n });\n\n constructor(context: APIRequestContext, assertionTracker?: AssertionTracker, apiConfig?: ResolvedApiConfig) {\n this.context = context;\n this.assertionTracker = assertionTracker ?? null;\n this.apiConfig = apiConfig ?? ApiRequestTracker.DEFAULT_API_CONFIG;\n try {\n this.apiCallWriter = new JsonlFileWriter('api-calls');\n } catch {\n // Graceful degradation\n }\n }\n\n /** The most recent API call ID (for temporal fallback linking). */\n get lastCallId(): string | null {\n return this._lastCallId;\n }\n\n /**\n * Get the call ID for a value extracted from a response.\n * Works for objects (via WeakMap) and primitives (via internal Map).\n */\n getCallIdForValue(value: unknown): string | null {\n if (value !== null && value !== undefined && typeof value === 'object') {\n return valueCallIdMap.get(value as object) ?? null;\n }\n return this.primitiveCallIds.get(value) ?? null;\n }\n\n /** Replace HTTP methods and dispose on the context with instrumented wrappers. */\n intercept(): void {\n for (const method of HTTP_METHODS) {\n const original = (this.context[method] as (...args: never[]) => unknown).bind(this.context);\n this.originals.set(method, original);\n (this.context as unknown as Record<string, unknown>)[method] = this.createWrapper(method, original);\n }\n\n // Intercept dispose\n const origDispose = this.context.dispose.bind(this.context);\n this.originals.set('dispose', origDispose as (...args: never[]) => unknown);\n (this.context as unknown as Record<string, unknown>).dispose = async (\n options?: { reason?: string },\n ): Promise<void> => {\n this.disposed = true;\n return origDispose(options);\n };\n }\n\n /** Count one response against the status mix. A null code is a transport error. */\n private tallyStatus(code: number | null): void {\n if (code === null || code <= 0) { this.statusMix.error++; return; }\n if (code >= 500) { this.statusMix['5xx']++; return; }\n if (code >= 400) { this.statusMix['4xx']++; return; }\n if (code >= 300) { this.statusMix['3xx']++; return; }\n if (code >= 200) { this.statusMix['2xx']++; return; }\n this.statusMix.error++;\n }\n\n /** Return file path, count and status mix for the JSONL file of API call records. */\n getFileData(): {\n apiCallsFile: string | null;\n apiCallsCount: number;\n apiStatusMix: ApiCallsByStatusRange;\n } {\n this.apiCallWriter?.close();\n return {\n apiCallsFile: this.apiCallWriter?.getCount() ? this.apiCallWriter.getPath() : null,\n apiCallsCount: this.apiCallWriter?.getCount() ?? 0,\n apiStatusMix: { ...this.statusMix },\n };\n }\n\n /** @deprecated Legacy annotations no longer used — data flows via file-based payloads. */\n flushLegacyAnnotations(_testInfo: TestInfoLike): void {\n // No-op in v2\n }\n\n /** Restore original methods and clear captured data. */\n dispose(): void {\n for (const [method, original] of this.originals) {\n (this.context as unknown as Record<string, unknown>)[method] = original;\n }\n this.originals.clear();\n this.apiCallWriter?.close();\n this.callCounter = 0;\n this.apiCallCount = 0;\n this._lastCallId = null;\n this.primitiveCallIds.clear();\n }\n\n /** Whether the underlying context has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /** Access captured call count (for testing). */\n getCapturedCallCount(): number {\n return this.apiCallCount;\n }\n\n /**\n * Wraps response methods to tag extracted values with the originating call ID.\n * This enables assertion linking for values extracted from responses.\n */\n private tagResponseMethods(response: APIResponse, callId: string): void {\n const self = this;\n\n // Wrap methods that return objects (headers, json)\n const origHeaders = response.headers.bind(response);\n (response as unknown as Record<string, unknown>).headers = function taggedHeaders(): Record<string, string> {\n const result = origHeaders();\n valueCallIdMap.set(result, callId);\n return result;\n };\n\n const origHeadersArray = response.headersArray.bind(response);\n (response as unknown as Record<string, unknown>).headersArray = function taggedHeadersArray(): Array<{ name: string; value: string }> {\n const result = origHeadersArray();\n valueCallIdMap.set(result, callId);\n return result;\n };\n\n const origJson = response.json.bind(response);\n (response as unknown as Record<string, unknown>).json = async function taggedJson(): Promise<unknown> {\n const result = await origJson();\n if (result !== null && result !== undefined && typeof result === 'object') {\n valueCallIdMap.set(result as object, callId);\n }\n return result;\n };\n\n // Wrap methods that return primitives — track in primitiveCallIds\n const origStatus = response.status.bind(response);\n (response as unknown as Record<string, unknown>).status = function taggedStatus(): number {\n const result = origStatus();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origStatusText = response.statusText.bind(response);\n (response as unknown as Record<string, unknown>).statusText = function taggedStatusText(): string {\n const result = origStatusText();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origOk = response.ok.bind(response);\n (response as unknown as Record<string, unknown>).ok = function taggedOk(): boolean {\n const result = origOk();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origText = response.text.bind(response);\n (response as unknown as Record<string, unknown>).text = async function taggedText(): Promise<string> {\n const result = await origText();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origBody = response.body.bind(response);\n (response as unknown as Record<string, unknown>).body = async function taggedBody(): Promise<Buffer> {\n const result = await origBody();\n valueCallIdMap.set(result, callId);\n return result;\n };\n }\n\n private createWrapper(\n method: HttpMethod,\n original: (...args: never[]) => unknown,\n ): (url: string, options?: Record<string, unknown>) => Promise<APIResponse> {\n const self = this;\n\n return async function wrappedMethod(\n url: string,\n options?: Record<string, unknown>,\n ): Promise<APIResponse> {\n // URL filtering: skip tracking if URL doesn't match include/exclude patterns\n if (!shouldTrackApiUrl(url, self.apiConfig.apiIncludeUrls, self.apiConfig.apiExcludeUrls)) {\n return (original as (url: string, options?: Record<string, unknown>) => Promise<APIResponse>)(url, options);\n }\n\n const id = `api-call-${self.callCounter++}`;\n const timestamp = new Date().toISOString();\n const httpMethod = method === 'fetch'\n ? ((options?.method as string) ?? 'GET').toUpperCase()\n : method.toUpperCase();\n const requestBody = extractRequestBody(options);\n const startTime = performance.now();\n\n // Update temporal tracking for assertion linking\n self._lastCallId = id;\n if (self.assertionTracker) {\n self.assertionTracker.setCurrentCallId(id);\n }\n\n let response: APIResponse;\n try {\n response = await (original as (url: string, options?: Record<string, unknown>) => Promise<APIResponse>)(\n url,\n options,\n );\n } catch (err: unknown) {\n // Error path: capture partial record, re-throw\n const endTime = performance.now();\n try {\n const errorRequestBody = self.apiConfig.captureRequestBody\n ? redactBodyFields(requestBody, self.apiConfig.redactBodyFields)\n : null;\n const errorRecord: ApiCallRecord = {\n id,\n timestamp,\n method: httpMethod,\n url: stripSecretQueryParams(url),\n requestHeaders: null,\n requestBody: errorRequestBody,\n responseStatusCode: null,\n responseStatusText: null,\n responseHeaders: null,\n responseBody: null,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: false,\n error: err instanceof Error ? err.message : String(err),\n };\n if (self.apiCallWriter) {\n self.apiCallWriter.append(errorRecord);\n self.apiCallCount++;\n self.tallyStatus(null);\n }\n } catch {\n // FR-016: if recording fails, still re-throw the original exception\n }\n throw err;\n }\n\n // Success path: capture full record with config-driven capture control and redaction\n try {\n const endTime = performance.now();\n const rawResponseHeaders = response.headers();\n const contentType = rawResponseHeaders['content-type'] ?? null;\n const binary = contentType ? !isTextContentType(contentType) : false;\n\n // Capture control: headers\n // For API calls, request headers come from options.headers (user-provided).\n // Playwright's APIResponse doesn't expose the actual sent request headers,\n // so we capture what we can from the options object.\n let capturedRequestHeaders: Record<string, string> | null = null;\n if (self.apiConfig.captureRequestHeaders && options?.headers) {\n capturedRequestHeaders = options.headers as Record<string, string>;\n }\n let capturedResponseHeaders: Record<string, string> | null = null;\n if (self.apiConfig.captureResponseHeaders) {\n capturedResponseHeaders = rawResponseHeaders;\n }\n\n // Capture control: bodies\n let capturedRequestBody: string | null = self.apiConfig.captureRequestBody ? requestBody : null;\n let capturedResponseBody: string | null = null;\n if (self.apiConfig.captureResponseBody) {\n try {\n if (binary) {\n const buf = await response.body();\n capturedResponseBody = buf.toString('base64');\n } else {\n capturedResponseBody = await response.text();\n }\n } catch {\n // Body read failed — record what we have\n }\n }\n\n // Parse cookies BEFORE redaction strips the Cookie/Set-Cookie headers.\n // Values are masked, so the structured fields surface cookie names (and\n // response attributes) for the inspector without leaking session tokens.\n let requestCookies: CapturedCookie[] = [];\n let responseCookies: CapturedCookie[] = [];\n try {\n if (self.apiConfig.captureRequestHeaders) {\n requestCookies = parseRequestCookies(options?.headers as Record<string, string> | undefined);\n }\n if (self.apiConfig.captureResponseHeaders) {\n const setCookieValues = response\n .headersArray()\n .filter((h) => h.name.toLowerCase() === 'set-cookie')\n .map((h) => h.value);\n responseCookies = parseResponseCookies(setCookieValues);\n }\n } catch {\n // Cookie parsing is best-effort — never break capture.\n }\n\n // Apply redaction to captured data (FR-006: before storage)\n capturedRequestHeaders = redactHeaders(capturedRequestHeaders, self.apiConfig.redactHeaders);\n capturedResponseHeaders = redactHeaders(capturedResponseHeaders, self.apiConfig.redactHeaders);\n capturedRequestBody = redactBodyFields(capturedRequestBody, self.apiConfig.redactBodyFields);\n capturedResponseBody = redactBodyFields(capturedResponseBody, self.apiConfig.redactBodyFields);\n\n const record: ApiCallRecord = {\n id,\n timestamp,\n method: httpMethod,\n url: stripSecretQueryParams(response.url()),\n requestHeaders: capturedRequestHeaders,\n requestBody: capturedRequestBody,\n responseStatusCode: response.status(),\n responseStatusText: response.statusText(),\n responseHeaders: capturedResponseHeaders,\n responseBody: capturedResponseBody,\n requestCookies: requestCookies.length ? requestCookies : null,\n responseCookies: responseCookies.length ? responseCookies : null,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: binary,\n error: null,\n };\n if (self.apiCallWriter) {\n self.apiCallWriter.append(record);\n self.apiCallCount++;\n self.tallyStatus(record.responseStatusCode);\n }\n } catch {\n // FR-016: never crash the test — silently skip recording\n }\n\n // Tag the response with the call ID for assertion linking\n try {\n (response as unknown as Record<symbol, string>)[CALL_ID_SYMBOL] = id;\n self.tagResponseMethods(response, id);\n } catch {\n // Tagging failure is non-critical — temporal fallback will be used\n }\n\n return response;\n };\n }\n}\n","/**\n * NavigationTracker — wraps a Playwright Page to track navigation events.\n *\n * Intercepts page methods (goto, goBack, goForward, reload) and listens\n * for DOM events (framenavigated, domcontentloaded, load) plus injected\n * SPA detection scripts.\n */\n\nimport type { CapturedNetworkRequest, ConsoleLogEntry, ConsoleLogLevel, NavigationAnnotation, NavigationType, NetworkStats, ResourceBreakdown, ResourceType } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\n\n// Minimal Playwright Page type subset for loose coupling\ninterface PageLike {\n goto(url: string, options?: unknown): Promise<unknown>;\n goBack(options?: unknown): Promise<unknown>;\n goForward(options?: unknown): Promise<unknown>;\n reload(options?: unknown): Promise<unknown>;\n url(): string;\n on(event: string, handler: (...args: unknown[]) => void): void;\n off(event: string, handler: (...args: unknown[]) => void): void;\n addInitScript(script: string | { path?: string } | (() => void)): Promise<void>;\n evaluate(fn: (...args: unknown[]) => unknown, ...args: unknown[]): Promise<unknown>;\n mainFrame(): { url(): string };\n}\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\ninterface NavigationRecord {\n url: string;\n navigationType: NavigationType;\n timestamp: string;\n domContentLoadedAt?: string;\n networkIdleAt?: string;\n networkStats?: NetworkStats;\n}\n\ninterface NetworkCounter {\n totalRequests: number;\n failedRequests: number;\n failedRequestUrls: string[];\n totalBytes: number;\n byType: Record<keyof ResourceBreakdown, number>;\n}\n\ninterface PendingRequest {\n url: string;\n method: string;\n resourceType: ResourceType;\n headers: Record<string, string>;\n postData: string | null;\n postDataTruncated: boolean;\n startedAt: string;\n startTimeMs: number;\n /** Index into `records`/`navCounters` of the navigation active when this\n * request STARTED — so its response/failure stats attribute to the right\n * navigation even if they arrive after the next navigation began. */\n navIndex: number;\n}\n\nconst TEXT_CONTENT_TYPES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\nfunction isTextContentType(contentType: string): boolean {\n const lower = contentType.toLowerCase();\n return TEXT_CONTENT_TYPES.some(prefix => lower.includes(prefix));\n}\n\nexport class NavigationTracker {\n private records: NavigationRecord[] = [];\n private listeners: Array<{ event: string; handler: (...args: unknown[]) => void }> = [];\n private origGoto: PageLike['goto'];\n private origGoBack: PageLike['goBack'];\n private origGoForward: PageLike['goForward'];\n private origReload: PageLike['reload'];\n private lastDomContentLoaded: string | undefined;\n private includeNetworkStats: boolean;\n /** One counter per navigation (parallel to `records` by index). A request's\n * stats land in the counter of the nav that issued it, so a response arriving\n * after the next navigation no longer leaks into it. */\n private navCounters: NetworkCounter[] = [];\n private pendingRequests = new Map<string, PendingRequest>();\n private networkWriter: JsonlFileWriter | null = null;\n private consoleWriter: JsonlFileWriter | null = null;\n private pendingBodyReads: Promise<void>[] = [];\n private requestIdCounter = 0;\n private networkRequestCount = 0;\n private consoleLogCount = 0;\n\n constructor(private page: PageLike, options?: { includeNetworkStats?: boolean }) {\n this.includeNetworkStats = options?.includeNetworkStats ?? true;\n // Store originals\n this.origGoto = page.goto.bind(page);\n this.origGoBack = page.goBack.bind(page);\n this.origGoForward = page.goForward.bind(page);\n this.origReload = page.reload.bind(page);\n\n // Initialize JSONL writers for heavy data\n try {\n if (this.includeNetworkStats) {\n this.networkWriter = new JsonlFileWriter('network');\n }\n this.consoleWriter = new JsonlFileWriter('console');\n } catch {\n // Graceful degradation if temp dir is not writable\n }\n\n this.interceptMethods();\n this.attachListeners();\n }\n\n async init(): Promise<void> {\n await this.injectSPADetection();\n }\n\n /** Flush remaining pending requests to the JSONL writer and close it. */\n async finalizeCapturedRequests(): Promise<void> {\n // Wait for all pending body reads to complete\n await Promise.allSettled(this.pendingBodyReads);\n this.pendingBodyReads = [];\n // Convert any remaining pending requests (incomplete) to JSONL entries\n for (const [, pending] of this.pendingRequests) {\n if (this.networkWriter) {\n this.networkWriter.append({\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: 0,\n responseTimeMs: Date.now() - pending.startTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody: null,\n responseHeaders: null,\n contentType: null,\n responseSize: 0,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: false,\n error: 'incomplete',\n } satisfies CapturedNetworkRequest);\n this.networkRequestCount++;\n }\n }\n this.pendingRequests.clear();\n // Close writers so files are ready to be moved\n this.networkWriter?.close();\n this.consoleWriter?.close();\n }\n\n private static mapConsoleType(type: string): ConsoleLogLevel {\n switch (type) {\n case 'log': return 'log';\n case 'warning': return 'warn';\n case 'error': return 'error';\n case 'info': return 'info';\n case 'debug': return 'debug';\n default: return 'log';\n }\n }\n\n async getFileData(): Promise<{ navigations: NavigationAnnotation[]; networkRequestsFile: string | null; networkRequestsCount: number; consoleLogsFile: string | null; consoleLogsCount: number }> {\n // Assemble each navigation's network stats from its OWN counter, so every\n // navigation (not just the last) reflects exactly the requests it issued —\n // including responses that arrived after the next navigation started.\n this.assembleNetworkStats();\n\n const navigations: NavigationAnnotation[] = this.records.map(record => ({\n url: record.url,\n navigationType: record.navigationType,\n timestamp: record.timestamp,\n domContentLoadedAt: record.domContentLoadedAt,\n networkIdleAt: record.networkIdleAt,\n networkStats: record.networkStats,\n }));\n\n // Finalize and close JSONL writers\n if (this.includeNetworkStats) {\n await this.finalizeCapturedRequests();\n } else {\n this.consoleWriter?.close();\n }\n\n return {\n navigations,\n networkRequestsFile: this.networkWriter?.getCount() ? this.networkWriter.getPath() : null,\n networkRequestsCount: this.networkWriter?.getCount() ?? 0,\n consoleLogsFile: this.consoleWriter?.getCount() ? this.consoleWriter.getPath() : null,\n consoleLogsCount: this.consoleWriter?.getCount() ?? 0,\n };\n }\n\n /** @deprecated Legacy annotations no longer used — data flows via file-based payloads. */\n async flushLegacyAnnotations(_testInfo: TestInfoLike): Promise<void> {\n // No-op in v2 — all data flows via getFileData() and JSONL files\n }\n\n dispose(): void {\n // Restore original methods\n const patchable = NavigationTracker.asPatchable(this.page);\n patchable.goto = this.origGoto;\n patchable.goBack = this.origGoBack;\n patchable.goForward = this.origGoForward;\n patchable.reload = this.origReload;\n\n // Remove event listeners\n for (const { event, handler } of this.listeners) {\n this.page.off(event, handler);\n }\n this.listeners = [];\n this.records = [];\n this.pendingRequests.clear();\n this.pendingBodyReads = [];\n // Close writers (files kept for reporter to consume)\n this.networkWriter?.close();\n this.consoleWriter?.close();\n }\n\n getRecords(): readonly NavigationRecord[] {\n // Assemble on demand so each record reflects the latest per-nav counters\n // (late responses keep accumulating into the issuing nav's counter).\n this.assembleNetworkStats();\n return this.records;\n }\n\n /**\n * Treat the page as an indexable object so a method can be replaced.\n *\n * SAFETY: navigation capture swaps four methods on a live Playwright `Page`\n * and restores them in `dispose`. `Page` has no index signature, so the\n * write needs a widening step, and TypeScript prescribes routing a\n * conversion between non-overlapping types through `unknown`. The keys are\n * literals below, never user input.\n */\n private static asPatchable(page: PageLike): Record<string, unknown> {\n return page as unknown as Record<string, unknown>;\n }\n\n private interceptMethods(): void {\n const self = this;\n const page = this.page;\n\n const patchable = NavigationTracker.asPatchable(page);\n\n patchable.goto = async function (url: string, options?: unknown) {\n self.recordNavigation(url, 'goto');\n return self.origGoto(url, options);\n };\n\n patchable.goBack = async function (options?: unknown) {\n const result = await self.origGoBack(options);\n self.recordNavigation(page.url(), 'back');\n return result;\n };\n\n patchable.goForward = async function (options?: unknown) {\n const result = await self.origGoForward(options);\n self.recordNavigation(page.url(), 'forward');\n return result;\n };\n\n patchable.reload = async function (options?: unknown) {\n self.recordNavigation(page.url(), 'refresh');\n return self.origReload(options);\n };\n }\n\n private attachListeners(): void {\n // Track DOMContentLoaded for lifecycle timestamps\n const onDomContentLoaded = () => {\n this.lastDomContentLoaded = new Date().toISOString();\n if (this.records.length > 0) {\n this.records[this.records.length - 1].domContentLoadedAt = this.lastDomContentLoaded;\n }\n };\n this.page.on('domcontentloaded', onDomContentLoaded as (...args: unknown[]) => void);\n this.listeners.push({ event: 'domcontentloaded', handler: onDomContentLoaded as (...args: unknown[]) => void });\n\n // Track main frame navigation for non-intercepted navigations (link clicks, form submits)\n const onFrameNavigated = (frame: unknown) => {\n // Only track main frame\n try {\n const frameObj = frame as { url(): string; parentFrame(): unknown };\n if (typeof frameObj.parentFrame === 'function' && frameObj.parentFrame() !== null) {\n return; // Skip sub-frames\n }\n const url = frameObj.url();\n // Skip if we already recorded this URL in the last 50ms (from method interception)\n const lastRecord = this.records[this.records.length - 1];\n if (lastRecord) {\n const timeDiff = Date.now() - new Date(lastRecord.timestamp).getTime();\n if (timeDiff < 50 && lastRecord.url === url) {\n return; // Duplicate from method interception\n }\n }\n // This is an untracked navigation (link click, form submit, etc.)\n this.recordNavigation(url, 'navigation');\n } catch {\n // Ignore frame navigation errors\n }\n };\n this.page.on('framenavigated', onFrameNavigated);\n this.listeners.push({ event: 'framenavigated', handler: onFrameNavigated });\n\n // Track console messages: SPA detection + full console log capture\n const onConsoleMessage = (msg: unknown) => {\n try {\n const msgObj = msg as { text(): string; type(): string; location(): { url: string; lineNumber: number; columnNumber: number } };\n const type = msgObj.type();\n const text = msgObj.text();\n\n // Internal SPA navigation detection via __testrelic_nav: debug messages\n if (type === 'debug' && text.startsWith('__testrelic_nav:')) {\n try {\n const data = JSON.parse(text.slice('__testrelic_nav:'.length));\n if (data.type && data.url) {\n this.recordNavigation(data.url, data.type as NavigationType);\n }\n } catch {\n // Ignore parse errors\n }\n return;\n }\n\n // Capture all console messages\n {\n let location: string | null = null;\n try {\n const loc = msgObj.location();\n if (loc && loc.url) {\n location = `${loc.url}:${loc.lineNumber}:${loc.columnNumber}`;\n }\n } catch {\n // location() may not be available\n }\n const level = NavigationTracker.mapConsoleType(type);\n if (this.consoleWriter) {\n this.consoleWriter.append({ level, text, timestamp: new Date().toISOString(), location } satisfies ConsoleLogEntry);\n this.consoleLogCount++;\n }\n }\n } catch {\n // Ignore console capture errors\n }\n };\n this.page.on('console', onConsoleMessage);\n this.listeners.push({ event: 'console', handler: onConsoleMessage });\n\n // Network stats tracking + individual request capture\n if (this.includeNetworkStats) {\n const onRequest = (request: unknown) => {\n // Attribute this request to the navigation active right now; its response\n // may arrive after the next navigation starts, but the count belongs here.\n const navIndex = this.records.length - 1;\n const counter = this.counterForNav(navIndex);\n if (counter) {\n counter.totalRequests++;\n }\n // Capture individual request details\n try {\n const req = request as {\n url(): string;\n method(): string;\n resourceType(): string;\n headers(): Record<string, string>;\n postData(): string | null;\n };\n const reqId = String(this.requestIdCounter++);\n const postData = req.postData() ?? null;\n const pending: PendingRequest = {\n url: req.url(),\n method: req.method(),\n resourceType: this.mapResourceType(req.resourceType()),\n headers: req.headers(),\n postData,\n postDataTruncated: false,\n startedAt: new Date().toISOString(),\n startTimeMs: Date.now(),\n navIndex,\n };\n this.pendingRequests.set(reqId, pending);\n // Tag the request object with our ID for lookup in response/failed handlers\n (request as Record<string, unknown>).__testrelic_id = reqId;\n } catch {\n // Ignore request capture errors\n }\n };\n this.page.on('request', onRequest);\n this.listeners.push({ event: 'request', handler: onRequest });\n\n const onResponse = (response: unknown) => {\n try {\n const resp = response as {\n url(): string;\n status(): number;\n headers(): Record<string, string>;\n request(): { resourceType(): string; __testrelic_id?: string };\n body(): Promise<Buffer>;\n };\n // Attribute aggregate stats to the navigation that ISSUED the request\n // (falls back to the current nav when the request wasn't tracked).\n const reqId = (resp.request() as Record<string, unknown>).__testrelic_id as string | undefined;\n const pending = reqId ? this.pendingRequests.get(reqId) : undefined;\n const counter = this.counterForNav(pending ? pending.navIndex : this.records.length - 1);\n if (counter) {\n const status = resp.status();\n if (status >= 400) {\n counter.failedRequests++;\n counter.failedRequestUrls.push(status + ' ' + resp.url());\n }\n const contentLength = resp.headers()['content-length'];\n if (contentLength) {\n counter.totalBytes += parseInt(contentLength, 10) || 0;\n }\n const resourceType = resp.request().resourceType();\n const typeKey = this.mapResourceType(resourceType);\n counter.byType[typeKey]++;\n }\n // Individual request capture\n if (!reqId || !pending) return;\n this.pendingRequests.delete(reqId);\n const responseTimeMs = Date.now() - pending.startTimeMs;\n const respHeaders = resp.headers();\n const contentType = respHeaders['content-type'] ?? null;\n const responseSize = parseInt(respHeaders['content-length'] ?? '0', 10) || 0;\n const binary = contentType ? !isTextContentType(contentType) : false;\n // Read response body asynchronously and write to JSONL (not held in memory)\n const bodyPromise = (async () => {\n let responseBody: string | null = null;\n if (!binary) {\n try {\n const buf = await resp.body();\n responseBody = buf.toString('utf-8');\n } catch {\n // Body unavailable (e.g., redirect)\n }\n }\n const captured: CapturedNetworkRequest = {\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: resp.status(),\n responseTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody,\n responseHeaders: respHeaders,\n contentType,\n responseSize,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: binary,\n error: null,\n };\n // Write to JSONL file — body is released from memory after this\n if (this.networkWriter) {\n this.networkWriter.append(captured);\n this.networkRequestCount++;\n }\n })();\n this.pendingBodyReads.push(bodyPromise);\n } catch {\n // Ignore response processing errors\n }\n };\n this.page.on('response', onResponse);\n this.listeners.push({ event: 'response', handler: onResponse });\n\n const onRequestFailed = (request: unknown) => {\n const reqId = (request as Record<string, unknown>).__testrelic_id as string | undefined;\n const pending = reqId ? this.pendingRequests.get(reqId) : undefined;\n // Attribute the failure to the navigation that issued the request.\n const counter = this.counterForNav(pending ? pending.navIndex : this.records.length - 1);\n if (counter) {\n counter.failedRequests++;\n try {\n const req = request as { url(): string };\n counter.failedRequestUrls.push('ERR ' + req.url());\n } catch {\n // Ignore\n }\n }\n // Individual request capture\n try {\n const req = request as { failure(): { errorText: string } | null };\n if (!reqId || !pending) return;\n this.pendingRequests.delete(reqId);\n const captured: CapturedNetworkRequest = {\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: 0,\n responseTimeMs: Date.now() - pending.startTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody: null,\n responseHeaders: null,\n contentType: null,\n responseSize: 0,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: false,\n error: req.failure()?.errorText ?? 'Unknown error',\n };\n if (this.networkWriter) {\n this.networkWriter.append(captured);\n this.networkRequestCount++;\n }\n } catch {\n // Ignore\n }\n };\n this.page.on('requestfailed', onRequestFailed);\n this.listeners.push({ event: 'requestfailed', handler: onRequestFailed });\n }\n }\n\n private async injectSPADetection(): Promise<void> {\n try {\n await this.page.addInitScript(() => {\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = function (...args: Parameters<typeof origPush>) {\n origPush(...args);\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'spa_route',\n url: location.href,\n }));\n };\n\n history.replaceState = function (...args: Parameters<typeof origReplace>) {\n origReplace(...args);\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'spa_replace',\n url: location.href,\n }));\n };\n\n window.addEventListener('popstate', () => {\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'popstate',\n url: location.href,\n }));\n });\n\n window.addEventListener('hashchange', () => {\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'hash_change',\n url: location.href,\n }));\n });\n });\n } catch {\n // Ignore injection errors (page may be closed)\n }\n }\n\n private recordNavigation(url: string, type: NavigationType): void {\n this.records.push({\n url,\n navigationType: type,\n timestamp: new Date().toISOString(),\n });\n\n // Each navigation gets its own counter at the same index as its record, so\n // late-arriving responses/failures attribute to the nav that issued the\n // request (via PendingRequest.navIndex) instead of leaking to the next nav.\n if (this.includeNetworkStats) {\n this.navCounters.push(this.createNetworkCounter());\n }\n }\n\n private createNetworkCounter(): NetworkCounter {\n return {\n totalRequests: 0,\n failedRequests: 0,\n failedRequestUrls: [],\n totalBytes: 0,\n byType: { xhr: 0, document: 0, script: 0, stylesheet: 0, image: 0, font: 0, other: 0 },\n };\n }\n\n /** The per-navigation counter for a record index, or undefined when the index\n * predates the first navigation (-1) or is out of range. */\n private counterForNav(index: number): NetworkCounter | undefined {\n return index >= 0 ? this.navCounters[index] : undefined;\n }\n\n /** Snapshot each navigation's accumulated counter into record.networkStats.\n * Idempotent — safe to call repeatedly (getRecords / getFileData). */\n private assembleNetworkStats(): void {\n if (!this.includeNetworkStats) return;\n for (let i = 0; i < this.records.length; i++) {\n const counter = this.navCounters[i];\n if (!counter) continue;\n this.records[i].networkStats = {\n totalRequests: counter.totalRequests,\n failedRequests: counter.failedRequests,\n failedRequestUrls: [...counter.failedRequestUrls],\n totalBytes: counter.totalBytes,\n byType: { ...counter.byType },\n };\n }\n }\n\n private mapResourceType(type: string): keyof ResourceBreakdown {\n switch (type) {\n case 'xhr':\n case 'fetch':\n return 'xhr';\n case 'document':\n return 'document';\n case 'script':\n return 'script';\n case 'stylesheet':\n return 'stylesheet';\n case 'image':\n return 'image';\n case 'font':\n return 'font';\n default:\n return 'other';\n }\n }\n}\n","/**\n * Numbers for a visual comparison, recovered without decoding an image.\n *\n * Playwright compares images internally and keeps the result to itself: the\n * differing-pixel count exists only as prose inside the failure message, and\n * the image dimensions only inside the PNG. Rather than decode a PNG (which\n * would mean a dependency, and Principle V says no), both are read back — the\n * count from the message Playwright already formats, the dimensions from the\n * 8 bytes of header every PNG carries.\n */\n\n/** PNG magic number: \\x89 P N G \\r \\n \\x1a \\n */\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\n/** Byte offsets of width and height inside the IHDR chunk of a PNG. */\nconst IHDR_WIDTH_OFFSET = 16;\nconst IHDR_HEIGHT_OFFSET = 20;\n\n/** Enough bytes to cover the signature, the chunk header and both dimensions. */\nexport const PNG_HEADER_BYTES = 24;\n\nexport interface ImageDimensions {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Read a PNG's pixel dimensions from its IHDR chunk.\n *\n * Only the first 24 bytes are consulted, so the caller can hand over a partial\n * read of a large file. Returns `null` for anything that is not a PNG — JPEG\n * baselines are legal in Playwright and simply go un-measured.\n */\nexport function readPngDimensions(header: Buffer): ImageDimensions | null {\n if (header.length < PNG_HEADER_BYTES) return null;\n if (!header.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return null;\n\n const width = header.readUInt32BE(IHDR_WIDTH_OFFSET);\n const height = header.readUInt32BE(IHDR_HEIGHT_OFFSET);\n\n // A zero dimension is not a valid PNG, and would make a ratio divide by zero.\n if (width === 0 || height === 0) return null;\n\n return { width, height };\n}\n\nexport interface ParsedDiffMessage {\n /** Differing pixel count, from `\"N pixels (ratio …) are different.\"` */\n readonly diffPixels?: number;\n /** Baseline dimensions, present only when the two images differ in size. */\n readonly expectedWidth?: number;\n readonly expectedHeight?: number;\n /** Actual dimensions, present only when the two images differ in size. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The snapshot name Playwright echoed, when the assertion supplied one. */\n readonly snapshotName?: string;\n}\n\n/**\n * `123 pixels (ratio 0.02 of all image pixels) are different.`\n *\n * The ratio in that sentence is rounded up to two decimals, so it is read past\n * deliberately — the caller recomputes it from the count and the dimensions.\n */\nconst PIXELS_DIFFERENT_RE = /(\\d+) pixels \\(ratio [\\d.]+ of all image pixels\\) are different/;\n\n/** `Expected an image 800px by 600px, received 800px by 640px.` */\nconst SIZE_MISMATCH_RE = /Expected an image (\\d+)px by (\\d+)px, received (\\d+)px by (\\d+)px/;\n\n/**\n * Whether a failure message is a size mismatch.\n *\n * The comparator cannot draw a diff across differing dimensions, so it\n * attaches expected + actual and throws instead. Callers that would otherwise\n * infer \"no baseline existed\" from a missing diff need this to tell the two\n * apart — without it a real failure was reported as a freshly written\n * baseline.\n */\nexport function isSizeMismatchFailure(message: string | null | undefined): boolean {\n if (!message) return false;\n return SIZE_MISMATCH_RE.test(message);\n}\n\n/** ` Snapshot: home.png` — only emitted when the assertion named the snapshot. */\nconst SNAPSHOT_NAME_RE = /^\\s*Snapshot:\\s*(.+?)\\s*$/m;\n\n/**\n * Pull the comparison numbers out of a Playwright snapshot failure message.\n *\n * Every field is optional and a malformed message yields an empty object: a\n * report that is missing a pixel count is a far better outcome than a reporter\n * that throws while assembling one.\n */\nexport function parseDiffMessage(message: string | null | undefined): ParsedDiffMessage {\n if (!message) return {};\n\n const result: {\n diffPixels?: number;\n expectedWidth?: number;\n expectedHeight?: number;\n actualWidth?: number;\n actualHeight?: number;\n snapshotName?: string;\n } = {};\n\n const pixels = PIXELS_DIFFERENT_RE.exec(message);\n if (pixels) {\n const count = Number.parseInt(pixels[1] as string, 10);\n if (Number.isFinite(count)) result.diffPixels = count;\n }\n\n const sizes = SIZE_MISMATCH_RE.exec(message);\n if (sizes) {\n const [, ew, eh, aw, ah] = sizes;\n const parsed = [ew, eh, aw, ah].map((v) => Number.parseInt(v as string, 10));\n if (parsed.every((v) => Number.isFinite(v))) {\n result.expectedWidth = parsed[0];\n result.expectedHeight = parsed[1];\n result.actualWidth = parsed[2];\n result.actualHeight = parsed[3];\n }\n }\n\n const name = SNAPSHOT_NAME_RE.exec(message);\n if (name) result.snapshotName = name[1];\n\n return result;\n}\n\n/**\n * Differing pixels as a fraction of the image, at full precision.\n *\n * Returns `undefined` rather than a wrong number when either input is missing,\n * so a consumer can tell \"no measurement\" apart from \"measured zero\".\n */\nexport function computeDiffRatio(\n diffPixels: number | undefined,\n dimensions: ImageDimensions | null | undefined,\n): number | undefined {\n if (diffPixels === undefined || !dimensions) return undefined;\n const total = dimensions.width * dimensions.height;\n if (total <= 0) return undefined;\n return diffPixels / total;\n}\n","/// <reference lib=\"dom\" />\n//\n// This one file runs in a browser, so it needs the DOM lib. The package's\n// tsconfig sets `lib: [\"ES2020\"]` on purpose — a reporter that runs in Node has\n// no business reaching for `document` — so the exception is scoped here by a\n// per-file reference rather than opened up package-wide.\n\n/**\n * The DOM walker that runs inside the page.\n *\n * Playwright's own trace snapshotter deliberately captures neither computed\n * styles nor per-element geometry — it records HTML and replays real CSS\n * instead — so it cannot answer \"which element moved, and why\". It is also\n * unreachable: `playwright-core`'s `exports` map does not expose the\n * snapshotter, and it operates on server-side objects a test never holds.\n * Hence this small walker of our own, which captures exactly the two things\n * that are missing.\n *\n * ⛔ `captureDomSnapshot` is handed to `page.evaluate`, which serializes it via\n * `Function.prototype.toString`. It MUST therefore be entirely self-contained:\n * no imports, no references to module scope, no closure captures. Everything it\n * needs arrives in its single argument. A stray outer reference does not fail\n * at build time — it throws inside the browser at runtime.\n */\n\n/** A single captured element. Field names are short: this is written per node. */\nexport interface DomNode {\n /** Stable structural path, id-anchored. `#main>div[0]>span[2]` */\n p: string;\n /** Tag name, lowercase. */\n t: string;\n i?: string;\n c?: string;\n /** Document-space `[x, y, width, height]`, rounded. */\n r: [number, number, number, number];\n /** Direct text content, normalized and truncated. */\n x?: string;\n /** Computed styles, with inherited and default values omitted. */\n s?: Record<string, string>;\n /** Set when the element intersects a mask and is excluded from diffing. */\n m?: 1;\n}\n\nexport interface DomSnapshot {\n readonly version: '1.0';\n readonly url: string;\n readonly viewport: { width: number; height: number };\n readonly content: { width: number; height: number };\n /** The document-space region the screenshot covered. */\n readonly captureBox: [number, number, number, number];\n readonly nodes: DomNode[];\n /** Elements the cap excluded. Counted, never silently dropped. */\n readonly truncatedNodes: number;\n}\n\nexport interface CaptureOptions {\n readonly styleProps: readonly string[];\n /** Property → value that is so common it is not worth storing. */\n readonly defaults: Readonly<Record<string, string>>;\n /** Properties that inherit, stored only when they differ from the parent. */\n readonly inherited: readonly string[];\n readonly maxNodes: number;\n readonly maxText: number;\n /**\n * Boxes to mark as masked, in VIEWPORT coordinates — that is what\n * `locator.boundingBox()` returns on the Node side. Converted to document\n * space in here, where the scroll offset is known.\n */\n readonly maskBoxes: readonly [number, number, number, number][];\n /**\n * The region the screenshot covers, in viewport coordinates, when asserting\n * against a locator rather than the page. Null lets the walker derive it.\n */\n readonly viewportBox: [number, number, number, number] | null;\n /** True when the screenshot was taken with `fullPage`. */\n readonly fullPage: boolean;\n}\n\n/**\n * Walk the rendered DOM and return a flat, diffable snapshot.\n *\n * Flat rather than nested because the path is the match key: diffing two flat\n * arrays keyed by path is a map lookup, where diffing two trees is an edit-\n * distance problem.\n */\nexport function captureDomSnapshot(options: CaptureOptions): DomSnapshot {\n const { styleProps, defaults, inherited, maxNodes, maxText, maskBoxes, viewportBox, fullPage } =\n options;\n\n const inheritedSet: Record<string, true> = {};\n for (let i = 0; i < inherited.length; i++) inheritedSet[inherited[i] as string] = true;\n\n const scrollX = window.scrollX || window.pageXOffset || 0;\n const scrollY = window.scrollY || window.pageYOffset || 0;\n\n const nodes: DomNode[] = [];\n let truncatedNodes = 0;\n\n const docMasks: [number, number, number, number][] = [];\n for (let i = 0; i < maskBoxes.length; i++) {\n const mask = maskBoxes[i] as [number, number, number, number];\n docMasks.push([mask[0] + scrollX, mask[1] + scrollY, mask[2], mask[3]]);\n }\n\n /** Does this element's box overlap anything the user masked? */\n function isMasked(x: number, y: number, w: number, h: number): boolean {\n for (let i = 0; i < docMasks.length; i++) {\n const b = docMasks[i] as [number, number, number, number];\n if (x < b[0] + b[2] && x + w > b[0] && y < b[1] + b[3] && y + h > b[1]) return true;\n }\n return false;\n }\n\n /** Index among same-tag siblings — more stable than nth-child when text moves. */\n function indexAmongSiblings(el: Element): number {\n const parent = el.parentElement;\n if (!parent) return 0;\n const siblings = parent.children;\n let index = 0;\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i] as Element;\n if (sibling === el) break;\n if (sibling.tagName === el.tagName) index++;\n }\n return index;\n }\n\n /** Direct text children only — a parent must not inherit its child's text. */\n function directText(el: Element): string {\n let text = '';\n const children = el.childNodes;\n for (let i = 0; i < children.length; i++) {\n const node = children[i] as Node;\n if (node.nodeType === 3) text += node.nodeValue ?? '';\n }\n text = text.replace(/\\s+/g, ' ').trim();\n return text.length > maxText ? text.slice(0, maxText) : text;\n }\n\n /**\n * Store only what is surprising.\n *\n * An inherited property is recorded only where it differs from the parent, so\n * changing the body font produces one change on `body` rather than the same\n * change echoed on all six hundred of its descendants. A non-inherited\n * property is recorded only where it differs from its initial value, which is\n * what keeps a typical element down to three or four entries instead of\n * thirty.\n */\n function collectStyles(\n computed: CSSStyleDeclaration,\n parentComputed: CSSStyleDeclaration | null,\n ): Record<string, string> | undefined {\n let styles: Record<string, string> | undefined;\n for (let i = 0; i < styleProps.length; i++) {\n const prop = styleProps[i] as string;\n const value = computed.getPropertyValue(prop);\n if (!value) continue;\n if (inheritedSet[prop]) {\n if (parentComputed && parentComputed.getPropertyValue(prop) === value) continue;\n } else if (defaults[prop] === value) {\n continue;\n }\n if (!styles) styles = {};\n styles[prop] = value;\n }\n return styles;\n }\n\n interface Frame {\n el: Element;\n parentPath: string;\n parentComputed: CSSStyleDeclaration | null;\n }\n\n // Explicit stack rather than recursion: a deep page should not risk blowing\n // the call stack inside someone else's test run.\n const stack: Frame[] = [{ el: document.body, parentPath: '', parentComputed: null }];\n\n while (stack.length > 0) {\n const frame = stack.pop() as Frame;\n const el = frame.el;\n const computed = window.getComputedStyle(el);\n\n // display:none removes the whole subtree from rendering, so nothing beneath\n // it can have caused a pixel difference.\n if (computed.display === 'none') continue;\n\n const tag = el.tagName.toLowerCase();\n const path = el.id\n ? '#' + el.id\n : frame.parentPath\n ? frame.parentPath + '>' + tag + '[' + indexAmongSiblings(el) + ']'\n : tag;\n\n // Children are pushed regardless of whether this element is recorded: an\n // invisible or zero-sized wrapper routinely contains visible children.\n const children = el.children;\n for (let i = children.length - 1; i >= 0; i--) {\n stack.push({\n el: children[i] as Element,\n parentPath: path,\n parentComputed: computed,\n });\n }\n\n if (computed.visibility === 'hidden') continue;\n\n const box = el.getBoundingClientRect();\n if (box.width <= 0 || box.height <= 0) continue;\n\n if (nodes.length >= maxNodes) {\n truncatedNodes++;\n continue;\n }\n\n const x = Math.round(box.left + scrollX);\n const y = Math.round(box.top + scrollY);\n const w = Math.round(box.width);\n const h = Math.round(box.height);\n\n const node: DomNode = { p: path, t: tag, r: [x, y, w, h] };\n if (el.id) node.i = el.id;\n const className = el.getAttribute('class');\n if (className) node.c = className.replace(/\\s+/g, ' ').trim();\n\n const text = directText(el);\n if (text) node.x = text;\n\n const styles = collectStyles(computed, frame.parentComputed);\n if (styles) node.s = styles;\n\n if (isMasked(x, y, w, h)) node.m = 1;\n\n nodes.push(node);\n }\n\n const contentWidth = Math.max(document.documentElement.scrollWidth, window.innerWidth);\n const contentHeight = Math.max(document.documentElement.scrollHeight, window.innerHeight);\n\n // Which region of the document the screenshot actually shows. The report\n // positions its boxes as percentages of this, so a locator screenshot lines\n // up against the element it framed rather than against the whole viewport.\n let captureBox: [number, number, number, number];\n if (viewportBox) {\n captureBox = [\n Math.round(viewportBox[0] + scrollX),\n Math.round(viewportBox[1] + scrollY),\n Math.round(viewportBox[2]),\n Math.round(viewportBox[3]),\n ];\n } else if (fullPage) {\n captureBox = [0, 0, contentWidth, contentHeight];\n } else {\n captureBox = [Math.round(scrollX), Math.round(scrollY), window.innerWidth, window.innerHeight];\n }\n\n return {\n version: '1.0',\n url: location.href,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n content: { width: contentWidth, height: contentHeight },\n captureBox,\n nodes,\n truncatedNodes,\n };\n}\n","/**\n * Node-side orchestration for DOM snapshots: what to capture, how much of it,\n * and where the baseline lives.\n *\n * The walker itself is `dom-capture.ts` and runs in the browser. This module\n * never touches the DOM; it decides the policy and owns the files.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { captureDomSnapshot, type DomSnapshot, type CaptureOptions } from './dom-capture.js';\n\n/**\n * Computed properties worth recording.\n *\n * Curated rather than exhaustive: every property here can change what a\n * screenshot looks like, and nothing here is a derived duplicate of something\n * else in the list. Margin and padding are kept as longhands so a report can\n * say `margin-top 0px → 24px` instead of the far less useful\n * `margin \"0px\" → \"24px 0px 0px\"`.\n */\nexport const STYLE_PROPS: readonly string[] = [\n 'display',\n 'position',\n 'color',\n 'background-color',\n 'background-image',\n 'border-top-width',\n 'border-right-width',\n 'border-bottom-width',\n 'border-left-width',\n 'border-color',\n 'border-style',\n 'border-radius',\n 'box-shadow',\n 'opacity',\n 'transform',\n 'margin-top',\n 'margin-right',\n 'margin-bottom',\n 'margin-left',\n 'padding-top',\n 'padding-right',\n 'padding-bottom',\n 'padding-left',\n 'font-family',\n 'font-size',\n 'font-weight',\n 'line-height',\n 'letter-spacing',\n 'text-align',\n 'text-decoration-line',\n 'text-transform',\n 'flex-direction',\n 'justify-content',\n 'align-items',\n 'gap',\n 'z-index',\n 'overflow',\n];\n\n/**\n * Initial values, omitted from the snapshot where they match.\n *\n * This is what keeps snapshots small — most elements are at the initial value\n * for most of these, so a typical node stores three or four entries instead of\n * thirty-seven. `display` is deliberately absent: its initial value depends on\n * the tag, and it is high-signal enough to always be worth the bytes.\n */\nexport const STYLE_DEFAULTS: Readonly<Record<string, string>> = {\n position: 'static',\n 'background-color': 'rgba(0, 0, 0, 0)',\n 'background-image': 'none',\n 'border-top-width': '0px',\n 'border-right-width': '0px',\n 'border-bottom-width': '0px',\n 'border-left-width': '0px',\n 'border-style': 'none',\n 'border-radius': '0px',\n 'box-shadow': 'none',\n opacity: '1',\n transform: 'none',\n 'margin-top': '0px',\n 'margin-right': '0px',\n 'margin-bottom': '0px',\n 'margin-left': '0px',\n 'padding-top': '0px',\n 'padding-right': '0px',\n 'padding-bottom': '0px',\n 'padding-left': '0px',\n 'text-decoration-line': 'none',\n 'flex-direction': 'row',\n 'justify-content': 'normal',\n 'align-items': 'normal',\n gap: 'normal',\n 'z-index': 'auto',\n overflow: 'visible',\n};\n\n/**\n * Properties that inherit, recorded only where they differ from the parent.\n *\n * Without this, restyling the body font would report an identical change on\n * every one of its descendants — technically true and completely unreadable.\n * With it, the change appears once, on the element that actually caused it.\n */\nexport const INHERITED_PROPS: readonly string[] = [\n 'color',\n 'font-family',\n 'font-size',\n 'font-weight',\n 'line-height',\n 'letter-spacing',\n 'text-align',\n 'text-transform',\n];\n\n/** Matches `maestro-analytics`' body cap — the house default for captured payloads. */\nexport const DEFAULT_DOM_MAX_BYTES = 1024 * 1024;\nexport const DEFAULT_DOM_MAX_NODES = 1500;\nexport const DEFAULT_DOM_MAX_TEXT = 200;\n\nexport interface DomCaptureConfig {\n readonly maxNodes?: number;\n readonly maxBytes?: number;\n readonly maxText?: number;\n}\n\n/** The subset of a Playwright Page or Locator this module needs. */\nexport interface EvaluateTarget {\n evaluate<R, A>(fn: (arg: A) => R, arg: A): Promise<R>;\n}\n\n/** A Locator, for the mask and capture-box cases. */\nexport interface BoundedTarget {\n boundingBox(): Promise<{ x: number; y: number; width: number; height: number } | null>;\n}\n\nexport interface CaptureRequest {\n /** Page or Locator — whatever the assertion was made against. */\n readonly target: EvaluateTarget;\n /** Set when asserting against a locator, so the capture box can be framed. */\n readonly bounded?: BoundedTarget | null;\n readonly maskLocators?: readonly BoundedTarget[];\n readonly fullPage?: boolean;\n readonly config?: DomCaptureConfig;\n}\n\n/** Resolve mask locators to boxes, skipping any that are not currently laid out. */\nasync function resolveMaskBoxes(\n locators: readonly BoundedTarget[],\n): Promise<[number, number, number, number][]> {\n const boxes: [number, number, number, number][] = [];\n for (const locator of locators) {\n try {\n const box = await locator.boundingBox();\n if (box) boxes.push([box.x, box.y, box.width, box.height]);\n } catch {\n // A mask that cannot be resolved just does not mask anything.\n }\n }\n return boxes;\n}\n\n/**\n * Capture a DOM snapshot of whatever the assertion is about to compare.\n *\n * Returns null rather than throwing on any failure. A visual assertion must\n * behave identically whether or not DOM capture worked.\n */\nexport async function captureDom(request: CaptureRequest): Promise<DomSnapshot | null> {\n const config = request.config ?? {};\n try {\n const maskBoxes = request.maskLocators?.length\n ? await resolveMaskBoxes(request.maskLocators)\n : [];\n\n let viewportBox: [number, number, number, number] | null = null;\n if (request.bounded) {\n const box = await request.bounded.boundingBox();\n if (box) viewportBox = [box.x, box.y, box.width, box.height];\n }\n\n const options: CaptureOptions = {\n styleProps: STYLE_PROPS,\n defaults: STYLE_DEFAULTS,\n inherited: INHERITED_PROPS,\n maxNodes: config.maxNodes ?? DEFAULT_DOM_MAX_NODES,\n maxText: config.maxText ?? DEFAULT_DOM_MAX_TEXT,\n maskBoxes,\n viewportBox,\n fullPage: request.fullPage === true,\n };\n\n return await request.target.evaluate(captureDomSnapshot, options);\n } catch {\n return null;\n }\n}\n\nexport interface SerializedSnapshot {\n readonly json: string;\n readonly truncatedBytes: boolean;\n}\n\n/**\n * Serialize a snapshot, dropping nodes from the end until it fits.\n *\n * Nodes are in document order, so trimming from the end keeps the top of the\n * page — the part a screenshot is most likely to be about. The caller surfaces\n * `truncatedBytes` as a visible badge; silently shipping a partial snapshot\n * would make the diff lie about what was compared.\n */\nexport function serializeSnapshot(\n snapshot: DomSnapshot,\n maxBytes: number = DEFAULT_DOM_MAX_BYTES,\n): SerializedSnapshot {\n let json = JSON.stringify(snapshot);\n if (Buffer.byteLength(json, 'utf-8') <= maxBytes) {\n return { json, truncatedBytes: false };\n }\n\n const nodes = snapshot.nodes.slice();\n let dropped = 0;\n while (nodes.length > 0 && Buffer.byteLength(json, 'utf-8') > maxBytes) {\n // Drop in blocks: re-serializing once per node is quadratic on a big page.\n const block = Math.max(1, Math.floor(nodes.length * 0.1));\n nodes.splice(-block, block);\n dropped += block;\n json = JSON.stringify({\n ...snapshot,\n nodes,\n truncatedNodes: snapshot.truncatedNodes + dropped,\n });\n }\n\n return { json, truncatedBytes: true };\n}\n\n/** The DOM baseline sits beside the image baseline: `home.png` → `home.dom.json`. */\nexport function domBaselinePath(screenshotPath: string): string {\n return screenshotPath.replace(/\\.[A-Za-z0-9]+$/, '') + '.dom.json';\n}\n\n/** Read a committed DOM baseline. Returns null when there is not one yet. */\nexport function readDomBaseline(path: string): DomSnapshot | null {\n try {\n if (!existsSync(path)) return null;\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf-8'));\n return isDomSnapshot(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/** Write a DOM baseline, creating its directory if Playwright has not yet. */\nexport function writeDomBaseline(path: string, json: string): boolean {\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, json, 'utf-8');\n return true;\n } catch {\n return false;\n }\n}\n\n// SAFETY: read back from a file a previous run wrote, which a human may have\n// since edited or a merge may have mangled. Shape-checked before use.\nexport function isDomSnapshot(value: unknown): value is DomSnapshot {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.version === 'string' &&\n Array.isArray(v.nodes) &&\n Array.isArray(v.captureBox) &&\n (v.captureBox as unknown[]).length === 4\n );\n}\n","/**\n * Diff two DOM snapshots into a ranked list of changed elements.\n *\n * Pure: no filesystem, no Playwright, no browser. Everything the diff needs is\n * in the two snapshots, which is what makes the interesting parts — path\n * matching, moved-versus-replaced, noise suppression — directly testable.\n */\n\nimport type { DomChange, DomChangeKind, DomStyleChange } from '@testrelic/core';\nimport type { DomNode, DomSnapshot } from './dom-capture.js';\n\n/** Sub-pixel jitter is not a layout change; browsers round differently run to run. */\nconst RECT_TOLERANCE_PX = 1;\n\n/** Beyond this the list stops being readable; the remainder is reported as a count. */\nexport const DEFAULT_MAX_CHANGES = 50;\n\nexport interface DomDiffOptions {\n readonly maxChanges?: number;\n /** Turn off individual categories for pages where they are pure noise. */\n readonly includeText?: boolean;\n readonly includeStyles?: boolean;\n}\n\nexport interface DomDiffResult {\n readonly changes: DomChange[];\n /** Total found, which may exceed `changes.length`. */\n readonly total: number;\n /** Of the total, how many actually changed rather than merely shifted. */\n readonly rootCauses: number;\n}\n\n/** Movement alone is something that happened TO an element, not something it did. */\nconst MOVEMENT_ONLY: ReadonlySet<DomChangeKind> = new Set<DomChangeKind>(['moved', 'resized']);\n\n/**\n * Did this element change, or was it just pushed around?\n *\n * Anything restyled, retexted, added or removed changed on its own account.\n * An element that only moved or resized did not — in a layout, one element\n * growing by 24px relocates everything after it, and reporting all of those as\n * peers of the edit that caused them buries the actual finding.\n */\nfunction isRootCause(kinds: readonly DomChangeKind[]): boolean {\n return kinds.some((kind) => !MOVEMENT_ONLY.has(kind));\n}\n\n/** `div.card.accent` — what the report shows, built from what the node carries. */\nfunction labelOf(node: DomNode): string {\n let label = node.t;\n if (node.i) label += '#' + node.i;\n if (node.c) {\n const classes = node.c.split(' ').filter(Boolean).slice(0, 3);\n if (classes.length > 0) label += '.' + classes.join('.');\n }\n return label;\n}\n\nfunction movedOrResized(a: DomNode, b: DomNode): DomChangeKind[] {\n const kinds: DomChangeKind[] = [];\n const dx = Math.abs(a.r[0] - b.r[0]);\n const dy = Math.abs(a.r[1] - b.r[1]);\n const dw = Math.abs(a.r[2] - b.r[2]);\n const dh = Math.abs(a.r[3] - b.r[3]);\n if (dx > RECT_TOLERANCE_PX || dy > RECT_TOLERANCE_PX) kinds.push('moved');\n if (dw > RECT_TOLERANCE_PX || dh > RECT_TOLERANCE_PX) kinds.push('resized');\n return kinds;\n}\n\nfunction diffStyles(a: DomNode, b: DomNode): DomStyleChange[] {\n const from = a.s ?? {};\n const to = b.s ?? {};\n const props = new Set<string>([...Object.keys(from), ...Object.keys(to)]);\n const changes: DomStyleChange[] = [];\n for (const prop of props) {\n const before = from[prop];\n const after = to[prop];\n if (before === after) continue;\n // A property present on one side and absent on the other is at its initial\n // value there — say so rather than printing an empty string.\n changes.push({\n prop,\n from: before ?? '(initial)',\n to: after ?? '(initial)',\n });\n }\n changes.sort((x, y) => x.prop.localeCompare(y.prop));\n return changes;\n}\n\n/**\n * Secondary match key, for elements whose path changed.\n *\n * An element that moved in the tree would otherwise read as one removal plus\n * one addition, which is both wrong and twice as noisy. An id is unique enough\n * to match on alone; failing that, tag plus class list is a reasonable guess\n * and a wrong guess only downgrades a moved element back to removed + added.\n */\nfunction identityKey(node: DomNode): string | null {\n // The tag is part of the key for BOTH forms. Without it, a node whose tag\n // changed — deliberately left unmatched by the path pass so it reports as\n // removed + added rather than as an edit — was re-paired here on its id\n // alone, and a <div id=\"hero\"> that became a <section id=\"hero\"> came back\n // as a restyle. The class key already included the tag; the id key did not.\n if (node.i) return 'i:' + node.t + '#' + node.i;\n if (node.c) return 'c:' + node.t + '.' + node.c;\n return null;\n}\n\ninterface Pairing {\n readonly pairs: Array<{ before: DomNode; after: DomNode }>;\n readonly removed: DomNode[];\n readonly added: DomNode[];\n}\n\n/** Match nodes across the two snapshots: first by path, then by identity. */\nfunction pairNodes(baseline: readonly DomNode[], actual: readonly DomNode[]): Pairing {\n const byPath = new Map<string, DomNode>();\n for (const node of baseline) byPath.set(node.p, node);\n\n const pairs: Array<{ before: DomNode; after: DomNode }> = [];\n const unmatchedActual: DomNode[] = [];\n const matchedBaseline = new Set<DomNode>();\n\n for (const node of actual) {\n const before = byPath.get(node.p);\n // Same position but a different tag is a replacement, not an edit: a\n // <div> that became a <section> is not the same element wearing new\n // clothes, and reporting it as one would hide the swap.\n if (before && before.t !== node.t) {\n unmatchedActual.push(node);\n continue;\n }\n if (before && !matchedBaseline.has(before)) {\n matchedBaseline.add(before);\n pairs.push({ before, after: node });\n } else {\n unmatchedActual.push(node);\n }\n }\n\n const unmatchedBaseline = baseline.filter((node) => !matchedBaseline.has(node));\n\n // Second pass over what is left, by identity rather than position.\n const byIdentity = new Map<string, DomNode[]>();\n for (const node of unmatchedBaseline) {\n const key = identityKey(node);\n if (!key) continue;\n const bucket = byIdentity.get(key);\n if (bucket) bucket.push(node);\n else byIdentity.set(key, [node]);\n }\n\n const added: DomNode[] = [];\n const claimed = new Set<DomNode>();\n for (const node of unmatchedActual) {\n const key = identityKey(node);\n const bucket = key ? byIdentity.get(key) : undefined;\n const before = bucket?.find((candidate) => !claimed.has(candidate));\n if (before) {\n claimed.add(before);\n pairs.push({ before, after: node });\n } else {\n added.push(node);\n }\n }\n\n const removed = unmatchedBaseline.filter((node) => !claimed.has(node));\n return { pairs, removed, added };\n}\n\n/**\n * Identity attributes that differ.\n *\n * A class list changing from `card` to `banner` is usually the actual edit,\n * and every style difference underneath it is a consequence. Without this the\n * report shows the consequences and stays silent about the cause.\n */\nfunction diffAttrs(a: DomNode, b: DomNode): DomStyleChange[] {\n const changes: DomStyleChange[] = [];\n if ((a.i ?? '') !== (b.i ?? '')) {\n changes.push({ prop: 'id', from: a.i ?? '(none)', to: b.i ?? '(none)' });\n }\n if ((a.c ?? '') !== (b.c ?? '')) {\n changes.push({ prop: 'class', from: a.c ?? '(none)', to: b.c ?? '(none)' });\n }\n return changes;\n}\n\n/** Share of the captured region an element covers, clamped to 0–1. */\nfunction areaShare(node: DomNode, captureBox: readonly number[]): number {\n const total = (captureBox[2] ?? 0) * (captureBox[3] ?? 0);\n if (total <= 0) return 0;\n return Math.min(1, (node.r[2] * node.r[3]) / total);\n}\n\n/**\n * Compare two snapshots.\n *\n * Masked elements are dropped on both sides before anything else happens: the\n * user already told us that region is not part of the comparison, and honouring\n * that here is what keeps the text category usable on a page with a clock on it.\n */\nexport function diffDomSnapshots(\n baseline: DomSnapshot,\n actual: DomSnapshot,\n options: DomDiffOptions = {},\n): DomDiffResult {\n const maxChanges = options.maxChanges ?? DEFAULT_MAX_CHANGES;\n const includeText = options.includeText !== false;\n const includeStyles = options.includeStyles !== false;\n\n const baseNodes = baseline.nodes.filter((node) => node.m !== 1);\n const actualNodes = actual.nodes.filter((node) => node.m !== 1);\n\n const { pairs, removed, added } = pairNodes(baseNodes, actualNodes);\n\n interface Scored {\n change: Omit<DomChange, 'rank'>;\n score: number;\n }\n const scored: Scored[] = [];\n\n for (const { before, after } of pairs) {\n const kinds = movedOrResized(before, after);\n const styles = includeStyles ? diffStyles(before, after) : [];\n if (styles.length > 0) kinds.push('restyled');\n\n const attrs = diffAttrs(before, after);\n if (attrs.length > 0) kinds.push('attrs');\n\n const textChanged = includeText && (before.x ?? '') !== (after.x ?? '');\n if (textChanged) kinds.push('text');\n\n if (kinds.length === 0) continue;\n\n const share = areaShare(after, actual.captureBox);\n scored.push({\n score: share,\n change: {\n path: after.p,\n label: labelOf(after),\n kinds,\n rect: after.r,\n baselineRect: before.r,\n areaPct: share,\n ...(attrs.length > 0 ? { attrs } : {}),\n ...(styles.length > 0 ? { styles } : {}),\n ...(textChanged ? { text: { from: before.x ?? '', to: after.x ?? '' } } : {}),\n },\n });\n }\n\n for (const node of added) {\n const share = areaShare(node, actual.captureBox);\n scored.push({\n score: share,\n change: {\n path: node.p,\n label: labelOf(node),\n kinds: ['added'],\n rect: node.r,\n areaPct: share,\n },\n });\n }\n\n for (const node of removed) {\n const share = areaShare(node, baseline.captureBox);\n scored.push({\n score: share,\n change: {\n path: node.p,\n label: labelOf(node),\n kinds: ['removed'],\n baselineRect: node.r,\n areaPct: share,\n },\n });\n }\n\n const rootCauses = scored.filter((entry) => isRootCause(entry.change.kinds)).length;\n\n // Root causes first, then movement, each by area. A reviewer wants the\n // element that was restyled, not the largest of the eleven things it shoved.\n //\n // When EVERYTHING only moved — a whole page nudged by a scrollbar, say —\n // nothing is demoted, because then the movement is the finding.\n scored.sort((a, b) => {\n if (rootCauses > 0) {\n const aRoot = isRootCause(a.change.kinds) ? 1 : 0;\n const bRoot = isRootCause(b.change.kinds) ? 1 : 0;\n if (aRoot !== bRoot) return bRoot - aRoot;\n }\n return b.score - a.score;\n });\n\n const changes: DomChange[] = scored.slice(0, maxChanges).map((entry, index) => ({\n rank: index + 1,\n ...entry.change,\n ...(rootCauses > 0 && !isRootCause(entry.change.kinds) ? { consequence: true } : {}),\n }));\n\n return { changes, total: scored.length, rootCauses };\n}\n","/**\n * Whether this Playwright supports `snapshotPath(name, { kind })`.\n *\n * The options form arrived in Playwright 1.51. Before that `snapshotPath` took\n * variadic path segments, so an options object is consumed as a segment and\n * the resolved path is wrong — and because both call sites sit inside a\n * `try`/`catch` that swallows everything, DOM comparison and the\n * passing-baseline attachment simply never worked and said nothing about it.\n *\n * The package's peer range is `>=1.35.0` and stays that way deliberately:\n * everything except these two visual extras works on the older versions, and\n * raising the floor would lock those users out of the whole reporter over one\n * feature. So the loss is detected and *announced* once, instead of being\n * silent.\n *\n * The check is a feature probe rather than a version comparison. Reading\n * `@playwright/test/package.json` meant resolving a module at runtime from a\n * file that has to stay importable from both the CJS and the ESM build, and\n * the only spelling that worked in both reached the module loader through a\n * dynamically evaluated string. The repo's payload scanner rejects that shape\n * on sight, and rightly — this package is published to npm, and the scanner\n * does not exempt code that means well. Asking the API what it does needs no\n * module resolution, no dynamic execution and no version table, and it tests\n * the behaviour actually depended on rather than a number that correlates\n * with it.\n */\n\n/**\n * The one method probed. Both call sites already hold a `testInfo`; this is the\n * narrowest shape that covers the two call forms.\n */\nexport interface SnapshotPathCapable {\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n}\n\n/**\n * Passed to the probe. Never written to disk — `snapshotPath` only computes a\n * path — and named so it is unmistakable in a stack trace or a log line.\n */\nconst PROBE_NAME = '__testrelic_snapshot_path_probe__.png';\n\n/** Printed at most once per process, however many tests hit the path. */\nconst WARNED_KEY = Symbol.for('testrelic.snapshotPathWarned');\n\ninterface WarnSlot {\n warned: boolean;\n}\n\n/**\n * The latch lives on `globalThis`, not in a module variable.\n *\n * tsup builds with `splitting: false`, so every entry point bundles its own\n * copy of this module — and this module is reached from five of them. A\n * module-level `let` would therefore be five separate latches, and the\n * \"at most once per process\" promise above would print up to five times. This\n * is the same fix, and the same reason, as the active context in\n * `assertion-tracker.ts`.\n */\nfunction warnSlot(): WarnSlot {\n const g = globalThis as unknown as Record<symbol, WarnSlot | undefined>;\n let slot = g[WARNED_KEY];\n if (!slot) {\n slot = { warned: false };\n g[WARNED_KEY] = slot;\n }\n return slot;\n}\n\n/**\n * True when `snapshotPath(name, { kind })` resolves the way this code needs.\n *\n * On 1.51 and later the options form returns the snapshot directory joined\n * with the name, so the result ends with the name it was given. Before that\n * the options object is treated as another path segment: `path.join` throws on\n * a non-string, and any version that coerced it instead would still not return\n * a path ending in the probe name. Both failure shapes answer `false`.\n */\nexport function supportsSnapshotPathOptions(testInfo: SnapshotPathCapable): boolean {\n try {\n const resolved = testInfo.snapshotPath(PROBE_NAME, { kind: 'screenshot' });\n return typeof resolved === 'string' && resolved.endsWith(PROBE_NAME);\n } catch {\n return false;\n }\n}\n\n/**\n * Say once that a visual extra is unavailable on this Playwright, and why.\n *\n * `feature` names what the user loses, so the line is actionable rather than\n * an abstract version complaint.\n */\nexport function warnSnapshotPathUnsupported(feature: string): void {\n const slot = warnSlot();\n if (slot.warned) return;\n slot.warned = true;\n process.stderr.write(\n `[testrelic] ${feature} needs Playwright >= 1.51 for ` +\n `testInfo.snapshotPath(name, { kind }); this project's Playwright ` +\n `resolves that call the older way. Everything else in the reporter is ` +\n `unaffected — upgrade Playwright to enable it.\\n`,\n );\n}\n\n/** Reset the once-per-process latch. Test-only. */\nexport function resetSnapshotPathWarning(): void {\n warnSlot().warned = false;\n}\n","/**\n * The `expect` layer that attaches DOM comparison to a visual assertion.\n *\n * Wraps `toHaveScreenshot` and `toMatchVisualBaseline` so that both capture a\n * DOM snapshot, compare it against a committed baseline, and record which\n * elements differ. The pixel comparison is untouched — this runs either side of\n * it and never changes its verdict.\n *\n * Composed as a layer rather than folded into `assertion-tracker.ts`: that\n * module records API assertions, and giving it visual concerns too would give\n * it two jobs. It also returns early for anything without an API call id, which\n * is exactly why `expect(page).toHaveScreenshot()` reaches raw Playwright today.\n */\n\nimport { basename, extname } from 'node:path';\nimport { writeFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport type { DomChange } from '@testrelic/core';\nimport {\n captureDom,\n domBaselinePath,\n readDomBaseline,\n writeDomBaseline,\n serializeSnapshot,\n type BoundedTarget,\n type EvaluateTarget,\n} from './dom-snapshot.js';\nimport { diffDomSnapshots } from './dom-diff.js';\nimport { supportsSnapshotPathOptions, warnSnapshotPathUnsupported } from './snapshot-path-support.js';\nimport type { DomSnapshot } from './dom-capture.js';\n\n/** Attachment carrying the DOM comparison from the worker to the reporter. */\nexport const VISUAL_DOM_ATTACHMENT = 'testrelic-visual-dom';\n\n/** One comparison's DOM result, as it crosses the process boundary. */\nexport interface VisualDomRecord {\n readonly stem: string;\n readonly changes: readonly DomChange[];\n readonly total: number;\n /** Of the total, how many changed rather than merely shifted. */\n readonly rootCauses: number;\n readonly truncated: boolean;\n readonly captureBox: readonly [number, number, number, number];\n /** Absolute paths for the reporter to copy into the run's artifacts. */\n readonly baselineDomPath?: string;\n readonly actualDomPath?: string;\n}\n\nconst VISUAL_MATCHERS = new Set(['toHaveScreenshot', 'toMatchVisualBaseline']);\n\ninterface MinimalTestInfo {\n outputPath(name: string): string;\n attachments: ReadonlyArray<{ name: string }>;\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n attach(name: string, options: { body?: Buffer; contentType?: string }): Promise<void>;\n config: { updateSnapshots?: unknown };\n}\n\ninterface LocatorLike extends BoundedTarget {\n page(): EvaluateTarget;\n}\n\n// SAFETY: the received value of an `expect(...)` call is genuinely unknown.\n// These two guards check for the shape each path needs and nothing more.\nfunction isLocatorLike(value: unknown): value is LocatorLike {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.boundingBox === 'function' && typeof v.page === 'function';\n}\n\nfunction isPageLike(value: unknown): value is EvaluateTarget {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.evaluate === 'function' && typeof v.screenshot === 'function';\n}\n\n/** The snapshot name, whichever of the two call shapes was used. */\nfunction snapshotNameOf(args: readonly unknown[]): string | null {\n const first = args[0];\n if (typeof first === 'string') return first;\n if (Array.isArray(first)) return first.join('/');\n if (typeof first === 'object' && first !== null) {\n const name = (first as Record<string, unknown>).name;\n if (typeof name === 'string') return name;\n }\n return null;\n}\n\n/** The options object, which may be the first or the second argument. */\nfunction optionsOf(args: readonly unknown[]): Record<string, unknown> {\n for (const arg of args) {\n if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {\n return arg as Record<string, unknown>;\n }\n }\n return {};\n}\n\nfunction maskLocatorsOf(options: Record<string, unknown>): BoundedTarget[] {\n const mask = options.mask;\n if (!Array.isArray(mask)) return [];\n return mask.filter((entry): entry is BoundedTarget => {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as Record<string, unknown>).boundingBox === 'function'\n );\n });\n}\n\n/** `toHaveScreenshot` rejects a bare name; the resolved path needs the same form. */\nfunction withPngExtension(name: string): string {\n return /\\.(png|jpg|jpeg)$/i.test(name) ? name : `${name}.png`;\n}\n\ninterface DomComparisonInput {\n readonly received: unknown;\n readonly args: readonly unknown[];\n readonly testInfo: MinimalTestInfo;\n /** Attachment count before the assertion ran, to spot the ones it added. */\n readonly attachedBefore: number;\n}\n\nconst ROLE_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)\\.[A-Za-z0-9]+$/;\n\n/**\n * The stem the reporter will group this comparison under.\n *\n * It must be read back from the attachments the assertion just produced, not\n * derived from the snapshot path: Playwright names attachments after the\n * resolved OUTPUT path, and the two are genuinely different — a snapshot at\n * `drift-target-win32.png` attaches as `drift-target-expected.png`. Deriving it\n * the obvious way silently produced a stem that matched nothing, so every\n * natively-asserted comparison lost its element list.\n *\n * Falls back to the snapshot basename for a passing assertion, which attaches\n * no images — that is the path `toMatchVisualBaseline` takes, and it names its\n * own attachment with exactly that stem.\n */\nfunction resolveStem(testInfo: MinimalTestInfo, attachedBefore: number, fallback: string): string {\n const attachments = testInfo.attachments;\n for (let i = attachedBefore; i < attachments.length; i++) {\n const match = ROLE_SUFFIX_RE.exec(attachments[i]?.name ?? '');\n if (match) return match[1] as string;\n }\n return fallback;\n}\n\n/** Capture the DOM of whatever is about to be screenshotted. */\nasync function capture(input: DomComparisonInput): Promise<DomSnapshot | null> {\n const options = optionsOf(input.args);\n const locator = isLocatorLike(input.received) ? input.received : null;\n const target = locator ? locator.page() : (input.received as EvaluateTarget);\n\n return captureDom({\n target,\n bounded: locator,\n maskLocators: maskLocatorsOf(options),\n fullPage: options.fullPage === true,\n });\n}\n\n/** True when the run was told to rewrite baselines rather than compare against them. */\nfunction isUpdatingSnapshots(testInfo: MinimalTestInfo): boolean {\n const mode = testInfo.config?.updateSnapshots;\n return mode === 'all' || mode === 'changed';\n}\n\n/**\n * Compare the captured DOM against its baseline and record the result.\n *\n * Writes the baseline when there is not one yet, or when the run was invoked\n * with `--update-snapshots` — mirroring exactly what Playwright does with the\n * image beside it, so the two never drift apart.\n */\nasync function recordDomComparison(input: DomComparisonInput, actual: DomSnapshot): Promise<void> {\n const name = snapshotNameOf(input.args);\n if (!name) return;\n\n // Resolving the baseline needs the options form of snapshotPath (>= 1.51).\n // Without it the path comes back wrong and this whole feature silently did\n // nothing; say so once rather than leave the user guessing.\n if (!supportsSnapshotPathOptions(input.testInfo)) {\n warnSnapshotPathUnsupported('DOM comparison');\n return;\n }\n\n const screenshotPath = input.testInfo.snapshotPath(withPngExtension(name), {\n kind: 'screenshot',\n });\n const baselinePath = domBaselinePath(screenshotPath);\n const stem = resolveStem(\n input.testInfo,\n input.attachedBefore,\n basename(screenshotPath, extname(screenshotPath)),\n );\n\n const serialized = serializeSnapshot(actual);\n const updating = isUpdatingSnapshots(input.testInfo);\n const baseline = updating ? null : readDomBaseline(baselinePath);\n\n if (!baseline) {\n // No reference to compare against — write one, the way Playwright writes\n // the image baseline in the same situation. Nothing is reported as changed,\n // because nothing was compared.\n writeDomBaseline(baselinePath, serialized.json);\n return;\n }\n\n const { changes, total, rootCauses } = diffDomSnapshots(baseline, actual);\n\n let actualDomPath: string | undefined;\n try {\n actualDomPath = input.testInfo.outputPath(`${stem}.actual.dom.json`);\n writeFileSync(actualDomPath, serialized.json, 'utf-8');\n } catch {\n actualDomPath = undefined;\n }\n\n const record: VisualDomRecord = {\n stem,\n changes,\n total,\n rootCauses,\n truncated: serialized.truncatedBytes || actual.truncatedNodes > 0,\n captureBox: actual.captureBox,\n ...(existsSync(baselinePath) ? { baselineDomPath: baselinePath } : {}),\n ...(actualDomPath ? { actualDomPath } : {}),\n };\n\n await input.testInfo.attach(VISUAL_DOM_ATTACHMENT, {\n body: Buffer.from(JSON.stringify([record])),\n contentType: 'application/json',\n });\n}\n\ntype MatcherFn = (...args: unknown[]) => unknown;\n\n/**\n * Wrap an `expect` so visual assertions also compare the DOM.\n *\n * Everything that is not a visual assertion against a Page or Locator passes\n * through untouched, including `.not`, `.soft` and every other matcher.\n */\nexport function withVisualCapture(\n baseExpect: (...args: unknown[]) => unknown,\n getTestInfo: () => MinimalTestInfo | null,\n): (...args: unknown[]) => unknown {\n const wrapped = function visualExpect(received: unknown, ...rest: unknown[]): unknown {\n const matchers = baseExpect(received, ...rest);\n\n const isTarget = isLocatorLike(received) || isPageLike(received);\n if (!isTarget || typeof matchers !== 'object' || matchers === null) return matchers;\n\n return new Proxy(matchers as object, {\n get(target, prop, receiver): unknown {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop !== 'string' || !VISUAL_MATCHERS.has(prop)) return value;\n if (typeof value !== 'function') return value;\n\n const original = value as MatcherFn;\n return async function domAwareMatcher(...args: unknown[]): Promise<unknown> {\n const testInfo = getTestInfo();\n\n // Capture BEFORE the assertion: a failing comparison may leave the\n // page mid-retry, and the DOM we want is the one that was rendered.\n let snapshot: DomSnapshot | null = null;\n const attachedBefore = testInfo?.attachments.length ?? 0;\n if (testInfo) {\n try {\n snapshot = await capture({ received, args, testInfo, attachedBefore });\n } catch {\n // DOM capture never changes whether the assertion runs.\n }\n }\n\n try {\n return await original.apply(target, args);\n } finally {\n if (testInfo && snapshot) {\n try {\n await recordDomComparison({ received, args, testInfo, attachedBefore }, snapshot);\n } catch {\n // A DOM comparison problem must never alter the test outcome.\n }\n }\n }\n };\n },\n });\n };\n\n // Carry `expect.soft`, `expect.configure`, `expect.extend` and the rest\n // forward; a wrapper that drops them silently breaks unrelated assertions.\n const statics = baseExpect as unknown as Record<string, unknown>;\n const target = wrapped as unknown as Record<string, unknown>;\n for (const key of Object.keys(statics)) {\n target[key] = statics[key];\n }\n\n return wrapped;\n}\n","/**\n * Recover visual baseline comparisons from a Playwright test result.\n *\n * Playwright's snapshot matchers do not report what they compared; they only\n * leave attachments behind. `toHaveScreenshot()` / `toMatchSnapshot()` attach\n * `<stem>-expected`, `<stem>-actual`, `<stem>-diff` and, on a retried failure,\n * `<stem>-previous`. Those four share a stem, and the stem is the snapshot's\n * identity within the test.\n *\n * This module is pure — it groups names and reads a message, and touches no\n * filesystem. Copying the images out is `artifact-manager.copyVisualArtifacts`.\n */\n\nimport type { VisualSource, VisualStatus } from '@testrelic/core';\nimport { parseDiffMessage, isSizeMismatchFailure } from './visual-metrics.js';\nimport { VISUAL_DOM_ATTACHMENT, type VisualDomRecord } from './visual-expect.js';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n/**\n * A comparison found in the attachments, still pointing at Playwright's temp\n * files. `artifact-manager` turns this into the report-facing\n * `VisualComparison` once the images have been copied somewhere durable.\n */\nexport interface VisualCandidate {\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n readonly expectedPath?: string;\n readonly actualPath?: string;\n readonly diffPath?: string;\n readonly previousPath?: string;\n readonly diffPixels?: number;\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The DOM comparison for this snapshot, when one was made. */\n readonly dom?: VisualDomRecord;\n}\n\n/** Attachment name carrying the explicit matcher's JSON side-channel. */\nexport const VISUAL_META_ATTACHMENT = 'testrelic-visual';\n\n/** The four roles a snapshot image can play, as Playwright suffixes them. */\ntype VisualRole = 'expected' | 'actual' | 'diff' | 'previous';\n\nconst VISUAL_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)(\\.[A-Za-z0-9]+)$/;\n\n/** One stem's worth of images, before a status is decided. */\ninterface StemGroup {\n expected?: string;\n actual?: string;\n diff?: string;\n previous?: string;\n}\n\n/**\n * Metadata the `toMatchVisualBaseline` matcher attaches alongside its images.\n *\n * It exists because a passing comparison is otherwise invisible: Playwright\n * attaches nothing at all when the images match, so without this the report\n * could only ever show the failures.\n */\nexport interface VisualMetaRecord {\n readonly stem: string;\n readonly name: string;\n readonly status: VisualStatus;\n}\n\n/** Read and validate the explicit matcher's metadata attachment. */\nexport function parseVisualMeta(attachments: readonly Attachment[]): Map<string, VisualMetaRecord> {\n const byStem = new Map<string, VisualMetaRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_META_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n const record = toMetaRecord(entry);\n if (record) byStem.set(record.stem, record);\n }\n } catch {\n // A malformed side-channel costs us the pass-case detail, nothing more.\n }\n }\n\n return byStem;\n}\n\nconst VALID_STATUSES: readonly string[] = ['passed', 'failed', 'new', 'updated'];\n\n// SAFETY: parsed from an attachment body written by the worker process; every\n// field is checked before use.\nfunction toMetaRecord(entry: unknown): VisualMetaRecord | null {\n if (typeof entry !== 'object' || entry === null) return null;\n const e = entry as Record<string, unknown>;\n if (typeof e.stem !== 'string' || !e.stem) return null;\n if (typeof e.name !== 'string' || !e.name) return null;\n if (typeof e.status !== 'string' || !VALID_STATUSES.includes(e.status)) return null;\n return { stem: e.stem, name: e.name, status: e.status as VisualStatus };\n}\n\n/**\n * Read the DOM comparison side-channel, keyed by the same stem the images use.\n *\n * Written by `visual-expect`; absent whenever the assertion used Playwright's\n * own `expect`, or the page had no DOM baseline yet.\n */\nexport function parseVisualDom(\n attachments: readonly Attachment[],\n): Map<string, VisualDomRecord> {\n const byStem = new Map<string, VisualDomRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_DOM_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n // SAFETY: written by the worker process; the two fields read before\n // anything else uses it are checked here.\n if (typeof entry !== 'object' || entry === null) continue;\n const record = entry as Record<string, unknown>;\n if (typeof record.stem !== 'string' || !Array.isArray(record.changes)) continue;\n byStem.set(record.stem, entry as VisualDomRecord);\n }\n } catch {\n // A malformed side-channel costs the element list, nothing more.\n }\n }\n\n return byStem;\n}\n\n/** Group every `<stem>-<role>.<ext>` attachment that has a file behind it. */\nfunction groupBySnapshotStem(attachments: readonly Attachment[]): Map<string, StemGroup> {\n const groups = new Map<string, StemGroup>();\n\n for (const attachment of attachments) {\n if (!attachment.path) continue;\n const match = VISUAL_SUFFIX_RE.exec(attachment.name);\n if (!match) continue;\n\n const [, stem, role] = match;\n const group = groups.get(stem as string) ?? {};\n group[role as VisualRole] = attachment.path;\n groups.set(stem as string, group);\n }\n\n return groups;\n}\n\n/**\n * Decide whether a stem is really a snapshot comparison.\n *\n * An ordinary user attachment named `report-actual.json` would otherwise be\n * mistaken for one. A genuine comparison always produces an `actual` next to\n * either the baseline it was compared against or the diff it produced, so\n * requiring that pair costs nothing real and rejects the lookalikes. Stems the\n * explicit matcher has vouched for skip this check — it attaches a lone\n * baseline on a pass, which is the one legitimate single-image case.\n */\nfunction isCredibleComparison(group: StemGroup): boolean {\n return Boolean(group.actual && (group.expected || group.diff));\n}\n\n/** Derive a status from the images alone, for natively-produced comparisons. */\nfunction deriveStatus(\n group: StemGroup,\n updatingSnapshots: boolean,\n failureMessage: string | null | undefined,\n): VisualStatus {\n if (group.diff) return 'failed';\n // A size mismatch also produces no diff — the comparator cannot draw one\n // when the two images have different dimensions, so it attaches expected +\n // actual and throws. Absence of a diff therefore does NOT imply \"no baseline\n // existed\": read the message before concluding that. Without this a genuine\n // failure was reported as a freshly written baseline, it was missing from\n // visualFailures / visualFailedCount / visualDiffPath, and because\n // applyMetrics only targets failed stems the parsed dimensions were dropped\n // too (making SIZE_MISMATCH_RE dead in practice).\n if (isSizeMismatchFailure(failureMessage)) return 'failed';\n // Playwright attached a baseline and an actual but no diff: it had no\n // baseline and wrote one. That is not a pass — nothing was compared.\n return updatingSnapshots ? 'updated' : 'new';\n}\n\n/**\n * Attach the pixel count to the comparison it actually describes.\n *\n * The failure message belongs to whichever assertion threw. When it echoes a\n * snapshot name, that name is matched against the stems; otherwise the numbers\n * are only trustworthy if exactly one comparison failed, and are dropped when\n * several did rather than being pinned on an arbitrary one.\n */\nfunction selectMetricsTarget(\n failed: readonly string[],\n snapshotName: string | undefined,\n): string | null {\n if (failed.length === 0) return null;\n if (failed.length === 1) return failed[0] as string;\n if (!snapshotName) return null;\n\n const base = snapshotName.replace(/\\.[A-Za-z0-9]+$/, '');\n const matches = failed.filter((stem) => stem === base || stem.endsWith(`/${base}`));\n return matches.length === 1 ? (matches[0] as string) : null;\n}\n\nexport interface ParseVisualOptions {\n /** True when the run was invoked with `--update-snapshots`. */\n readonly updatingSnapshots?: boolean;\n /** Concatenated failure messages for the test attempt. */\n readonly failureMessage?: string | null;\n}\n\n/**\n * Extract every visual comparison recorded against one test attempt.\n *\n * Returns an empty array — never throws — for a test that made no visual\n * assertions, which is the overwhelming majority of them.\n */\nexport function parseVisualAttachments(\n attachments: readonly Attachment[],\n options: ParseVisualOptions = {},\n): VisualCandidate[] {\n if (attachments.length === 0) return [];\n\n const meta = parseVisualMeta(attachments);\n const dom = parseVisualDom(attachments);\n const groups = groupBySnapshotStem(attachments);\n if (groups.size === 0) return [];\n\n const updating = options.updatingSnapshots === true;\n const candidates: VisualCandidate[] = [];\n\n for (const [stem, group] of groups) {\n const vouched = meta.get(stem);\n if (!vouched && !isCredibleComparison(group)) continue;\n\n candidates.push({\n name: vouched?.name ?? stem,\n status: vouched?.status ?? deriveStatus(group, updating, options.failureMessage),\n source: vouched ? 'toMatchVisualBaseline' : 'toHaveScreenshot',\n ...(group.expected ? { expectedPath: group.expected } : {}),\n ...(group.actual ? { actualPath: group.actual } : {}),\n ...(group.diff ? { diffPath: group.diff } : {}),\n ...(group.previous ? { previousPath: group.previous } : {}),\n ...(dom.has(stem) ? { dom: dom.get(stem) as VisualDomRecord } : {}),\n });\n }\n\n return applyMetrics(candidates, groups, options.failureMessage);\n}\n\n/** Fold the parsed failure numbers into the one comparison they belong to. */\nfunction applyMetrics(\n candidates: readonly VisualCandidate[],\n groups: ReadonlyMap<string, StemGroup>,\n failureMessage: string | null | undefined,\n): VisualCandidate[] {\n const parsed = parseDiffMessage(failureMessage);\n if (parsed.diffPixels === undefined && parsed.actualWidth === undefined) {\n return [...candidates];\n }\n\n const failedStems = candidates.filter((c) => c.status === 'failed').map((c) => stemOf(c, groups));\n const target = selectMetricsTarget(failedStems, parsed.snapshotName);\n if (target === null) return [...candidates];\n\n return candidates.map((candidate) =>\n stemOf(candidate, groups) === target\n ? {\n ...candidate,\n ...(parsed.diffPixels !== undefined ? { diffPixels: parsed.diffPixels } : {}),\n ...(parsed.actualWidth !== undefined ? { actualWidth: parsed.actualWidth } : {}),\n ...(parsed.actualHeight !== undefined ? { actualHeight: parsed.actualHeight } : {}),\n }\n : candidate,\n );\n}\n\n/**\n * Recover the stem a candidate came from.\n *\n * The explicit matcher renames a candidate to the author's chosen name, so the\n * name cannot be used to look it back up among the groups.\n */\nfunction stemOf(candidate: VisualCandidate, groups: ReadonlyMap<string, StemGroup>): string {\n if (groups.has(candidate.name)) return candidate.name;\n for (const [stem, group] of groups) {\n if (\n group.actual === candidate.actualPath &&\n group.expected === candidate.expectedPath &&\n group.diff === candidate.diffPath\n ) {\n return stem;\n }\n }\n return candidate.name;\n}\n","/**\n * `toMatchVisualBaseline` — TestRelic's visual assertion.\n *\n * It does not compare images itself. Playwright already ships a comparator\n * (pixelmatch, or SSIM-CIE94 when asked) and bundles the decoders it needs, so\n * this delegates to `toHaveScreenshot` and adds the two things Playwright does\n * not give a reporter:\n *\n * 1. **A name that survives into the report.** Playwright's attachments are\n * named after the resolved snapshot path, which carries project and\n * platform suffixes. The author's own name is recorded alongside.\n * 2. **Evidence for a passing comparison.** Playwright attaches nothing at all\n * when the images match, so a green visual check is invisible to any\n * reporter. The baseline is attached here so the report can show what was\n * actually asserted against, not just what broke.\n *\n * Both ride the same `-expected` / `-actual` / `-diff` attachment convention\n * the reporter already groups by, so nothing downstream needs a second path.\n */\n\nimport { expect as playwrightExpect, test as playwrightTest } from '@playwright/test';\nimport { basename, extname } from 'node:path';\nimport { VISUAL_META_ATTACHMENT, type VisualMetaRecord } from './visual-capture.js';\nimport { isSizeMismatchFailure } from './visual-metrics.js';\nimport { supportsSnapshotPathOptions, warnSnapshotPathUnsupported } from './snapshot-path-support.js';\n\n/** Options accepted on top of Playwright's own screenshot options. */\nexport interface VisualBaselineOptions {\n /**\n * Labels recorded with the comparison. Reserved for the cloud baseline store,\n * where they will select which baselines a branch is allowed to promote.\n */\n readonly tags?: readonly string[];\n /** Anything else is forwarded to `toHaveScreenshot` untouched. */\n readonly [key: string]: unknown;\n}\n\ninterface MatcherResult {\n pass: boolean;\n message: () => string;\n name: string;\n}\n\n// SAFETY: Playwright's TestInfo is reached through `test.info()`, which throws\n// outside a running test. Only the three members used here are described.\ninterface MinimalTestInfo {\n attachments: Array<{ name: string; contentType: string; path?: string; body?: Buffer }>;\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n attach(\n name: string,\n options: { path?: string; body?: Buffer; contentType?: string },\n ): Promise<void>;\n}\n\n/** Image extensions Playwright will accept for a screenshot baseline. */\nconst IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg'];\n\n/**\n * Give the snapshot name a file extension if the author left it off.\n *\n * `toHaveScreenshot` rejects a bare name outright (\"must have '.png'\n * extension\"). Requiring the suffix here would be the sort of papercut that\n * makes a wrapper worse than the thing it wraps, so `'home'` and `'home.png'`\n * both work and resolve to the same baseline.\n */\nexport function normalizeSnapshotName(name: string): string {\n const lower = name.toLowerCase();\n return IMAGE_EXTENSIONS.some((ext) => lower.endsWith(ext)) ? name : `${name}.png`;\n}\n\n/** `test.info()` throws when no test is running; a matcher must not. */\nfunction currentTestInfo(): MinimalTestInfo | null {\n try {\n return playwrightTest.info() as unknown as MinimalTestInfo;\n } catch {\n return null;\n }\n}\n\n/**\n * The stem Playwright used for the attachments this assertion just produced.\n *\n * Reading it back off `testInfo.attachments` is the only reliable way to learn\n * it: the stem comes from the resolved output path, which the snapshot path\n * template controls, and reversing that template would be guesswork.\n */\nfunction stemOfNewAttachments(\n attachments: readonly { name: string }[],\n addedAfter: number,\n): string | null {\n for (let i = addedAfter; i < attachments.length; i++) {\n const match = /^(.+)-(expected|actual|diff|previous)\\.[A-Za-z0-9]+$/.exec(\n attachments[i]?.name ?? '',\n );\n if (match) return match[1] as string;\n }\n return null;\n}\n\n/** Attach the baseline for a comparison that passed and left no evidence. */\nasync function attachPassingBaseline(\n testInfo: MinimalTestInfo,\n snapshotName: string,\n): Promise<string | null> {\n if (!supportsSnapshotPathOptions(testInfo)) {\n warnSnapshotPathUnsupported('Attaching the baseline of a passing comparison');\n return null;\n }\n try {\n const path = testInfo.snapshotPath(snapshotName, { kind: 'screenshot' });\n const ext = extname(path) || '.png';\n const stem = basename(path, ext);\n await testInfo.attach(`${stem}-expected${ext}`, { path });\n return stem;\n } catch {\n // No baseline on disk, or an unwritable output dir. The comparison still\n // passed; it simply has no picture to show for it.\n return null;\n }\n}\n\n/** Record the author's name and outcome against the stem the reporter will see. */\nasync function attachMeta(testInfo: MinimalTestInfo, record: VisualMetaRecord): Promise<void> {\n try {\n await testInfo.attach(VISUAL_META_ATTACHMENT, {\n body: Buffer.from(JSON.stringify([record])),\n contentType: 'application/json',\n });\n } catch {\n // Without this the comparison still appears, just under Playwright's own\n // name and inferred status.\n }\n}\n\n/**\n * Assert a page or locator against its committed visual baseline.\n *\n * Registered onto `expect` by `./visual`; see that module for the type\n * declaration that makes it visible to TypeScript.\n */\nexport async function toMatchVisualBaseline(\n this: { isNot?: boolean },\n target: unknown,\n name: string,\n options: VisualBaselineOptions = {},\n): Promise<MatcherResult> {\n // `tags` is ours; everything else belongs to Playwright and is forwarded\n // verbatim, so that every screenshot option keeps working here.\n const screenshotOptions: Record<string, unknown> = { ...options };\n delete screenshotOptions.tags;\n const snapshotName = normalizeSnapshotName(name);\n const testInfo = currentTestInfo();\n const attachedBefore = testInfo?.attachments.length ?? 0;\n\n let pass = true;\n let message = '';\n try {\n await (\n playwrightExpect(target) as unknown as {\n toHaveScreenshot(n: string, o: Record<string, unknown>): Promise<void>;\n }\n ).toHaveScreenshot(snapshotName, screenshotOptions);\n } catch (error) {\n pass = false;\n message = error instanceof Error ? error.message : String(error);\n }\n\n if (testInfo) {\n const stem = pass\n ? await attachPassingBaseline(testInfo, snapshotName)\n : stemOfNewAttachments(testInfo.attachments, attachedBefore);\n\n if (stem) {\n // A failure that wrote no diff wrote a first baseline instead — the\n // distinction matters, because nothing was compared in that case.\n //\n // But a size mismatch also writes no diff: the comparator cannot draw\n // one across different dimensions. Absence of a diff alone therefore\n // misreported a real failure as a new baseline, so the message decides.\n const wroteNewBaseline =\n !pass &&\n !isSizeMismatchFailure(message) &&\n !testInfo.attachments.some(\n (a, i) => i >= attachedBefore && a.name.startsWith(`${stem}-diff.`),\n );\n await attachMeta(testInfo, {\n stem,\n name,\n status: pass ? 'passed' : wroteNewBaseline ? 'new' : 'failed',\n });\n }\n }\n\n return {\n pass,\n name: 'toMatchVisualBaseline',\n message: () =>\n pass ? `Expected \"${name}\" to differ from its visual baseline, but it matched.` : message,\n };\n}\n","/**\n * @testrelic/playwright-analytics/visual\n *\n * Registers `toMatchVisualBaseline` onto Playwright's `expect` and declares it\n * to TypeScript.\n *\n * Importing this module is only necessary when using Playwright's own `expect`.\n * The SDK fixture (`@testrelic/playwright-analytics/fixture`) already imports\n * it, so `expect` from there has the matcher without a second import.\n *\n * Native `toHaveScreenshot()` needs none of this — the reporter recovers those\n * comparisons from the attachments on its own.\n */\n\nimport { expect as playwrightExpect } from '@playwright/test';\nimport { toMatchVisualBaseline } from './visual-matcher.js';\n\nexport type { VisualBaselineOptions } from './visual-matcher.js';\nexport { toMatchVisualBaseline } from './visual-matcher.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace PlaywrightTest {\n // `T` is unused here but structurally required: declaration merging only\n // works against Playwright's `Matchers<R, T = unknown>` if the parameter\n // list matches, and dropping it silently stops the merge.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface Matchers<R, T = unknown> {\n /**\n * Compare a page or locator against its committed visual baseline.\n *\n * Uses Playwright's own comparator, so every `toHaveScreenshot` option\n * (`threshold`, `maxDiffPixels`, `maxDiffPixelRatio`, `mask`, `clip`,\n * `fullPage`, `animations`, …) applies unchanged. Baselines live where\n * Playwright puts them and are updated with `--update-snapshots`.\n *\n * ```ts\n * await expect(page).toMatchVisualBaseline('home', {\n * maxDiffPixelRatio: 0.01,\n * mask: [page.locator('.live-ticker')],\n * });\n * ```\n */\n toMatchVisualBaseline(\n name: string,\n options?: import('./visual-matcher.js').VisualBaselineOptions,\n ): Promise<R>;\n }\n }\n}\n\n// Registered at module scope so a bare import is enough to install it.\n// `expect.extend` is additive and idempotent here: the fixture and a direct\n// import of this module both land on the same registration.\nplaywrightExpect.extend({ toMatchVisualBaseline });\n","/**\n * @testrelic/playwright-analytics/fixture\n *\n * Extended Playwright test fixture that wraps the default `page` fixture\n * to automatically track navigation events and API requests.\n *\n * Exports:\n * - testRelicFixture: Fixture object for use with base.extend()\n * - test: Pre-extended test instance (backward compatibility)\n * - expect: Re-exported from @playwright/test (backward compatibility)\n */\n\nimport { test as base, expect as playwrightExpect } from '@playwright/test';\nimport { NavigationTracker } from './navigation-tracker.js';\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker, createActiveAssertionAwareExpect, setActiveAssertionContext } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { TestRelicFilePayload } from '@testrelic/core';\nimport type { ResolvedApiConfig } from './config.js';\n// Side-effect import: registers `toMatchVisualBaseline` on Playwright's expect,\n// so the `expect` exported below carries it without a second import in tests.\nimport './visual.js';\nimport { withVisualCapture } from './visual-expect.js';\n\n/**\n * Assertion-aware `expect`. Inside a test that ran through this fixture, each\n * assertion on an API response is recorded and linked to its originating call,\n * so it surfaces in the cloud report's API workspace; outside one it behaves\n * exactly like Playwright's `expect`. Import THIS `expect` (not the one from\n * '@playwright/test') for API assertion capture.\n */\n/**\n * `test.info()` throws outside a running test. The visual layer must degrade\n * to a plain pass-through there rather than take an assertion down with it.\n */\nfunction currentTestInfo(): ReturnType<typeof base.info> | null {\n try {\n return base.info();\n } catch {\n return null;\n }\n}\n\n/**\n * Two single-purpose layers, composed.\n *\n * The inner layer records API assertions. The outer one attaches DOM\n * comparison to `toHaveScreenshot` and `toMatchVisualBaseline`, which is why\n * visual assertions written with THIS `expect` report which elements changed\n * while ones written with Playwright's own `expect` compare pixels only.\n */\nexport const expect = withVisualCapture(\n createActiveAssertionAwareExpect(\n playwrightExpect as unknown as (...args: unknown[]) => unknown,\n ),\n currentTestInfo,\n) as unknown as typeof playwrightExpect;\n\n/** Annotation type for API config (reporter → fixture). */\nconst API_CONFIG_ANNOTATION = '__testrelic_api_config';\n\n/** Legacy annotation type for backward compatibility. */\nconst LEGACY_TRACK_API_CALLS_ANNOTATION = '__testrelic_config_trackApiCalls';\n\n/**\n * Reads API config from testInfo annotations pushed by the reporter.\n * Returns a default config if no annotation is found (FR-013).\n */\nexport function readApiConfig(testInfo: { annotations: Array<{ type: string; description?: string }> }): ResolvedApiConfig {\n // Try new unified annotation first\n const configAnnotation = testInfo.annotations.find(\n (a) => a.type === API_CONFIG_ANNOTATION && a.description !== undefined,\n );\n if (configAnnotation) {\n try {\n const parsed = JSON.parse(configAnnotation.description!, deserializeRegExp);\n return parsed as ResolvedApiConfig;\n } catch {\n // Fall through to defaults\n }\n }\n\n // Backward compatibility: check legacy trackApiCalls annotation\n const legacyAnnotation = testInfo.annotations.find(\n (a) => a.type === LEGACY_TRACK_API_CALLS_ANNOTATION && a.description !== undefined,\n );\n if (legacyAnnotation) {\n return {\n trackApiCalls: legacyAnnotation.description !== 'false',\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n };\n }\n\n // No annotation found — return sensible defaults (FR-013)\n return {\n trackApiCalls: true,\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n };\n}\n\n/** JSON reviver that reconstructs serialized RegExp objects. */\nfunction deserializeRegExp(_key: string, value: unknown): unknown {\n if (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).__regexp === true &&\n typeof (value as Record<string, unknown>).source === 'string'\n ) {\n const { source, flags } = value as { source: string; flags: string };\n return new RegExp(source, flags);\n }\n return value;\n}\n\n/**\n * Playwright-compatible fixture object for extending `test.extend()`.\n * Provides `page` (with navigation tracking) and `request` (with API tracking).\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicFixture);\n */\nexport const testRelicFixture = {\n page: async (\n { page }: { page: import('@playwright/test').Page },\n use: (page: import('@playwright/test').Page) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const tracker = new NavigationTracker(page as never);\n try {\n await tracker.init();\n } catch {\n // Graceful degradation: continue without SPA detection\n }\n\n await use(page);\n\n try {\n const { navigations, networkRequestsFile, networkRequestsCount, consoleLogsFile, consoleLogsCount } = await tracker.getFileData();\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations,\n apiAssertions: [],\n networkRequestsFile,\n networkRequestsCount,\n consoleLogsFile,\n consoleLogsCount,\n apiCallsFile: null,\n apiCallsCount: 0,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // FR-009: fixture without reporter is harmless\n }\n tracker.dispose();\n },\n\n request: async (\n { request }: { request: import('@playwright/test').APIRequestContext },\n use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const apiConfig = readApiConfig(testInfo);\n\n if (!apiConfig.trackApiCalls) {\n await use(request);\n return;\n }\n\n const assertionTracker = new AssertionTracker();\n const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n apiTracker.intercept();\n // Route the exported assertion-aware `expect` to THIS test's tracker.\n setActiveAssertionContext(assertionTracker, apiTracker);\n\n try {\n await use(request);\n } finally {\n setActiveAssertionContext(null, null);\n }\n\n try {\n const { apiCallsFile, apiCallsCount, apiStatusMix } = apiTracker.getFileData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations: [],\n apiAssertions,\n networkRequestsFile: null,\n networkRequestsCount: 0,\n consoleLogsFile: null,\n consoleLogsCount: 0,\n apiCallsFile,\n apiCallsCount,\n apiStatusMix,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation: fixture without reporter is harmless\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n\n/**\n * Track a manually-created page for network requests, console logs, and navigations.\n *\n * Use this when your test creates pages via `browser.newContext()` / `context.newPage()`\n * instead of the built-in `page` fixture. Call it right after creating the page,\n * and call the returned cleanup function after `use()`.\n *\n * @example\n * authenticatedPage: async ({ browser }, use, testInfo) => {\n * const ctx = await browser.newContext({ storageState: AUTH_FILE });\n * const page = await ctx.newPage();\n * const cleanup = await trackPage(page, testInfo);\n * await use(page);\n * await cleanup();\n * await ctx.close();\n * },\n */\nexport async function trackPage(\n page: import('@playwright/test').Page,\n testInfo: import('@playwright/test').TestInfo,\n): Promise<() => Promise<void>> {\n const tracker = new NavigationTracker(page as never);\n try {\n await tracker.init();\n } catch {\n // Graceful degradation\n }\n\n return async () => {\n try {\n const { navigations, networkRequestsFile, networkRequestsCount, consoleLogsFile, consoleLogsCount } = await tracker.getFileData();\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations,\n apiAssertions: [],\n networkRequestsFile,\n networkRequestsCount,\n consoleLogsFile,\n consoleLogsCount,\n apiCallsFile: null,\n apiCallsCount: 0,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation\n }\n tracker.dispose();\n };\n}\n\n/** Pre-extended test instance for backward compatibility. */\nexport const test = base.extend(testRelicFixture);\n","/**\n * @testrelic/playwright-analytics/api-fixture\n *\n * Standalone API-only fixture for pure API test suites.\n * Wraps only the Playwright `request` fixture with the API request tracker\n * and assertion tracker. Does NOT import any navigation or browser-related code.\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicApiFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicApiFixture);\n */\n\nimport { expect as playwrightExpect } from '@playwright/test';\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker, createActiveAssertionAwareExpect, setActiveAssertionContext } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { ConsoleLogEntry, TestRelicFilePayload } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\nimport { readApiConfig } from './fixture.js';\n\n/**\n * Assertion-aware `expect` for API-only suites. Inside a test that ran through\n * this fixture, each assertion on an API response is recorded and linked to its\n * originating call so it surfaces in the cloud report's API workspace; outside\n * one it behaves exactly like Playwright's `expect`. Import THIS `expect` (not\n * the one from '@playwright/test') for API assertion capture.\n */\nexport const expect = createActiveAssertionAwareExpect(\n playwrightExpect as unknown as (...args: unknown[]) => unknown,\n) as unknown as typeof playwrightExpect;\n\n/**\n * Playwright-compatible fixture object for API-only test suites.\n * Provides only `request` (with API tracking and assertion capture). No browser dependency.\n */\nexport const testRelicApiFixture = {\n request: async (\n { request }: { request: import('@playwright/test').APIRequestContext },\n use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const apiConfig = readApiConfig(testInfo);\n\n if (!apiConfig.trackApiCalls) {\n await use(request);\n return;\n }\n\n const assertionTracker = new AssertionTracker();\n const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n apiTracker.intercept();\n // Route the exported assertion-aware `expect` to THIS test's tracker.\n setActiveAssertionContext(assertionTracker, apiTracker);\n\n // Capture terminal stdout/stderr during the test — stream to JSONL\n let consoleWriter: JsonlFileWriter | null = null;\n try { consoleWriter = new JsonlFileWriter('api-console'); } catch { /* graceful */ }\n let consoleLogCount = 0;\n const origStdoutWrite = process.stdout.write;\n const origStderrWrite = process.stderr.write;\n\n process.stdout.write = function (chunk: unknown, ...args: unknown[]): boolean {\n try {\n const text = typeof chunk === 'string' ? chunk : Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk);\n if (!text.startsWith('[testrelic]') && !text.startsWith('ℹ TestRelic') && !text.startsWith('✓ TestRelic') && !text.startsWith('⚠ TestRelic')) {\n if (consoleWriter) {\n consoleWriter.append({ level: 'stdout', text: text.replace(/\\n$/, ''), timestamp: new Date().toISOString(), location: null } satisfies ConsoleLogEntry);\n consoleLogCount++;\n }\n }\n } catch { /* never crash */ }\n return origStdoutWrite.apply(process.stdout, [chunk, ...args] as never);\n } as typeof process.stdout.write;\n\n process.stderr.write = function (chunk: unknown, ...args: unknown[]): boolean {\n try {\n const text = typeof chunk === 'string' ? chunk : Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk);\n if (!text.startsWith('[testrelic]') && !text.startsWith('⚠ TestRelic')) {\n if (consoleWriter) {\n consoleWriter.append({ level: 'stderr', text: text.replace(/\\n$/, ''), timestamp: new Date().toISOString(), location: null } satisfies ConsoleLogEntry);\n consoleLogCount++;\n }\n }\n } catch { /* never crash */ }\n return origStderrWrite.apply(process.stderr, [chunk, ...args] as never);\n } as typeof process.stderr.write;\n\n try {\n await use(request);\n } finally {\n process.stdout.write = origStdoutWrite;\n process.stderr.write = origStderrWrite;\n setActiveAssertionContext(null, null);\n }\n\n try {\n consoleWriter?.close();\n const { apiCallsFile, apiCallsCount, apiStatusMix } = apiTracker.getFileData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations: [],\n apiAssertions,\n networkRequestsFile: null,\n networkRequestsCount: 0,\n consoleLogsFile: consoleLogCount > 0 ? consoleWriter?.getPath() ?? null : null,\n consoleLogsCount: consoleLogCount,\n apiCallsFile,\n apiCallsCount,\n apiStatusMix,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation: fixture without reporter is harmless\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/jsonl-stream.ts","../src/ansi.ts","../src/assertion-tracker.ts","../src/api-redactor.ts","../src/api-url-filter.ts","../src/api-request-tracker.ts","../src/navigation-tracker.ts","../src/visual-metrics.ts","../src/dom-capture.ts","../src/dom-snapshot.ts","../src/dom-diff.ts","../src/snapshot-path-support.ts","../src/visual-expect.ts","../src/visual-capture.ts","../src/visual-matcher.ts","../src/visual.ts","../src/fixture.ts","../src/api-fixture.ts"],"names":["TESTRELIC_TMP_DIR","join","tmpdir","JsonlFileWriter","prefix","mkdirSync","randomUUID","openSync","obj","line","writeSync","closeSync","unlinkSync","ANSI_PATTERN","stripAnsi","text","CALL_ID_SYMBOL","ASSERTION_ANNOTATION_TYPE","valueCallIdMap","AssertionTracker","assertion","callId","testInfo","sourceCache","getSourceLines","filePath","cached","path","fileURLToPath","lines","readFileSync","OWN_STACK_DIR","stack","m","cut","toPlainPath","file","captureAssertionLocation","findFrame","excludeOwnDir","match","inferAssertionType","expression","_value","expr","buildExpectWrapper","getTracker","getApiTracker","originalExpect","wrappedExpect","received","rest","tracker","apiTracker","tagged","location","matchers","createMatcherProxy","statics","target","key","ACTIVE_CONTEXT_KEY","contextSlot","g","slot","setActiveAssertionContext","createActiveAssertionAwareExpect","ctx","prop","receiver","value","args","expected","status","actual","thrownError","result","recordAssertionResult","err","receivedMatch","matcherName","assertionType","safeSerializable","REDACTED","redactHeaders","headers","redactList","lowerList","h","redactBodyFields","body","parsed","fieldSet","f","redacted","walkAndRedact","fields","item","val","SECRET_QUERY_PARAMS","stripSecretQueryParams","rawUrl","url","changed","shouldTrackApiUrl","include","exclude","pattern","matchesPattern","globToRegex","glob","i","char","HTTP_METHODS","TEXT_CONTENT_TYPES","isTextContentType","contentType","lower","extractRequestBody","options","data","multipart","descriptor","maskCookieValue","parseRequestCookies","raw","k","v","out","part","seg","eq","name","parseResponseCookies","setCookieValues","segRaw","parts","cookieValue","attributes","p","_ApiRequestTracker","context","assertionTracker","apiConfig","method","original","origDispose","code","_testInfo","response","self","origHeaders","origHeadersArray","origJson","origStatus","origStatusText","origOk","origText","origBody","id","timestamp","httpMethod","requestBody","startTime","performance","endTime","errorRequestBody","errorRecord","rawResponseHeaders","binary","capturedRequestHeaders","capturedResponseHeaders","capturedRequestBody","capturedResponseBody","requestCookies","responseCookies","record","ApiRequestTracker","NavigationTracker","_NavigationTracker","page","pending","type","navigations","patchable","event","handler","onDomContentLoaded","onFrameNavigated","frame","frameObj","lastRecord","onConsoleMessage","msg","msgObj","loc","level","onRequest","request","navIndex","counter","req","reqId","postData","onResponse","resp","contentLength","resourceType","typeKey","responseTimeMs","respHeaders","responseSize","bodyPromise","responseBody","captured","onRequestFailed","origPush","origReplace","index","SIZE_MISMATCH_RE","isSizeMismatchFailure","message","captureDomSnapshot","styleProps","defaults","inherited","maxNodes","maxText","maskBoxes","viewportBox","fullPage","inheritedSet","scrollX","scrollY","nodes","truncatedNodes","docMasks","mask","isMasked","x","y","w","b","indexAmongSiblings","el","parent","siblings","sibling","directText","children","node","collectStyles","computed","parentComputed","styles","tag","box","className","contentWidth","contentHeight","captureBox","STYLE_PROPS","STYLE_DEFAULTS","INHERITED_PROPS","DEFAULT_DOM_MAX_BYTES","DEFAULT_DOM_MAX_NODES","DEFAULT_DOM_MAX_TEXT","resolveMaskBoxes","locators","boxes","locator","captureDom","config","serializeSnapshot","snapshot","maxBytes","json","dropped","block","domBaselinePath","screenshotPath","readDomBaseline","existsSync","isDomSnapshot","writeDomBaseline","dirname","writeFileSync","MOVEMENT_ONLY","isRootCause","kinds","kind","labelOf","label","classes","movedOrResized","a","dx","dy","dw","dh","diffStyles","from","to","props","changes","before","after","identityKey","pairNodes","baseline","byPath","pairs","unmatchedActual","matchedBaseline","unmatchedBaseline","byIdentity","bucket","added","claimed","candidate","removed","diffAttrs","areaShare","total","diffDomSnapshots","maxChanges","includeText","includeStyles","baseNodes","actualNodes","scored","attrs","textChanged","share","rootCauses","entry","aRoot","bRoot","PROBE_NAME","WARNED_KEY","warnSlot","supportsSnapshotPathOptions","resolved","warnSnapshotPathUnsupported","feature","VISUAL_DOM_ATTACHMENT","VISUAL_MATCHERS","isLocatorLike","isPageLike","snapshotNameOf","first","optionsOf","arg","maskLocatorsOf","withPngExtension","ROLE_SUFFIX_RE","resolveStem","attachedBefore","fallback","attachments","capture","input","isUpdatingSnapshots","mode","recordDomComparison","baselinePath","stem","basename","extname","serialized","actualDomPath","withVisualCapture","baseExpect","getTestInfo","wrapped","VISUAL_META_ATTACHMENT","IMAGE_EXTENSIONS","normalizeSnapshotName","ext","currentTestInfo","playwrightTest","stemOfNewAttachments","addedAfter","attachPassingBaseline","snapshotName","attachMeta","toMatchVisualBaseline","screenshotOptions","pass","playwrightExpect","error","wroteNewBaseline","base","API_CONFIG_ANNOTATION","LEGACY_TRACK_API_CALLS_ANNOTATION","readApiConfig","configAnnotation","deserializeRegExp","legacyAnnotation","_key","source","flags","testRelicFixture","use","networkRequestsFile","networkRequestsCount","consoleLogsFile","consoleLogsCount","payload","PAYLOAD_VERSION","ATTACHMENT_NAME","ATTACHMENT_CONTENT_TYPE","apiCallsFile","apiCallsCount","apiStatusMix","apiAssertions","expect","testRelicApiFixture","consoleWriter","consoleLogCount","origStdoutWrite","origStderrWrite","chunk"],"mappings":"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","file":"api-fixture.cjs","sourcesContent":["/**\n * JSONL (JSON Lines) streaming writer and reader.\n *\n * Writer: Appends one JSON object per line to a file using a persistent fd.\n * Used in worker processes to stream network requests, console logs,\n * and API calls to disk instead of accumulating in memory.\n *\n * Reader: Reads a specific page of lines from a JSONL file without loading\n * the entire file into memory.\n */\n\nimport { openSync, writeSync, closeSync, unlinkSync, mkdirSync, createReadStream } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport { createInterface } from 'node:readline';\n\nconst TESTRELIC_TMP_DIR = join(tmpdir(), 'testrelic-data');\n\n/**\n * Append-only JSONL file writer.\n *\n * Keeps a file descriptor open for the lifetime of the writer.\n * Each `append()` call writes one JSON-serialized line synchronously,\n * so data hits disk immediately and is eligible for GC.\n */\nexport class JsonlFileWriter {\n private readonly filePath: string;\n private readonly fd: number;\n private count = 0;\n private closed = false;\n\n constructor(prefix: string) {\n mkdirSync(TESTRELIC_TMP_DIR, { recursive: true });\n this.filePath = join(TESTRELIC_TMP_DIR, `${prefix}-${randomUUID().substring(0, 8)}.jsonl`);\n this.fd = openSync(this.filePath, 'w');\n }\n\n /** Serialize `obj` as a single JSON line and write to disk. */\n append(obj: unknown): void {\n if (this.closed) return;\n const line = JSON.stringify(obj) + '\\n';\n writeSync(this.fd, line);\n this.count++;\n }\n\n /** Absolute path to the JSONL file. */\n getPath(): string {\n return this.filePath;\n }\n\n /** Number of lines written so far. */\n getCount(): number {\n return this.count;\n }\n\n /** Close the file descriptor. Must be called before the file is moved. */\n close(): void {\n if (this.closed) return;\n this.closed = true;\n try {\n closeSync(this.fd);\n } catch {\n // Ignore close errors (fd may already be closed)\n }\n }\n\n /** Remove the temporary file from disk. */\n cleanup(): void {\n try {\n unlinkSync(this.filePath);\n } catch {\n // File may already be moved/deleted\n }\n }\n}\n\n/**\n * Read a page of items from a JSONL file.\n *\n * Uses `readline` to stream through the file line by line.\n * Only parses lines within the requested page range.\n * Skips lines before the page start without parsing them.\n *\n * @param filePath — Absolute path to the JSONL file\n * @param page — 1-based page number\n * @param pageSize — Number of items per page\n * @param knownTotal — If provided, avoids counting remaining lines after the page\n */\nexport async function readJsonlPage<T>(\n filePath: string,\n page: number,\n pageSize: number,\n knownTotal?: number,\n): Promise<{ items: T[]; total: number; page: number; pageSize: number; totalPages: number }> {\n const skip = (page - 1) * pageSize;\n const items: T[] = [];\n let lineCount = 0;\n\n const rl = createInterface({\n input: createReadStream(filePath, { encoding: 'utf-8' }),\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (line.length === 0) continue;\n if (lineCount >= skip && items.length < pageSize) {\n try {\n items.push(JSON.parse(line) as T);\n } catch {\n // Skip malformed lines\n }\n }\n lineCount++;\n // Early exit if we have all items and know the total\n if (items.length >= pageSize && knownTotal !== undefined) {\n break;\n }\n }\n\n const total = knownTotal ?? lineCount;\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n return { items, total, page, pageSize, totalPages };\n}\n","/**\n * Strip terminal colour codes from text bound for the HTML report.\n *\n * Playwright formats assertion failures for a terminal, so the message a\n * reporter receives carries SGR escape sequences. The report rendered them\n * verbatim, which is why a failure panel read:\n *\n * Error: ESC[2mexpect(ESC[22mESC[31mpageESC[39m...\n *\n * and the Playwright call log came out sprayed with `[2m` / `[22m`. The escape\n * character survives JSON and `textContent` untouched — only a terminal ever\n * consumed it.\n */\n\n/** ESC (0x1B) and the single-byte CSI introducer (0x9B). */\nconst ESC = '\\u001B';\nconst CSI = '\\u009B';\n\n/**\n * ANSI escape sequences, both the modern CSI form and the older two-byte one.\n *\n * Deliberately broader than colour codes alone: cursor moves and erase\n * sequences turn up in wrapped tool output too, and none of them mean anything\n * in HTML.\n */\nconst ANSI_PATTERN = new RegExp(\n `[${ESC}${CSI}][[\\\\]()#;?]*(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[0-9A-PR-TZcf-nqry=><]`,\n 'g',\n);\n\n/**\n * Remove ANSI escape sequences from a string.\n *\n * Returns the input unchanged when there is nothing to strip. That fast path\n * matters: this runs on every captured step title in a large suite, and the\n * overwhelming majority contain no escapes at all.\n */\nexport function stripAnsi(text: string): string {\n if (!text) return text;\n if (text.indexOf(ESC) === -1 && text.indexOf(CSI) === -1) return text;\n return text.replace(ANSI_PATTERN, '');\n}\n\n/** Strip, tolerating null and undefined so call sites stay uncluttered. */\nexport function stripAnsiSafe(text: string | null | undefined): string | null {\n if (text === null || text === undefined) return null;\n return stripAnsi(text);\n}\n","/**\n * AssertionTracker — Captures and links assertions to API calls\n *\n * Records every assertion made on API response data (both passing and failing),\n * links each to the originating API call by call ID, and flushes the data to\n * testInfo.annotations for the reporter to consume.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { ApiAssertion, AssertionType, AssertionLocation } from '@testrelic/core';\nimport { stripAnsi } from './ansi.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Symbol used to tag APIResponse objects with their call ID. */\nexport const CALL_ID_SYMBOL: unique symbol = Symbol.for('__testrelic_call_id');\n\n/** Annotation type for assertion data transport (fixture → reporter). */\nexport const ASSERTION_ANNOTATION_TYPE = '__testrelic_api_assertions';\n\n/** WeakMap to track call IDs for object values extracted from responses. */\nexport const valueCallIdMap = new WeakMap<object, string>();\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// AssertionTracker\n// ---------------------------------------------------------------------------\n\nexport class AssertionTracker {\n private assertions: ApiAssertion[] = [];\n private currentCallId: string | null = null;\n\n /** Record a captured assertion. */\n recordAssertion(assertion: ApiAssertion): void {\n this.assertions.push(assertion);\n }\n\n /** Get all recorded assertions. */\n getAssertions(): readonly ApiAssertion[] {\n return this.assertions;\n }\n\n /** Set the most recent API call ID for temporal fallback linking. */\n setCurrentCallId(callId: string): void {\n this.currentCallId = callId;\n }\n\n /** Get the most recent API call ID. */\n getCurrentCallId(): string | null {\n return this.currentCallId;\n }\n\n /** Return assertion data for consolidated payload. */\n getData(): ApiAssertion[] {\n return [...this.assertions];\n }\n\n /** Flush assertion data to testInfo annotations (legacy). */\n flushLegacyAnnotations(testInfo: TestInfoLike): void {\n if (this.assertions.length === 0) return;\n\n testInfo.annotations.push({\n type: ASSERTION_ANNOTATION_TYPE,\n description: JSON.stringify(this.assertions),\n });\n }\n\n /** Clear all state for garbage collection. */\n dispose(): void {\n this.assertions = [];\n this.currentCallId = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Location Capture\n// ---------------------------------------------------------------------------\n\n/** Cache for source file lines to avoid repeated reads. */\nconst sourceCache = new Map<string, string[]>();\n\nfunction getSourceLines(filePath: string): string[] | null {\n const cached = sourceCache.get(filePath);\n if (cached) return cached;\n\n try {\n // V8 stack frames are file:// URLs under ESM (and always on Windows, e.g.\n // `file:///C:/…`). readFileSync needs a filesystem path, so normalize first\n // — otherwise the source line (and thus the assertion expression + type)\n // can't be read and every assertion degrades to a generic \"custom\" one.\n const path = filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath;\n const content = readFileSync(path, 'utf-8');\n const lines = content.split('\\n');\n sourceCache.set(filePath, lines);\n return lines;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the call stack to find the assertion location in user test code.\n * Returns the first stack frame that is NOT inside the SDK's own source files.\n */\n/**\n * The directory this module ended up in.\n *\n * The package is bundled per entry point with `splitting: false`, so at\n * runtime this code lives in `fixture.cjs` / `api-fixture.cjs` / etc., never\n * in a file called `assertion-tracker`. Matching on the source filename\n * therefore skipped nothing, and every captured location pointed at a line of\n * our own wrapper instead of the user's test.\n */\nconst OWN_STACK_DIR: string | null = (() => {\n const stack = new Error().stack;\n if (!stack) return null;\n for (const line of stack.split('\\n')) {\n const m = line.match(/at\\s+(?:.*?\\s+\\()?(.+?):\\d+:\\d+\\)?$/);\n if (!m || m[1].startsWith('node:')) continue;\n // Source maps rewrite bundled frames back to their ORIGINAL source\n // paths, so the frame to skip can name any module in this package, not\n // only this one. The directory they share is the reliable marker.\n const cut = Math.max(m[1].lastIndexOf('/'), m[1].lastIndexOf('\\\\'));\n return cut > 0 ? m[1].slice(0, cut + 1) : null;\n }\n return null;\n})();\n\n/** A stack-frame path as a plain filesystem path. */\nfunction toPlainPath(file: string): string {\n if (!file.startsWith('file:')) return file;\n try { return fileURLToPath(file); } catch { return file; }\n}\n\nfunction captureAssertionLocation(): AssertionLocation | null {\n const err = new Error();\n // SAFETY: Error.stack is V8-specific but guaranteed on Node.js 18+\n const stack = err.stack;\n if (!stack) return null;\n\n const lines = stack.split('\\n');\n\n // Two passes. The first skips our own code; the second skips only the\n // machinery that is ours by name, so a stack the first pass rejects\n // entirely still yields a real file and line rather than `unknown:0`.\n return findFrame(lines, true) ?? findFrame(lines, false);\n}\n\n/**\n * First frame that is not ours.\n *\n * `excludeOwnDir` is what separates the two passes. Note what is deliberately\n * NOT here: a filter on our own source filenames. `'/assertion-tracker.'`\n * used to be in this list, and it matches the path of any file whose name\n * merely STARTS that way — including this module's own test,\n * `assertion-tracker.test.ts`. On Windows the separator is a backslash so the\n * substring never matched and the filter looked harmless; on POSIX it dropped\n * the very frame being looked for, every frame was skipped, and the location\n * came back as the `unknown` fallback. `OWN_STACK_DIR` identifies our code by\n * the directory it actually runs from, which is both correct and not\n * defeatable by what a user names a test file.\n */\nfunction findFrame(lines: readonly string[], excludeOwnDir: boolean): AssertionLocation | null {\n for (const line of lines) {\n if (\n (excludeOwnDir && OWN_STACK_DIR !== null && line.includes(OWN_STACK_DIR)) ||\n line.includes('node_modules') ||\n line.includes('node:internal') ||\n line.includes(' at new Error') ||\n line.includes(' at captureAssertionLocation') ||\n line.includes(' at findFrame') ||\n line.includes(' at expectWrapper') ||\n line.includes(' at wrappedMatcher')\n ) {\n continue;\n }\n\n // Match V8 stack frame: \" at functionName (file:line:column)\"\n // or \" at file:line:column\"\n const match = line.match(/at\\s+(?:.*?\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/);\n if (match) {\n return {\n // ESM frames are file:// URLs and CJS frames are plain paths. Callers\n // compare this against paths from Playwright, so normalise here.\n file: toPlainPath(match[1]),\n line: parseInt(match[2], 10),\n column: parseInt(match[3], 10),\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Type Detection\n// ---------------------------------------------------------------------------\n\n/**\n * Infer the assertion type from the source expression and the value being asserted.\n */\nfunction inferAssertionType(\n expression: string | undefined,\n // SAFETY: value is the raw asserted value from user code — may be any type\n _value: unknown,\n): AssertionType {\n if (!expression) return 'custom';\n\n const expr = expression.toLowerCase();\n\n if (expr.includes('.status()') && !expr.includes('.statustext()')) {\n return expr.includes('.ok()') ? 'statusOk' : 'status';\n }\n if (expr.includes('.ok()')) return 'statusOk';\n if (expr.includes('.headers()') || expr.includes('.headersarray()')) return 'header';\n if (expr.includes('tomatchobject') || expr.includes('toequal')) return 'bodyMatch';\n if (expr.includes('tocontain') || expr.includes('tostringcontaining')) return 'bodyContains';\n\n // If expression references json() or text() it's a body field\n if (expr.includes('.json()') || expr.includes('.text()')) return 'bodyField';\n\n // If none of the above match, default to bodyField for parsed data, or custom\n return 'custom';\n}\n\n// ---------------------------------------------------------------------------\n// Assertion-Aware Expect Wrapper\n// ---------------------------------------------------------------------------\n\n/** Minimal interface for ApiRequestTracker to avoid circular imports. */\ninterface ApiTrackerLike {\n readonly lastCallId: string | null;\n getCallIdForValue(value: unknown): string | null;\n}\n\n/**\n * Creates a wrapped `expect` that captures assertion metadata and records it\n * via the AssertionTracker. Preserves all original Playwright expect behavior.\n */\nfunction buildExpectWrapper(\n getTracker: () => AssertionTracker | null,\n getApiTracker: () => ApiTrackerLike | null,\n // SAFETY: originalExpect is the Playwright expect — typed as Function to avoid\n // importing @playwright/test in this module's runtime\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n const wrappedExpect = function expectWrapper(received: unknown, ...rest: unknown[]): unknown {\n // Resolve the assertion context (explicit per-instance, or the active\n // per-test context set by the fixture). With no context, this is a\n // transparent pass-through to the raw Playwright expect.\n const tracker = getTracker();\n const apiTracker = getApiTracker();\n if (!tracker || !apiTracker) {\n return (originalExpect as Function)(received, ...rest);\n }\n\n // Determine call ID from the value being asserted\n let callId: string | null = null;\n\n // Strategy 1: Check if the value itself is tagged (response object)\n if (received !== null && received !== undefined && typeof received === 'object') {\n const tagged = (received as Record<symbol, string>)[CALL_ID_SYMBOL];\n if (typeof tagged === 'string') {\n callId = tagged;\n }\n }\n\n // Strategy 2: Check if value is in the WeakMap (extracted from response)\n if (!callId && received !== null && received !== undefined && typeof received === 'object') {\n callId = valueCallIdMap.get(received as object) ?? null;\n }\n\n // Strategy 3: Check via the API tracker's value-to-callId mapping\n if (!callId) {\n callId = apiTracker.getCallIdForValue(received);\n }\n\n // Strategy 4: Temporal fallback — use the most recent API call\n if (!callId) {\n callId = tracker.getCurrentCallId();\n }\n\n // If no call ID found, this assertion is not related to an API call — skip capture\n if (!callId) {\n return (originalExpect as Function)(received, ...rest);\n }\n\n // Capture location before the assertion runs\n const location = captureAssertionLocation();\n\n // Read the source expression (best-effort)\n let expression: string | undefined;\n if (location) {\n const lines = getSourceLines(location.file);\n if (lines && location.line > 0 && location.line <= lines.length) {\n expression = lines[location.line - 1].trim();\n }\n }\n\n // Get the matchers object from the original expect\n const matchers = (originalExpect as Function)(received, ...rest);\n\n // If no callId at this point, just return the matchers directly\n if (!callId) return matchers;\n\n // Wrap the matchers to intercept assertion calls\n return createMatcherProxy(matchers as object, {\n tracker,\n callId,\n received,\n location: location ?? { file: 'unknown', line: 0 },\n expression,\n });\n };\n\n // Copy static methods from originalExpect (e.g., expect.soft, expect.configure, etc.)\n //\n // SAFETY: both sides are functions, which have no index signature, so\n // reading and writing arbitrary statics needs a widening step. The keys come\n // from `Object.keys` of the original, never from user input.\n const statics = originalExpect as unknown as Record<string, unknown>;\n const target = wrappedExpect as unknown as Record<string, unknown>;\n for (const key of Object.keys(statics)) {\n target[key] = statics[key];\n }\n\n return wrappedExpect;\n}\n\n/**\n * Per-instance assertion-aware expect — records into the explicitly-provided\n * tracker. Used by unit tests and any caller holding tracker references.\n */\nexport function createAssertionAwareExpect(\n tracker: AssertionTracker,\n apiTracker: ApiTrackerLike,\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n return buildExpectWrapper(() => tracker, () => apiTracker, originalExpect);\n}\n\n// ---------------------------------------------------------------------------\n// Active assertion context (fixture → exported `expect`)\n//\n// Fixtures export a single module-level `expect`, but the trackers are created\n// per-test inside the fixture. Playwright runs tests serially within a worker,\n// so the fixture sets an \"active context\" at test start and clears it at test\n// end; the exported `expect` records into whichever test is currently running.\n// ---------------------------------------------------------------------------\n\n/**\n * The active context lives on `globalThis`, not in a module variable.\n *\n * The package ships several entry points (`/fixture`, `/api-fixture`,\n * `/visual`) built with `splitting: false`, so each bundle carries its OWN\n * copy of this module — and its own module-level variables. The documented\n * setup imports `test` from one entry and `expect` from another, which meant\n * the fixture set the context in one copy while `expect` read `null` from\n * another and silently passed every assertion straight through. A single\n * process-wide slot is what makes the two halves meet.\n */\ninterface AssertionContextSlot {\n tracker: AssertionTracker | null;\n apiTracker: ApiTrackerLike | null;\n}\n\nconst ACTIVE_CONTEXT_KEY = Symbol.for('testrelic.activeAssertionContext');\n\nfunction contextSlot(): AssertionContextSlot {\n const g = globalThis as unknown as Record<symbol, AssertionContextSlot | undefined>;\n let slot = g[ACTIVE_CONTEXT_KEY];\n if (!slot) {\n slot = { tracker: null, apiTracker: null };\n g[ACTIVE_CONTEXT_KEY] = slot;\n }\n return slot;\n}\n\n/** Set (or clear, with nulls) the active per-test assertion context. */\nexport function setActiveAssertionContext(\n tracker: AssertionTracker | null,\n apiTracker: ApiTrackerLike | null,\n): void {\n const slot = contextSlot();\n slot.tracker = tracker;\n slot.apiTracker = apiTracker;\n}\n\n/**\n * Assertion-aware expect that records into whichever tracker is currently\n * active (see setActiveAssertionContext). Falls back to raw expect when none —\n * so it is safe to construct once at module load and import everywhere.\n */\nexport function createActiveAssertionAwareExpect(\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n return buildExpectWrapper(\n () => contextSlot().tracker,\n () => contextSlot().apiTracker,\n originalExpect,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Matcher Proxy\n// ---------------------------------------------------------------------------\n\ninterface MatcherContext {\n tracker: AssertionTracker;\n callId: string;\n received: unknown;\n location: AssertionLocation;\n expression: string | undefined;\n}\n\n/**\n * Creates a Proxy around the matchers object returned by expect().\n * Intercepts matcher method calls (toBe, toEqual, etc.) to record assertions.\n */\nfunction createMatcherProxy(matchers: object, ctx: MatcherContext): object {\n return new Proxy(matchers, {\n get(target: object, prop: string | symbol, receiver: unknown): unknown {\n const value = Reflect.get(target, prop, receiver);\n\n // Only intercept function calls (matcher methods)\n if (typeof value !== 'function') return value;\n\n // Skip internal Playwright properties\n if (typeof prop === 'symbol') return value;\n\n // Handle .not — return a new proxy with negation\n if (prop === 'not') {\n const negated = value;\n return createMatcherProxy(\n negated as object,\n ctx,\n );\n }\n\n // Wrap the matcher function to capture the assertion\n return function wrappedMatcher(this: unknown, ...args: unknown[]): unknown {\n const expected = args[0];\n let status: 'passed' | 'failed' = 'passed';\n let actual: unknown = ctx.received;\n let thrownError: unknown = null;\n\n try {\n const result = (value as Function).apply(this ?? target, args);\n\n // If result is a Promise (async matchers), handle it\n if (result && typeof result === 'object' && typeof (result as { then?: unknown }).then === 'function') {\n return (result as Promise<unknown>).then(\n () => {\n recordAssertionResult(ctx, prop, expected, actual, 'passed');\n },\n (err: unknown) => {\n recordAssertionResult(ctx, prop, expected, actual, 'failed');\n throw err;\n },\n );\n }\n\n return result;\n } catch (err: unknown) {\n status = 'failed';\n thrownError = err;\n\n // Try to extract actual value from the error message\n if (err instanceof Error && err.message) {\n const receivedMatch = err.message.match(/Received:\\s*(.+)/);\n if (receivedMatch) {\n actual = receivedMatch[1];\n }\n }\n\n throw err;\n } finally {\n // Only record sync assertions here; async assertions are handled above\n if (thrownError !== null || status === 'passed') {\n recordAssertionResult(ctx, prop, expected, actual, status);\n }\n }\n };\n },\n });\n}\n\nfunction recordAssertionResult(\n ctx: MatcherContext,\n matcherName: string,\n expected: unknown,\n actual: unknown,\n status: 'passed' | 'failed',\n): void {\n const assertionType = inferAssertionType(ctx.expression, ctx.received);\n\n const assertion: ApiAssertion = {\n callId: ctx.callId,\n type: assertionType,\n expected: safeSerializable(expected),\n actual: safeSerializable(actual),\n status,\n location: ctx.location,\n expression: ctx.expression,\n };\n\n ctx.tracker.recordAssertion(assertion);\n}\n\n/**\n * Ensure a value is safely serializable to JSON.\n * Converts non-serializable values to string representations.\n */\nfunction safeSerializable(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n // Values scraped out of a matcher's error message carry Playwright's ANSI\n // colour codes. They are invisible in a terminal and garbage everywhere\n // else — the HTML report, the JSON file and the cloud payload all showed\n // \"\\u001b[31m100\\u001b[39m\" where the received value should be.\n if (typeof value === 'string') return stripAnsi(value);\n if (typeof value === 'number' || typeof value === 'boolean') return value;\n try {\n JSON.stringify(value);\n return value;\n } catch {\n return String(value);\n }\n}\n","/**\n * API data redaction — header values and recursive body field redaction.\n *\n * Separate from redaction.ts (pattern-based string redaction).\n * This module operates on structured data: header name→value pairs\n * and JSON object field names at any nesting depth.\n */\n\nconst REDACTED = '[REDACTED]';\n\n/**\n * Redact header values by name (case-insensitive).\n * Returns a new object with matching header values replaced by \"[REDACTED]\".\n */\nexport function redactHeaders(\n headers: Record<string, string> | null,\n redactList: readonly string[],\n): Record<string, string> | null {\n if (headers === null || redactList.length === 0) return headers;\n\n const lowerList = new Set(redactList.map((h) => h.toLowerCase()));\n const result: Record<string, string> = {};\n\n for (const key of Object.keys(headers)) {\n // `Object.hasOwn` is ES2022 and this package pins `lib: [\"ES2020\"]` on\n // purpose. The prototype call is the same check with no lib requirement.\n if (Object.prototype.hasOwnProperty.call(headers, key)) {\n result[key] = lowerList.has(key.toLowerCase()) ? REDACTED : headers[key];\n }\n }\n\n return result;\n}\n\n/**\n * Redact body field values by name (recursive, any depth).\n * Parses JSON, walks object/array, replaces matching field values with \"[REDACTED]\".\n * Non-JSON bodies are returned as-is.\n */\nexport function redactBodyFields(\n body: string | null,\n redactList: readonly string[],\n): string | null {\n if (body === null || redactList.length === 0) return body;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n // Not JSON — return as-is (FR-019)\n return body;\n }\n\n if (typeof parsed !== 'object' || parsed === null) return body;\n\n // Field matching is case-insensitive: a config entry of `token` must also\n // catch `Token`, `TOKEN`, `Access_Token`, etc. so casing can't smuggle a\n // secret past the redactor.\n const fieldSet = new Set(redactList.map((f) => f.toLowerCase()));\n const redacted = walkAndRedact(parsed, fieldSet);\n return JSON.stringify(redacted);\n}\n\n/**\n * Recursively walk an object/array and redact matching field values.\n * Field names are compared case-insensitively (lowercased).\n */\nfunction walkAndRedact(value: unknown, fields: Set<string>): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => walkAndRedact(item, fields));\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (!Object.prototype.hasOwnProperty.call(value, key)) continue;\n const val = (value as Record<string, unknown>)[key];\n if (fields.has(key.toLowerCase())) {\n result[key] = REDACTED;\n } else {\n result[key] = walkAndRedact(val, fields);\n }\n }\n return result;\n }\n\n return value;\n}\n\n/**\n * Query-string parameters whose values are commonly secrets. Their values are\n * stripped from any URL we store so tokens don't leak via the request URL\n * (which is persisted and rendered verbatim in the report/cloud).\n */\nconst SECRET_QUERY_PARAMS = new Set(['access_token', 'api_key', 'token', 'sig']);\n\n/**\n * Remove known secret query parameters (access_token, api_key, token, sig)\n * from a URL. Matching is case-insensitive on the parameter name. Returns the\n * original string unchanged when it isn't a parseable absolute URL or when no\n * secret params are present (so URLs aren't needlessly re-serialized).\n */\nexport function stripSecretQueryParams(rawUrl: string): string {\n if (!rawUrl) return rawUrl;\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n // Not an absolute URL — leave it untouched.\n return rawUrl;\n }\n let changed = false;\n for (const key of [...url.searchParams.keys()]) {\n if (SECRET_QUERY_PARAMS.has(key.toLowerCase())) {\n url.searchParams.delete(key);\n changed = true;\n }\n }\n return changed ? url.toString() : rawUrl;\n}\n","/**\n * API URL filtering — include/exclude patterns for API call tracking.\n *\n * Supports glob strings and RegExp objects. Exclude takes precedence over include.\n */\n\n/**\n * Determines whether an API call URL should be tracked.\n *\n * - No filters → track all\n * - Include set → URL must match at least one include pattern\n * - Exclude set → URL must not match any exclude pattern\n * - Both set → exclude takes precedence\n */\nexport function shouldTrackApiUrl(\n url: string,\n include: readonly (string | RegExp)[],\n exclude: readonly (string | RegExp)[],\n): boolean {\n // Check exclude first (takes precedence)\n if (exclude.length > 0) {\n for (const pattern of exclude) {\n if (matchesPattern(url, pattern)) return false;\n }\n }\n\n // Check include\n if (include.length > 0) {\n for (const pattern of include) {\n if (matchesPattern(url, pattern)) return true;\n }\n return false; // Include set but no match → don't track\n }\n\n return true; // No filters → track all\n}\n\n/**\n * Test a URL against a single pattern (glob string or RegExp).\n */\nfunction matchesPattern(url: string, pattern: string | RegExp): boolean {\n try {\n if (pattern instanceof RegExp) {\n return pattern.test(url);\n }\n // Convert glob to regex\n const regex = globToRegex(pattern);\n return regex.test(url);\n } catch {\n // Invalid pattern — skip with warning\n console.warn(`[testrelic] Invalid URL filter pattern: ${String(pattern)}`);\n return false;\n }\n}\n\n/**\n * Convert a glob pattern to a RegExp.\n * Handles `**` (match anything including /) and `*` (match anything except /).\n */\nfunction globToRegex(glob: string): RegExp {\n let result = '';\n let i = 0;\n while (i < glob.length) {\n const char = glob[i];\n if (char === '*' && glob[i + 1] === '*') {\n result += '.*';\n i += 2;\n // Skip optional trailing /\n if (glob[i] === '/') i++;\n } else if (char === '*') {\n result += '[^/]*';\n i++;\n } else if (char === '?') {\n result += '[^/]';\n i++;\n } else if ('.+^${}()|[]\\\\'.includes(char)) {\n result += '\\\\' + char;\n i++;\n } else {\n result += char;\n i++;\n }\n }\n return new RegExp(result);\n}\n","/**\n * ApiRequestTracker — Proxy wrapper for Playwright's APIRequestContext\n *\n * Intercepts all HTTP method calls (.get, .post, .put, .patch, .delete,\n * .head, .fetch, .dispose) to record detailed API call analytics.\n * Data is flushed to testInfo.annotations for the reporter to consume.\n */\n\nimport { performance } from 'node:perf_hooks';\nimport type { APIRequestContext, APIResponse } from '@playwright/test';\nimport type { ApiCallRecord, ApiCallsByStatusRange, CapturedCookie } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\nimport { CALL_ID_SYMBOL, valueCallIdMap } from './assertion-tracker.js';\nimport type { AssertionTracker } from './assertion-tracker.js';\nimport type { ResolvedApiConfig } from './config.js';\nimport { redactHeaders, redactBodyFields, stripSecretQueryParams } from './api-redactor.js';\nimport { shouldTrackApiUrl } from './api-url-filter.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'fetch'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nconst TEXT_CONTENT_TYPES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\nconst ANNOTATION_TYPE = '__testrelic_api_calls';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isTextContentType(contentType: string): boolean {\n const lower = contentType.toLowerCase();\n return TEXT_CONTENT_TYPES.some((prefix) => lower.includes(prefix));\n}\n\n/**\n * Extract request body from Playwright request options.\n * Handles `data` (string/object/Buffer), `form` (key-value), and\n * `multipart` (field descriptors) cases.\n */\nfunction extractRequestBody(options?: Record<string, unknown> | null): string | null {\n if (!options) return null;\n\n if (options.data !== undefined && options.data !== null) {\n const data = options.data;\n if (typeof data === 'string') return data;\n if (Buffer.isBuffer(data)) return data.toString('base64');\n return JSON.stringify(data);\n }\n\n if (options.form !== undefined && options.form !== null) {\n return JSON.stringify(options.form);\n }\n\n if (options.multipart !== undefined && options.multipart !== null) {\n // Capture field names and metadata, not full binary contents\n const multipart = options.multipart as Record<string, unknown>;\n const descriptor: Record<string, string> = {};\n for (const [key, value] of Object.entries(multipart)) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n descriptor[key] = String(value);\n } else if (value && typeof value === 'object' && 'name' in value) {\n descriptor[key] = `[file: ${(value as { name: string }).name}]`;\n } else {\n descriptor[key] = '[binary]';\n }\n }\n return JSON.stringify(descriptor);\n }\n\n return null;\n}\n\n/** Mask a cookie value so raw session tokens never reach the cloud. */\nfunction maskCookieValue(value: string): string {\n return value ? '••••' : '';\n}\n\n/** Parse a request `Cookie` header (`a=1; b=2`) into masked cookies. */\nfunction parseRequestCookies(headers: Record<string, string> | null | undefined): CapturedCookie[] {\n if (!headers) return [];\n let raw: string | undefined;\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === 'cookie') {\n raw = v;\n break;\n }\n }\n if (!raw) return [];\n const out: CapturedCookie[] = [];\n for (const part of raw.split(';')) {\n const seg = part.trim();\n if (!seg) continue;\n const eq = seg.indexOf('=');\n const name = eq === -1 ? seg : seg.slice(0, eq).trim();\n const value = eq === -1 ? '' : seg.slice(eq + 1).trim();\n if (name) out.push({ name, value: maskCookieValue(value) });\n }\n return out;\n}\n\n/** Parse `Set-Cookie` response header values into masked cookies + attributes. */\nfunction parseResponseCookies(setCookieValues: string[]): CapturedCookie[] {\n const out: CapturedCookie[] = [];\n for (const value of setCookieValues) {\n // Playwright's headersArray() usually returns one entry per Set-Cookie, but\n // some runtimes join multiple cookies into one value — split on the\n // `, <token>=` boundary so commas inside Expires=... don't break the parse.\n for (const segRaw of value.split(/,(?=\\s*[A-Za-z0-9_.\\-]+=)/)) {\n const seg = segRaw.trim();\n if (!seg) continue;\n const parts = seg.split(';');\n const eq = parts[0].indexOf('=');\n if (eq === -1) continue;\n const name = parts[0].slice(0, eq).trim();\n const cookieValue = parts[0].slice(eq + 1).trim();\n const attributes = parts.slice(1).map((p) => p.trim()).filter(Boolean).join(' · ') || undefined;\n if (name) out.push({ name, value: maskCookieValue(cookieValue), attributes });\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// ApiRequestTracker\n// ---------------------------------------------------------------------------\n\nexport class ApiRequestTracker {\n private readonly context: APIRequestContext;\n private readonly originals: Map<string, (...args: never[]) => unknown> = new Map();\n private apiCallWriter: JsonlFileWriter | null = null;\n /**\n * Running tally of response-status ranges.\n *\n * The records go straight to a JSONL file and are never read back in\n * streaming mode, so anything the grid needs about them has to be counted\n * here, while we still hold the record.\n */\n private readonly statusMix = { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 };\n private callCounter = 0;\n private apiCallCount = 0;\n private disposed = false;\n private readonly assertionTracker: AssertionTracker | null;\n private readonly apiConfig: ResolvedApiConfig;\n private _lastCallId: string | null = null;\n\n /** Map of primitive values to their originating call IDs (for status, ok, etc.). */\n private readonly primitiveCallIds = new Map<unknown, string>();\n\n /** Default API config when none is provided (all capture enabled, default redaction). */\n private static readonly DEFAULT_API_CONFIG: ResolvedApiConfig = Object.freeze({\n trackApiCalls: true,\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n });\n\n constructor(context: APIRequestContext, assertionTracker?: AssertionTracker, apiConfig?: ResolvedApiConfig) {\n this.context = context;\n this.assertionTracker = assertionTracker ?? null;\n this.apiConfig = apiConfig ?? ApiRequestTracker.DEFAULT_API_CONFIG;\n try {\n this.apiCallWriter = new JsonlFileWriter('api-calls');\n } catch {\n // Graceful degradation\n }\n }\n\n /** The most recent API call ID (for temporal fallback linking). */\n get lastCallId(): string | null {\n return this._lastCallId;\n }\n\n /**\n * Get the call ID for a value extracted from a response.\n * Works for objects (via WeakMap) and primitives (via internal Map).\n */\n getCallIdForValue(value: unknown): string | null {\n if (value !== null && value !== undefined && typeof value === 'object') {\n return valueCallIdMap.get(value as object) ?? null;\n }\n return this.primitiveCallIds.get(value) ?? null;\n }\n\n /** Replace HTTP methods and dispose on the context with instrumented wrappers. */\n intercept(): void {\n for (const method of HTTP_METHODS) {\n const original = (this.context[method] as (...args: never[]) => unknown).bind(this.context);\n this.originals.set(method, original);\n (this.context as unknown as Record<string, unknown>)[method] = this.createWrapper(method, original);\n }\n\n // Intercept dispose\n const origDispose = this.context.dispose.bind(this.context);\n this.originals.set('dispose', origDispose as (...args: never[]) => unknown);\n (this.context as unknown as Record<string, unknown>).dispose = async (\n options?: { reason?: string },\n ): Promise<void> => {\n this.disposed = true;\n return origDispose(options);\n };\n }\n\n /** Count one response against the status mix. A null code is a transport error. */\n private tallyStatus(code: number | null): void {\n if (code === null || code <= 0) { this.statusMix.error++; return; }\n if (code >= 500) { this.statusMix['5xx']++; return; }\n if (code >= 400) { this.statusMix['4xx']++; return; }\n if (code >= 300) { this.statusMix['3xx']++; return; }\n if (code >= 200) { this.statusMix['2xx']++; return; }\n this.statusMix.error++;\n }\n\n /** Return file path, count and status mix for the JSONL file of API call records. */\n getFileData(): {\n apiCallsFile: string | null;\n apiCallsCount: number;\n apiStatusMix: ApiCallsByStatusRange;\n } {\n this.apiCallWriter?.close();\n return {\n apiCallsFile: this.apiCallWriter?.getCount() ? this.apiCallWriter.getPath() : null,\n apiCallsCount: this.apiCallWriter?.getCount() ?? 0,\n apiStatusMix: { ...this.statusMix },\n };\n }\n\n /** @deprecated Legacy annotations no longer used — data flows via file-based payloads. */\n flushLegacyAnnotations(_testInfo: TestInfoLike): void {\n // No-op in v2\n }\n\n /** Restore original methods and clear captured data. */\n dispose(): void {\n for (const [method, original] of this.originals) {\n (this.context as unknown as Record<string, unknown>)[method] = original;\n }\n this.originals.clear();\n this.apiCallWriter?.close();\n this.callCounter = 0;\n this.apiCallCount = 0;\n this._lastCallId = null;\n this.primitiveCallIds.clear();\n }\n\n /** Whether the underlying context has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /** Access captured call count (for testing). */\n getCapturedCallCount(): number {\n return this.apiCallCount;\n }\n\n /**\n * Wraps response methods to tag extracted values with the originating call ID.\n * This enables assertion linking for values extracted from responses.\n */\n private tagResponseMethods(response: APIResponse, callId: string): void {\n const self = this;\n\n // Wrap methods that return objects (headers, json)\n const origHeaders = response.headers.bind(response);\n (response as unknown as Record<string, unknown>).headers = function taggedHeaders(): Record<string, string> {\n const result = origHeaders();\n valueCallIdMap.set(result, callId);\n return result;\n };\n\n const origHeadersArray = response.headersArray.bind(response);\n (response as unknown as Record<string, unknown>).headersArray = function taggedHeadersArray(): Array<{ name: string; value: string }> {\n const result = origHeadersArray();\n valueCallIdMap.set(result, callId);\n return result;\n };\n\n const origJson = response.json.bind(response);\n (response as unknown as Record<string, unknown>).json = async function taggedJson(): Promise<unknown> {\n const result = await origJson();\n if (result !== null && result !== undefined && typeof result === 'object') {\n valueCallIdMap.set(result as object, callId);\n }\n return result;\n };\n\n // Wrap methods that return primitives — track in primitiveCallIds\n const origStatus = response.status.bind(response);\n (response as unknown as Record<string, unknown>).status = function taggedStatus(): number {\n const result = origStatus();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origStatusText = response.statusText.bind(response);\n (response as unknown as Record<string, unknown>).statusText = function taggedStatusText(): string {\n const result = origStatusText();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origOk = response.ok.bind(response);\n (response as unknown as Record<string, unknown>).ok = function taggedOk(): boolean {\n const result = origOk();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origText = response.text.bind(response);\n (response as unknown as Record<string, unknown>).text = async function taggedText(): Promise<string> {\n const result = await origText();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origBody = response.body.bind(response);\n (response as unknown as Record<string, unknown>).body = async function taggedBody(): Promise<Buffer> {\n const result = await origBody();\n valueCallIdMap.set(result, callId);\n return result;\n };\n }\n\n private createWrapper(\n method: HttpMethod,\n original: (...args: never[]) => unknown,\n ): (url: string, options?: Record<string, unknown>) => Promise<APIResponse> {\n const self = this;\n\n return async function wrappedMethod(\n url: string,\n options?: Record<string, unknown>,\n ): Promise<APIResponse> {\n // URL filtering: skip tracking if URL doesn't match include/exclude patterns\n if (!shouldTrackApiUrl(url, self.apiConfig.apiIncludeUrls, self.apiConfig.apiExcludeUrls)) {\n return (original as (url: string, options?: Record<string, unknown>) => Promise<APIResponse>)(url, options);\n }\n\n const id = `api-call-${self.callCounter++}`;\n const timestamp = new Date().toISOString();\n const httpMethod = method === 'fetch'\n ? ((options?.method as string) ?? 'GET').toUpperCase()\n : method.toUpperCase();\n const requestBody = extractRequestBody(options);\n const startTime = performance.now();\n\n // Update temporal tracking for assertion linking\n self._lastCallId = id;\n if (self.assertionTracker) {\n self.assertionTracker.setCurrentCallId(id);\n }\n\n let response: APIResponse;\n try {\n response = await (original as (url: string, options?: Record<string, unknown>) => Promise<APIResponse>)(\n url,\n options,\n );\n } catch (err: unknown) {\n // Error path: capture partial record, re-throw\n const endTime = performance.now();\n try {\n const errorRequestBody = self.apiConfig.captureRequestBody\n ? redactBodyFields(requestBody, self.apiConfig.redactBodyFields)\n : null;\n const errorRecord: ApiCallRecord = {\n id,\n timestamp,\n method: httpMethod,\n url: stripSecretQueryParams(url),\n requestHeaders: null,\n requestBody: errorRequestBody,\n responseStatusCode: null,\n responseStatusText: null,\n responseHeaders: null,\n responseBody: null,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: false,\n error: err instanceof Error ? err.message : String(err),\n };\n if (self.apiCallWriter) {\n self.apiCallWriter.append(errorRecord);\n self.apiCallCount++;\n self.tallyStatus(null);\n }\n } catch {\n // FR-016: if recording fails, still re-throw the original exception\n }\n throw err;\n }\n\n // Success path: capture full record with config-driven capture control and redaction\n try {\n const endTime = performance.now();\n const rawResponseHeaders = response.headers();\n const contentType = rawResponseHeaders['content-type'] ?? null;\n const binary = contentType ? !isTextContentType(contentType) : false;\n\n // Capture control: headers\n // For API calls, request headers come from options.headers (user-provided).\n // Playwright's APIResponse doesn't expose the actual sent request headers,\n // so we capture what we can from the options object.\n let capturedRequestHeaders: Record<string, string> | null = null;\n if (self.apiConfig.captureRequestHeaders && options?.headers) {\n capturedRequestHeaders = options.headers as Record<string, string>;\n }\n let capturedResponseHeaders: Record<string, string> | null = null;\n if (self.apiConfig.captureResponseHeaders) {\n capturedResponseHeaders = rawResponseHeaders;\n }\n\n // Capture control: bodies\n let capturedRequestBody: string | null = self.apiConfig.captureRequestBody ? requestBody : null;\n let capturedResponseBody: string | null = null;\n if (self.apiConfig.captureResponseBody) {\n try {\n if (binary) {\n const buf = await response.body();\n capturedResponseBody = buf.toString('base64');\n } else {\n capturedResponseBody = await response.text();\n }\n } catch {\n // Body read failed — record what we have\n }\n }\n\n // Parse cookies BEFORE redaction strips the Cookie/Set-Cookie headers.\n // Values are masked, so the structured fields surface cookie names (and\n // response attributes) for the inspector without leaking session tokens.\n let requestCookies: CapturedCookie[] = [];\n let responseCookies: CapturedCookie[] = [];\n try {\n if (self.apiConfig.captureRequestHeaders) {\n requestCookies = parseRequestCookies(options?.headers as Record<string, string> | undefined);\n }\n if (self.apiConfig.captureResponseHeaders) {\n const setCookieValues = response\n .headersArray()\n .filter((h) => h.name.toLowerCase() === 'set-cookie')\n .map((h) => h.value);\n responseCookies = parseResponseCookies(setCookieValues);\n }\n } catch {\n // Cookie parsing is best-effort — never break capture.\n }\n\n // Apply redaction to captured data (FR-006: before storage)\n capturedRequestHeaders = redactHeaders(capturedRequestHeaders, self.apiConfig.redactHeaders);\n capturedResponseHeaders = redactHeaders(capturedResponseHeaders, self.apiConfig.redactHeaders);\n capturedRequestBody = redactBodyFields(capturedRequestBody, self.apiConfig.redactBodyFields);\n capturedResponseBody = redactBodyFields(capturedResponseBody, self.apiConfig.redactBodyFields);\n\n const record: ApiCallRecord = {\n id,\n timestamp,\n method: httpMethod,\n url: stripSecretQueryParams(response.url()),\n requestHeaders: capturedRequestHeaders,\n requestBody: capturedRequestBody,\n responseStatusCode: response.status(),\n responseStatusText: response.statusText(),\n responseHeaders: capturedResponseHeaders,\n responseBody: capturedResponseBody,\n requestCookies: requestCookies.length ? requestCookies : null,\n responseCookies: responseCookies.length ? responseCookies : null,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: binary,\n error: null,\n };\n if (self.apiCallWriter) {\n self.apiCallWriter.append(record);\n self.apiCallCount++;\n self.tallyStatus(record.responseStatusCode);\n }\n } catch {\n // FR-016: never crash the test — silently skip recording\n }\n\n // Tag the response with the call ID for assertion linking\n try {\n (response as unknown as Record<symbol, string>)[CALL_ID_SYMBOL] = id;\n self.tagResponseMethods(response, id);\n } catch {\n // Tagging failure is non-critical — temporal fallback will be used\n }\n\n return response;\n };\n }\n}\n","/**\n * NavigationTracker — wraps a Playwright Page to track navigation events.\n *\n * Intercepts page methods (goto, goBack, goForward, reload) and listens\n * for DOM events (framenavigated, domcontentloaded, load) plus injected\n * SPA detection scripts.\n */\n\nimport type { CapturedNetworkRequest, ConsoleLogEntry, ConsoleLogLevel, NavigationAnnotation, NavigationType, NetworkStats, ResourceBreakdown, ResourceType } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\n\n// Minimal Playwright Page type subset for loose coupling\ninterface PageLike {\n goto(url: string, options?: unknown): Promise<unknown>;\n goBack(options?: unknown): Promise<unknown>;\n goForward(options?: unknown): Promise<unknown>;\n reload(options?: unknown): Promise<unknown>;\n url(): string;\n on(event: string, handler: (...args: unknown[]) => void): void;\n off(event: string, handler: (...args: unknown[]) => void): void;\n addInitScript(script: string | { path?: string } | (() => void)): Promise<void>;\n evaluate(fn: (...args: unknown[]) => unknown, ...args: unknown[]): Promise<unknown>;\n mainFrame(): { url(): string };\n}\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\ninterface NavigationRecord {\n url: string;\n navigationType: NavigationType;\n timestamp: string;\n domContentLoadedAt?: string;\n networkIdleAt?: string;\n networkStats?: NetworkStats;\n}\n\ninterface NetworkCounter {\n totalRequests: number;\n failedRequests: number;\n failedRequestUrls: string[];\n totalBytes: number;\n byType: Record<keyof ResourceBreakdown, number>;\n}\n\ninterface PendingRequest {\n url: string;\n method: string;\n resourceType: ResourceType;\n headers: Record<string, string>;\n postData: string | null;\n postDataTruncated: boolean;\n startedAt: string;\n startTimeMs: number;\n /** Index into `records`/`navCounters` of the navigation active when this\n * request STARTED — so its response/failure stats attribute to the right\n * navigation even if they arrive after the next navigation began. */\n navIndex: number;\n}\n\nconst TEXT_CONTENT_TYPES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\nfunction isTextContentType(contentType: string): boolean {\n const lower = contentType.toLowerCase();\n return TEXT_CONTENT_TYPES.some(prefix => lower.includes(prefix));\n}\n\nexport class NavigationTracker {\n private records: NavigationRecord[] = [];\n private listeners: Array<{ event: string; handler: (...args: unknown[]) => void }> = [];\n private origGoto: PageLike['goto'];\n private origGoBack: PageLike['goBack'];\n private origGoForward: PageLike['goForward'];\n private origReload: PageLike['reload'];\n private lastDomContentLoaded: string | undefined;\n private includeNetworkStats: boolean;\n /** One counter per navigation (parallel to `records` by index). A request's\n * stats land in the counter of the nav that issued it, so a response arriving\n * after the next navigation no longer leaks into it. */\n private navCounters: NetworkCounter[] = [];\n private pendingRequests = new Map<string, PendingRequest>();\n private networkWriter: JsonlFileWriter | null = null;\n private consoleWriter: JsonlFileWriter | null = null;\n private pendingBodyReads: Promise<void>[] = [];\n private requestIdCounter = 0;\n private networkRequestCount = 0;\n private consoleLogCount = 0;\n\n constructor(private page: PageLike, options?: { includeNetworkStats?: boolean }) {\n this.includeNetworkStats = options?.includeNetworkStats ?? true;\n // Store originals\n this.origGoto = page.goto.bind(page);\n this.origGoBack = page.goBack.bind(page);\n this.origGoForward = page.goForward.bind(page);\n this.origReload = page.reload.bind(page);\n\n // Initialize JSONL writers for heavy data\n try {\n if (this.includeNetworkStats) {\n this.networkWriter = new JsonlFileWriter('network');\n }\n this.consoleWriter = new JsonlFileWriter('console');\n } catch {\n // Graceful degradation if temp dir is not writable\n }\n\n this.interceptMethods();\n this.attachListeners();\n }\n\n async init(): Promise<void> {\n await this.injectSPADetection();\n }\n\n /** Flush remaining pending requests to the JSONL writer and close it. */\n async finalizeCapturedRequests(): Promise<void> {\n // Wait for all pending body reads to complete\n await Promise.allSettled(this.pendingBodyReads);\n this.pendingBodyReads = [];\n // Convert any remaining pending requests (incomplete) to JSONL entries\n for (const [, pending] of this.pendingRequests) {\n if (this.networkWriter) {\n this.networkWriter.append({\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: 0,\n responseTimeMs: Date.now() - pending.startTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody: null,\n responseHeaders: null,\n contentType: null,\n responseSize: 0,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: false,\n error: 'incomplete',\n } satisfies CapturedNetworkRequest);\n this.networkRequestCount++;\n }\n }\n this.pendingRequests.clear();\n // Close writers so files are ready to be moved\n this.networkWriter?.close();\n this.consoleWriter?.close();\n }\n\n private static mapConsoleType(type: string): ConsoleLogLevel {\n switch (type) {\n case 'log': return 'log';\n case 'warning': return 'warn';\n case 'error': return 'error';\n case 'info': return 'info';\n case 'debug': return 'debug';\n default: return 'log';\n }\n }\n\n async getFileData(): Promise<{ navigations: NavigationAnnotation[]; networkRequestsFile: string | null; networkRequestsCount: number; consoleLogsFile: string | null; consoleLogsCount: number }> {\n // Assemble each navigation's network stats from its OWN counter, so every\n // navigation (not just the last) reflects exactly the requests it issued —\n // including responses that arrived after the next navigation started.\n this.assembleNetworkStats();\n\n const navigations: NavigationAnnotation[] = this.records.map(record => ({\n url: record.url,\n navigationType: record.navigationType,\n timestamp: record.timestamp,\n domContentLoadedAt: record.domContentLoadedAt,\n networkIdleAt: record.networkIdleAt,\n networkStats: record.networkStats,\n }));\n\n // Finalize and close JSONL writers\n if (this.includeNetworkStats) {\n await this.finalizeCapturedRequests();\n } else {\n this.consoleWriter?.close();\n }\n\n return {\n navigations,\n networkRequestsFile: this.networkWriter?.getCount() ? this.networkWriter.getPath() : null,\n networkRequestsCount: this.networkWriter?.getCount() ?? 0,\n consoleLogsFile: this.consoleWriter?.getCount() ? this.consoleWriter.getPath() : null,\n consoleLogsCount: this.consoleWriter?.getCount() ?? 0,\n };\n }\n\n /** @deprecated Legacy annotations no longer used — data flows via file-based payloads. */\n async flushLegacyAnnotations(_testInfo: TestInfoLike): Promise<void> {\n // No-op in v2 — all data flows via getFileData() and JSONL files\n }\n\n dispose(): void {\n // Restore original methods\n const patchable = NavigationTracker.asPatchable(this.page);\n patchable.goto = this.origGoto;\n patchable.goBack = this.origGoBack;\n patchable.goForward = this.origGoForward;\n patchable.reload = this.origReload;\n\n // Remove event listeners\n for (const { event, handler } of this.listeners) {\n this.page.off(event, handler);\n }\n this.listeners = [];\n this.records = [];\n this.pendingRequests.clear();\n this.pendingBodyReads = [];\n // Close writers (files kept for reporter to consume)\n this.networkWriter?.close();\n this.consoleWriter?.close();\n }\n\n getRecords(): readonly NavigationRecord[] {\n // Assemble on demand so each record reflects the latest per-nav counters\n // (late responses keep accumulating into the issuing nav's counter).\n this.assembleNetworkStats();\n return this.records;\n }\n\n /**\n * Treat the page as an indexable object so a method can be replaced.\n *\n * SAFETY: navigation capture swaps four methods on a live Playwright `Page`\n * and restores them in `dispose`. `Page` has no index signature, so the\n * write needs a widening step, and TypeScript prescribes routing a\n * conversion between non-overlapping types through `unknown`. The keys are\n * literals below, never user input.\n */\n private static asPatchable(page: PageLike): Record<string, unknown> {\n return page as unknown as Record<string, unknown>;\n }\n\n private interceptMethods(): void {\n const self = this;\n const page = this.page;\n\n const patchable = NavigationTracker.asPatchable(page);\n\n patchable.goto = async function (url: string, options?: unknown) {\n self.recordNavigation(url, 'goto');\n return self.origGoto(url, options);\n };\n\n patchable.goBack = async function (options?: unknown) {\n const result = await self.origGoBack(options);\n self.recordNavigation(page.url(), 'back');\n return result;\n };\n\n patchable.goForward = async function (options?: unknown) {\n const result = await self.origGoForward(options);\n self.recordNavigation(page.url(), 'forward');\n return result;\n };\n\n patchable.reload = async function (options?: unknown) {\n self.recordNavigation(page.url(), 'refresh');\n return self.origReload(options);\n };\n }\n\n private attachListeners(): void {\n // Track DOMContentLoaded for lifecycle timestamps\n const onDomContentLoaded = () => {\n this.lastDomContentLoaded = new Date().toISOString();\n if (this.records.length > 0) {\n this.records[this.records.length - 1].domContentLoadedAt = this.lastDomContentLoaded;\n }\n };\n this.page.on('domcontentloaded', onDomContentLoaded as (...args: unknown[]) => void);\n this.listeners.push({ event: 'domcontentloaded', handler: onDomContentLoaded as (...args: unknown[]) => void });\n\n // Track main frame navigation for non-intercepted navigations (link clicks, form submits)\n const onFrameNavigated = (frame: unknown) => {\n // Only track main frame\n try {\n const frameObj = frame as { url(): string; parentFrame(): unknown };\n if (typeof frameObj.parentFrame === 'function' && frameObj.parentFrame() !== null) {\n return; // Skip sub-frames\n }\n const url = frameObj.url();\n // Skip if we already recorded this URL in the last 50ms (from method interception)\n const lastRecord = this.records[this.records.length - 1];\n if (lastRecord) {\n const timeDiff = Date.now() - new Date(lastRecord.timestamp).getTime();\n if (timeDiff < 50 && lastRecord.url === url) {\n return; // Duplicate from method interception\n }\n }\n // This is an untracked navigation (link click, form submit, etc.)\n this.recordNavigation(url, 'navigation');\n } catch {\n // Ignore frame navigation errors\n }\n };\n this.page.on('framenavigated', onFrameNavigated);\n this.listeners.push({ event: 'framenavigated', handler: onFrameNavigated });\n\n // Track console messages: SPA detection + full console log capture\n const onConsoleMessage = (msg: unknown) => {\n try {\n const msgObj = msg as { text(): string; type(): string; location(): { url: string; lineNumber: number; columnNumber: number } };\n const type = msgObj.type();\n const text = msgObj.text();\n\n // Internal SPA navigation detection via __testrelic_nav: debug messages\n if (type === 'debug' && text.startsWith('__testrelic_nav:')) {\n try {\n const data = JSON.parse(text.slice('__testrelic_nav:'.length));\n if (data.type && data.url) {\n this.recordNavigation(data.url, data.type as NavigationType);\n }\n } catch {\n // Ignore parse errors\n }\n return;\n }\n\n // Capture all console messages\n {\n let location: string | null = null;\n try {\n const loc = msgObj.location();\n if (loc && loc.url) {\n location = `${loc.url}:${loc.lineNumber}:${loc.columnNumber}`;\n }\n } catch {\n // location() may not be available\n }\n const level = NavigationTracker.mapConsoleType(type);\n if (this.consoleWriter) {\n this.consoleWriter.append({ level, text, timestamp: new Date().toISOString(), location } satisfies ConsoleLogEntry);\n this.consoleLogCount++;\n }\n }\n } catch {\n // Ignore console capture errors\n }\n };\n this.page.on('console', onConsoleMessage);\n this.listeners.push({ event: 'console', handler: onConsoleMessage });\n\n // Network stats tracking + individual request capture\n if (this.includeNetworkStats) {\n const onRequest = (request: unknown) => {\n // Attribute this request to the navigation active right now; its response\n // may arrive after the next navigation starts, but the count belongs here.\n const navIndex = this.records.length - 1;\n const counter = this.counterForNav(navIndex);\n if (counter) {\n counter.totalRequests++;\n }\n // Capture individual request details\n try {\n const req = request as {\n url(): string;\n method(): string;\n resourceType(): string;\n headers(): Record<string, string>;\n postData(): string | null;\n };\n const reqId = String(this.requestIdCounter++);\n const postData = req.postData() ?? null;\n const pending: PendingRequest = {\n url: req.url(),\n method: req.method(),\n resourceType: this.mapResourceType(req.resourceType()),\n headers: req.headers(),\n postData,\n postDataTruncated: false,\n startedAt: new Date().toISOString(),\n startTimeMs: Date.now(),\n navIndex,\n };\n this.pendingRequests.set(reqId, pending);\n // Tag the request object with our ID for lookup in response/failed handlers\n (request as Record<string, unknown>).__testrelic_id = reqId;\n } catch {\n // Ignore request capture errors\n }\n };\n this.page.on('request', onRequest);\n this.listeners.push({ event: 'request', handler: onRequest });\n\n const onResponse = (response: unknown) => {\n try {\n const resp = response as {\n url(): string;\n status(): number;\n headers(): Record<string, string>;\n request(): { resourceType(): string; __testrelic_id?: string };\n body(): Promise<Buffer>;\n };\n // Attribute aggregate stats to the navigation that ISSUED the request\n // (falls back to the current nav when the request wasn't tracked).\n const reqId = (resp.request() as Record<string, unknown>).__testrelic_id as string | undefined;\n const pending = reqId ? this.pendingRequests.get(reqId) : undefined;\n const counter = this.counterForNav(pending ? pending.navIndex : this.records.length - 1);\n if (counter) {\n const status = resp.status();\n if (status >= 400) {\n counter.failedRequests++;\n counter.failedRequestUrls.push(status + ' ' + resp.url());\n }\n const contentLength = resp.headers()['content-length'];\n if (contentLength) {\n counter.totalBytes += parseInt(contentLength, 10) || 0;\n }\n const resourceType = resp.request().resourceType();\n const typeKey = this.mapResourceType(resourceType);\n counter.byType[typeKey]++;\n }\n // Individual request capture\n if (!reqId || !pending) return;\n this.pendingRequests.delete(reqId);\n const responseTimeMs = Date.now() - pending.startTimeMs;\n const respHeaders = resp.headers();\n const contentType = respHeaders['content-type'] ?? null;\n const responseSize = parseInt(respHeaders['content-length'] ?? '0', 10) || 0;\n const binary = contentType ? !isTextContentType(contentType) : false;\n // Read response body asynchronously and write to JSONL (not held in memory)\n const bodyPromise = (async () => {\n let responseBody: string | null = null;\n if (!binary) {\n try {\n const buf = await resp.body();\n responseBody = buf.toString('utf-8');\n } catch {\n // Body unavailable (e.g., redirect)\n }\n }\n const captured: CapturedNetworkRequest = {\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: resp.status(),\n responseTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody,\n responseHeaders: respHeaders,\n contentType,\n responseSize,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: binary,\n error: null,\n };\n // Write to JSONL file — body is released from memory after this\n if (this.networkWriter) {\n this.networkWriter.append(captured);\n this.networkRequestCount++;\n }\n })();\n this.pendingBodyReads.push(bodyPromise);\n } catch {\n // Ignore response processing errors\n }\n };\n this.page.on('response', onResponse);\n this.listeners.push({ event: 'response', handler: onResponse });\n\n const onRequestFailed = (request: unknown) => {\n const reqId = (request as Record<string, unknown>).__testrelic_id as string | undefined;\n const pending = reqId ? this.pendingRequests.get(reqId) : undefined;\n // Attribute the failure to the navigation that issued the request.\n const counter = this.counterForNav(pending ? pending.navIndex : this.records.length - 1);\n if (counter) {\n counter.failedRequests++;\n try {\n const req = request as { url(): string };\n counter.failedRequestUrls.push('ERR ' + req.url());\n } catch {\n // Ignore\n }\n }\n // Individual request capture\n try {\n const req = request as { failure(): { errorText: string } | null };\n if (!reqId || !pending) return;\n this.pendingRequests.delete(reqId);\n const captured: CapturedNetworkRequest = {\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: 0,\n responseTimeMs: Date.now() - pending.startTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody: null,\n responseHeaders: null,\n contentType: null,\n responseSize: 0,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: false,\n error: req.failure()?.errorText ?? 'Unknown error',\n };\n if (this.networkWriter) {\n this.networkWriter.append(captured);\n this.networkRequestCount++;\n }\n } catch {\n // Ignore\n }\n };\n this.page.on('requestfailed', onRequestFailed);\n this.listeners.push({ event: 'requestfailed', handler: onRequestFailed });\n }\n }\n\n private async injectSPADetection(): Promise<void> {\n try {\n await this.page.addInitScript(() => {\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = function (...args: Parameters<typeof origPush>) {\n origPush(...args);\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'spa_route',\n url: location.href,\n }));\n };\n\n history.replaceState = function (...args: Parameters<typeof origReplace>) {\n origReplace(...args);\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'spa_replace',\n url: location.href,\n }));\n };\n\n window.addEventListener('popstate', () => {\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'popstate',\n url: location.href,\n }));\n });\n\n window.addEventListener('hashchange', () => {\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'hash_change',\n url: location.href,\n }));\n });\n });\n } catch {\n // Ignore injection errors (page may be closed)\n }\n }\n\n private recordNavigation(url: string, type: NavigationType): void {\n this.records.push({\n url,\n navigationType: type,\n timestamp: new Date().toISOString(),\n });\n\n // Each navigation gets its own counter at the same index as its record, so\n // late-arriving responses/failures attribute to the nav that issued the\n // request (via PendingRequest.navIndex) instead of leaking to the next nav.\n if (this.includeNetworkStats) {\n this.navCounters.push(this.createNetworkCounter());\n }\n }\n\n private createNetworkCounter(): NetworkCounter {\n return {\n totalRequests: 0,\n failedRequests: 0,\n failedRequestUrls: [],\n totalBytes: 0,\n byType: { xhr: 0, document: 0, script: 0, stylesheet: 0, image: 0, font: 0, other: 0 },\n };\n }\n\n /** The per-navigation counter for a record index, or undefined when the index\n * predates the first navigation (-1) or is out of range. */\n private counterForNav(index: number): NetworkCounter | undefined {\n return index >= 0 ? this.navCounters[index] : undefined;\n }\n\n /** Snapshot each navigation's accumulated counter into record.networkStats.\n * Idempotent — safe to call repeatedly (getRecords / getFileData). */\n private assembleNetworkStats(): void {\n if (!this.includeNetworkStats) return;\n for (let i = 0; i < this.records.length; i++) {\n const counter = this.navCounters[i];\n if (!counter) continue;\n this.records[i].networkStats = {\n totalRequests: counter.totalRequests,\n failedRequests: counter.failedRequests,\n failedRequestUrls: [...counter.failedRequestUrls],\n totalBytes: counter.totalBytes,\n byType: { ...counter.byType },\n };\n }\n }\n\n private mapResourceType(type: string): keyof ResourceBreakdown {\n switch (type) {\n case 'xhr':\n case 'fetch':\n return 'xhr';\n case 'document':\n return 'document';\n case 'script':\n return 'script';\n case 'stylesheet':\n return 'stylesheet';\n case 'image':\n return 'image';\n case 'font':\n return 'font';\n default:\n return 'other';\n }\n }\n}\n","/**\n * Numbers for a visual comparison, recovered without decoding an image.\n *\n * Playwright compares images internally and keeps the result to itself: the\n * differing-pixel count exists only as prose inside the failure message, and\n * the image dimensions only inside the PNG. Rather than decode a PNG (which\n * would mean a dependency, and Principle V says no), both are read back — the\n * count from the message Playwright already formats, the dimensions from the\n * 8 bytes of header every PNG carries.\n */\n\n/** PNG magic number: \\x89 P N G \\r \\n \\x1a \\n */\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\n/** Byte offsets of width and height inside the IHDR chunk of a PNG. */\nconst IHDR_WIDTH_OFFSET = 16;\nconst IHDR_HEIGHT_OFFSET = 20;\n\n/** Enough bytes to cover the signature, the chunk header and both dimensions. */\nexport const PNG_HEADER_BYTES = 24;\n\nexport interface ImageDimensions {\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * Read a PNG's pixel dimensions from its IHDR chunk.\n *\n * Only the first 24 bytes are consulted, so the caller can hand over a partial\n * read of a large file. Returns `null` for anything that is not a PNG — JPEG\n * baselines are legal in Playwright and simply go un-measured.\n */\nexport function readPngDimensions(header: Buffer): ImageDimensions | null {\n if (header.length < PNG_HEADER_BYTES) return null;\n if (!header.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) return null;\n\n const width = header.readUInt32BE(IHDR_WIDTH_OFFSET);\n const height = header.readUInt32BE(IHDR_HEIGHT_OFFSET);\n\n // A zero dimension is not a valid PNG, and would make a ratio divide by zero.\n if (width === 0 || height === 0) return null;\n\n return { width, height };\n}\n\nexport interface ParsedDiffMessage {\n /** Differing pixel count, from `\"N pixels (ratio …) are different.\"` */\n readonly diffPixels?: number;\n /** Baseline dimensions, present only when the two images differ in size. */\n readonly expectedWidth?: number;\n readonly expectedHeight?: number;\n /** Actual dimensions, present only when the two images differ in size. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The snapshot name Playwright echoed, when the assertion supplied one. */\n readonly snapshotName?: string;\n}\n\n/**\n * `123 pixels (ratio 0.02 of all image pixels) are different.`\n *\n * The ratio in that sentence is rounded up to two decimals, so it is read past\n * deliberately — the caller recomputes it from the count and the dimensions.\n */\nconst PIXELS_DIFFERENT_RE = /(\\d+) pixels \\(ratio [\\d.]+ of all image pixels\\) are different/;\n\n/** `Expected an image 800px by 600px, received 800px by 640px.` */\nconst SIZE_MISMATCH_RE = /Expected an image (\\d+)px by (\\d+)px, received (\\d+)px by (\\d+)px/;\n\n/**\n * Whether a failure message is a size mismatch.\n *\n * The comparator cannot draw a diff across differing dimensions, so it\n * attaches expected + actual and throws instead. Callers that would otherwise\n * infer \"no baseline existed\" from a missing diff need this to tell the two\n * apart — without it a real failure was reported as a freshly written\n * baseline.\n */\nexport function isSizeMismatchFailure(message: string | null | undefined): boolean {\n if (!message) return false;\n return SIZE_MISMATCH_RE.test(message);\n}\n\n/** ` Snapshot: home.png` — only emitted when the assertion named the snapshot. */\nconst SNAPSHOT_NAME_RE = /^\\s*Snapshot:\\s*(.+?)\\s*$/m;\n\n/**\n * Pull the comparison numbers out of a Playwright snapshot failure message.\n *\n * Every field is optional and a malformed message yields an empty object: a\n * report that is missing a pixel count is a far better outcome than a reporter\n * that throws while assembling one.\n */\nexport function parseDiffMessage(message: string | null | undefined): ParsedDiffMessage {\n if (!message) return {};\n\n const result: {\n diffPixels?: number;\n expectedWidth?: number;\n expectedHeight?: number;\n actualWidth?: number;\n actualHeight?: number;\n snapshotName?: string;\n } = {};\n\n const pixels = PIXELS_DIFFERENT_RE.exec(message);\n if (pixels) {\n const count = Number.parseInt(pixels[1] as string, 10);\n if (Number.isFinite(count)) result.diffPixels = count;\n }\n\n const sizes = SIZE_MISMATCH_RE.exec(message);\n if (sizes) {\n const [, ew, eh, aw, ah] = sizes;\n const parsed = [ew, eh, aw, ah].map((v) => Number.parseInt(v as string, 10));\n if (parsed.every((v) => Number.isFinite(v))) {\n result.expectedWidth = parsed[0];\n result.expectedHeight = parsed[1];\n result.actualWidth = parsed[2];\n result.actualHeight = parsed[3];\n }\n }\n\n const name = SNAPSHOT_NAME_RE.exec(message);\n if (name) result.snapshotName = name[1];\n\n return result;\n}\n\n/**\n * Differing pixels as a fraction of the image, at full precision.\n *\n * Returns `undefined` rather than a wrong number when either input is missing,\n * so a consumer can tell \"no measurement\" apart from \"measured zero\".\n */\nexport function computeDiffRatio(\n diffPixels: number | undefined,\n dimensions: ImageDimensions | null | undefined,\n): number | undefined {\n if (diffPixels === undefined || !dimensions) return undefined;\n const total = dimensions.width * dimensions.height;\n if (total <= 0) return undefined;\n return diffPixels / total;\n}\n","/// <reference lib=\"dom\" />\n//\n// This one file runs in a browser, so it needs the DOM lib. The package's\n// tsconfig sets `lib: [\"ES2020\"]` on purpose — a reporter that runs in Node has\n// no business reaching for `document` — so the exception is scoped here by a\n// per-file reference rather than opened up package-wide.\n\n/**\n * The DOM walker that runs inside the page.\n *\n * Playwright's own trace snapshotter deliberately captures neither computed\n * styles nor per-element geometry — it records HTML and replays real CSS\n * instead — so it cannot answer \"which element moved, and why\". It is also\n * unreachable: `playwright-core`'s `exports` map does not expose the\n * snapshotter, and it operates on server-side objects a test never holds.\n * Hence this small walker of our own, which captures exactly the two things\n * that are missing.\n *\n * ⛔ `captureDomSnapshot` is handed to `page.evaluate`, which serializes it via\n * `Function.prototype.toString`. It MUST therefore be entirely self-contained:\n * no imports, no references to module scope, no closure captures. Everything it\n * needs arrives in its single argument. A stray outer reference does not fail\n * at build time — it throws inside the browser at runtime.\n */\n\n/** A single captured element. Field names are short: this is written per node. */\nexport interface DomNode {\n /** Stable structural path, id-anchored. `#main>div[0]>span[2]` */\n p: string;\n /** Tag name, lowercase. */\n t: string;\n i?: string;\n c?: string;\n /** Document-space `[x, y, width, height]`, rounded. */\n r: [number, number, number, number];\n /** Direct text content, normalized and truncated. */\n x?: string;\n /** Computed styles, with inherited and default values omitted. */\n s?: Record<string, string>;\n /** Set when the element intersects a mask and is excluded from diffing. */\n m?: 1;\n}\n\nexport interface DomSnapshot {\n readonly version: '1.0';\n readonly url: string;\n readonly viewport: { width: number; height: number };\n readonly content: { width: number; height: number };\n /** The document-space region the screenshot covered. */\n readonly captureBox: [number, number, number, number];\n readonly nodes: DomNode[];\n /** Elements the cap excluded. Counted, never silently dropped. */\n readonly truncatedNodes: number;\n}\n\nexport interface CaptureOptions {\n readonly styleProps: readonly string[];\n /** Property → value that is so common it is not worth storing. */\n readonly defaults: Readonly<Record<string, string>>;\n /** Properties that inherit, stored only when they differ from the parent. */\n readonly inherited: readonly string[];\n readonly maxNodes: number;\n readonly maxText: number;\n /**\n * Boxes to mark as masked, in VIEWPORT coordinates — that is what\n * `locator.boundingBox()` returns on the Node side. Converted to document\n * space in here, where the scroll offset is known.\n */\n readonly maskBoxes: readonly [number, number, number, number][];\n /**\n * The region the screenshot covers, in viewport coordinates, when asserting\n * against a locator rather than the page. Null lets the walker derive it.\n */\n readonly viewportBox: [number, number, number, number] | null;\n /** True when the screenshot was taken with `fullPage`. */\n readonly fullPage: boolean;\n}\n\n/**\n * Walk the rendered DOM and return a flat, diffable snapshot.\n *\n * Flat rather than nested because the path is the match key: diffing two flat\n * arrays keyed by path is a map lookup, where diffing two trees is an edit-\n * distance problem.\n */\nexport function captureDomSnapshot(options: CaptureOptions): DomSnapshot {\n const { styleProps, defaults, inherited, maxNodes, maxText, maskBoxes, viewportBox, fullPage } =\n options;\n\n const inheritedSet: Record<string, true> = {};\n for (let i = 0; i < inherited.length; i++) inheritedSet[inherited[i] as string] = true;\n\n const scrollX = window.scrollX || window.pageXOffset || 0;\n const scrollY = window.scrollY || window.pageYOffset || 0;\n\n const nodes: DomNode[] = [];\n let truncatedNodes = 0;\n\n const docMasks: [number, number, number, number][] = [];\n for (let i = 0; i < maskBoxes.length; i++) {\n const mask = maskBoxes[i] as [number, number, number, number];\n docMasks.push([mask[0] + scrollX, mask[1] + scrollY, mask[2], mask[3]]);\n }\n\n /** Does this element's box overlap anything the user masked? */\n function isMasked(x: number, y: number, w: number, h: number): boolean {\n for (let i = 0; i < docMasks.length; i++) {\n const b = docMasks[i] as [number, number, number, number];\n if (x < b[0] + b[2] && x + w > b[0] && y < b[1] + b[3] && y + h > b[1]) return true;\n }\n return false;\n }\n\n /** Index among same-tag siblings — more stable than nth-child when text moves. */\n function indexAmongSiblings(el: Element): number {\n const parent = el.parentElement;\n if (!parent) return 0;\n const siblings = parent.children;\n let index = 0;\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i] as Element;\n if (sibling === el) break;\n if (sibling.tagName === el.tagName) index++;\n }\n return index;\n }\n\n /** Direct text children only — a parent must not inherit its child's text. */\n function directText(el: Element): string {\n let text = '';\n const children = el.childNodes;\n for (let i = 0; i < children.length; i++) {\n const node = children[i] as Node;\n if (node.nodeType === 3) text += node.nodeValue ?? '';\n }\n text = text.replace(/\\s+/g, ' ').trim();\n return text.length > maxText ? text.slice(0, maxText) : text;\n }\n\n /**\n * Store only what is surprising.\n *\n * An inherited property is recorded only where it differs from the parent, so\n * changing the body font produces one change on `body` rather than the same\n * change echoed on all six hundred of its descendants. A non-inherited\n * property is recorded only where it differs from its initial value, which is\n * what keeps a typical element down to three or four entries instead of\n * thirty.\n */\n function collectStyles(\n computed: CSSStyleDeclaration,\n parentComputed: CSSStyleDeclaration | null,\n ): Record<string, string> | undefined {\n let styles: Record<string, string> | undefined;\n for (let i = 0; i < styleProps.length; i++) {\n const prop = styleProps[i] as string;\n const value = computed.getPropertyValue(prop);\n if (!value) continue;\n if (inheritedSet[prop]) {\n if (parentComputed && parentComputed.getPropertyValue(prop) === value) continue;\n } else if (defaults[prop] === value) {\n continue;\n }\n if (!styles) styles = {};\n styles[prop] = value;\n }\n return styles;\n }\n\n interface Frame {\n el: Element;\n parentPath: string;\n parentComputed: CSSStyleDeclaration | null;\n }\n\n // Explicit stack rather than recursion: a deep page should not risk blowing\n // the call stack inside someone else's test run.\n const stack: Frame[] = [{ el: document.body, parentPath: '', parentComputed: null }];\n\n while (stack.length > 0) {\n const frame = stack.pop() as Frame;\n const el = frame.el;\n const computed = window.getComputedStyle(el);\n\n // display:none removes the whole subtree from rendering, so nothing beneath\n // it can have caused a pixel difference.\n if (computed.display === 'none') continue;\n\n const tag = el.tagName.toLowerCase();\n const path = el.id\n ? '#' + el.id\n : frame.parentPath\n ? frame.parentPath + '>' + tag + '[' + indexAmongSiblings(el) + ']'\n : tag;\n\n // Children are pushed regardless of whether this element is recorded: an\n // invisible or zero-sized wrapper routinely contains visible children.\n const children = el.children;\n for (let i = children.length - 1; i >= 0; i--) {\n stack.push({\n el: children[i] as Element,\n parentPath: path,\n parentComputed: computed,\n });\n }\n\n if (computed.visibility === 'hidden') continue;\n\n const box = el.getBoundingClientRect();\n if (box.width <= 0 || box.height <= 0) continue;\n\n if (nodes.length >= maxNodes) {\n truncatedNodes++;\n continue;\n }\n\n const x = Math.round(box.left + scrollX);\n const y = Math.round(box.top + scrollY);\n const w = Math.round(box.width);\n const h = Math.round(box.height);\n\n const node: DomNode = { p: path, t: tag, r: [x, y, w, h] };\n if (el.id) node.i = el.id;\n const className = el.getAttribute('class');\n if (className) node.c = className.replace(/\\s+/g, ' ').trim();\n\n const text = directText(el);\n if (text) node.x = text;\n\n const styles = collectStyles(computed, frame.parentComputed);\n if (styles) node.s = styles;\n\n if (isMasked(x, y, w, h)) node.m = 1;\n\n nodes.push(node);\n }\n\n const contentWidth = Math.max(document.documentElement.scrollWidth, window.innerWidth);\n const contentHeight = Math.max(document.documentElement.scrollHeight, window.innerHeight);\n\n // Which region of the document the screenshot actually shows. The report\n // positions its boxes as percentages of this, so a locator screenshot lines\n // up against the element it framed rather than against the whole viewport.\n let captureBox: [number, number, number, number];\n if (viewportBox) {\n captureBox = [\n Math.round(viewportBox[0] + scrollX),\n Math.round(viewportBox[1] + scrollY),\n Math.round(viewportBox[2]),\n Math.round(viewportBox[3]),\n ];\n } else if (fullPage) {\n captureBox = [0, 0, contentWidth, contentHeight];\n } else {\n captureBox = [Math.round(scrollX), Math.round(scrollY), window.innerWidth, window.innerHeight];\n }\n\n return {\n version: '1.0',\n url: location.href,\n viewport: { width: window.innerWidth, height: window.innerHeight },\n content: { width: contentWidth, height: contentHeight },\n captureBox,\n nodes,\n truncatedNodes,\n };\n}\n","/**\n * Node-side orchestration for DOM snapshots: what to capture, how much of it,\n * and where the baseline lives.\n *\n * The walker itself is `dom-capture.ts` and runs in the browser. This module\n * never touches the DOM; it decides the policy and owns the files.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { captureDomSnapshot, type DomSnapshot, type CaptureOptions } from './dom-capture.js';\n\n/**\n * Computed properties worth recording.\n *\n * Curated rather than exhaustive: every property here can change what a\n * screenshot looks like, and nothing here is a derived duplicate of something\n * else in the list. Margin and padding are kept as longhands so a report can\n * say `margin-top 0px → 24px` instead of the far less useful\n * `margin \"0px\" → \"24px 0px 0px\"`.\n */\nexport const STYLE_PROPS: readonly string[] = [\n 'display',\n 'position',\n 'color',\n 'background-color',\n 'background-image',\n 'border-top-width',\n 'border-right-width',\n 'border-bottom-width',\n 'border-left-width',\n 'border-color',\n 'border-style',\n 'border-radius',\n 'box-shadow',\n 'opacity',\n 'transform',\n 'margin-top',\n 'margin-right',\n 'margin-bottom',\n 'margin-left',\n 'padding-top',\n 'padding-right',\n 'padding-bottom',\n 'padding-left',\n 'font-family',\n 'font-size',\n 'font-weight',\n 'line-height',\n 'letter-spacing',\n 'text-align',\n 'text-decoration-line',\n 'text-transform',\n 'flex-direction',\n 'justify-content',\n 'align-items',\n 'gap',\n 'z-index',\n 'overflow',\n];\n\n/**\n * Initial values, omitted from the snapshot where they match.\n *\n * This is what keeps snapshots small — most elements are at the initial value\n * for most of these, so a typical node stores three or four entries instead of\n * thirty-seven. `display` is deliberately absent: its initial value depends on\n * the tag, and it is high-signal enough to always be worth the bytes.\n */\nexport const STYLE_DEFAULTS: Readonly<Record<string, string>> = {\n position: 'static',\n 'background-color': 'rgba(0, 0, 0, 0)',\n 'background-image': 'none',\n 'border-top-width': '0px',\n 'border-right-width': '0px',\n 'border-bottom-width': '0px',\n 'border-left-width': '0px',\n 'border-style': 'none',\n 'border-radius': '0px',\n 'box-shadow': 'none',\n opacity: '1',\n transform: 'none',\n 'margin-top': '0px',\n 'margin-right': '0px',\n 'margin-bottom': '0px',\n 'margin-left': '0px',\n 'padding-top': '0px',\n 'padding-right': '0px',\n 'padding-bottom': '0px',\n 'padding-left': '0px',\n 'text-decoration-line': 'none',\n 'flex-direction': 'row',\n 'justify-content': 'normal',\n 'align-items': 'normal',\n gap: 'normal',\n 'z-index': 'auto',\n overflow: 'visible',\n};\n\n/**\n * Properties that inherit, recorded only where they differ from the parent.\n *\n * Without this, restyling the body font would report an identical change on\n * every one of its descendants — technically true and completely unreadable.\n * With it, the change appears once, on the element that actually caused it.\n */\nexport const INHERITED_PROPS: readonly string[] = [\n 'color',\n 'font-family',\n 'font-size',\n 'font-weight',\n 'line-height',\n 'letter-spacing',\n 'text-align',\n 'text-transform',\n];\n\n/** Matches `maestro-analytics`' body cap — the house default for captured payloads. */\nexport const DEFAULT_DOM_MAX_BYTES = 1024 * 1024;\nexport const DEFAULT_DOM_MAX_NODES = 1500;\nexport const DEFAULT_DOM_MAX_TEXT = 200;\n\nexport interface DomCaptureConfig {\n readonly maxNodes?: number;\n readonly maxBytes?: number;\n readonly maxText?: number;\n}\n\n/** The subset of a Playwright Page or Locator this module needs. */\nexport interface EvaluateTarget {\n evaluate<R, A>(fn: (arg: A) => R, arg: A): Promise<R>;\n}\n\n/** A Locator, for the mask and capture-box cases. */\nexport interface BoundedTarget {\n boundingBox(): Promise<{ x: number; y: number; width: number; height: number } | null>;\n}\n\nexport interface CaptureRequest {\n /** Page or Locator — whatever the assertion was made against. */\n readonly target: EvaluateTarget;\n /** Set when asserting against a locator, so the capture box can be framed. */\n readonly bounded?: BoundedTarget | null;\n readonly maskLocators?: readonly BoundedTarget[];\n readonly fullPage?: boolean;\n readonly config?: DomCaptureConfig;\n}\n\n/** Resolve mask locators to boxes, skipping any that are not currently laid out. */\nasync function resolveMaskBoxes(\n locators: readonly BoundedTarget[],\n): Promise<[number, number, number, number][]> {\n const boxes: [number, number, number, number][] = [];\n for (const locator of locators) {\n try {\n const box = await locator.boundingBox();\n if (box) boxes.push([box.x, box.y, box.width, box.height]);\n } catch {\n // A mask that cannot be resolved just does not mask anything.\n }\n }\n return boxes;\n}\n\n/**\n * Capture a DOM snapshot of whatever the assertion is about to compare.\n *\n * Returns null rather than throwing on any failure. A visual assertion must\n * behave identically whether or not DOM capture worked.\n */\nexport async function captureDom(request: CaptureRequest): Promise<DomSnapshot | null> {\n const config = request.config ?? {};\n try {\n const maskBoxes = request.maskLocators?.length\n ? await resolveMaskBoxes(request.maskLocators)\n : [];\n\n let viewportBox: [number, number, number, number] | null = null;\n if (request.bounded) {\n const box = await request.bounded.boundingBox();\n if (box) viewportBox = [box.x, box.y, box.width, box.height];\n }\n\n const options: CaptureOptions = {\n styleProps: STYLE_PROPS,\n defaults: STYLE_DEFAULTS,\n inherited: INHERITED_PROPS,\n maxNodes: config.maxNodes ?? DEFAULT_DOM_MAX_NODES,\n maxText: config.maxText ?? DEFAULT_DOM_MAX_TEXT,\n maskBoxes,\n viewportBox,\n fullPage: request.fullPage === true,\n };\n\n return await request.target.evaluate(captureDomSnapshot, options);\n } catch {\n return null;\n }\n}\n\nexport interface SerializedSnapshot {\n readonly json: string;\n readonly truncatedBytes: boolean;\n}\n\n/**\n * Serialize a snapshot, dropping nodes from the end until it fits.\n *\n * Nodes are in document order, so trimming from the end keeps the top of the\n * page — the part a screenshot is most likely to be about. The caller surfaces\n * `truncatedBytes` as a visible badge; silently shipping a partial snapshot\n * would make the diff lie about what was compared.\n */\nexport function serializeSnapshot(\n snapshot: DomSnapshot,\n maxBytes: number = DEFAULT_DOM_MAX_BYTES,\n): SerializedSnapshot {\n let json = JSON.stringify(snapshot);\n if (Buffer.byteLength(json, 'utf-8') <= maxBytes) {\n return { json, truncatedBytes: false };\n }\n\n const nodes = snapshot.nodes.slice();\n let dropped = 0;\n while (nodes.length > 0 && Buffer.byteLength(json, 'utf-8') > maxBytes) {\n // Drop in blocks: re-serializing once per node is quadratic on a big page.\n const block = Math.max(1, Math.floor(nodes.length * 0.1));\n nodes.splice(-block, block);\n dropped += block;\n json = JSON.stringify({\n ...snapshot,\n nodes,\n truncatedNodes: snapshot.truncatedNodes + dropped,\n });\n }\n\n return { json, truncatedBytes: true };\n}\n\n/** The DOM baseline sits beside the image baseline: `home.png` → `home.dom.json`. */\nexport function domBaselinePath(screenshotPath: string): string {\n return screenshotPath.replace(/\\.[A-Za-z0-9]+$/, '') + '.dom.json';\n}\n\n/** Read a committed DOM baseline. Returns null when there is not one yet. */\nexport function readDomBaseline(path: string): DomSnapshot | null {\n try {\n if (!existsSync(path)) return null;\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf-8'));\n return isDomSnapshot(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/** Write a DOM baseline, creating its directory if Playwright has not yet. */\nexport function writeDomBaseline(path: string, json: string): boolean {\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, json, 'utf-8');\n return true;\n } catch {\n return false;\n }\n}\n\n// SAFETY: read back from a file a previous run wrote, which a human may have\n// since edited or a merge may have mangled. Shape-checked before use.\nexport function isDomSnapshot(value: unknown): value is DomSnapshot {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.version === 'string' &&\n Array.isArray(v.nodes) &&\n Array.isArray(v.captureBox) &&\n (v.captureBox as unknown[]).length === 4\n );\n}\n","/**\n * Diff two DOM snapshots into a ranked list of changed elements.\n *\n * Pure: no filesystem, no Playwright, no browser. Everything the diff needs is\n * in the two snapshots, which is what makes the interesting parts — path\n * matching, moved-versus-replaced, noise suppression — directly testable.\n */\n\nimport type { DomChange, DomChangeKind, DomStyleChange } from '@testrelic/core';\nimport type { DomNode, DomSnapshot } from './dom-capture.js';\n\n/** Sub-pixel jitter is not a layout change; browsers round differently run to run. */\nconst RECT_TOLERANCE_PX = 1;\n\n/** Beyond this the list stops being readable; the remainder is reported as a count. */\nexport const DEFAULT_MAX_CHANGES = 50;\n\nexport interface DomDiffOptions {\n readonly maxChanges?: number;\n /** Turn off individual categories for pages where they are pure noise. */\n readonly includeText?: boolean;\n readonly includeStyles?: boolean;\n}\n\nexport interface DomDiffResult {\n readonly changes: DomChange[];\n /** Total found, which may exceed `changes.length`. */\n readonly total: number;\n /** Of the total, how many actually changed rather than merely shifted. */\n readonly rootCauses: number;\n}\n\n/** Movement alone is something that happened TO an element, not something it did. */\nconst MOVEMENT_ONLY: ReadonlySet<DomChangeKind> = new Set<DomChangeKind>(['moved', 'resized']);\n\n/**\n * Did this element change, or was it just pushed around?\n *\n * Anything restyled, retexted, added or removed changed on its own account.\n * An element that only moved or resized did not — in a layout, one element\n * growing by 24px relocates everything after it, and reporting all of those as\n * peers of the edit that caused them buries the actual finding.\n */\nfunction isRootCause(kinds: readonly DomChangeKind[]): boolean {\n return kinds.some((kind) => !MOVEMENT_ONLY.has(kind));\n}\n\n/** `div.card.accent` — what the report shows, built from what the node carries. */\nfunction labelOf(node: DomNode): string {\n let label = node.t;\n if (node.i) label += '#' + node.i;\n if (node.c) {\n const classes = node.c.split(' ').filter(Boolean).slice(0, 3);\n if (classes.length > 0) label += '.' + classes.join('.');\n }\n return label;\n}\n\nfunction movedOrResized(a: DomNode, b: DomNode): DomChangeKind[] {\n const kinds: DomChangeKind[] = [];\n const dx = Math.abs(a.r[0] - b.r[0]);\n const dy = Math.abs(a.r[1] - b.r[1]);\n const dw = Math.abs(a.r[2] - b.r[2]);\n const dh = Math.abs(a.r[3] - b.r[3]);\n if (dx > RECT_TOLERANCE_PX || dy > RECT_TOLERANCE_PX) kinds.push('moved');\n if (dw > RECT_TOLERANCE_PX || dh > RECT_TOLERANCE_PX) kinds.push('resized');\n return kinds;\n}\n\nfunction diffStyles(a: DomNode, b: DomNode): DomStyleChange[] {\n const from = a.s ?? {};\n const to = b.s ?? {};\n const props = new Set<string>([...Object.keys(from), ...Object.keys(to)]);\n const changes: DomStyleChange[] = [];\n for (const prop of props) {\n const before = from[prop];\n const after = to[prop];\n if (before === after) continue;\n // A property present on one side and absent on the other is at its initial\n // value there — say so rather than printing an empty string.\n changes.push({\n prop,\n from: before ?? '(initial)',\n to: after ?? '(initial)',\n });\n }\n changes.sort((x, y) => x.prop.localeCompare(y.prop));\n return changes;\n}\n\n/**\n * Secondary match key, for elements whose path changed.\n *\n * An element that moved in the tree would otherwise read as one removal plus\n * one addition, which is both wrong and twice as noisy. An id is unique enough\n * to match on alone; failing that, tag plus class list is a reasonable guess\n * and a wrong guess only downgrades a moved element back to removed + added.\n */\nfunction identityKey(node: DomNode): string | null {\n // The tag is part of the key for BOTH forms. Without it, a node whose tag\n // changed — deliberately left unmatched by the path pass so it reports as\n // removed + added rather than as an edit — was re-paired here on its id\n // alone, and a <div id=\"hero\"> that became a <section id=\"hero\"> came back\n // as a restyle. The class key already included the tag; the id key did not.\n if (node.i) return 'i:' + node.t + '#' + node.i;\n if (node.c) return 'c:' + node.t + '.' + node.c;\n return null;\n}\n\ninterface Pairing {\n readonly pairs: Array<{ before: DomNode; after: DomNode }>;\n readonly removed: DomNode[];\n readonly added: DomNode[];\n}\n\n/** Match nodes across the two snapshots: first by path, then by identity. */\nfunction pairNodes(baseline: readonly DomNode[], actual: readonly DomNode[]): Pairing {\n const byPath = new Map<string, DomNode>();\n for (const node of baseline) byPath.set(node.p, node);\n\n const pairs: Array<{ before: DomNode; after: DomNode }> = [];\n const unmatchedActual: DomNode[] = [];\n const matchedBaseline = new Set<DomNode>();\n\n for (const node of actual) {\n const before = byPath.get(node.p);\n // Same position but a different tag is a replacement, not an edit: a\n // <div> that became a <section> is not the same element wearing new\n // clothes, and reporting it as one would hide the swap.\n if (before && before.t !== node.t) {\n unmatchedActual.push(node);\n continue;\n }\n if (before && !matchedBaseline.has(before)) {\n matchedBaseline.add(before);\n pairs.push({ before, after: node });\n } else {\n unmatchedActual.push(node);\n }\n }\n\n const unmatchedBaseline = baseline.filter((node) => !matchedBaseline.has(node));\n\n // Second pass over what is left, by identity rather than position.\n const byIdentity = new Map<string, DomNode[]>();\n for (const node of unmatchedBaseline) {\n const key = identityKey(node);\n if (!key) continue;\n const bucket = byIdentity.get(key);\n if (bucket) bucket.push(node);\n else byIdentity.set(key, [node]);\n }\n\n const added: DomNode[] = [];\n const claimed = new Set<DomNode>();\n for (const node of unmatchedActual) {\n const key = identityKey(node);\n const bucket = key ? byIdentity.get(key) : undefined;\n const before = bucket?.find((candidate) => !claimed.has(candidate));\n if (before) {\n claimed.add(before);\n pairs.push({ before, after: node });\n } else {\n added.push(node);\n }\n }\n\n const removed = unmatchedBaseline.filter((node) => !claimed.has(node));\n return { pairs, removed, added };\n}\n\n/**\n * Identity attributes that differ.\n *\n * A class list changing from `card` to `banner` is usually the actual edit,\n * and every style difference underneath it is a consequence. Without this the\n * report shows the consequences and stays silent about the cause.\n */\nfunction diffAttrs(a: DomNode, b: DomNode): DomStyleChange[] {\n const changes: DomStyleChange[] = [];\n if ((a.i ?? '') !== (b.i ?? '')) {\n changes.push({ prop: 'id', from: a.i ?? '(none)', to: b.i ?? '(none)' });\n }\n if ((a.c ?? '') !== (b.c ?? '')) {\n changes.push({ prop: 'class', from: a.c ?? '(none)', to: b.c ?? '(none)' });\n }\n return changes;\n}\n\n/** Share of the captured region an element covers, clamped to 0–1. */\nfunction areaShare(node: DomNode, captureBox: readonly number[]): number {\n const total = (captureBox[2] ?? 0) * (captureBox[3] ?? 0);\n if (total <= 0) return 0;\n return Math.min(1, (node.r[2] * node.r[3]) / total);\n}\n\n/**\n * Compare two snapshots.\n *\n * Masked elements are dropped on both sides before anything else happens: the\n * user already told us that region is not part of the comparison, and honouring\n * that here is what keeps the text category usable on a page with a clock on it.\n */\nexport function diffDomSnapshots(\n baseline: DomSnapshot,\n actual: DomSnapshot,\n options: DomDiffOptions = {},\n): DomDiffResult {\n const maxChanges = options.maxChanges ?? DEFAULT_MAX_CHANGES;\n const includeText = options.includeText !== false;\n const includeStyles = options.includeStyles !== false;\n\n const baseNodes = baseline.nodes.filter((node) => node.m !== 1);\n const actualNodes = actual.nodes.filter((node) => node.m !== 1);\n\n const { pairs, removed, added } = pairNodes(baseNodes, actualNodes);\n\n interface Scored {\n change: Omit<DomChange, 'rank'>;\n score: number;\n }\n const scored: Scored[] = [];\n\n for (const { before, after } of pairs) {\n const kinds = movedOrResized(before, after);\n const styles = includeStyles ? diffStyles(before, after) : [];\n if (styles.length > 0) kinds.push('restyled');\n\n const attrs = diffAttrs(before, after);\n if (attrs.length > 0) kinds.push('attrs');\n\n const textChanged = includeText && (before.x ?? '') !== (after.x ?? '');\n if (textChanged) kinds.push('text');\n\n if (kinds.length === 0) continue;\n\n const share = areaShare(after, actual.captureBox);\n scored.push({\n score: share,\n change: {\n path: after.p,\n label: labelOf(after),\n kinds,\n rect: after.r,\n baselineRect: before.r,\n areaPct: share,\n ...(attrs.length > 0 ? { attrs } : {}),\n ...(styles.length > 0 ? { styles } : {}),\n ...(textChanged ? { text: { from: before.x ?? '', to: after.x ?? '' } } : {}),\n },\n });\n }\n\n for (const node of added) {\n const share = areaShare(node, actual.captureBox);\n scored.push({\n score: share,\n change: {\n path: node.p,\n label: labelOf(node),\n kinds: ['added'],\n rect: node.r,\n areaPct: share,\n },\n });\n }\n\n for (const node of removed) {\n const share = areaShare(node, baseline.captureBox);\n scored.push({\n score: share,\n change: {\n path: node.p,\n label: labelOf(node),\n kinds: ['removed'],\n baselineRect: node.r,\n areaPct: share,\n },\n });\n }\n\n const rootCauses = scored.filter((entry) => isRootCause(entry.change.kinds)).length;\n\n // Root causes first, then movement, each by area. A reviewer wants the\n // element that was restyled, not the largest of the eleven things it shoved.\n //\n // When EVERYTHING only moved — a whole page nudged by a scrollbar, say —\n // nothing is demoted, because then the movement is the finding.\n scored.sort((a, b) => {\n if (rootCauses > 0) {\n const aRoot = isRootCause(a.change.kinds) ? 1 : 0;\n const bRoot = isRootCause(b.change.kinds) ? 1 : 0;\n if (aRoot !== bRoot) return bRoot - aRoot;\n }\n return b.score - a.score;\n });\n\n const changes: DomChange[] = scored.slice(0, maxChanges).map((entry, index) => ({\n rank: index + 1,\n ...entry.change,\n ...(rootCauses > 0 && !isRootCause(entry.change.kinds) ? { consequence: true } : {}),\n }));\n\n return { changes, total: scored.length, rootCauses };\n}\n","/**\n * Whether this Playwright supports `snapshotPath(name, { kind })`.\n *\n * The options form arrived in Playwright 1.51. Before that `snapshotPath` took\n * variadic path segments, so an options object is consumed as a segment and\n * the resolved path is wrong — and because both call sites sit inside a\n * `try`/`catch` that swallows everything, DOM comparison and the\n * passing-baseline attachment simply never worked and said nothing about it.\n *\n * The package's peer range is `>=1.35.0` and stays that way deliberately:\n * everything except these two visual extras works on the older versions, and\n * raising the floor would lock those users out of the whole reporter over one\n * feature. So the loss is detected and *announced* once, instead of being\n * silent.\n *\n * The check is a feature probe rather than a version comparison. Reading\n * `@playwright/test/package.json` meant resolving a module at runtime from a\n * file that has to stay importable from both the CJS and the ESM build, and\n * the only spelling that worked in both reached the module loader through a\n * dynamically evaluated string. The repo's payload scanner rejects that shape\n * on sight, and rightly — this package is published to npm, and the scanner\n * does not exempt code that means well. Asking the API what it does needs no\n * module resolution, no dynamic execution and no version table, and it tests\n * the behaviour actually depended on rather than a number that correlates\n * with it.\n */\n\n/**\n * The one method probed. Both call sites already hold a `testInfo`; this is the\n * narrowest shape that covers the two call forms.\n */\nexport interface SnapshotPathCapable {\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n}\n\n/**\n * Passed to the probe. Never written to disk — `snapshotPath` only computes a\n * path — and named so it is unmistakable in a stack trace or a log line.\n */\nconst PROBE_NAME = '__testrelic_snapshot_path_probe__.png';\n\n/** Printed at most once per process, however many tests hit the path. */\nconst WARNED_KEY = Symbol.for('testrelic.snapshotPathWarned');\n\ninterface WarnSlot {\n warned: boolean;\n}\n\n/**\n * The latch lives on `globalThis`, not in a module variable.\n *\n * tsup builds with `splitting: false`, so every entry point bundles its own\n * copy of this module — and this module is reached from five of them. A\n * module-level `let` would therefore be five separate latches, and the\n * \"at most once per process\" promise above would print up to five times. This\n * is the same fix, and the same reason, as the active context in\n * `assertion-tracker.ts`.\n */\nfunction warnSlot(): WarnSlot {\n const g = globalThis as unknown as Record<symbol, WarnSlot | undefined>;\n let slot = g[WARNED_KEY];\n if (!slot) {\n slot = { warned: false };\n g[WARNED_KEY] = slot;\n }\n return slot;\n}\n\n/**\n * True when `snapshotPath(name, { kind })` resolves the way this code needs.\n *\n * The probe asks what the OLD form would have done wrong, not what the new one\n * returns verbatim. Before 1.51 the options object is consumed as another path\n * segment: `path.join` throws on a non-string, and a version that coerced it\n * instead would stringify it into the path as `[object Object]`. Either shape\n * answers `false`.\n *\n * It deliberately does NOT check that the result ends with the name it was\n * given. It did, and that was wrong: `snapshotPathTemplate` legitimately\n * inserts the project and platform before the extension, so\n * `toHaveScreenshot('dashboard.png')` resolves to `dashboard-win32.png` on\n * every default Playwright project. The probe therefore answered `false`\n * everywhere and silently disabled BOTH of its callers — passing-baseline\n * capture and DOM comparison — which shipped in 2.16.0 and is why a green\n * visual run recorded no comparisons at all and no element changes ever\n * appeared. Never assert on the shape of a path a template owns.\n */\nexport function supportsSnapshotPathOptions(testInfo: SnapshotPathCapable): boolean {\n try {\n const resolved = testInfo.snapshotPath(PROBE_NAME, { kind: 'screenshot' });\n return typeof resolved === 'string' && !resolved.includes('[object Object]');\n } catch {\n return false;\n }\n}\n\n/**\n * Say once that a visual extra is unavailable on this Playwright, and why.\n *\n * `feature` names what the user loses, so the line is actionable rather than\n * an abstract version complaint.\n */\nexport function warnSnapshotPathUnsupported(feature: string): void {\n const slot = warnSlot();\n if (slot.warned) return;\n slot.warned = true;\n process.stderr.write(\n `[testrelic] ${feature} needs Playwright >= 1.51 for ` +\n `testInfo.snapshotPath(name, { kind }); this project's Playwright ` +\n `resolves that call the older way. Everything else in the reporter is ` +\n `unaffected — upgrade Playwright to enable it.\\n`,\n );\n}\n\n/** Reset the once-per-process latch. Test-only. */\nexport function resetSnapshotPathWarning(): void {\n warnSlot().warned = false;\n}\n","/**\n * The `expect` layer that attaches DOM comparison to a visual assertion.\n *\n * Wraps `toHaveScreenshot` and `toMatchVisualBaseline` so that both capture a\n * DOM snapshot, compare it against a committed baseline, and record which\n * elements differ. The pixel comparison is untouched — this runs either side of\n * it and never changes its verdict.\n *\n * Composed as a layer rather than folded into `assertion-tracker.ts`: that\n * module records API assertions, and giving it visual concerns too would give\n * it two jobs. It also returns early for anything without an API call id, which\n * is exactly why `expect(page).toHaveScreenshot()` reaches raw Playwright today.\n */\n\nimport { basename, extname } from 'node:path';\nimport { writeFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport type { DomChange } from '@testrelic/core';\nimport {\n captureDom,\n domBaselinePath,\n readDomBaseline,\n writeDomBaseline,\n serializeSnapshot,\n type BoundedTarget,\n type EvaluateTarget,\n} from './dom-snapshot.js';\nimport { diffDomSnapshots } from './dom-diff.js';\nimport { supportsSnapshotPathOptions, warnSnapshotPathUnsupported } from './snapshot-path-support.js';\nimport type { DomSnapshot } from './dom-capture.js';\n\n/** Attachment carrying the DOM comparison from the worker to the reporter. */\nexport const VISUAL_DOM_ATTACHMENT = 'testrelic-visual-dom';\n\n/** One comparison's DOM result, as it crosses the process boundary. */\nexport interface VisualDomRecord {\n readonly stem: string;\n readonly changes: readonly DomChange[];\n readonly total: number;\n /** Of the total, how many changed rather than merely shifted. */\n readonly rootCauses: number;\n readonly truncated: boolean;\n readonly captureBox: readonly [number, number, number, number];\n /** Absolute paths for the reporter to copy into the run's artifacts. */\n readonly baselineDomPath?: string;\n readonly actualDomPath?: string;\n}\n\nconst VISUAL_MATCHERS = new Set(['toHaveScreenshot', 'toMatchVisualBaseline']);\n\ninterface MinimalTestInfo {\n outputPath(name: string): string;\n attachments: ReadonlyArray<{ name: string }>;\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n attach(name: string, options: { body?: Buffer; contentType?: string }): Promise<void>;\n config: { updateSnapshots?: unknown };\n}\n\ninterface LocatorLike extends BoundedTarget {\n page(): EvaluateTarget;\n}\n\n// SAFETY: the received value of an `expect(...)` call is genuinely unknown.\n// These two guards check for the shape each path needs and nothing more.\nfunction isLocatorLike(value: unknown): value is LocatorLike {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.boundingBox === 'function' && typeof v.page === 'function';\n}\n\nfunction isPageLike(value: unknown): value is EvaluateTarget {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.evaluate === 'function' && typeof v.screenshot === 'function';\n}\n\n/** The snapshot name, whichever of the two call shapes was used. */\nfunction snapshotNameOf(args: readonly unknown[]): string | null {\n const first = args[0];\n if (typeof first === 'string') return first;\n if (Array.isArray(first)) return first.join('/');\n if (typeof first === 'object' && first !== null) {\n const name = (first as Record<string, unknown>).name;\n if (typeof name === 'string') return name;\n }\n return null;\n}\n\n/** The options object, which may be the first or the second argument. */\nfunction optionsOf(args: readonly unknown[]): Record<string, unknown> {\n for (const arg of args) {\n if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) {\n return arg as Record<string, unknown>;\n }\n }\n return {};\n}\n\nfunction maskLocatorsOf(options: Record<string, unknown>): BoundedTarget[] {\n const mask = options.mask;\n if (!Array.isArray(mask)) return [];\n return mask.filter((entry): entry is BoundedTarget => {\n return (\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as Record<string, unknown>).boundingBox === 'function'\n );\n });\n}\n\n/** `toHaveScreenshot` rejects a bare name; the resolved path needs the same form. */\nfunction withPngExtension(name: string): string {\n return /\\.(png|jpg|jpeg)$/i.test(name) ? name : `${name}.png`;\n}\n\ninterface DomComparisonInput {\n readonly received: unknown;\n readonly args: readonly unknown[];\n readonly testInfo: MinimalTestInfo;\n /** Attachment count before the assertion ran, to spot the ones it added. */\n readonly attachedBefore: number;\n}\n\nconst ROLE_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)\\.[A-Za-z0-9]+$/;\n\n/**\n * The stem the reporter will group this comparison under.\n *\n * It must be read back from the attachments the assertion just produced, not\n * derived from the snapshot path: Playwright names attachments after the\n * resolved OUTPUT path, and the two are genuinely different — a snapshot at\n * `drift-target-win32.png` attaches as `drift-target-expected.png`. Deriving it\n * the obvious way silently produced a stem that matched nothing, so every\n * natively-asserted comparison lost its element list.\n *\n * Falls back to the snapshot basename for a passing assertion, which attaches\n * no images — that is the path `toMatchVisualBaseline` takes, and it names its\n * own attachment with exactly that stem.\n */\nfunction resolveStem(testInfo: MinimalTestInfo, attachedBefore: number, fallback: string): string {\n const attachments = testInfo.attachments;\n for (let i = attachedBefore; i < attachments.length; i++) {\n const match = ROLE_SUFFIX_RE.exec(attachments[i]?.name ?? '');\n if (match) return match[1] as string;\n }\n return fallback;\n}\n\n/** Capture the DOM of whatever is about to be screenshotted. */\nasync function capture(input: DomComparisonInput): Promise<DomSnapshot | null> {\n const options = optionsOf(input.args);\n const locator = isLocatorLike(input.received) ? input.received : null;\n const target = locator ? locator.page() : (input.received as EvaluateTarget);\n\n return captureDom({\n target,\n bounded: locator,\n maskLocators: maskLocatorsOf(options),\n fullPage: options.fullPage === true,\n });\n}\n\n/** True when the run was told to rewrite baselines rather than compare against them. */\nfunction isUpdatingSnapshots(testInfo: MinimalTestInfo): boolean {\n const mode = testInfo.config?.updateSnapshots;\n return mode === 'all' || mode === 'changed';\n}\n\n/**\n * Compare the captured DOM against its baseline and record the result.\n *\n * Writes the baseline when there is not one yet, or when the run was invoked\n * with `--update-snapshots` — mirroring exactly what Playwright does with the\n * image beside it, so the two never drift apart.\n */\nasync function recordDomComparison(input: DomComparisonInput, actual: DomSnapshot): Promise<void> {\n const name = snapshotNameOf(input.args);\n if (!name) return;\n\n // Resolving the baseline needs the options form of snapshotPath (>= 1.51).\n // Without it the path comes back wrong and this whole feature silently did\n // nothing; say so once rather than leave the user guessing.\n if (!supportsSnapshotPathOptions(input.testInfo)) {\n warnSnapshotPathUnsupported('DOM comparison');\n return;\n }\n\n const screenshotPath = input.testInfo.snapshotPath(withPngExtension(name), {\n kind: 'screenshot',\n });\n const baselinePath = domBaselinePath(screenshotPath);\n const stem = resolveStem(\n input.testInfo,\n input.attachedBefore,\n basename(screenshotPath, extname(screenshotPath)),\n );\n\n const serialized = serializeSnapshot(actual);\n const updating = isUpdatingSnapshots(input.testInfo);\n const baseline = updating ? null : readDomBaseline(baselinePath);\n\n if (!baseline) {\n // No reference to compare against — write one, the way Playwright writes\n // the image baseline in the same situation. Nothing is reported as changed,\n // because nothing was compared.\n writeDomBaseline(baselinePath, serialized.json);\n return;\n }\n\n const { changes, total, rootCauses } = diffDomSnapshots(baseline, actual);\n\n let actualDomPath: string | undefined;\n try {\n actualDomPath = input.testInfo.outputPath(`${stem}.actual.dom.json`);\n writeFileSync(actualDomPath, serialized.json, 'utf-8');\n } catch {\n actualDomPath = undefined;\n }\n\n const record: VisualDomRecord = {\n stem,\n changes,\n total,\n rootCauses,\n truncated: serialized.truncatedBytes || actual.truncatedNodes > 0,\n captureBox: actual.captureBox,\n ...(existsSync(baselinePath) ? { baselineDomPath: baselinePath } : {}),\n ...(actualDomPath ? { actualDomPath } : {}),\n };\n\n await input.testInfo.attach(VISUAL_DOM_ATTACHMENT, {\n body: Buffer.from(JSON.stringify([record])),\n contentType: 'application/json',\n });\n}\n\ntype MatcherFn = (...args: unknown[]) => unknown;\n\n/**\n * Wrap an `expect` so visual assertions also compare the DOM.\n *\n * Everything that is not a visual assertion against a Page or Locator passes\n * through untouched, including `.not`, `.soft` and every other matcher.\n */\nexport function withVisualCapture(\n baseExpect: (...args: unknown[]) => unknown,\n getTestInfo: () => MinimalTestInfo | null,\n): (...args: unknown[]) => unknown {\n const wrapped = function visualExpect(received: unknown, ...rest: unknown[]): unknown {\n const matchers = baseExpect(received, ...rest);\n\n const isTarget = isLocatorLike(received) || isPageLike(received);\n if (!isTarget || typeof matchers !== 'object' || matchers === null) return matchers;\n\n return new Proxy(matchers as object, {\n get(target, prop, receiver): unknown {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop !== 'string' || !VISUAL_MATCHERS.has(prop)) return value;\n if (typeof value !== 'function') return value;\n\n const original = value as MatcherFn;\n return async function domAwareMatcher(...args: unknown[]): Promise<unknown> {\n const testInfo = getTestInfo();\n\n // Capture BEFORE the assertion: a failing comparison may leave the\n // page mid-retry, and the DOM we want is the one that was rendered.\n let snapshot: DomSnapshot | null = null;\n const attachedBefore = testInfo?.attachments.length ?? 0;\n if (testInfo) {\n try {\n snapshot = await capture({ received, args, testInfo, attachedBefore });\n } catch {\n // DOM capture never changes whether the assertion runs.\n }\n }\n\n try {\n return await original.apply(target, args);\n } finally {\n if (testInfo && snapshot) {\n try {\n await recordDomComparison({ received, args, testInfo, attachedBefore }, snapshot);\n } catch {\n // A DOM comparison problem must never alter the test outcome.\n }\n }\n }\n };\n },\n });\n };\n\n // Carry `expect.soft`, `expect.configure`, `expect.extend` and the rest\n // forward; a wrapper that drops them silently breaks unrelated assertions.\n const statics = baseExpect as unknown as Record<string, unknown>;\n const target = wrapped as unknown as Record<string, unknown>;\n for (const key of Object.keys(statics)) {\n target[key] = statics[key];\n }\n\n return wrapped;\n}\n","/**\n * Recover visual baseline comparisons from a Playwright test result.\n *\n * Playwright's snapshot matchers do not report what they compared; they only\n * leave attachments behind. `toHaveScreenshot()` / `toMatchSnapshot()` attach\n * `<stem>-expected`, `<stem>-actual`, `<stem>-diff` and, on a retried failure,\n * `<stem>-previous`. Those four share a stem, and the stem is the snapshot's\n * identity within the test.\n *\n * This module is pure — it groups names and reads a message, and touches no\n * filesystem. Copying the images out is `artifact-manager.copyVisualArtifacts`.\n */\n\nimport type { VisualSource, VisualStatus } from '@testrelic/core';\nimport { parseDiffMessage, isSizeMismatchFailure } from './visual-metrics.js';\nimport { VISUAL_DOM_ATTACHMENT, type VisualDomRecord } from './visual-expect.js';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n/**\n * A comparison found in the attachments, still pointing at Playwright's temp\n * files. `artifact-manager` turns this into the report-facing\n * `VisualComparison` once the images have been copied somewhere durable.\n */\nexport interface VisualCandidate {\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n readonly expectedPath?: string;\n readonly actualPath?: string;\n readonly diffPath?: string;\n readonly previousPath?: string;\n readonly diffPixels?: number;\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** The DOM comparison for this snapshot, when one was made. */\n readonly dom?: VisualDomRecord;\n}\n\n/** Attachment name carrying the explicit matcher's JSON side-channel. */\nexport const VISUAL_META_ATTACHMENT = 'testrelic-visual';\n\n/** The four roles a snapshot image can play, as Playwright suffixes them. */\ntype VisualRole = 'expected' | 'actual' | 'diff' | 'previous';\n\nconst VISUAL_SUFFIX_RE = /^(.+)-(expected|actual|diff|previous)(\\.[A-Za-z0-9]+)$/;\n\n/** One stem's worth of images, before a status is decided. */\ninterface StemGroup {\n expected?: string;\n actual?: string;\n diff?: string;\n previous?: string;\n}\n\n/**\n * Metadata the `toMatchVisualBaseline` matcher attaches alongside its images.\n *\n * It exists because a passing comparison is otherwise invisible: Playwright\n * attaches nothing at all when the images match, so without this the report\n * could only ever show the failures.\n */\nexport interface VisualMetaRecord {\n readonly stem: string;\n readonly name: string;\n readonly status: VisualStatus;\n}\n\n/** Read and validate the explicit matcher's metadata attachment. */\nexport function parseVisualMeta(attachments: readonly Attachment[]): Map<string, VisualMetaRecord> {\n const byStem = new Map<string, VisualMetaRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_META_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n const record = toMetaRecord(entry);\n if (record) byStem.set(record.stem, record);\n }\n } catch {\n // A malformed side-channel costs us the pass-case detail, nothing more.\n }\n }\n\n return byStem;\n}\n\nconst VALID_STATUSES: readonly string[] = ['passed', 'failed', 'new', 'updated'];\n\n// SAFETY: parsed from an attachment body written by the worker process; every\n// field is checked before use.\nfunction toMetaRecord(entry: unknown): VisualMetaRecord | null {\n if (typeof entry !== 'object' || entry === null) return null;\n const e = entry as Record<string, unknown>;\n if (typeof e.stem !== 'string' || !e.stem) return null;\n if (typeof e.name !== 'string' || !e.name) return null;\n if (typeof e.status !== 'string' || !VALID_STATUSES.includes(e.status)) return null;\n return { stem: e.stem, name: e.name, status: e.status as VisualStatus };\n}\n\n/**\n * Read the DOM comparison side-channel, keyed by the same stem the images use.\n *\n * Written by `visual-expect`; absent whenever the assertion used Playwright's\n * own `expect`, or the page had no DOM baseline yet.\n */\nexport function parseVisualDom(\n attachments: readonly Attachment[],\n): Map<string, VisualDomRecord> {\n const byStem = new Map<string, VisualDomRecord>();\n\n for (const attachment of attachments) {\n if (attachment.name !== VISUAL_DOM_ATTACHMENT || !attachment.body) continue;\n try {\n const parsed: unknown = JSON.parse(attachment.body.toString('utf-8'));\n if (!Array.isArray(parsed)) continue;\n for (const entry of parsed) {\n // SAFETY: written by the worker process; the two fields read before\n // anything else uses it are checked here.\n if (typeof entry !== 'object' || entry === null) continue;\n const record = entry as Record<string, unknown>;\n if (typeof record.stem !== 'string' || !Array.isArray(record.changes)) continue;\n byStem.set(record.stem, entry as VisualDomRecord);\n }\n } catch {\n // A malformed side-channel costs the element list, nothing more.\n }\n }\n\n return byStem;\n}\n\n/** Group every `<stem>-<role>.<ext>` attachment that has a file behind it. */\nfunction groupBySnapshotStem(attachments: readonly Attachment[]): Map<string, StemGroup> {\n const groups = new Map<string, StemGroup>();\n\n for (const attachment of attachments) {\n if (!attachment.path) continue;\n const match = VISUAL_SUFFIX_RE.exec(attachment.name);\n if (!match) continue;\n\n const [, stem, role] = match;\n const group = groups.get(stem as string) ?? {};\n group[role as VisualRole] = attachment.path;\n groups.set(stem as string, group);\n }\n\n return groups;\n}\n\n/**\n * Decide whether a stem is really a snapshot comparison.\n *\n * An ordinary user attachment named `report-actual.json` would otherwise be\n * mistaken for one. A genuine comparison always produces an `actual` next to\n * either the baseline it was compared against or the diff it produced, so\n * requiring that pair costs nothing real and rejects the lookalikes. Stems the\n * explicit matcher has vouched for skip this check — it attaches a lone\n * baseline on a pass, which is the one legitimate single-image case.\n */\nfunction isCredibleComparison(group: StemGroup): boolean {\n return Boolean(group.actual && (group.expected || group.diff));\n}\n\n/** Derive a status from the images alone, for natively-produced comparisons. */\nfunction deriveStatus(\n group: StemGroup,\n updatingSnapshots: boolean,\n failureMessage: string | null | undefined,\n): VisualStatus {\n if (group.diff) return 'failed';\n // A size mismatch also produces no diff — the comparator cannot draw one\n // when the two images have different dimensions, so it attaches expected +\n // actual and throws. Absence of a diff therefore does NOT imply \"no baseline\n // existed\": read the message before concluding that. Without this a genuine\n // failure was reported as a freshly written baseline, it was missing from\n // visualFailures / visualFailedCount / visualDiffPath, and because\n // applyMetrics only targets failed stems the parsed dimensions were dropped\n // too (making SIZE_MISMATCH_RE dead in practice).\n if (isSizeMismatchFailure(failureMessage)) return 'failed';\n // Playwright attached a baseline and an actual but no diff: it had no\n // baseline and wrote one. That is not a pass — nothing was compared.\n return updatingSnapshots ? 'updated' : 'new';\n}\n\n/**\n * Attach the pixel count to the comparison it actually describes.\n *\n * The failure message belongs to whichever assertion threw. When it echoes a\n * snapshot name, that name is matched against the stems; otherwise the numbers\n * are only trustworthy if exactly one comparison failed, and are dropped when\n * several did rather than being pinned on an arbitrary one.\n */\nfunction selectMetricsTarget(\n failed: readonly string[],\n snapshotName: string | undefined,\n): string | null {\n if (failed.length === 0) return null;\n if (failed.length === 1) return failed[0] as string;\n if (!snapshotName) return null;\n\n const base = snapshotName.replace(/\\.[A-Za-z0-9]+$/, '');\n const matches = failed.filter((stem) => stem === base || stem.endsWith(`/${base}`));\n return matches.length === 1 ? (matches[0] as string) : null;\n}\n\nexport interface ParseVisualOptions {\n /** True when the run was invoked with `--update-snapshots`. */\n readonly updatingSnapshots?: boolean;\n /** Concatenated failure messages for the test attempt. */\n readonly failureMessage?: string | null;\n}\n\n/**\n * Extract every visual comparison recorded against one test attempt.\n *\n * Returns an empty array — never throws — for a test that made no visual\n * assertions, which is the overwhelming majority of them.\n */\nexport function parseVisualAttachments(\n attachments: readonly Attachment[],\n options: ParseVisualOptions = {},\n): VisualCandidate[] {\n if (attachments.length === 0) return [];\n\n const meta = parseVisualMeta(attachments);\n const dom = parseVisualDom(attachments);\n const groups = groupBySnapshotStem(attachments);\n if (groups.size === 0) return [];\n\n const updating = options.updatingSnapshots === true;\n const candidates: VisualCandidate[] = [];\n\n for (const [stem, group] of groups) {\n const vouched = meta.get(stem);\n if (!vouched && !isCredibleComparison(group)) continue;\n\n candidates.push({\n name: vouched?.name ?? stem,\n status: vouched?.status ?? deriveStatus(group, updating, options.failureMessage),\n source: vouched ? 'toMatchVisualBaseline' : 'toHaveScreenshot',\n ...(group.expected ? { expectedPath: group.expected } : {}),\n ...(group.actual ? { actualPath: group.actual } : {}),\n ...(group.diff ? { diffPath: group.diff } : {}),\n ...(group.previous ? { previousPath: group.previous } : {}),\n ...(dom.has(stem) ? { dom: dom.get(stem) as VisualDomRecord } : {}),\n });\n }\n\n return applyMetrics(candidates, groups, options.failureMessage);\n}\n\n/** Fold the parsed failure numbers into the one comparison they belong to. */\nfunction applyMetrics(\n candidates: readonly VisualCandidate[],\n groups: ReadonlyMap<string, StemGroup>,\n failureMessage: string | null | undefined,\n): VisualCandidate[] {\n const parsed = parseDiffMessage(failureMessage);\n if (parsed.diffPixels === undefined && parsed.actualWidth === undefined) {\n return [...candidates];\n }\n\n const failedStems = candidates.filter((c) => c.status === 'failed').map((c) => stemOf(c, groups));\n const target = selectMetricsTarget(failedStems, parsed.snapshotName);\n if (target === null) return [...candidates];\n\n return candidates.map((candidate) =>\n stemOf(candidate, groups) === target\n ? {\n ...candidate,\n ...(parsed.diffPixels !== undefined ? { diffPixels: parsed.diffPixels } : {}),\n ...(parsed.actualWidth !== undefined ? { actualWidth: parsed.actualWidth } : {}),\n ...(parsed.actualHeight !== undefined ? { actualHeight: parsed.actualHeight } : {}),\n }\n : candidate,\n );\n}\n\n/**\n * Recover the stem a candidate came from.\n *\n * The explicit matcher renames a candidate to the author's chosen name, so the\n * name cannot be used to look it back up among the groups.\n */\nfunction stemOf(candidate: VisualCandidate, groups: ReadonlyMap<string, StemGroup>): string {\n if (groups.has(candidate.name)) return candidate.name;\n for (const [stem, group] of groups) {\n if (\n group.actual === candidate.actualPath &&\n group.expected === candidate.expectedPath &&\n group.diff === candidate.diffPath\n ) {\n return stem;\n }\n }\n return candidate.name;\n}\n","/**\n * `toMatchVisualBaseline` — TestRelic's visual assertion.\n *\n * It does not compare images itself. Playwright already ships a comparator\n * (pixelmatch, or SSIM-CIE94 when asked) and bundles the decoders it needs, so\n * this delegates to `toHaveScreenshot` and adds the two things Playwright does\n * not give a reporter:\n *\n * 1. **A name that survives into the report.** Playwright's attachments are\n * named after the resolved snapshot path, which carries project and\n * platform suffixes. The author's own name is recorded alongside.\n * 2. **Evidence for a passing comparison.** Playwright attaches nothing at all\n * when the images match, so a green visual check is invisible to any\n * reporter. The baseline is attached here so the report can show what was\n * actually asserted against, not just what broke.\n *\n * Both ride the same `-expected` / `-actual` / `-diff` attachment convention\n * the reporter already groups by, so nothing downstream needs a second path.\n */\n\nimport { expect as playwrightExpect, test as playwrightTest } from '@playwright/test';\nimport { basename, extname } from 'node:path';\nimport { VISUAL_META_ATTACHMENT, type VisualMetaRecord } from './visual-capture.js';\nimport { isSizeMismatchFailure } from './visual-metrics.js';\nimport { supportsSnapshotPathOptions, warnSnapshotPathUnsupported } from './snapshot-path-support.js';\n\n/** Options accepted on top of Playwright's own screenshot options. */\nexport interface VisualBaselineOptions {\n /**\n * Labels recorded with the comparison. Reserved for the cloud baseline store,\n * where they will select which baselines a branch is allowed to promote.\n */\n readonly tags?: readonly string[];\n /** Anything else is forwarded to `toHaveScreenshot` untouched. */\n readonly [key: string]: unknown;\n}\n\ninterface MatcherResult {\n pass: boolean;\n message: () => string;\n name: string;\n}\n\n// SAFETY: Playwright's TestInfo is reached through `test.info()`, which throws\n// outside a running test. Only the three members used here are described.\ninterface MinimalTestInfo {\n attachments: Array<{ name: string; contentType: string; path?: string; body?: Buffer }>;\n snapshotPath(name: string, options: { kind: 'screenshot' }): string;\n attach(\n name: string,\n options: { path?: string; body?: Buffer; contentType?: string },\n ): Promise<void>;\n}\n\n/** Image extensions Playwright will accept for a screenshot baseline. */\nconst IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg'];\n\n/**\n * Give the snapshot name a file extension if the author left it off.\n *\n * `toHaveScreenshot` rejects a bare name outright (\"must have '.png'\n * extension\"). Requiring the suffix here would be the sort of papercut that\n * makes a wrapper worse than the thing it wraps, so `'home'` and `'home.png'`\n * both work and resolve to the same baseline.\n */\nexport function normalizeSnapshotName(name: string): string {\n const lower = name.toLowerCase();\n return IMAGE_EXTENSIONS.some((ext) => lower.endsWith(ext)) ? name : `${name}.png`;\n}\n\n/** `test.info()` throws when no test is running; a matcher must not. */\nfunction currentTestInfo(): MinimalTestInfo | null {\n try {\n return playwrightTest.info() as unknown as MinimalTestInfo;\n } catch {\n return null;\n }\n}\n\n/**\n * The stem Playwright used for the attachments this assertion just produced.\n *\n * Reading it back off `testInfo.attachments` is the only reliable way to learn\n * it: the stem comes from the resolved output path, which the snapshot path\n * template controls, and reversing that template would be guesswork.\n */\nfunction stemOfNewAttachments(\n attachments: readonly { name: string }[],\n addedAfter: number,\n): string | null {\n for (let i = addedAfter; i < attachments.length; i++) {\n const match = /^(.+)-(expected|actual|diff|previous)\\.[A-Za-z0-9]+$/.exec(\n attachments[i]?.name ?? '',\n );\n if (match) return match[1] as string;\n }\n return null;\n}\n\n/** Attach the baseline for a comparison that passed and left no evidence. */\nasync function attachPassingBaseline(\n testInfo: MinimalTestInfo,\n snapshotName: string,\n): Promise<string | null> {\n if (!supportsSnapshotPathOptions(testInfo)) {\n warnSnapshotPathUnsupported('Attaching the baseline of a passing comparison');\n return null;\n }\n try {\n const path = testInfo.snapshotPath(snapshotName, { kind: 'screenshot' });\n const ext = extname(path) || '.png';\n const stem = basename(path, ext);\n await testInfo.attach(`${stem}-expected${ext}`, { path });\n return stem;\n } catch {\n // No baseline on disk, or an unwritable output dir. The comparison still\n // passed; it simply has no picture to show for it.\n return null;\n }\n}\n\n/** Record the author's name and outcome against the stem the reporter will see. */\nasync function attachMeta(testInfo: MinimalTestInfo, record: VisualMetaRecord): Promise<void> {\n try {\n await testInfo.attach(VISUAL_META_ATTACHMENT, {\n body: Buffer.from(JSON.stringify([record])),\n contentType: 'application/json',\n });\n } catch {\n // Without this the comparison still appears, just under Playwright's own\n // name and inferred status.\n }\n}\n\n/**\n * Assert a page or locator against its committed visual baseline.\n *\n * Registered onto `expect` by `./visual`; see that module for the type\n * declaration that makes it visible to TypeScript.\n */\nexport async function toMatchVisualBaseline(\n this: { isNot?: boolean },\n target: unknown,\n name: string,\n options: VisualBaselineOptions = {},\n): Promise<MatcherResult> {\n // `tags` is ours; everything else belongs to Playwright and is forwarded\n // verbatim, so that every screenshot option keeps working here.\n const screenshotOptions: Record<string, unknown> = { ...options };\n delete screenshotOptions.tags;\n const snapshotName = normalizeSnapshotName(name);\n const testInfo = currentTestInfo();\n const attachedBefore = testInfo?.attachments.length ?? 0;\n\n let pass = true;\n let message = '';\n try {\n await (\n playwrightExpect(target) as unknown as {\n toHaveScreenshot(n: string, o: Record<string, unknown>): Promise<void>;\n }\n ).toHaveScreenshot(snapshotName, screenshotOptions);\n } catch (error) {\n pass = false;\n message = error instanceof Error ? error.message : String(error);\n }\n\n if (testInfo) {\n const stem = pass\n ? await attachPassingBaseline(testInfo, snapshotName)\n : stemOfNewAttachments(testInfo.attachments, attachedBefore);\n\n if (stem) {\n // A failure that wrote no diff wrote a first baseline instead — the\n // distinction matters, because nothing was compared in that case.\n //\n // But a size mismatch also writes no diff: the comparator cannot draw\n // one across different dimensions. Absence of a diff alone therefore\n // misreported a real failure as a new baseline, so the message decides.\n const wroteNewBaseline =\n !pass &&\n !isSizeMismatchFailure(message) &&\n !testInfo.attachments.some(\n (a, i) => i >= attachedBefore && a.name.startsWith(`${stem}-diff.`),\n );\n await attachMeta(testInfo, {\n stem,\n name,\n status: pass ? 'passed' : wroteNewBaseline ? 'new' : 'failed',\n });\n }\n }\n\n return {\n pass,\n name: 'toMatchVisualBaseline',\n message: () =>\n pass ? `Expected \"${name}\" to differ from its visual baseline, but it matched.` : message,\n };\n}\n","/**\n * @testrelic/playwright-analytics/visual\n *\n * Registers `toMatchVisualBaseline` onto Playwright's `expect` and declares it\n * to TypeScript.\n *\n * Importing this module is only necessary when using Playwright's own `expect`.\n * The SDK fixture (`@testrelic/playwright-analytics/fixture`) already imports\n * it, so `expect` from there has the matcher without a second import.\n *\n * Native `toHaveScreenshot()` needs none of this — the reporter recovers those\n * comparisons from the attachments on its own.\n */\n\nimport { expect as playwrightExpect } from '@playwright/test';\nimport { toMatchVisualBaseline } from './visual-matcher.js';\n\nexport type { VisualBaselineOptions } from './visual-matcher.js';\nexport { toMatchVisualBaseline } from './visual-matcher.js';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace PlaywrightTest {\n // `T` is unused here but structurally required: declaration merging only\n // works against Playwright's `Matchers<R, T = unknown>` if the parameter\n // list matches, and dropping it silently stops the merge.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface Matchers<R, T = unknown> {\n /**\n * Compare a page or locator against its committed visual baseline.\n *\n * Uses Playwright's own comparator, so every `toHaveScreenshot` option\n * (`threshold`, `maxDiffPixels`, `maxDiffPixelRatio`, `mask`, `clip`,\n * `fullPage`, `animations`, …) applies unchanged. Baselines live where\n * Playwright puts them and are updated with `--update-snapshots`.\n *\n * ```ts\n * await expect(page).toMatchVisualBaseline('home', {\n * maxDiffPixelRatio: 0.01,\n * mask: [page.locator('.live-ticker')],\n * });\n * ```\n */\n toMatchVisualBaseline(\n name: string,\n options?: import('./visual-matcher.js').VisualBaselineOptions,\n ): Promise<R>;\n }\n }\n}\n\n// Registered at module scope so a bare import is enough to install it.\n// `expect.extend` is additive and idempotent here: the fixture and a direct\n// import of this module both land on the same registration.\nplaywrightExpect.extend({ toMatchVisualBaseline });\n","/**\n * @testrelic/playwright-analytics/fixture\n *\n * Extended Playwright test fixture that wraps the default `page` fixture\n * to automatically track navigation events and API requests.\n *\n * Exports:\n * - testRelicFixture: Fixture object for use with base.extend()\n * - test: Pre-extended test instance (backward compatibility)\n * - expect: Re-exported from @playwright/test (backward compatibility)\n */\n\nimport { test as base, expect as playwrightExpect } from '@playwright/test';\nimport { NavigationTracker } from './navigation-tracker.js';\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker, createActiveAssertionAwareExpect, setActiveAssertionContext } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { TestRelicFilePayload } from '@testrelic/core';\nimport type { ResolvedApiConfig } from './config.js';\n// Side-effect import: registers `toMatchVisualBaseline` on Playwright's expect,\n// so the `expect` exported below carries it without a second import in tests.\nimport './visual.js';\nimport { withVisualCapture } from './visual-expect.js';\n\n/**\n * Assertion-aware `expect`. Inside a test that ran through this fixture, each\n * assertion on an API response is recorded and linked to its originating call,\n * so it surfaces in the cloud report's API workspace; outside one it behaves\n * exactly like Playwright's `expect`. Import THIS `expect` (not the one from\n * '@playwright/test') for API assertion capture.\n */\n/**\n * `test.info()` throws outside a running test. The visual layer must degrade\n * to a plain pass-through there rather than take an assertion down with it.\n */\nfunction currentTestInfo(): ReturnType<typeof base.info> | null {\n try {\n return base.info();\n } catch {\n return null;\n }\n}\n\n/**\n * Two single-purpose layers, composed.\n *\n * The inner layer records API assertions. The outer one attaches DOM\n * comparison to `toHaveScreenshot` and `toMatchVisualBaseline`, which is why\n * visual assertions written with THIS `expect` report which elements changed\n * while ones written with Playwright's own `expect` compare pixels only.\n */\nexport const expect = withVisualCapture(\n createActiveAssertionAwareExpect(\n playwrightExpect as unknown as (...args: unknown[]) => unknown,\n ),\n currentTestInfo,\n) as unknown as typeof playwrightExpect;\n\n/** Annotation type for API config (reporter → fixture). */\nconst API_CONFIG_ANNOTATION = '__testrelic_api_config';\n\n/** Legacy annotation type for backward compatibility. */\nconst LEGACY_TRACK_API_CALLS_ANNOTATION = '__testrelic_config_trackApiCalls';\n\n/**\n * Reads API config from testInfo annotations pushed by the reporter.\n * Returns a default config if no annotation is found (FR-013).\n */\nexport function readApiConfig(testInfo: { annotations: Array<{ type: string; description?: string }> }): ResolvedApiConfig {\n // Try new unified annotation first\n const configAnnotation = testInfo.annotations.find(\n (a) => a.type === API_CONFIG_ANNOTATION && a.description !== undefined,\n );\n if (configAnnotation) {\n try {\n const parsed = JSON.parse(configAnnotation.description!, deserializeRegExp);\n return parsed as ResolvedApiConfig;\n } catch {\n // Fall through to defaults\n }\n }\n\n // Backward compatibility: check legacy trackApiCalls annotation\n const legacyAnnotation = testInfo.annotations.find(\n (a) => a.type === LEGACY_TRACK_API_CALLS_ANNOTATION && a.description !== undefined,\n );\n if (legacyAnnotation) {\n return {\n trackApiCalls: legacyAnnotation.description !== 'false',\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n };\n }\n\n // No annotation found — return sensible defaults (FR-013)\n return {\n trackApiCalls: true,\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n };\n}\n\n/** JSON reviver that reconstructs serialized RegExp objects. */\nfunction deserializeRegExp(_key: string, value: unknown): unknown {\n if (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).__regexp === true &&\n typeof (value as Record<string, unknown>).source === 'string'\n ) {\n const { source, flags } = value as { source: string; flags: string };\n return new RegExp(source, flags);\n }\n return value;\n}\n\n/**\n * Playwright-compatible fixture object for extending `test.extend()`.\n * Provides `page` (with navigation tracking) and `request` (with API tracking).\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicFixture);\n */\nexport const testRelicFixture = {\n page: async (\n { page }: { page: import('@playwright/test').Page },\n use: (page: import('@playwright/test').Page) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const tracker = new NavigationTracker(page as never);\n try {\n await tracker.init();\n } catch {\n // Graceful degradation: continue without SPA detection\n }\n\n await use(page);\n\n try {\n const { navigations, networkRequestsFile, networkRequestsCount, consoleLogsFile, consoleLogsCount } = await tracker.getFileData();\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations,\n apiAssertions: [],\n networkRequestsFile,\n networkRequestsCount,\n consoleLogsFile,\n consoleLogsCount,\n apiCallsFile: null,\n apiCallsCount: 0,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // FR-009: fixture without reporter is harmless\n }\n tracker.dispose();\n },\n\n request: async (\n { request }: { request: import('@playwright/test').APIRequestContext },\n use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const apiConfig = readApiConfig(testInfo);\n\n if (!apiConfig.trackApiCalls) {\n await use(request);\n return;\n }\n\n const assertionTracker = new AssertionTracker();\n const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n apiTracker.intercept();\n // Route the exported assertion-aware `expect` to THIS test's tracker.\n setActiveAssertionContext(assertionTracker, apiTracker);\n\n try {\n await use(request);\n } finally {\n setActiveAssertionContext(null, null);\n }\n\n try {\n const { apiCallsFile, apiCallsCount, apiStatusMix } = apiTracker.getFileData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations: [],\n apiAssertions,\n networkRequestsFile: null,\n networkRequestsCount: 0,\n consoleLogsFile: null,\n consoleLogsCount: 0,\n apiCallsFile,\n apiCallsCount,\n apiStatusMix,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation: fixture without reporter is harmless\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n\n/**\n * Track a manually-created page for network requests, console logs, and navigations.\n *\n * Use this when your test creates pages via `browser.newContext()` / `context.newPage()`\n * instead of the built-in `page` fixture. Call it right after creating the page,\n * and call the returned cleanup function after `use()`.\n *\n * @example\n * authenticatedPage: async ({ browser }, use, testInfo) => {\n * const ctx = await browser.newContext({ storageState: AUTH_FILE });\n * const page = await ctx.newPage();\n * const cleanup = await trackPage(page, testInfo);\n * await use(page);\n * await cleanup();\n * await ctx.close();\n * },\n */\nexport async function trackPage(\n page: import('@playwright/test').Page,\n testInfo: import('@playwright/test').TestInfo,\n): Promise<() => Promise<void>> {\n const tracker = new NavigationTracker(page as never);\n try {\n await tracker.init();\n } catch {\n // Graceful degradation\n }\n\n return async () => {\n try {\n const { navigations, networkRequestsFile, networkRequestsCount, consoleLogsFile, consoleLogsCount } = await tracker.getFileData();\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations,\n apiAssertions: [],\n networkRequestsFile,\n networkRequestsCount,\n consoleLogsFile,\n consoleLogsCount,\n apiCallsFile: null,\n apiCallsCount: 0,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation\n }\n tracker.dispose();\n };\n}\n\n/** Pre-extended test instance for backward compatibility. */\nexport const test = base.extend(testRelicFixture);\n","/**\n * @testrelic/playwright-analytics/api-fixture\n *\n * Standalone API-only fixture for pure API test suites.\n * Wraps only the Playwright `request` fixture with the API request tracker\n * and assertion tracker. Does NOT import any navigation or browser-related code.\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicApiFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicApiFixture);\n */\n\nimport { expect as playwrightExpect } from '@playwright/test';\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker, createActiveAssertionAwareExpect, setActiveAssertionContext } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { ConsoleLogEntry, TestRelicFilePayload } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\nimport { readApiConfig } from './fixture.js';\n\n/**\n * Assertion-aware `expect` for API-only suites. Inside a test that ran through\n * this fixture, each assertion on an API response is recorded and linked to its\n * originating call so it surfaces in the cloud report's API workspace; outside\n * one it behaves exactly like Playwright's `expect`. Import THIS `expect` (not\n * the one from '@playwright/test') for API assertion capture.\n */\nexport const expect = createActiveAssertionAwareExpect(\n playwrightExpect as unknown as (...args: unknown[]) => unknown,\n) as unknown as typeof playwrightExpect;\n\n/**\n * Playwright-compatible fixture object for API-only test suites.\n * Provides only `request` (with API tracking and assertion capture). No browser dependency.\n */\nexport const testRelicApiFixture = {\n request: async (\n { request }: { request: import('@playwright/test').APIRequestContext },\n use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const apiConfig = readApiConfig(testInfo);\n\n if (!apiConfig.trackApiCalls) {\n await use(request);\n return;\n }\n\n const assertionTracker = new AssertionTracker();\n const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n apiTracker.intercept();\n // Route the exported assertion-aware `expect` to THIS test's tracker.\n setActiveAssertionContext(assertionTracker, apiTracker);\n\n // Capture terminal stdout/stderr during the test — stream to JSONL\n let consoleWriter: JsonlFileWriter | null = null;\n try { consoleWriter = new JsonlFileWriter('api-console'); } catch { /* graceful */ }\n let consoleLogCount = 0;\n const origStdoutWrite = process.stdout.write;\n const origStderrWrite = process.stderr.write;\n\n process.stdout.write = function (chunk: unknown, ...args: unknown[]): boolean {\n try {\n const text = typeof chunk === 'string' ? chunk : Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk);\n if (!text.startsWith('[testrelic]') && !text.startsWith('ℹ TestRelic') && !text.startsWith('✓ TestRelic') && !text.startsWith('⚠ TestRelic')) {\n if (consoleWriter) {\n consoleWriter.append({ level: 'stdout', text: text.replace(/\\n$/, ''), timestamp: new Date().toISOString(), location: null } satisfies ConsoleLogEntry);\n consoleLogCount++;\n }\n }\n } catch { /* never crash */ }\n return origStdoutWrite.apply(process.stdout, [chunk, ...args] as never);\n } as typeof process.stdout.write;\n\n process.stderr.write = function (chunk: unknown, ...args: unknown[]): boolean {\n try {\n const text = typeof chunk === 'string' ? chunk : Buffer.isBuffer(chunk) ? chunk.toString('utf-8') : String(chunk);\n if (!text.startsWith('[testrelic]') && !text.startsWith('⚠ TestRelic')) {\n if (consoleWriter) {\n consoleWriter.append({ level: 'stderr', text: text.replace(/\\n$/, ''), timestamp: new Date().toISOString(), location: null } satisfies ConsoleLogEntry);\n consoleLogCount++;\n }\n }\n } catch { /* never crash */ }\n return origStderrWrite.apply(process.stderr, [chunk, ...args] as never);\n } as typeof process.stderr.write;\n\n try {\n await use(request);\n } finally {\n process.stdout.write = origStdoutWrite;\n process.stderr.write = origStderrWrite;\n setActiveAssertionContext(null, null);\n }\n\n try {\n consoleWriter?.close();\n const { apiCallsFile, apiCallsCount, apiStatusMix } = apiTracker.getFileData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicFilePayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION as '2.0.0',\n navigations: [],\n apiAssertions,\n networkRequestsFile: null,\n networkRequestsCount: 0,\n consoleLogsFile: consoleLogCount > 0 ? consoleWriter?.getPath() ?? null : null,\n consoleLogsCount: consoleLogCount,\n apiCallsFile,\n apiCallsCount,\n apiStatusMix,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation: fixture without reporter is harmless\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n"]}
|