@testrelic/playwright-analytics 2.3.2 → 2.3.4
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 +2 -1
- package/dist/api-fixture.cjs.map +1 -1
- package/dist/api-fixture.js +2 -1
- package/dist/api-fixture.js.map +1 -1
- package/dist/cli.cjs +134 -42
- package/dist/fixture.cjs +2 -1
- package/dist/fixture.cjs.map +1 -1
- package/dist/fixture.js +2 -1
- package/dist/fixture.js.map +1 -1
- package/dist/index.cjs +158 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +158 -88
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/api-fixture.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/assertion-tracker.ts","../src/api-redactor.ts","../src/api-url-filter.ts","../src/api-request-tracker.ts","../src/navigation-tracker.ts","../src/fixture.ts","../src/api-fixture.ts"],"names":["CALL_ID_SYMBOL","ASSERTION_ANNOTATION_TYPE","valueCallIdMap","AssertionTracker","assertion","callId","testInfo","REDACTED","redactHeaders","headers","redactList","lowerList","h","result","key","redactBodyFields","body","parsed","fieldSet","redacted","walkAndRedact","value","fields","item","val","shouldTrackApiUrl","url","include","exclude","pattern","matchesPattern","globToRegex","glob","i","char","HTTP_METHODS","TEXT_CONTENT_TYPES","ANNOTATION_TYPE","isTextContentType","contentType","lower","prefix","extractRequestBody","options","data","multipart","descriptor","_ApiRequestTracker","context","assertionTracker","apiConfig","method","original","origDispose","response","self","origHeaders","origHeadersArray","origJson","origStatus","origStatusText","origOk","origText","origBody","id","timestamp","httpMethod","requestBody","startTime","performance","err","endTime","errorRequestBody","errorRecord","rawResponseHeaders","binary","capturedRequestHeaders","capturedResponseHeaders","capturedRequestBody","capturedResponseBody","record","ApiRequestTracker","NavigationTracker","_NavigationTracker","page","pending","a","b","type","navigations","networkRequests","annotation","requests","event","handler","onDomContentLoaded","onFrameNavigated","frame","frameObj","lastRecord","onConsoleMessage","msg","msgObj","text","location","loc","level","onRequest","request","req","reqId","postData","onResponse","resp","status","contentLength","resourceType","typeKey","responseTimeMs","respHeaders","responseSize","bodyPromise","responseBody","captured","onRequestFailed","origPush","origReplace","args","API_CONFIG_ANNOTATION","LEGACY_TRACK_API_CALLS_ANNOTATION","readApiConfig","configAnnotation","deserializeRegExp","legacyAnnotation","_key","source","flags","testRelicFixture","use","tracker","consoleLogs","payload","PAYLOAD_VERSION","ATTACHMENT_NAME","ATTACHMENT_CONTENT_TYPE","apiTracker","apiCalls","apiAssertions","base","testRelicApiFixture","origStdoutWrite","origStderrWrite","chunk"],"mappings":"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","file":"api-fixture.cjs","sourcesContent":["/**\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 type { ApiAssertion, AssertionType, AssertionLocation } from '@testrelic/core';\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 const content = readFileSync(filePath, '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 */\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 for (const line of lines) {\n // Skip frames from the SDK's own assertion capture internals\n if (line.includes('/assertion-tracker.') ||\n line.includes('/api-request-tracker.') ||\n line.includes('node:internal') ||\n line.includes(' at new Error') ||\n line.includes(' at captureAssertionLocation') ||\n line.includes(' at expectWrapper') ||\n line.includes(' at wrappedMatcher')) {\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 file: 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 */\nexport function createAssertionAwareExpect(\n tracker: AssertionTracker,\n apiTracker: ApiTrackerLike,\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 // 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 for (const key of Object.keys(originalExpect as object)) {\n (wrappedExpect as Record<string, unknown>)[key] =\n (originalExpect as Record<string, unknown>)[key];\n }\n\n return wrappedExpect;\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 if (typeof value === 'string' || 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 if (Object.hasOwn(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 const fieldSet = new Set(redactList);\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 */\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.hasOwn(value, key)) continue;\n const val = (value as Record<string, unknown>)[key];\n if (fields.has(key)) {\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 * 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 } from '@testrelic/core';\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 } 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// ---------------------------------------------------------------------------\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 capturedCalls: ApiCallRecord[] = [];\n private callCounter = 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 }\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 /** Return captured API call records for consolidated payload. */\n getData(): ApiCallRecord[] {\n return [...this.capturedCalls];\n }\n\n /** Flush captured API call records to testInfo annotations (legacy). */\n flushLegacyAnnotations(testInfo: TestInfoLike): void {\n if (this.capturedCalls.length === 0) return;\n\n testInfo.annotations.push({\n type: ANNOTATION_TYPE,\n description: JSON.stringify(this.capturedCalls),\n });\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.capturedCalls = [];\n this.callCounter = 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 calls (for testing). */\n getCapturedCalls(): readonly ApiCallRecord[] {\n return this.capturedCalls;\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,\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 self.capturedCalls.push(errorRecord);\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 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 // 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: response.url(),\n requestHeaders: capturedRequestHeaders,\n requestBody: capturedRequestBody,\n responseStatusCode: response.status(),\n responseStatusText: response.statusText(),\n responseHeaders: capturedResponseHeaders,\n responseBody: capturedResponseBody,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: binary,\n error: null,\n };\n self.capturedCalls.push(record);\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';\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}\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 private currentNetworkCounter: NetworkCounter | null = null;\n private pendingRequests = new Map<string, PendingRequest>();\n private capturedRequests: CapturedNetworkRequest[] = [];\n private pendingBodyReads: Promise<void>[] = [];\n private requestIdCounter = 0;\n private consoleLogs: ConsoleLogEntry[] = [];\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 this.interceptMethods();\n this.attachListeners();\n }\n\n async init(): Promise<void> {\n await this.injectSPADetection();\n }\n\n async getCapturedRequests(): Promise<CapturedNetworkRequest[]> {\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 captured entries\n for (const [id, pending] of this.pendingRequests) {\n this.capturedRequests.push({\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 });\n this.pendingRequests.delete(id);\n }\n // Return sorted by startedAt\n return [...this.capturedRequests].sort((a, b) => a.startedAt.localeCompare(b.startedAt));\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 getData(): Promise<{ navigations: NavigationAnnotation[]; networkRequests: CapturedNetworkRequest[]; consoleLogs: ConsoleLogEntry[] }> {\n // Finalize network stats for the last navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\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 const networkRequests = this.includeNetworkStats\n ? await this.getCapturedRequests()\n : [];\n\n return { navigations, networkRequests, consoleLogs: [...this.consoleLogs] };\n }\n\n async flushLegacyAnnotations(testInfo: TestInfoLike): Promise<void> {\n // Finalize network stats for the last navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\n\n for (const record of this.records) {\n const annotation: NavigationAnnotation = {\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 testInfo.annotations.push({\n type: 'lambdatest-navigation',\n description: JSON.stringify(annotation),\n });\n }\n\n // Capture and serialize individual network requests\n if (this.includeNetworkStats) {\n const requests = await this.getCapturedRequests();\n if (requests.length > 0) {\n testInfo.annotations.push({\n type: '__testrelic_network_requests',\n description: JSON.stringify(requests),\n });\n }\n }\n }\n\n dispose(): void {\n // Restore original methods\n (this.page as Record<string, unknown>).goto = this.origGoto;\n (this.page as Record<string, unknown>).goBack = this.origGoBack;\n (this.page as Record<string, unknown>).goForward = this.origGoForward;\n (this.page as Record<string, unknown>).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.capturedRequests = [];\n this.pendingBodyReads = [];\n }\n\n getRecords(): readonly NavigationRecord[] {\n return this.records;\n }\n\n private interceptMethods(): void {\n const self = this;\n const page = this.page;\n\n (page as Record<string, unknown>).goto = async function (url: string, options?: unknown) {\n self.recordNavigation(url, 'goto');\n return self.origGoto(url, options);\n };\n\n (page as Record<string, unknown>).goBack = async function (options?: unknown) {\n const result = await self.origGoBack(options);\n self.recordNavigation(page.url(), 'back');\n return result;\n };\n\n (page as Record<string, unknown>).goForward = async function (options?: unknown) {\n const result = await self.origGoForward(options);\n self.recordNavigation(page.url(), 'forward');\n return result;\n };\n\n (page as Record<string, unknown>).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 this.consoleLogs.push({ level, text, timestamp: new Date().toISOString(), location });\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 if (this.currentNetworkCounter) {\n this.currentNetworkCounter.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 };\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 // Aggregate stats (existing logic)\n if (this.currentNetworkCounter) {\n const status = resp.status();\n if (status >= 400) {\n this.currentNetworkCounter.failedRequests++;\n this.currentNetworkCounter.failedRequestUrls.push(status + ' ' + resp.url());\n }\n const contentLength = resp.headers()['content-length'];\n if (contentLength) {\n this.currentNetworkCounter.totalBytes += parseInt(contentLength, 10) || 0;\n }\n const resourceType = resp.request().resourceType();\n const typeKey = this.mapResourceType(resourceType);\n this.currentNetworkCounter.byType[typeKey]++;\n }\n // Individual request capture\n const reqId = (resp.request() as Record<string, unknown>).__testrelic_id as string | undefined;\n if (!reqId) return;\n const pending = this.pendingRequests.get(reqId);\n if (!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\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 this.capturedRequests.push(captured);\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 // Aggregate stats (existing logic)\n if (this.currentNetworkCounter) {\n this.currentNetworkCounter.failedRequests++;\n try {\n const req = request as { url(): string };\n this.currentNetworkCounter.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; __testrelic_id?: string };\n const reqId = (req as Record<string, unknown>).__testrelic_id as string | undefined;\n if (!reqId) return;\n const pending = this.pendingRequests.get(reqId);\n if (!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 this.capturedRequests.push(captured);\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 // Finalize network stats for the previous navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\n\n this.records.push({\n url,\n navigationType: type,\n timestamp: new Date().toISOString(),\n });\n\n // Start fresh counter for this navigation\n if (this.includeNetworkStats) {\n this.currentNetworkCounter = 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 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 * @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, createAssertionAwareExpect } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { TestRelicDataPayload } from '@testrelic/core';\nimport type { ResolvedApiConfig } from './config.js';\n\nexport { playwrightExpect as expect };\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, networkRequests, consoleLogs } = await tracker.getData();\n const payload: TestRelicDataPayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION,\n navigations,\n networkRequests,\n apiCalls: [],\n apiAssertions: [],\n consoleLogs,\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 try {\n await tracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\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\n await use(request);\n\n try {\n const apiCalls = apiTracker.getData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicDataPayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION,\n navigations: [],\n networkRequests: [],\n apiCalls,\n apiAssertions,\n consoleLogs: [],\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 try {\n apiTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n try {\n assertionTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n apiTracker.dispose();\n assertionTracker.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 { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { ConsoleLogEntry, TestRelicDataPayload } from '@testrelic/core';\nimport { readApiConfig } from './fixture.js';\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\n // Capture terminal stdout/stderr during the test\n const consoleLogs: ConsoleLogEntry[] = [];\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 consoleLogs.push({ level: 'stdout', text: text.replace(/\\n$/, ''), timestamp: new Date().toISOString(), location: null });\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 consoleLogs.push({ level: 'stderr', text: text.replace(/\\n$/, ''), timestamp: new Date().toISOString(), location: null });\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 }\n\n try {\n const apiCalls = apiTracker.getData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicDataPayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION,\n navigations: [],\n networkRequests: [],\n apiCalls,\n apiAssertions,\n consoleLogs,\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 try {\n apiTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n try {\n assertionTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/jsonl-stream.ts","../src/assertion-tracker.ts","../src/api-redactor.ts","../src/api-url-filter.ts","../src/api-request-tracker.ts","../src/navigation-tracker.ts","../src/fixture.ts","../src/api-fixture.ts"],"names":["TESTRELIC_TMP_DIR","join","tmpdir","JsonlFileWriter","prefix","mkdirSync","randomUUID","openSync","obj","line","writeSync","closeSync","unlinkSync","CALL_ID_SYMBOL","ASSERTION_ANNOTATION_TYPE","valueCallIdMap","AssertionTracker","assertion","callId","testInfo","REDACTED","redactHeaders","headers","redactList","lowerList","h","result","key","redactBodyFields","body","parsed","fieldSet","redacted","walkAndRedact","value","fields","item","val","shouldTrackApiUrl","url","include","exclude","pattern","matchesPattern","globToRegex","glob","i","char","HTTP_METHODS","TEXT_CONTENT_TYPES","isTextContentType","contentType","lower","extractRequestBody","options","data","multipart","descriptor","_ApiRequestTracker","context","assertionTracker","apiConfig","method","original","origDispose","_testInfo","response","self","origHeaders","origHeadersArray","origJson","origStatus","origStatusText","origOk","origText","origBody","id","timestamp","httpMethod","requestBody","startTime","performance","err","endTime","errorRequestBody","errorRecord","rawResponseHeaders","binary","capturedRequestHeaders","capturedResponseHeaders","capturedRequestBody","capturedResponseBody","record","ApiRequestTracker","NavigationTracker","_NavigationTracker","page","pending","type","navigations","event","handler","onDomContentLoaded","onFrameNavigated","frame","frameObj","lastRecord","onConsoleMessage","msg","msgObj","text","location","loc","level","onRequest","request","req","reqId","postData","onResponse","resp","status","contentLength","resourceType","typeKey","responseTimeMs","respHeaders","responseSize","bodyPromise","responseBody","captured","onRequestFailed","origPush","origReplace","args","API_CONFIG_ANNOTATION","LEGACY_TRACK_API_CALLS_ANNOTATION","readApiConfig","configAnnotation","a","deserializeRegExp","legacyAnnotation","_key","source","flags","testRelicFixture","use","tracker","networkRequestsFile","networkRequestsCount","consoleLogsFile","consoleLogsCount","payload","PAYLOAD_VERSION","ATTACHMENT_NAME","ATTACHMENT_CONTENT_TYPE","apiTracker","apiCallsFile","apiCallsCount","apiAssertions","base","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 * 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 type { ApiAssertion, AssertionType, AssertionLocation } from '@testrelic/core';\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 const content = readFileSync(filePath, '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 */\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 for (const line of lines) {\n // Skip frames from the SDK's own assertion capture internals\n if (line.includes('/assertion-tracker.') ||\n line.includes('/api-request-tracker.') ||\n line.includes('node:internal') ||\n line.includes(' at new Error') ||\n line.includes(' at captureAssertionLocation') ||\n line.includes(' at expectWrapper') ||\n line.includes(' at wrappedMatcher')) {\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 file: 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 */\nexport function createAssertionAwareExpect(\n tracker: AssertionTracker,\n apiTracker: ApiTrackerLike,\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 // 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 for (const key of Object.keys(originalExpect as object)) {\n (wrappedExpect as Record<string, unknown>)[key] =\n (originalExpect as Record<string, unknown>)[key];\n }\n\n return wrappedExpect;\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 if (typeof value === 'string' || 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 if (Object.hasOwn(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 const fieldSet = new Set(redactList);\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 */\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.hasOwn(value, key)) continue;\n const val = (value as Record<string, unknown>)[key];\n if (fields.has(key)) {\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 * 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 } 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 } 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// ---------------------------------------------------------------------------\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 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 /** Return file path and count for the JSONL file containing API call records. */\n getFileData(): { apiCallsFile: string | null; apiCallsCount: number } {\n this.apiCallWriter?.close();\n return {\n apiCallsFile: this.apiCallWriter?.getCount() ? this.apiCallWriter.getPath() : null,\n apiCallsCount: this.apiCallWriter?.getCount() ?? 0,\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,\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 }\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 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 // 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: response.url(),\n requestHeaders: capturedRequestHeaders,\n requestBody: capturedRequestBody,\n responseStatusCode: response.status(),\n responseStatusText: response.statusText(),\n responseHeaders: capturedResponseHeaders,\n responseBody: capturedResponseBody,\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 }\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}\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 private currentNetworkCounter: NetworkCounter | null = null;\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 // Finalize network stats for the last navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\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 (this.page as Record<string, unknown>).goto = this.origGoto;\n (this.page as Record<string, unknown>).goBack = this.origGoBack;\n (this.page as Record<string, unknown>).goForward = this.origGoForward;\n (this.page as Record<string, unknown>).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 return this.records;\n }\n\n private interceptMethods(): void {\n const self = this;\n const page = this.page;\n\n (page as Record<string, unknown>).goto = async function (url: string, options?: unknown) {\n self.recordNavigation(url, 'goto');\n return self.origGoto(url, options);\n };\n\n (page as Record<string, unknown>).goBack = async function (options?: unknown) {\n const result = await self.origGoBack(options);\n self.recordNavigation(page.url(), 'back');\n return result;\n };\n\n (page as Record<string, unknown>).goForward = async function (options?: unknown) {\n const result = await self.origGoForward(options);\n self.recordNavigation(page.url(), 'forward');\n return result;\n };\n\n (page as Record<string, unknown>).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 if (this.currentNetworkCounter) {\n this.currentNetworkCounter.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 };\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 // Aggregate stats (existing logic)\n if (this.currentNetworkCounter) {\n const status = resp.status();\n if (status >= 400) {\n this.currentNetworkCounter.failedRequests++;\n this.currentNetworkCounter.failedRequestUrls.push(status + ' ' + resp.url());\n }\n const contentLength = resp.headers()['content-length'];\n if (contentLength) {\n this.currentNetworkCounter.totalBytes += parseInt(contentLength, 10) || 0;\n }\n const resourceType = resp.request().resourceType();\n const typeKey = this.mapResourceType(resourceType);\n this.currentNetworkCounter.byType[typeKey]++;\n }\n // Individual request capture\n const reqId = (resp.request() as Record<string, unknown>).__testrelic_id as string | undefined;\n if (!reqId) return;\n const pending = this.pendingRequests.get(reqId);\n if (!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 // Aggregate stats (existing logic)\n if (this.currentNetworkCounter) {\n this.currentNetworkCounter.failedRequests++;\n try {\n const req = request as { url(): string };\n this.currentNetworkCounter.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; __testrelic_id?: string };\n const reqId = (req as Record<string, unknown>).__testrelic_id as string | undefined;\n if (!reqId) return;\n const pending = this.pendingRequests.get(reqId);\n if (!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 // Finalize network stats for the previous navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\n\n this.records.push({\n url,\n navigationType: type,\n timestamp: new Date().toISOString(),\n });\n\n // Start fresh counter for this navigation\n if (this.includeNetworkStats) {\n this.currentNetworkCounter = 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 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 * @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, createAssertionAwareExpect } 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\nexport { playwrightExpect as expect };\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\n await use(request);\n\n try {\n const { apiCallsFile, apiCallsCount } = 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 };\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/** 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 { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker } 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 * 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\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 }\n\n try {\n consoleWriter?.close();\n const { apiCallsFile, apiCallsCount } = 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 };\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"]}
|