@testrelic/core 2.16.2-next.153 → 2.16.3-next.157

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["// @testrelic/core — barrel export\n// Re-exports only, no implementation (Constitution II)\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n ApiCallsByStatusRange,\n ApiResponseTimeStats,\n CIMetadata,\n CIProvider,\n TimelineEntry,\n TestResult,\n TestStatus,\n TestType,\n FailureDiagnostic,\n NetworkStats,\n ResourceBreakdown,\n ResourceType,\n CapturedNetworkRequest,\n ApiCallRecord,\n CapturedCookie,\n ApiAssertion,\n FailedAssertionSummary,\n AssertionType,\n AssertionLocation,\n ReporterConfig,\n TestArtifacts,\n VisualComparison,\n VisualStatus,\n VisualSource,\n DomChange,\n DomChangeKind,\n DomStyleChange,\n NavigationAnnotation,\n MergeOptions,\n StepTestIdentity,\n ApiCallStepRequest,\n ApiCallStepResponse,\n StepAssertion,\n NavigationStep,\n ApiCallStep,\n TimelineStep,\n ActionStep,\n StepKind,\n LogSource,\n MobilePlatform,\n AppTestMode,\n DeviceInfo,\n AiDefect,\n AiDefectSeverity,\n AiDefectType,\n ActionCategory,\n ConsoleLogEntry,\n ConsoleLogLevel,\n TestRelicDataPayload,\n TestRelicFilePayload,\n CloudConfig,\n AuthState,\n AuthMode,\n GitMetadata,\n RepoResolution,\n QueueEntry,\n UploadStrategy,\n CloudReporterOptions,\n ArtifactFile,\n ArtifactTestEntry,\n ArtifactRunEntry,\n ArtifactRunManifest,\n ReportMode,\n TestIndexEntry,\n TestDetailData,\n StreamingReportSummary,\n StreamingFileStats,\n StreamingValidation,\n StreamingWriteError,\n StreamingReportManifest,\n} from './types.js';\n\nexport {\n PAYLOAD_VERSION,\n PAYLOAD_VERSION_LEGACY,\n ATTACHMENT_NAME,\n ATTACHMENT_CONTENT_TYPE,\n isTestRelicDataPayload,\n isTestRelicFilePayload,\n} from './types.js';\n\nexport type { Logger, LoggerOptions, LogLevel } from './logger.js';\nexport { createLogger } from './logger.js';\n\nexport { ErrorCode, TestRelicError, createError } from './errors.js';\n\nexport {\n isValidConfig,\n isValidNavigationType,\n isValidTestRunReport,\n isValidCloudConfig,\n isValidApiKeyFormat,\n isValidEndpointUrl,\n isValidUploadStrategy,\n isValidQueueEntry,\n} from './validation.js';\n","/**\n * @testrelic/core — Shared type definitions\n *\n * All interfaces for the JSON output schema, reporter configuration,\n * and internal fixture-to-reporter communication.\n *\n * Schema Version: 1.7.0\n */\n\n// ---------------------------------------------------------------------------\n// Navigation Types\n// ---------------------------------------------------------------------------\n\nexport type NavigationType =\n | 'goto'\n | 'navigation'\n | 'back'\n | 'forward'\n | 'refresh'\n | 'spa_route'\n | 'spa_replace'\n | 'hash_change'\n | 'link_click'\n | 'form_submit'\n | 'redirect'\n | 'popstate'\n | 'page_load'\n | 'manual_record'\n | 'fallback'\n | 'dummy';\n\n// ---------------------------------------------------------------------------\n// Output Schema Types (JSON report structure)\n// ---------------------------------------------------------------------------\n\nexport interface TestRunReport {\n readonly schemaVersion: string;\n readonly testRunId: string;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly totalDuration: number;\n readonly summary: Summary;\n readonly ci: CIMetadata | null;\n readonly metadata: Record<string, unknown> | null;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly shardRunIds: string[] | null;\n}\n\n/** Breakdown of API calls by HTTP response status range. */\nexport interface ApiCallsByStatusRange {\n readonly '2xx': number;\n readonly '3xx': number;\n readonly '4xx': number;\n readonly '5xx': number;\n readonly error: number;\n}\n\n/** Statistical distribution of API call response times in milliseconds. */\nexport interface ApiResponseTimeStats {\n readonly avg: number;\n readonly min: number;\n readonly max: number;\n readonly p50: number;\n readonly p95: number;\n readonly p99: number;\n}\n\nexport interface Summary {\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedout: number;\n readonly totalApiCalls: number;\n readonly uniqueApiUrls: number;\n readonly apiCallsByMethod: Record<string, number>;\n readonly apiCallsByStatusRange: ApiCallsByStatusRange;\n readonly apiResponseTime: ApiResponseTimeStats | null;\n readonly totalAssertions: number;\n readonly passedAssertions: number;\n readonly failedAssertions: number;\n readonly totalNavigations: number;\n readonly uniqueNavigationUrls: number;\n readonly totalTimelineSteps: number;\n readonly totalActionSteps: number;\n readonly actionStepsByCategory: Record<string, number>;\n /** Visual baseline comparisons across the run. */\n readonly visualChecks?: number;\n /** Of those, how many differed from their baseline. */\n readonly visualFailures?: number;\n /** Of those, how many had no baseline and wrote one. */\n readonly visualNew?: number;\n /** DOM elements that differed across the run's visual comparisons. */\n readonly visualDomChanges?: number;\n /** Action steps across the run, bucketed by the driver's own category. */\n readonly actionStepsByKind?: Record<string, number>;\n /** Device-log lines captured across the run (mobile). */\n readonly deviceLogLines?: number;\n /** Of those, how many were error or fatal. */\n readonly deviceLogErrors?: number;\n /** Of those, how many came from a crash log. */\n readonly deviceLogCrashes?: number;\n /** Screenshots captured across the run. */\n readonly screenshotCount?: number;\n /** Screen recordings captured across the run. */\n readonly videoCount?: number;\n /** AI-detected defects across the run, by severity. */\n readonly aiDefectsBySeverity?: Record<string, number>;\n /** Distinct devices the run touched. */\n readonly devices?: readonly DeviceInfo[];\n}\n\nexport interface CIMetadata {\n readonly provider: CIProvider;\n readonly buildId: string | null;\n readonly commitSha: string | null;\n readonly branch: string | null;\n readonly runUrl: string | null;\n}\n\nexport type CIProvider =\n | 'github-actions'\n | 'gitlab-ci'\n | 'jenkins'\n | 'circleci'\n | 'bitbucket-pipelines'\n | 'unknown';\n\nexport interface TimelineEntry {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly visitedAt: string;\n readonly duration: number;\n readonly specFile: string;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly tests: TestResult[];\n}\n\nexport interface TestResult {\n readonly title: string;\n readonly status: TestStatus;\n readonly duration: number;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly retryCount: number;\n readonly tags: string[];\n readonly failure: FailureDiagnostic | null;\n readonly testId: string;\n readonly filePath: string;\n readonly suiteName: string;\n readonly testType: TestType;\n readonly isFlaky: boolean;\n readonly retryStatus: string | null;\n readonly expectedStatus: TestStatus;\n readonly actualStatus: TestStatus;\n readonly artifacts: TestArtifacts | null;\n readonly networkRequests: CapturedNetworkRequest[] | null;\n readonly apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n readonly actions: readonly ActionStep[] | null;\n readonly consoleLogs: readonly ConsoleLogEntry[] | null;\n /** Visual baseline comparisons recorded during this test, if any. */\n readonly visual?: readonly VisualComparison[] | null;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\nexport type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';\n\nexport interface CapturedNetworkRequest {\n readonly url: string;\n readonly method: string;\n readonly resourceType: ResourceType;\n readonly statusCode: number;\n readonly responseTimeMs: number;\n readonly startedAt: string;\n readonly requestHeaders: Record<string, string> | null;\n readonly requestBody: string | null;\n readonly responseBody: string | null;\n readonly responseHeaders: Record<string, string> | null;\n readonly contentType: string | null;\n readonly responseSize: number;\n readonly requestBodyTruncated: boolean;\n readonly responseBodyTruncated: boolean;\n readonly isBinary: boolean;\n readonly error: string | null;\n}\n\nexport interface TestArtifacts {\n readonly screenshot?: string;\n /**\n * Every screenshot captured for this test, report-relative.\n *\n * `screenshot` is the first one and stays for compatibility. Mobile\n * collectors have always gathered an array — one per step, or one per\n * failure — and only index 0 survived: the artifact copier wrote a single\n * `screenshot.png` and dropped the rest before the report could ask.\n */\n readonly screenshots?: readonly string[];\n readonly video?: string;\n /**\n * Playwright's trace archive — the DOM snapshots, network log and screencast\n * frames a failure is actually debugged from.\n *\n * Its absence here went all the way down the stack: with no field to fill,\n * the collector had nothing to look for, so a project configured with\n * `trace: 'on'` wrote `trace.zip` to disk and shipped only the picture and\n * the video. The gap bit hardest exactly where it mattered most — a failure\n * caught in a hook leaves no screenshot worth having, so those runs reached\n * the cloud with a video and nothing anyone could step through.\n */\n readonly trace?: string;\n readonly screenshotKey?: string;\n readonly videoKey?: string;\n readonly traceKey?: string;\n}\n\n/**\n * Outcome of a single visual baseline comparison.\n *\n * `new` is a snapshot Playwright had no baseline for and therefore wrote one\n * for — it is not a pass, because nothing was actually compared. `updated`\n * means the run was invoked with `--update-snapshots` and the baseline was\n * rewritten, which likewise compares nothing.\n */\nexport type VisualStatus = 'passed' | 'failed' | 'new' | 'updated';\n\n/** Which assertion produced a comparison. */\nexport type VisualSource = 'toHaveScreenshot' | 'toMatchSnapshot' | 'toMatchVisualBaseline';\n\n/**\n * One visual baseline comparison within a test.\n *\n * A test may hold many of these — `TestArtifacts` deliberately is not the home\n * for them, because it is single-slot per type (one screenshot, one video, one\n * trace) while a single test can assert against any number of named snapshots.\n *\n * Every image field is a report-relative POSIX path, matching `TestArtifacts`.\n * A passing comparison carries no `actual` or `diff`: Playwright attaches\n * nothing when the images match, so there is nothing to copy.\n */\nexport interface VisualComparison {\n /** Snapshot identity within the test, e.g. `home` or `checkout/cart`. */\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n /** The committed baseline image. */\n readonly baseline?: string;\n /**\n * Where the committed baseline lives, relative to the Playwright config\n * file's directory (where `test-results` sits), POSIX separators — the\n * file a reviewer overwrites to accept this run.\n *\n * Deliberately not the artifact copy in `baseline`: that path is relative to\n * the report and points at a per-run duplicate, so a tool that \"accepted\" a\n * run by writing there would leave the baseline Playwright actually reads\n * untouched and the failure would come straight back.\n */\n readonly baselinePath?: string;\n /** What this run rendered. Absent when the comparison passed. */\n readonly actual?: string;\n /** Playwright's rendered diff. Present only on failure. */\n readonly diff?: string;\n /** The previous attempt's render, on a retried failure. */\n readonly previous?: string;\n /** Baseline dimensions, read from the PNG header. */\n readonly width?: number;\n readonly height?: number;\n /** Actual dimensions — set only when they differ from the baseline. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** Differing pixel count, as reported by Playwright's comparator. */\n readonly diffPixels?: number;\n /** `diffPixels / (width * height)`, computed at full precision. */\n readonly diffPixelRatio?: number;\n /** Cloud storage keys, populated after artifact upload. */\n readonly baselineKey?: string;\n readonly actualKey?: string;\n readonly diffKey?: string;\n /**\n * Which elements changed, ranked by the area they affect.\n *\n * Present only when a DOM baseline existed to compare against. Capped — see\n * `domChangeCount` for the number found before the cap.\n */\n readonly domChanges?: readonly DomChange[];\n /** Total changes found, which may exceed `domChanges.length`. */\n readonly domChangeCount?: number;\n /**\n * Of those, how many actually changed rather than merely shifting position.\n *\n * The gap between this and `domChangeCount` is the layout knock-on: one card\n * gaining a margin moves everything below it, and a report that presents all\n * of those as equals hides the single edit that caused them.\n */\n readonly domRootCauses?: number;\n /** True when the DOM capture itself hit a node or byte cap. */\n readonly domTruncated?: boolean;\n /** Report-relative paths to the two captured DOM snapshots. */\n readonly baselineDom?: string;\n readonly actualDom?: string;\n /**\n * The document-space region the screenshot covered: `[x, y, width, height]`.\n *\n * Element rectangles are in document coordinates, so the report needs this to\n * place a box over the image — for a locator screenshot the image covers only\n * that element's box, not the viewport.\n */\n readonly domCaptureBox?: readonly [number, number, number, number];\n /** Cloud storage keys for the DOM snapshots. */\n readonly baselineDomKey?: string;\n readonly actualDomKey?: string;\n}\n\n/** How an element differs between the baseline render and this one. */\nexport type DomChangeKind =\n | 'added'\n | 'removed'\n | 'moved'\n | 'resized'\n | 'restyled'\n | 'text'\n /** Its id or class list changed — often the real story behind a restyle. */\n | 'attrs';\n\n/** One computed style property that differs. */\nexport interface DomStyleChange {\n readonly prop: string;\n readonly from: string;\n readonly to: string;\n}\n\n/**\n * One element that differs between two renders.\n *\n * Deliberately shaped like the platform's existing `RegionEvidence` — rank,\n * bbox, area share, a kind and a human label — so that a DOM overlay becomes\n * another caller of the dashboard's existing box-overlay component rather than\n * a second renderer built alongside it.\n */\nexport interface DomChange {\n /** 1-based, ordered by affected area descending. Drives the overlay colour. */\n readonly rank: number;\n /** Structural path used to match the element across the two snapshots. */\n readonly path: string;\n /** What a human reads, e.g. `div.card.accent`. */\n readonly label: string;\n readonly kinds: readonly DomChangeKind[];\n /** Document-space `[x, y, width, height]` in this render. */\n readonly rect?: readonly [number, number, number, number];\n /** The same, in the baseline. Absent for an added element. */\n readonly baselineRect?: readonly [number, number, number, number];\n /** Share of the capture box this element occupies, 0–1. */\n readonly areaPct: number;\n readonly styles?: readonly DomStyleChange[];\n readonly text?: { readonly from: string; readonly to: string };\n /** Identity attributes that changed, e.g. `class: \"card\" → \"banner\"`. */\n readonly attrs?: readonly DomStyleChange[];\n /**\n * True when this element only moved or resized, in a comparison where\n * something else actually changed.\n *\n * Shifting one card down by 24px shifts everything below it too, so a naive\n * list reports eleven changes for one edit. This flags the ten that merely\n * got pushed around, so a report can lead with the one that did something.\n *\n * It is a statement about this element — that nothing about it changed except\n * its position — and deliberately not a claim to have proven what moved it.\n */\n readonly consequence?: boolean;\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';\n\nexport type TestType = 'e2e' | 'api' | 'unit' | 'mobile' | 'unknown';\n\n// ---------------------------------------------------------------------------\n// Mobile Device Types\n// ---------------------------------------------------------------------------\n\n/** Platform a mobile test drove. */\nexport type MobilePlatform = 'android' | 'ios' | 'web' | 'unknown';\n\n/** How the app under test was driven. */\nexport type AppTestMode = 'native' | 'hybrid' | 'mobile-web';\n\n/**\n * The device a mobile test ran on.\n *\n * Appium detects all of this from the W3C capabilities and Maestro reads it\n * from the CLI flags and driver log. Both used to collapse it to two words in\n * `tags` before the report saw it; this carries it through intact.\n */\nexport interface DeviceInfo {\n readonly platform: MobilePlatform;\n /** OS version, e.g. \"14\" or \"17.5\". */\n readonly platformVersion: string | null;\n /** Human-facing device name, e.g. \"Pixel 4a\" or \"iPhone 15\". */\n readonly deviceName: string | null;\n /** Driver, e.g. \"UiAutomator2\", \"XCUITest\", \"maestro\". */\n readonly automationName: string | null;\n /** App package / bundle id / apk path under test. */\n readonly appId: string | null;\n /** Native, hybrid or mobile-web. Null when the runner cannot tell. */\n readonly appTestMode: AppTestMode | null;\n /** Device serial or UDID, e.g. \"emulator-5554\". */\n readonly udid: string | null;\n}\n\n/** Severity of an AI-detected defect. */\nexport type AiDefectSeverity = 'critical' | 'warning' | 'info';\n\n/** Category of an AI-detected defect. */\nexport type AiDefectType = 'ui' | 'spelling' | 'i18n' | 'layout' | 'accessibility';\n\n/** A defect reported by an AI analysis pass over a captured screen. */\nexport interface AiDefect {\n readonly type: AiDefectType;\n readonly severity: AiDefectSeverity;\n readonly description: string;\n /** Where in the app it was seen, when the analyser says. */\n readonly location: string | null;\n /** Report-relative path to the screenshot it was found on. */\n readonly screenshot: string | null;\n}\n\nexport interface FailureDiagnostic {\n readonly message: string;\n readonly line: number | null;\n readonly code: string | null;\n readonly stack: string | null;\n}\n\nexport interface NetworkStats {\n readonly totalRequests: number;\n readonly failedRequests: number;\n readonly failedRequestUrls: string[];\n readonly totalBytes: number;\n readonly byType: ResourceBreakdown;\n}\n\nexport interface ResourceBreakdown {\n readonly xhr: number;\n readonly document: number;\n readonly script: number;\n readonly stylesheet: number;\n readonly image: number;\n readonly font: number;\n readonly other: number;\n}\n\n// ---------------------------------------------------------------------------\n// API Call Record (captured from Playwright APIRequestContext proxy)\n// ---------------------------------------------------------------------------\n\n/**\n * A cookie surfaced for the API-workspace Cookies tab. Values are MASKED by\n * default (the SDK never uploads raw session tokens to the cloud); the name and\n * any non-secret attributes (Path/Domain/Secure/HttpOnly/SameSite) are kept so\n * the inspector can show which cookies were sent/set, Postman-style.\n */\nexport interface CapturedCookie {\n readonly name: string;\n /** Masked value (e.g. \"••••\"). Never the raw cookie value. */\n readonly value: string;\n /** Response-cookie attributes joined for display (Set-Cookie only). */\n readonly attributes?: string;\n}\n\nexport interface ApiCallRecord {\n /** Unique call identifier within the test (e.g., \"api-call-0\") */\n readonly id: string;\n /** ISO 8601 timestamp when the call was initiated */\n readonly timestamp: string;\n /** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */\n readonly method: string;\n /** Full resolved URL (after baseURL resolution by Playwright) */\n readonly url: string;\n /** All request headers sent, or null if unavailable */\n readonly requestHeaders: Record<string, string> | null;\n /** Request payload as string (JSON-serialized for objects). Null for bodiless methods */\n readonly requestBody: string | null;\n /** HTTP response status code, or null if request errored before response */\n readonly responseStatusCode: number | null;\n /** HTTP response status text, or null if request errored before response */\n readonly responseStatusText: string | null;\n /** All response headers received, or null if request errored before response */\n readonly responseHeaders: Record<string, string> | null;\n /** Response body: string for text/JSON, base64 for binary. Null on error */\n readonly responseBody: string | null;\n /** Request cookies parsed from the `Cookie` header (values masked). Optional/null when none. */\n readonly requestCookies?: readonly CapturedCookie[] | null;\n /** Response cookies parsed from `Set-Cookie` headers (values masked). Optional/null when none. */\n readonly responseCookies?: readonly CapturedCookie[] | null;\n /** Duration in milliseconds from request initiation to response received */\n readonly responseTimeMs: number;\n /** Whether the response body is stored as a base64-encoded binary string */\n readonly isBinary: boolean;\n /** Error message if the call failed (network error, timeout), null on success */\n readonly error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// API Assertion Types (captured from assertion tracking)\n// ---------------------------------------------------------------------------\n\n/** Categorization of the assertion being made. */\nexport type AssertionType =\n | 'status'\n | 'statusOk'\n | 'header'\n | 'bodyField'\n | 'bodyMatch'\n | 'bodyContains'\n | 'custom';\n\n/** Source location where an assertion was made in the test file. */\nexport interface AssertionLocation {\n /** Absolute or relative file path to the test file */\n readonly file: string;\n /** Line number (1-based) */\n readonly line: number;\n /** Column number (1-based), if available */\n readonly column?: number;\n}\n\n/** A failed assertion reduced to what a summary row needs. */\nexport interface FailedAssertionSummary {\n readonly type: AssertionType;\n readonly expected: string;\n readonly actual: string;\n readonly expression?: string;\n}\n\n/** A single captured assertion on API response data. */\nexport interface ApiAssertion {\n /** Links to ApiCallRecord.id from the API proxy */\n readonly callId: string;\n /** Category of assertion */\n readonly type: AssertionType;\n /** What the test expected */\n readonly expected: unknown;\n /** What was actually received */\n readonly actual: unknown;\n /** Whether the assertion passed or failed */\n readonly status: 'passed' | 'failed';\n /** Source location of the assertion in the test file */\n readonly location: AssertionLocation;\n /** String representation of the assertion expression (best-effort) */\n readonly expression?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Unified Timeline Types (navigation + API call steps)\n// ---------------------------------------------------------------------------\n\n/** Test identity block attached to every timeline step. */\nexport interface StepTestIdentity {\n readonly title: string;\n readonly fullTitle: readonly string[];\n readonly status: TestStatus;\n readonly duration: number;\n readonly retries: number;\n readonly retry: number;\n readonly tags: readonly string[];\n readonly failure: FailureDiagnostic | null;\n}\n\n/** Request details within an API call step. */\nexport interface ApiCallStepRequest {\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** Response details within an API call step. */\nexport interface ApiCallStepResponse {\n readonly statusCode: number;\n readonly statusText: string;\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** An assertion linked to an API call step (callId omitted — implicit from parent). */\nexport interface StepAssertion {\n readonly type: AssertionType;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly status: 'passed' | 'failed';\n readonly location: AssertionLocation;\n readonly expression?: string;\n}\n\n/** A browser navigation event in the unified timeline. */\nexport interface NavigationStep {\n readonly index: number;\n readonly type: 'navigation';\n readonly url: string;\n readonly timestamp: string;\n readonly durationOnUrl: number;\n readonly navigationType: NavigationType;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** An API request event in the unified timeline. */\nexport interface ApiCallStep {\n readonly index: number;\n readonly type: 'api_call';\n readonly callId: string;\n readonly method: string;\n readonly url: string;\n readonly timestamp: string;\n readonly responseTime: number | null;\n readonly request: ApiCallStepRequest;\n readonly response: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions: readonly StepAssertion[];\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** Discriminated union of all timeline step types. */\nexport type TimelineStep = NavigationStep | ApiCallStep;\n\n// ---------------------------------------------------------------------------\n// Action Step Types (Playwright test step capture)\n// ---------------------------------------------------------------------------\n\n/**\n * Categorized type of a captured test action.\n *\n * `navigation` covers app-level navigation events that aren't UI actions —\n * Maestro `launchApp` / `openLink` / `back`, Appium activity transitions, and\n * Playwright top-level page navigations. The platform's timeline UI and Ask\n * AI tools (`query_test_steps`'s `categoryFilter`) treat navigation as a\n * distinct bucket from generic `custom_step`.\n */\nexport type ActionCategory = 'ui_action' | 'assertion' | 'navigation' | 'custom_step';\n\n/**\n * The driver's own step category, preserved alongside `ActionCategory`.\n *\n * `ActionCategory` is the four-bucket vocabulary the platform's timeline and\n * Ask AI tools filter on, and it stays closed. Mobile drivers distinguish far\n * more than four kinds — Appium reports eleven, Maestro ten — and flattening\n * them made a swipe, an app launch and a screenshot render identically. This\n * keeps the finer label for the report to badge.\n */\nexport type StepKind =\n | 'find'\n | 'interaction'\n | 'gesture'\n | 'navigation'\n | 'lifecycle'\n | 'context'\n | 'device'\n | 'keyboard'\n | 'alert'\n | 'screenshot'\n | 'assertion'\n | 'media'\n | 'script'\n | 'flow_control'\n | 'ai'\n | 'other';\n\n/** A single captured test action step. */\nexport interface ActionStep {\n readonly title: string;\n readonly category: ActionCategory;\n /** The driver's own finer-grained category, when it reported one. */\n readonly kind?: StepKind | null;\n /** Element selector this step acted on, when the driver reported one. */\n readonly selector?: string | null;\n readonly timestamp: string;\n readonly duration: number;\n readonly videoOffset: number | null;\n readonly status: 'passed' | 'failed';\n readonly error: string | null;\n readonly children: readonly ActionStep[];\n}\n\n// ---------------------------------------------------------------------------\n// Console Log Types (browser console + terminal stdout/stderr)\n// ---------------------------------------------------------------------------\n\n/** Level of a captured console/terminal log message. */\nexport type ConsoleLogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' | 'stdout' | 'stderr';\n\n/** A single captured console or terminal log entry. */\n/**\n * Where a captured log line came from.\n *\n * Browser consoles only ever produce `console`. Mobile runs mix several\n * streams, and telling a crash log from ordinary logcat chatter is the whole\n * point of showing them — so the stream survives to the row.\n */\nexport type LogSource =\n | 'console'\n | 'logcat'\n | 'syslog'\n | 'crashlog'\n | 'browser'\n | 'safariConsole'\n | 'performance'\n | 'server'\n | 'bidi'\n | 'driver';\n\nexport interface ConsoleLogEntry {\n /** Log level or stream type */\n readonly level: ConsoleLogLevel;\n /** Text content of the log message */\n readonly text: string;\n /** ISO 8601 timestamp when the log was captured */\n readonly timestamp: string;\n /** Source file location (browser console only, best-effort) */\n readonly location: string | null;\n /** Which stream produced the line. Defaults to `console` when absent. */\n readonly source?: LogSource | null;\n /** Emitting tag, e.g. an Android logcat tag like \"ActivityManager\". */\n readonly tag?: string | null;\n /** Emitting process id, when the stream reports one. */\n readonly pid?: number | null;\n /** Stack trace attached to the line (crash logs). */\n readonly stackTrace?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration Types (reporter tuple options)\n// ---------------------------------------------------------------------------\n\n/** Report generation mode. */\nexport type ReportMode = 'streaming' | 'embedded' | 'auto';\n\nexport interface ReporterConfig {\n readonly outputPath?: string;\n readonly includeStackTrace?: boolean;\n readonly includeCodeSnippets?: boolean;\n readonly codeContextLines?: number;\n readonly includeNetworkStats?: boolean;\n /** Master on/off for Test Navigation capture. Default: true. When false the\n * reporter drops navigation data before upload and logs a one-time notice. */\n readonly captureNavigation?: boolean;\n readonly navigationTypes?: NavigationType[] | null;\n readonly redactPatterns?: (string | RegExp)[];\n readonly testRunId?: string | null;\n readonly metadata?: Record<string, unknown> | null;\n readonly openReport?: boolean;\n readonly htmlReportPath?: string;\n readonly includeArtifacts?: boolean;\n /** When false, disables API call interception in the unified fixture. Default: true */\n readonly trackApiCalls?: boolean;\n /** When true, suppresses console summary output. Default: false */\n readonly quiet?: boolean;\n /** When true, captures Playwright test steps (actions). Default: true */\n readonly includeActionSteps?: boolean;\n /** Capture request headers for API calls. Default: true */\n readonly captureRequestHeaders?: boolean;\n /** Capture response headers for API calls. Default: true */\n readonly captureResponseHeaders?: boolean;\n /** Capture request body for API calls. Default: true */\n readonly captureRequestBody?: boolean;\n /** Capture response body for API calls. Default: true */\n readonly captureResponseBody?: boolean;\n /** Capture API assertions (both pass and fail). Default: true */\n readonly captureAssertions?: boolean;\n /**\n * Header names whose values are replaced with \"[REDACTED]\".\n * Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']\n */\n readonly redactHeaders?: string[];\n /**\n * Body field names whose values are replaced with \"[REDACTED]\" at any depth.\n * Default: ['password', 'secret', 'token', 'apiKey', 'api_key']\n */\n readonly redactBodyFields?: string[];\n /**\n * Only track API calls matching these URL patterns.\n * Default: undefined (track all)\n */\n readonly apiIncludeUrls?: (string | RegExp)[];\n /**\n * Exclude API calls matching these URL patterns. Takes precedence over include.\n * Default: undefined (exclude none)\n */\n readonly apiExcludeUrls?: (string | RegExp)[];\n /** Capture browser console logs (E2E) and terminal output (API). Default: true */\n readonly captureConsoleLogs?: boolean;\n /** Cloud integration configuration. Default: undefined (local mode) */\n readonly cloud?: CloudReporterOptions;\n /**\n * Report generation mode.\n * - `'embedded'`: All data embedded in a single HTML file (current behavior)\n * - `'streaming'`: Data streamed to disk per-test, served via local server\n * - `'auto'`: Use embedded for < streamingThreshold tests, streaming otherwise\n * Default: `'auto'`\n */\n readonly reportMode?: ReportMode;\n /**\n * Test count threshold for auto mode. When the suite has >= this many tests,\n * streaming mode is used. Default: 500\n */\n readonly streamingThreshold?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types (fixture → reporter communication)\n// ---------------------------------------------------------------------------\n\nexport interface NavigationAnnotation {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly timestamp: string;\n readonly domContentLoadedAt?: string;\n readonly networkIdleAt?: string;\n readonly networkStats?: NetworkStats;\n}\n\n// ---------------------------------------------------------------------------\n// Worker-Safe Data Payload (worker → reporter communication)\n// ---------------------------------------------------------------------------\n\n/** Consolidated data payload sent from a worker fixture to the reporter. */\nexport interface TestRelicDataPayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version (semver). */\n readonly version: string;\n /** Navigation events collected during the test. */\n readonly navigations: readonly NavigationAnnotation[];\n /** Captured network requests during the test. */\n readonly networkRequests: readonly CapturedNetworkRequest[];\n /** API calls made via APIRequestContext proxy. */\n readonly apiCalls: readonly ApiCallRecord[];\n /** Assertions captured on API responses. */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Console/terminal logs captured during the test. Optional for backward compat. */\n readonly consoleLogs?: readonly ConsoleLogEntry[];\n}\n\n/** Current payload format version. */\nexport const PAYLOAD_VERSION = '2.0.0';\n\n/** Legacy payload version (pre-streaming). */\nexport const PAYLOAD_VERSION_LEGACY = '1.0.0';\n\n/** Attachment name used for the consolidated payload. */\nexport const ATTACHMENT_NAME = 'testrelic-data';\n\n/** Attachment content type. */\nexport const ATTACHMENT_CONTENT_TYPE = 'application/json';\n\n/**\n * File-based data payload (v2.0.0).\n *\n * Heavy data (network requests, console logs, API calls) is written to\n * JSONL files on disk and referenced by path. Only lightweight data\n * (navigations, assertions) is kept inline.\n */\nexport interface TestRelicFilePayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version — `\"2.0.0\"` for file-based payloads. */\n readonly version: '2.0.0';\n /** Navigation events (small, kept inline). */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertions (small, kept inline). */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Absolute path to JSONL file containing CapturedNetworkRequest items. */\n readonly networkRequestsFile: string | null;\n /** Number of network request items in the file. */\n readonly networkRequestsCount: number;\n /** Absolute path to JSONL file containing ConsoleLogEntry items. */\n readonly consoleLogsFile: string | null;\n /** Number of console log items in the file. */\n readonly consoleLogsCount: number;\n /** Absolute path to JSONL file containing ApiCallRecord items. */\n readonly apiCallsFile: string | null;\n /** Number of API call items in the file. */\n readonly apiCallsCount: number;\n /**\n * Response-status mix of the calls in the file.\n *\n * Tallied as each record is appended. The file itself is never read back\n * into memory in streaming mode, so an aggregate computed at write time is\n * the only way the grid can show a row's status chips.\n */\n readonly apiStatusMix?: ApiCallsByStatusRange | null;\n}\n\n/** Type guard: validates that a parsed object is a v2 file-based payload. */\nexport function isTestRelicFilePayload(obj: unknown): obj is TestRelicFilePayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n record.version === '2.0.0' &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n/** Type guard: validates that a parsed object is a v1 inline data payload. */\nexport function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n typeof record.version === 'string' &&\n record.version.length > 0 &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.networkRequests) &&\n Array.isArray(record.apiCalls) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Integration Types\n// ---------------------------------------------------------------------------\n\n/** Upload strategy for cloud timeline data. */\nexport type UploadStrategy = 'batch' | 'realtime' | 'both';\n\n/** Current cloud/local operating mode. */\nexport type AuthMode = 'cloud' | 'local' | 'pending';\n\n/** Resolved cloud configuration, immutable after initialization. */\nexport interface CloudConfig {\n readonly apiKey: string | null;\n readonly endpoint: string;\n readonly uploadStrategy: UploadStrategy;\n /** Per-request ceiling. ⚠ It governed only the auth handshake until 2.14.0 —\n * the upload calls used bare `fetch` and inherited undici's 5-minute\n * defaults, so a tester who set this had bounded the token exchange and\n * nothing else. It now bounds the upload requests too. */\n readonly timeout: number;\n /** Total time the whole upload phase may take before it gives up, writes the\n * queue and lets the process exit. The phase runs inside Playwright's\n * `onEnd`, so this is the difference between a late run and a run that never\n * finishes. Default 120s.\n *\n * ⛔ OPTIONAL on purpose. `CloudConfig` is built by every analytics package\n * (appium, maestro, playwright), and making a new field required breaks all\n * of them at once for a knob only one upload path reads — CI caught exactly\n * that. Callers resolve it as `?? DEFAULT_UPLOAD_BUDGET_MS`. */\n readonly uploadBudgetMs?: number;\n readonly projectName: string | null;\n readonly queueMaxAge: number;\n readonly queueDirectory: string;\n readonly uploadArtifacts: boolean;\n readonly artifactMaxSizeMb: number;\n}\n\n/** Mutable auth state tracked by the cloud client. */\nexport interface AuthState {\n mode: AuthMode;\n accessToken: string | null;\n refreshToken: string | null;\n expiresAt: number | null;\n orgId: string | null;\n orgName: string | null;\n userId: string | null;\n userName: string | null;\n}\n\n/** Auto-collected git information. All fields nullable. */\nexport interface GitMetadata {\n readonly branch: string | null;\n readonly commitSha: string | null;\n readonly commitMessage: string | null;\n readonly commitAuthor: string | null;\n readonly remoteUrl: string | null;\n}\n\n/** Cached repo identity. */\nexport interface RepoResolution {\n readonly repoId: string;\n readonly gitId: string;\n readonly displayName: string;\n readonly resolvedAt: number;\n readonly apiKeyHash: string;\n}\n\n/** A single queued upload payload. */\nexport interface QueueEntry {\n readonly version: string;\n readonly queuedAt: string;\n readonly reason: string;\n readonly retryCount: number;\n readonly targetEndpoint: string;\n readonly method: string;\n readonly payload: Record<string, unknown>;\n readonly headers: Record<string, string>;\n}\n\n/** Inline cloud configuration accepted via reporter options. */\nexport interface CloudReporterOptions {\n readonly apiKey?: string;\n readonly endpoint?: string;\n readonly upload?: UploadStrategy;\n readonly timeout?: number;\n /** Total budget for the upload phase (ms). See CloudConfig.uploadBudgetMs. */\n readonly uploadBudgetMs?: number;\n readonly projectName?: string;\n readonly queueMaxAge?: string;\n readonly queueDirectory?: string;\n readonly uploadArtifacts?: boolean;\n readonly artifactMaxSizeMb?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Merge CLI Types\n// ---------------------------------------------------------------------------\n\nexport interface MergeOptions {\n readonly output: string;\n readonly testRunId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact Management Types\n// ---------------------------------------------------------------------------\n\n/** A single artifact file within a test's artifact folder. */\nexport interface ArtifactFile {\n /** File name (e.g., `screenshot.png`, `video.webm`, `trace.zip`) */\n readonly name: string;\n /** Artifact type for rendering */\n readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';\n /** Path relative to the report output directory */\n readonly relativePath: string;\n /** File size in bytes */\n readonly sizeBytes: number;\n}\n\n/** A single test's artifacts within a run folder. */\nexport interface ArtifactTestEntry {\n /** Sanitized test folder name */\n readonly testName: string;\n /** List of artifact files for this test */\n readonly files: readonly ArtifactFile[];\n}\n\n/** A single test run's artifact folder in the manifest. */\nexport interface ArtifactRunEntry {\n /** Timestamp-based folder name (e.g., `2026-03-02T14-30-00`) */\n readonly folderName: string;\n /** ISO 8601 timestamp parsed from folder name */\n readonly timestamp: string;\n /** Sum of all file sizes in this run folder */\n readonly totalSizeBytes: number;\n /** Number of test artifact subdirectories */\n readonly testCount: number;\n /** Per-test artifact details */\n readonly tests: readonly ArtifactTestEntry[];\n /** Whether this folder belongs to the current report's run */\n readonly isCurrentRun: boolean;\n}\n\n/** Top-level manifest structure written as `artifact-manifest.json`. */\nexport interface ArtifactRunManifest {\n /** Manifest schema version (starts at `\"1.0\"`) */\n readonly schemaVersion: string;\n /** ISO 8601 timestamp of manifest generation */\n readonly generatedAt: string;\n /** Relative path to artifacts directory from report (e.g., `\"artifacts\"`) */\n readonly artifactBaseDir: string;\n /** Sum of all run folder sizes */\n readonly totalSizeBytes: number;\n /** All discovered run folders, sorted newest first */\n readonly runs: readonly ArtifactRunEntry[];\n /** Port of the artifact management server, or null if not running */\n readonly serverPort: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Streaming Report Types (server-backed reporter)\n// ---------------------------------------------------------------------------\n\n/** Compact index entry for a single test — kept in memory during run. */\nexport interface TestIndexEntry {\n /** Deterministic hash ID (first 12 chars of SHA-256) */\n readonly id: string;\n /** Test title (leaf name) */\n readonly title: string;\n /** Full title path (describe blocks + test name) */\n readonly titlePath: readonly string[];\n /** Relative path to test file */\n readonly filePath: string;\n /** Test outcome */\n readonly status: TestStatus;\n /** Test duration in milliseconds */\n readonly duration: number;\n /** Playwright project name */\n readonly project: string;\n /** Retry attempt number (0 = first run) */\n readonly retryIndex: number;\n /** Test tags */\n readonly tags: readonly string[];\n /** What kind of test this is — badges the row, and picks the row's evidence. */\n readonly testType?: TestType;\n /** Whether this test has network request data */\n readonly hasNetworkData: boolean;\n /** Whether this test has console log data */\n readonly hasConsoleData: boolean;\n /** Whether this test has screenshot/video artifacts */\n readonly hasArtifacts: boolean;\n /** Whether this test has action step data */\n readonly hasActionSteps: boolean;\n /** Number of visual baseline comparisons recorded */\n readonly visualCount?: number;\n /** Of those, how many differed from their baseline */\n readonly visualFailedCount?: number;\n /** DOM elements that differed across this test's comparisons */\n readonly domChangeCount?: number;\n /**\n * Report-relative path to a diff image for this test, when one exists.\n *\n * Carried on the index so the run overview can show the failure itself\n * rather than only describing it — the grid must never fetch per-test\n * detail files to render.\n */\n readonly visualDiffPath?: string;\n /** First line of error message (failed/flaky tests only) */\n readonly errorMessage: string | null;\n /** Number of network requests captured */\n readonly networkCount: number;\n /** Number of console log entries captured */\n readonly consoleCount: number;\n /** Number of action steps captured */\n readonly actionCount: number;\n /** API calls captured for this test. */\n readonly apiCallCount?: number;\n /** Response-status mix of those calls, for the row's status chips. */\n readonly apiStatusMix?: ApiCallsByStatusRange;\n /** API assertions that passed during this test. */\n readonly assertionsPassed?: number;\n /** API assertions that failed during this test. */\n readonly assertionsFailed?: number;\n /**\n * The first failed assertion, flattened to strings.\n *\n * The run overview leads with \"expected 101, received undefined\" and must\n * do so without opening the test's detail file. The first line of an API\n * failure message is the matcher wrapper, which says nothing.\n */\n readonly firstFailedAssertion?: FailedAssertionSummary;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** Action steps for this test, bucketed by the driver's own category. */\n readonly stepKinds?: Record<string, number>;\n /** Device-log errors and fatals recorded during this test. */\n readonly deviceLogErrors?: number;\n /** Whether a crash-log line was recorded during this test. */\n readonly hasCrashLog?: boolean;\n /** Whether a screen recording exists for this test. */\n readonly hasVideo?: boolean;\n /** AI-detected defects attributed to this test. */\n readonly aiDefectCount?: number;\n /** Whether this is a retry attempt (not the final attempt) */\n readonly isRetry: boolean;\n /** Stable key shared by every attempt of the same test (same across retries, unlike `id`) */\n readonly retryGroupKey: string;\n}\n\n/**\n * Full detail data for a single test, stored on disk.\n *\n * In v3 (stream-to-disk), heavy data (network requests, console logs,\n * API calls) is stored in separate JSONL files alongside meta.json.\n * The `has*` and `*Count` fields indicate availability without loading.\n */\nexport interface TestDetailData {\n /** Same as TestIndexEntry.id */\n readonly id: string;\n /** Page navigation events (inline — typically small) */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertion results (inline — typically small) */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Nested action step tree (inline — typically small) */\n readonly actions: readonly ActionStep[];\n /** References to screenshot/video files */\n readonly artifacts: TestArtifacts | null;\n /** Visual baseline comparisons (inline — a handful per test at most) */\n readonly visual?: readonly VisualComparison[] | null;\n /** Error details for failed/flaky tests */\n readonly failureDiagnostic: FailureDiagnostic | null;\n /** Whether network.jsonl exists for this test */\n readonly hasNetworkFile: boolean;\n /** Number of items in network.jsonl */\n readonly networkRequestsCount: number;\n /** Whether console.jsonl exists for this test */\n readonly hasConsoleFile: boolean;\n /** Number of items in console.jsonl */\n readonly consoleLogsCount: number;\n /** Whether api-calls.jsonl exists for this test */\n readonly hasApiCallsFile: boolean;\n /** Number of items in api-calls.jsonl */\n readonly apiCallsCount: number;\n /** Test start time (ISO 8601) — used for video sync */\n readonly startedAt?: string;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\n/** Streaming report summary — running counters written to summary.json. */\nexport interface StreamingReportSummary {\n /** Unique run identifier */\n readonly testRunId: string;\n /** ISO 8601 start timestamp */\n readonly startedAt: string;\n /** ISO 8601 completion timestamp */\n readonly completedAt: string;\n /** Wall-clock duration in ms */\n readonly totalDuration: number;\n /** Total number of test results */\n readonly total: number;\n /** Tests that passed */\n readonly passed: number;\n /** Tests that failed */\n readonly failed: number;\n /** Tests that passed on retry */\n readonly flaky: number;\n /** Tests that were skipped */\n readonly skipped: number;\n /** Tests that timed out */\n readonly timedOut: number;\n /** Tests that were interrupted */\n readonly interrupted: number;\n /** Total API calls across all tests */\n readonly totalApiCalls: number;\n /** Total assertions across all tests */\n readonly totalAssertions: number;\n /** Total page navigations */\n readonly totalNavigations: number;\n /** Total network requests captured */\n readonly totalNetworkRequests: number;\n /** Total console log entries captured */\n readonly totalConsoleLogs: number;\n /** Total action steps captured */\n readonly totalActionSteps: number;\n /** User-provided metadata */\n readonly metadata: Record<string, unknown> | null;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Per-file aggregation */\n readonly files: readonly StreamingFileStats[];\n /** Enriched summary (matches existing Summary interface) */\n readonly enrichedSummary: Summary | null;\n /** Validation cross-check data */\n readonly validation: StreamingValidation | null;\n /** Write errors encountered during streaming */\n readonly writeErrors: readonly StreamingWriteError[];\n}\n\n/** Per-file test aggregation for streaming reports. */\nexport interface StreamingFileStats {\n readonly filePath: string;\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedOut: number;\n}\n\n/** Validation cross-check between reporter counts and Playwright. */\nexport interface StreamingValidation {\n readonly reporterTotal: number;\n readonly indexTotal: number;\n readonly playwrightStatus: string;\n readonly isValid: boolean;\n}\n\n/** A write error that occurred during streaming. */\nexport interface StreamingWriteError {\n readonly testId: string;\n readonly error: string;\n readonly timestamp: string;\n}\n\n/** Top-level manifest for a streaming report directory. */\nexport interface StreamingReportManifest {\n /** Schema version (`\"2.0\"` for streaming format) */\n readonly schemaVersion: string;\n /** ISO 8601 generation timestamp */\n readonly generatedAt: string;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Relative path to summary.json */\n readonly summaryFile: string;\n /** Relative path to index.json */\n readonly indexFile: string;\n /** Relative path to tests/ directory */\n readonly testsDir: string;\n /** Relative path to artifacts/ directory */\n readonly artifactsDir: string;\n /** Total number of test detail files */\n readonly testCount: number;\n /** Total size of all report data on disk */\n readonly totalSizeBytes: number;\n}\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n CLOUD_AUTH_FAILED: 'TESTRELIC_CLOUD_AUTH_FAILED',\n CLOUD_UPLOAD_FAILED: 'TESTRELIC_CLOUD_UPLOAD_FAILED',\n CLOUD_CONFIG_INVALID: 'TESTRELIC_CLOUD_CONFIG_INVALID',\n CLOUD_QUEUE_FAILED: 'TESTRELIC_CLOUD_QUEUE_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n CloudConfig,\n QueueEntry,\n UploadStrategy,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n // API tracking configuration\n if (obj.trackApiCalls !== undefined && typeof obj.trackApiCalls !== 'boolean') return false;\n if (obj.captureRequestHeaders !== undefined && typeof obj.captureRequestHeaders !== 'boolean') return false;\n if (obj.captureResponseHeaders !== undefined && typeof obj.captureResponseHeaders !== 'boolean') return false;\n if (obj.captureRequestBody !== undefined && typeof obj.captureRequestBody !== 'boolean') return false;\n if (obj.captureResponseBody !== undefined && typeof obj.captureResponseBody !== 'boolean') return false;\n if (obj.captureAssertions !== undefined && typeof obj.captureAssertions !== 'boolean') return false;\n\n if (obj.redactHeaders !== undefined) {\n if (!Array.isArray(obj.redactHeaders)) return false;\n for (const h of obj.redactHeaders) {\n if (typeof h !== 'string') return false;\n }\n }\n\n if (obj.redactBodyFields !== undefined) {\n if (!Array.isArray(obj.redactBodyFields)) return false;\n for (const f of obj.redactBodyFields) {\n if (typeof f !== 'string') return false;\n }\n }\n\n if (obj.apiIncludeUrls !== undefined) {\n if (!Array.isArray(obj.apiIncludeUrls)) return false;\n for (const p of obj.apiIncludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.apiExcludeUrls !== undefined) {\n if (!Array.isArray(obj.apiExcludeUrls)) return false;\n for (const p of obj.apiExcludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Config Validation\n// ---------------------------------------------------------------------------\n\nconst API_KEY_PATTERN = /^tr[a-z]*_[a-z]+_[a-zA-Z0-9]+$/;\n\nconst UPLOAD_STRATEGIES = new Set<string>(['batch', 'realtime', 'both']);\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0']);\n\nexport function isValidApiKeyFormat(value: unknown): value is string {\n return typeof value === 'string' && API_KEY_PATTERN.test(value);\n}\n\nexport function isValidUploadStrategy(value: unknown): value is UploadStrategy {\n return typeof value === 'string' && UPLOAD_STRATEGIES.has(value);\n}\n\nexport function isValidEndpointUrl(value: unknown): boolean {\n if (typeof value !== 'string') return false;\n try {\n const url = new URL(value);\n if (url.protocol === 'https:') return true;\n if (url.protocol === 'http:' && LOCALHOST_HOSTS.has(url.hostname)) return true;\n return false;\n } catch {\n return false;\n }\n}\n\nexport function isValidCloudConfig(input: unknown): input is CloudConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.apiKey !== undefined && obj.apiKey !== null) {\n if (typeof obj.apiKey !== 'string') return false;\n if (obj.apiKey.length > 0 && !isValidApiKeyFormat(obj.apiKey)) return false;\n }\n\n if (obj.endpoint !== undefined) {\n if (!isValidEndpointUrl(obj.endpoint)) return false;\n }\n\n if (obj.uploadStrategy !== undefined) {\n if (!isValidUploadStrategy(obj.uploadStrategy)) return false;\n }\n\n if (obj.timeout !== undefined) {\n if (typeof obj.timeout !== 'number' || obj.timeout < 1000 || obj.timeout > 120000) return false;\n }\n\n if (obj.projectName !== undefined && obj.projectName !== null && typeof obj.projectName !== 'string') return false;\n\n if (obj.queueMaxAge !== undefined) {\n if (typeof obj.queueMaxAge !== 'number' || obj.queueMaxAge < 3600000 || obj.queueMaxAge > 2592000000) return false;\n }\n\n if (obj.queueDirectory !== undefined) {\n if (typeof obj.queueDirectory !== 'string') return false;\n if (obj.queueDirectory.startsWith('/') || obj.queueDirectory.includes('..')) return false;\n }\n\n return true;\n}\n\nexport function isValidQueueEntry(input: unknown): input is QueueEntry {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (typeof obj.version !== 'string') return false;\n if (typeof obj.queuedAt !== 'string') return false;\n if (typeof obj.reason !== 'string') return false;\n if (typeof obj.retryCount !== 'number') return false;\n if (typeof obj.targetEndpoint !== 'string') return false;\n if (typeof obj.method !== 'string') return false;\n if (typeof obj.payload !== 'object' || obj.payload === null) return false;\n if (typeof obj.headers !== 'object' || obj.headers === null) return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACk1BO,IAAM,kBAAkB;AAGxB,IAAM,yBAAyB;AAG/B,IAAM,kBAAkB;AAGxB,IAAM,0BAA0B;AAyChC,SAAS,uBAAuB,KAA2C;AAChF,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,QAAM,SAAS;AACf,SACE,OAAO,kBAAkB,QACzB,OAAO,YAAY,WACnB,MAAM,QAAQ,OAAO,WAAW,KAChC,MAAM,QAAQ,OAAO,aAAa;AAEtC;AAGO,SAAS,uBAAuB,KAA2C;AAChF,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,QAAM,SAAS;AACf,SACE,OAAO,kBAAkB,QACzB,OAAO,OAAO,YAAY,YAC1B,OAAO,QAAQ,SAAS,KACxB,MAAM,QAAQ,OAAO,WAAW,KAChC,MAAM,QAAQ,OAAO,eAAe,KACpC,MAAM,QAAQ,OAAO,QAAQ,KAC7B,MAAM,QAAQ,OAAO,aAAa;AAEtC;;;ACt4BA,IAAM,cAA0B,MAAM;AAAC;AAEhC,SAAS,aAAa,SAAiC;AAC5D,QAAM,UAAU,SAAS,WAAW;AAEpC,SAAO;AAAA,IACL,KAAK,YAAoB,MAAiB;AACxC,cAAQ,QAAQ,SAAS,IAAI;AAAA,IAC/B;AAAA,IACA,KAAK,YAAoB,MAAiB;AACxC,cAAQ,QAAQ,SAAS,IAAI;AAAA,IAC/B;AAAA,IACA,MAAM,YAAoB,MAAiB;AACzC,cAAQ,SAAS,SAAS,IAAI;AAAA,IAChC;AAAA,IACA,MAAM,YAAoB,MAAiB;AACzC,cAAQ,SAAS,SAAS,IAAI;AAAA,IAChC;AAAA,EACF;AACF;;;ACnCO,IAAM,YAAY;AAAA,EACvB,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,sBAAsB;AAAA,EACtB,oBAAoB;AACtB;AAIO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EAGxC,YAAY,MAAiB,SAAiB,OAAiB;AAC7D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,QAAI,UAAU,QAAW;AACvB,aAAO,eAAe,MAAM,SAAS,EAAE,OAAO,OAAO,UAAU,OAAO,YAAY,MAAM,CAAC;AAAA,IAC3F;AAAA,EACF;AACF;AAEO,SAAS,YACd,MACA,SACA,OACgB;AAChB,SAAO,IAAI,eAAe,MAAM,SAAS,KAAK;AAChD;;;AC1BA,IAAM,mBAAmB,oBAAI,IAAY;AAAA,EACvC;AAAA,EAAQ;AAAA,EAAc;AAAA,EAAQ;AAAA,EAAW;AAAA,EACzC;AAAA,EAAa;AAAA,EAAe;AAAA,EAAe;AAAA,EAC3C;AAAA,EAAe;AAAA,EAAY;AAAA,EAAY;AAAA,EACvC;AAAA,EAAiB;AAAA,EAAY;AAC/B,CAAC;AAED,IAAM,iBAAiB,oBAAI,IAAI,CAAC,aAAa,eAAe,WAAW,CAAC;AAEjE,SAAS,sBAAsB,OAAyC;AAC7E,SAAO,OAAO,UAAU,YAAY,iBAAiB,IAAI,KAAK;AAChE;AAEA,SAAS,wBAAwB,KAAuB;AACtD,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,QAAI,eAAe,IAAI,GAAG,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEO,SAAS,cAAc,OAAyC;AACrE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,MAAI,CAAC,wBAAwB,KAAK,EAAG,QAAO;AAE5C,QAAM,MAAM;AAEZ,MAAI,IAAI,eAAe,UAAa,OAAO,IAAI,eAAe,SAAU,QAAO;AAC/E,MAAI,IAAI,sBAAsB,UAAa,OAAO,IAAI,sBAAsB,UAAW,QAAO;AAC9F,MAAI,IAAI,wBAAwB,UAAa,OAAO,IAAI,wBAAwB,UAAW,QAAO;AAClG,MAAI,IAAI,qBAAqB,UAAa,OAAO,IAAI,qBAAqB,SAAU,QAAO;AAC3F,MAAI,IAAI,wBAAwB,UAAa,OAAO,IAAI,wBAAwB,UAAW,QAAO;AAElG,MAAI,IAAI,oBAAoB,UAAa,IAAI,oBAAoB,MAAM;AACrE,QAAI,CAAC,MAAM,QAAQ,IAAI,eAAe,EAAG,QAAO;AAChD,eAAW,KAAK,IAAI,iBAAiB;AACnC,UAAI,CAAC,sBAAsB,CAAC,EAAG,QAAO;AAAA,IACxC;AAAA,EACF;AAEA,MAAI,IAAI,mBAAmB,QAAW;AACpC,QAAI,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAC/C,eAAW,KAAK,IAAI,gBAAgB;AAClC,UAAI,OAAO,MAAM,YAAY,EAAE,aAAa,QAAS,QAAO;AAAA,IAC9D;AAAA,EACF;AAEA,MAAI,IAAI,cAAc,UAAa,IAAI,cAAc,QAAQ,OAAO,IAAI,cAAc,SAAU,QAAO;AAEvG,MAAI,IAAI,aAAa,UAAa,IAAI,aAAa,MAAM;AACvD,QAAI,OAAO,IAAI,aAAa,SAAU,QAAO;AAC7C,QAAI,CAAC,wBAAwB,IAAI,QAAQ,EAAG,QAAO;AAAA,EACrD;AAEA,MAAI,IAAI,eAAe,UAAa,OAAO,IAAI,eAAe,UAAW,QAAO;AAChF,MAAI,IAAI,mBAAmB,WAAc,OAAO,IAAI,mBAAmB,YAAY,IAAI,mBAAmB,IAAK,QAAO;AACtH,MAAI,IAAI,qBAAqB,UAAa,OAAO,IAAI,qBAAqB,UAAW,QAAO;AAG5F,MAAI,IAAI,kBAAkB,UAAa,OAAO,IAAI,kBAAkB,UAAW,QAAO;AACtF,MAAI,IAAI,0BAA0B,UAAa,OAAO,IAAI,0BAA0B,UAAW,QAAO;AACtG,MAAI,IAAI,2BAA2B,UAAa,OAAO,IAAI,2BAA2B,UAAW,QAAO;AACxG,MAAI,IAAI,uBAAuB,UAAa,OAAO,IAAI,uBAAuB,UAAW,QAAO;AAChG,MAAI,IAAI,wBAAwB,UAAa,OAAO,IAAI,wBAAwB,UAAW,QAAO;AAClG,MAAI,IAAI,sBAAsB,UAAa,OAAO,IAAI,sBAAsB,UAAW,QAAO;AAE9F,MAAI,IAAI,kBAAkB,QAAW;AACnC,QAAI,CAAC,MAAM,QAAQ,IAAI,aAAa,EAAG,QAAO;AAC9C,eAAW,KAAK,IAAI,eAAe;AACjC,UAAI,OAAO,MAAM,SAAU,QAAO;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,IAAI,qBAAqB,QAAW;AACtC,QAAI,CAAC,MAAM,QAAQ,IAAI,gBAAgB,EAAG,QAAO;AACjD,eAAW,KAAK,IAAI,kBAAkB;AACpC,UAAI,OAAO,MAAM,SAAU,QAAO;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,IAAI,mBAAmB,QAAW;AACpC,QAAI,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAC/C,eAAW,KAAK,IAAI,gBAAgB;AAClC,UAAI,OAAO,MAAM,YAAY,EAAE,aAAa,QAAS,QAAO;AAAA,IAC9D;AAAA,EACF;AAEA,MAAI,IAAI,mBAAmB,QAAW;AACpC,QAAI,CAAC,MAAM,QAAQ,IAAI,cAAc,EAAG,QAAO;AAC/C,eAAW,KAAK,IAAI,gBAAgB;AAClC,UAAI,OAAO,MAAM,YAAY,EAAE,aAAa,QAAS,QAAO;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AACT;AAMA,IAAM,kBAAkB;AAExB,IAAM,oBAAoB,oBAAI,IAAY,CAAC,SAAS,YAAY,MAAM,CAAC;AAEvE,IAAM,kBAAkB,oBAAI,IAAI,CAAC,aAAa,aAAa,SAAS,CAAC;AAE9D,SAAS,oBAAoB,OAAiC;AACnE,SAAO,OAAO,UAAU,YAAY,gBAAgB,KAAK,KAAK;AAChE;AAEO,SAAS,sBAAsB,OAAyC;AAC7E,SAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,KAAK;AACjE;AAEO,SAAS,mBAAmB,OAAyB;AAC1D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,QAAI,IAAI,aAAa,SAAU,QAAO;AACtC,QAAI,IAAI,aAAa,WAAW,gBAAgB,IAAI,IAAI,QAAQ,EAAG,QAAO;AAC1E,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,OAAsC;AACvE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,MAAI,CAAC,wBAAwB,KAAK,EAAG,QAAO;AAE5C,QAAM,MAAM;AAEZ,MAAI,IAAI,WAAW,UAAa,IAAI,WAAW,MAAM;AACnD,QAAI,OAAO,IAAI,WAAW,SAAU,QAAO;AAC3C,QAAI,IAAI,OAAO,SAAS,KAAK,CAAC,oBAAoB,IAAI,MAAM,EAAG,QAAO;AAAA,EACxE;AAEA,MAAI,IAAI,aAAa,QAAW;AAC9B,QAAI,CAAC,mBAAmB,IAAI,QAAQ,EAAG,QAAO;AAAA,EAChD;AAEA,MAAI,IAAI,mBAAmB,QAAW;AACpC,QAAI,CAAC,sBAAsB,IAAI,cAAc,EAAG,QAAO;AAAA,EACzD;AAEA,MAAI,IAAI,YAAY,QAAW;AAC7B,QAAI,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU,OAAQ,IAAI,UAAU,KAAQ,QAAO;AAAA,EAC5F;AAEA,MAAI,IAAI,gBAAgB,UAAa,IAAI,gBAAgB,QAAQ,OAAO,IAAI,gBAAgB,SAAU,QAAO;AAE7G,MAAI,IAAI,gBAAgB,QAAW;AACjC,QAAI,OAAO,IAAI,gBAAgB,YAAY,IAAI,cAAc,QAAW,IAAI,cAAc,OAAY,QAAO;AAAA,EAC/G;AAEA,MAAI,IAAI,mBAAmB,QAAW;AACpC,QAAI,OAAO,IAAI,mBAAmB,SAAU,QAAO;AACnD,QAAI,IAAI,eAAe,WAAW,GAAG,KAAK,IAAI,eAAe,SAAS,IAAI,EAAG,QAAO;AAAA,EACtF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,OAAqC;AACrE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,MAAI,CAAC,wBAAwB,KAAK,EAAG,QAAO;AAE5C,QAAM,MAAM;AAEZ,MAAI,OAAO,IAAI,YAAY,SAAU,QAAO;AAC5C,MAAI,OAAO,IAAI,aAAa,SAAU,QAAO;AAC7C,MAAI,OAAO,IAAI,WAAW,SAAU,QAAO;AAC3C,MAAI,OAAO,IAAI,eAAe,SAAU,QAAO;AAC/C,MAAI,OAAO,IAAI,mBAAmB,SAAU,QAAO;AACnD,MAAI,OAAO,IAAI,WAAW,SAAU,QAAO;AAC3C,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,YAAY,KAAM,QAAO;AACpE,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,YAAY,KAAM,QAAO;AAEpE,SAAO;AACT;AAEO,SAAS,qBAAqB,OAAwC;AAC3E,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AAExD,QAAM,MAAM;AAEZ,MAAI,OAAO,IAAI,kBAAkB,SAAU,QAAO;AAClD,MAAI,OAAO,IAAI,cAAc,SAAU,QAAO;AAC9C,MAAI,OAAO,IAAI,cAAc,SAAU,QAAO;AAC9C,MAAI,OAAO,IAAI,gBAAgB,SAAU,QAAO;AAChD,MAAI,OAAO,IAAI,kBAAkB,SAAU,QAAO;AAClD,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,YAAY,KAAM,QAAO;AACpE,MAAI,CAAC,MAAM,QAAQ,IAAI,QAAQ,EAAG,QAAO;AAEzC,QAAM,UAAU,IAAI;AACpB,MAAI,OAAO,QAAQ,UAAU,SAAU,QAAO;AAC9C,MAAI,OAAO,QAAQ,WAAW,SAAU,QAAO;AAC/C,MAAI,OAAO,QAAQ,WAAW,SAAU,QAAO;AAC/C,MAAI,OAAO,QAAQ,UAAU,SAAU,QAAO;AAC9C,MAAI,OAAO,QAAQ,YAAY,SAAU,QAAO;AAEhD,MAAI,QAAQ,aAAa,UAAa,OAAO,QAAQ,aAAa,SAAU,QAAO;AAEnF,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["// @testrelic/core — barrel export\n// Re-exports only, no implementation (Constitution II)\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n ApiCallsByStatusRange,\n ApiResponseTimeStats,\n CIMetadata,\n CIProvider,\n TimelineEntry,\n TestResult,\n TestStatus,\n TestType,\n FailureDiagnostic,\n NetworkStats,\n ResourceBreakdown,\n ResourceType,\n CapturedNetworkRequest,\n ApiCallRecord,\n CapturedCookie,\n ApiAssertion,\n FailedAssertionSummary,\n AssertionType,\n AssertionLocation,\n ReporterConfig,\n TestArtifacts,\n VisualComparison,\n VisualStatus,\n VisualSource,\n DomChange,\n DomChangeKind,\n DomStyleChange,\n NavigationAnnotation,\n MergeOptions,\n StepTestIdentity,\n ApiCallStepRequest,\n ApiCallStepResponse,\n StepAssertion,\n NavigationStep,\n ApiCallStep,\n TimelineStep,\n ActionStep,\n StepKind,\n LogSource,\n MobilePlatform,\n AppTestMode,\n DeviceInfo,\n AiDefect,\n AiDefectSeverity,\n AiDefectType,\n ActionCategory,\n ConsoleLogEntry,\n ConsoleLogLevel,\n TestRelicDataPayload,\n TestRelicFilePayload,\n CloudConfig,\n AuthState,\n AuthMode,\n GitMetadata,\n RepoResolution,\n QueueEntry,\n UploadStrategy,\n CloudReporterOptions,\n ArtifactFile,\n ArtifactTestEntry,\n ArtifactRunEntry,\n ArtifactRunManifest,\n ReportMode,\n TestIndexEntry,\n TestDetailData,\n StreamingReportSummary,\n StreamingFileStats,\n StreamingValidation,\n StreamingWriteError,\n StreamingReportManifest,\n} from './types.js';\n\nexport {\n PAYLOAD_VERSION,\n PAYLOAD_VERSION_LEGACY,\n ATTACHMENT_NAME,\n ATTACHMENT_CONTENT_TYPE,\n isTestRelicDataPayload,\n isTestRelicFilePayload,\n} from './types.js';\n\nexport type { Logger, LoggerOptions, LogLevel } from './logger.js';\nexport { createLogger } from './logger.js';\n\nexport { ErrorCode, TestRelicError, createError } from './errors.js';\n\nexport {\n isValidConfig,\n isValidNavigationType,\n isValidTestRunReport,\n isValidCloudConfig,\n isValidApiKeyFormat,\n isValidEndpointUrl,\n isValidUploadStrategy,\n isValidQueueEntry,\n} from './validation.js';\n","/**\n * @testrelic/core — Shared type definitions\n *\n * All interfaces for the JSON output schema, reporter configuration,\n * and internal fixture-to-reporter communication.\n *\n * Schema Version: 1.8.0\n */\n\n// ---------------------------------------------------------------------------\n// Navigation Types\n// ---------------------------------------------------------------------------\n\nexport type NavigationType =\n | 'goto'\n | 'navigation'\n | 'back'\n | 'forward'\n | 'refresh'\n | 'spa_route'\n | 'spa_replace'\n | 'hash_change'\n | 'link_click'\n | 'form_submit'\n | 'redirect'\n | 'popstate'\n | 'page_load'\n | 'manual_record'\n | 'fallback'\n | 'dummy';\n\n// ---------------------------------------------------------------------------\n// Output Schema Types (JSON report structure)\n// ---------------------------------------------------------------------------\n\nexport interface TestRunReport {\n readonly schemaVersion: string;\n readonly testRunId: string;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly totalDuration: number;\n readonly summary: Summary;\n readonly ci: CIMetadata | null;\n readonly metadata: Record<string, unknown> | null;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly shardRunIds: string[] | null;\n}\n\n/** Breakdown of API calls by HTTP response status range. */\nexport interface ApiCallsByStatusRange {\n readonly '2xx': number;\n readonly '3xx': number;\n readonly '4xx': number;\n readonly '5xx': number;\n readonly error: number;\n}\n\n/** Statistical distribution of API call response times in milliseconds. */\nexport interface ApiResponseTimeStats {\n readonly avg: number;\n readonly min: number;\n readonly max: number;\n readonly p50: number;\n readonly p95: number;\n readonly p99: number;\n}\n\nexport interface Summary {\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedout: number;\n readonly totalApiCalls: number;\n readonly uniqueApiUrls: number;\n readonly apiCallsByMethod: Record<string, number>;\n readonly apiCallsByStatusRange: ApiCallsByStatusRange;\n readonly apiResponseTime: ApiResponseTimeStats | null;\n readonly totalAssertions: number;\n readonly passedAssertions: number;\n readonly failedAssertions: number;\n readonly totalNavigations: number;\n readonly uniqueNavigationUrls: number;\n readonly totalTimelineSteps: number;\n readonly totalActionSteps: number;\n readonly actionStepsByCategory: Record<string, number>;\n /** Visual baseline comparisons across the run. */\n readonly visualChecks?: number;\n /** Of those, how many differed from their baseline. */\n readonly visualFailures?: number;\n /** Of those, how many had no baseline and wrote one. */\n readonly visualNew?: number;\n /** DOM elements that differed across the run's visual comparisons. */\n readonly visualDomChanges?: number;\n /** Action steps across the run, bucketed by the driver's own category. */\n readonly actionStepsByKind?: Record<string, number>;\n /** Device-log lines captured across the run (mobile). */\n readonly deviceLogLines?: number;\n /** Of those, how many were error or fatal. */\n readonly deviceLogErrors?: number;\n /** Of those, how many came from a crash log. */\n readonly deviceLogCrashes?: number;\n /** Screenshots captured across the run. */\n readonly screenshotCount?: number;\n /** Screen recordings captured across the run. */\n readonly videoCount?: number;\n /** AI-detected defects across the run, by severity. */\n readonly aiDefectsBySeverity?: Record<string, number>;\n /** Distinct devices the run touched. */\n readonly devices?: readonly DeviceInfo[];\n}\n\nexport interface CIMetadata {\n readonly provider: CIProvider;\n readonly buildId: string | null;\n readonly commitSha: string | null;\n readonly branch: string | null;\n readonly runUrl: string | null;\n}\n\nexport type CIProvider =\n | 'github-actions'\n | 'gitlab-ci'\n | 'jenkins'\n | 'circleci'\n | 'bitbucket-pipelines'\n | 'unknown';\n\nexport interface TimelineEntry {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly visitedAt: string;\n readonly duration: number;\n readonly specFile: string;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly tests: TestResult[];\n}\n\nexport interface TestResult {\n readonly title: string;\n readonly status: TestStatus;\n readonly duration: number;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly retryCount: number;\n readonly tags: string[];\n readonly failure: FailureDiagnostic | null;\n readonly testId: string;\n readonly filePath: string;\n readonly suiteName: string;\n readonly testType: TestType;\n readonly isFlaky: boolean;\n readonly retryStatus: string | null;\n readonly expectedStatus: TestStatus;\n readonly actualStatus: TestStatus;\n readonly artifacts: TestArtifacts | null;\n readonly networkRequests: CapturedNetworkRequest[] | null;\n readonly apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n readonly actions: readonly ActionStep[] | null;\n readonly consoleLogs: readonly ConsoleLogEntry[] | null;\n /** Visual baseline comparisons recorded during this test, if any. */\n readonly visual?: readonly VisualComparison[] | null;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\nexport type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';\n\nexport interface CapturedNetworkRequest {\n readonly url: string;\n readonly method: string;\n readonly resourceType: ResourceType;\n readonly statusCode: number;\n readonly responseTimeMs: number;\n readonly startedAt: string;\n readonly requestHeaders: Record<string, string> | null;\n readonly requestBody: string | null;\n readonly responseBody: string | null;\n readonly responseHeaders: Record<string, string> | null;\n readonly contentType: string | null;\n readonly responseSize: number;\n readonly requestBodyTruncated: boolean;\n readonly responseBodyTruncated: boolean;\n readonly isBinary: boolean;\n readonly error: string | null;\n}\n\nexport interface TestArtifacts {\n readonly screenshot?: string;\n /**\n * Every screenshot captured for this test, report-relative.\n *\n * `screenshot` is the first one and stays for compatibility. Mobile\n * collectors have always gathered an array — one per step, or one per\n * failure — and only index 0 survived: the artifact copier wrote a single\n * `screenshot.png` and dropped the rest before the report could ask.\n */\n readonly screenshots?: readonly string[];\n readonly video?: string;\n /**\n * Playwright's trace archive — the DOM snapshots, network log and screencast\n * frames a failure is actually debugged from.\n *\n * Its absence here went all the way down the stack: with no field to fill,\n * the collector had nothing to look for, so a project configured with\n * `trace: 'on'` wrote `trace.zip` to disk and shipped only the picture and\n * the video. The gap bit hardest exactly where it mattered most — a failure\n * caught in a hook leaves no screenshot worth having, so those runs reached\n * the cloud with a video and nothing anyone could step through.\n */\n readonly trace?: string;\n readonly screenshotKey?: string;\n readonly videoKey?: string;\n readonly traceKey?: string;\n}\n\n/**\n * Outcome of a single visual baseline comparison.\n *\n * `new` is a snapshot Playwright had no baseline for and therefore wrote one\n * for — it is not a pass, because nothing was actually compared. `updated`\n * means the run was invoked with `--update-snapshots` and the baseline was\n * rewritten, which likewise compares nothing.\n */\nexport type VisualStatus = 'passed' | 'failed' | 'new' | 'updated';\n\n/** Which assertion produced a comparison. */\nexport type VisualSource = 'toHaveScreenshot' | 'toMatchSnapshot' | 'toMatchVisualBaseline';\n\n/**\n * One visual baseline comparison within a test.\n *\n * A test may hold many of these — `TestArtifacts` deliberately is not the home\n * for them, because it is single-slot per type (one screenshot, one video, one\n * trace) while a single test can assert against any number of named snapshots.\n *\n * Every image field is a report-relative POSIX path, matching `TestArtifacts`.\n * A passing comparison carries no `actual` or `diff`: Playwright attaches\n * nothing when the images match, so there is nothing to copy.\n */\nexport interface VisualComparison {\n /** Snapshot identity within the test, e.g. `home` or `checkout/cart`. */\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n /** The committed baseline image. */\n readonly baseline?: string;\n /**\n * Where the committed baseline lives, relative to the Playwright config\n * file's directory (where `test-results` sits), POSIX separators — the\n * file a reviewer overwrites to accept this run.\n *\n * Deliberately not the artifact copy in `baseline`: that path is relative to\n * the report and points at a per-run duplicate, so a tool that \"accepted\" a\n * run by writing there would leave the baseline Playwright actually reads\n * untouched and the failure would come straight back.\n */\n readonly baselinePath?: string;\n /** What this run rendered. Absent when the comparison passed. */\n readonly actual?: string;\n /** Playwright's rendered diff. Present only on failure. */\n readonly diff?: string;\n /** The previous attempt's render, on a retried failure. */\n readonly previous?: string;\n /** Baseline dimensions, read from the PNG header. */\n readonly width?: number;\n readonly height?: number;\n /** Actual dimensions — set only when they differ from the baseline. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** Differing pixel count, as reported by Playwright's comparator. */\n readonly diffPixels?: number;\n /** `diffPixels / (width * height)`, computed at full precision. */\n readonly diffPixelRatio?: number;\n /** Cloud storage keys, populated after artifact upload. */\n readonly baselineKey?: string;\n readonly actualKey?: string;\n readonly diffKey?: string;\n /**\n * Which elements changed, ranked by the area they affect.\n *\n * Present only when a DOM baseline existed to compare against. Capped — see\n * `domChangeCount` for the number found before the cap.\n */\n readonly domChanges?: readonly DomChange[];\n /** Total changes found, which may exceed `domChanges.length`. */\n readonly domChangeCount?: number;\n /**\n * Of those, how many actually changed rather than merely shifting position.\n *\n * The gap between this and `domChangeCount` is the layout knock-on: one card\n * gaining a margin moves everything below it, and a report that presents all\n * of those as equals hides the single edit that caused them.\n */\n readonly domRootCauses?: number;\n /** True when the DOM capture itself hit a node or byte cap. */\n readonly domTruncated?: boolean;\n /** Report-relative paths to the two captured DOM snapshots. */\n readonly baselineDom?: string;\n readonly actualDom?: string;\n /**\n * The document-space region the screenshot covered: `[x, y, width, height]`.\n *\n * Element rectangles are in document coordinates, so the report needs this to\n * place a box over the image — for a locator screenshot the image covers only\n * that element's box, not the viewport.\n */\n readonly domCaptureBox?: readonly [number, number, number, number];\n /** Cloud storage keys for the DOM snapshots. */\n readonly baselineDomKey?: string;\n readonly actualDomKey?: string;\n}\n\n/** How an element differs between the baseline render and this one. */\nexport type DomChangeKind =\n | 'added'\n | 'removed'\n | 'moved'\n | 'resized'\n | 'restyled'\n | 'text'\n /** Its id or class list changed — often the real story behind a restyle. */\n | 'attrs';\n\n/** One computed style property that differs. */\nexport interface DomStyleChange {\n readonly prop: string;\n readonly from: string;\n readonly to: string;\n}\n\n/**\n * One element that differs between two renders.\n *\n * Deliberately shaped like the platform's existing `RegionEvidence` — rank,\n * bbox, area share, a kind and a human label — so that a DOM overlay becomes\n * another caller of the dashboard's existing box-overlay component rather than\n * a second renderer built alongside it.\n */\nexport interface DomChange {\n /** 1-based, ordered by affected area descending. Drives the overlay colour. */\n readonly rank: number;\n /** Structural path used to match the element across the two snapshots. */\n readonly path: string;\n /** What a human reads, e.g. `div.card.accent`. */\n readonly label: string;\n readonly kinds: readonly DomChangeKind[];\n /** Document-space `[x, y, width, height]` in this render. */\n readonly rect?: readonly [number, number, number, number];\n /** The same, in the baseline. Absent for an added element. */\n readonly baselineRect?: readonly [number, number, number, number];\n /** Share of the capture box this element occupies, 0–1. */\n readonly areaPct: number;\n readonly styles?: readonly DomStyleChange[];\n readonly text?: { readonly from: string; readonly to: string };\n /** Identity attributes that changed, e.g. `class: \"card\" → \"banner\"`. */\n readonly attrs?: readonly DomStyleChange[];\n /**\n * True when this element only moved or resized, in a comparison where\n * something else actually changed.\n *\n * Shifting one card down by 24px shifts everything below it too, so a naive\n * list reports eleven changes for one edit. This flags the ten that merely\n * got pushed around, so a report can lead with the one that did something.\n *\n * It is a statement about this element — that nothing about it changed except\n * its position — and deliberately not a claim to have proven what moved it.\n */\n readonly consequence?: boolean;\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';\n\nexport type TestType = 'e2e' | 'api' | 'unit' | 'mobile' | 'unknown';\n\n// ---------------------------------------------------------------------------\n// Mobile Device Types\n// ---------------------------------------------------------------------------\n\n/** Platform a mobile test drove. */\nexport type MobilePlatform = 'android' | 'ios' | 'web' | 'unknown';\n\n/** How the app under test was driven. */\nexport type AppTestMode = 'native' | 'hybrid' | 'mobile-web';\n\n/**\n * The device a mobile test ran on.\n *\n * Appium detects all of this from the W3C capabilities and Maestro reads it\n * from the CLI flags and driver log. Both used to collapse it to two words in\n * `tags` before the report saw it; this carries it through intact.\n */\nexport interface DeviceInfo {\n readonly platform: MobilePlatform;\n /** OS version, e.g. \"14\" or \"17.5\". */\n readonly platformVersion: string | null;\n /** Human-facing device name, e.g. \"Pixel 4a\" or \"iPhone 15\". */\n readonly deviceName: string | null;\n /** Driver, e.g. \"UiAutomator2\", \"XCUITest\", \"maestro\". */\n readonly automationName: string | null;\n /** App package / bundle id / apk path under test. */\n readonly appId: string | null;\n /** Native, hybrid or mobile-web. Null when the runner cannot tell. */\n readonly appTestMode: AppTestMode | null;\n /** Device serial or UDID, e.g. \"emulator-5554\". */\n readonly udid: string | null;\n}\n\n/** Severity of an AI-detected defect. */\nexport type AiDefectSeverity = 'critical' | 'warning' | 'info';\n\n/** Category of an AI-detected defect. */\nexport type AiDefectType = 'ui' | 'spelling' | 'i18n' | 'layout' | 'accessibility';\n\n/** A defect reported by an AI analysis pass over a captured screen. */\nexport interface AiDefect {\n readonly type: AiDefectType;\n readonly severity: AiDefectSeverity;\n readonly description: string;\n /** Where in the app it was seen, when the analyser says. */\n readonly location: string | null;\n /** Report-relative path to the screenshot it was found on. */\n readonly screenshot: string | null;\n}\n\nexport interface FailureDiagnostic {\n readonly message: string;\n readonly line: number | null;\n readonly code: string | null;\n readonly stack: string | null;\n}\n\nexport interface NetworkStats {\n readonly totalRequests: number;\n readonly failedRequests: number;\n readonly failedRequestUrls: string[];\n readonly totalBytes: number;\n readonly byType: ResourceBreakdown;\n}\n\nexport interface ResourceBreakdown {\n readonly xhr: number;\n readonly document: number;\n readonly script: number;\n readonly stylesheet: number;\n readonly image: number;\n readonly font: number;\n readonly other: number;\n}\n\n// ---------------------------------------------------------------------------\n// API Call Record (captured from Playwright APIRequestContext proxy)\n// ---------------------------------------------------------------------------\n\n/**\n * A cookie surfaced for the API-workspace Cookies tab. Values are MASKED by\n * default (the SDK never uploads raw session tokens to the cloud); the name and\n * any non-secret attributes (Path/Domain/Secure/HttpOnly/SameSite) are kept so\n * the inspector can show which cookies were sent/set, Postman-style.\n */\nexport interface CapturedCookie {\n readonly name: string;\n /** Masked value (e.g. \"••••\"). Never the raw cookie value. */\n readonly value: string;\n /** Response-cookie attributes joined for display (Set-Cookie only). */\n readonly attributes?: string;\n}\n\nexport interface ApiCallRecord {\n /** Unique call identifier within the test (e.g., \"api-call-0\") */\n readonly id: string;\n /** ISO 8601 timestamp when the call was initiated */\n readonly timestamp: string;\n /** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */\n readonly method: string;\n /** Full resolved URL (after baseURL resolution by Playwright) */\n readonly url: string;\n /** All request headers sent, or null if unavailable */\n readonly requestHeaders: Record<string, string> | null;\n /** Request payload as string (JSON-serialized for objects). Null for bodiless methods */\n readonly requestBody: string | null;\n /** HTTP response status code, or null if request errored before response */\n readonly responseStatusCode: number | null;\n /** HTTP response status text, or null if request errored before response */\n readonly responseStatusText: string | null;\n /** All response headers received, or null if request errored before response */\n readonly responseHeaders: Record<string, string> | null;\n /** Response body: string for text/JSON, base64 for binary. Null on error */\n readonly responseBody: string | null;\n /** Request cookies parsed from the `Cookie` header (values masked). Optional/null when none. */\n readonly requestCookies?: readonly CapturedCookie[] | null;\n /** Response cookies parsed from `Set-Cookie` headers (values masked). Optional/null when none. */\n readonly responseCookies?: readonly CapturedCookie[] | null;\n /** Duration in milliseconds from request initiation to response received */\n readonly responseTimeMs: number;\n /** Whether the response body is stored as a base64-encoded binary string */\n readonly isBinary: boolean;\n /** Error message if the call failed (network error, timeout), null on success */\n readonly error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// API Assertion Types (captured from assertion tracking)\n// ---------------------------------------------------------------------------\n\n/** Categorization of the assertion being made. */\nexport type AssertionType =\n | 'status'\n | 'statusOk'\n | 'header'\n | 'bodyField'\n | 'bodyMatch'\n | 'bodyContains'\n | 'custom';\n\n/** Source location where an assertion was made in the test file. */\nexport interface AssertionLocation {\n /** Absolute or relative file path to the test file */\n readonly file: string;\n /** Line number (1-based) */\n readonly line: number;\n /** Column number (1-based), if available */\n readonly column?: number;\n}\n\n/** A failed assertion reduced to what a summary row needs. */\nexport interface FailedAssertionSummary {\n readonly type: AssertionType;\n readonly expected: string;\n readonly actual: string;\n readonly expression?: string;\n}\n\n/** A single captured assertion on API response data. */\nexport interface ApiAssertion {\n /** Links to ApiCallRecord.id from the API proxy */\n readonly callId: string;\n /** Category of assertion */\n readonly type: AssertionType;\n /** What the test expected */\n readonly expected: unknown;\n /** What was actually received */\n readonly actual: unknown;\n /** Whether the assertion passed or failed */\n readonly status: 'passed' | 'failed';\n /** Source location of the assertion in the test file */\n readonly location: AssertionLocation;\n /** String representation of the assertion expression (best-effort) */\n readonly expression?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Unified Timeline Types (navigation + API call steps)\n// ---------------------------------------------------------------------------\n\n/** Test identity block attached to every timeline step. */\nexport interface StepTestIdentity {\n readonly title: string;\n readonly fullTitle: readonly string[];\n readonly status: TestStatus;\n readonly duration: number;\n readonly retries: number;\n readonly retry: number;\n readonly tags: readonly string[];\n readonly failure: FailureDiagnostic | null;\n}\n\n/** Request details within an API call step. */\nexport interface ApiCallStepRequest {\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** Response details within an API call step. */\nexport interface ApiCallStepResponse {\n readonly statusCode: number;\n readonly statusText: string;\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** An assertion linked to an API call step (callId omitted — implicit from parent). */\nexport interface StepAssertion {\n readonly type: AssertionType;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly status: 'passed' | 'failed';\n readonly location: AssertionLocation;\n readonly expression?: string;\n}\n\n/** A browser navigation event in the unified timeline. */\nexport interface NavigationStep {\n readonly index: number;\n readonly type: 'navigation';\n readonly url: string;\n readonly timestamp: string;\n readonly durationOnUrl: number;\n readonly navigationType: NavigationType;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** An API request event in the unified timeline. */\nexport interface ApiCallStep {\n readonly index: number;\n readonly type: 'api_call';\n readonly callId: string;\n readonly method: string;\n readonly url: string;\n readonly timestamp: string;\n readonly responseTime: number | null;\n readonly request: ApiCallStepRequest;\n readonly response: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions: readonly StepAssertion[];\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** Discriminated union of all timeline step types. */\nexport type TimelineStep = NavigationStep | ApiCallStep;\n\n// ---------------------------------------------------------------------------\n// Action Step Types (Playwright test step capture)\n// ---------------------------------------------------------------------------\n\n/**\n * Categorized type of a captured test action.\n *\n * `navigation` covers app-level navigation events that aren't UI actions —\n * Maestro `launchApp` / `openLink` / `back`, Appium activity transitions, and\n * Playwright top-level page navigations. The platform's timeline UI and Ask\n * AI tools (`query_test_steps`'s `categoryFilter`) treat navigation as a\n * distinct bucket from generic `custom_step`.\n */\nexport type ActionCategory = 'ui_action' | 'assertion' | 'navigation' | 'custom_step';\n\n/**\n * The driver's own step category, preserved alongside `ActionCategory`.\n *\n * `ActionCategory` is the four-bucket vocabulary the platform's timeline and\n * Ask AI tools filter on, and it stays closed. Mobile drivers distinguish far\n * more than four kinds — Appium reports eleven, Maestro ten — and flattening\n * them made a swipe, an app launch and a screenshot render identically. This\n * keeps the finer label for the report to badge.\n */\nexport type StepKind =\n | 'find'\n | 'interaction'\n | 'gesture'\n | 'navigation'\n | 'lifecycle'\n | 'context'\n | 'device'\n | 'keyboard'\n | 'alert'\n | 'screenshot'\n | 'assertion'\n | 'media'\n | 'script'\n | 'flow_control'\n | 'ai'\n | 'other';\n\n/** A single captured test action step. */\nexport interface ActionStep {\n readonly title: string;\n readonly category: ActionCategory;\n /** The driver's own finer-grained category, when it reported one. */\n readonly kind?: StepKind | null;\n /** Element selector this step acted on, when the driver reported one. */\n readonly selector?: string | null;\n readonly timestamp: string;\n readonly duration: number;\n readonly videoOffset: number | null;\n readonly status: 'passed' | 'failed';\n readonly error: string | null;\n readonly children: readonly ActionStep[];\n}\n\n// ---------------------------------------------------------------------------\n// Console Log Types (browser console + terminal stdout/stderr)\n// ---------------------------------------------------------------------------\n\n/** Level of a captured console/terminal log message. */\nexport type ConsoleLogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' | 'stdout' | 'stderr';\n\n/** A single captured console or terminal log entry. */\n/**\n * Where a captured log line came from.\n *\n * Browser consoles only ever produce `console`. Mobile runs mix several\n * streams, and telling a crash log from ordinary logcat chatter is the whole\n * point of showing them — so the stream survives to the row.\n */\nexport type LogSource =\n | 'console'\n | 'logcat'\n | 'syslog'\n | 'crashlog'\n | 'browser'\n | 'safariConsole'\n | 'performance'\n | 'server'\n | 'bidi'\n | 'driver';\n\nexport interface ConsoleLogEntry {\n /** Log level or stream type */\n readonly level: ConsoleLogLevel;\n /** Text content of the log message */\n readonly text: string;\n /** ISO 8601 timestamp when the log was captured */\n readonly timestamp: string;\n /** Source file location (browser console only, best-effort) */\n readonly location: string | null;\n /** Which stream produced the line. Defaults to `console` when absent. */\n readonly source?: LogSource | null;\n /** Emitting tag, e.g. an Android logcat tag like \"ActivityManager\". */\n readonly tag?: string | null;\n /** Emitting process id, when the stream reports one. */\n readonly pid?: number | null;\n /** Stack trace attached to the line (crash logs). */\n readonly stackTrace?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration Types (reporter tuple options)\n// ---------------------------------------------------------------------------\n\n/** Report generation mode. */\nexport type ReportMode = 'streaming' | 'embedded' | 'auto';\n\nexport interface ReporterConfig {\n readonly outputPath?: string;\n readonly includeStackTrace?: boolean;\n readonly includeCodeSnippets?: boolean;\n readonly codeContextLines?: number;\n readonly includeNetworkStats?: boolean;\n /** Master on/off for Test Navigation capture. Default: true. When false the\n * reporter drops navigation data before upload and logs a one-time notice. */\n readonly captureNavigation?: boolean;\n readonly navigationTypes?: NavigationType[] | null;\n readonly redactPatterns?: (string | RegExp)[];\n readonly testRunId?: string | null;\n readonly metadata?: Record<string, unknown> | null;\n readonly openReport?: boolean;\n readonly htmlReportPath?: string;\n readonly includeArtifacts?: boolean;\n /** When false, disables API call interception in the unified fixture. Default: true */\n readonly trackApiCalls?: boolean;\n /** When true, suppresses console summary output. Default: false */\n readonly quiet?: boolean;\n /** When true, captures Playwright test steps (actions). Default: true */\n readonly includeActionSteps?: boolean;\n /** Capture request headers for API calls. Default: true */\n readonly captureRequestHeaders?: boolean;\n /** Capture response headers for API calls. Default: true */\n readonly captureResponseHeaders?: boolean;\n /** Capture request body for API calls. Default: true */\n readonly captureRequestBody?: boolean;\n /** Capture response body for API calls. Default: true */\n readonly captureResponseBody?: boolean;\n /** Capture API assertions (both pass and fail). Default: true */\n readonly captureAssertions?: boolean;\n /**\n * Header names whose values are replaced with \"[REDACTED]\".\n * Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']\n */\n readonly redactHeaders?: string[];\n /**\n * Body field names whose values are replaced with \"[REDACTED]\" at any depth.\n * Default: ['password', 'secret', 'token', 'apiKey', 'api_key']\n */\n readonly redactBodyFields?: string[];\n /**\n * Only track API calls matching these URL patterns.\n * Default: undefined (track all)\n */\n readonly apiIncludeUrls?: (string | RegExp)[];\n /**\n * Exclude API calls matching these URL patterns. Takes precedence over include.\n * Default: undefined (exclude none)\n */\n readonly apiExcludeUrls?: (string | RegExp)[];\n /** Capture browser console logs (E2E) and terminal output (API). Default: true */\n readonly captureConsoleLogs?: boolean;\n /** Cloud integration configuration. Default: undefined (local mode) */\n readonly cloud?: CloudReporterOptions;\n /**\n * Report generation mode.\n * - `'embedded'`: All data embedded in a single HTML file (current behavior)\n * - `'streaming'`: Data streamed to disk per-test, served via local server\n * - `'auto'`: Use embedded for < streamingThreshold tests, streaming otherwise\n * Default: `'auto'`\n */\n readonly reportMode?: ReportMode;\n /**\n * Test count threshold for auto mode. When the suite has >= this many tests,\n * streaming mode is used. Default: 500\n */\n readonly streamingThreshold?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types (fixture → reporter communication)\n// ---------------------------------------------------------------------------\n\nexport interface NavigationAnnotation {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly timestamp: string;\n readonly domContentLoadedAt?: string;\n readonly networkIdleAt?: string;\n readonly networkStats?: NetworkStats;\n}\n\n// ---------------------------------------------------------------------------\n// Worker-Safe Data Payload (worker → reporter communication)\n// ---------------------------------------------------------------------------\n\n/** Consolidated data payload sent from a worker fixture to the reporter. */\nexport interface TestRelicDataPayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version (semver). */\n readonly version: string;\n /** Navigation events collected during the test. */\n readonly navigations: readonly NavigationAnnotation[];\n /** Captured network requests during the test. */\n readonly networkRequests: readonly CapturedNetworkRequest[];\n /** API calls made via APIRequestContext proxy. */\n readonly apiCalls: readonly ApiCallRecord[];\n /** Assertions captured on API responses. */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Console/terminal logs captured during the test. Optional for backward compat. */\n readonly consoleLogs?: readonly ConsoleLogEntry[];\n}\n\n/** Current payload format version. */\nexport const PAYLOAD_VERSION = '2.0.0';\n\n/** Legacy payload version (pre-streaming). */\nexport const PAYLOAD_VERSION_LEGACY = '1.0.0';\n\n/** Attachment name used for the consolidated payload. */\nexport const ATTACHMENT_NAME = 'testrelic-data';\n\n/** Attachment content type. */\nexport const ATTACHMENT_CONTENT_TYPE = 'application/json';\n\n/**\n * File-based data payload (v2.0.0).\n *\n * Heavy data (network requests, console logs, API calls) is written to\n * JSONL files on disk and referenced by path. Only lightweight data\n * (navigations, assertions) is kept inline.\n */\nexport interface TestRelicFilePayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version — `\"2.0.0\"` for file-based payloads. */\n readonly version: '2.0.0';\n /** Navigation events (small, kept inline). */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertions (small, kept inline). */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Absolute path to JSONL file containing CapturedNetworkRequest items. */\n readonly networkRequestsFile: string | null;\n /** Number of network request items in the file. */\n readonly networkRequestsCount: number;\n /** Absolute path to JSONL file containing ConsoleLogEntry items. */\n readonly consoleLogsFile: string | null;\n /** Number of console log items in the file. */\n readonly consoleLogsCount: number;\n /** Absolute path to JSONL file containing ApiCallRecord items. */\n readonly apiCallsFile: string | null;\n /** Number of API call items in the file. */\n readonly apiCallsCount: number;\n /**\n * Response-status mix of the calls in the file.\n *\n * Tallied as each record is appended. The file itself is never read back\n * into memory in streaming mode, so an aggregate computed at write time is\n * the only way the grid can show a row's status chips.\n */\n readonly apiStatusMix?: ApiCallsByStatusRange | null;\n}\n\n/** Type guard: validates that a parsed object is a v2 file-based payload. */\nexport function isTestRelicFilePayload(obj: unknown): obj is TestRelicFilePayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n record.version === '2.0.0' &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n/** Type guard: validates that a parsed object is a v1 inline data payload. */\nexport function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n typeof record.version === 'string' &&\n record.version.length > 0 &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.networkRequests) &&\n Array.isArray(record.apiCalls) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Integration Types\n// ---------------------------------------------------------------------------\n\n/** Upload strategy for cloud timeline data. */\nexport type UploadStrategy = 'batch' | 'realtime' | 'both';\n\n/** Current cloud/local operating mode. */\nexport type AuthMode = 'cloud' | 'local' | 'pending';\n\n/** Resolved cloud configuration, immutable after initialization. */\nexport interface CloudConfig {\n readonly apiKey: string | null;\n readonly endpoint: string;\n readonly uploadStrategy: UploadStrategy;\n /** Per-request ceiling. ⚠ It governed only the auth handshake until 2.14.0 —\n * the upload calls used bare `fetch` and inherited undici's 5-minute\n * defaults, so a tester who set this had bounded the token exchange and\n * nothing else. It now bounds the upload requests too. */\n readonly timeout: number;\n /** Total time the whole upload phase may take before it gives up, writes the\n * queue and lets the process exit. The phase runs inside Playwright's\n * `onEnd`, so this is the difference between a late run and a run that never\n * finishes. Default 120s.\n *\n * ⛔ OPTIONAL on purpose. `CloudConfig` is built by every analytics package\n * (appium, maestro, playwright), and making a new field required breaks all\n * of them at once for a knob only one upload path reads — CI caught exactly\n * that. Callers resolve it as `?? DEFAULT_UPLOAD_BUDGET_MS`. */\n readonly uploadBudgetMs?: number;\n readonly projectName: string | null;\n readonly queueMaxAge: number;\n readonly queueDirectory: string;\n readonly uploadArtifacts: boolean;\n readonly artifactMaxSizeMb: number;\n}\n\n/** Mutable auth state tracked by the cloud client. */\nexport interface AuthState {\n mode: AuthMode;\n accessToken: string | null;\n refreshToken: string | null;\n expiresAt: number | null;\n orgId: string | null;\n orgName: string | null;\n userId: string | null;\n userName: string | null;\n}\n\n/** Auto-collected git information. All fields nullable. */\nexport interface GitMetadata {\n readonly branch: string | null;\n readonly commitSha: string | null;\n readonly commitMessage: string | null;\n readonly commitAuthor: string | null;\n readonly remoteUrl: string | null;\n}\n\n/** Cached repo identity. */\nexport interface RepoResolution {\n readonly repoId: string;\n readonly gitId: string;\n readonly displayName: string;\n readonly resolvedAt: number;\n readonly apiKeyHash: string;\n}\n\n/** A single queued upload payload. */\nexport interface QueueEntry {\n readonly version: string;\n readonly queuedAt: string;\n readonly reason: string;\n readonly retryCount: number;\n readonly targetEndpoint: string;\n readonly method: string;\n readonly payload: Record<string, unknown>;\n readonly headers: Record<string, string>;\n}\n\n/** Inline cloud configuration accepted via reporter options. */\nexport interface CloudReporterOptions {\n readonly apiKey?: string;\n readonly endpoint?: string;\n readonly upload?: UploadStrategy;\n readonly timeout?: number;\n /** Total budget for the upload phase (ms). See CloudConfig.uploadBudgetMs. */\n readonly uploadBudgetMs?: number;\n readonly projectName?: string;\n readonly queueMaxAge?: string;\n readonly queueDirectory?: string;\n readonly uploadArtifacts?: boolean;\n readonly artifactMaxSizeMb?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Merge CLI Types\n// ---------------------------------------------------------------------------\n\nexport interface MergeOptions {\n readonly output: string;\n readonly testRunId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact Management Types\n// ---------------------------------------------------------------------------\n\n/** A single artifact file within a test's artifact folder. */\nexport interface ArtifactFile {\n /** File name (e.g., `screenshot.png`, `video.webm`, `trace.zip`) */\n readonly name: string;\n /** Artifact type for rendering */\n readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';\n /** Path relative to the report output directory */\n readonly relativePath: string;\n /** File size in bytes */\n readonly sizeBytes: number;\n}\n\n/** A single test's artifacts within a run folder. */\nexport interface ArtifactTestEntry {\n /** Sanitized test folder name */\n readonly testName: string;\n /** List of artifact files for this test */\n readonly files: readonly ArtifactFile[];\n}\n\n/** A single test run's artifact folder in the manifest. */\nexport interface ArtifactRunEntry {\n /** Timestamp-based folder name (e.g., `2026-03-02T14-30-00`) */\n readonly folderName: string;\n /** ISO 8601 timestamp parsed from folder name */\n readonly timestamp: string;\n /** Sum of all file sizes in this run folder */\n readonly totalSizeBytes: number;\n /** Number of test artifact subdirectories */\n readonly testCount: number;\n /** Per-test artifact details */\n readonly tests: readonly ArtifactTestEntry[];\n /** Whether this folder belongs to the current report's run */\n readonly isCurrentRun: boolean;\n}\n\n/** Top-level manifest structure written as `artifact-manifest.json`. */\nexport interface ArtifactRunManifest {\n /** Manifest schema version (starts at `\"1.0\"`) */\n readonly schemaVersion: string;\n /** ISO 8601 timestamp of manifest generation */\n readonly generatedAt: string;\n /** Relative path to artifacts directory from report (e.g., `\"artifacts\"`) */\n readonly artifactBaseDir: string;\n /** Sum of all run folder sizes */\n readonly totalSizeBytes: number;\n /** All discovered run folders, sorted newest first */\n readonly runs: readonly ArtifactRunEntry[];\n /** Port of the artifact management server, or null if not running */\n readonly serverPort: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Streaming Report Types (server-backed reporter)\n// ---------------------------------------------------------------------------\n\n/** Compact index entry for a single test — kept in memory during run. */\nexport interface TestIndexEntry {\n /** Deterministic hash ID (first 12 chars of SHA-256) */\n readonly id: string;\n /** Test title (leaf name) */\n readonly title: string;\n /** Full title path (describe blocks + test name) */\n readonly titlePath: readonly string[];\n /** Relative path to test file */\n readonly filePath: string;\n /** Test outcome */\n readonly status: TestStatus;\n /**\n * The status Playwright expected: `test.fail()` expects 'failed', and\n * `test.skip()` / `test.fixme()` expect 'skipped'. Absent means 'passed'.\n *\n * `status` keeps its meaning: the actual result, or 'flaky' / 'skipped'\n * from Playwright's outcome. A reader derives the verdict from the pair the\n * way Playwright does: a result is expected only when actual === expected.\n * A `test.fail()` that failed is therefore a pass, and one that passed (or\n * timed out) is a failure.\n */\n readonly expectedStatus?: TestStatus;\n /** Test duration in milliseconds */\n readonly duration: number;\n /** Playwright project name */\n readonly project: string;\n /** Retry attempt number (0 = first run) */\n readonly retryIndex: number;\n /** Test tags */\n readonly tags: readonly string[];\n /** What kind of test this is — badges the row, and picks the row's evidence. */\n readonly testType?: TestType;\n /** Whether this test has network request data */\n readonly hasNetworkData: boolean;\n /** Whether this test has console log data */\n readonly hasConsoleData: boolean;\n /** Whether this test has screenshot/video artifacts */\n readonly hasArtifacts: boolean;\n /** Whether this test has action step data */\n readonly hasActionSteps: boolean;\n /** Number of visual baseline comparisons recorded */\n readonly visualCount?: number;\n /** Of those, how many differed from their baseline */\n readonly visualFailedCount?: number;\n /** DOM elements that differed across this test's comparisons */\n readonly domChangeCount?: number;\n /**\n * Report-relative path to a diff image for this test, when one exists.\n *\n * Carried on the index so the run overview can show the failure itself\n * rather than only describing it — the grid must never fetch per-test\n * detail files to render.\n */\n readonly visualDiffPath?: string;\n /** First line of error message (failed/flaky tests only) */\n readonly errorMessage: string | null;\n /** Number of network requests captured */\n readonly networkCount: number;\n /** Number of console log entries captured */\n readonly consoleCount: number;\n /** Number of action steps captured */\n readonly actionCount: number;\n /** API calls captured for this test. */\n readonly apiCallCount?: number;\n /** Response-status mix of those calls, for the row's status chips. */\n readonly apiStatusMix?: ApiCallsByStatusRange;\n /** API assertions that passed during this test. */\n readonly assertionsPassed?: number;\n /** API assertions that failed during this test. */\n readonly assertionsFailed?: number;\n /**\n * The first failed assertion, flattened to strings.\n *\n * The run overview leads with \"expected 101, received undefined\" and must\n * do so without opening the test's detail file. The first line of an API\n * failure message is the matcher wrapper, which says nothing.\n */\n readonly firstFailedAssertion?: FailedAssertionSummary;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** Action steps for this test, bucketed by the driver's own category. */\n readonly stepKinds?: Record<string, number>;\n /** Device-log errors and fatals recorded during this test. */\n readonly deviceLogErrors?: number;\n /** Whether a crash-log line was recorded during this test. */\n readonly hasCrashLog?: boolean;\n /** Whether a screen recording exists for this test. */\n readonly hasVideo?: boolean;\n /** AI-detected defects attributed to this test. */\n readonly aiDefectCount?: number;\n /** Whether this is a retry attempt (not the final attempt) */\n readonly isRetry: boolean;\n /** Stable key shared by every attempt of the same test (same across retries, unlike `id`) */\n readonly retryGroupKey: string;\n}\n\n/**\n * Full detail data for a single test, stored on disk.\n *\n * In v3 (stream-to-disk), heavy data (network requests, console logs,\n * API calls) is stored in separate JSONL files alongside meta.json.\n * The `has*` and `*Count` fields indicate availability without loading.\n */\nexport interface TestDetailData {\n /** Same as TestIndexEntry.id */\n readonly id: string;\n /** Page navigation events (inline — typically small) */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertion results (inline — typically small) */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Nested action step tree (inline — typically small) */\n readonly actions: readonly ActionStep[];\n /** References to screenshot/video files */\n readonly artifacts: TestArtifacts | null;\n /** Visual baseline comparisons (inline — a handful per test at most) */\n readonly visual?: readonly VisualComparison[] | null;\n /** Error details for failed/flaky tests */\n readonly failureDiagnostic: FailureDiagnostic | null;\n /** Whether network.jsonl exists for this test */\n readonly hasNetworkFile: boolean;\n /** Number of items in network.jsonl */\n readonly networkRequestsCount: number;\n /** Whether console.jsonl exists for this test */\n readonly hasConsoleFile: boolean;\n /** Number of items in console.jsonl */\n readonly consoleLogsCount: number;\n /** Whether api-calls.jsonl exists for this test */\n readonly hasApiCallsFile: boolean;\n /** Number of items in api-calls.jsonl */\n readonly apiCallsCount: number;\n /** Test start time (ISO 8601) — used for video sync */\n readonly startedAt?: string;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\n/** Streaming report summary — running counters written to summary.json. */\nexport interface StreamingReportSummary {\n /** Unique run identifier */\n readonly testRunId: string;\n /** ISO 8601 start timestamp */\n readonly startedAt: string;\n /** ISO 8601 completion timestamp */\n readonly completedAt: string;\n /** Wall-clock duration in ms */\n readonly totalDuration: number;\n /** Total number of test results */\n readonly total: number;\n /** Tests that passed */\n readonly passed: number;\n /** Tests that failed */\n readonly failed: number;\n /** Tests that passed on retry */\n readonly flaky: number;\n /** Tests that were skipped */\n readonly skipped: number;\n /** Tests that timed out */\n readonly timedOut: number;\n /** Tests that were interrupted */\n readonly interrupted: number;\n /** Total API calls across all tests */\n readonly totalApiCalls: number;\n /** Total assertions across all tests */\n readonly totalAssertions: number;\n /** Total page navigations */\n readonly totalNavigations: number;\n /** Total network requests captured */\n readonly totalNetworkRequests: number;\n /** Total console log entries captured */\n readonly totalConsoleLogs: number;\n /** Total action steps captured */\n readonly totalActionSteps: number;\n /** User-provided metadata */\n readonly metadata: Record<string, unknown> | null;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Per-file aggregation */\n readonly files: readonly StreamingFileStats[];\n /** Enriched summary (matches existing Summary interface) */\n readonly enrichedSummary: Summary | null;\n /** Validation cross-check data */\n readonly validation: StreamingValidation | null;\n /** Write errors encountered during streaming */\n readonly writeErrors: readonly StreamingWriteError[];\n}\n\n/** Per-file test aggregation for streaming reports. */\nexport interface StreamingFileStats {\n readonly filePath: string;\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedOut: number;\n}\n\n/** Validation cross-check between reporter counts and Playwright. */\nexport interface StreamingValidation {\n readonly reporterTotal: number;\n readonly indexTotal: number;\n readonly playwrightStatus: string;\n readonly isValid: boolean;\n}\n\n/** A write error that occurred during streaming. */\nexport interface StreamingWriteError {\n readonly testId: string;\n readonly error: string;\n readonly timestamp: string;\n}\n\n/** Top-level manifest for a streaming report directory. */\nexport interface StreamingReportManifest {\n /** Schema version (`\"2.0\"` for streaming format) */\n readonly schemaVersion: string;\n /** ISO 8601 generation timestamp */\n readonly generatedAt: string;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Relative path to summary.json */\n readonly summaryFile: string;\n /** Relative path to index.json */\n readonly indexFile: string;\n /** Relative path to tests/ directory */\n readonly testsDir: string;\n /** Relative path to artifacts/ directory */\n readonly artifactsDir: string;\n /** Total number of test detail files */\n readonly testCount: number;\n /** Total size of all report data on disk */\n readonly totalSizeBytes: number;\n}\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n CLOUD_AUTH_FAILED: 'TESTRELIC_CLOUD_AUTH_FAILED',\n CLOUD_UPLOAD_FAILED: 'TESTRELIC_CLOUD_UPLOAD_FAILED',\n CLOUD_CONFIG_INVALID: 'TESTRELIC_CLOUD_CONFIG_INVALID',\n CLOUD_QUEUE_FAILED: 'TESTRELIC_CLOUD_QUEUE_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n CloudConfig,\n QueueEntry,\n UploadStrategy,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n // API tracking configuration\n if (obj.trackApiCalls !== undefined && typeof obj.trackApiCalls !== 'boolean') return false;\n if (obj.captureRequestHeaders !== undefined && typeof obj.captureRequestHeaders !== 'boolean') return false;\n if (obj.captureResponseHeaders !== undefined && typeof obj.captureResponseHeaders !== 'boolean') return false;\n if (obj.captureRequestBody !== undefined && typeof obj.captureRequestBody !== 'boolean') return false;\n if (obj.captureResponseBody !== undefined && typeof obj.captureResponseBody !== 'boolean') return false;\n if (obj.captureAssertions !== undefined && typeof obj.captureAssertions !== 'boolean') return false;\n\n if (obj.redactHeaders !== undefined) {\n if (!Array.isArray(obj.redactHeaders)) return false;\n for (const h of obj.redactHeaders) {\n if (typeof h !== 'string') return false;\n }\n }\n\n if (obj.redactBodyFields !== undefined) {\n if (!Array.isArray(obj.redactBodyFields)) return false;\n for (const f of obj.redactBodyFields) {\n if (typeof f !== 'string') return false;\n }\n }\n\n if (obj.apiIncludeUrls !== undefined) {\n if (!Array.isArray(obj.apiIncludeUrls)) return false;\n for (const p of obj.apiIncludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.apiExcludeUrls !== undefined) {\n if (!Array.isArray(obj.apiExcludeUrls)) return false;\n for (const p of obj.apiExcludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Config Validation\n// ---------------------------------------------------------------------------\n\nconst API_KEY_PATTERN = /^tr[a-z]*_[a-z]+_[a-zA-Z0-9]+$/;\n\nconst UPLOAD_STRATEGIES = new Set<string>(['batch', 'realtime', 'both']);\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0']);\n\nexport function isValidApiKeyFormat(value: unknown): value is string {\n return typeof value === 'string' && API_KEY_PATTERN.test(value);\n}\n\nexport function isValidUploadStrategy(value: unknown): value is UploadStrategy {\n return typeof value === 'string' && UPLOAD_STRATEGIES.has(value);\n}\n\nexport function isValidEndpointUrl(value: unknown): boolean {\n if (typeof value !== 'string') return false;\n try {\n const url = new URL(value);\n if (url.protocol === 'https:') return true;\n if (url.protocol === 'http:' && LOCALHOST_HOSTS.has(url.hostname)) return true;\n return false;\n } catch {\n return false;\n }\n}\n\nexport function isValidCloudConfig(input: unknown): input is CloudConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.apiKey !== undefined && obj.apiKey !== null) {\n if (typeof obj.apiKey !== 'string') return false;\n if (obj.apiKey.length > 0 && !isValidApiKeyFormat(obj.apiKey)) return false;\n }\n\n if (obj.endpoint !== undefined) {\n if (!isValidEndpointUrl(obj.endpoint)) return false;\n }\n\n if (obj.uploadStrategy !== undefined) {\n if (!isValidUploadStrategy(obj.uploadStrategy)) return false;\n }\n\n if (obj.timeout !== undefined) {\n if (typeof obj.timeout !== 'number' || obj.timeout < 1000 || obj.timeout > 120000) return false;\n }\n\n if (obj.projectName !== undefined && obj.projectName !== null && typeof obj.projectName !== 'string') return false;\n\n if (obj.queueMaxAge !== undefined) {\n if (typeof obj.queueMaxAge !== 'number' || obj.queueMaxAge < 3600000 || obj.queueMaxAge > 2592000000) return false;\n }\n\n if (obj.queueDirectory !== undefined) {\n if (typeof obj.queueDirectory !== 'string') return false;\n if (obj.queueDirectory.startsWith('/') || obj.queueDirectory.includes('..')) return false;\n }\n\n return true;\n}\n\nexport function isValidQueueEntry(input: unknown): input is QueueEntry {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (typeof obj.version !== 'string') return false;\n if (typeof obj.queuedAt !== 'string') return false;\n if (typeof obj.reason !== 'string') return false;\n if (typeof obj.retryCount !== 'number') return false;\n if (typeof obj.targetEndpoint !== 'string') return false;\n if (typeof obj.method !== 'string') return false;\n if (typeof obj.payload !== 'object' || obj.payload === null) return false;\n if (typeof obj.headers !== 'object' || obj.headers === null) return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -4,7 +4,7 @@
4
4
  * All interfaces for the JSON output schema, reporter configuration,
5
5
  * and internal fixture-to-reporter communication.
6
6
  *
7
- * Schema Version: 1.7.0
7
+ * Schema Version: 1.8.0
8
8
  */
9
9
  type NavigationType = 'goto' | 'navigation' | 'back' | 'forward' | 'refresh' | 'spa_route' | 'spa_replace' | 'hash_change' | 'link_click' | 'form_submit' | 'redirect' | 'popstate' | 'page_load' | 'manual_record' | 'fallback' | 'dummy';
10
10
  interface TestRunReport {
@@ -887,6 +887,17 @@ interface TestIndexEntry {
887
887
  readonly filePath: string;
888
888
  /** Test outcome */
889
889
  readonly status: TestStatus;
890
+ /**
891
+ * The status Playwright expected: `test.fail()` expects 'failed', and
892
+ * `test.skip()` / `test.fixme()` expect 'skipped'. Absent means 'passed'.
893
+ *
894
+ * `status` keeps its meaning: the actual result, or 'flaky' / 'skipped'
895
+ * from Playwright's outcome. A reader derives the verdict from the pair the
896
+ * way Playwright does: a result is expected only when actual === expected.
897
+ * A `test.fail()` that failed is therefore a pass, and one that passed (or
898
+ * timed out) is a failure.
899
+ */
900
+ readonly expectedStatus?: TestStatus;
890
901
  /** Test duration in milliseconds */
891
902
  readonly duration: number;
892
903
  /** Playwright project name */
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * All interfaces for the JSON output schema, reporter configuration,
5
5
  * and internal fixture-to-reporter communication.
6
6
  *
7
- * Schema Version: 1.7.0
7
+ * Schema Version: 1.8.0
8
8
  */
9
9
  type NavigationType = 'goto' | 'navigation' | 'back' | 'forward' | 'refresh' | 'spa_route' | 'spa_replace' | 'hash_change' | 'link_click' | 'form_submit' | 'redirect' | 'popstate' | 'page_load' | 'manual_record' | 'fallback' | 'dummy';
10
10
  interface TestRunReport {
@@ -887,6 +887,17 @@ interface TestIndexEntry {
887
887
  readonly filePath: string;
888
888
  /** Test outcome */
889
889
  readonly status: TestStatus;
890
+ /**
891
+ * The status Playwright expected: `test.fail()` expects 'failed', and
892
+ * `test.skip()` / `test.fixme()` expect 'skipped'. Absent means 'passed'.
893
+ *
894
+ * `status` keeps its meaning: the actual result, or 'flaky' / 'skipped'
895
+ * from Playwright's outcome. A reader derives the verdict from the pair the
896
+ * way Playwright does: a result is expected only when actual === expected.
897
+ * A `test.fail()` that failed is therefore a pass, and one that passed (or
898
+ * timed out) is a failure.
899
+ */
900
+ readonly expectedStatus?: TestStatus;
890
901
  /** Test duration in milliseconds */
891
902
  readonly duration: number;
892
903
  /** Playwright project name */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["/**\n * @testrelic/core — Shared type definitions\n *\n * All interfaces for the JSON output schema, reporter configuration,\n * and internal fixture-to-reporter communication.\n *\n * Schema Version: 1.7.0\n */\n\n// ---------------------------------------------------------------------------\n// Navigation Types\n// ---------------------------------------------------------------------------\n\nexport type NavigationType =\n | 'goto'\n | 'navigation'\n | 'back'\n | 'forward'\n | 'refresh'\n | 'spa_route'\n | 'spa_replace'\n | 'hash_change'\n | 'link_click'\n | 'form_submit'\n | 'redirect'\n | 'popstate'\n | 'page_load'\n | 'manual_record'\n | 'fallback'\n | 'dummy';\n\n// ---------------------------------------------------------------------------\n// Output Schema Types (JSON report structure)\n// ---------------------------------------------------------------------------\n\nexport interface TestRunReport {\n readonly schemaVersion: string;\n readonly testRunId: string;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly totalDuration: number;\n readonly summary: Summary;\n readonly ci: CIMetadata | null;\n readonly metadata: Record<string, unknown> | null;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly shardRunIds: string[] | null;\n}\n\n/** Breakdown of API calls by HTTP response status range. */\nexport interface ApiCallsByStatusRange {\n readonly '2xx': number;\n readonly '3xx': number;\n readonly '4xx': number;\n readonly '5xx': number;\n readonly error: number;\n}\n\n/** Statistical distribution of API call response times in milliseconds. */\nexport interface ApiResponseTimeStats {\n readonly avg: number;\n readonly min: number;\n readonly max: number;\n readonly p50: number;\n readonly p95: number;\n readonly p99: number;\n}\n\nexport interface Summary {\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedout: number;\n readonly totalApiCalls: number;\n readonly uniqueApiUrls: number;\n readonly apiCallsByMethod: Record<string, number>;\n readonly apiCallsByStatusRange: ApiCallsByStatusRange;\n readonly apiResponseTime: ApiResponseTimeStats | null;\n readonly totalAssertions: number;\n readonly passedAssertions: number;\n readonly failedAssertions: number;\n readonly totalNavigations: number;\n readonly uniqueNavigationUrls: number;\n readonly totalTimelineSteps: number;\n readonly totalActionSteps: number;\n readonly actionStepsByCategory: Record<string, number>;\n /** Visual baseline comparisons across the run. */\n readonly visualChecks?: number;\n /** Of those, how many differed from their baseline. */\n readonly visualFailures?: number;\n /** Of those, how many had no baseline and wrote one. */\n readonly visualNew?: number;\n /** DOM elements that differed across the run's visual comparisons. */\n readonly visualDomChanges?: number;\n /** Action steps across the run, bucketed by the driver's own category. */\n readonly actionStepsByKind?: Record<string, number>;\n /** Device-log lines captured across the run (mobile). */\n readonly deviceLogLines?: number;\n /** Of those, how many were error or fatal. */\n readonly deviceLogErrors?: number;\n /** Of those, how many came from a crash log. */\n readonly deviceLogCrashes?: number;\n /** Screenshots captured across the run. */\n readonly screenshotCount?: number;\n /** Screen recordings captured across the run. */\n readonly videoCount?: number;\n /** AI-detected defects across the run, by severity. */\n readonly aiDefectsBySeverity?: Record<string, number>;\n /** Distinct devices the run touched. */\n readonly devices?: readonly DeviceInfo[];\n}\n\nexport interface CIMetadata {\n readonly provider: CIProvider;\n readonly buildId: string | null;\n readonly commitSha: string | null;\n readonly branch: string | null;\n readonly runUrl: string | null;\n}\n\nexport type CIProvider =\n | 'github-actions'\n | 'gitlab-ci'\n | 'jenkins'\n | 'circleci'\n | 'bitbucket-pipelines'\n | 'unknown';\n\nexport interface TimelineEntry {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly visitedAt: string;\n readonly duration: number;\n readonly specFile: string;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly tests: TestResult[];\n}\n\nexport interface TestResult {\n readonly title: string;\n readonly status: TestStatus;\n readonly duration: number;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly retryCount: number;\n readonly tags: string[];\n readonly failure: FailureDiagnostic | null;\n readonly testId: string;\n readonly filePath: string;\n readonly suiteName: string;\n readonly testType: TestType;\n readonly isFlaky: boolean;\n readonly retryStatus: string | null;\n readonly expectedStatus: TestStatus;\n readonly actualStatus: TestStatus;\n readonly artifacts: TestArtifacts | null;\n readonly networkRequests: CapturedNetworkRequest[] | null;\n readonly apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n readonly actions: readonly ActionStep[] | null;\n readonly consoleLogs: readonly ConsoleLogEntry[] | null;\n /** Visual baseline comparisons recorded during this test, if any. */\n readonly visual?: readonly VisualComparison[] | null;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\nexport type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';\n\nexport interface CapturedNetworkRequest {\n readonly url: string;\n readonly method: string;\n readonly resourceType: ResourceType;\n readonly statusCode: number;\n readonly responseTimeMs: number;\n readonly startedAt: string;\n readonly requestHeaders: Record<string, string> | null;\n readonly requestBody: string | null;\n readonly responseBody: string | null;\n readonly responseHeaders: Record<string, string> | null;\n readonly contentType: string | null;\n readonly responseSize: number;\n readonly requestBodyTruncated: boolean;\n readonly responseBodyTruncated: boolean;\n readonly isBinary: boolean;\n readonly error: string | null;\n}\n\nexport interface TestArtifacts {\n readonly screenshot?: string;\n /**\n * Every screenshot captured for this test, report-relative.\n *\n * `screenshot` is the first one and stays for compatibility. Mobile\n * collectors have always gathered an array — one per step, or one per\n * failure — and only index 0 survived: the artifact copier wrote a single\n * `screenshot.png` and dropped the rest before the report could ask.\n */\n readonly screenshots?: readonly string[];\n readonly video?: string;\n /**\n * Playwright's trace archive — the DOM snapshots, network log and screencast\n * frames a failure is actually debugged from.\n *\n * Its absence here went all the way down the stack: with no field to fill,\n * the collector had nothing to look for, so a project configured with\n * `trace: 'on'` wrote `trace.zip` to disk and shipped only the picture and\n * the video. The gap bit hardest exactly where it mattered most — a failure\n * caught in a hook leaves no screenshot worth having, so those runs reached\n * the cloud with a video and nothing anyone could step through.\n */\n readonly trace?: string;\n readonly screenshotKey?: string;\n readonly videoKey?: string;\n readonly traceKey?: string;\n}\n\n/**\n * Outcome of a single visual baseline comparison.\n *\n * `new` is a snapshot Playwright had no baseline for and therefore wrote one\n * for — it is not a pass, because nothing was actually compared. `updated`\n * means the run was invoked with `--update-snapshots` and the baseline was\n * rewritten, which likewise compares nothing.\n */\nexport type VisualStatus = 'passed' | 'failed' | 'new' | 'updated';\n\n/** Which assertion produced a comparison. */\nexport type VisualSource = 'toHaveScreenshot' | 'toMatchSnapshot' | 'toMatchVisualBaseline';\n\n/**\n * One visual baseline comparison within a test.\n *\n * A test may hold many of these — `TestArtifacts` deliberately is not the home\n * for them, because it is single-slot per type (one screenshot, one video, one\n * trace) while a single test can assert against any number of named snapshots.\n *\n * Every image field is a report-relative POSIX path, matching `TestArtifacts`.\n * A passing comparison carries no `actual` or `diff`: Playwright attaches\n * nothing when the images match, so there is nothing to copy.\n */\nexport interface VisualComparison {\n /** Snapshot identity within the test, e.g. `home` or `checkout/cart`. */\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n /** The committed baseline image. */\n readonly baseline?: string;\n /**\n * Where the committed baseline lives, relative to the Playwright config\n * file's directory (where `test-results` sits), POSIX separators — the\n * file a reviewer overwrites to accept this run.\n *\n * Deliberately not the artifact copy in `baseline`: that path is relative to\n * the report and points at a per-run duplicate, so a tool that \"accepted\" a\n * run by writing there would leave the baseline Playwright actually reads\n * untouched and the failure would come straight back.\n */\n readonly baselinePath?: string;\n /** What this run rendered. Absent when the comparison passed. */\n readonly actual?: string;\n /** Playwright's rendered diff. Present only on failure. */\n readonly diff?: string;\n /** The previous attempt's render, on a retried failure. */\n readonly previous?: string;\n /** Baseline dimensions, read from the PNG header. */\n readonly width?: number;\n readonly height?: number;\n /** Actual dimensions — set only when they differ from the baseline. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** Differing pixel count, as reported by Playwright's comparator. */\n readonly diffPixels?: number;\n /** `diffPixels / (width * height)`, computed at full precision. */\n readonly diffPixelRatio?: number;\n /** Cloud storage keys, populated after artifact upload. */\n readonly baselineKey?: string;\n readonly actualKey?: string;\n readonly diffKey?: string;\n /**\n * Which elements changed, ranked by the area they affect.\n *\n * Present only when a DOM baseline existed to compare against. Capped — see\n * `domChangeCount` for the number found before the cap.\n */\n readonly domChanges?: readonly DomChange[];\n /** Total changes found, which may exceed `domChanges.length`. */\n readonly domChangeCount?: number;\n /**\n * Of those, how many actually changed rather than merely shifting position.\n *\n * The gap between this and `domChangeCount` is the layout knock-on: one card\n * gaining a margin moves everything below it, and a report that presents all\n * of those as equals hides the single edit that caused them.\n */\n readonly domRootCauses?: number;\n /** True when the DOM capture itself hit a node or byte cap. */\n readonly domTruncated?: boolean;\n /** Report-relative paths to the two captured DOM snapshots. */\n readonly baselineDom?: string;\n readonly actualDom?: string;\n /**\n * The document-space region the screenshot covered: `[x, y, width, height]`.\n *\n * Element rectangles are in document coordinates, so the report needs this to\n * place a box over the image — for a locator screenshot the image covers only\n * that element's box, not the viewport.\n */\n readonly domCaptureBox?: readonly [number, number, number, number];\n /** Cloud storage keys for the DOM snapshots. */\n readonly baselineDomKey?: string;\n readonly actualDomKey?: string;\n}\n\n/** How an element differs between the baseline render and this one. */\nexport type DomChangeKind =\n | 'added'\n | 'removed'\n | 'moved'\n | 'resized'\n | 'restyled'\n | 'text'\n /** Its id or class list changed — often the real story behind a restyle. */\n | 'attrs';\n\n/** One computed style property that differs. */\nexport interface DomStyleChange {\n readonly prop: string;\n readonly from: string;\n readonly to: string;\n}\n\n/**\n * One element that differs between two renders.\n *\n * Deliberately shaped like the platform's existing `RegionEvidence` — rank,\n * bbox, area share, a kind and a human label — so that a DOM overlay becomes\n * another caller of the dashboard's existing box-overlay component rather than\n * a second renderer built alongside it.\n */\nexport interface DomChange {\n /** 1-based, ordered by affected area descending. Drives the overlay colour. */\n readonly rank: number;\n /** Structural path used to match the element across the two snapshots. */\n readonly path: string;\n /** What a human reads, e.g. `div.card.accent`. */\n readonly label: string;\n readonly kinds: readonly DomChangeKind[];\n /** Document-space `[x, y, width, height]` in this render. */\n readonly rect?: readonly [number, number, number, number];\n /** The same, in the baseline. Absent for an added element. */\n readonly baselineRect?: readonly [number, number, number, number];\n /** Share of the capture box this element occupies, 0–1. */\n readonly areaPct: number;\n readonly styles?: readonly DomStyleChange[];\n readonly text?: { readonly from: string; readonly to: string };\n /** Identity attributes that changed, e.g. `class: \"card\" → \"banner\"`. */\n readonly attrs?: readonly DomStyleChange[];\n /**\n * True when this element only moved or resized, in a comparison where\n * something else actually changed.\n *\n * Shifting one card down by 24px shifts everything below it too, so a naive\n * list reports eleven changes for one edit. This flags the ten that merely\n * got pushed around, so a report can lead with the one that did something.\n *\n * It is a statement about this element — that nothing about it changed except\n * its position — and deliberately not a claim to have proven what moved it.\n */\n readonly consequence?: boolean;\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';\n\nexport type TestType = 'e2e' | 'api' | 'unit' | 'mobile' | 'unknown';\n\n// ---------------------------------------------------------------------------\n// Mobile Device Types\n// ---------------------------------------------------------------------------\n\n/** Platform a mobile test drove. */\nexport type MobilePlatform = 'android' | 'ios' | 'web' | 'unknown';\n\n/** How the app under test was driven. */\nexport type AppTestMode = 'native' | 'hybrid' | 'mobile-web';\n\n/**\n * The device a mobile test ran on.\n *\n * Appium detects all of this from the W3C capabilities and Maestro reads it\n * from the CLI flags and driver log. Both used to collapse it to two words in\n * `tags` before the report saw it; this carries it through intact.\n */\nexport interface DeviceInfo {\n readonly platform: MobilePlatform;\n /** OS version, e.g. \"14\" or \"17.5\". */\n readonly platformVersion: string | null;\n /** Human-facing device name, e.g. \"Pixel 4a\" or \"iPhone 15\". */\n readonly deviceName: string | null;\n /** Driver, e.g. \"UiAutomator2\", \"XCUITest\", \"maestro\". */\n readonly automationName: string | null;\n /** App package / bundle id / apk path under test. */\n readonly appId: string | null;\n /** Native, hybrid or mobile-web. Null when the runner cannot tell. */\n readonly appTestMode: AppTestMode | null;\n /** Device serial or UDID, e.g. \"emulator-5554\". */\n readonly udid: string | null;\n}\n\n/** Severity of an AI-detected defect. */\nexport type AiDefectSeverity = 'critical' | 'warning' | 'info';\n\n/** Category of an AI-detected defect. */\nexport type AiDefectType = 'ui' | 'spelling' | 'i18n' | 'layout' | 'accessibility';\n\n/** A defect reported by an AI analysis pass over a captured screen. */\nexport interface AiDefect {\n readonly type: AiDefectType;\n readonly severity: AiDefectSeverity;\n readonly description: string;\n /** Where in the app it was seen, when the analyser says. */\n readonly location: string | null;\n /** Report-relative path to the screenshot it was found on. */\n readonly screenshot: string | null;\n}\n\nexport interface FailureDiagnostic {\n readonly message: string;\n readonly line: number | null;\n readonly code: string | null;\n readonly stack: string | null;\n}\n\nexport interface NetworkStats {\n readonly totalRequests: number;\n readonly failedRequests: number;\n readonly failedRequestUrls: string[];\n readonly totalBytes: number;\n readonly byType: ResourceBreakdown;\n}\n\nexport interface ResourceBreakdown {\n readonly xhr: number;\n readonly document: number;\n readonly script: number;\n readonly stylesheet: number;\n readonly image: number;\n readonly font: number;\n readonly other: number;\n}\n\n// ---------------------------------------------------------------------------\n// API Call Record (captured from Playwright APIRequestContext proxy)\n// ---------------------------------------------------------------------------\n\n/**\n * A cookie surfaced for the API-workspace Cookies tab. Values are MASKED by\n * default (the SDK never uploads raw session tokens to the cloud); the name and\n * any non-secret attributes (Path/Domain/Secure/HttpOnly/SameSite) are kept so\n * the inspector can show which cookies were sent/set, Postman-style.\n */\nexport interface CapturedCookie {\n readonly name: string;\n /** Masked value (e.g. \"••••\"). Never the raw cookie value. */\n readonly value: string;\n /** Response-cookie attributes joined for display (Set-Cookie only). */\n readonly attributes?: string;\n}\n\nexport interface ApiCallRecord {\n /** Unique call identifier within the test (e.g., \"api-call-0\") */\n readonly id: string;\n /** ISO 8601 timestamp when the call was initiated */\n readonly timestamp: string;\n /** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */\n readonly method: string;\n /** Full resolved URL (after baseURL resolution by Playwright) */\n readonly url: string;\n /** All request headers sent, or null if unavailable */\n readonly requestHeaders: Record<string, string> | null;\n /** Request payload as string (JSON-serialized for objects). Null for bodiless methods */\n readonly requestBody: string | null;\n /** HTTP response status code, or null if request errored before response */\n readonly responseStatusCode: number | null;\n /** HTTP response status text, or null if request errored before response */\n readonly responseStatusText: string | null;\n /** All response headers received, or null if request errored before response */\n readonly responseHeaders: Record<string, string> | null;\n /** Response body: string for text/JSON, base64 for binary. Null on error */\n readonly responseBody: string | null;\n /** Request cookies parsed from the `Cookie` header (values masked). Optional/null when none. */\n readonly requestCookies?: readonly CapturedCookie[] | null;\n /** Response cookies parsed from `Set-Cookie` headers (values masked). Optional/null when none. */\n readonly responseCookies?: readonly CapturedCookie[] | null;\n /** Duration in milliseconds from request initiation to response received */\n readonly responseTimeMs: number;\n /** Whether the response body is stored as a base64-encoded binary string */\n readonly isBinary: boolean;\n /** Error message if the call failed (network error, timeout), null on success */\n readonly error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// API Assertion Types (captured from assertion tracking)\n// ---------------------------------------------------------------------------\n\n/** Categorization of the assertion being made. */\nexport type AssertionType =\n | 'status'\n | 'statusOk'\n | 'header'\n | 'bodyField'\n | 'bodyMatch'\n | 'bodyContains'\n | 'custom';\n\n/** Source location where an assertion was made in the test file. */\nexport interface AssertionLocation {\n /** Absolute or relative file path to the test file */\n readonly file: string;\n /** Line number (1-based) */\n readonly line: number;\n /** Column number (1-based), if available */\n readonly column?: number;\n}\n\n/** A failed assertion reduced to what a summary row needs. */\nexport interface FailedAssertionSummary {\n readonly type: AssertionType;\n readonly expected: string;\n readonly actual: string;\n readonly expression?: string;\n}\n\n/** A single captured assertion on API response data. */\nexport interface ApiAssertion {\n /** Links to ApiCallRecord.id from the API proxy */\n readonly callId: string;\n /** Category of assertion */\n readonly type: AssertionType;\n /** What the test expected */\n readonly expected: unknown;\n /** What was actually received */\n readonly actual: unknown;\n /** Whether the assertion passed or failed */\n readonly status: 'passed' | 'failed';\n /** Source location of the assertion in the test file */\n readonly location: AssertionLocation;\n /** String representation of the assertion expression (best-effort) */\n readonly expression?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Unified Timeline Types (navigation + API call steps)\n// ---------------------------------------------------------------------------\n\n/** Test identity block attached to every timeline step. */\nexport interface StepTestIdentity {\n readonly title: string;\n readonly fullTitle: readonly string[];\n readonly status: TestStatus;\n readonly duration: number;\n readonly retries: number;\n readonly retry: number;\n readonly tags: readonly string[];\n readonly failure: FailureDiagnostic | null;\n}\n\n/** Request details within an API call step. */\nexport interface ApiCallStepRequest {\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** Response details within an API call step. */\nexport interface ApiCallStepResponse {\n readonly statusCode: number;\n readonly statusText: string;\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** An assertion linked to an API call step (callId omitted — implicit from parent). */\nexport interface StepAssertion {\n readonly type: AssertionType;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly status: 'passed' | 'failed';\n readonly location: AssertionLocation;\n readonly expression?: string;\n}\n\n/** A browser navigation event in the unified timeline. */\nexport interface NavigationStep {\n readonly index: number;\n readonly type: 'navigation';\n readonly url: string;\n readonly timestamp: string;\n readonly durationOnUrl: number;\n readonly navigationType: NavigationType;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** An API request event in the unified timeline. */\nexport interface ApiCallStep {\n readonly index: number;\n readonly type: 'api_call';\n readonly callId: string;\n readonly method: string;\n readonly url: string;\n readonly timestamp: string;\n readonly responseTime: number | null;\n readonly request: ApiCallStepRequest;\n readonly response: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions: readonly StepAssertion[];\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** Discriminated union of all timeline step types. */\nexport type TimelineStep = NavigationStep | ApiCallStep;\n\n// ---------------------------------------------------------------------------\n// Action Step Types (Playwright test step capture)\n// ---------------------------------------------------------------------------\n\n/**\n * Categorized type of a captured test action.\n *\n * `navigation` covers app-level navigation events that aren't UI actions —\n * Maestro `launchApp` / `openLink` / `back`, Appium activity transitions, and\n * Playwright top-level page navigations. The platform's timeline UI and Ask\n * AI tools (`query_test_steps`'s `categoryFilter`) treat navigation as a\n * distinct bucket from generic `custom_step`.\n */\nexport type ActionCategory = 'ui_action' | 'assertion' | 'navigation' | 'custom_step';\n\n/**\n * The driver's own step category, preserved alongside `ActionCategory`.\n *\n * `ActionCategory` is the four-bucket vocabulary the platform's timeline and\n * Ask AI tools filter on, and it stays closed. Mobile drivers distinguish far\n * more than four kinds — Appium reports eleven, Maestro ten — and flattening\n * them made a swipe, an app launch and a screenshot render identically. This\n * keeps the finer label for the report to badge.\n */\nexport type StepKind =\n | 'find'\n | 'interaction'\n | 'gesture'\n | 'navigation'\n | 'lifecycle'\n | 'context'\n | 'device'\n | 'keyboard'\n | 'alert'\n | 'screenshot'\n | 'assertion'\n | 'media'\n | 'script'\n | 'flow_control'\n | 'ai'\n | 'other';\n\n/** A single captured test action step. */\nexport interface ActionStep {\n readonly title: string;\n readonly category: ActionCategory;\n /** The driver's own finer-grained category, when it reported one. */\n readonly kind?: StepKind | null;\n /** Element selector this step acted on, when the driver reported one. */\n readonly selector?: string | null;\n readonly timestamp: string;\n readonly duration: number;\n readonly videoOffset: number | null;\n readonly status: 'passed' | 'failed';\n readonly error: string | null;\n readonly children: readonly ActionStep[];\n}\n\n// ---------------------------------------------------------------------------\n// Console Log Types (browser console + terminal stdout/stderr)\n// ---------------------------------------------------------------------------\n\n/** Level of a captured console/terminal log message. */\nexport type ConsoleLogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' | 'stdout' | 'stderr';\n\n/** A single captured console or terminal log entry. */\n/**\n * Where a captured log line came from.\n *\n * Browser consoles only ever produce `console`. Mobile runs mix several\n * streams, and telling a crash log from ordinary logcat chatter is the whole\n * point of showing them — so the stream survives to the row.\n */\nexport type LogSource =\n | 'console'\n | 'logcat'\n | 'syslog'\n | 'crashlog'\n | 'browser'\n | 'safariConsole'\n | 'performance'\n | 'server'\n | 'bidi'\n | 'driver';\n\nexport interface ConsoleLogEntry {\n /** Log level or stream type */\n readonly level: ConsoleLogLevel;\n /** Text content of the log message */\n readonly text: string;\n /** ISO 8601 timestamp when the log was captured */\n readonly timestamp: string;\n /** Source file location (browser console only, best-effort) */\n readonly location: string | null;\n /** Which stream produced the line. Defaults to `console` when absent. */\n readonly source?: LogSource | null;\n /** Emitting tag, e.g. an Android logcat tag like \"ActivityManager\". */\n readonly tag?: string | null;\n /** Emitting process id, when the stream reports one. */\n readonly pid?: number | null;\n /** Stack trace attached to the line (crash logs). */\n readonly stackTrace?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration Types (reporter tuple options)\n// ---------------------------------------------------------------------------\n\n/** Report generation mode. */\nexport type ReportMode = 'streaming' | 'embedded' | 'auto';\n\nexport interface ReporterConfig {\n readonly outputPath?: string;\n readonly includeStackTrace?: boolean;\n readonly includeCodeSnippets?: boolean;\n readonly codeContextLines?: number;\n readonly includeNetworkStats?: boolean;\n /** Master on/off for Test Navigation capture. Default: true. When false the\n * reporter drops navigation data before upload and logs a one-time notice. */\n readonly captureNavigation?: boolean;\n readonly navigationTypes?: NavigationType[] | null;\n readonly redactPatterns?: (string | RegExp)[];\n readonly testRunId?: string | null;\n readonly metadata?: Record<string, unknown> | null;\n readonly openReport?: boolean;\n readonly htmlReportPath?: string;\n readonly includeArtifacts?: boolean;\n /** When false, disables API call interception in the unified fixture. Default: true */\n readonly trackApiCalls?: boolean;\n /** When true, suppresses console summary output. Default: false */\n readonly quiet?: boolean;\n /** When true, captures Playwright test steps (actions). Default: true */\n readonly includeActionSteps?: boolean;\n /** Capture request headers for API calls. Default: true */\n readonly captureRequestHeaders?: boolean;\n /** Capture response headers for API calls. Default: true */\n readonly captureResponseHeaders?: boolean;\n /** Capture request body for API calls. Default: true */\n readonly captureRequestBody?: boolean;\n /** Capture response body for API calls. Default: true */\n readonly captureResponseBody?: boolean;\n /** Capture API assertions (both pass and fail). Default: true */\n readonly captureAssertions?: boolean;\n /**\n * Header names whose values are replaced with \"[REDACTED]\".\n * Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']\n */\n readonly redactHeaders?: string[];\n /**\n * Body field names whose values are replaced with \"[REDACTED]\" at any depth.\n * Default: ['password', 'secret', 'token', 'apiKey', 'api_key']\n */\n readonly redactBodyFields?: string[];\n /**\n * Only track API calls matching these URL patterns.\n * Default: undefined (track all)\n */\n readonly apiIncludeUrls?: (string | RegExp)[];\n /**\n * Exclude API calls matching these URL patterns. Takes precedence over include.\n * Default: undefined (exclude none)\n */\n readonly apiExcludeUrls?: (string | RegExp)[];\n /** Capture browser console logs (E2E) and terminal output (API). Default: true */\n readonly captureConsoleLogs?: boolean;\n /** Cloud integration configuration. Default: undefined (local mode) */\n readonly cloud?: CloudReporterOptions;\n /**\n * Report generation mode.\n * - `'embedded'`: All data embedded in a single HTML file (current behavior)\n * - `'streaming'`: Data streamed to disk per-test, served via local server\n * - `'auto'`: Use embedded for < streamingThreshold tests, streaming otherwise\n * Default: `'auto'`\n */\n readonly reportMode?: ReportMode;\n /**\n * Test count threshold for auto mode. When the suite has >= this many tests,\n * streaming mode is used. Default: 500\n */\n readonly streamingThreshold?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types (fixture → reporter communication)\n// ---------------------------------------------------------------------------\n\nexport interface NavigationAnnotation {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly timestamp: string;\n readonly domContentLoadedAt?: string;\n readonly networkIdleAt?: string;\n readonly networkStats?: NetworkStats;\n}\n\n// ---------------------------------------------------------------------------\n// Worker-Safe Data Payload (worker → reporter communication)\n// ---------------------------------------------------------------------------\n\n/** Consolidated data payload sent from a worker fixture to the reporter. */\nexport interface TestRelicDataPayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version (semver). */\n readonly version: string;\n /** Navigation events collected during the test. */\n readonly navigations: readonly NavigationAnnotation[];\n /** Captured network requests during the test. */\n readonly networkRequests: readonly CapturedNetworkRequest[];\n /** API calls made via APIRequestContext proxy. */\n readonly apiCalls: readonly ApiCallRecord[];\n /** Assertions captured on API responses. */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Console/terminal logs captured during the test. Optional for backward compat. */\n readonly consoleLogs?: readonly ConsoleLogEntry[];\n}\n\n/** Current payload format version. */\nexport const PAYLOAD_VERSION = '2.0.0';\n\n/** Legacy payload version (pre-streaming). */\nexport const PAYLOAD_VERSION_LEGACY = '1.0.0';\n\n/** Attachment name used for the consolidated payload. */\nexport const ATTACHMENT_NAME = 'testrelic-data';\n\n/** Attachment content type. */\nexport const ATTACHMENT_CONTENT_TYPE = 'application/json';\n\n/**\n * File-based data payload (v2.0.0).\n *\n * Heavy data (network requests, console logs, API calls) is written to\n * JSONL files on disk and referenced by path. Only lightweight data\n * (navigations, assertions) is kept inline.\n */\nexport interface TestRelicFilePayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version — `\"2.0.0\"` for file-based payloads. */\n readonly version: '2.0.0';\n /** Navigation events (small, kept inline). */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertions (small, kept inline). */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Absolute path to JSONL file containing CapturedNetworkRequest items. */\n readonly networkRequestsFile: string | null;\n /** Number of network request items in the file. */\n readonly networkRequestsCount: number;\n /** Absolute path to JSONL file containing ConsoleLogEntry items. */\n readonly consoleLogsFile: string | null;\n /** Number of console log items in the file. */\n readonly consoleLogsCount: number;\n /** Absolute path to JSONL file containing ApiCallRecord items. */\n readonly apiCallsFile: string | null;\n /** Number of API call items in the file. */\n readonly apiCallsCount: number;\n /**\n * Response-status mix of the calls in the file.\n *\n * Tallied as each record is appended. The file itself is never read back\n * into memory in streaming mode, so an aggregate computed at write time is\n * the only way the grid can show a row's status chips.\n */\n readonly apiStatusMix?: ApiCallsByStatusRange | null;\n}\n\n/** Type guard: validates that a parsed object is a v2 file-based payload. */\nexport function isTestRelicFilePayload(obj: unknown): obj is TestRelicFilePayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n record.version === '2.0.0' &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n/** Type guard: validates that a parsed object is a v1 inline data payload. */\nexport function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n typeof record.version === 'string' &&\n record.version.length > 0 &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.networkRequests) &&\n Array.isArray(record.apiCalls) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Integration Types\n// ---------------------------------------------------------------------------\n\n/** Upload strategy for cloud timeline data. */\nexport type UploadStrategy = 'batch' | 'realtime' | 'both';\n\n/** Current cloud/local operating mode. */\nexport type AuthMode = 'cloud' | 'local' | 'pending';\n\n/** Resolved cloud configuration, immutable after initialization. */\nexport interface CloudConfig {\n readonly apiKey: string | null;\n readonly endpoint: string;\n readonly uploadStrategy: UploadStrategy;\n /** Per-request ceiling. ⚠ It governed only the auth handshake until 2.14.0 —\n * the upload calls used bare `fetch` and inherited undici's 5-minute\n * defaults, so a tester who set this had bounded the token exchange and\n * nothing else. It now bounds the upload requests too. */\n readonly timeout: number;\n /** Total time the whole upload phase may take before it gives up, writes the\n * queue and lets the process exit. The phase runs inside Playwright's\n * `onEnd`, so this is the difference between a late run and a run that never\n * finishes. Default 120s.\n *\n * ⛔ OPTIONAL on purpose. `CloudConfig` is built by every analytics package\n * (appium, maestro, playwright), and making a new field required breaks all\n * of them at once for a knob only one upload path reads — CI caught exactly\n * that. Callers resolve it as `?? DEFAULT_UPLOAD_BUDGET_MS`. */\n readonly uploadBudgetMs?: number;\n readonly projectName: string | null;\n readonly queueMaxAge: number;\n readonly queueDirectory: string;\n readonly uploadArtifacts: boolean;\n readonly artifactMaxSizeMb: number;\n}\n\n/** Mutable auth state tracked by the cloud client. */\nexport interface AuthState {\n mode: AuthMode;\n accessToken: string | null;\n refreshToken: string | null;\n expiresAt: number | null;\n orgId: string | null;\n orgName: string | null;\n userId: string | null;\n userName: string | null;\n}\n\n/** Auto-collected git information. All fields nullable. */\nexport interface GitMetadata {\n readonly branch: string | null;\n readonly commitSha: string | null;\n readonly commitMessage: string | null;\n readonly commitAuthor: string | null;\n readonly remoteUrl: string | null;\n}\n\n/** Cached repo identity. */\nexport interface RepoResolution {\n readonly repoId: string;\n readonly gitId: string;\n readonly displayName: string;\n readonly resolvedAt: number;\n readonly apiKeyHash: string;\n}\n\n/** A single queued upload payload. */\nexport interface QueueEntry {\n readonly version: string;\n readonly queuedAt: string;\n readonly reason: string;\n readonly retryCount: number;\n readonly targetEndpoint: string;\n readonly method: string;\n readonly payload: Record<string, unknown>;\n readonly headers: Record<string, string>;\n}\n\n/** Inline cloud configuration accepted via reporter options. */\nexport interface CloudReporterOptions {\n readonly apiKey?: string;\n readonly endpoint?: string;\n readonly upload?: UploadStrategy;\n readonly timeout?: number;\n /** Total budget for the upload phase (ms). See CloudConfig.uploadBudgetMs. */\n readonly uploadBudgetMs?: number;\n readonly projectName?: string;\n readonly queueMaxAge?: string;\n readonly queueDirectory?: string;\n readonly uploadArtifacts?: boolean;\n readonly artifactMaxSizeMb?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Merge CLI Types\n// ---------------------------------------------------------------------------\n\nexport interface MergeOptions {\n readonly output: string;\n readonly testRunId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact Management Types\n// ---------------------------------------------------------------------------\n\n/** A single artifact file within a test's artifact folder. */\nexport interface ArtifactFile {\n /** File name (e.g., `screenshot.png`, `video.webm`, `trace.zip`) */\n readonly name: string;\n /** Artifact type for rendering */\n readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';\n /** Path relative to the report output directory */\n readonly relativePath: string;\n /** File size in bytes */\n readonly sizeBytes: number;\n}\n\n/** A single test's artifacts within a run folder. */\nexport interface ArtifactTestEntry {\n /** Sanitized test folder name */\n readonly testName: string;\n /** List of artifact files for this test */\n readonly files: readonly ArtifactFile[];\n}\n\n/** A single test run's artifact folder in the manifest. */\nexport interface ArtifactRunEntry {\n /** Timestamp-based folder name (e.g., `2026-03-02T14-30-00`) */\n readonly folderName: string;\n /** ISO 8601 timestamp parsed from folder name */\n readonly timestamp: string;\n /** Sum of all file sizes in this run folder */\n readonly totalSizeBytes: number;\n /** Number of test artifact subdirectories */\n readonly testCount: number;\n /** Per-test artifact details */\n readonly tests: readonly ArtifactTestEntry[];\n /** Whether this folder belongs to the current report's run */\n readonly isCurrentRun: boolean;\n}\n\n/** Top-level manifest structure written as `artifact-manifest.json`. */\nexport interface ArtifactRunManifest {\n /** Manifest schema version (starts at `\"1.0\"`) */\n readonly schemaVersion: string;\n /** ISO 8601 timestamp of manifest generation */\n readonly generatedAt: string;\n /** Relative path to artifacts directory from report (e.g., `\"artifacts\"`) */\n readonly artifactBaseDir: string;\n /** Sum of all run folder sizes */\n readonly totalSizeBytes: number;\n /** All discovered run folders, sorted newest first */\n readonly runs: readonly ArtifactRunEntry[];\n /** Port of the artifact management server, or null if not running */\n readonly serverPort: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Streaming Report Types (server-backed reporter)\n// ---------------------------------------------------------------------------\n\n/** Compact index entry for a single test — kept in memory during run. */\nexport interface TestIndexEntry {\n /** Deterministic hash ID (first 12 chars of SHA-256) */\n readonly id: string;\n /** Test title (leaf name) */\n readonly title: string;\n /** Full title path (describe blocks + test name) */\n readonly titlePath: readonly string[];\n /** Relative path to test file */\n readonly filePath: string;\n /** Test outcome */\n readonly status: TestStatus;\n /** Test duration in milliseconds */\n readonly duration: number;\n /** Playwright project name */\n readonly project: string;\n /** Retry attempt number (0 = first run) */\n readonly retryIndex: number;\n /** Test tags */\n readonly tags: readonly string[];\n /** What kind of test this is — badges the row, and picks the row's evidence. */\n readonly testType?: TestType;\n /** Whether this test has network request data */\n readonly hasNetworkData: boolean;\n /** Whether this test has console log data */\n readonly hasConsoleData: boolean;\n /** Whether this test has screenshot/video artifacts */\n readonly hasArtifacts: boolean;\n /** Whether this test has action step data */\n readonly hasActionSteps: boolean;\n /** Number of visual baseline comparisons recorded */\n readonly visualCount?: number;\n /** Of those, how many differed from their baseline */\n readonly visualFailedCount?: number;\n /** DOM elements that differed across this test's comparisons */\n readonly domChangeCount?: number;\n /**\n * Report-relative path to a diff image for this test, when one exists.\n *\n * Carried on the index so the run overview can show the failure itself\n * rather than only describing it — the grid must never fetch per-test\n * detail files to render.\n */\n readonly visualDiffPath?: string;\n /** First line of error message (failed/flaky tests only) */\n readonly errorMessage: string | null;\n /** Number of network requests captured */\n readonly networkCount: number;\n /** Number of console log entries captured */\n readonly consoleCount: number;\n /** Number of action steps captured */\n readonly actionCount: number;\n /** API calls captured for this test. */\n readonly apiCallCount?: number;\n /** Response-status mix of those calls, for the row's status chips. */\n readonly apiStatusMix?: ApiCallsByStatusRange;\n /** API assertions that passed during this test. */\n readonly assertionsPassed?: number;\n /** API assertions that failed during this test. */\n readonly assertionsFailed?: number;\n /**\n * The first failed assertion, flattened to strings.\n *\n * The run overview leads with \"expected 101, received undefined\" and must\n * do so without opening the test's detail file. The first line of an API\n * failure message is the matcher wrapper, which says nothing.\n */\n readonly firstFailedAssertion?: FailedAssertionSummary;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** Action steps for this test, bucketed by the driver's own category. */\n readonly stepKinds?: Record<string, number>;\n /** Device-log errors and fatals recorded during this test. */\n readonly deviceLogErrors?: number;\n /** Whether a crash-log line was recorded during this test. */\n readonly hasCrashLog?: boolean;\n /** Whether a screen recording exists for this test. */\n readonly hasVideo?: boolean;\n /** AI-detected defects attributed to this test. */\n readonly aiDefectCount?: number;\n /** Whether this is a retry attempt (not the final attempt) */\n readonly isRetry: boolean;\n /** Stable key shared by every attempt of the same test (same across retries, unlike `id`) */\n readonly retryGroupKey: string;\n}\n\n/**\n * Full detail data for a single test, stored on disk.\n *\n * In v3 (stream-to-disk), heavy data (network requests, console logs,\n * API calls) is stored in separate JSONL files alongside meta.json.\n * The `has*` and `*Count` fields indicate availability without loading.\n */\nexport interface TestDetailData {\n /** Same as TestIndexEntry.id */\n readonly id: string;\n /** Page navigation events (inline — typically small) */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertion results (inline — typically small) */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Nested action step tree (inline — typically small) */\n readonly actions: readonly ActionStep[];\n /** References to screenshot/video files */\n readonly artifacts: TestArtifacts | null;\n /** Visual baseline comparisons (inline — a handful per test at most) */\n readonly visual?: readonly VisualComparison[] | null;\n /** Error details for failed/flaky tests */\n readonly failureDiagnostic: FailureDiagnostic | null;\n /** Whether network.jsonl exists for this test */\n readonly hasNetworkFile: boolean;\n /** Number of items in network.jsonl */\n readonly networkRequestsCount: number;\n /** Whether console.jsonl exists for this test */\n readonly hasConsoleFile: boolean;\n /** Number of items in console.jsonl */\n readonly consoleLogsCount: number;\n /** Whether api-calls.jsonl exists for this test */\n readonly hasApiCallsFile: boolean;\n /** Number of items in api-calls.jsonl */\n readonly apiCallsCount: number;\n /** Test start time (ISO 8601) — used for video sync */\n readonly startedAt?: string;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\n/** Streaming report summary — running counters written to summary.json. */\nexport interface StreamingReportSummary {\n /** Unique run identifier */\n readonly testRunId: string;\n /** ISO 8601 start timestamp */\n readonly startedAt: string;\n /** ISO 8601 completion timestamp */\n readonly completedAt: string;\n /** Wall-clock duration in ms */\n readonly totalDuration: number;\n /** Total number of test results */\n readonly total: number;\n /** Tests that passed */\n readonly passed: number;\n /** Tests that failed */\n readonly failed: number;\n /** Tests that passed on retry */\n readonly flaky: number;\n /** Tests that were skipped */\n readonly skipped: number;\n /** Tests that timed out */\n readonly timedOut: number;\n /** Tests that were interrupted */\n readonly interrupted: number;\n /** Total API calls across all tests */\n readonly totalApiCalls: number;\n /** Total assertions across all tests */\n readonly totalAssertions: number;\n /** Total page navigations */\n readonly totalNavigations: number;\n /** Total network requests captured */\n readonly totalNetworkRequests: number;\n /** Total console log entries captured */\n readonly totalConsoleLogs: number;\n /** Total action steps captured */\n readonly totalActionSteps: number;\n /** User-provided metadata */\n readonly metadata: Record<string, unknown> | null;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Per-file aggregation */\n readonly files: readonly StreamingFileStats[];\n /** Enriched summary (matches existing Summary interface) */\n readonly enrichedSummary: Summary | null;\n /** Validation cross-check data */\n readonly validation: StreamingValidation | null;\n /** Write errors encountered during streaming */\n readonly writeErrors: readonly StreamingWriteError[];\n}\n\n/** Per-file test aggregation for streaming reports. */\nexport interface StreamingFileStats {\n readonly filePath: string;\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedOut: number;\n}\n\n/** Validation cross-check between reporter counts and Playwright. */\nexport interface StreamingValidation {\n readonly reporterTotal: number;\n readonly indexTotal: number;\n readonly playwrightStatus: string;\n readonly isValid: boolean;\n}\n\n/** A write error that occurred during streaming. */\nexport interface StreamingWriteError {\n readonly testId: string;\n readonly error: string;\n readonly timestamp: string;\n}\n\n/** Top-level manifest for a streaming report directory. */\nexport interface StreamingReportManifest {\n /** Schema version (`\"2.0\"` for streaming format) */\n readonly schemaVersion: string;\n /** ISO 8601 generation timestamp */\n readonly generatedAt: string;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Relative path to summary.json */\n readonly summaryFile: string;\n /** Relative path to index.json */\n readonly indexFile: string;\n /** Relative path to tests/ directory */\n readonly testsDir: string;\n /** Relative path to artifacts/ directory */\n readonly artifactsDir: string;\n /** Total number of test detail files */\n readonly testCount: number;\n /** Total size of all report data on disk */\n readonly totalSizeBytes: number;\n}\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n CLOUD_AUTH_FAILED: 'TESTRELIC_CLOUD_AUTH_FAILED',\n CLOUD_UPLOAD_FAILED: 'TESTRELIC_CLOUD_UPLOAD_FAILED',\n CLOUD_CONFIG_INVALID: 'TESTRELIC_CLOUD_CONFIG_INVALID',\n CLOUD_QUEUE_FAILED: 'TESTRELIC_CLOUD_QUEUE_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n CloudConfig,\n QueueEntry,\n UploadStrategy,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n // API tracking configuration\n if (obj.trackApiCalls !== undefined && typeof obj.trackApiCalls !== 'boolean') return false;\n if (obj.captureRequestHeaders !== undefined && typeof obj.captureRequestHeaders !== 'boolean') return false;\n if (obj.captureResponseHeaders !== undefined && typeof obj.captureResponseHeaders !== 'boolean') return false;\n if (obj.captureRequestBody !== undefined && typeof obj.captureRequestBody !== 'boolean') return false;\n if (obj.captureResponseBody !== undefined && typeof obj.captureResponseBody !== 'boolean') return false;\n if (obj.captureAssertions !== undefined && typeof obj.captureAssertions !== 'boolean') return false;\n\n if (obj.redactHeaders !== undefined) {\n if (!Array.isArray(obj.redactHeaders)) return false;\n for (const h of obj.redactHeaders) {\n if (typeof h !== 'string') return false;\n }\n }\n\n if (obj.redactBodyFields !== undefined) {\n if (!Array.isArray(obj.redactBodyFields)) return false;\n for (const f of obj.redactBodyFields) {\n if (typeof f !== 'string') return false;\n }\n }\n\n if (obj.apiIncludeUrls !== undefined) {\n if (!Array.isArray(obj.apiIncludeUrls)) return false;\n for (const p of obj.apiIncludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.apiExcludeUrls !== undefined) {\n if (!Array.isArray(obj.apiExcludeUrls)) return false;\n for (const p of obj.apiExcludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Config Validation\n// ---------------------------------------------------------------------------\n\nconst API_KEY_PATTERN = /^tr[a-z]*_[a-z]+_[a-zA-Z0-9]+$/;\n\nconst UPLOAD_STRATEGIES = new Set<string>(['batch', 'realtime', 'both']);\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0']);\n\nexport function isValidApiKeyFormat(value: unknown): value is string {\n return typeof value === 'string' && API_KEY_PATTERN.test(value);\n}\n\nexport function isValidUploadStrategy(value: unknown): value is UploadStrategy {\n return typeof value === 'string' && UPLOAD_STRATEGIES.has(value);\n}\n\nexport function isValidEndpointUrl(value: unknown): boolean {\n if (typeof value !== 'string') return false;\n try {\n const url = new URL(value);\n if (url.protocol === 'https:') return true;\n if (url.protocol === 'http:' && LOCALHOST_HOSTS.has(url.hostname)) return true;\n return false;\n } catch {\n return false;\n }\n}\n\nexport function isValidCloudConfig(input: unknown): input is CloudConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.apiKey !== undefined && obj.apiKey !== null) {\n if (typeof obj.apiKey !== 'string') return false;\n if (obj.apiKey.length > 0 && !isValidApiKeyFormat(obj.apiKey)) return false;\n }\n\n if (obj.endpoint !== undefined) {\n if (!isValidEndpointUrl(obj.endpoint)) return false;\n }\n\n if (obj.uploadStrategy !== undefined) {\n if (!isValidUploadStrategy(obj.uploadStrategy)) return false;\n }\n\n if (obj.timeout !== undefined) {\n if (typeof obj.timeout !== 'number' || obj.timeout < 1000 || obj.timeout > 120000) return false;\n }\n\n if (obj.projectName !== undefined && obj.projectName !== null && typeof obj.projectName !== 'string') return false;\n\n if (obj.queueMaxAge !== undefined) {\n if (typeof obj.queueMaxAge !== 'number' || obj.queueMaxAge < 3600000 || obj.queueMaxAge > 2592000000) return false;\n }\n\n if (obj.queueDirectory !== undefined) {\n if (typeof obj.queueDirectory !== 'string') return false;\n if (obj.queueDirectory.startsWith('/') || obj.queueDirectory.includes('..')) return false;\n }\n\n return true;\n}\n\nexport function isValidQueueEntry(input: unknown): input is QueueEntry {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (typeof obj.version !== 'string') return false;\n if (typeof obj.queuedAt !== 'string') return false;\n if (typeof obj.reason !== 'string') return false;\n if (typeof obj.retryCount !== 'number') return false;\n if (typeof obj.targetEndpoint !== 'string') return false;\n if (typeof obj.method !== 'string') return false;\n if (typeof obj.payload !== 'object' || obj.payload === null) return false;\n if (typeof obj.headers !== 'object' || obj.headers === null) return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["/**\n * @testrelic/core — Shared type definitions\n *\n * All interfaces for the JSON output schema, reporter configuration,\n * and internal fixture-to-reporter communication.\n *\n * Schema Version: 1.8.0\n */\n\n// ---------------------------------------------------------------------------\n// Navigation Types\n// ---------------------------------------------------------------------------\n\nexport type NavigationType =\n | 'goto'\n | 'navigation'\n | 'back'\n | 'forward'\n | 'refresh'\n | 'spa_route'\n | 'spa_replace'\n | 'hash_change'\n | 'link_click'\n | 'form_submit'\n | 'redirect'\n | 'popstate'\n | 'page_load'\n | 'manual_record'\n | 'fallback'\n | 'dummy';\n\n// ---------------------------------------------------------------------------\n// Output Schema Types (JSON report structure)\n// ---------------------------------------------------------------------------\n\nexport interface TestRunReport {\n readonly schemaVersion: string;\n readonly testRunId: string;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly totalDuration: number;\n readonly summary: Summary;\n readonly ci: CIMetadata | null;\n readonly metadata: Record<string, unknown> | null;\n readonly timeline: readonly (TimelineEntry | TimelineStep)[];\n readonly shardRunIds: string[] | null;\n}\n\n/** Breakdown of API calls by HTTP response status range. */\nexport interface ApiCallsByStatusRange {\n readonly '2xx': number;\n readonly '3xx': number;\n readonly '4xx': number;\n readonly '5xx': number;\n readonly error: number;\n}\n\n/** Statistical distribution of API call response times in milliseconds. */\nexport interface ApiResponseTimeStats {\n readonly avg: number;\n readonly min: number;\n readonly max: number;\n readonly p50: number;\n readonly p95: number;\n readonly p99: number;\n}\n\nexport interface Summary {\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedout: number;\n readonly totalApiCalls: number;\n readonly uniqueApiUrls: number;\n readonly apiCallsByMethod: Record<string, number>;\n readonly apiCallsByStatusRange: ApiCallsByStatusRange;\n readonly apiResponseTime: ApiResponseTimeStats | null;\n readonly totalAssertions: number;\n readonly passedAssertions: number;\n readonly failedAssertions: number;\n readonly totalNavigations: number;\n readonly uniqueNavigationUrls: number;\n readonly totalTimelineSteps: number;\n readonly totalActionSteps: number;\n readonly actionStepsByCategory: Record<string, number>;\n /** Visual baseline comparisons across the run. */\n readonly visualChecks?: number;\n /** Of those, how many differed from their baseline. */\n readonly visualFailures?: number;\n /** Of those, how many had no baseline and wrote one. */\n readonly visualNew?: number;\n /** DOM elements that differed across the run's visual comparisons. */\n readonly visualDomChanges?: number;\n /** Action steps across the run, bucketed by the driver's own category. */\n readonly actionStepsByKind?: Record<string, number>;\n /** Device-log lines captured across the run (mobile). */\n readonly deviceLogLines?: number;\n /** Of those, how many were error or fatal. */\n readonly deviceLogErrors?: number;\n /** Of those, how many came from a crash log. */\n readonly deviceLogCrashes?: number;\n /** Screenshots captured across the run. */\n readonly screenshotCount?: number;\n /** Screen recordings captured across the run. */\n readonly videoCount?: number;\n /** AI-detected defects across the run, by severity. */\n readonly aiDefectsBySeverity?: Record<string, number>;\n /** Distinct devices the run touched. */\n readonly devices?: readonly DeviceInfo[];\n}\n\nexport interface CIMetadata {\n readonly provider: CIProvider;\n readonly buildId: string | null;\n readonly commitSha: string | null;\n readonly branch: string | null;\n readonly runUrl: string | null;\n}\n\nexport type CIProvider =\n | 'github-actions'\n | 'gitlab-ci'\n | 'jenkins'\n | 'circleci'\n | 'bitbucket-pipelines'\n | 'unknown';\n\nexport interface TimelineEntry {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly visitedAt: string;\n readonly duration: number;\n readonly specFile: string;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly tests: TestResult[];\n}\n\nexport interface TestResult {\n readonly title: string;\n readonly status: TestStatus;\n readonly duration: number;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly retryCount: number;\n readonly tags: string[];\n readonly failure: FailureDiagnostic | null;\n readonly testId: string;\n readonly filePath: string;\n readonly suiteName: string;\n readonly testType: TestType;\n readonly isFlaky: boolean;\n readonly retryStatus: string | null;\n readonly expectedStatus: TestStatus;\n readonly actualStatus: TestStatus;\n readonly artifacts: TestArtifacts | null;\n readonly networkRequests: CapturedNetworkRequest[] | null;\n readonly apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n readonly actions: readonly ActionStep[] | null;\n readonly consoleLogs: readonly ConsoleLogEntry[] | null;\n /** Visual baseline comparisons recorded during this test, if any. */\n readonly visual?: readonly VisualComparison[] | null;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\nexport type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';\n\nexport interface CapturedNetworkRequest {\n readonly url: string;\n readonly method: string;\n readonly resourceType: ResourceType;\n readonly statusCode: number;\n readonly responseTimeMs: number;\n readonly startedAt: string;\n readonly requestHeaders: Record<string, string> | null;\n readonly requestBody: string | null;\n readonly responseBody: string | null;\n readonly responseHeaders: Record<string, string> | null;\n readonly contentType: string | null;\n readonly responseSize: number;\n readonly requestBodyTruncated: boolean;\n readonly responseBodyTruncated: boolean;\n readonly isBinary: boolean;\n readonly error: string | null;\n}\n\nexport interface TestArtifacts {\n readonly screenshot?: string;\n /**\n * Every screenshot captured for this test, report-relative.\n *\n * `screenshot` is the first one and stays for compatibility. Mobile\n * collectors have always gathered an array — one per step, or one per\n * failure — and only index 0 survived: the artifact copier wrote a single\n * `screenshot.png` and dropped the rest before the report could ask.\n */\n readonly screenshots?: readonly string[];\n readonly video?: string;\n /**\n * Playwright's trace archive — the DOM snapshots, network log and screencast\n * frames a failure is actually debugged from.\n *\n * Its absence here went all the way down the stack: with no field to fill,\n * the collector had nothing to look for, so a project configured with\n * `trace: 'on'` wrote `trace.zip` to disk and shipped only the picture and\n * the video. The gap bit hardest exactly where it mattered most — a failure\n * caught in a hook leaves no screenshot worth having, so those runs reached\n * the cloud with a video and nothing anyone could step through.\n */\n readonly trace?: string;\n readonly screenshotKey?: string;\n readonly videoKey?: string;\n readonly traceKey?: string;\n}\n\n/**\n * Outcome of a single visual baseline comparison.\n *\n * `new` is a snapshot Playwright had no baseline for and therefore wrote one\n * for — it is not a pass, because nothing was actually compared. `updated`\n * means the run was invoked with `--update-snapshots` and the baseline was\n * rewritten, which likewise compares nothing.\n */\nexport type VisualStatus = 'passed' | 'failed' | 'new' | 'updated';\n\n/** Which assertion produced a comparison. */\nexport type VisualSource = 'toHaveScreenshot' | 'toMatchSnapshot' | 'toMatchVisualBaseline';\n\n/**\n * One visual baseline comparison within a test.\n *\n * A test may hold many of these — `TestArtifacts` deliberately is not the home\n * for them, because it is single-slot per type (one screenshot, one video, one\n * trace) while a single test can assert against any number of named snapshots.\n *\n * Every image field is a report-relative POSIX path, matching `TestArtifacts`.\n * A passing comparison carries no `actual` or `diff`: Playwright attaches\n * nothing when the images match, so there is nothing to copy.\n */\nexport interface VisualComparison {\n /** Snapshot identity within the test, e.g. `home` or `checkout/cart`. */\n readonly name: string;\n readonly status: VisualStatus;\n readonly source: VisualSource;\n /** The committed baseline image. */\n readonly baseline?: string;\n /**\n * Where the committed baseline lives, relative to the Playwright config\n * file's directory (where `test-results` sits), POSIX separators — the\n * file a reviewer overwrites to accept this run.\n *\n * Deliberately not the artifact copy in `baseline`: that path is relative to\n * the report and points at a per-run duplicate, so a tool that \"accepted\" a\n * run by writing there would leave the baseline Playwright actually reads\n * untouched and the failure would come straight back.\n */\n readonly baselinePath?: string;\n /** What this run rendered. Absent when the comparison passed. */\n readonly actual?: string;\n /** Playwright's rendered diff. Present only on failure. */\n readonly diff?: string;\n /** The previous attempt's render, on a retried failure. */\n readonly previous?: string;\n /** Baseline dimensions, read from the PNG header. */\n readonly width?: number;\n readonly height?: number;\n /** Actual dimensions — set only when they differ from the baseline. */\n readonly actualWidth?: number;\n readonly actualHeight?: number;\n /** Differing pixel count, as reported by Playwright's comparator. */\n readonly diffPixels?: number;\n /** `diffPixels / (width * height)`, computed at full precision. */\n readonly diffPixelRatio?: number;\n /** Cloud storage keys, populated after artifact upload. */\n readonly baselineKey?: string;\n readonly actualKey?: string;\n readonly diffKey?: string;\n /**\n * Which elements changed, ranked by the area they affect.\n *\n * Present only when a DOM baseline existed to compare against. Capped — see\n * `domChangeCount` for the number found before the cap.\n */\n readonly domChanges?: readonly DomChange[];\n /** Total changes found, which may exceed `domChanges.length`. */\n readonly domChangeCount?: number;\n /**\n * Of those, how many actually changed rather than merely shifting position.\n *\n * The gap between this and `domChangeCount` is the layout knock-on: one card\n * gaining a margin moves everything below it, and a report that presents all\n * of those as equals hides the single edit that caused them.\n */\n readonly domRootCauses?: number;\n /** True when the DOM capture itself hit a node or byte cap. */\n readonly domTruncated?: boolean;\n /** Report-relative paths to the two captured DOM snapshots. */\n readonly baselineDom?: string;\n readonly actualDom?: string;\n /**\n * The document-space region the screenshot covered: `[x, y, width, height]`.\n *\n * Element rectangles are in document coordinates, so the report needs this to\n * place a box over the image — for a locator screenshot the image covers only\n * that element's box, not the viewport.\n */\n readonly domCaptureBox?: readonly [number, number, number, number];\n /** Cloud storage keys for the DOM snapshots. */\n readonly baselineDomKey?: string;\n readonly actualDomKey?: string;\n}\n\n/** How an element differs between the baseline render and this one. */\nexport type DomChangeKind =\n | 'added'\n | 'removed'\n | 'moved'\n | 'resized'\n | 'restyled'\n | 'text'\n /** Its id or class list changed — often the real story behind a restyle. */\n | 'attrs';\n\n/** One computed style property that differs. */\nexport interface DomStyleChange {\n readonly prop: string;\n readonly from: string;\n readonly to: string;\n}\n\n/**\n * One element that differs between two renders.\n *\n * Deliberately shaped like the platform's existing `RegionEvidence` — rank,\n * bbox, area share, a kind and a human label — so that a DOM overlay becomes\n * another caller of the dashboard's existing box-overlay component rather than\n * a second renderer built alongside it.\n */\nexport interface DomChange {\n /** 1-based, ordered by affected area descending. Drives the overlay colour. */\n readonly rank: number;\n /** Structural path used to match the element across the two snapshots. */\n readonly path: string;\n /** What a human reads, e.g. `div.card.accent`. */\n readonly label: string;\n readonly kinds: readonly DomChangeKind[];\n /** Document-space `[x, y, width, height]` in this render. */\n readonly rect?: readonly [number, number, number, number];\n /** The same, in the baseline. Absent for an added element. */\n readonly baselineRect?: readonly [number, number, number, number];\n /** Share of the capture box this element occupies, 0–1. */\n readonly areaPct: number;\n readonly styles?: readonly DomStyleChange[];\n readonly text?: { readonly from: string; readonly to: string };\n /** Identity attributes that changed, e.g. `class: \"card\" → \"banner\"`. */\n readonly attrs?: readonly DomStyleChange[];\n /**\n * True when this element only moved or resized, in a comparison where\n * something else actually changed.\n *\n * Shifting one card down by 24px shifts everything below it too, so a naive\n * list reports eleven changes for one edit. This flags the ten that merely\n * got pushed around, so a report can lead with the one that did something.\n *\n * It is a statement about this element — that nothing about it changed except\n * its position — and deliberately not a claim to have proven what moved it.\n */\n readonly consequence?: boolean;\n}\n\nexport type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';\n\nexport type TestType = 'e2e' | 'api' | 'unit' | 'mobile' | 'unknown';\n\n// ---------------------------------------------------------------------------\n// Mobile Device Types\n// ---------------------------------------------------------------------------\n\n/** Platform a mobile test drove. */\nexport type MobilePlatform = 'android' | 'ios' | 'web' | 'unknown';\n\n/** How the app under test was driven. */\nexport type AppTestMode = 'native' | 'hybrid' | 'mobile-web';\n\n/**\n * The device a mobile test ran on.\n *\n * Appium detects all of this from the W3C capabilities and Maestro reads it\n * from the CLI flags and driver log. Both used to collapse it to two words in\n * `tags` before the report saw it; this carries it through intact.\n */\nexport interface DeviceInfo {\n readonly platform: MobilePlatform;\n /** OS version, e.g. \"14\" or \"17.5\". */\n readonly platformVersion: string | null;\n /** Human-facing device name, e.g. \"Pixel 4a\" or \"iPhone 15\". */\n readonly deviceName: string | null;\n /** Driver, e.g. \"UiAutomator2\", \"XCUITest\", \"maestro\". */\n readonly automationName: string | null;\n /** App package / bundle id / apk path under test. */\n readonly appId: string | null;\n /** Native, hybrid or mobile-web. Null when the runner cannot tell. */\n readonly appTestMode: AppTestMode | null;\n /** Device serial or UDID, e.g. \"emulator-5554\". */\n readonly udid: string | null;\n}\n\n/** Severity of an AI-detected defect. */\nexport type AiDefectSeverity = 'critical' | 'warning' | 'info';\n\n/** Category of an AI-detected defect. */\nexport type AiDefectType = 'ui' | 'spelling' | 'i18n' | 'layout' | 'accessibility';\n\n/** A defect reported by an AI analysis pass over a captured screen. */\nexport interface AiDefect {\n readonly type: AiDefectType;\n readonly severity: AiDefectSeverity;\n readonly description: string;\n /** Where in the app it was seen, when the analyser says. */\n readonly location: string | null;\n /** Report-relative path to the screenshot it was found on. */\n readonly screenshot: string | null;\n}\n\nexport interface FailureDiagnostic {\n readonly message: string;\n readonly line: number | null;\n readonly code: string | null;\n readonly stack: string | null;\n}\n\nexport interface NetworkStats {\n readonly totalRequests: number;\n readonly failedRequests: number;\n readonly failedRequestUrls: string[];\n readonly totalBytes: number;\n readonly byType: ResourceBreakdown;\n}\n\nexport interface ResourceBreakdown {\n readonly xhr: number;\n readonly document: number;\n readonly script: number;\n readonly stylesheet: number;\n readonly image: number;\n readonly font: number;\n readonly other: number;\n}\n\n// ---------------------------------------------------------------------------\n// API Call Record (captured from Playwright APIRequestContext proxy)\n// ---------------------------------------------------------------------------\n\n/**\n * A cookie surfaced for the API-workspace Cookies tab. Values are MASKED by\n * default (the SDK never uploads raw session tokens to the cloud); the name and\n * any non-secret attributes (Path/Domain/Secure/HttpOnly/SameSite) are kept so\n * the inspector can show which cookies were sent/set, Postman-style.\n */\nexport interface CapturedCookie {\n readonly name: string;\n /** Masked value (e.g. \"••••\"). Never the raw cookie value. */\n readonly value: string;\n /** Response-cookie attributes joined for display (Set-Cookie only). */\n readonly attributes?: string;\n}\n\nexport interface ApiCallRecord {\n /** Unique call identifier within the test (e.g., \"api-call-0\") */\n readonly id: string;\n /** ISO 8601 timestamp when the call was initiated */\n readonly timestamp: string;\n /** HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD, or custom via fetch() */\n readonly method: string;\n /** Full resolved URL (after baseURL resolution by Playwright) */\n readonly url: string;\n /** All request headers sent, or null if unavailable */\n readonly requestHeaders: Record<string, string> | null;\n /** Request payload as string (JSON-serialized for objects). Null for bodiless methods */\n readonly requestBody: string | null;\n /** HTTP response status code, or null if request errored before response */\n readonly responseStatusCode: number | null;\n /** HTTP response status text, or null if request errored before response */\n readonly responseStatusText: string | null;\n /** All response headers received, or null if request errored before response */\n readonly responseHeaders: Record<string, string> | null;\n /** Response body: string for text/JSON, base64 for binary. Null on error */\n readonly responseBody: string | null;\n /** Request cookies parsed from the `Cookie` header (values masked). Optional/null when none. */\n readonly requestCookies?: readonly CapturedCookie[] | null;\n /** Response cookies parsed from `Set-Cookie` headers (values masked). Optional/null when none. */\n readonly responseCookies?: readonly CapturedCookie[] | null;\n /** Duration in milliseconds from request initiation to response received */\n readonly responseTimeMs: number;\n /** Whether the response body is stored as a base64-encoded binary string */\n readonly isBinary: boolean;\n /** Error message if the call failed (network error, timeout), null on success */\n readonly error: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// API Assertion Types (captured from assertion tracking)\n// ---------------------------------------------------------------------------\n\n/** Categorization of the assertion being made. */\nexport type AssertionType =\n | 'status'\n | 'statusOk'\n | 'header'\n | 'bodyField'\n | 'bodyMatch'\n | 'bodyContains'\n | 'custom';\n\n/** Source location where an assertion was made in the test file. */\nexport interface AssertionLocation {\n /** Absolute or relative file path to the test file */\n readonly file: string;\n /** Line number (1-based) */\n readonly line: number;\n /** Column number (1-based), if available */\n readonly column?: number;\n}\n\n/** A failed assertion reduced to what a summary row needs. */\nexport interface FailedAssertionSummary {\n readonly type: AssertionType;\n readonly expected: string;\n readonly actual: string;\n readonly expression?: string;\n}\n\n/** A single captured assertion on API response data. */\nexport interface ApiAssertion {\n /** Links to ApiCallRecord.id from the API proxy */\n readonly callId: string;\n /** Category of assertion */\n readonly type: AssertionType;\n /** What the test expected */\n readonly expected: unknown;\n /** What was actually received */\n readonly actual: unknown;\n /** Whether the assertion passed or failed */\n readonly status: 'passed' | 'failed';\n /** Source location of the assertion in the test file */\n readonly location: AssertionLocation;\n /** String representation of the assertion expression (best-effort) */\n readonly expression?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Unified Timeline Types (navigation + API call steps)\n// ---------------------------------------------------------------------------\n\n/** Test identity block attached to every timeline step. */\nexport interface StepTestIdentity {\n readonly title: string;\n readonly fullTitle: readonly string[];\n readonly status: TestStatus;\n readonly duration: number;\n readonly retries: number;\n readonly retry: number;\n readonly tags: readonly string[];\n readonly failure: FailureDiagnostic | null;\n}\n\n/** Request details within an API call step. */\nexport interface ApiCallStepRequest {\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** Response details within an API call step. */\nexport interface ApiCallStepResponse {\n readonly statusCode: number;\n readonly statusText: string;\n readonly headers: Record<string, string> | null;\n readonly body: unknown;\n}\n\n/** An assertion linked to an API call step (callId omitted — implicit from parent). */\nexport interface StepAssertion {\n readonly type: AssertionType;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly status: 'passed' | 'failed';\n readonly location: AssertionLocation;\n readonly expression?: string;\n}\n\n/** A browser navigation event in the unified timeline. */\nexport interface NavigationStep {\n readonly index: number;\n readonly type: 'navigation';\n readonly url: string;\n readonly timestamp: string;\n readonly durationOnUrl: number;\n readonly navigationType: NavigationType;\n readonly domContentLoadedAt: string | null;\n readonly networkIdleAt: string | null;\n readonly networkStats: NetworkStats | null;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** An API request event in the unified timeline. */\nexport interface ApiCallStep {\n readonly index: number;\n readonly type: 'api_call';\n readonly callId: string;\n readonly method: string;\n readonly url: string;\n readonly timestamp: string;\n readonly responseTime: number | null;\n readonly request: ApiCallStepRequest;\n readonly response: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions: readonly StepAssertion[];\n readonly specFile: string;\n readonly test: StepTestIdentity;\n}\n\n/** Discriminated union of all timeline step types. */\nexport type TimelineStep = NavigationStep | ApiCallStep;\n\n// ---------------------------------------------------------------------------\n// Action Step Types (Playwright test step capture)\n// ---------------------------------------------------------------------------\n\n/**\n * Categorized type of a captured test action.\n *\n * `navigation` covers app-level navigation events that aren't UI actions —\n * Maestro `launchApp` / `openLink` / `back`, Appium activity transitions, and\n * Playwright top-level page navigations. The platform's timeline UI and Ask\n * AI tools (`query_test_steps`'s `categoryFilter`) treat navigation as a\n * distinct bucket from generic `custom_step`.\n */\nexport type ActionCategory = 'ui_action' | 'assertion' | 'navigation' | 'custom_step';\n\n/**\n * The driver's own step category, preserved alongside `ActionCategory`.\n *\n * `ActionCategory` is the four-bucket vocabulary the platform's timeline and\n * Ask AI tools filter on, and it stays closed. Mobile drivers distinguish far\n * more than four kinds — Appium reports eleven, Maestro ten — and flattening\n * them made a swipe, an app launch and a screenshot render identically. This\n * keeps the finer label for the report to badge.\n */\nexport type StepKind =\n | 'find'\n | 'interaction'\n | 'gesture'\n | 'navigation'\n | 'lifecycle'\n | 'context'\n | 'device'\n | 'keyboard'\n | 'alert'\n | 'screenshot'\n | 'assertion'\n | 'media'\n | 'script'\n | 'flow_control'\n | 'ai'\n | 'other';\n\n/** A single captured test action step. */\nexport interface ActionStep {\n readonly title: string;\n readonly category: ActionCategory;\n /** The driver's own finer-grained category, when it reported one. */\n readonly kind?: StepKind | null;\n /** Element selector this step acted on, when the driver reported one. */\n readonly selector?: string | null;\n readonly timestamp: string;\n readonly duration: number;\n readonly videoOffset: number | null;\n readonly status: 'passed' | 'failed';\n readonly error: string | null;\n readonly children: readonly ActionStep[];\n}\n\n// ---------------------------------------------------------------------------\n// Console Log Types (browser console + terminal stdout/stderr)\n// ---------------------------------------------------------------------------\n\n/** Level of a captured console/terminal log message. */\nexport type ConsoleLogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' | 'stdout' | 'stderr';\n\n/** A single captured console or terminal log entry. */\n/**\n * Where a captured log line came from.\n *\n * Browser consoles only ever produce `console`. Mobile runs mix several\n * streams, and telling a crash log from ordinary logcat chatter is the whole\n * point of showing them — so the stream survives to the row.\n */\nexport type LogSource =\n | 'console'\n | 'logcat'\n | 'syslog'\n | 'crashlog'\n | 'browser'\n | 'safariConsole'\n | 'performance'\n | 'server'\n | 'bidi'\n | 'driver';\n\nexport interface ConsoleLogEntry {\n /** Log level or stream type */\n readonly level: ConsoleLogLevel;\n /** Text content of the log message */\n readonly text: string;\n /** ISO 8601 timestamp when the log was captured */\n readonly timestamp: string;\n /** Source file location (browser console only, best-effort) */\n readonly location: string | null;\n /** Which stream produced the line. Defaults to `console` when absent. */\n readonly source?: LogSource | null;\n /** Emitting tag, e.g. an Android logcat tag like \"ActivityManager\". */\n readonly tag?: string | null;\n /** Emitting process id, when the stream reports one. */\n readonly pid?: number | null;\n /** Stack trace attached to the line (crash logs). */\n readonly stackTrace?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration Types (reporter tuple options)\n// ---------------------------------------------------------------------------\n\n/** Report generation mode. */\nexport type ReportMode = 'streaming' | 'embedded' | 'auto';\n\nexport interface ReporterConfig {\n readonly outputPath?: string;\n readonly includeStackTrace?: boolean;\n readonly includeCodeSnippets?: boolean;\n readonly codeContextLines?: number;\n readonly includeNetworkStats?: boolean;\n /** Master on/off for Test Navigation capture. Default: true. When false the\n * reporter drops navigation data before upload and logs a one-time notice. */\n readonly captureNavigation?: boolean;\n readonly navigationTypes?: NavigationType[] | null;\n readonly redactPatterns?: (string | RegExp)[];\n readonly testRunId?: string | null;\n readonly metadata?: Record<string, unknown> | null;\n readonly openReport?: boolean;\n readonly htmlReportPath?: string;\n readonly includeArtifacts?: boolean;\n /** When false, disables API call interception in the unified fixture. Default: true */\n readonly trackApiCalls?: boolean;\n /** When true, suppresses console summary output. Default: false */\n readonly quiet?: boolean;\n /** When true, captures Playwright test steps (actions). Default: true */\n readonly includeActionSteps?: boolean;\n /** Capture request headers for API calls. Default: true */\n readonly captureRequestHeaders?: boolean;\n /** Capture response headers for API calls. Default: true */\n readonly captureResponseHeaders?: boolean;\n /** Capture request body for API calls. Default: true */\n readonly captureRequestBody?: boolean;\n /** Capture response body for API calls. Default: true */\n readonly captureResponseBody?: boolean;\n /** Capture API assertions (both pass and fail). Default: true */\n readonly captureAssertions?: boolean;\n /**\n * Header names whose values are replaced with \"[REDACTED]\".\n * Case-insensitive matching. Default: ['authorization', 'cookie', 'set-cookie', 'x-api-key']\n */\n readonly redactHeaders?: string[];\n /**\n * Body field names whose values are replaced with \"[REDACTED]\" at any depth.\n * Default: ['password', 'secret', 'token', 'apiKey', 'api_key']\n */\n readonly redactBodyFields?: string[];\n /**\n * Only track API calls matching these URL patterns.\n * Default: undefined (track all)\n */\n readonly apiIncludeUrls?: (string | RegExp)[];\n /**\n * Exclude API calls matching these URL patterns. Takes precedence over include.\n * Default: undefined (exclude none)\n */\n readonly apiExcludeUrls?: (string | RegExp)[];\n /** Capture browser console logs (E2E) and terminal output (API). Default: true */\n readonly captureConsoleLogs?: boolean;\n /** Cloud integration configuration. Default: undefined (local mode) */\n readonly cloud?: CloudReporterOptions;\n /**\n * Report generation mode.\n * - `'embedded'`: All data embedded in a single HTML file (current behavior)\n * - `'streaming'`: Data streamed to disk per-test, served via local server\n * - `'auto'`: Use embedded for < streamingThreshold tests, streaming otherwise\n * Default: `'auto'`\n */\n readonly reportMode?: ReportMode;\n /**\n * Test count threshold for auto mode. When the suite has >= this many tests,\n * streaming mode is used. Default: 500\n */\n readonly streamingThreshold?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal Types (fixture → reporter communication)\n// ---------------------------------------------------------------------------\n\nexport interface NavigationAnnotation {\n readonly url: string;\n readonly navigationType: NavigationType;\n readonly timestamp: string;\n readonly domContentLoadedAt?: string;\n readonly networkIdleAt?: string;\n readonly networkStats?: NetworkStats;\n}\n\n// ---------------------------------------------------------------------------\n// Worker-Safe Data Payload (worker → reporter communication)\n// ---------------------------------------------------------------------------\n\n/** Consolidated data payload sent from a worker fixture to the reporter. */\nexport interface TestRelicDataPayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version (semver). */\n readonly version: string;\n /** Navigation events collected during the test. */\n readonly navigations: readonly NavigationAnnotation[];\n /** Captured network requests during the test. */\n readonly networkRequests: readonly CapturedNetworkRequest[];\n /** API calls made via APIRequestContext proxy. */\n readonly apiCalls: readonly ApiCallRecord[];\n /** Assertions captured on API responses. */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Console/terminal logs captured during the test. Optional for backward compat. */\n readonly consoleLogs?: readonly ConsoleLogEntry[];\n}\n\n/** Current payload format version. */\nexport const PAYLOAD_VERSION = '2.0.0';\n\n/** Legacy payload version (pre-streaming). */\nexport const PAYLOAD_VERSION_LEGACY = '1.0.0';\n\n/** Attachment name used for the consolidated payload. */\nexport const ATTACHMENT_NAME = 'testrelic-data';\n\n/** Attachment content type. */\nexport const ATTACHMENT_CONTENT_TYPE = 'application/json';\n\n/**\n * File-based data payload (v2.0.0).\n *\n * Heavy data (network requests, console logs, API calls) is written to\n * JSONL files on disk and referenced by path. Only lightweight data\n * (navigations, assertions) is kept inline.\n */\nexport interface TestRelicFilePayload {\n /** Marker to identify TestRelic attachments. Always `true`. */\n readonly testRelicData: true;\n /** Payload format version — `\"2.0.0\"` for file-based payloads. */\n readonly version: '2.0.0';\n /** Navigation events (small, kept inline). */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertions (small, kept inline). */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Absolute path to JSONL file containing CapturedNetworkRequest items. */\n readonly networkRequestsFile: string | null;\n /** Number of network request items in the file. */\n readonly networkRequestsCount: number;\n /** Absolute path to JSONL file containing ConsoleLogEntry items. */\n readonly consoleLogsFile: string | null;\n /** Number of console log items in the file. */\n readonly consoleLogsCount: number;\n /** Absolute path to JSONL file containing ApiCallRecord items. */\n readonly apiCallsFile: string | null;\n /** Number of API call items in the file. */\n readonly apiCallsCount: number;\n /**\n * Response-status mix of the calls in the file.\n *\n * Tallied as each record is appended. The file itself is never read back\n * into memory in streaming mode, so an aggregate computed at write time is\n * the only way the grid can show a row's status chips.\n */\n readonly apiStatusMix?: ApiCallsByStatusRange | null;\n}\n\n/** Type guard: validates that a parsed object is a v2 file-based payload. */\nexport function isTestRelicFilePayload(obj: unknown): obj is TestRelicFilePayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n record.version === '2.0.0' &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n/** Type guard: validates that a parsed object is a v1 inline data payload. */\nexport function isTestRelicDataPayload(obj: unknown): obj is TestRelicDataPayload {\n if (typeof obj !== 'object' || obj === null) return false;\n const record = obj as Record<string, unknown>;\n return (\n record.testRelicData === true &&\n typeof record.version === 'string' &&\n record.version.length > 0 &&\n Array.isArray(record.navigations) &&\n Array.isArray(record.networkRequests) &&\n Array.isArray(record.apiCalls) &&\n Array.isArray(record.apiAssertions)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Integration Types\n// ---------------------------------------------------------------------------\n\n/** Upload strategy for cloud timeline data. */\nexport type UploadStrategy = 'batch' | 'realtime' | 'both';\n\n/** Current cloud/local operating mode. */\nexport type AuthMode = 'cloud' | 'local' | 'pending';\n\n/** Resolved cloud configuration, immutable after initialization. */\nexport interface CloudConfig {\n readonly apiKey: string | null;\n readonly endpoint: string;\n readonly uploadStrategy: UploadStrategy;\n /** Per-request ceiling. ⚠ It governed only the auth handshake until 2.14.0 —\n * the upload calls used bare `fetch` and inherited undici's 5-minute\n * defaults, so a tester who set this had bounded the token exchange and\n * nothing else. It now bounds the upload requests too. */\n readonly timeout: number;\n /** Total time the whole upload phase may take before it gives up, writes the\n * queue and lets the process exit. The phase runs inside Playwright's\n * `onEnd`, so this is the difference between a late run and a run that never\n * finishes. Default 120s.\n *\n * ⛔ OPTIONAL on purpose. `CloudConfig` is built by every analytics package\n * (appium, maestro, playwright), and making a new field required breaks all\n * of them at once for a knob only one upload path reads — CI caught exactly\n * that. Callers resolve it as `?? DEFAULT_UPLOAD_BUDGET_MS`. */\n readonly uploadBudgetMs?: number;\n readonly projectName: string | null;\n readonly queueMaxAge: number;\n readonly queueDirectory: string;\n readonly uploadArtifacts: boolean;\n readonly artifactMaxSizeMb: number;\n}\n\n/** Mutable auth state tracked by the cloud client. */\nexport interface AuthState {\n mode: AuthMode;\n accessToken: string | null;\n refreshToken: string | null;\n expiresAt: number | null;\n orgId: string | null;\n orgName: string | null;\n userId: string | null;\n userName: string | null;\n}\n\n/** Auto-collected git information. All fields nullable. */\nexport interface GitMetadata {\n readonly branch: string | null;\n readonly commitSha: string | null;\n readonly commitMessage: string | null;\n readonly commitAuthor: string | null;\n readonly remoteUrl: string | null;\n}\n\n/** Cached repo identity. */\nexport interface RepoResolution {\n readonly repoId: string;\n readonly gitId: string;\n readonly displayName: string;\n readonly resolvedAt: number;\n readonly apiKeyHash: string;\n}\n\n/** A single queued upload payload. */\nexport interface QueueEntry {\n readonly version: string;\n readonly queuedAt: string;\n readonly reason: string;\n readonly retryCount: number;\n readonly targetEndpoint: string;\n readonly method: string;\n readonly payload: Record<string, unknown>;\n readonly headers: Record<string, string>;\n}\n\n/** Inline cloud configuration accepted via reporter options. */\nexport interface CloudReporterOptions {\n readonly apiKey?: string;\n readonly endpoint?: string;\n readonly upload?: UploadStrategy;\n readonly timeout?: number;\n /** Total budget for the upload phase (ms). See CloudConfig.uploadBudgetMs. */\n readonly uploadBudgetMs?: number;\n readonly projectName?: string;\n readonly queueMaxAge?: string;\n readonly queueDirectory?: string;\n readonly uploadArtifacts?: boolean;\n readonly artifactMaxSizeMb?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Merge CLI Types\n// ---------------------------------------------------------------------------\n\nexport interface MergeOptions {\n readonly output: string;\n readonly testRunId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Artifact Management Types\n// ---------------------------------------------------------------------------\n\n/** A single artifact file within a test's artifact folder. */\nexport interface ArtifactFile {\n /** File name (e.g., `screenshot.png`, `video.webm`, `trace.zip`) */\n readonly name: string;\n /** Artifact type for rendering */\n readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';\n /** Path relative to the report output directory */\n readonly relativePath: string;\n /** File size in bytes */\n readonly sizeBytes: number;\n}\n\n/** A single test's artifacts within a run folder. */\nexport interface ArtifactTestEntry {\n /** Sanitized test folder name */\n readonly testName: string;\n /** List of artifact files for this test */\n readonly files: readonly ArtifactFile[];\n}\n\n/** A single test run's artifact folder in the manifest. */\nexport interface ArtifactRunEntry {\n /** Timestamp-based folder name (e.g., `2026-03-02T14-30-00`) */\n readonly folderName: string;\n /** ISO 8601 timestamp parsed from folder name */\n readonly timestamp: string;\n /** Sum of all file sizes in this run folder */\n readonly totalSizeBytes: number;\n /** Number of test artifact subdirectories */\n readonly testCount: number;\n /** Per-test artifact details */\n readonly tests: readonly ArtifactTestEntry[];\n /** Whether this folder belongs to the current report's run */\n readonly isCurrentRun: boolean;\n}\n\n/** Top-level manifest structure written as `artifact-manifest.json`. */\nexport interface ArtifactRunManifest {\n /** Manifest schema version (starts at `\"1.0\"`) */\n readonly schemaVersion: string;\n /** ISO 8601 timestamp of manifest generation */\n readonly generatedAt: string;\n /** Relative path to artifacts directory from report (e.g., `\"artifacts\"`) */\n readonly artifactBaseDir: string;\n /** Sum of all run folder sizes */\n readonly totalSizeBytes: number;\n /** All discovered run folders, sorted newest first */\n readonly runs: readonly ArtifactRunEntry[];\n /** Port of the artifact management server, or null if not running */\n readonly serverPort: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Streaming Report Types (server-backed reporter)\n// ---------------------------------------------------------------------------\n\n/** Compact index entry for a single test — kept in memory during run. */\nexport interface TestIndexEntry {\n /** Deterministic hash ID (first 12 chars of SHA-256) */\n readonly id: string;\n /** Test title (leaf name) */\n readonly title: string;\n /** Full title path (describe blocks + test name) */\n readonly titlePath: readonly string[];\n /** Relative path to test file */\n readonly filePath: string;\n /** Test outcome */\n readonly status: TestStatus;\n /**\n * The status Playwright expected: `test.fail()` expects 'failed', and\n * `test.skip()` / `test.fixme()` expect 'skipped'. Absent means 'passed'.\n *\n * `status` keeps its meaning: the actual result, or 'flaky' / 'skipped'\n * from Playwright's outcome. A reader derives the verdict from the pair the\n * way Playwright does: a result is expected only when actual === expected.\n * A `test.fail()` that failed is therefore a pass, and one that passed (or\n * timed out) is a failure.\n */\n readonly expectedStatus?: TestStatus;\n /** Test duration in milliseconds */\n readonly duration: number;\n /** Playwright project name */\n readonly project: string;\n /** Retry attempt number (0 = first run) */\n readonly retryIndex: number;\n /** Test tags */\n readonly tags: readonly string[];\n /** What kind of test this is — badges the row, and picks the row's evidence. */\n readonly testType?: TestType;\n /** Whether this test has network request data */\n readonly hasNetworkData: boolean;\n /** Whether this test has console log data */\n readonly hasConsoleData: boolean;\n /** Whether this test has screenshot/video artifacts */\n readonly hasArtifacts: boolean;\n /** Whether this test has action step data */\n readonly hasActionSteps: boolean;\n /** Number of visual baseline comparisons recorded */\n readonly visualCount?: number;\n /** Of those, how many differed from their baseline */\n readonly visualFailedCount?: number;\n /** DOM elements that differed across this test's comparisons */\n readonly domChangeCount?: number;\n /**\n * Report-relative path to a diff image for this test, when one exists.\n *\n * Carried on the index so the run overview can show the failure itself\n * rather than only describing it — the grid must never fetch per-test\n * detail files to render.\n */\n readonly visualDiffPath?: string;\n /** First line of error message (failed/flaky tests only) */\n readonly errorMessage: string | null;\n /** Number of network requests captured */\n readonly networkCount: number;\n /** Number of console log entries captured */\n readonly consoleCount: number;\n /** Number of action steps captured */\n readonly actionCount: number;\n /** API calls captured for this test. */\n readonly apiCallCount?: number;\n /** Response-status mix of those calls, for the row's status chips. */\n readonly apiStatusMix?: ApiCallsByStatusRange;\n /** API assertions that passed during this test. */\n readonly assertionsPassed?: number;\n /** API assertions that failed during this test. */\n readonly assertionsFailed?: number;\n /**\n * The first failed assertion, flattened to strings.\n *\n * The run overview leads with \"expected 101, received undefined\" and must\n * do so without opening the test's detail file. The first line of an API\n * failure message is the matcher wrapper, which says nothing.\n */\n readonly firstFailedAssertion?: FailedAssertionSummary;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** Action steps for this test, bucketed by the driver's own category. */\n readonly stepKinds?: Record<string, number>;\n /** Device-log errors and fatals recorded during this test. */\n readonly deviceLogErrors?: number;\n /** Whether a crash-log line was recorded during this test. */\n readonly hasCrashLog?: boolean;\n /** Whether a screen recording exists for this test. */\n readonly hasVideo?: boolean;\n /** AI-detected defects attributed to this test. */\n readonly aiDefectCount?: number;\n /** Whether this is a retry attempt (not the final attempt) */\n readonly isRetry: boolean;\n /** Stable key shared by every attempt of the same test (same across retries, unlike `id`) */\n readonly retryGroupKey: string;\n}\n\n/**\n * Full detail data for a single test, stored on disk.\n *\n * In v3 (stream-to-disk), heavy data (network requests, console logs,\n * API calls) is stored in separate JSONL files alongside meta.json.\n * The `has*` and `*Count` fields indicate availability without loading.\n */\nexport interface TestDetailData {\n /** Same as TestIndexEntry.id */\n readonly id: string;\n /** Page navigation events (inline — typically small) */\n readonly navigations: readonly NavigationAnnotation[];\n /** API assertion results (inline — typically small) */\n readonly apiAssertions: readonly ApiAssertion[];\n /** Nested action step tree (inline — typically small) */\n readonly actions: readonly ActionStep[];\n /** References to screenshot/video files */\n readonly artifacts: TestArtifacts | null;\n /** Visual baseline comparisons (inline — a handful per test at most) */\n readonly visual?: readonly VisualComparison[] | null;\n /** Error details for failed/flaky tests */\n readonly failureDiagnostic: FailureDiagnostic | null;\n /** Whether network.jsonl exists for this test */\n readonly hasNetworkFile: boolean;\n /** Number of items in network.jsonl */\n readonly networkRequestsCount: number;\n /** Whether console.jsonl exists for this test */\n readonly hasConsoleFile: boolean;\n /** Number of items in console.jsonl */\n readonly consoleLogsCount: number;\n /** Whether api-calls.jsonl exists for this test */\n readonly hasApiCallsFile: boolean;\n /** Number of items in api-calls.jsonl */\n readonly apiCallsCount: number;\n /** Test start time (ISO 8601) — used for video sync */\n readonly startedAt?: string;\n /** The device this test drove (mobile runs only). */\n readonly device?: DeviceInfo | null;\n /** AI-detected defects attributed to this test. */\n readonly aiDefects?: readonly AiDefect[] | null;\n}\n\n/** Streaming report summary — running counters written to summary.json. */\nexport interface StreamingReportSummary {\n /** Unique run identifier */\n readonly testRunId: string;\n /** ISO 8601 start timestamp */\n readonly startedAt: string;\n /** ISO 8601 completion timestamp */\n readonly completedAt: string;\n /** Wall-clock duration in ms */\n readonly totalDuration: number;\n /** Total number of test results */\n readonly total: number;\n /** Tests that passed */\n readonly passed: number;\n /** Tests that failed */\n readonly failed: number;\n /** Tests that passed on retry */\n readonly flaky: number;\n /** Tests that were skipped */\n readonly skipped: number;\n /** Tests that timed out */\n readonly timedOut: number;\n /** Tests that were interrupted */\n readonly interrupted: number;\n /** Total API calls across all tests */\n readonly totalApiCalls: number;\n /** Total assertions across all tests */\n readonly totalAssertions: number;\n /** Total page navigations */\n readonly totalNavigations: number;\n /** Total network requests captured */\n readonly totalNetworkRequests: number;\n /** Total console log entries captured */\n readonly totalConsoleLogs: number;\n /** Total action steps captured */\n readonly totalActionSteps: number;\n /** User-provided metadata */\n readonly metadata: Record<string, unknown> | null;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Per-file aggregation */\n readonly files: readonly StreamingFileStats[];\n /** Enriched summary (matches existing Summary interface) */\n readonly enrichedSummary: Summary | null;\n /** Validation cross-check data */\n readonly validation: StreamingValidation | null;\n /** Write errors encountered during streaming */\n readonly writeErrors: readonly StreamingWriteError[];\n}\n\n/** Per-file test aggregation for streaming reports. */\nexport interface StreamingFileStats {\n readonly filePath: string;\n readonly total: number;\n readonly passed: number;\n readonly failed: number;\n readonly flaky: number;\n readonly skipped: number;\n readonly timedOut: number;\n}\n\n/** Validation cross-check between reporter counts and Playwright. */\nexport interface StreamingValidation {\n readonly reporterTotal: number;\n readonly indexTotal: number;\n readonly playwrightStatus: string;\n readonly isValid: boolean;\n}\n\n/** A write error that occurred during streaming. */\nexport interface StreamingWriteError {\n readonly testId: string;\n readonly error: string;\n readonly timestamp: string;\n}\n\n/** Top-level manifest for a streaming report directory. */\nexport interface StreamingReportManifest {\n /** Schema version (`\"2.0\"` for streaming format) */\n readonly schemaVersion: string;\n /** ISO 8601 generation timestamp */\n readonly generatedAt: string;\n /** Report generation mode */\n readonly reportMode: ReportMode;\n /** Relative path to summary.json */\n readonly summaryFile: string;\n /** Relative path to index.json */\n readonly indexFile: string;\n /** Relative path to tests/ directory */\n readonly testsDir: string;\n /** Relative path to artifacts/ directory */\n readonly artifactsDir: string;\n /** Total number of test detail files */\n readonly testCount: number;\n /** Total size of all report data on disk */\n readonly totalSizeBytes: number;\n}\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n CLOUD_AUTH_FAILED: 'TESTRELIC_CLOUD_AUTH_FAILED',\n CLOUD_UPLOAD_FAILED: 'TESTRELIC_CLOUD_UPLOAD_FAILED',\n CLOUD_CONFIG_INVALID: 'TESTRELIC_CLOUD_CONFIG_INVALID',\n CLOUD_QUEUE_FAILED: 'TESTRELIC_CLOUD_QUEUE_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n CloudConfig,\n QueueEntry,\n UploadStrategy,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n if (obj.includeArtifacts !== undefined && typeof obj.includeArtifacts !== 'boolean') return false;\n\n // API tracking configuration\n if (obj.trackApiCalls !== undefined && typeof obj.trackApiCalls !== 'boolean') return false;\n if (obj.captureRequestHeaders !== undefined && typeof obj.captureRequestHeaders !== 'boolean') return false;\n if (obj.captureResponseHeaders !== undefined && typeof obj.captureResponseHeaders !== 'boolean') return false;\n if (obj.captureRequestBody !== undefined && typeof obj.captureRequestBody !== 'boolean') return false;\n if (obj.captureResponseBody !== undefined && typeof obj.captureResponseBody !== 'boolean') return false;\n if (obj.captureAssertions !== undefined && typeof obj.captureAssertions !== 'boolean') return false;\n\n if (obj.redactHeaders !== undefined) {\n if (!Array.isArray(obj.redactHeaders)) return false;\n for (const h of obj.redactHeaders) {\n if (typeof h !== 'string') return false;\n }\n }\n\n if (obj.redactBodyFields !== undefined) {\n if (!Array.isArray(obj.redactBodyFields)) return false;\n for (const f of obj.redactBodyFields) {\n if (typeof f !== 'string') return false;\n }\n }\n\n if (obj.apiIncludeUrls !== undefined) {\n if (!Array.isArray(obj.apiIncludeUrls)) return false;\n for (const p of obj.apiIncludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.apiExcludeUrls !== undefined) {\n if (!Array.isArray(obj.apiExcludeUrls)) return false;\n for (const p of obj.apiExcludeUrls) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Cloud Config Validation\n// ---------------------------------------------------------------------------\n\nconst API_KEY_PATTERN = /^tr[a-z]*_[a-z]+_[a-zA-Z0-9]+$/;\n\nconst UPLOAD_STRATEGIES = new Set<string>(['batch', 'realtime', 'both']);\n\nconst LOCALHOST_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0']);\n\nexport function isValidApiKeyFormat(value: unknown): value is string {\n return typeof value === 'string' && API_KEY_PATTERN.test(value);\n}\n\nexport function isValidUploadStrategy(value: unknown): value is UploadStrategy {\n return typeof value === 'string' && UPLOAD_STRATEGIES.has(value);\n}\n\nexport function isValidEndpointUrl(value: unknown): boolean {\n if (typeof value !== 'string') return false;\n try {\n const url = new URL(value);\n if (url.protocol === 'https:') return true;\n if (url.protocol === 'http:' && LOCALHOST_HOSTS.has(url.hostname)) return true;\n return false;\n } catch {\n return false;\n }\n}\n\nexport function isValidCloudConfig(input: unknown): input is CloudConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.apiKey !== undefined && obj.apiKey !== null) {\n if (typeof obj.apiKey !== 'string') return false;\n if (obj.apiKey.length > 0 && !isValidApiKeyFormat(obj.apiKey)) return false;\n }\n\n if (obj.endpoint !== undefined) {\n if (!isValidEndpointUrl(obj.endpoint)) return false;\n }\n\n if (obj.uploadStrategy !== undefined) {\n if (!isValidUploadStrategy(obj.uploadStrategy)) return false;\n }\n\n if (obj.timeout !== undefined) {\n if (typeof obj.timeout !== 'number' || obj.timeout < 1000 || obj.timeout > 120000) return false;\n }\n\n if (obj.projectName !== undefined && obj.projectName !== null && typeof obj.projectName !== 'string') return false;\n\n if (obj.queueMaxAge !== undefined) {\n if (typeof obj.queueMaxAge !== 'number' || obj.queueMaxAge < 3600000 || obj.queueMaxAge > 2592000000) return false;\n }\n\n if (obj.queueDirectory !== undefined) {\n if (typeof obj.queueDirectory !== 'string') return false;\n if (obj.queueDirectory.startsWith('/') || obj.queueDirectory.includes('..')) return false;\n }\n\n return true;\n}\n\nexport function isValidQueueEntry(input: unknown): input is QueueEntry {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (typeof obj.version !== 'string') return false;\n if (typeof obj.queuedAt !== 'string') return false;\n if (typeof obj.reason !== 'string') return false;\n if (typeof obj.retryCount !== 'number') return false;\n if (typeof obj.targetEndpoint !== 'string') return false;\n if (typeof obj.method !== 'string') return false;\n if (typeof obj.payload !== 'object' || obj.payload === null) return false;\n if (typeof obj.headers !== 'object' || obj.headers === null) return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n // timedout is optional for backward compat with schema 1.0.0\n if (summary.timedout !== undefined && typeof summary.timedout !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testrelic/core",
3
- "version": "2.16.2-next.153",
3
+ "version": "2.16.3-next.157",
4
4
  "description": "Shared types, logger, errors, and validation for TestRelic packages",
5
5
  "keywords": [
6
6
  "testrelic",