@molecule/api-ai-tools 1.9.15 → 1.9.16

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/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-18T11:04:38.283Z
6
+ Generated: 2026-10-18T12:49:51.606Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -426,6 +426,14 @@ interface TraceRequest {
426
426
  * credential-less request from one whose credential is a cookie.
427
427
  */
428
428
  sentCookie?: boolean
429
+ /**
430
+ * The trace member this request came from — the browser context Playwright numbered (`1`, `3`).
431
+ * A test that opens a second browser context writes one trace member per context, and a test's
432
+ * request is only evidence about the page it was made on (xr-molecule-flash-m160).
433
+ */
434
+ context?: string
435
+ /** The page the request was made on, as the HAR entry's `pageref` records it. */
436
+ pageId?: string
429
437
  }
430
438
  ```
431
439
 
@@ -472,6 +480,35 @@ interface TraceAction {
472
480
  start: number
473
481
  /** When it finished, or null when the run ended before it did. */
474
482
  end: number | null
483
+ /** The browser context the action was recorded in (`1`, `3`), when the trace names one. */
484
+ context?: string
485
+ /** The page the action ran on, from the frame snapshot the trace paired with it. */
486
+ pageId?: string
487
+ /** The frame the action ran in, from the same snapshot. */
488
+ frameId?: string
489
+ }
490
+ ```
491
+
492
+ #### `TraceContextEvidence`
493
+
494
+ What a browser context OTHER than the failing page's recorded — the second context a
495
+ two-context test opens for its own setup.
496
+
497
+ Its actions and requests belong to a different page: a request it sent is not the failing
498
+ action's effect, and the URL it left the page on is not where the assertion's page went
499
+ (xr-molecule-flash-m160: the helper context created an article in an editor page while the
500
+ primary page moved to the profile the assertion then failed on, and the note called the
501
+ editor page the failing page).
502
+
503
+ ```typescript
504
+ interface TraceContextEvidence {
505
+ /** The trace member's context id (`3`). */
506
+ context: string
507
+ /**
508
+ * The app requests that context made, bounded — failures first, then the answered ones — each
509
+ * labelled by the note as set-up work rather than the failing page's.
510
+ */
511
+ requests: TraceRequest[]
475
512
  }
476
513
  ```
477
514
 
@@ -506,19 +543,44 @@ interface TraceArtifact {
506
543
  */
507
544
  console: string[]
508
545
  /**
509
- * The test's LAST user action before the failing assertion and the requests it caused — what the
510
- * row leads with, when the trace carries the action times ({@link TraceLastAction}).
546
+ * The test's LAST user action before the failing assertion ON THE PAGE THE ASSERTION RAN ON, and
547
+ * the requests it caused — what the row leads with, when the trace carries the action times
548
+ * ({@link TraceLastAction}).
511
549
  *
512
- * Absent on traces read before this field existed, and on traces whose network entries recorded
513
- * no clock; the row then falls back to {@link lead}.
550
+ * A test that opens a second browser context writes one trace member per context, and the
551
+ * archive does not order them by time: the last action of the concatenation may belong to the
552
+ * helper's page while the assertion failed on another (xr-molecule-flash-m160). The action is
553
+ * therefore the failing page's own, and its `pageAfter` is that page's last frame snapshot.
554
+ *
555
+ * Absent on traces read before this field existed, on traces whose network entries recorded no
556
+ * clock, and on a trace with more than one used context whose failing page cannot be identified
557
+ * ({@link TraceArtifact.unattributed}) — the row then falls back to {@link lead}, or to no
558
+ * causal claim at all.
514
559
  */
515
560
  lastAction?: TraceLastAction
516
561
  /**
517
562
  * The page's own actions in the order the trace recorded them, bounded — what a test that ran
518
563
  * out of a per-test budget spends its time on. A test that repeats one action prints it once per
519
- * repeat, so the gap between two starts is one repeat's measured cost.
564
+ * repeat, so the gap between two starts is one repeat's measured cost. Scoped to the page the
565
+ * failing assertion ran on; a second browser context's actions are not this test's page.
520
566
  */
521
567
  actions: TraceAction[]
568
+ /**
569
+ * True when the trace held more than one browser context and none of them could be tied to the
570
+ * failing assertion's page. The note then makes no causal claim — no "the test's last action",
571
+ * no "the page stayed at …" — rather than attributing one context's click and URL to another
572
+ * context's assertion (xr-molecule-flash-m160).
573
+ */
574
+ unattributed?: true
575
+ /**
576
+ * The browser contexts the test opened BESIDES the failing assertion's page, with the app
577
+ * requests each made. Kept apart so the note can name them as set-up work instead of printing
578
+ * their requests as the failing action's effects (xr-molecule-flash-m160).
579
+ *
580
+ * Absent when the trace recorded one context only, when every other context made no request,
581
+ * and on traces read before this field existed.
582
+ */
583
+ otherContexts?: TraceContextEvidence[]
522
584
  }
523
585
  ```
524
586
 
@@ -1905,9 +1967,9 @@ over anything else when it is the page talking to the app (`fetch`/`xhr`/`docume
1905
1967
  test's trace holds the whole page load. A request with no response and NO error text was aborted
1906
1968
  or still in flight when the test ended; it is kept too, but as what it is, not as a failure.
1907
1969
 
1908
- A request answered 4xx by the app's OWN API also keeps what the page SENT and the first of what
1909
- came back: Playwright stores either inline (`text`) or in a resource entry (`_file`), and both
1910
- are read. That is where a wrapped body shows: xr-molecule-flash-m111's helper passed
1970
+ A trace.zip holds ONE trace member per browser context the test used (beside the runner's own
1971
+ `test.trace`), and the archive's member order is not the test's chronology. Each member is read
1972
+ as its own context: actions and requests keep their context id, requests and frame snapshots
1911
1973
 
1912
1974
  _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/trace-artifacts.d.ts`.)_
1913
1975
 
@@ -89,6 +89,14 @@ export interface TraceRequest {
89
89
  * credential-less request from one whose credential is a cookie.
90
90
  */
91
91
  sentCookie?: boolean;
92
+ /**
93
+ * The trace member this request came from — the browser context Playwright numbered (`1`, `3`).
94
+ * A test that opens a second browser context writes one trace member per context, and a test's
95
+ * request is only evidence about the page it was made on (xr-molecule-flash-m160).
96
+ */
97
+ context?: string;
98
+ /** The page the request was made on, as the HAR entry's `pageref` records it. */
99
+ pageId?: string;
92
100
  }
93
101
  /**
94
102
  * The test's LAST user action before the failing assertion, and the requests it caused.
@@ -125,6 +133,31 @@ export interface TraceAction {
125
133
  start: number;
126
134
  /** When it finished, or null when the run ended before it did. */
127
135
  end: number | null;
136
+ /** The browser context the action was recorded in (`1`, `3`), when the trace names one. */
137
+ context?: string;
138
+ /** The page the action ran on, from the frame snapshot the trace paired with it. */
139
+ pageId?: string;
140
+ /** The frame the action ran in, from the same snapshot. */
141
+ frameId?: string;
142
+ }
143
+ /**
144
+ * What a browser context OTHER than the failing page's recorded — the second context a
145
+ * two-context test opens for its own setup.
146
+ *
147
+ * Its actions and requests belong to a different page: a request it sent is not the failing
148
+ * action's effect, and the URL it left the page on is not where the assertion's page went
149
+ * (xr-molecule-flash-m160: the helper context created an article in an editor page while the
150
+ * primary page moved to the profile the assertion then failed on, and the note called the
151
+ * editor page the failing page).
152
+ */
153
+ export interface TraceContextEvidence {
154
+ /** The trace member's context id (`3`). */
155
+ context: string;
156
+ /**
157
+ * The app requests that context made, bounded — failures first, then the answered ones — each
158
+ * labelled by the note as set-up work rather than the failing page's.
159
+ */
160
+ requests: TraceRequest[];
128
161
  }
129
162
  /** What one trace says: the test it belongs to, the requests it made, and its console lines. */
130
163
  export interface TraceArtifact {
@@ -153,19 +186,44 @@ export interface TraceArtifact {
153
186
  */
154
187
  console: string[];
155
188
  /**
156
- * The test's LAST user action before the failing assertion and the requests it caused — what the
157
- * row leads with, when the trace carries the action times ({@link TraceLastAction}).
189
+ * The test's LAST user action before the failing assertion ON THE PAGE THE ASSERTION RAN ON, and
190
+ * the requests it caused — what the row leads with, when the trace carries the action times
191
+ * ({@link TraceLastAction}).
158
192
  *
159
- * Absent on traces read before this field existed, and on traces whose network entries recorded
160
- * no clock; the row then falls back to {@link lead}.
193
+ * A test that opens a second browser context writes one trace member per context, and the
194
+ * archive does not order them by time: the last action of the concatenation may belong to the
195
+ * helper's page while the assertion failed on another (xr-molecule-flash-m160). The action is
196
+ * therefore the failing page's own, and its `pageAfter` is that page's last frame snapshot.
197
+ *
198
+ * Absent on traces read before this field existed, on traces whose network entries recorded no
199
+ * clock, and on a trace with more than one used context whose failing page cannot be identified
200
+ * ({@link TraceArtifact.unattributed}) — the row then falls back to {@link lead}, or to no
201
+ * causal claim at all.
161
202
  */
162
203
  lastAction?: TraceLastAction;
163
204
  /**
164
205
  * The page's own actions in the order the trace recorded them, bounded — what a test that ran
165
206
  * out of a per-test budget spends its time on. A test that repeats one action prints it once per
166
- * repeat, so the gap between two starts is one repeat's measured cost.
207
+ * repeat, so the gap between two starts is one repeat's measured cost. Scoped to the page the
208
+ * failing assertion ran on; a second browser context's actions are not this test's page.
167
209
  */
168
210
  actions: TraceAction[];
211
+ /**
212
+ * True when the trace held more than one browser context and none of them could be tied to the
213
+ * failing assertion's page. The note then makes no causal claim — no "the test's last action",
214
+ * no "the page stayed at …" — rather than attributing one context's click and URL to another
215
+ * context's assertion (xr-molecule-flash-m160).
216
+ */
217
+ unattributed?: true;
218
+ /**
219
+ * The browser contexts the test opened BESIDES the failing assertion's page, with the app
220
+ * requests each made. Kept apart so the note can name them as set-up work instead of printing
221
+ * their requests as the failing action's effects (xr-molecule-flash-m160).
222
+ *
223
+ * Absent when the trace recorded one context only, when every other context made no request,
224
+ * and on traces read before this field existed.
225
+ */
226
+ otherContexts?: TraceContextEvidence[];
169
227
  }
170
228
  /** How many traces one read reports, newest first. */
171
229
  export declare const MAX_TRACE_ARTIFACTS = 12;
@@ -213,6 +271,13 @@ export declare const TRACE_NOTE_LEAD_GOT_CHARS = 200;
213
271
  * test's trace holds the whole page load. A request with no response and NO error text was aborted
214
272
  * or still in flight when the test ended; it is kept too, but as what it is, not as a failure.
215
273
  *
274
+ * A trace.zip holds ONE trace member per browser context the test used (beside the runner's own
275
+ * `test.trace`), and the archive's member order is not the test's chronology. Each member is read
276
+ * as its own context: actions and requests keep their context id, requests and frame snapshots
277
+ * keep the pageId Playwright records, and the failing assertion's page — the page its own step id
278
+ * ran on — decides which actions, snapshots and requests become the test's row. What the test's
279
+ * other browser contexts recorded is reported apart, as `otherContexts` (xr-molecule-flash-m160).
280
+ *
216
281
  * A request answered 4xx by the app's OWN API also keeps what the page SENT and the first of what
217
282
  * came back: Playwright stores either inline (`text`) or in a resource entry (`_file`), and both
218
283
  * are read. That is where a wrapped body shows: xr-molecule-flash-m111's helper passed
@@ -346,6 +411,14 @@ export interface TraceRunReport {
346
411
  * (answered 201, the page still rendering signed-out) never appeared. With no action clock, the
347
412
  * row falls back to {@link TraceArtifact.lead}: the page's last answered fetch/xhr.
348
413
  *
414
+ * The action, the URL and the requests are the FAILING ASSERTION'S PAGE's ({@link
415
+ * TraceArtifact.lastAction}), never the archive's last context: a test that creates its data in a
416
+ * second browser context writes that context's click and `/editor` snapshot into the same zip, and
417
+ * reading the concatenation made them the failing page (xr-molecule-flash-m160). The other
418
+ * contexts' requests are named separately as set-up work. When the trace holds more than one used
419
+ * context and the assertion's page cannot be identified, the note lists the requests and makes NO
420
+ * causal claim — no "the test's last action", no "the page stayed at …" — instead of merging them.
421
+ *
349
422
  * WHICH rows it spends is evidence, not artifact order (xr-molecule-flash-m112 +53:46): a row
350
423
  * needs a recorded response, a real failure or a console line ({@link traceRowEvidence}), rows are
351
424
  * deduplicated by the test's location, and they are ordered real failure → repeated from an
@@ -1 +1 @@
1
- {"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"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"}
@@ -84,6 +84,13 @@ const TRACE_NOTE_FAILURE_CHARS = 120;
84
84
  * test's trace holds the whole page load. A request with no response and NO error text was aborted
85
85
  * or still in flight when the test ended; it is kept too, but as what it is, not as a failure.
86
86
  *
87
+ * A trace.zip holds ONE trace member per browser context the test used (beside the runner's own
88
+ * `test.trace`), and the archive's member order is not the test's chronology. Each member is read
89
+ * as its own context: actions and requests keep their context id, requests and frame snapshots
90
+ * keep the pageId Playwright records, and the failing assertion's page — the page its own step id
91
+ * ran on — decides which actions, snapshots and requests become the test's row. What the test's
92
+ * other browser contexts recorded is reported apart, as `otherContexts` (xr-molecule-flash-m160).
93
+ *
87
94
  * A request answered 4xx by the app's OWN API also keeps what the page SENT and the first of what
88
95
  * came back: Playwright stores either inline (`text`) or in a resource entry (`_file`), and both
89
96
  * are read. That is where a wrapped body shows: xr-molecule-flash-m111's helper passed
@@ -276,26 +283,69 @@ const found = []
276
283
  walk(ROOT, 5, found)
277
284
  found.sort(function (a, b) { return b.mtime - a.mtime })
278
285
 
286
+ // A trace.zip holds ONE trace member per browser context the test used (1-trace.trace,
287
+ // 3-trace.trace, …) beside the test runner's own test.trace. The archive ORDER of those members
288
+ // is not the test's chronology: xr-molecule-flash-m160's helper context was written after the
289
+ // primary page's, so the concatenation's last user action was the helper's editor click and the
290
+ // last frame snapshot was /editor, while the assertion had failed on the primary page at
291
+ // /profile/:username. Each member is read as its own context, and everything below attributes a
292
+ // request, an action and a frame snapshot to the page and context that recorded it.
293
+ function traceContextOf(name) {
294
+ if (name === 'test.trace') return 'test'
295
+ if (name === 'trace.trace') return '0'
296
+ const m = /^(.+)-trace\\.trace$/.exec(name)
297
+ return m === null ? null : m[1]
298
+ }
299
+ function networkContextOf(name) {
300
+ if (name === 'trace.network') return '0'
301
+ const m = /^(.+)-trace\\.network$/.exec(name)
302
+ return m === null ? null : m[1]
303
+ }
304
+
279
305
  for (const one of found.slice(0, MAX_TRACES)) {
280
306
  let buf
281
307
  try { buf = fs.readFileSync(one.file) } catch (e) { continue }
282
308
  let entries
283
309
  try { entries = zipEntries(buf) } catch (e) { continue }
284
- const network = []
285
- const trace = []
310
+ const runner = []
311
+ const contexts = []
312
+ const contextById = {}
313
+ function contextFor(id) {
314
+ let ctx = contextById[id]
315
+ if (!ctx) {
316
+ ctx = { id: id, trace: [], network: [], requests: [], actions: [], frames: [] }
317
+ contextById[id] = ctx
318
+ contexts.push(ctx)
319
+ }
320
+ return ctx
321
+ }
286
322
  for (const entry of entries) {
287
- if (/\\.network$/.test(entry.name)) network.push.apply(network, ndjson(zipData(buf, entry)))
288
- else if (/\\.trace$/.test(entry.name)) trace.push.apply(trace, ndjson(zipData(buf, entry)))
323
+ const traceContext = traceContextOf(entry.name)
324
+ if (traceContext === 'test') {
325
+ runner.push.apply(runner, ndjson(zipData(buf, entry)))
326
+ continue
327
+ }
328
+ if (traceContext !== null) {
329
+ const ctx = contextFor(traceContext)
330
+ ctx.trace.push.apply(ctx.trace, ndjson(zipData(buf, entry)))
331
+ continue
332
+ }
333
+ const networkContext = networkContextOf(entry.name)
334
+ if (networkContext !== null) {
335
+ const ctx = contextFor(networkContext)
336
+ ctx.network.push.apply(ctx.network, ndjson(zipData(buf, entry)))
337
+ }
289
338
  }
290
- const requests = []
291
- for (const line of network) {
292
- if (!line || line.type !== 'resource-snapshot') continue
293
- const snapshot = line.snapshot || {}
294
- const request = snapshot.request || {}
295
- if (!request.url || !request.method) continue
296
- const response = snapshot.response || {}
297
- const type = String(snapshot._resourceType || '')
298
- if (!origin) origin = type === 'document' ? originOf(String(request.url)) : ''
339
+ const requestsAll = []
340
+ for (const ctx of contexts) {
341
+ for (const line of ctx.network) {
342
+ if (!line || line.type !== 'resource-snapshot') continue
343
+ const snapshot = line.snapshot || {}
344
+ const request = snapshot.request || {}
345
+ if (!request.url || !request.method) continue
346
+ const response = snapshot.response || {}
347
+ const type = String(snapshot._resourceType || '')
348
+ if (!origin) origin = type === 'document' ? originOf(String(request.url)) : ''
299
349
  // What the request carried for CREDENTIALS — never the secret itself. A 401 on the app's own
300
350
  // API cannot be read without this: m148's favorite POST answered 401 with no Authorization
301
351
  // header at all, so the client never attached the token, and the note sent the run to the
@@ -312,51 +362,213 @@ for (const one of found.slice(0, MAX_TRACES)) {
312
362
  const authSpace = authValue.search(/\\s/)
313
363
  const authWord = authSpace > 0 ? authValue.slice(0, authSpace) : ''
314
364
  const authScheme = /^[A-Za-z][A-Za-z0-9._~+/-]{0,23}$/.test(authWord) ? authWord : undefined
315
- const sentCookie = headerList.some(function (h) {
316
- return h && typeof h.name === 'string' && /^cookie$/i.test(h.name)
317
- })
318
- requests.push({
319
- method: String(request.method).toUpperCase(),
320
- path: String(request.url).slice(0, MAX_URL),
321
- status: Number(response.status) || 0,
322
- failure: typeof response._failureText === 'string' ? response._failureText : undefined,
323
- type: type,
324
- sentAuthorization: authHeader !== undefined,
325
- authorization: authScheme,
326
- sentCookie: sentCookie,
327
- // The HAR entry carries the monotonic clock the trace's action entries use, so a request
328
- // can be attributed to the action that caused it (see \`lastAction\` below).
329
- startedAt: typeof snapshot._monotonicTime === 'number' ? snapshot._monotonicTime : undefined,
330
- postData: request.postData,
331
- content: response.content,
332
- })
365
+ const sentCookie = headerList.some(function (h) {
366
+ return h && typeof h.name === 'string' && /^cookie$/i.test(h.name)
367
+ })
368
+ const parsed = {
369
+ method: String(request.method).toUpperCase(),
370
+ path: String(request.url).slice(0, MAX_URL),
371
+ status: Number(response.status) || 0,
372
+ failure: typeof response._failureText === 'string' ? response._failureText : undefined,
373
+ type: type,
374
+ sentAuthorization: authHeader !== undefined,
375
+ authorization: authScheme,
376
+ sentCookie: sentCookie,
377
+ // The HAR entry carries the monotonic clock the trace's action entries use, so a request
378
+ // can be attributed to the action that caused it (see \`lastAction\` below).
379
+ startedAt: typeof snapshot._monotonicTime === 'number' ? snapshot._monotonicTime : undefined,
380
+ // The context member and the page ref the HAR entry records: a request made by the test's
381
+ // helper context is not the failing page's, however late the archive wrote it (m160).
382
+ context: ctx.id,
383
+ pageId: typeof snapshot.pageref === 'string' ? snapshot.pageref : undefined,
384
+ postData: request.postData,
385
+ content: response.content,
386
+ }
387
+ ctx.requests.push(parsed)
388
+ requestsAll.push(parsed)
389
+ }
333
390
  }
334
391
  if (!origin) {
335
- const first = requests.find(function (r) { return r.type === 'document' }) || requests[0]
392
+ const first = requestsAll.find(function (r) { return r.type === 'document' }) || requestsAll[0]
336
393
  origin = first ? originOf(first.path) : ''
337
394
  }
338
- for (const r of requests) r.path = pathOf(r.path).slice(0, MAX_URL)
395
+ for (const r of requestsAll) r.path = pathOf(r.path).slice(0, MAX_URL)
339
396
  // The page's own actions, timed. before/after pair on the call id, and only the LIBRARY
340
397
  // traces carry a class and a method (Frame.goto, Locator.fill); the test runner's own
341
- // trace times its hooks and fixtures with class Test, which is not a page action. Read BEFORE
342
- // the request buckets: whether the navigation itself finished decides whether a subresource
343
- // with no response is evidence (see \`navigationOpen\` below).
344
- const ended = {}
345
- for (const entry of trace) {
346
- if (entry && entry.type === 'after' && typeof entry.callId === 'string' && typeof entry.endTime === 'number')
347
- ended[entry.callId] = entry.endTime
398
+ // trace times its hooks and fixtures with class Test, which is not a page action. Each action
399
+ // keeps the context it came from and the page/frame its frame snapshot names, so the failing
400
+ // assertion's own page can be selected below (m160).
401
+ const actionsAll = []
402
+ const framesAll = []
403
+ for (const ctx of contexts) {
404
+ const ended = {}
405
+ for (const entry of ctx.trace) {
406
+ if (entry && entry.type === 'after' && typeof entry.callId === 'string' && typeof entry.endTime === 'number')
407
+ ended[entry.callId] = entry.endTime
408
+ }
409
+ const pageByCall = {}
410
+ for (const entry of ctx.trace) {
411
+ if (!entry || entry.type !== 'frame-snapshot') continue
412
+ const snap = entry.snapshot || {}
413
+ if (typeof snap.callId === 'string' && typeof snap.pageId === 'string' && pageByCall[snap.callId] === undefined)
414
+ pageByCall[snap.callId] = snap.pageId
415
+ if (snap.isMainFrame === false) continue
416
+ if (typeof snap.frameUrl !== 'string' || typeof snap.timestamp !== 'number') continue
417
+ ctx.frames.push({
418
+ url: pathOf(snap.frameUrl).slice(0, MAX_URL),
419
+ at: snap.timestamp,
420
+ context: ctx.id,
421
+ pageId: typeof snap.pageId === 'string' ? snap.pageId : undefined,
422
+ frameId: typeof snap.frameId === 'string' ? snap.frameId : undefined,
423
+ })
424
+ }
425
+ for (const entry of ctx.trace) {
426
+ if (!entry || entry.type !== 'before') continue
427
+ if (typeof entry.class !== 'string' || typeof entry.method !== 'string') continue
428
+ if (entry.class === 'Test' || typeof entry.startTime !== 'number') continue
429
+ const action = {
430
+ api: entry.class + '.' + entry.method,
431
+ start: entry.startTime,
432
+ end: typeof ended[entry.callId] === 'number' ? ended[entry.callId] : null,
433
+ context: ctx.id,
434
+ pageId: typeof entry.callId === 'string' ? pageByCall[entry.callId] : undefined,
435
+ frameId: undefined,
436
+ stepId: typeof entry.stepId === 'string' ? entry.stepId : undefined,
437
+ }
438
+ if (action.pageId !== undefined) {
439
+ for (const frame of ctx.frames) {
440
+ if (frame.pageId !== action.pageId) continue
441
+ action.frameId = frame.frameId
442
+ break
443
+ }
444
+ }
445
+ ctx.actions.push(action)
446
+ }
447
+ // Monotonic time, not archive order: two contexts interleave in the zip, and one context's
448
+ // own actions still have to be read in the order the page performed them.
449
+ ctx.actions.sort(function (a, b) { return a.start - b.start })
450
+ ctx.frames.sort(function (a, b) { return a.at - b.at })
451
+ for (const action of ctx.actions) actionsAll.push(action)
452
+ for (const frame of ctx.frames) framesAll.push(frame)
348
453
  }
349
- const actions = []
350
- for (const entry of trace) {
351
- if (!entry || entry.type !== 'before') continue
352
- if (typeof entry.class !== 'string' || typeof entry.method !== 'string') continue
353
- if (entry.class === 'Test' || typeof entry.startTime !== 'number') continue
354
- actions.push({
355
- api: entry.class + '.' + entry.method,
356
- start: entry.startTime,
357
- end: typeof ended[entry.callId] === 'number' ? ended[entry.callId] : null,
358
- })
454
+ // The step the runner itself reports as failed, and the location it names. The runner writes
455
+ // the error on the failed step's \`after\` entry; taking the LAST step with a spec stack instead
456
+ // named the afterEach's \`context.close()\` (m160's social.spec.ts:13) rather than the strict-mode
457
+ // assertion at :90. The first error is the test's own failure — an afterEach that also fails
458
+ // runs after it.
459
+ let failedStepId = null
460
+ let failedStep = null
461
+ let failedEnd = null
462
+ let failureSpec = null
463
+ let failureLine = 0
464
+ let lastSpec = null
465
+ let lastSpecLine = 0
466
+ for (const entry of runner) {
467
+ if (!entry) continue
468
+ if (entry.type === 'after' && entry.error && typeof entry.callId === 'string' && failedStepId === null) {
469
+ failedStepId = entry.callId
470
+ failedEnd = typeof entry.endTime === 'number' ? entry.endTime : null
471
+ }
359
472
  }
473
+ // The fallback location — the last step that names a spec — is read from every member, runner
474
+ // and library alike: a trace written without a test.trace (a synthetic fixture, an older
475
+ // Playwright) still names its spec on the library entries, and dropping that would leave the
476
+ // row placeless.
477
+ for (const traceEntries of [runner].concat(contexts.map(function (ctx) { return ctx.trace }))) {
478
+ for (const entry of traceEntries) {
479
+ if (!entry || entry.type !== 'before' || !Array.isArray(entry.stack)) continue
480
+ for (const frame of entry.stack) {
481
+ if (typeof frame.file !== 'string' || !/\\.(?:spec|test)\\.[cm]?[jt]sx?$/.test(frame.file)) continue
482
+ lastSpec = specPathOf(frame.file)
483
+ lastSpecLine = Number(frame.line) || 0
484
+ break
485
+ }
486
+ }
487
+ }
488
+ if (failedStepId !== null) {
489
+ for (const entry of runner) {
490
+ if (!entry || entry.type !== 'before' || entry.callId !== failedStepId) continue
491
+ failedStep = entry
492
+ if (!Array.isArray(entry.stack)) break
493
+ for (const frame of entry.stack) {
494
+ if (typeof frame.file !== 'string' || !/\\.(?:spec|test)\\.[cm]?[jt]sx?$/.test(frame.file)) continue
495
+ failureSpec = specPathOf(frame.file)
496
+ failureLine = Number(frame.line) || 0
497
+ break
498
+ }
499
+ break
500
+ }
501
+ }
502
+ const spec = failureSpec !== null ? failureSpec : lastSpec
503
+ const line = failureSpec !== null ? failureLine : lastSpecLine
504
+ // The page the failing step ran on. The library trace records the runner's step id on every
505
+ // action it performed for that step, and each action's frame snapshot carries its pageId — so
506
+ // the failing assertion's page is the step's own page, never the last page any context left
507
+ // behind (m160: the helper context's /editor won because its member came later in the zip).
508
+ let failedPageIds = []
509
+ if (failedStepId !== null) {
510
+ for (const action of actionsAll) {
511
+ if (action.stepId !== failedStepId || action.pageId === undefined) continue
512
+ if (failedPageIds.indexOf(action.pageId) === -1) failedPageIds.push(action.pageId)
513
+ }
514
+ // A step with no library action of its own (a plain \`page.waitForTimeout\`, a runner-only
515
+ // step) falls back to the actions that ran inside its own interval.
516
+ if (failedPageIds.length === 0 && failedStep !== null && typeof failedStep.startTime === 'number') {
517
+ for (const action of actionsAll) {
518
+ if (action.pageId === undefined) continue
519
+ if (action.start < failedStep.startTime - 1) continue
520
+ if (failedEnd !== null && action.start > failedEnd + 1) continue
521
+ if (failedPageIds.indexOf(action.pageId) === -1) failedPageIds.push(action.pageId)
522
+ }
523
+ }
524
+ }
525
+ let failedPage = failedPageIds.length === 1 ? failedPageIds[0] : null
526
+ // A member with no action, request or frame is not a context the test used: Playwright writes
527
+ // a trace entry for a context it opened and closed without a page, and counting that as a
528
+ // second context would make every ordinary trace unattributable.
529
+ const usedContexts = contexts.filter(function (ctx) {
530
+ return ctx.actions.length > 0 || ctx.requests.length > 0 || ctx.frames.length > 0
531
+ })
532
+ // One context, one page: nothing to attribute, and no second context to merge.
533
+ if (failedPage === null && usedContexts.length === 1) {
534
+ const pages = []
535
+ for (const frame of framesAll) {
536
+ if (frame.pageId !== undefined && pages.indexOf(frame.pageId) === -1) pages.push(frame.pageId)
537
+ }
538
+ if (pages.length === 1) failedPage = pages[0]
539
+ }
540
+ let failedContext = null
541
+ if (failedPage !== null) {
542
+ for (const frame of framesAll) {
543
+ if (frame.pageId !== failedPage) continue
544
+ failedContext = frame.context
545
+ break
546
+ }
547
+ if (failedContext === null) {
548
+ for (const action of actionsAll) {
549
+ if (action.pageId !== failedPage) continue
550
+ failedContext = action.context
551
+ break
552
+ }
553
+ }
554
+ if (failedContext === null) {
555
+ for (const r of requestsAll) {
556
+ if (r.pageId !== failedPage) continue
557
+ failedContext = r.context
558
+ break
559
+ }
560
+ }
561
+ }
562
+ // Two used contexts and no identified page: the note makes no causal claim rather than
563
+ // merging them (m160's rule). With one used context everything in it belongs to the test's page.
564
+ const unattributed = failedPage === null && usedContexts.length > 1
565
+ const actions = failedPage === null ? actionsAll : actionsAll.filter(function (a) { return a.pageId === failedPage })
566
+ const frames = failedPage === null ? (usedContexts.length === 1 ? framesAll : []) : framesAll.filter(function (f) { return f.pageId === failedPage })
567
+ let requests = failedPage === null ? requestsAll : requestsAll.filter(function (r) { return r.pageId === failedPage })
568
+ // A trace whose HAR entries carry no page ref: the failing context's own requests are still
569
+ // its page's, because a context in a test with one page has nowhere else to send them.
570
+ if (failedPage !== null && requests.length === 0 && failedContext !== null)
571
+ requests = requestsAll.filter(function (r) { return r.context === failedContext })
360
572
  // A \`page.goto\` that has no end time: the page never finished loading. When that is the
361
573
  // failure, a document subresource with no response is the reason — the navigation waits for
362
574
  // its stylesheets and scripts — and it is evidence, not the aborted noise a finished
@@ -446,7 +658,9 @@ for (const one of found.slice(0, MAX_TRACES)) {
446
658
  // then rendering signed-out) leads instead of the Home page's own fetch.
447
659
  const USER_ACTION = /\\.(?:click|dblclick|tap|fill|press|pressSequentially|selectOption|check|uncheck|setInputFiles|goto|type)$/
448
660
  const userActions = actions.filter(function (a) { return USER_ACTION.test(a.api) })
449
- const lastAction = userActions.length > 0 ? userActions[userActions.length - 1] : null
661
+ // With two contexts and no identified page there is no causal claim to make: the last action
662
+ // of the concatenation could belong to the helper's page (m160).
663
+ const lastAction = unattributed || userActions.length === 0 ? null : userActions[userActions.length - 1]
450
664
  // A request the action caused started after it (a millisecond of clock slack) and before the
451
665
  // assertion — which is everything later in the trace. A request that started before it is the
452
666
  // page's own load, not the action's; the page shell's files are never the step's, except a
@@ -511,19 +725,13 @@ for (const one of found.slice(0, MAX_TRACES)) {
511
725
  ...(r.sentAuthorization ? { sentAuthorization: true } : {}),
512
726
  ...(r.authorization ? { authorization: r.authorization } : {}),
513
727
  ...(r.sentCookie ? { sentCookie: true } : {}),
728
+ ...(r.context ? { context: r.context } : {}),
729
+ ...(r.pageId ? { pageId: r.pageId } : {}),
514
730
  }
515
731
  }
516
- // The main frame's URL, timed: what the page showed when the action ran, and where it ended up.
517
- // The trace records these around every action, which is how the note can say what the page did
518
- // after the request — a client-side redirect never appears in the network log as a document.
519
- const frames = []
520
- for (const entry of trace) {
521
- if (!entry || entry.type !== 'frame-snapshot') continue
522
- const snap = entry.snapshot || {}
523
- if (snap.isMainFrame === false) continue
524
- if (typeof snap.frameUrl !== 'string' || typeof snap.timestamp !== 'number') continue
525
- frames.push({ url: pathOf(snap.frameUrl).slice(0, MAX_URL), at: snap.timestamp })
526
- }
732
+ // The failing page's own frame snapshots, timed: what the page showed when the action ran, and
733
+ // where it ended up. They are the snapshots of the assertion's page only — another context's
734
+ // editor page is not where this page went (m160) — and they are ordered by the trace's clock.
527
735
  let last = null
528
736
  if (lastAction !== null) {
529
737
  let pageBefore
@@ -547,8 +755,9 @@ for (const one of found.slice(0, MAX_TRACES)) {
547
755
  }
548
756
  // The fallback lead stays for traces with no action clock. With an action, only a request it
549
757
  // caused is the lead; an action that sent none leaves the page's own load to the row's
550
- // "also answered" bucket, not to a lead that would hide it.
551
- if (lead === null && lastAction === null) {
758
+ // "also answered" bucket, not to a lead that would hide it. With two contexts and no identified
759
+ // page there is no causal lead at all (m160).
760
+ if (!unattributed && lead === null && lastAction === null) {
552
761
  let leadIndex = -1
553
762
  for (let i = requests.length - 1; i >= 0; i--) {
554
763
  const r = requests[i]
@@ -608,24 +817,49 @@ for (const one of found.slice(0, MAX_TRACES)) {
608
817
  const consoleErrors = []
609
818
  const consoleSignals = []
610
819
  const consoleWarnings = []
611
- let spec = null
612
- let line = 0
613
- for (const entry of trace) {
614
- if (!entry) continue
615
- if (entry.type === 'console' && typeof entry.text === 'string') {
616
- if (entry.messageType === 'error') consoleErrors.push(entry.text)
617
- else if (consoleSignal(entry.text)) consoleSignals.push(entry.text)
618
- else if (entry.messageType === 'warning' || entry.messageType === 'warn')
619
- consoleWarnings.push(entry.text)
620
- } else if (entry.type === 'event' && entry.method === 'pageError') {
621
- const message = messageOf(entry.params)
622
- if (message) consoleErrors.push(message)
623
- } else if (entry.type === 'before' && Array.isArray(entry.stack) && entry.stack.length) {
624
- const frame = entry.stack[0] || {}
625
- if (typeof frame.file === 'string' && /\\.(?:spec|test)\\.[cm]?[jt]sx?$/.test(frame.file)) {
626
- spec = specPathOf(frame.file)
627
- line = Number(frame.line) || 0
820
+ // The console lines of the page the assertion ran on; with no identified page, of every context.
821
+ const consoleTraces =
822
+ failedContext === null
823
+ ? contexts.map(function (ctx) { return ctx.trace })
824
+ : contexts.filter(function (ctx) { return ctx.id === failedContext }).map(function (ctx) { return ctx.trace })
825
+ for (const traceEntries of consoleTraces) {
826
+ for (const entry of traceEntries) {
827
+ if (!entry) continue
828
+ if (entry.type === 'console' && typeof entry.text === 'string') {
829
+ if (entry.messageType === 'error') consoleErrors.push(entry.text)
830
+ else if (consoleSignal(entry.text)) consoleSignals.push(entry.text)
831
+ else if (entry.messageType === 'warning' || entry.messageType === 'warn')
832
+ consoleWarnings.push(entry.text)
833
+ } else if (entry.type === 'event' && entry.method === 'pageError') {
834
+ const message = messageOf(entry.params)
835
+ if (message) consoleErrors.push(message)
836
+ }
837
+ }
838
+ }
839
+ // Evidence from the OTHER browser contexts the test opened — kept apart from the failing page's
840
+ // requests and labelled as set-up work, never as the failing action's effect (m160).
841
+ const otherContexts = []
842
+ if (failedContext !== null) {
843
+ for (const ctx of usedContexts) {
844
+ if (ctx.id === failedContext) continue
845
+ const picked = []
846
+ for (const r of ctx.requests) {
847
+ if (shellRequest(r)) continue
848
+ const sent = bodyText(buf, entries, r.postData, MAX_BODY)
849
+ const got = gotText(buf, entries, r)
850
+ if (sent) r.sent = sent
851
+ if (got) r.got = got
852
+ picked.push(r)
628
853
  }
854
+ if (picked.length === 0 && ctx.actions.length === 0) continue
855
+ // Failures first, then by the trace's clock: a 401 from the helper context is worth naming
856
+ // even when its later answered calls would fill the bound.
857
+ picked.sort(function (a, b) {
858
+ const left = failedRequest(a) ? 0 : 1
859
+ const right = failedRequest(b) ? 0 : 1
860
+ return left === right ? (a.startedAt || 0) - (b.startedAt || 0) : left - right
861
+ })
862
+ otherContexts.push({ context: ctx.id, requests: picked.slice(0, MAX_NOTE).map(wire) })
629
863
  }
630
864
  }
631
865
  // Chromium reports every failed request as a console error too; where the page logged something
@@ -649,6 +883,8 @@ for (const one of found.slice(0, MAX_TRACES)) {
649
883
  requests: chosen.slice(0, MAX_REQUESTS),
650
884
  console: keptConsole.slice(0, MAX_CONSOLE).map(function (text) { return text.slice(0, 300) }),
651
885
  actions: actions.slice(-MAX_ACTIONS),
886
+ ...(unattributed ? { unattributed: true } : {}),
887
+ ...(otherContexts.length > 0 ? { otherContexts: otherContexts } : {}),
652
888
  }) + '\\n')
653
889
  }
654
890
  `;
@@ -677,6 +913,8 @@ function parseTraceRequest(value) {
677
913
  ? { authorization: r.authorization }
678
914
  : {}),
679
915
  ...(r.sentCookie === true ? { sentCookie: true } : {}),
916
+ ...(typeof r.context === 'string' && r.context ? { context: r.context } : {}),
917
+ ...(typeof r.pageId === 'string' && r.pageId ? { pageId: r.pageId } : {}),
680
918
  };
681
919
  }
682
920
  /**
@@ -724,6 +962,17 @@ export function parseTraceArtifacts(stdout) {
724
962
  const actions = Array.isArray(raw.actions) ? raw.actions : [];
725
963
  const lead = parseTraceRequest(raw.lead);
726
964
  const lastAction = parseLastAction(raw.lastAction);
965
+ const otherContexts = Array.isArray(raw.otherContexts)
966
+ ? raw.otherContexts
967
+ .filter((c) => typeof c === 'object' && c !== null)
968
+ .filter((c) => typeof c.context === 'string')
969
+ .map((c) => ({
970
+ context: String(c.context),
971
+ requests: (Array.isArray(c.requests) ? c.requests : [])
972
+ .map(parseTraceRequest)
973
+ .filter((request) => request !== null),
974
+ }))
975
+ : [];
727
976
  out.push({
728
977
  spec: typeof raw.spec === 'string' ? raw.spec : null,
729
978
  line: typeof raw.line === 'number' ? raw.line : 0,
@@ -740,7 +989,12 @@ export function parseTraceArtifacts(stdout) {
740
989
  api: String(a.api),
741
990
  start: Number(a.start),
742
991
  end: a.end === null || a.end === undefined ? null : Number(a.end),
992
+ ...(typeof a.context === 'string' ? { context: a.context } : {}),
993
+ ...(typeof a.pageId === 'string' ? { pageId: a.pageId } : {}),
994
+ ...(typeof a.frameId === 'string' ? { frameId: a.frameId } : {}),
743
995
  })),
996
+ ...(otherContexts.length === 0 ? {} : { otherContexts }),
997
+ ...(raw.unattributed === true ? { unattributed: true } : {}),
744
998
  });
745
999
  }
746
1000
  catch (_error) {
@@ -1062,6 +1316,36 @@ function runFailureOf(item, failures) {
1062
1316
  }
1063
1317
  return undefined;
1064
1318
  }
1319
+ /**
1320
+ * Where the row names this failing test.
1321
+ *
1322
+ * The trace names the line of the step it recorded — the failing assertion when the runner wrote
1323
+ * its error on that step, and the suite's `afterEach` (`context.close()`) when it did not. The
1324
+ * runner's own summary names the test's DECLARATION line, and that is the location the result
1325
+ * quotes and the line a re-run command takes, so it wins whenever the run reported one for this
1326
+ * file: matched by the trace's own line inside the test body ({@link runFailureOf}), and otherwise
1327
+ * by the file alone when that file has exactly ONE reported failure — an `afterEach` line sits
1328
+ * BEFORE the declaration, so the line window cannot match it (xr-molecule-flash-m163's row said
1329
+ * `navigation.spec.ts:12`, the suite's `afterEach`, for the test declared at `:200`).
1330
+ *
1331
+ * @param item - One traced failure.
1332
+ * @param failures - The runner's report, one entry per failing test.
1333
+ * @returns The `file:line` the row names.
1334
+ */
1335
+ function failureWhereOf(item, failures) {
1336
+ const spec = item.spec;
1337
+ if (spec === null)
1338
+ return whereOf(item);
1339
+ let reported = runFailureOf(item, failures);
1340
+ if (reported === undefined) {
1341
+ const sameFile = failures.filter((failure) => sameLocationFile(spec, failure.spec));
1342
+ if (sameFile.length === 1)
1343
+ reported = sameFile[0];
1344
+ }
1345
+ if (reported === undefined)
1346
+ return whereOf(item);
1347
+ return reported.line > 0 ? `${reported.spec}:${reported.line}` : reported.spec;
1348
+ }
1065
1349
  /**
1066
1350
  * Whether a pending request's interval covers a navigation's wait: it started while the
1067
1351
  * navigation was loading (at or after the wait began) and it has no recorded response, so it was
@@ -1473,6 +1757,14 @@ function missingApiPrefixSentence(request) {
1473
1757
  * (answered 201, the page still rendering signed-out) never appeared. With no action clock, the
1474
1758
  * row falls back to {@link TraceArtifact.lead}: the page's last answered fetch/xhr.
1475
1759
  *
1760
+ * The action, the URL and the requests are the FAILING ASSERTION'S PAGE's ({@link
1761
+ * TraceArtifact.lastAction}), never the archive's last context: a test that creates its data in a
1762
+ * second browser context writes that context's click and `/editor` snapshot into the same zip, and
1763
+ * reading the concatenation made them the failing page (xr-molecule-flash-m160). The other
1764
+ * contexts' requests are named separately as set-up work. When the trace holds more than one used
1765
+ * context and the assertion's page cannot be identified, the note lists the requests and makes NO
1766
+ * causal claim — no "the test's last action", no "the page stayed at …" — instead of merging them.
1767
+ *
1476
1768
  * WHICH rows it spends is evidence, not artifact order (xr-molecule-flash-m112 +53:46): a row
1477
1769
  * needs a recorded response, a real failure or a console line ({@link traceRowEvidence}), rows are
1478
1770
  * deduplicated by the test's location, and they are ordered real failure → repeated from an
@@ -1527,6 +1819,7 @@ function missingApiPrefixSentence(request) {
1527
1819
  * error.
1528
1820
  */
1529
1821
  export function failureTraceNote(items, target, earlierFailures = [], apiPrefixes = [], report = {}) {
1822
+ const failures = report.failures ?? [];
1530
1823
  const candidates = items.filter((item) => traceRowEvidence(item, report));
1531
1824
  if (candidates.length === 0)
1532
1825
  return null;
@@ -1539,7 +1832,7 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1539
1832
  const seen = new Set();
1540
1833
  for (const { item, index } of ranked) {
1541
1834
  // A trace that names no spec cannot be placed; it is never collapsed onto another one.
1542
- const where = item.spec === null ? `#${index}` : whereOf(item);
1835
+ const where = item.spec === null ? `#${index}` : failureWhereOf(item, failures);
1543
1836
  if (seen.has(where))
1544
1837
  continue;
1545
1838
  seen.add(where);
@@ -1593,6 +1886,9 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1593
1886
  ];
1594
1887
  const anyStalled = stalls.some((stall) => stall !== null);
1595
1888
  const rows = [];
1889
+ // Rows whose failing action sent nothing and left the page on a new route: the page is still
1890
+ // showing the previous route's data. Counted so the note can say it once for the whole run.
1891
+ let staleRouteRows = 0;
1596
1892
  for (const [index, item] of traced.entries()) {
1597
1893
  // The test's LAST action before the failing assertion leads the row (m148): the requests it
1598
1894
  // caused and what came back explain a page that never showed the signed-in state, while the
@@ -1630,7 +1926,26 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1630
1926
  const parts = [];
1631
1927
  if (step !== undefined && !stalledNav) {
1632
1928
  const head = `the test's last action was ${step.api} at ${actionClock(item, step.start)}`;
1633
- if (stepRequests.length === 0) {
1929
+ const pageBefore = step.pageBefore;
1930
+ const pageAfter = step.pageAfter;
1931
+ // The action asked the API for nothing and the page ended up on another route: the page is
1932
+ // still showing the data it fetched for the route before, which is why the new route's list
1933
+ // is wrong (xr-molecule-flash-m163 +10:00 — the note printed "it sent no request" beside
1934
+ // "the page then navigated to …", then pointed at a response body that never existed, and
1935
+ // the run spent six minutes on markup and CSS).
1936
+ const staleRoute = stepRequests.length === 0 &&
1937
+ pageBefore !== undefined &&
1938
+ pageAfter !== undefined &&
1939
+ pageBefore !== pageAfter;
1940
+ if (staleRoute) {
1941
+ staleRouteRows += 1;
1942
+ parts.push(`${head}, and it sent no request — the page moved from ${pageBefore} to ${pageAfter}. ` +
1943
+ `The page changed route but fetched nothing new, so it is still showing the previous ` +
1944
+ `route's data: check how the page reads the route (a param the route does not define ` +
1945
+ `is always undefined; a missing effect dependency on the route means the fetch never ` +
1946
+ `re-runs), not the markup or the CSS`);
1947
+ }
1948
+ else if (stepRequests.length === 0) {
1634
1949
  parts.push(`${head}, and it sent no request`);
1635
1950
  }
1636
1951
  else {
@@ -1642,10 +1957,10 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1642
1957
  }
1643
1958
  // What the page did next: a navigation shows in the main frame's URL even when the app moved
1644
1959
  // client-side and the network log recorded no document request.
1645
- if (step.pageAfter !== undefined) {
1646
- parts.push(step.pageBefore !== undefined && step.pageBefore !== step.pageAfter
1647
- ? `the page then navigated to ${step.pageAfter}`
1648
- : `the page stayed at ${step.pageAfter}`);
1960
+ if (!staleRoute && pageAfter !== undefined) {
1961
+ parts.push(pageBefore !== undefined && pageBefore !== pageAfter
1962
+ ? `the page then navigated to ${pageAfter}`
1963
+ : `the page stayed at ${pageAfter}`);
1649
1964
  }
1650
1965
  // A 4xx on the logout endpoint with the page unmoved is not a backend failure: the auth
1651
1966
  // client swallows the call and clears the session, and the URL can only move if the page
@@ -1702,14 +2017,29 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1702
2017
  .slice(0, 2)
1703
2018
  .map((text) => text.replace(/\s+/g, ' ').trim().slice(0, 160))
1704
2019
  .join(' | ')}`);
1705
- rows.push(` ${index + 1}. ${whereOf(item)} — ${parts.join(' — ')}`);
2020
+ // A request from another browser context the test opened is not the failing action's effect:
2021
+ // it is set-up work on a different page, and it is named as such rather than merged into the
2022
+ // row (xr-molecule-flash-m160: the helper context's POST /api/articles was printed as the
2023
+ // click's effect while the assertion had failed on the profile page).
2024
+ const others = (item.otherContexts ?? []).filter((context) => context.requests.length > 0);
2025
+ for (const [otherIndex, context] of others.slice(0, 2).entries()) {
2026
+ const label = otherIndex === 0
2027
+ ? 'a second browser context, opened by the test for set-up and not the page the assertion ran on'
2028
+ : 'another browser context the test opened';
2029
+ parts.push(`${label}: ${context.requests.slice(0, 2).map(requestLine).join('; ')}`);
2030
+ }
2031
+ rows.push(` ${index + 1}. ${failureWhereOf(item, failures)} — ${parts.join(' — ')}`);
1706
2032
  }
1707
2033
  // Distinct locations the note did not name: the repeats collapse, so the count is what is left
1708
2034
  // to look at, not how many zips Playwright wrote.
1709
- const distinct = new Set(candidates.map((item, index) => (item.spec === null ? `#${index}` : whereOf(item))));
2035
+ const distinct = new Set(candidates.map((item, index) => item.spec === null ? `#${index}` : failureWhereOf(item, failures)));
1710
2036
  const more = distinct.size > traced.length
1711
2037
  ? `\n … and ${distinct.size - traced.length} more traced failures`
1712
2038
  : '';
2039
+ // Every row came from a trace with more than one browser context and no page to tie the
2040
+ // assertion to: the note may list the requests, and may not say what action sent them or where
2041
+ // "the page" went (m160).
2042
+ const allUnattributed = traced.every((item) => item.unattributed === true);
1713
2043
  // A run whose pages never loaded has one cause, and it is not in the app: every traced failure is
1714
2044
  // a `page.goto` that timed out on a document the browser could not load. The transport error is
1715
2045
  // the first line the executor needs — m118 (+37:09) read 14 goto timeouts as app failures while
@@ -1752,12 +2082,33 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1752
2082
  `and if the stall repeats measure that exact fetch ` +
1753
2083
  `(\`curl -o /dev/null -w '%{time_starttransfer} %{time_total}' <url>\`) before ` +
1754
2084
  `treating it as the app's.\n\n`);
2085
+ // Every row is a route change that fetched nothing: the cause and the next read are the same
2086
+ // for the whole run, and the generic "read the response body" paragraph would point at a
2087
+ // response that never existed (xr-molecule-flash-m163 +10:00).
2088
+ const everyStaleRoute = staleRouteRows > 0 &&
2089
+ staleRouteRows === traced.length &&
2090
+ !anyFailed &&
2091
+ !anyStalled &&
2092
+ !allUnattributed &&
2093
+ transport === null;
2094
+ const staleRouteLead = everyStaleRoute
2095
+ ? `Every failure this run traced is the same shape: the failing action asked the API for ` +
2096
+ `nothing and the page changed route, so the page is still showing the data it fetched for ` +
2097
+ `the route before. Read how each page derives its query from the route — a param the route ` +
2098
+ `does not define is always undefined, and a missing effect dependency on the route means ` +
2099
+ `the fetch never re-runs — before touching the markup or the CSS.\n\n`
2100
+ : '';
1755
2101
  return (transportLead +
1756
2102
  stalledLead +
2103
+ staleRouteLead +
1757
2104
  `The platform read the failing tests' own Playwright traces from the run against ${target} ` +
1758
- `(what each page sent and got — first what the test's last action sent and where the page went ` +
1759
- `next, then the requests that failed, the ones that got no response, and its last answered ` +
1760
- `ones):\n` +
2105
+ (allUnattributed
2106
+ ? `(what each page sent and got — the requests their contexts recorded, with no claim about ` +
2107
+ `which action caused them, then the ones that failed, the ones that got no response, and ` +
2108
+ `the last answered ones):\n`
2109
+ : `(what each page sent and got — first what the test's last action sent and where the page ` +
2110
+ `went next, then the requests that failed, the ones that got no response, and its last ` +
2111
+ `answered ones):\n`) +
1761
2112
  `${rows.join('\n')}${more}\n` +
1762
2113
  (anyFailed
1763
2114
  ? everyFailureLacksPrefix
@@ -1787,13 +2138,24 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
1787
2138
  'not finish loading because its own subresource never answered. Do not read this as a ' +
1788
2139
  'page-state bug: re-run these tests against the same URL first, and change the app only ' +
1789
2140
  'if the same navigation times out again with no stalled asset in the trace.'
1790
- : 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
1791
- "page, not the API. Start with what the test's last action sent (the first line of each " +
1792
- 'row) and what the page did next: a request that answered (2xx included) while the page ' +
1793
- 'still did not show the state the assertion waited on is the fact to read — its status ' +
1794
- 'and response body, not the selector. If the request is not the one the test meant — an ' +
1795
- 'unfollow sent where the test clicked follow — the state the action read was stale ' +
1796
- 'before it.') +
2141
+ : allUnattributed
2142
+ ? 'No listed request failed — none got a 4xx/5xx or a network error. The trace held more ' +
2143
+ 'than one browser context and nothing ties a page to the failing assertion, so no ' +
2144
+ 'action or URL is attributed here: read the requests above together with the runner’s ' +
2145
+ 'own failure text to tell which page the test asserted on, and read that page’s state, ' +
2146
+ 'not the selector.'
2147
+ : everyStaleRoute
2148
+ ? 'No listed request failed — none got a 4xx/5xx or a network error — and the failing ' +
2149
+ 'action sent no request at all, so there is no response to read. The page changed ' +
2150
+ 'route and fetched nothing, which is why it still shows the previous route’s data: ' +
2151
+ 'fix how the page reads the route, not the selector.'
2152
+ : 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
2153
+ "page, not the API. Start with what the test's last action sent (the first line of each " +
2154
+ 'row) and what the page did next: a request that answered (2xx included) while the page ' +
2155
+ 'still did not show the state the assertion waited on is the fact to read — its status ' +
2156
+ 'and response body, not the selector. If the request is not the one the test meant — an ' +
2157
+ 'unfollow sent where the test clicked follow — the state the action read was stale ' +
2158
+ 'before it.') +
1797
2159
  prefixAdvice +
1798
2160
  (wrapped
1799
2161
  ? ' The API received a body wrapped in `data`: app-http’s `post(url, data, config)` / ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.9.15",
3
+ "version": "1.9.16",
4
4
  "description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",