{"version":3,"sources":["../src/jsonl-stream.ts","../src/navigation-tracker.ts","../src/assertion-tracker.ts","../src/api-redactor.ts","../src/api-url-filter.ts","../src/api-request-tracker.ts","../src/fixture.ts"],"names":["TESTRELIC_TMP_DIR","join","tmpdir","JsonlFileWriter","prefix","mkdirSync","randomUUID","openSync","obj","line","writeSync","closeSync","unlinkSync","TEXT_CONTENT_TYPES","isTextContentType","contentType","lower","NavigationTracker","_NavigationTracker","page","options","pending","type","navigations","record","_testInfo","event","handler","self","url","result","onDomContentLoaded","onFrameNavigated","frame","frameObj","lastRecord","onConsoleMessage","msg","msgObj","text","data","location","loc","level","onRequest","request","navIndex","counter","req","reqId","postData","onResponse","response","resp","status","contentLength","resourceType","typeKey","responseTimeMs","respHeaders","responseSize","binary","bodyPromise","responseBody","captured","onRequestFailed","origPush","origReplace","args","index","i","CALL_ID_SYMBOL","ASSERTION_ANNOTATION_TYPE","valueCallIdMap","AssertionTracker","assertion","callId","testInfo","sourceCache","getSourceLines","filePath","cached","path","fileURLToPath","lines","readFileSync","captureAssertionLocation","stack","match","inferAssertionType","expression","_value","expr","buildExpectWrapper","getTracker","getApiTracker","originalExpect","wrappedExpect","received","rest","tracker","apiTracker","tagged","matchers","createMatcherProxy","key","activeTracker","activeApiTracker","setActiveAssertionContext","createActiveAssertionAwareExpect","ctx","target","prop","receiver","value","expected","actual","thrownError","recordAssertionResult","err","receivedMatch","matcherName","assertionType","safeSerializable","REDACTED","redactHeaders","headers","redactList","lowerList","h","redactBodyFields","body","parsed","fieldSet","f","redacted","walkAndRedact","fields","item","val","SECRET_QUERY_PARAMS","stripSecretQueryParams","rawUrl","changed","shouldTrackApiUrl","include","exclude","pattern","matchesPattern","globToRegex","glob","char","HTTP_METHODS","extractRequestBody","multipart","descriptor","maskCookieValue","parseRequestCookies","raw","k","v","out","part","seg","eq","name","parseResponseCookies","setCookieValues","segRaw","parts","cookieValue","attributes","p","_ApiRequestTracker","context","assertionTracker","apiConfig","method","original","origDispose","origHeaders","origHeadersArray","origJson","origStatus","origStatusText","origOk","origText","origBody","id","timestamp","httpMethod","requestBody","startTime","performance","endTime","errorRequestBody","errorRecord","rawResponseHeaders","capturedRequestHeaders","capturedResponseHeaders","capturedRequestBody","capturedResponseBody","requestCookies","responseCookies","ApiRequestTracker","expect","playwrightExpect","API_CONFIG_ANNOTATION","LEGACY_TRACK_API_CALLS_ANNOTATION","readApiConfig","configAnnotation","a","deserializeRegExp","legacyAnnotation","_key","source","flags","testRelicFixture","use","networkRequestsFile","networkRequestsCount","consoleLogsFile","consoleLogsCount","payload","PAYLOAD_VERSION","ATTACHMENT_NAME","ATTACHMENT_CONTENT_TYPE","apiCallsFile","apiCallsCount","apiAssertions","trackPage","test","base"],"mappings":"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","file":"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 * NavigationTracker — wraps a Playwright Page to track navigation events.\n *\n * Intercepts page methods (goto, goBack, goForward, reload) and listens\n * for DOM events (framenavigated, domcontentloaded, load) plus injected\n * SPA detection scripts.\n */\n\nimport type { CapturedNetworkRequest, ConsoleLogEntry, ConsoleLogLevel, NavigationAnnotation, NavigationType, NetworkStats, ResourceBreakdown, ResourceType } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\n\n// Minimal Playwright Page type subset for loose coupling\ninterface PageLike {\n  goto(url: string, options?: unknown): Promise<unknown>;\n  goBack(options?: unknown): Promise<unknown>;\n  goForward(options?: unknown): Promise<unknown>;\n  reload(options?: unknown): Promise<unknown>;\n  url(): string;\n  on(event: string, handler: (...args: unknown[]) => void): void;\n  off(event: string, handler: (...args: unknown[]) => void): void;\n  addInitScript(script: string | { path?: string } | (() => void)): Promise<void>;\n  evaluate(fn: (...args: unknown[]) => unknown, ...args: unknown[]): Promise<unknown>;\n  mainFrame(): { url(): string };\n}\n\ninterface TestInfoLike {\n  annotations: Array<{ type: string; description?: string }>;\n}\n\ninterface NavigationRecord {\n  url: string;\n  navigationType: NavigationType;\n  timestamp: string;\n  domContentLoadedAt?: string;\n  networkIdleAt?: string;\n  networkStats?: NetworkStats;\n}\n\ninterface NetworkCounter {\n  totalRequests: number;\n  failedRequests: number;\n  failedRequestUrls: string[];\n  totalBytes: number;\n  byType: Record<keyof ResourceBreakdown, number>;\n}\n\ninterface PendingRequest {\n  url: string;\n  method: string;\n  resourceType: ResourceType;\n  headers: Record<string, string>;\n  postData: string | null;\n  postDataTruncated: boolean;\n  startedAt: string;\n  startTimeMs: number;\n  /** Index into `records`/`navCounters` of the navigation active when this\n   *  request STARTED — so its response/failure stats attribute to the right\n   *  navigation even if they arrive after the next navigation began. */\n  navIndex: number;\n}\n\nconst TEXT_CONTENT_TYPES = [\n  'text/',\n  'application/json',\n  'application/xml',\n  'application/javascript',\n  'application/x-www-form-urlencoded',\n  'application/graphql',\n];\n\nfunction isTextContentType(contentType: string): boolean {\n  const lower = contentType.toLowerCase();\n  return TEXT_CONTENT_TYPES.some(prefix => lower.includes(prefix));\n}\n\nexport class NavigationTracker {\n  private records: NavigationRecord[] = [];\n  private listeners: Array<{ event: string; handler: (...args: unknown[]) => void }> = [];\n  private origGoto: PageLike['goto'];\n  private origGoBack: PageLike['goBack'];\n  private origGoForward: PageLike['goForward'];\n  private origReload: PageLike['reload'];\n  private lastDomContentLoaded: string | undefined;\n  private includeNetworkStats: boolean;\n  /** One counter per navigation (parallel to `records` by index). A request's\n   *  stats land in the counter of the nav that issued it, so a response arriving\n   *  after the next navigation no longer leaks into it. */\n  private navCounters: NetworkCounter[] = [];\n  private pendingRequests = new Map<string, PendingRequest>();\n  private networkWriter: JsonlFileWriter | null = null;\n  private consoleWriter: JsonlFileWriter | null = null;\n  private pendingBodyReads: Promise<void>[] = [];\n  private requestIdCounter = 0;\n  private networkRequestCount = 0;\n  private consoleLogCount = 0;\n\n  constructor(private page: PageLike, options?: { includeNetworkStats?: boolean }) {\n    this.includeNetworkStats = options?.includeNetworkStats ?? true;\n    // Store originals\n    this.origGoto = page.goto.bind(page);\n    this.origGoBack = page.goBack.bind(page);\n    this.origGoForward = page.goForward.bind(page);\n    this.origReload = page.reload.bind(page);\n\n    // Initialize JSONL writers for heavy data\n    try {\n      if (this.includeNetworkStats) {\n        this.networkWriter = new JsonlFileWriter('network');\n      }\n      this.consoleWriter = new JsonlFileWriter('console');\n    } catch {\n      // Graceful degradation if temp dir is not writable\n    }\n\n    this.interceptMethods();\n    this.attachListeners();\n  }\n\n  async init(): Promise<void> {\n    await this.injectSPADetection();\n  }\n\n  /** Flush remaining pending requests to the JSONL writer and close it. */\n  async finalizeCapturedRequests(): Promise<void> {\n    // Wait for all pending body reads to complete\n    await Promise.allSettled(this.pendingBodyReads);\n    this.pendingBodyReads = [];\n    // Convert any remaining pending requests (incomplete) to JSONL entries\n    for (const [, pending] of this.pendingRequests) {\n      if (this.networkWriter) {\n        this.networkWriter.append({\n          url: pending.url,\n          method: pending.method,\n          resourceType: pending.resourceType,\n          statusCode: 0,\n          responseTimeMs: Date.now() - pending.startTimeMs,\n          startedAt: pending.startedAt,\n          requestHeaders: pending.headers,\n          requestBody: pending.postData,\n          responseBody: null,\n          responseHeaders: null,\n          contentType: null,\n          responseSize: 0,\n          requestBodyTruncated: pending.postDataTruncated,\n          responseBodyTruncated: false,\n          isBinary: false,\n          error: 'incomplete',\n        } satisfies CapturedNetworkRequest);\n        this.networkRequestCount++;\n      }\n    }\n    this.pendingRequests.clear();\n    // Close writers so files are ready to be moved\n    this.networkWriter?.close();\n    this.consoleWriter?.close();\n  }\n\n  private static mapConsoleType(type: string): ConsoleLogLevel {\n    switch (type) {\n      case 'log': return 'log';\n      case 'warning': return 'warn';\n      case 'error': return 'error';\n      case 'info': return 'info';\n      case 'debug': return 'debug';\n      default: return 'log';\n    }\n  }\n\n  async getFileData(): Promise<{ navigations: NavigationAnnotation[]; networkRequestsFile: string | null; networkRequestsCount: number; consoleLogsFile: string | null; consoleLogsCount: number }> {\n    // Assemble each navigation's network stats from its OWN counter, so every\n    // navigation (not just the last) reflects exactly the requests it issued —\n    // including responses that arrived after the next navigation started.\n    this.assembleNetworkStats();\n\n    const navigations: NavigationAnnotation[] = this.records.map(record => ({\n      url: record.url,\n      navigationType: record.navigationType,\n      timestamp: record.timestamp,\n      domContentLoadedAt: record.domContentLoadedAt,\n      networkIdleAt: record.networkIdleAt,\n      networkStats: record.networkStats,\n    }));\n\n    // Finalize and close JSONL writers\n    if (this.includeNetworkStats) {\n      await this.finalizeCapturedRequests();\n    } else {\n      this.consoleWriter?.close();\n    }\n\n    return {\n      navigations,\n      networkRequestsFile: this.networkWriter?.getCount() ? this.networkWriter.getPath() : null,\n      networkRequestsCount: this.networkWriter?.getCount() ?? 0,\n      consoleLogsFile: this.consoleWriter?.getCount() ? this.consoleWriter.getPath() : null,\n      consoleLogsCount: this.consoleWriter?.getCount() ?? 0,\n    };\n  }\n\n  /** @deprecated Legacy annotations no longer used — data flows via file-based payloads. */\n  async flushLegacyAnnotations(_testInfo: TestInfoLike): Promise<void> {\n    // No-op in v2 — all data flows via getFileData() and JSONL files\n  }\n\n  dispose(): void {\n    // Restore original methods\n    (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    // Assemble on demand so each record reflects the latest per-nav counters\n    // (late responses keep accumulating into the issuing nav's counter).\n    this.assembleNetworkStats();\n    return this.records;\n  }\n\n  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        // Attribute this request to the navigation active right now; its response\n        // may arrive after the next navigation starts, but the count belongs here.\n        const navIndex = this.records.length - 1;\n        const counter = this.counterForNav(navIndex);\n        if (counter) {\n          counter.totalRequests++;\n        }\n        // Capture individual request details\n        try {\n          const req = request as {\n            url(): string;\n            method(): string;\n            resourceType(): string;\n            headers(): Record<string, string>;\n            postData(): string | null;\n          };\n          const reqId = String(this.requestIdCounter++);\n          const postData = req.postData() ?? null;\n          const pending: PendingRequest = {\n            url: req.url(),\n            method: req.method(),\n            resourceType: this.mapResourceType(req.resourceType()),\n            headers: req.headers(),\n            postData,\n            postDataTruncated: false,\n            startedAt: new Date().toISOString(),\n            startTimeMs: Date.now(),\n            navIndex,\n          };\n          this.pendingRequests.set(reqId, pending);\n          // Tag the request object with our ID for lookup in response/failed handlers\n          (request as Record<string, unknown>).__testrelic_id = reqId;\n        } catch {\n          // Ignore request capture errors\n        }\n      };\n      this.page.on('request', onRequest);\n      this.listeners.push({ event: 'request', handler: onRequest });\n\n      const onResponse = (response: unknown) => {\n        try {\n          const resp = response as {\n            url(): string;\n            status(): number;\n            headers(): Record<string, string>;\n            request(): { resourceType(): string; __testrelic_id?: string };\n            body(): Promise<Buffer>;\n          };\n          // Attribute aggregate stats to the navigation that ISSUED the request\n          // (falls back to the current nav when the request wasn't tracked).\n          const reqId = (resp.request() as Record<string, unknown>).__testrelic_id as string | undefined;\n          const pending = reqId ? this.pendingRequests.get(reqId) : undefined;\n          const counter = this.counterForNav(pending ? pending.navIndex : this.records.length - 1);\n          if (counter) {\n            const status = resp.status();\n            if (status >= 400) {\n              counter.failedRequests++;\n              counter.failedRequestUrls.push(status + ' ' + resp.url());\n            }\n            const contentLength = resp.headers()['content-length'];\n            if (contentLength) {\n              counter.totalBytes += parseInt(contentLength, 10) || 0;\n            }\n            const resourceType = resp.request().resourceType();\n            const typeKey = this.mapResourceType(resourceType);\n            counter.byType[typeKey]++;\n          }\n          // Individual request capture\n          if (!reqId || !pending) return;\n          this.pendingRequests.delete(reqId);\n          const responseTimeMs = Date.now() - pending.startTimeMs;\n          const respHeaders = resp.headers();\n          const contentType = respHeaders['content-type'] ?? null;\n          const responseSize = parseInt(respHeaders['content-length'] ?? '0', 10) || 0;\n          const binary = contentType ? !isTextContentType(contentType) : false;\n          // Read response body asynchronously and write to JSONL (not held in memory)\n          const bodyPromise = (async () => {\n            let responseBody: string | null = null;\n            if (!binary) {\n              try {\n                const buf = await resp.body();\n                responseBody = buf.toString('utf-8');\n              } catch {\n                // Body unavailable (e.g., redirect)\n              }\n            }\n            const captured: CapturedNetworkRequest = {\n              url: pending.url,\n              method: pending.method,\n              resourceType: pending.resourceType,\n              statusCode: resp.status(),\n              responseTimeMs,\n              startedAt: pending.startedAt,\n              requestHeaders: pending.headers,\n              requestBody: pending.postData,\n              responseBody,\n              responseHeaders: respHeaders,\n              contentType,\n              responseSize,\n              requestBodyTruncated: pending.postDataTruncated,\n              responseBodyTruncated: false,\n              isBinary: binary,\n              error: null,\n            };\n            // Write to JSONL file — body is released from memory after this\n            if (this.networkWriter) {\n              this.networkWriter.append(captured);\n              this.networkRequestCount++;\n            }\n          })();\n          this.pendingBodyReads.push(bodyPromise);\n        } catch {\n          // Ignore response processing errors\n        }\n      };\n      this.page.on('response', onResponse);\n      this.listeners.push({ event: 'response', handler: onResponse });\n\n      const onRequestFailed = (request: unknown) => {\n        const reqId = (request as Record<string, unknown>).__testrelic_id as string | undefined;\n        const pending = reqId ? this.pendingRequests.get(reqId) : undefined;\n        // Attribute the failure to the navigation that issued the request.\n        const counter = this.counterForNav(pending ? pending.navIndex : this.records.length - 1);\n        if (counter) {\n          counter.failedRequests++;\n          try {\n            const req = request as { url(): string };\n            counter.failedRequestUrls.push('ERR ' + req.url());\n          } catch {\n            // Ignore\n          }\n        }\n        // Individual request capture\n        try {\n          const req = request as { failure(): { errorText: string } | null };\n          if (!reqId || !pending) return;\n          this.pendingRequests.delete(reqId);\n          const captured: CapturedNetworkRequest = {\n            url: pending.url,\n            method: pending.method,\n            resourceType: pending.resourceType,\n            statusCode: 0,\n            responseTimeMs: Date.now() - pending.startTimeMs,\n            startedAt: pending.startedAt,\n            requestHeaders: pending.headers,\n            requestBody: pending.postData,\n            responseBody: null,\n            responseHeaders: null,\n            contentType: null,\n            responseSize: 0,\n            requestBodyTruncated: pending.postDataTruncated,\n            responseBodyTruncated: false,\n            isBinary: false,\n            error: req.failure()?.errorText ?? 'Unknown error',\n          };\n          if (this.networkWriter) {\n            this.networkWriter.append(captured);\n            this.networkRequestCount++;\n          }\n        } catch {\n          // Ignore\n        }\n      };\n      this.page.on('requestfailed', onRequestFailed);\n      this.listeners.push({ event: 'requestfailed', handler: onRequestFailed });\n    }\n  }\n\n  private async injectSPADetection(): Promise<void> {\n    try {\n      await this.page.addInitScript(() => {\n        const origPush = history.pushState.bind(history);\n        const origReplace = history.replaceState.bind(history);\n\n        history.pushState = function (...args: Parameters<typeof origPush>) {\n          origPush(...args);\n          // eslint-disable-next-line no-console\n          console.debug('__testrelic_nav:' + JSON.stringify({\n            type: 'spa_route',\n            url: location.href,\n          }));\n        };\n\n        history.replaceState = function (...args: Parameters<typeof origReplace>) {\n          origReplace(...args);\n          // eslint-disable-next-line no-console\n          console.debug('__testrelic_nav:' + JSON.stringify({\n            type: 'spa_replace',\n            url: location.href,\n          }));\n        };\n\n        window.addEventListener('popstate', () => {\n          // eslint-disable-next-line no-console\n          console.debug('__testrelic_nav:' + JSON.stringify({\n            type: 'popstate',\n            url: location.href,\n          }));\n        });\n\n        window.addEventListener('hashchange', () => {\n          // eslint-disable-next-line no-console\n          console.debug('__testrelic_nav:' + JSON.stringify({\n            type: 'hash_change',\n            url: location.href,\n          }));\n        });\n      });\n    } catch {\n      // Ignore injection errors (page may be closed)\n    }\n  }\n\n  private recordNavigation(url: string, type: NavigationType): void {\n    this.records.push({\n      url,\n      navigationType: type,\n      timestamp: new Date().toISOString(),\n    });\n\n    // Each navigation gets its own counter at the same index as its record, so\n    // late-arriving responses/failures attribute to the nav that issued the\n    // request (via PendingRequest.navIndex) instead of leaking to the next nav.\n    if (this.includeNetworkStats) {\n      this.navCounters.push(this.createNetworkCounter());\n    }\n  }\n\n  private createNetworkCounter(): NetworkCounter {\n    return {\n      totalRequests: 0,\n      failedRequests: 0,\n      failedRequestUrls: [],\n      totalBytes: 0,\n      byType: { xhr: 0, document: 0, script: 0, stylesheet: 0, image: 0, font: 0, other: 0 },\n    };\n  }\n\n  /** The per-navigation counter for a record index, or undefined when the index\n   *  predates the first navigation (-1) or is out of range. */\n  private counterForNav(index: number): NetworkCounter | undefined {\n    return index >= 0 ? this.navCounters[index] : undefined;\n  }\n\n  /** Snapshot each navigation's accumulated counter into record.networkStats.\n   *  Idempotent — safe to call repeatedly (getRecords / getFileData). */\n  private assembleNetworkStats(): void {\n    if (!this.includeNetworkStats) return;\n    for (let i = 0; i < this.records.length; i++) {\n      const counter = this.navCounters[i];\n      if (!counter) continue;\n      this.records[i].networkStats = {\n        totalRequests: counter.totalRequests,\n        failedRequests: counter.failedRequests,\n        failedRequestUrls: [...counter.failedRequestUrls],\n        totalBytes: counter.totalBytes,\n        byType: { ...counter.byType },\n      };\n    }\n  }\n\n  private mapResourceType(type: string): keyof ResourceBreakdown {\n    switch (type) {\n      case 'xhr':\n      case 'fetch':\n        return 'xhr';\n      case 'document':\n        return 'document';\n      case 'script':\n        return 'script';\n      case 'stylesheet':\n        return 'stylesheet';\n      case 'image':\n        return 'image';\n      case 'font':\n        return 'font';\n      default:\n        return 'other';\n    }\n  }\n}\n","/**\n * AssertionTracker — Captures and links assertions to API calls\n *\n * Records every assertion made on API response data (both passing and failing),\n * links each to the originating API call by call ID, and flushes the data to\n * testInfo.annotations for the reporter to consume.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport type { ApiAssertion, AssertionType, AssertionLocation } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Symbol used to tag APIResponse objects with their call ID. */\nexport const CALL_ID_SYMBOL: unique symbol = Symbol.for('__testrelic_call_id');\n\n/** Annotation type for assertion data transport (fixture → reporter). */\nexport const ASSERTION_ANNOTATION_TYPE = '__testrelic_api_assertions';\n\n/** WeakMap to track call IDs for object values extracted from responses. */\nexport const valueCallIdMap = new WeakMap<object, string>();\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n  annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// AssertionTracker\n// ---------------------------------------------------------------------------\n\nexport class AssertionTracker {\n  private assertions: ApiAssertion[] = [];\n  private currentCallId: string | null = null;\n\n  /** Record a captured assertion. */\n  recordAssertion(assertion: ApiAssertion): void {\n    this.assertions.push(assertion);\n  }\n\n  /** Get all recorded assertions. */\n  getAssertions(): readonly ApiAssertion[] {\n    return this.assertions;\n  }\n\n  /** Set the most recent API call ID for temporal fallback linking. */\n  setCurrentCallId(callId: string): void {\n    this.currentCallId = callId;\n  }\n\n  /** Get the most recent API call ID. */\n  getCurrentCallId(): string | null {\n    return this.currentCallId;\n  }\n\n  /** Return assertion data for consolidated payload. */\n  getData(): ApiAssertion[] {\n    return [...this.assertions];\n  }\n\n  /** Flush assertion data to testInfo annotations (legacy). */\n  flushLegacyAnnotations(testInfo: TestInfoLike): void {\n    if (this.assertions.length === 0) return;\n\n    testInfo.annotations.push({\n      type: ASSERTION_ANNOTATION_TYPE,\n      description: JSON.stringify(this.assertions),\n    });\n  }\n\n  /** Clear all state for garbage collection. */\n  dispose(): void {\n    this.assertions = [];\n    this.currentCallId = null;\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Location Capture\n// ---------------------------------------------------------------------------\n\n/** Cache for source file lines to avoid repeated reads. */\nconst sourceCache = new Map<string, string[]>();\n\nfunction getSourceLines(filePath: string): string[] | null {\n  const cached = sourceCache.get(filePath);\n  if (cached) return cached;\n\n  try {\n    // V8 stack frames are file:// URLs under ESM (and always on Windows, e.g.\n    // `file:///C:/…`). readFileSync needs a filesystem path, so normalize first\n    // — otherwise the source line (and thus the assertion expression + type)\n    // can't be read and every assertion degrades to a generic \"custom\" one.\n    const path = filePath.startsWith('file://') ? fileURLToPath(filePath) : filePath;\n    const content = readFileSync(path, 'utf-8');\n    const lines = content.split('\\n');\n    sourceCache.set(filePath, lines);\n    return lines;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Parse the call stack to find the assertion location in user test code.\n * Returns the first stack frame that is NOT inside the SDK's own source files.\n */\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 */\nfunction buildExpectWrapper(\n  getTracker: () => AssertionTracker | null,\n  getApiTracker: () => ApiTrackerLike | null,\n  // SAFETY: originalExpect is the Playwright expect — typed as Function to avoid\n  // importing @playwright/test in this module's runtime\n  originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n  const wrappedExpect = function expectWrapper(received: unknown, ...rest: unknown[]): unknown {\n    // Resolve the assertion context (explicit per-instance, or the active\n    // per-test context set by the fixture). With no context, this is a\n    // transparent pass-through to the raw Playwright expect.\n    const tracker = getTracker();\n    const apiTracker = getApiTracker();\n    if (!tracker || !apiTracker) {\n      return (originalExpect as Function)(received, ...rest);\n    }\n\n    // Determine call ID from the value being asserted\n    let callId: string | null = null;\n\n    // Strategy 1: Check if the value itself is tagged (response object)\n    if (received !== null && received !== undefined && typeof received === 'object') {\n      const tagged = (received as Record<symbol, string>)[CALL_ID_SYMBOL];\n      if (typeof tagged === 'string') {\n        callId = tagged;\n      }\n    }\n\n    // Strategy 2: Check if value is in the WeakMap (extracted from response)\n    if (!callId && received !== null && received !== undefined && typeof received === 'object') {\n      callId = valueCallIdMap.get(received as object) ?? null;\n    }\n\n    // Strategy 3: Check via the API tracker's value-to-callId mapping\n    if (!callId) {\n      callId = apiTracker.getCallIdForValue(received);\n    }\n\n    // Strategy 4: Temporal fallback — use the most recent API call\n    if (!callId) {\n      callId = tracker.getCurrentCallId();\n    }\n\n    // If no call ID found, this assertion is not related to an API call — skip capture\n    if (!callId) {\n      return (originalExpect as Function)(received, ...rest);\n    }\n\n    // Capture location before the assertion runs\n    const location = captureAssertionLocation();\n\n    // Read the source expression (best-effort)\n    let expression: string | undefined;\n    if (location) {\n      const lines = getSourceLines(location.file);\n      if (lines && location.line > 0 && location.line <= lines.length) {\n        expression = lines[location.line - 1].trim();\n      }\n    }\n\n    // Get the matchers object from the original expect\n    const matchers = (originalExpect as Function)(received, ...rest);\n\n    // If no callId at this point, just return the matchers directly\n    if (!callId) return matchers;\n\n    // Wrap the matchers to intercept assertion calls\n    return createMatcherProxy(matchers as object, {\n      tracker,\n      callId,\n      received,\n      location: location ?? { file: 'unknown', line: 0 },\n      expression,\n    });\n  };\n\n  // Copy static methods from originalExpect (e.g., expect.soft, expect.configure, etc.)\n  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 * Per-instance assertion-aware expect — records into the explicitly-provided\n * tracker. Used by unit tests and any caller holding tracker references.\n */\nexport function createAssertionAwareExpect(\n  tracker: AssertionTracker,\n  apiTracker: ApiTrackerLike,\n  originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n  return buildExpectWrapper(() => tracker, () => apiTracker, originalExpect);\n}\n\n// ---------------------------------------------------------------------------\n// Active assertion context (fixture → exported `expect`)\n//\n// Fixtures export a single module-level `expect`, but the trackers are created\n// per-test inside the fixture. Playwright runs tests serially within a worker,\n// so the fixture sets an \"active context\" at test start and clears it at test\n// end; the exported `expect` records into whichever test is currently running.\n// ---------------------------------------------------------------------------\n\nlet activeTracker: AssertionTracker | null = null;\nlet activeApiTracker: ApiTrackerLike | null = null;\n\n/** Set (or clear, with nulls) the active per-test assertion context. */\nexport function setActiveAssertionContext(\n  tracker: AssertionTracker | null,\n  apiTracker: ApiTrackerLike | null,\n): void {\n  activeTracker = tracker;\n  activeApiTracker = apiTracker;\n}\n\n/**\n * Assertion-aware expect that records into whichever tracker is currently\n * active (see setActiveAssertionContext). Falls back to raw expect when none —\n * so it is safe to construct once at module load and import everywhere.\n */\nexport function createActiveAssertionAwareExpect(\n  originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n  return buildExpectWrapper(() => activeTracker, () => activeApiTracker, originalExpect);\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  // Field matching is case-insensitive: a config entry of `token` must also\n  // catch `Token`, `TOKEN`, `Access_Token`, etc. so casing can't smuggle a\n  // secret past the redactor.\n  const fieldSet = new Set(redactList.map((f) => f.toLowerCase()));\n  const redacted = walkAndRedact(parsed, fieldSet);\n  return JSON.stringify(redacted);\n}\n\n/**\n * Recursively walk an object/array and redact matching field values.\n * Field names are compared case-insensitively (lowercased).\n */\nfunction walkAndRedact(value: unknown, fields: Set<string>): unknown {\n  if (Array.isArray(value)) {\n    return value.map((item) => walkAndRedact(item, fields));\n  }\n\n  if (typeof value === 'object' && value !== null) {\n    const result: Record<string, unknown> = {};\n    for (const key of Object.keys(value)) {\n      if (!Object.hasOwn(value, key)) continue;\n      const val = (value as Record<string, unknown>)[key];\n      if (fields.has(key.toLowerCase())) {\n        result[key] = REDACTED;\n      } else {\n        result[key] = walkAndRedact(val, fields);\n      }\n    }\n    return result;\n  }\n\n  return value;\n}\n\n/**\n * Query-string parameters whose values are commonly secrets. Their values are\n * stripped from any URL we store so tokens don't leak via the request URL\n * (which is persisted and rendered verbatim in the report/cloud).\n */\nconst SECRET_QUERY_PARAMS = new Set(['access_token', 'api_key', 'token', 'sig']);\n\n/**\n * Remove known secret query parameters (access_token, api_key, token, sig)\n * from a URL. Matching is case-insensitive on the parameter name. Returns the\n * original string unchanged when it isn't a parseable absolute URL or when no\n * secret params are present (so URLs aren't needlessly re-serialized).\n */\nexport function stripSecretQueryParams(rawUrl: string): string {\n  if (!rawUrl) return rawUrl;\n  let url: URL;\n  try {\n    url = new URL(rawUrl);\n  } catch {\n    // Not an absolute URL — leave it untouched.\n    return rawUrl;\n  }\n  let changed = false;\n  for (const key of [...url.searchParams.keys()]) {\n    if (SECRET_QUERY_PARAMS.has(key.toLowerCase())) {\n      url.searchParams.delete(key);\n      changed = true;\n    }\n  }\n  return changed ? url.toString() : rawUrl;\n}\n","/**\n * API URL filtering — include/exclude patterns for API call tracking.\n *\n * Supports glob strings and RegExp objects. Exclude takes precedence over include.\n */\n\n/**\n * Determines whether an API call URL should be tracked.\n *\n * - No filters → track all\n * - Include set → URL must match at least one include pattern\n * - Exclude set → URL must not match any exclude pattern\n * - Both set → exclude takes precedence\n */\nexport function shouldTrackApiUrl(\n  url: string,\n  include: readonly (string | RegExp)[],\n  exclude: readonly (string | RegExp)[],\n): boolean {\n  // Check exclude first (takes precedence)\n  if (exclude.length > 0) {\n    for (const pattern of exclude) {\n      if (matchesPattern(url, pattern)) return false;\n    }\n  }\n\n  // Check include\n  if (include.length > 0) {\n    for (const pattern of include) {\n      if (matchesPattern(url, pattern)) return true;\n    }\n    return false; // Include set but no match → don't track\n  }\n\n  return true; // No filters → track all\n}\n\n/**\n * Test a URL against a single pattern (glob string or RegExp).\n */\nfunction matchesPattern(url: string, pattern: string | RegExp): boolean {\n  try {\n    if (pattern instanceof RegExp) {\n      return pattern.test(url);\n    }\n    // Convert glob to regex\n    const regex = globToRegex(pattern);\n    return regex.test(url);\n  } catch {\n    // Invalid pattern — skip with warning\n    console.warn(`[testrelic] Invalid URL filter pattern: ${String(pattern)}`);\n    return false;\n  }\n}\n\n/**\n * Convert a glob pattern to a RegExp.\n * Handles `**` (match anything including /) and `*` (match anything except /).\n */\nfunction globToRegex(glob: string): RegExp {\n  let result = '';\n  let i = 0;\n  while (i < glob.length) {\n    const char = glob[i];\n    if (char === '*' && glob[i + 1] === '*') {\n      result += '.*';\n      i += 2;\n      // Skip optional trailing /\n      if (glob[i] === '/') i++;\n    } else if (char === '*') {\n      result += '[^/]*';\n      i++;\n    } else if (char === '?') {\n      result += '[^/]';\n      i++;\n    } else if ('.+^${}()|[]\\\\'.includes(char)) {\n      result += '\\\\' + char;\n      i++;\n    } else {\n      result += char;\n      i++;\n    }\n  }\n  return new RegExp(result);\n}\n","/**\n * ApiRequestTracker — Proxy wrapper for Playwright's APIRequestContext\n *\n * Intercepts all HTTP method calls (.get, .post, .put, .patch, .delete,\n * .head, .fetch, .dispose) to record detailed API call analytics.\n * Data is flushed to testInfo.annotations for the reporter to consume.\n */\n\nimport { performance } from 'node:perf_hooks';\nimport type { APIRequestContext, APIResponse } from '@playwright/test';\nimport type { ApiCallRecord, CapturedCookie } from '@testrelic/core';\nimport { JsonlFileWriter } from './jsonl-stream.js';\nimport { CALL_ID_SYMBOL, valueCallIdMap } from './assertion-tracker.js';\nimport type { AssertionTracker } from './assertion-tracker.js';\nimport type { ResolvedApiConfig } from './config.js';\nimport { redactHeaders, redactBodyFields, stripSecretQueryParams } from './api-redactor.js';\nimport { shouldTrackApiUrl } from './api-url-filter.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'fetch'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nconst TEXT_CONTENT_TYPES = [\n  'text/',\n  'application/json',\n  'application/xml',\n  'application/javascript',\n  'application/x-www-form-urlencoded',\n  'application/graphql',\n];\n\nconst ANNOTATION_TYPE = '__testrelic_api_calls';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isTextContentType(contentType: string): boolean {\n  const lower = contentType.toLowerCase();\n  return TEXT_CONTENT_TYPES.some((prefix) => lower.includes(prefix));\n}\n\n/**\n * Extract request body from Playwright request options.\n * Handles `data` (string/object/Buffer), `form` (key-value), and\n * `multipart` (field descriptors) cases.\n */\nfunction extractRequestBody(options?: Record<string, unknown> | null): string | null {\n  if (!options) return null;\n\n  if (options.data !== undefined && options.data !== null) {\n    const data = options.data;\n    if (typeof data === 'string') return data;\n    if (Buffer.isBuffer(data)) return data.toString('base64');\n    return JSON.stringify(data);\n  }\n\n  if (options.form !== undefined && options.form !== null) {\n    return JSON.stringify(options.form);\n  }\n\n  if (options.multipart !== undefined && options.multipart !== null) {\n    // Capture field names and metadata, not full binary contents\n    const multipart = options.multipart as Record<string, unknown>;\n    const descriptor: Record<string, string> = {};\n    for (const [key, value] of Object.entries(multipart)) {\n      if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n        descriptor[key] = String(value);\n      } else if (value && typeof value === 'object' && 'name' in value) {\n        descriptor[key] = `[file: ${(value as { name: string }).name}]`;\n      } else {\n        descriptor[key] = '[binary]';\n      }\n    }\n    return JSON.stringify(descriptor);\n  }\n\n  return null;\n}\n\n/** Mask a cookie value so raw session tokens never reach the cloud. */\nfunction maskCookieValue(value: string): string {\n  return value ? '••••' : '';\n}\n\n/** Parse a request `Cookie` header (`a=1; b=2`) into masked cookies. */\nfunction parseRequestCookies(headers: Record<string, string> | null | undefined): CapturedCookie[] {\n  if (!headers) return [];\n  let raw: string | undefined;\n  for (const [k, v] of Object.entries(headers)) {\n    if (k.toLowerCase() === 'cookie') {\n      raw = v;\n      break;\n    }\n  }\n  if (!raw) return [];\n  const out: CapturedCookie[] = [];\n  for (const part of raw.split(';')) {\n    const seg = part.trim();\n    if (!seg) continue;\n    const eq = seg.indexOf('=');\n    const name = eq === -1 ? seg : seg.slice(0, eq).trim();\n    const value = eq === -1 ? '' : seg.slice(eq + 1).trim();\n    if (name) out.push({ name, value: maskCookieValue(value) });\n  }\n  return out;\n}\n\n/** Parse `Set-Cookie` response header values into masked cookies + attributes. */\nfunction parseResponseCookies(setCookieValues: string[]): CapturedCookie[] {\n  const out: CapturedCookie[] = [];\n  for (const value of setCookieValues) {\n    // Playwright's headersArray() usually returns one entry per Set-Cookie, but\n    // some runtimes join multiple cookies into one value — split on the\n    // `, <token>=` boundary so commas inside Expires=... don't break the parse.\n    for (const segRaw of value.split(/,(?=\\s*[A-Za-z0-9_.\\-]+=)/)) {\n      const seg = segRaw.trim();\n      if (!seg) continue;\n      const parts = seg.split(';');\n      const eq = parts[0].indexOf('=');\n      if (eq === -1) continue;\n      const name = parts[0].slice(0, eq).trim();\n      const cookieValue = parts[0].slice(eq + 1).trim();\n      const attributes = parts.slice(1).map((p) => p.trim()).filter(Boolean).join(' · ') || undefined;\n      if (name) out.push({ name, value: maskCookieValue(cookieValue), attributes });\n    }\n  }\n  return out;\n}\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n  annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// ApiRequestTracker\n// ---------------------------------------------------------------------------\n\nexport class ApiRequestTracker {\n  private readonly context: APIRequestContext;\n  private readonly originals: Map<string, (...args: never[]) => unknown> = new Map();\n  private apiCallWriter: JsonlFileWriter | null = null;\n  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: stripSecretQueryParams(url),\n            requestHeaders: null,\n            requestBody: errorRequestBody,\n            responseStatusCode: null,\n            responseStatusText: null,\n            responseHeaders: null,\n            responseBody: null,\n            responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n            isBinary: false,\n            error: err instanceof Error ? err.message : String(err),\n          };\n          if (self.apiCallWriter) {\n            self.apiCallWriter.append(errorRecord);\n            self.apiCallCount++;\n          }\n        } catch {\n          // FR-016: if recording fails, still re-throw the original exception\n        }\n        throw err;\n      }\n\n      // Success path: capture full record with config-driven capture control and redaction\n      try {\n        const endTime = performance.now();\n        const rawResponseHeaders = response.headers();\n        const contentType = rawResponseHeaders['content-type'] ?? null;\n        const binary = contentType ? !isTextContentType(contentType) : false;\n\n        // Capture control: headers\n        // For API calls, request headers come from options.headers (user-provided).\n        // Playwright's APIResponse doesn't expose the actual sent request headers,\n        // so we capture what we can from the options object.\n        let capturedRequestHeaders: Record<string, string> | null = null;\n        if (self.apiConfig.captureRequestHeaders && options?.headers) {\n          capturedRequestHeaders = options.headers as Record<string, string>;\n        }\n        let capturedResponseHeaders: Record<string, string> | null = null;\n        if (self.apiConfig.captureResponseHeaders) {\n          capturedResponseHeaders = rawResponseHeaders;\n        }\n\n        // Capture control: bodies\n        let capturedRequestBody: string | null = self.apiConfig.captureRequestBody ? requestBody : null;\n        let capturedResponseBody: string | null = null;\n        if (self.apiConfig.captureResponseBody) {\n          try {\n            if (binary) {\n              const buf = await response.body();\n              capturedResponseBody = buf.toString('base64');\n            } else {\n              capturedResponseBody = await response.text();\n            }\n          } catch {\n            // Body read failed — record what we have\n          }\n        }\n\n        // Parse cookies BEFORE redaction strips the Cookie/Set-Cookie headers.\n        // Values are masked, so the structured fields surface cookie names (and\n        // response attributes) for the inspector without leaking session tokens.\n        let requestCookies: CapturedCookie[] = [];\n        let responseCookies: CapturedCookie[] = [];\n        try {\n          if (self.apiConfig.captureRequestHeaders) {\n            requestCookies = parseRequestCookies(options?.headers as Record<string, string> | undefined);\n          }\n          if (self.apiConfig.captureResponseHeaders) {\n            const setCookieValues = response\n              .headersArray()\n              .filter((h) => h.name.toLowerCase() === 'set-cookie')\n              .map((h) => h.value);\n            responseCookies = parseResponseCookies(setCookieValues);\n          }\n        } catch {\n          // Cookie parsing is best-effort — never break capture.\n        }\n\n        // Apply redaction to captured data (FR-006: before storage)\n        capturedRequestHeaders = redactHeaders(capturedRequestHeaders, self.apiConfig.redactHeaders);\n        capturedResponseHeaders = redactHeaders(capturedResponseHeaders, self.apiConfig.redactHeaders);\n        capturedRequestBody = redactBodyFields(capturedRequestBody, self.apiConfig.redactBodyFields);\n        capturedResponseBody = redactBodyFields(capturedResponseBody, self.apiConfig.redactBodyFields);\n\n        const record: ApiCallRecord = {\n          id,\n          timestamp,\n          method: httpMethod,\n          url: stripSecretQueryParams(response.url()),\n          requestHeaders: capturedRequestHeaders,\n          requestBody: capturedRequestBody,\n          responseStatusCode: response.status(),\n          responseStatusText: response.statusText(),\n          responseHeaders: capturedResponseHeaders,\n          responseBody: capturedResponseBody,\n          requestCookies: requestCookies.length ? requestCookies : null,\n          responseCookies: responseCookies.length ? responseCookies : null,\n          responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n          isBinary: binary,\n          error: null,\n        };\n        if (self.apiCallWriter) {\n          self.apiCallWriter.append(record);\n          self.apiCallCount++;\n        }\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 * @testrelic/playwright-analytics/fixture\n *\n * Extended Playwright test fixture that wraps the default `page` fixture\n * to automatically track navigation events and API requests.\n *\n * Exports:\n * - testRelicFixture: Fixture object for use with base.extend()\n * - test: Pre-extended test instance (backward compatibility)\n * - expect: Re-exported from @playwright/test (backward compatibility)\n */\n\nimport { test as base, expect as playwrightExpect } from '@playwright/test';\nimport { NavigationTracker } from './navigation-tracker.js';\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker, createActiveAssertionAwareExpect, setActiveAssertionContext } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { TestRelicFilePayload } from '@testrelic/core';\nimport type { ResolvedApiConfig } from './config.js';\n\n/**\n * Assertion-aware `expect`. Inside a test that ran through this fixture, each\n * assertion on an API response is recorded and linked to its originating call,\n * so it surfaces in the cloud report's API workspace; outside one it behaves\n * exactly like Playwright's `expect`. Import THIS `expect` (not the one from\n * '@playwright/test') for API assertion capture.\n */\nexport const expect = createActiveAssertionAwareExpect(\n  playwrightExpect as unknown as (...args: unknown[]) => unknown,\n) as unknown as typeof playwrightExpect;\n\n/** Annotation type for API config (reporter → fixture). */\nconst API_CONFIG_ANNOTATION = '__testrelic_api_config';\n\n/** Legacy annotation type for backward compatibility. */\nconst LEGACY_TRACK_API_CALLS_ANNOTATION = '__testrelic_config_trackApiCalls';\n\n/**\n * Reads API config from testInfo annotations pushed by the reporter.\n * Returns a default config if no annotation is found (FR-013).\n */\nexport function readApiConfig(testInfo: { annotations: Array<{ type: string; description?: string }> }): ResolvedApiConfig {\n  // Try new unified annotation first\n  const configAnnotation = testInfo.annotations.find(\n    (a) => a.type === API_CONFIG_ANNOTATION && a.description !== undefined,\n  );\n  if (configAnnotation) {\n    try {\n      const parsed = JSON.parse(configAnnotation.description!, deserializeRegExp);\n      return parsed as ResolvedApiConfig;\n    } catch {\n      // Fall through to defaults\n    }\n  }\n\n  // Backward compatibility: check legacy trackApiCalls annotation\n  const legacyAnnotation = testInfo.annotations.find(\n    (a) => a.type === LEGACY_TRACK_API_CALLS_ANNOTATION && a.description !== undefined,\n  );\n  if (legacyAnnotation) {\n    return {\n      trackApiCalls: legacyAnnotation.description !== 'false',\n      captureRequestHeaders: true,\n      captureResponseHeaders: true,\n      captureRequestBody: true,\n      captureResponseBody: true,\n      captureAssertions: true,\n      redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n      redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n      apiIncludeUrls: [],\n      apiExcludeUrls: [],\n    };\n  }\n\n  // No annotation found — return sensible defaults (FR-013)\n  return {\n    trackApiCalls: true,\n    captureRequestHeaders: true,\n    captureResponseHeaders: true,\n    captureRequestBody: true,\n    captureResponseBody: true,\n    captureAssertions: true,\n    redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n    redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n    apiIncludeUrls: [],\n    apiExcludeUrls: [],\n  };\n}\n\n/** JSON reviver that reconstructs serialized RegExp objects. */\nfunction deserializeRegExp(_key: string, value: unknown): unknown {\n  if (\n    typeof value === 'object' &&\n    value !== null &&\n    (value as Record<string, unknown>).__regexp === true &&\n    typeof (value as Record<string, unknown>).source === 'string'\n  ) {\n    const { source, flags } = value as { source: string; flags: string };\n    return new RegExp(source, flags);\n  }\n  return value;\n}\n\n/**\n * Playwright-compatible fixture object for extending `test.extend()`.\n * Provides `page` (with navigation tracking) and `request` (with API tracking).\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicFixture);\n */\nexport const testRelicFixture = {\n  page: async (\n    { page }: { page: import('@playwright/test').Page },\n    use: (page: import('@playwright/test').Page) => Promise<void>,\n    testInfo: import('@playwright/test').TestInfo,\n  ): Promise<void> => {\n    const tracker = new NavigationTracker(page as never);\n    try {\n      await tracker.init();\n    } catch {\n      // Graceful degradation: continue without SPA detection\n    }\n\n    await use(page);\n\n    try {\n      const { navigations, networkRequestsFile, networkRequestsCount, consoleLogsFile, consoleLogsCount } = await tracker.getFileData();\n      const payload: TestRelicFilePayload = {\n        testRelicData: true,\n        version: PAYLOAD_VERSION as '2.0.0',\n        navigations,\n        apiAssertions: [],\n        networkRequestsFile,\n        networkRequestsCount,\n        consoleLogsFile,\n        consoleLogsCount,\n        apiCallsFile: null,\n        apiCallsCount: 0,\n      };\n      await testInfo.attach(ATTACHMENT_NAME, {\n        body: Buffer.from(JSON.stringify(payload)),\n        contentType: ATTACHMENT_CONTENT_TYPE,\n      });\n    } catch {\n      // FR-009: fixture without reporter is harmless\n    }\n    tracker.dispose();\n  },\n\n  request: async (\n    { request }: { request: import('@playwright/test').APIRequestContext },\n    use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n    testInfo: import('@playwright/test').TestInfo,\n  ): Promise<void> => {\n    const apiConfig = readApiConfig(testInfo);\n\n    if (!apiConfig.trackApiCalls) {\n      await use(request);\n      return;\n    }\n\n    const assertionTracker = new AssertionTracker();\n    const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n    apiTracker.intercept();\n    // Route the exported assertion-aware `expect` to THIS test's tracker.\n    setActiveAssertionContext(assertionTracker, apiTracker);\n\n    try {\n      await use(request);\n    } finally {\n      setActiveAssertionContext(null, null);\n    }\n\n    try {\n      const { apiCallsFile, apiCallsCount } = 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/**\n * Track a manually-created page for network requests, console logs, and navigations.\n *\n * Use this when your test creates pages via `browser.newContext()` / `context.newPage()`\n * instead of the built-in `page` fixture. Call it right after creating the page,\n * and call the returned cleanup function after `use()`.\n *\n * @example\n * authenticatedPage: async ({ browser }, use, testInfo) => {\n *   const ctx = await browser.newContext({ storageState: AUTH_FILE });\n *   const page = await ctx.newPage();\n *   const cleanup = await trackPage(page, testInfo);\n *   await use(page);\n *   await cleanup();\n *   await ctx.close();\n * },\n */\nexport async function trackPage(\n  page: import('@playwright/test').Page,\n  testInfo: import('@playwright/test').TestInfo,\n): Promise<() => Promise<void>> {\n  const tracker = new NavigationTracker(page as never);\n  try {\n    await tracker.init();\n  } catch {\n    // Graceful degradation\n  }\n\n  return async () => {\n    try {\n      const { navigations, networkRequestsFile, networkRequestsCount, consoleLogsFile, consoleLogsCount } = await tracker.getFileData();\n      const payload: TestRelicFilePayload = {\n        testRelicData: true,\n        version: PAYLOAD_VERSION as '2.0.0',\n        navigations,\n        apiAssertions: [],\n        networkRequestsFile,\n        networkRequestsCount,\n        consoleLogsFile,\n        consoleLogsCount,\n        apiCallsFile: null,\n        apiCallsCount: 0,\n      };\n      await testInfo.attach(ATTACHMENT_NAME, {\n        body: Buffer.from(JSON.stringify(payload)),\n        contentType: ATTACHMENT_CONTENT_TYPE,\n      });\n    } catch {\n      // Graceful degradation\n    }\n    tracker.dispose();\n  };\n}\n\n/** Pre-extended test instance for backward compatibility. */\nexport const test = base.extend(testRelicFixture);\n"]}