@molecule/api-ai-tools 1.9.12 → 1.9.13
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 +50 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +77 -0
- package/dist/trace-artifacts.d.ts +52 -7
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +202 -54
- package/package.json +1 -1
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-
|
|
6
|
+
Generated: 2026-10-18T09:17:15.122Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -522,6 +522,52 @@ interface TraceArtifact {
|
|
|
522
522
|
}
|
|
523
523
|
```
|
|
524
524
|
|
|
525
|
+
#### `TraceRunFailure`
|
|
526
|
+
|
|
527
|
+
What the runner's own report says about ONE failing test: where the test is declared, which
|
|
528
|
+
Playwright call its failure names, and the per-test deadline the failure measured against.
|
|
529
|
+
|
|
530
|
+
A trace cannot tell a navigation that ran out of budget from a selector wait that did. On
|
|
531
|
+
xr-molecule-flash-m153 a 15 s `page.waitForSelector` timeout left a trace whose `Frame.goto`
|
|
532
|
+
never finished (the test was cut off mid-navigation) with a `/src/components/*.tsx` module still
|
|
533
|
+
pending when the context closed — and the note read that as an asset hang, then sent the run to
|
|
534
|
+
re-run the same tests unchanged against the same dev server while the same result's
|
|
535
|
+
`testRunNote` was telling it to measure a production build.
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
interface TraceRunFailure {
|
|
539
|
+
/** The spec file the runner's heading names (`e2e/url-navigation.spec.ts`). */
|
|
540
|
+
spec: string
|
|
541
|
+
/** The line the test is DECLARED on, from that heading (`206`). */
|
|
542
|
+
line: number
|
|
543
|
+
/** The Playwright call the failure names (`page.waitForSelector`, `page.goto`), or null. */
|
|
544
|
+
operation: string | null
|
|
545
|
+
/** The per-test deadline the failure names, in ms (`15000`), or null when it names none. */
|
|
546
|
+
budgetMs: number | null
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### `TraceRunReport`
|
|
551
|
+
|
|
552
|
+
The runner's own report, beside the traces it wrote.
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
interface TraceRunReport {
|
|
556
|
+
/**
|
|
557
|
+
* One entry per failing test, as the runner printed them. Empty when the caller supplied none:
|
|
558
|
+
* without the runner's reported operation a pending asset is never called the cause.
|
|
559
|
+
*/
|
|
560
|
+
failures?: readonly TraceRunFailure[]
|
|
561
|
+
/**
|
|
562
|
+
* True when the same result's `testRunNote` already names a production build as the next
|
|
563
|
+
* measurement (a local dev-server walk that ran out of its per-test budget). The note then
|
|
564
|
+
* states the ONE next step instead of directing an unchanged same-target re-run that
|
|
565
|
+
* contradicts it.
|
|
566
|
+
*/
|
|
567
|
+
productionBuildAdvised?: boolean
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
525
571
|
#### `DiagnosisTraceRewrite`
|
|
526
572
|
|
|
527
573
|
A command with the trace flag this platform decided to add, and the sentence to report it in.
|
|
@@ -1953,7 +1999,7 @@ function stalledDocumentSubresource(request: TraceRequest): boolean
|
|
|
1953
1999
|
|
|
1954
2000
|
**Returns:** True when the request is a document subresource that never finished.
|
|
1955
2001
|
|
|
1956
|
-
#### `failureTraceNote(items, target, earlierFailures, apiPrefixes)`
|
|
2002
|
+
#### `failureTraceNote(items, target, earlierFailures, apiPrefixes, report)`
|
|
1957
2003
|
|
|
1958
2004
|
The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
1959
2005
|
what came back.
|
|
@@ -1985,6 +2031,7 @@ function failureTraceNote(
|
|
|
1985
2031
|
target: string,
|
|
1986
2032
|
earlierFailures?: readonly string[],
|
|
1987
2033
|
apiPrefixes?: readonly string[],
|
|
2034
|
+
report?: TraceRunReport,
|
|
1988
2035
|
): string | null
|
|
1989
2036
|
```
|
|
1990
2037
|
|
|
@@ -1992,6 +2039,7 @@ function failureTraceNote(
|
|
|
1992
2039
|
- `target` — Where the run went — the deployed host, as the note names it.
|
|
1993
2040
|
- `earlierFailures` — The failing `file:line`s earlier walks of this suite left, so a repeat is ranked above a one-off. Empty when the caller knows of no earlier walk.
|
|
1994
2041
|
- `apiPrefixes` — The API path prefixes the app's client config names (`['/api']`), read from the app by the caller. Empty when the caller could not read one; the traces' own requests may still supply it. Never a hard-coded literal.
|
|
2042
|
+
- `report` — The runner's own report for this run ({@link TraceRunReport}): each failing test's declared location, the operation its failure names and the deadline it names, plus whether the same result's `testRunNote` already points at a production build. A pending asset is called the cause only when the runner says the failing operation for THAT test is the navigation it was loading; without the report nothing is called a navigation stall.
|
|
1995
2043
|
|
|
1996
2044
|
**Returns:** The note, or null when no trace held a recorded response, a real failure or a console error.
|
|
1997
2045
|
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAkJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAkOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAsEnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AA2GD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAkJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAkOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAsEnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AA2GD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAokFxF"}
|
package/dist/tools.js
CHANGED
|
@@ -849,6 +849,65 @@ export function buildTools(backend, config) {
|
|
|
849
849
|
result = isEnvFilePath(path) ? redactSecrets(result) : redactSecretsInCode(result);
|
|
850
850
|
return result;
|
|
851
851
|
}
|
|
852
|
+
// ── Binary reads ───────────────────────────────────────────────
|
|
853
|
+
/** Extensions whose files are never text: a `read_file` on one is refused by name. */
|
|
854
|
+
const BINARY_FILE_EXTENSIONS = new Set([
|
|
855
|
+
'avif',
|
|
856
|
+
'gif',
|
|
857
|
+
'gz',
|
|
858
|
+
'ico',
|
|
859
|
+
'jpeg',
|
|
860
|
+
'jpg',
|
|
861
|
+
'otf',
|
|
862
|
+
'pdf',
|
|
863
|
+
'png',
|
|
864
|
+
'tgz',
|
|
865
|
+
'ttf',
|
|
866
|
+
'wasm',
|
|
867
|
+
'webp',
|
|
868
|
+
'woff',
|
|
869
|
+
'woff2',
|
|
870
|
+
'zip',
|
|
871
|
+
]);
|
|
872
|
+
/** How many chars of a read are inspected for binary markers. */
|
|
873
|
+
const BINARY_SAMPLE_CHARS = 512;
|
|
874
|
+
/**
|
|
875
|
+
* A replacement character (a byte UTF-8 could not decode) marks binary only in
|
|
876
|
+
* bulk: one literal U+FFFD in a source string is ordinary text.
|
|
877
|
+
*/
|
|
878
|
+
const BINARY_MIN_REPLACEMENTS = 8;
|
|
879
|
+
const BINARY_REPLACEMENT_DENSITY = 0.05;
|
|
880
|
+
/**
|
|
881
|
+
* The kind of a BINARY file, or `null` for text.
|
|
882
|
+
*
|
|
883
|
+
* `read_file` asks the backend for a string, so a PNG comes back as UTF-8
|
|
884
|
+
* replacement characters and used to be handed to the model whole — 21,191
|
|
885
|
+
* chars of unreadable bytes it cannot look at, ~12k tokens per read, asked for
|
|
886
|
+
* twice in one run (xr-molecule-flash-m155 +09:58). A known binary extension is
|
|
887
|
+
* refused by name; a file without one is caught from the bytes themselves, by
|
|
888
|
+
* a NUL byte or by the density of replacement characters in its first
|
|
889
|
+
* {@link BINARY_SAMPLE_CHARS} chars.
|
|
890
|
+
*
|
|
891
|
+
* @param path - The already-resolved path.
|
|
892
|
+
* @param content - The content the backend decoded as UTF-8.
|
|
893
|
+
* @returns The binary kind to name (e.g. `png`, `binary data`), or `null` for text.
|
|
894
|
+
*/
|
|
895
|
+
function binaryFileKind(path, content) {
|
|
896
|
+
const ext = posix.extname(path).slice(1).toLowerCase();
|
|
897
|
+
if (BINARY_FILE_EXTENSIONS.has(ext))
|
|
898
|
+
return ext;
|
|
899
|
+
const sample = content.slice(0, BINARY_SAMPLE_CHARS);
|
|
900
|
+
if (sample.includes('\u0000'))
|
|
901
|
+
return 'binary data';
|
|
902
|
+
let replacements = 0;
|
|
903
|
+
for (const char of sample)
|
|
904
|
+
if (char === '\uFFFD')
|
|
905
|
+
replacements += 1;
|
|
906
|
+
return replacements >= BINARY_MIN_REPLACEMENTS &&
|
|
907
|
+
replacements / sample.length >= BINARY_REPLACEMENT_DENSITY
|
|
908
|
+
? 'binary data'
|
|
909
|
+
: null;
|
|
910
|
+
}
|
|
852
911
|
// ── Empty-read verification ────────────────────────────────────
|
|
853
912
|
/**
|
|
854
913
|
* The file's size in bytes as the backend's own shell reports it, or `null`
|
|
@@ -1022,6 +1081,19 @@ export function buildTools(backend, config) {
|
|
|
1022
1081
|
...(verdict.note ? { note: verdict.note } : {}),
|
|
1023
1082
|
});
|
|
1024
1083
|
}
|
|
1084
|
+
// A binary file decoded as UTF-8 is unreadable by definition — say what it
|
|
1085
|
+
// is instead of returning the bytes (see binaryFileKind).
|
|
1086
|
+
const binaryKind = binaryFileKind(path, content);
|
|
1087
|
+
if (binaryKind) {
|
|
1088
|
+
// The read already happened, so the decoded length is the fallback size;
|
|
1089
|
+
// `wc -c` gives the file's real byte count when the backend can run it.
|
|
1090
|
+
const bytes = (await probeFileSize(path)) ?? content.length;
|
|
1091
|
+
return {
|
|
1092
|
+
error: `${path} is a binary file (${binaryKind}, ${Math.max(1, Math.round(bytes / 1024))} KB). ` +
|
|
1093
|
+
`read_file returns text only — to look at a page use screenshot_preview; to inspect an ` +
|
|
1094
|
+
`image you produced, measure it in a script and print the numbers (its size, pixel counts).`,
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1025
1097
|
if (content.length > MAX_READ_SIZE)
|
|
1026
1098
|
return {
|
|
1027
1099
|
error: `File too large (${Math.round(content.length / 1024)}KB). Maximum is ${MAX_READ_SIZE / 1024 / 1024}MB.`,
|
|
@@ -2911,8 +2983,13 @@ export function buildTools(backend, config) {
|
|
|
2911
2983
|
: { piped: false, unplaced: false };
|
|
2912
2984
|
const reported = suiteRunExitCode(exitCode, output, placement.piped);
|
|
2913
2985
|
const noCodeNote = suiteExitCodeNote(placement, reported);
|
|
2986
|
+
// A FINISHED result says so, even when the code it would report is withheld: a filtered
|
|
2987
|
+
// pipeline's code is not the run's verdict ({@link FILTERED_EXIT_NOTE}), so `exitCode` alone
|
|
2988
|
+
// cannot tell a caller this is the delivered result rather than a progress return — and a
|
|
2989
|
+
// caller that tracks background tasks would hand the same result back as fresh news later.
|
|
2914
2990
|
return {
|
|
2915
2991
|
taskId,
|
|
2992
|
+
status: 'completed',
|
|
2916
2993
|
exitCode: reported,
|
|
2917
2994
|
waitedMs,
|
|
2918
2995
|
...(causes || noCodeNote
|
|
@@ -279,6 +279,42 @@ export declare function unansweredRequest(request: TraceRequest): boolean;
|
|
|
279
279
|
* @returns True when the request is a document subresource that never finished.
|
|
280
280
|
*/
|
|
281
281
|
export declare function stalledDocumentSubresource(request: TraceRequest): boolean;
|
|
282
|
+
/**
|
|
283
|
+
* What the runner's own report says about ONE failing test: where the test is declared, which
|
|
284
|
+
* Playwright call its failure names, and the per-test deadline the failure measured against.
|
|
285
|
+
*
|
|
286
|
+
* A trace cannot tell a navigation that ran out of budget from a selector wait that did. On
|
|
287
|
+
* xr-molecule-flash-m153 a 15 s `page.waitForSelector` timeout left a trace whose `Frame.goto`
|
|
288
|
+
* never finished (the test was cut off mid-navigation) with a `/src/components/*.tsx` module still
|
|
289
|
+
* pending when the context closed — and the note read that as an asset hang, then sent the run to
|
|
290
|
+
* re-run the same tests unchanged against the same dev server while the same result's
|
|
291
|
+
* `testRunNote` was telling it to measure a production build.
|
|
292
|
+
*/
|
|
293
|
+
export interface TraceRunFailure {
|
|
294
|
+
/** The spec file the runner's heading names (`e2e/url-navigation.spec.ts`). */
|
|
295
|
+
spec: string;
|
|
296
|
+
/** The line the test is DECLARED on, from that heading (`206`). */
|
|
297
|
+
line: number;
|
|
298
|
+
/** The Playwright call the failure names (`page.waitForSelector`, `page.goto`), or null. */
|
|
299
|
+
operation: string | null;
|
|
300
|
+
/** The per-test deadline the failure names, in ms (`15000`), or null when it names none. */
|
|
301
|
+
budgetMs: number | null;
|
|
302
|
+
}
|
|
303
|
+
/** The runner's own report, beside the traces it wrote. */
|
|
304
|
+
export interface TraceRunReport {
|
|
305
|
+
/**
|
|
306
|
+
* One entry per failing test, as the runner printed them. Empty when the caller supplied none:
|
|
307
|
+
* without the runner's reported operation a pending asset is never called the cause.
|
|
308
|
+
*/
|
|
309
|
+
failures?: readonly TraceRunFailure[];
|
|
310
|
+
/**
|
|
311
|
+
* True when the same result's `testRunNote` already names a production build as the next
|
|
312
|
+
* measurement (a local dev-server walk that ran out of its per-test budget). The note then
|
|
313
|
+
* states the ONE next step instead of directing an unchanged same-target re-run that
|
|
314
|
+
* contradicts it.
|
|
315
|
+
*/
|
|
316
|
+
productionBuildAdvised?: boolean;
|
|
317
|
+
}
|
|
282
318
|
/**
|
|
283
319
|
* The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
284
320
|
* what came back.
|
|
@@ -321,12 +357,16 @@ export declare function stalledDocumentSubresource(request: TraceRequest): boole
|
|
|
321
357
|
* transport error (`net::ERR_CONTENT_DECODING_FAILED` and friends) and the document it hit: nothing
|
|
322
358
|
* rendered, so no row below is app evidence (xr-molecule-flash-m118 +37:09).
|
|
323
359
|
*
|
|
324
|
-
* When a navigation never finished and one of the document's
|
|
325
|
-
* script, an asset under `/assets/`) never got a response either
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* (`/sw.js`, the manifest, a
|
|
360
|
+
* When a navigation the runner reports as the failure never finished and one of the document's
|
|
361
|
+
* own subresources (a stylesheet, a script, an asset under `/assets/`) never got a response either
|
|
362
|
+
* — having started inside that wait — the request takes a row and the note says the page was
|
|
363
|
+
* waiting on it. It is not an API call, but on a `page.goto` timeout it is the only request that
|
|
364
|
+
* can explain the timeout — the deployed app was serving the page while one fetch stalled for 27 s
|
|
365
|
+
* (xr-molecule-flash-m135 +34:10). The page shell's incidental requests (`/sw.js`, the manifest, a
|
|
366
|
+
* favicon, a font) stay out, for m125's reason. An asset merely unfinished when a selector wait
|
|
367
|
+
* ran out the test budget, or one a teardown/`afterEach` trace holds, is not a cause: the note
|
|
368
|
+
* says it "had no recorded response when the test ended; that alone does not establish the cause"
|
|
369
|
+
* (xr-molecule-flash-m153 +13:43).
|
|
330
370
|
*
|
|
331
371
|
* A 404 whose path lacks the API prefix the app's own calls carry is called out with that prefix
|
|
332
372
|
* ({@link missingApiPrefixRequests}): xr-molecule-flash-m145 posted `/users` to `vite preview`,
|
|
@@ -351,10 +391,15 @@ export declare function stalledDocumentSubresource(request: TraceRequest): boole
|
|
|
351
391
|
* @param apiPrefixes - The API path prefixes the app's client config names (`['/api']`), read from
|
|
352
392
|
* the app by the caller. Empty when the caller could not read one; the traces' own requests may
|
|
353
393
|
* still supply it. Never a hard-coded literal.
|
|
394
|
+
* @param report - The runner's own report for this run ({@link TraceRunReport}): each failing
|
|
395
|
+
* test's declared location, the operation its failure names and the deadline it names, plus
|
|
396
|
+
* whether the same result's `testRunNote` already points at a production build. A pending asset
|
|
397
|
+
* is called the cause only when the runner says the failing operation for THAT test is the
|
|
398
|
+
* navigation it was loading; without the report nothing is called a navigation stall.
|
|
354
399
|
* @returns The note, or null when no trace held a recorded response, a real failure or a console
|
|
355
400
|
* error.
|
|
356
401
|
*/
|
|
357
|
-
export declare function failureTraceNote(items: readonly TraceArtifact[], target: string, earlierFailures?: readonly string[], apiPrefixes?: readonly string[]): string | null;
|
|
402
|
+
export declare function failureTraceNote(items: readonly TraceArtifact[], target: string, earlierFailures?: readonly string[], apiPrefixes?: readonly string[], report?: TraceRunReport): string | null;
|
|
358
403
|
/**
|
|
359
404
|
* Whether a Playwright config records traces for the tests that fail.
|
|
360
405
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,6FAA6F;IAC7F,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B;;;;OAIG;IACH,OAAO,EAAE,WAAW,EAAE,CAAA;CACvB;AAED,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC,yFAAyF;AACzF,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC,wFAAwF;AACxF,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAK5C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,QAAO,
|
|
1
|
+
{"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"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"}
|
package/dist/trace-artifacts.js
CHANGED
|
@@ -452,6 +452,9 @@ for (const one of found.slice(0, MAX_TRACES)) {
|
|
|
452
452
|
...(r.sent ? { sent: r.sent } : {}),
|
|
453
453
|
...(r.got ? { got: r.got } : {}),
|
|
454
454
|
type: r.type || undefined,
|
|
455
|
+
// The trace's own clock, so the note can tell a request the navigation was waiting on from
|
|
456
|
+
// one that was already pending before it started (m135/m153).
|
|
457
|
+
...(typeof r.startedAt === 'number' ? { startedAt: r.startedAt } : {}),
|
|
455
458
|
...(r.sentAuthorization ? { sentAuthorization: true } : {}),
|
|
456
459
|
...(r.authorization ? { authorization: r.authorization } : {}),
|
|
457
460
|
...(r.sentCookie ? { sentCookie: true } : {}),
|
|
@@ -965,6 +968,97 @@ function repeatsEarlierFailure(item, earlier) {
|
|
|
965
968
|
}
|
|
966
969
|
return false;
|
|
967
970
|
}
|
|
971
|
+
/** The Playwright navigation calls whose wait a pending document subresource can explain. */
|
|
972
|
+
const NAVIGATION_OPERATIONS = new Set(['goto', 'waitForURL', 'waitForLoadState', 'reload']);
|
|
973
|
+
/**
|
|
974
|
+
* The method a runner-reported operation names (`page.waitForSelector` → `waitForSelector`), or
|
|
975
|
+
* null when it is not one of the navigation calls a stalled asset can explain.
|
|
976
|
+
*
|
|
977
|
+
* @param operation - The call the runner's error text names, or null.
|
|
978
|
+
* @returns The navigation method, or null.
|
|
979
|
+
*/
|
|
980
|
+
function navigationMethod(operation) {
|
|
981
|
+
if (operation === null)
|
|
982
|
+
return null;
|
|
983
|
+
const method = operation.slice(operation.lastIndexOf('.') + 1);
|
|
984
|
+
return NAVIGATION_OPERATIONS.has(method) ? method : null;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* The runner's report for the test THIS trace belongs to, or undefined when it belongs to none.
|
|
988
|
+
*
|
|
989
|
+
* The trace names the line of its LAST recorded step; the runner's summary names the test's
|
|
990
|
+
* DECLARATION line, and the step sits inside the body just after it ({@link
|
|
991
|
+
* RECURRENCE_LINE_WINDOW}). A line BEFORE the declaration is a hook — m153's teardown trace sits
|
|
992
|
+
* at `url-navigation.spec.ts:107`, the Pagination suite's `afterEach`, while the failing test is
|
|
993
|
+
* declared at `:206` — and a hook is not the failing test's operation.
|
|
994
|
+
*
|
|
995
|
+
* @param item - One traced failure.
|
|
996
|
+
* @param failures - The runner's report, one entry per failing test.
|
|
997
|
+
* @returns The matching failure, or undefined.
|
|
998
|
+
*/
|
|
999
|
+
function runFailureOf(item, failures) {
|
|
1000
|
+
if (item.spec === null)
|
|
1001
|
+
return undefined;
|
|
1002
|
+
for (const failure of failures) {
|
|
1003
|
+
if (!sameLocationFile(item.spec, failure.spec))
|
|
1004
|
+
continue;
|
|
1005
|
+
if (item.line < failure.line)
|
|
1006
|
+
continue;
|
|
1007
|
+
if (item.line - failure.line <= RECURRENCE_LINE_WINDOW)
|
|
1008
|
+
return failure;
|
|
1009
|
+
}
|
|
1010
|
+
return undefined;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Whether a pending request's interval covers a navigation's wait: it started while the
|
|
1014
|
+
* navigation was loading (at or after the wait began) and it has no recorded response, so it was
|
|
1015
|
+
* still pending when the wait ran out. A request that started before the wait belongs to the page
|
|
1016
|
+
* already on screen, not to the navigation that never finished.
|
|
1017
|
+
*
|
|
1018
|
+
* @param request - One request the trace recorded.
|
|
1019
|
+
* @param wait - The navigation action the runner reported.
|
|
1020
|
+
* @returns True when the request's pending interval covers the wait.
|
|
1021
|
+
*/
|
|
1022
|
+
function requestCoversWait(request, wait) {
|
|
1023
|
+
if (typeof request.startedAt !== 'number')
|
|
1024
|
+
return false;
|
|
1025
|
+
return request.startedAt >= wait.start - 1;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* The stalled-navigation reading for one trace — or null.
|
|
1029
|
+
*
|
|
1030
|
+
* All three facts must hold together: the runner says THIS test's failing operation is a
|
|
1031
|
+
* navigation ({@link NAVIGATION_OPERATIONS}); the trace's record of that call never finished; and
|
|
1032
|
+
* a document subresource still pending at the end started inside that wait
|
|
1033
|
+
* ({@link requestCoversWait}). An asset merely unfinished when the test was cancelled, or one a
|
|
1034
|
+
* teardown/`afterEach` trace holds, meets none of these and is not a cause (m153).
|
|
1035
|
+
*
|
|
1036
|
+
* @param item - One traced failure.
|
|
1037
|
+
* @param report - The runner's report and the run's next step.
|
|
1038
|
+
* @returns The wait and the assets it was waiting on, or null.
|
|
1039
|
+
*/
|
|
1040
|
+
function stalledNavigation(item, report) {
|
|
1041
|
+
const failure = runFailureOf(item, report.failures ?? []);
|
|
1042
|
+
if (failure === undefined)
|
|
1043
|
+
return null;
|
|
1044
|
+
const method = navigationMethod(failure.operation);
|
|
1045
|
+
if (method === null)
|
|
1046
|
+
return null;
|
|
1047
|
+
let wait = null;
|
|
1048
|
+
for (const action of item.actions) {
|
|
1049
|
+
if (!action.api.endsWith(`.${method}`))
|
|
1050
|
+
continue;
|
|
1051
|
+
if (wait === null || action.start > wait.start)
|
|
1052
|
+
wait = action;
|
|
1053
|
+
}
|
|
1054
|
+
// The navigation is the failure only while it is still running at the end of the test.
|
|
1055
|
+
if (wait === null || wait.end !== null)
|
|
1056
|
+
return null;
|
|
1057
|
+
const assets = item.requests.filter((request) => stalledDocumentSubresource(request) && requestCoversWait(request, wait));
|
|
1058
|
+
if (assets.length === 0)
|
|
1059
|
+
return null;
|
|
1060
|
+
return { reported: failure.operation ?? wait.api, wait, assets, budgetMs: failure.budgetMs };
|
|
1061
|
+
}
|
|
968
1062
|
/**
|
|
969
1063
|
* Whether a trace holds evidence the note can explain: a request that got a RECORDED response, a
|
|
970
1064
|
* real failure (4xx/5xx or a network error), or a console line. A request with no response is
|
|
@@ -974,31 +1068,38 @@ function repeatsEarlierFailure(item, earlier) {
|
|
|
974
1068
|
*
|
|
975
1069
|
* A request the page shell made (m125 +29:33: `/sw.js`, a font) is not evidence about the step
|
|
976
1070
|
* either, answered or not. The exception is a document subresource that never finished while the
|
|
977
|
-
*
|
|
978
|
-
* `page.goto` timed out ({@link
|
|
1071
|
+
* navigation the RUNNER reports failing was still open: that navigation waits on it, so it is the
|
|
1072
|
+
* reason the `page.goto` timed out ({@link stalledNavigation}).
|
|
979
1073
|
*
|
|
980
1074
|
* @param item - One traced failure.
|
|
1075
|
+
* @param report - The runner's report and the run's next step.
|
|
981
1076
|
* @returns True when the trace holds a request or console line worth a row.
|
|
982
1077
|
*/
|
|
983
|
-
function traceRowEvidence(item) {
|
|
1078
|
+
function traceRowEvidence(item, report) {
|
|
984
1079
|
return (item.console.length > 0 ||
|
|
985
1080
|
item.requests.some((request) => !isStaticAssetRequest(request) && !unansweredRequest(request)) ||
|
|
986
|
-
// The one shape in which a subresource with no response IS the cause: the navigation
|
|
987
|
-
//
|
|
988
|
-
(
|
|
1081
|
+
// The one shape in which a subresource with no response IS the cause: the navigation the
|
|
1082
|
+
// runner reported was still running at the end (see {@link stalledNavigation}).
|
|
1083
|
+
stalledNavigation(item, report) !== null);
|
|
989
1084
|
}
|
|
990
1085
|
/**
|
|
991
1086
|
* The note's row order: a row with a REAL failure first, then a row whose failure an earlier walk
|
|
992
1087
|
* also had, then the rest. A recurrence is the only evidence a row carries when its requests were
|
|
993
1088
|
* all answered (m112's favorite cause: the click's own request answered, the button never
|
|
994
1089
|
* changed).
|
|
1090
|
+
*
|
|
1091
|
+
* @param item - One traced failure.
|
|
1092
|
+
* @param earlier - The failing `file:line`s earlier walks of this suite left.
|
|
1093
|
+
* @param report - The runner's report and the run's next step.
|
|
1094
|
+
* @returns The row's tier: 0 first, 2 last.
|
|
995
1095
|
*/
|
|
996
|
-
function traceRowTier(item, earlier) {
|
|
1096
|
+
function traceRowTier(item, earlier, report) {
|
|
997
1097
|
if (item.requests.some(failedRequest))
|
|
998
1098
|
return 0;
|
|
999
|
-
// A navigation
|
|
1000
|
-
// strong a cause as a failed request — it is the one fact the timeout was waiting on
|
|
1001
|
-
|
|
1099
|
+
// A navigation the runner reported, still waiting on a subresource that never got a response,
|
|
1100
|
+
// is as strong a cause as a failed request — it is the one fact the timeout was waiting on
|
|
1101
|
+
// (m135).
|
|
1102
|
+
if (stalledNavigation(item, report) !== null)
|
|
1002
1103
|
return 0;
|
|
1003
1104
|
if (repeatsEarlierFailure(item, earlier))
|
|
1004
1105
|
return 1;
|
|
@@ -1267,12 +1368,16 @@ function missingApiPrefixSentence(request) {
|
|
|
1267
1368
|
* transport error (`net::ERR_CONTENT_DECODING_FAILED` and friends) and the document it hit: nothing
|
|
1268
1369
|
* rendered, so no row below is app evidence (xr-molecule-flash-m118 +37:09).
|
|
1269
1370
|
*
|
|
1270
|
-
* When a navigation never finished and one of the document's
|
|
1271
|
-
* script, an asset under `/assets/`) never got a response either
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
* (`/sw.js`, the manifest, a
|
|
1371
|
+
* When a navigation the runner reports as the failure never finished and one of the document's
|
|
1372
|
+
* own subresources (a stylesheet, a script, an asset under `/assets/`) never got a response either
|
|
1373
|
+
* — having started inside that wait — the request takes a row and the note says the page was
|
|
1374
|
+
* waiting on it. It is not an API call, but on a `page.goto` timeout it is the only request that
|
|
1375
|
+
* can explain the timeout — the deployed app was serving the page while one fetch stalled for 27 s
|
|
1376
|
+
* (xr-molecule-flash-m135 +34:10). The page shell's incidental requests (`/sw.js`, the manifest, a
|
|
1377
|
+
* favicon, a font) stay out, for m125's reason. An asset merely unfinished when a selector wait
|
|
1378
|
+
* ran out the test budget, or one a teardown/`afterEach` trace holds, is not a cause: the note
|
|
1379
|
+
* says it "had no recorded response when the test ended; that alone does not establish the cause"
|
|
1380
|
+
* (xr-molecule-flash-m153 +13:43).
|
|
1276
1381
|
*
|
|
1277
1382
|
* A 404 whose path lacks the API prefix the app's own calls carry is called out with that prefix
|
|
1278
1383
|
* ({@link missingApiPrefixRequests}): xr-molecule-flash-m145 posted `/users` to `vite preview`,
|
|
@@ -1297,17 +1402,22 @@ function missingApiPrefixSentence(request) {
|
|
|
1297
1402
|
* @param apiPrefixes - The API path prefixes the app's client config names (`['/api']`), read from
|
|
1298
1403
|
* the app by the caller. Empty when the caller could not read one; the traces' own requests may
|
|
1299
1404
|
* still supply it. Never a hard-coded literal.
|
|
1405
|
+
* @param report - The runner's own report for this run ({@link TraceRunReport}): each failing
|
|
1406
|
+
* test's declared location, the operation its failure names and the deadline it names, plus
|
|
1407
|
+
* whether the same result's `testRunNote` already points at a production build. A pending asset
|
|
1408
|
+
* is called the cause only when the runner says the failing operation for THAT test is the
|
|
1409
|
+
* navigation it was loading; without the report nothing is called a navigation stall.
|
|
1300
1410
|
* @returns The note, or null when no trace held a recorded response, a real failure or a console
|
|
1301
1411
|
* error.
|
|
1302
1412
|
*/
|
|
1303
|
-
export function failureTraceNote(items, target, earlierFailures = [], apiPrefixes = []) {
|
|
1304
|
-
const candidates = items.filter(traceRowEvidence);
|
|
1413
|
+
export function failureTraceNote(items, target, earlierFailures = [], apiPrefixes = [], report = {}) {
|
|
1414
|
+
const candidates = items.filter((item) => traceRowEvidence(item, report));
|
|
1305
1415
|
if (candidates.length === 0)
|
|
1306
1416
|
return null;
|
|
1307
1417
|
// Ranked, then deduplicated BY LOCATION — in that order, so the row kept for a location is the
|
|
1308
1418
|
// one carrying the strongest evidence (m112's duplicate `afterEach` rows).
|
|
1309
1419
|
const ranked = candidates
|
|
1310
|
-
.map((item, index) => ({ item, index, tier: traceRowTier(item, earlierFailures) }))
|
|
1420
|
+
.map((item, index) => ({ item, index, tier: traceRowTier(item, earlierFailures, report) }))
|
|
1311
1421
|
.sort((left, right) => left.tier - right.tier || left.index - right.index);
|
|
1312
1422
|
const traced = [];
|
|
1313
1423
|
const seen = new Set();
|
|
@@ -1345,14 +1455,20 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1345
1455
|
const authRefusals = failedRequests.filter((request) => sameOriginPath(request) !== null && (request.status === 401 || request.status === 403));
|
|
1346
1456
|
const everyFailureAuth = failedRequests.length > 0 && authRefusals.length === failedRequests.length;
|
|
1347
1457
|
const authAdvice = authRefusalAdvice(traced);
|
|
1348
|
-
// The
|
|
1349
|
-
//
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1458
|
+
// The stalled-navigation reading, per traced test: non-null only when the RUNNER says this
|
|
1459
|
+
// test's failing operation is a navigation and a document subresource still pending at the end
|
|
1460
|
+
// started inside that wait ({@link stalledNavigation}).
|
|
1461
|
+
const stalls = traced.map((item) => stalledNavigation(item, report));
|
|
1462
|
+
// "Every failure this run traced…" is only honest when every traced test meets that bar — one
|
|
1463
|
+
// stalled trace beside a selector-wait failure is not the cause of the run (m153).
|
|
1464
|
+
const everyTracedStalls = stalls.every((stall) => stall !== null);
|
|
1465
|
+
const stalledAssets = everyTracedStalls
|
|
1466
|
+
? [...new Set(stalls.flatMap((stall) => stall?.assets.map(requestLabel) ?? []))]
|
|
1467
|
+
: [];
|
|
1468
|
+
const stalledWaits = [
|
|
1469
|
+
...new Set(stalls.flatMap((stall) => (stall === null ? [] : [stall.reported]))),
|
|
1355
1470
|
];
|
|
1471
|
+
const anyStalled = stalls.some((stall) => stall !== null);
|
|
1356
1472
|
const rows = [];
|
|
1357
1473
|
for (const [index, item] of traced.entries()) {
|
|
1358
1474
|
// The test's LAST action before the failing assertion leads the row (m148): the requests it
|
|
@@ -1360,10 +1476,24 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1360
1476
|
// page's own last call (a redirect target's `GET /api/tags`) and the shell's files do not.
|
|
1361
1477
|
const lead = item.lead;
|
|
1362
1478
|
const step = item.lastAction;
|
|
1363
|
-
// A navigation
|
|
1364
|
-
//
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1479
|
+
// A navigation the runner reported, still waiting on a stalled asset (m135), leads the row
|
|
1480
|
+
// instead of the action's own document request.
|
|
1481
|
+
const stall = stalls[index] ?? null;
|
|
1482
|
+
const stalledNav = stall !== null;
|
|
1483
|
+
// The document subresources that never finished inside the navigation the runner reported:
|
|
1484
|
+
// the page was waiting on them when the test's budget ran out (m135).
|
|
1485
|
+
const stalled = stall?.assets ?? [];
|
|
1486
|
+
// The same kind of pending asset on a trace the runner does NOT report as a navigation
|
|
1487
|
+
// failure (a selector wait that ran out the test budget, an `afterEach`/teardown trace): it is
|
|
1488
|
+
// not a cause, and it is still worth naming so the executor does not have to guess (m153).
|
|
1489
|
+
const stalledKeys = new Set(stalled.map(requestKey));
|
|
1490
|
+
const pendingAssets = item.requests.filter((request) => stalledDocumentSubresource(request) && !stalledKeys.has(requestKey(request)));
|
|
1491
|
+
const pendingKeys = new Set(pendingAssets.map(requestKey));
|
|
1492
|
+
// A pending asset says nothing about what the action got back, so it never rides the action
|
|
1493
|
+
// lead — the honest bucket below names it (m153).
|
|
1494
|
+
const stepRequests = step !== undefined && !stalledNav
|
|
1495
|
+
? step.requests.filter((request) => !pendingKeys.has(requestKey(request)))
|
|
1496
|
+
: [];
|
|
1367
1497
|
const stepKeys = new Set(stepRequests.map(requestKey));
|
|
1368
1498
|
const isLead = (request) => lead !== undefined && requestKey(request) === requestKey(lead);
|
|
1369
1499
|
// The page shell's own requests take no row, in any bucket: an aborted `/sw.js` would
|
|
@@ -1372,20 +1502,16 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1372
1502
|
const failed = requests.filter(failedRequest);
|
|
1373
1503
|
const unanswered = requests.filter(unansweredRequest);
|
|
1374
1504
|
const answered = requests.filter((request) => !failedRequest(request) && !unansweredRequest(request));
|
|
1375
|
-
|
|
1376
|
-
// the page was waiting on it when the test's budget ran out (m135).
|
|
1377
|
-
const stalled = navigationNeverFinished(item)
|
|
1378
|
-
? item.requests.filter(stalledDocumentSubresource)
|
|
1379
|
-
: [];
|
|
1505
|
+
const budget = stall?.budgetMs ?? null;
|
|
1380
1506
|
let room = MAX_TRACE_NOTE_REQUESTS;
|
|
1381
1507
|
const parts = [];
|
|
1382
1508
|
if (step !== undefined && !stalledNav) {
|
|
1383
1509
|
const head = `the test's last action was ${step.api} at ${actionClock(item, step.start)}`;
|
|
1384
|
-
if (
|
|
1510
|
+
if (stepRequests.length === 0) {
|
|
1385
1511
|
parts.push(`${head}, and it sent no request`);
|
|
1386
1512
|
}
|
|
1387
1513
|
else {
|
|
1388
|
-
const shown =
|
|
1514
|
+
const shown = stepRequests.slice(0, room);
|
|
1389
1515
|
room -= shown.length;
|
|
1390
1516
|
// The response body is the point (m148: the register `POST /api/users` answered 201 and
|
|
1391
1517
|
// the page rendered signed-out anyway) — printed whatever the status.
|
|
@@ -1412,18 +1538,25 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1412
1538
|
const shown = stalled.slice(0, room);
|
|
1413
1539
|
room -= shown.length;
|
|
1414
1540
|
parts.push(`never finished: ${shown.map(requestLabel).join('; ')} — a document subresource with no ` +
|
|
1415
|
-
|
|
1541
|
+
`response, still in flight when the ${stall?.reported ?? 'navigation'} wait ran out` +
|
|
1542
|
+
(budget === null ? '' : ` of the suite's ${Math.round(budget / 1000)} s per-test budget`));
|
|
1543
|
+
}
|
|
1544
|
+
if (pendingAssets.length > 0 && room > 0) {
|
|
1545
|
+
const shown = pendingAssets.slice(0, room);
|
|
1546
|
+
room -= shown.length;
|
|
1547
|
+
parts.push(`had no recorded response when the test ended; that alone does not establish the cause: ` +
|
|
1548
|
+
shown.map(requestLabel).join('; '));
|
|
1416
1549
|
}
|
|
1417
1550
|
if (unanswered.length > 0 && room > 0) {
|
|
1418
1551
|
const shown = unanswered.slice(0, room);
|
|
1419
1552
|
room -= shown.length;
|
|
1420
|
-
parts.push(`no response recorded — still in flight when the test ended, so not a server failure
|
|
1421
|
-
shown.map(requestLabel).join('; '));
|
|
1553
|
+
parts.push(`no response recorded — still in flight when the test ended, so not a server failure; ` +
|
|
1554
|
+
`that alone does not establish the cause: ${shown.map(requestLabel).join('; ')}`);
|
|
1422
1555
|
}
|
|
1423
1556
|
if (answered.length > 0 && room > 0) {
|
|
1424
1557
|
// An action that sent nothing leaves the page's own load as the only requests there are:
|
|
1425
1558
|
// name them for what they are, not as the action's answer.
|
|
1426
|
-
const label = step !== undefined && !stalledNav &&
|
|
1559
|
+
const label = step !== undefined && !stalledNav && stepRequests.length === 0
|
|
1427
1560
|
? 'also answered (started before the action)'
|
|
1428
1561
|
: 'answered';
|
|
1429
1562
|
parts.push(`${label}: ${answered.slice(0, room).map(requestLine).join('; ')}`);
|
|
@@ -1459,22 +1592,30 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1459
1592
|
'`MOL_E2E_PREVIEW_PLUGIN=0 npx vite preview --host 127.0.0.1 --port 4173 --strictPort`. '
|
|
1460
1593
|
: '') +
|
|
1461
1594
|
'Fix the server before the app.\n\n';
|
|
1462
|
-
// A navigation
|
|
1595
|
+
// A navigation the RUNNER reported failing, with a subresource that never got a response, and no
|
|
1463
1596
|
// transport error: the page was waiting on the asset when the budget ran out. That is the host
|
|
1464
1597
|
// or the network serving that file, not the app's state — say it before any app-side reading
|
|
1465
1598
|
// (xr-molecule-flash-m135 +34:10: three deployed goto timeouts on tests passing locally in
|
|
1466
1599
|
// 1.2-5.6 s, one 88 KB stylesheet fetch measured at 27 s, and the note said "the cause is in
|
|
1467
|
-
// the page").
|
|
1600
|
+
// the page"). Every traced test must meet the bar; one stalled trace beside a selector-wait
|
|
1601
|
+
// failure is not the run's cause (m153).
|
|
1468
1602
|
const stalledLead = transport !== null || stalledAssets.length === 0
|
|
1469
1603
|
? ''
|
|
1470
|
-
: `Every failure this run traced is a
|
|
1471
|
-
`
|
|
1604
|
+
: `Every failure this run traced is a navigation the runner reported failing ` +
|
|
1605
|
+
`(${stalledWaits.join(', ')}), and the document's own ` +
|
|
1606
|
+
`${stalledAssets.length === 1 ? 'subresource' : 'subresources'} ${stalledAssets.join(', ')} ` +
|
|
1472
1607
|
`never got a response — the navigation was waiting on ${stalledAssets.length === 1 ? 'it' : 'them'} ` +
|
|
1473
1608
|
`when the test's budget ran out. That is the host or the network serving the asset, not the ` +
|
|
1474
|
-
`app's state or a selector:
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1609
|
+
`app's state or a selector: ` +
|
|
1610
|
+
(report.productionBuildAdvised
|
|
1611
|
+
? `the run's own test note already names the next measurement — the same suite against ` +
|
|
1612
|
+
`a production build, because the dev server's page load is not the built app's — take ` +
|
|
1613
|
+
`that one step first, and come back to the app only if the same navigation times out ` +
|
|
1614
|
+
`there too with a stalled asset in the trace.\n\n`
|
|
1615
|
+
: `re-run the same tests once, unchanged, against ${target} before editing anything, ` +
|
|
1616
|
+
`and if the stall repeats measure that exact fetch ` +
|
|
1617
|
+
`(\`curl -o /dev/null -w '%{time_starttransfer} %{time_total}' <url>\`) before ` +
|
|
1618
|
+
`treating it as the app's.\n\n`);
|
|
1478
1619
|
return (transportLead +
|
|
1479
1620
|
stalledLead +
|
|
1480
1621
|
`The platform read the failing tests' own Playwright traces from the run against ${target} ` +
|
|
@@ -1493,11 +1634,18 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1493
1634
|
'read the handler this route reaches in your own source, and stop `catch`-ing the request of a ' +
|
|
1494
1635
|
'user action silently — log it.' +
|
|
1495
1636
|
authAdvice
|
|
1496
|
-
:
|
|
1497
|
-
?
|
|
1498
|
-
'
|
|
1499
|
-
|
|
1500
|
-
|
|
1637
|
+
: anyStalled
|
|
1638
|
+
? report.productionBuildAdvised
|
|
1639
|
+
? 'No listed request failed — none got a 4xx/5xx or a network error — but a navigation ' +
|
|
1640
|
+
'the runner reported failing was still waiting on a document subresource that never ' +
|
|
1641
|
+
'answered. Do not read this as a page-state bug, and do not re-run the same tests ' +
|
|
1642
|
+
'unchanged: the run’s own test note names the production build as the next ' +
|
|
1643
|
+
'measurement — take that one step first, and change the app only if the same ' +
|
|
1644
|
+
'navigation times out there with no stalled asset in the trace.'
|
|
1645
|
+
: 'No listed request failed — none got a 4xx/5xx or a network error — but the document did ' +
|
|
1646
|
+
'not finish loading because its own subresource never answered. Do not read this as a ' +
|
|
1647
|
+
'page-state bug: re-run these tests against the same URL first, and change the app only ' +
|
|
1648
|
+
'if the same navigation times out again with no stalled asset in the trace.'
|
|
1501
1649
|
: 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
|
|
1502
1650
|
"page, not the API. Start with what the test's last action sent (the first line of each " +
|
|
1503
1651
|
'row) and what the page did next: a request that answered (2xx included) while the page ' +
|