@testrelic/core 2.9.0-next.73 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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.0.0
7
+ * Schema Version: 1.7.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 {
@@ -56,6 +56,30 @@ interface Summary {
56
56
  readonly totalTimelineSteps: number;
57
57
  readonly totalActionSteps: number;
58
58
  readonly actionStepsByCategory: Record<string, number>;
59
+ /** Visual baseline comparisons across the run. */
60
+ readonly visualChecks?: number;
61
+ /** Of those, how many differed from their baseline. */
62
+ readonly visualFailures?: number;
63
+ /** Of those, how many had no baseline and wrote one. */
64
+ readonly visualNew?: number;
65
+ /** DOM elements that differed across the run's visual comparisons. */
66
+ readonly visualDomChanges?: number;
67
+ /** Action steps across the run, bucketed by the driver's own category. */
68
+ readonly actionStepsByKind?: Record<string, number>;
69
+ /** Device-log lines captured across the run (mobile). */
70
+ readonly deviceLogLines?: number;
71
+ /** Of those, how many were error or fatal. */
72
+ readonly deviceLogErrors?: number;
73
+ /** Of those, how many came from a crash log. */
74
+ readonly deviceLogCrashes?: number;
75
+ /** Screenshots captured across the run. */
76
+ readonly screenshotCount?: number;
77
+ /** Screen recordings captured across the run. */
78
+ readonly videoCount?: number;
79
+ /** AI-detected defects across the run, by severity. */
80
+ readonly aiDefectsBySeverity?: Record<string, number>;
81
+ /** Distinct devices the run touched. */
82
+ readonly devices?: readonly DeviceInfo[];
59
83
  }
60
84
  interface CIMetadata {
61
85
  readonly provider: CIProvider;
@@ -99,6 +123,12 @@ interface TestResult {
99
123
  readonly apiAssertions: readonly ApiAssertion[] | null;
100
124
  readonly actions: readonly ActionStep[] | null;
101
125
  readonly consoleLogs: readonly ConsoleLogEntry[] | null;
126
+ /** Visual baseline comparisons recorded during this test, if any. */
127
+ readonly visual?: readonly VisualComparison[] | null;
128
+ /** The device this test drove (mobile runs only). */
129
+ readonly device?: DeviceInfo | null;
130
+ /** AI-detected defects attributed to this test. */
131
+ readonly aiDefects?: readonly AiDefect[] | null;
102
132
  }
103
133
  type ResourceType = 'xhr' | 'document' | 'script' | 'stylesheet' | 'image' | 'font' | 'other';
104
134
  interface CapturedNetworkRequest {
@@ -121,12 +151,220 @@ interface CapturedNetworkRequest {
121
151
  }
122
152
  interface TestArtifacts {
123
153
  readonly screenshot?: string;
154
+ /**
155
+ * Every screenshot captured for this test, report-relative.
156
+ *
157
+ * `screenshot` is the first one and stays for compatibility. Mobile
158
+ * collectors have always gathered an array — one per step, or one per
159
+ * failure — and only index 0 survived: the artifact copier wrote a single
160
+ * `screenshot.png` and dropped the rest before the report could ask.
161
+ */
162
+ readonly screenshots?: readonly string[];
124
163
  readonly video?: string;
164
+ /**
165
+ * Playwright's trace archive — the DOM snapshots, network log and screencast
166
+ * frames a failure is actually debugged from.
167
+ *
168
+ * Its absence here went all the way down the stack: with no field to fill,
169
+ * the collector had nothing to look for, so a project configured with
170
+ * `trace: 'on'` wrote `trace.zip` to disk and shipped only the picture and
171
+ * the video. The gap bit hardest exactly where it mattered most — a failure
172
+ * caught in a hook leaves no screenshot worth having, so those runs reached
173
+ * the cloud with a video and nothing anyone could step through.
174
+ */
175
+ readonly trace?: string;
125
176
  readonly screenshotKey?: string;
126
177
  readonly videoKey?: string;
178
+ readonly traceKey?: string;
179
+ }
180
+ /**
181
+ * Outcome of a single visual baseline comparison.
182
+ *
183
+ * `new` is a snapshot Playwright had no baseline for and therefore wrote one
184
+ * for — it is not a pass, because nothing was actually compared. `updated`
185
+ * means the run was invoked with `--update-snapshots` and the baseline was
186
+ * rewritten, which likewise compares nothing.
187
+ */
188
+ type VisualStatus = 'passed' | 'failed' | 'new' | 'updated';
189
+ /** Which assertion produced a comparison. */
190
+ type VisualSource = 'toHaveScreenshot' | 'toMatchSnapshot' | 'toMatchVisualBaseline';
191
+ /**
192
+ * One visual baseline comparison within a test.
193
+ *
194
+ * A test may hold many of these — `TestArtifacts` deliberately is not the home
195
+ * for them, because it is single-slot per type (one screenshot, one video, one
196
+ * trace) while a single test can assert against any number of named snapshots.
197
+ *
198
+ * Every image field is a report-relative POSIX path, matching `TestArtifacts`.
199
+ * A passing comparison carries no `actual` or `diff`: Playwright attaches
200
+ * nothing when the images match, so there is nothing to copy.
201
+ */
202
+ interface VisualComparison {
203
+ /** Snapshot identity within the test, e.g. `home` or `checkout/cart`. */
204
+ readonly name: string;
205
+ readonly status: VisualStatus;
206
+ readonly source: VisualSource;
207
+ /** The committed baseline image. */
208
+ readonly baseline?: string;
209
+ /**
210
+ * Where the committed baseline lives, relative to the Playwright config
211
+ * file's directory (where `test-results` sits), POSIX separators — the
212
+ * file a reviewer overwrites to accept this run.
213
+ *
214
+ * Deliberately not the artifact copy in `baseline`: that path is relative to
215
+ * the report and points at a per-run duplicate, so a tool that "accepted" a
216
+ * run by writing there would leave the baseline Playwright actually reads
217
+ * untouched and the failure would come straight back.
218
+ */
219
+ readonly baselinePath?: string;
220
+ /** What this run rendered. Absent when the comparison passed. */
221
+ readonly actual?: string;
222
+ /** Playwright's rendered diff. Present only on failure. */
223
+ readonly diff?: string;
224
+ /** The previous attempt's render, on a retried failure. */
225
+ readonly previous?: string;
226
+ /** Baseline dimensions, read from the PNG header. */
227
+ readonly width?: number;
228
+ readonly height?: number;
229
+ /** Actual dimensions — set only when they differ from the baseline. */
230
+ readonly actualWidth?: number;
231
+ readonly actualHeight?: number;
232
+ /** Differing pixel count, as reported by Playwright's comparator. */
233
+ readonly diffPixels?: number;
234
+ /** `diffPixels / (width * height)`, computed at full precision. */
235
+ readonly diffPixelRatio?: number;
236
+ /** Cloud storage keys, populated after artifact upload. */
237
+ readonly baselineKey?: string;
238
+ readonly actualKey?: string;
239
+ readonly diffKey?: string;
240
+ /**
241
+ * Which elements changed, ranked by the area they affect.
242
+ *
243
+ * Present only when a DOM baseline existed to compare against. Capped — see
244
+ * `domChangeCount` for the number found before the cap.
245
+ */
246
+ readonly domChanges?: readonly DomChange[];
247
+ /** Total changes found, which may exceed `domChanges.length`. */
248
+ readonly domChangeCount?: number;
249
+ /**
250
+ * Of those, how many actually changed rather than merely shifting position.
251
+ *
252
+ * The gap between this and `domChangeCount` is the layout knock-on: one card
253
+ * gaining a margin moves everything below it, and a report that presents all
254
+ * of those as equals hides the single edit that caused them.
255
+ */
256
+ readonly domRootCauses?: number;
257
+ /** True when the DOM capture itself hit a node or byte cap. */
258
+ readonly domTruncated?: boolean;
259
+ /** Report-relative paths to the two captured DOM snapshots. */
260
+ readonly baselineDom?: string;
261
+ readonly actualDom?: string;
262
+ /**
263
+ * The document-space region the screenshot covered: `[x, y, width, height]`.
264
+ *
265
+ * Element rectangles are in document coordinates, so the report needs this to
266
+ * place a box over the image — for a locator screenshot the image covers only
267
+ * that element's box, not the viewport.
268
+ */
269
+ readonly domCaptureBox?: readonly [number, number, number, number];
270
+ /** Cloud storage keys for the DOM snapshots. */
271
+ readonly baselineDomKey?: string;
272
+ readonly actualDomKey?: string;
273
+ }
274
+ /** How an element differs between the baseline render and this one. */
275
+ type DomChangeKind = 'added' | 'removed' | 'moved' | 'resized' | 'restyled' | 'text'
276
+ /** Its id or class list changed — often the real story behind a restyle. */
277
+ | 'attrs';
278
+ /** One computed style property that differs. */
279
+ interface DomStyleChange {
280
+ readonly prop: string;
281
+ readonly from: string;
282
+ readonly to: string;
283
+ }
284
+ /**
285
+ * One element that differs between two renders.
286
+ *
287
+ * Deliberately shaped like the platform's existing `RegionEvidence` — rank,
288
+ * bbox, area share, a kind and a human label — so that a DOM overlay becomes
289
+ * another caller of the dashboard's existing box-overlay component rather than
290
+ * a second renderer built alongside it.
291
+ */
292
+ interface DomChange {
293
+ /** 1-based, ordered by affected area descending. Drives the overlay colour. */
294
+ readonly rank: number;
295
+ /** Structural path used to match the element across the two snapshots. */
296
+ readonly path: string;
297
+ /** What a human reads, e.g. `div.card.accent`. */
298
+ readonly label: string;
299
+ readonly kinds: readonly DomChangeKind[];
300
+ /** Document-space `[x, y, width, height]` in this render. */
301
+ readonly rect?: readonly [number, number, number, number];
302
+ /** The same, in the baseline. Absent for an added element. */
303
+ readonly baselineRect?: readonly [number, number, number, number];
304
+ /** Share of the capture box this element occupies, 0–1. */
305
+ readonly areaPct: number;
306
+ readonly styles?: readonly DomStyleChange[];
307
+ readonly text?: {
308
+ readonly from: string;
309
+ readonly to: string;
310
+ };
311
+ /** Identity attributes that changed, e.g. `class: "card" → "banner"`. */
312
+ readonly attrs?: readonly DomStyleChange[];
313
+ /**
314
+ * True when this element only moved or resized, in a comparison where
315
+ * something else actually changed.
316
+ *
317
+ * Shifting one card down by 24px shifts everything below it too, so a naive
318
+ * list reports eleven changes for one edit. This flags the ten that merely
319
+ * got pushed around, so a report can lead with the one that did something.
320
+ *
321
+ * It is a statement about this element — that nothing about it changed except
322
+ * its position — and deliberately not a claim to have proven what moved it.
323
+ */
324
+ readonly consequence?: boolean;
127
325
  }
128
326
  type TestStatus = 'passed' | 'failed' | 'skipped' | 'flaky' | 'timedout';
129
327
  type TestType = 'e2e' | 'api' | 'unit' | 'mobile' | 'unknown';
328
+ /** Platform a mobile test drove. */
329
+ type MobilePlatform = 'android' | 'ios' | 'web' | 'unknown';
330
+ /** How the app under test was driven. */
331
+ type AppTestMode = 'native' | 'hybrid' | 'mobile-web';
332
+ /**
333
+ * The device a mobile test ran on.
334
+ *
335
+ * Appium detects all of this from the W3C capabilities and Maestro reads it
336
+ * from the CLI flags and driver log. Both used to collapse it to two words in
337
+ * `tags` before the report saw it; this carries it through intact.
338
+ */
339
+ interface DeviceInfo {
340
+ readonly platform: MobilePlatform;
341
+ /** OS version, e.g. "14" or "17.5". */
342
+ readonly platformVersion: string | null;
343
+ /** Human-facing device name, e.g. "Pixel 4a" or "iPhone 15". */
344
+ readonly deviceName: string | null;
345
+ /** Driver, e.g. "UiAutomator2", "XCUITest", "maestro". */
346
+ readonly automationName: string | null;
347
+ /** App package / bundle id / apk path under test. */
348
+ readonly appId: string | null;
349
+ /** Native, hybrid or mobile-web. Null when the runner cannot tell. */
350
+ readonly appTestMode: AppTestMode | null;
351
+ /** Device serial or UDID, e.g. "emulator-5554". */
352
+ readonly udid: string | null;
353
+ }
354
+ /** Severity of an AI-detected defect. */
355
+ type AiDefectSeverity = 'critical' | 'warning' | 'info';
356
+ /** Category of an AI-detected defect. */
357
+ type AiDefectType = 'ui' | 'spelling' | 'i18n' | 'layout' | 'accessibility';
358
+ /** A defect reported by an AI analysis pass over a captured screen. */
359
+ interface AiDefect {
360
+ readonly type: AiDefectType;
361
+ readonly severity: AiDefectSeverity;
362
+ readonly description: string;
363
+ /** Where in the app it was seen, when the analyser says. */
364
+ readonly location: string | null;
365
+ /** Report-relative path to the screenshot it was found on. */
366
+ readonly screenshot: string | null;
367
+ }
130
368
  interface FailureDiagnostic {
131
369
  readonly message: string;
132
370
  readonly line: number | null;
@@ -205,6 +443,13 @@ interface AssertionLocation {
205
443
  /** Column number (1-based), if available */
206
444
  readonly column?: number;
207
445
  }
446
+ /** A failed assertion reduced to what a summary row needs. */
447
+ interface FailedAssertionSummary {
448
+ readonly type: AssertionType;
449
+ readonly expected: string;
450
+ readonly actual: string;
451
+ readonly expression?: string;
452
+ }
208
453
  /** A single captured assertion on API response data. */
209
454
  interface ApiAssertion {
210
455
  /** Links to ApiCallRecord.id from the API proxy */
@@ -296,10 +541,24 @@ type TimelineStep = NavigationStep | ApiCallStep;
296
541
  * distinct bucket from generic `custom_step`.
297
542
  */
298
543
  type ActionCategory = 'ui_action' | 'assertion' | 'navigation' | 'custom_step';
544
+ /**
545
+ * The driver's own step category, preserved alongside `ActionCategory`.
546
+ *
547
+ * `ActionCategory` is the four-bucket vocabulary the platform's timeline and
548
+ * Ask AI tools filter on, and it stays closed. Mobile drivers distinguish far
549
+ * more than four kinds — Appium reports eleven, Maestro ten — and flattening
550
+ * them made a swipe, an app launch and a screenshot render identically. This
551
+ * keeps the finer label for the report to badge.
552
+ */
553
+ type StepKind = 'find' | 'interaction' | 'gesture' | 'navigation' | 'lifecycle' | 'context' | 'device' | 'keyboard' | 'alert' | 'screenshot' | 'assertion' | 'media' | 'script' | 'flow_control' | 'ai' | 'other';
299
554
  /** A single captured test action step. */
300
555
  interface ActionStep {
301
556
  readonly title: string;
302
557
  readonly category: ActionCategory;
558
+ /** The driver's own finer-grained category, when it reported one. */
559
+ readonly kind?: StepKind | null;
560
+ /** Element selector this step acted on, when the driver reported one. */
561
+ readonly selector?: string | null;
303
562
  readonly timestamp: string;
304
563
  readonly duration: number;
305
564
  readonly videoOffset: number | null;
@@ -310,6 +569,14 @@ interface ActionStep {
310
569
  /** Level of a captured console/terminal log message. */
311
570
  type ConsoleLogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' | 'stdout' | 'stderr';
312
571
  /** A single captured console or terminal log entry. */
572
+ /**
573
+ * Where a captured log line came from.
574
+ *
575
+ * Browser consoles only ever produce `console`. Mobile runs mix several
576
+ * streams, and telling a crash log from ordinary logcat chatter is the whole
577
+ * point of showing them — so the stream survives to the row.
578
+ */
579
+ type LogSource = 'console' | 'logcat' | 'syslog' | 'crashlog' | 'browser' | 'safariConsole' | 'performance' | 'server' | 'bidi' | 'driver';
313
580
  interface ConsoleLogEntry {
314
581
  /** Log level or stream type */
315
582
  readonly level: ConsoleLogLevel;
@@ -319,6 +586,14 @@ interface ConsoleLogEntry {
319
586
  readonly timestamp: string;
320
587
  /** Source file location (browser console only, best-effort) */
321
588
  readonly location: string | null;
589
+ /** Which stream produced the line. Defaults to `console` when absent. */
590
+ readonly source?: LogSource | null;
591
+ /** Emitting tag, e.g. an Android logcat tag like "ActivityManager". */
592
+ readonly tag?: string | null;
593
+ /** Emitting process id, when the stream reports one. */
594
+ readonly pid?: number | null;
595
+ /** Stack trace attached to the line (crash logs). */
596
+ readonly stackTrace?: string | null;
322
597
  }
323
598
  /** Report generation mode. */
324
599
  type ReportMode = 'streaming' | 'embedded' | 'auto';
@@ -453,6 +728,14 @@ interface TestRelicFilePayload {
453
728
  readonly apiCallsFile: string | null;
454
729
  /** Number of API call items in the file. */
455
730
  readonly apiCallsCount: number;
731
+ /**
732
+ * Response-status mix of the calls in the file.
733
+ *
734
+ * Tallied as each record is appended. The file itself is never read back
735
+ * into memory in streaming mode, so an aggregate computed at write time is
736
+ * the only way the grid can show a row's status chips.
737
+ */
738
+ readonly apiStatusMix?: ApiCallsByStatusRange | null;
456
739
  }
457
740
  /** Type guard: validates that a parsed object is a v2 file-based payload. */
458
741
  declare function isTestRelicFilePayload(obj: unknown): obj is TestRelicFilePayload;
@@ -467,7 +750,21 @@ interface CloudConfig {
467
750
  readonly apiKey: string | null;
468
751
  readonly endpoint: string;
469
752
  readonly uploadStrategy: UploadStrategy;
753
+ /** Per-request ceiling. ⚠ It governed only the auth handshake until 2.14.0 —
754
+ * the upload calls used bare `fetch` and inherited undici's 5-minute
755
+ * defaults, so a tester who set this had bounded the token exchange and
756
+ * nothing else. It now bounds the upload requests too. */
470
757
  readonly timeout: number;
758
+ /** Total time the whole upload phase may take before it gives up, writes the
759
+ * queue and lets the process exit. The phase runs inside Playwright's
760
+ * `onEnd`, so this is the difference between a late run and a run that never
761
+ * finishes. Default 120s.
762
+ *
763
+ * ⛔ OPTIONAL on purpose. `CloudConfig` is built by every analytics package
764
+ * (appium, maestro, playwright), and making a new field required breaks all
765
+ * of them at once for a knob only one upload path reads — CI caught exactly
766
+ * that. Callers resolve it as `?? DEFAULT_UPLOAD_BUDGET_MS`. */
767
+ readonly uploadBudgetMs?: number;
471
768
  readonly projectName: string | null;
472
769
  readonly queueMaxAge: number;
473
770
  readonly queueDirectory: string;
@@ -518,6 +815,8 @@ interface CloudReporterOptions {
518
815
  readonly endpoint?: string;
519
816
  readonly upload?: UploadStrategy;
520
817
  readonly timeout?: number;
818
+ /** Total budget for the upload phase (ms). See CloudConfig.uploadBudgetMs. */
819
+ readonly uploadBudgetMs?: number;
521
820
  readonly projectName?: string;
522
821
  readonly queueMaxAge?: string;
523
822
  readonly queueDirectory?: string;
@@ -530,10 +829,10 @@ interface MergeOptions {
530
829
  }
531
830
  /** A single artifact file within a test's artifact folder. */
532
831
  interface ArtifactFile {
533
- /** File name (e.g., `screenshot.png`, `video.webm`) */
832
+ /** File name (e.g., `screenshot.png`, `video.webm`, `trace.zip`) */
534
833
  readonly name: string;
535
834
  /** Artifact type for rendering */
536
- readonly type: 'screenshot' | 'video' | 'other';
835
+ readonly type: 'screenshot' | 'video' | 'trace' | 'baseline' | 'diff' | 'dom' | 'other';
537
836
  /** Path relative to the report output directory */
538
837
  readonly relativePath: string;
539
838
  /** File size in bytes */
@@ -596,6 +895,8 @@ interface TestIndexEntry {
596
895
  readonly retryIndex: number;
597
896
  /** Test tags */
598
897
  readonly tags: readonly string[];
898
+ /** What kind of test this is — badges the row, and picks the row's evidence. */
899
+ readonly testType?: TestType;
599
900
  /** Whether this test has network request data */
600
901
  readonly hasNetworkData: boolean;
601
902
  /** Whether this test has console log data */
@@ -604,6 +905,20 @@ interface TestIndexEntry {
604
905
  readonly hasArtifacts: boolean;
605
906
  /** Whether this test has action step data */
606
907
  readonly hasActionSteps: boolean;
908
+ /** Number of visual baseline comparisons recorded */
909
+ readonly visualCount?: number;
910
+ /** Of those, how many differed from their baseline */
911
+ readonly visualFailedCount?: number;
912
+ /** DOM elements that differed across this test's comparisons */
913
+ readonly domChangeCount?: number;
914
+ /**
915
+ * Report-relative path to a diff image for this test, when one exists.
916
+ *
917
+ * Carried on the index so the run overview can show the failure itself
918
+ * rather than only describing it — the grid must never fetch per-test
919
+ * detail files to render.
920
+ */
921
+ readonly visualDiffPath?: string;
607
922
  /** First line of error message (failed/flaky tests only) */
608
923
  readonly errorMessage: string | null;
609
924
  /** Number of network requests captured */
@@ -612,8 +927,38 @@ interface TestIndexEntry {
612
927
  readonly consoleCount: number;
613
928
  /** Number of action steps captured */
614
929
  readonly actionCount: number;
930
+ /** API calls captured for this test. */
931
+ readonly apiCallCount?: number;
932
+ /** Response-status mix of those calls, for the row's status chips. */
933
+ readonly apiStatusMix?: ApiCallsByStatusRange;
934
+ /** API assertions that passed during this test. */
935
+ readonly assertionsPassed?: number;
936
+ /** API assertions that failed during this test. */
937
+ readonly assertionsFailed?: number;
938
+ /**
939
+ * The first failed assertion, flattened to strings.
940
+ *
941
+ * The run overview leads with "expected 101, received undefined" and must
942
+ * do so without opening the test's detail file. The first line of an API
943
+ * failure message is the matcher wrapper, which says nothing.
944
+ */
945
+ readonly firstFailedAssertion?: FailedAssertionSummary;
946
+ /** The device this test drove (mobile runs only). */
947
+ readonly device?: DeviceInfo | null;
948
+ /** Action steps for this test, bucketed by the driver's own category. */
949
+ readonly stepKinds?: Record<string, number>;
950
+ /** Device-log errors and fatals recorded during this test. */
951
+ readonly deviceLogErrors?: number;
952
+ /** Whether a crash-log line was recorded during this test. */
953
+ readonly hasCrashLog?: boolean;
954
+ /** Whether a screen recording exists for this test. */
955
+ readonly hasVideo?: boolean;
956
+ /** AI-detected defects attributed to this test. */
957
+ readonly aiDefectCount?: number;
615
958
  /** Whether this is a retry attempt (not the final attempt) */
616
959
  readonly isRetry: boolean;
960
+ /** Stable key shared by every attempt of the same test (same across retries, unlike `id`) */
961
+ readonly retryGroupKey: string;
617
962
  }
618
963
  /**
619
964
  * Full detail data for a single test, stored on disk.
@@ -633,6 +978,8 @@ interface TestDetailData {
633
978
  readonly actions: readonly ActionStep[];
634
979
  /** References to screenshot/video files */
635
980
  readonly artifacts: TestArtifacts | null;
981
+ /** Visual baseline comparisons (inline — a handful per test at most) */
982
+ readonly visual?: readonly VisualComparison[] | null;
636
983
  /** Error details for failed/flaky tests */
637
984
  readonly failureDiagnostic: FailureDiagnostic | null;
638
985
  /** Whether network.jsonl exists for this test */
@@ -649,6 +996,10 @@ interface TestDetailData {
649
996
  readonly apiCallsCount: number;
650
997
  /** Test start time (ISO 8601) — used for video sync */
651
998
  readonly startedAt?: string;
999
+ /** The device this test drove (mobile runs only). */
1000
+ readonly device?: DeviceInfo | null;
1001
+ /** AI-detected defects attributed to this test. */
1002
+ readonly aiDefects?: readonly AiDefect[] | null;
652
1003
  }
653
1004
  /** Streaming report summary — running counters written to summary.json. */
654
1005
  interface StreamingReportSummary {
@@ -802,4 +1153,4 @@ declare function isValidCloudConfig(input: unknown): input is CloudConfig;
802
1153
  declare function isValidQueueEntry(input: unknown): input is QueueEntry;
803
1154
  declare function isValidTestRunReport(value: unknown): value is TestRunReport;
804
1155
 
805
- export { ATTACHMENT_CONTENT_TYPE, ATTACHMENT_NAME, type ActionCategory, type ActionStep, type ApiAssertion, type ApiCallRecord, type ApiCallStep, type ApiCallStepRequest, type ApiCallStepResponse, type ArtifactFile, type ArtifactRunEntry, type ArtifactRunManifest, type ArtifactTestEntry, type AssertionLocation, type AssertionType, type AuthMode, type AuthState, type CIMetadata, type CIProvider, type CapturedNetworkRequest, type CloudConfig, type CloudReporterOptions, type ConsoleLogEntry, type ConsoleLogLevel, ErrorCode, type FailureDiagnostic, type GitMetadata, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationStep, type NavigationType, type NetworkStats, PAYLOAD_VERSION, PAYLOAD_VERSION_LEGACY, type QueueEntry, type RepoResolution, type ReportMode, type ReporterConfig, type ResourceBreakdown, type ResourceType, type StepAssertion, type StepTestIdentity, type StreamingFileStats, type StreamingReportManifest, type StreamingReportSummary, type StreamingValidation, type StreamingWriteError, type Summary, type TestArtifacts, type TestDetailData, type TestIndexEntry, type TestRelicDataPayload, TestRelicError, type TestRelicFilePayload, type TestResult, type TestRunReport, type TestStatus, type TestType, type TimelineEntry, type TimelineStep, type UploadStrategy, createError, createLogger, isTestRelicDataPayload, isTestRelicFilePayload, isValidApiKeyFormat, isValidCloudConfig, isValidConfig, isValidEndpointUrl, isValidNavigationType, isValidQueueEntry, isValidTestRunReport, isValidUploadStrategy };
1156
+ export { ATTACHMENT_CONTENT_TYPE, ATTACHMENT_NAME, type ActionCategory, type ActionStep, type AiDefect, type AiDefectSeverity, type AiDefectType, type ApiAssertion, type ApiCallRecord, type ApiCallStep, type ApiCallStepRequest, type ApiCallStepResponse, type ApiCallsByStatusRange, type ApiResponseTimeStats, type AppTestMode, type ArtifactFile, type ArtifactRunEntry, type ArtifactRunManifest, type ArtifactTestEntry, type AssertionLocation, type AssertionType, type AuthMode, type AuthState, type CIMetadata, type CIProvider, type CapturedCookie, type CapturedNetworkRequest, type CloudConfig, type CloudReporterOptions, type ConsoleLogEntry, type ConsoleLogLevel, type DeviceInfo, type DomChange, type DomChangeKind, type DomStyleChange, ErrorCode, type FailedAssertionSummary, type FailureDiagnostic, type GitMetadata, type LogLevel, type LogSource, type Logger, type LoggerOptions, type MergeOptions, type MobilePlatform, type NavigationAnnotation, type NavigationStep, type NavigationType, type NetworkStats, PAYLOAD_VERSION, PAYLOAD_VERSION_LEGACY, type QueueEntry, type RepoResolution, type ReportMode, type ReporterConfig, type ResourceBreakdown, type ResourceType, type StepAssertion, type StepKind, type StepTestIdentity, type StreamingFileStats, type StreamingReportManifest, type StreamingReportSummary, type StreamingValidation, type StreamingWriteError, type Summary, type TestArtifacts, type TestDetailData, type TestIndexEntry, type TestRelicDataPayload, TestRelicError, type TestRelicFilePayload, type TestResult, type TestRunReport, type TestStatus, type TestType, type TimelineEntry, type TimelineStep, type UploadStrategy, type VisualComparison, type VisualSource, type VisualStatus, createError, createLogger, isTestRelicDataPayload, isTestRelicFilePayload, isValidApiKeyFormat, isValidCloudConfig, isValidConfig, isValidEndpointUrl, isValidNavigationType, isValidQueueEntry, isValidTestRunReport, isValidUploadStrategy };