@molecule/api-ai-tools 1.9.12 → 1.9.14
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 +303 -67
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +43 -9
- 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;
|
|
@@ -1077,19 +1178,36 @@ function sameOriginPath(request) {
|
|
|
1077
1178
|
* the request carried no `Authorization` header at all and the app's hand-rolled session store
|
|
1078
1179
|
* never handed its token to the shared client.
|
|
1079
1180
|
*
|
|
1181
|
+
* xr-molecule-flash-m156 (+16:15): the same 401 shape after a reload, and the page's own console
|
|
1182
|
+
* named the cause — `@molecule/app-http` logged `no client is bonded`. The auth setup ran before
|
|
1183
|
+
* the HTTP setup and kept the fallback client, so the restored token landed on a client nothing
|
|
1184
|
+
* sent through. When the trace carries that line, the no-header sentence names the ordering fix
|
|
1185
|
+
* instead of leaving the run to re-derive it.
|
|
1186
|
+
*
|
|
1080
1187
|
* The trace cannot name the file that made the call: a real HAR entry carries the request, response,
|
|
1081
1188
|
* `_resourceType`, `_monotonicTime` and a frame ref, but no initiator stack. So the sentence names
|
|
1082
1189
|
* the shared client and the auth bond instead of guessing a file.
|
|
1083
1190
|
*
|
|
1084
1191
|
* @param request - The refused request, a 401 or 403 on the app's own origin.
|
|
1192
|
+
* @param unbondedClient - Whether the traced page logged `no client is bonded`, which explains a
|
|
1193
|
+
* request that carried no `Authorization` header.
|
|
1085
1194
|
* @returns The sentence the note appends.
|
|
1086
1195
|
*/
|
|
1087
|
-
function authRefusalSentence(request) {
|
|
1196
|
+
function authRefusalSentence(request, unbondedClient = false) {
|
|
1088
1197
|
const label = requestLabel(request);
|
|
1089
1198
|
// The header's scheme, when it parsed; presence alone is still the server's half of the wire.
|
|
1090
1199
|
const carried = request.authorization !== undefined
|
|
1091
1200
|
? `with \`Authorization: ${request.authorization} …\``
|
|
1092
1201
|
: 'with an `Authorization` header';
|
|
1202
|
+
// The console line names the mechanism the no-header branch otherwise has to guess at:
|
|
1203
|
+
// `getClient()` ran before `setupHttpDefault()` and the token was mirrored into the
|
|
1204
|
+
// client that later setup replaced (xr-molecule-flash-m156).
|
|
1205
|
+
const unbonded = unbondedClient
|
|
1206
|
+
? ' The page logged `no client is bonded` — something called `getClient()` from ' +
|
|
1207
|
+
'`@molecule/app-http` before `setupHttpDefault()` ran and kept that fallback client; ' +
|
|
1208
|
+
'in `app/src/bonds/index.ts` call `setupHttpDefault()` before `setupAuthDefault()`, ' +
|
|
1209
|
+
'and read the client per request instead of capturing it at setup.'
|
|
1210
|
+
: '';
|
|
1093
1211
|
if (request.status === 403) {
|
|
1094
1212
|
return request.sentAuthorization === true
|
|
1095
1213
|
? `The browser sent \`${label}\` ${carried} and the API answered 403 — the token was ` +
|
|
@@ -1099,7 +1217,8 @@ function authRefusalSentence(request) {
|
|
|
1099
1217
|
'`Authorization` header, so either the client did not attach the token (check that the ' +
|
|
1100
1218
|
'request goes through the app’s HTTP client that the auth bond configures, and that the ' +
|
|
1101
1219
|
'token is stored where the client reads it) or the route refuses this user: read the ' +
|
|
1102
|
-
'route’s role/permission check in your own source.'
|
|
1220
|
+
'route’s role/permission check in your own source.' +
|
|
1221
|
+
unbonded;
|
|
1103
1222
|
}
|
|
1104
1223
|
if (request.sentAuthorization === true) {
|
|
1105
1224
|
return (`The browser sent \`${label}\` ${carried} and the API refused it: the token was sent and ` +
|
|
@@ -1117,7 +1236,8 @@ function authRefusalSentence(request) {
|
|
|
1117
1236
|
? 'The request did carry a `Cookie` header, so if this app signs in with a session cookie ' +
|
|
1118
1237
|
'that is the credential the server did not accept. '
|
|
1119
1238
|
: '') +
|
|
1120
|
-
'The API handler is not the problem.'
|
|
1239
|
+
'The API handler is not the problem.' +
|
|
1240
|
+
unbonded);
|
|
1121
1241
|
}
|
|
1122
1242
|
/**
|
|
1123
1243
|
* The note's credential-refusal advice, one sentence per distinct shape — at most two.
|
|
@@ -1133,23 +1253,67 @@ function authRefusalSentence(request) {
|
|
|
1133
1253
|
function authRefusalAdvice(items) {
|
|
1134
1254
|
const sentences = [];
|
|
1135
1255
|
const seen = new Set();
|
|
1256
|
+
// A page that logged `no client is bonded` explains every request it sent without a
|
|
1257
|
+
// token, so the advice names that mechanism once for the run
|
|
1258
|
+
// (xr-molecule-flash-m156).
|
|
1259
|
+
const unbondedClient = items.some((item) => item.console.some((line) => line.includes('no client is bonded')));
|
|
1136
1260
|
for (const item of items) {
|
|
1137
1261
|
for (const request of item.requests) {
|
|
1138
1262
|
if (request.status !== 401 && request.status !== 403)
|
|
1139
1263
|
continue;
|
|
1140
1264
|
if (sameOriginPath(request) === null)
|
|
1141
1265
|
continue;
|
|
1266
|
+
// A 4xx on the logout endpoint is the shape the row itself answers: the auth client
|
|
1267
|
+
// ignores it and clears the session, and the page owes the navigation (m156).
|
|
1268
|
+
if (isLogoutRequest(request))
|
|
1269
|
+
continue;
|
|
1142
1270
|
const key = `${request.status} ${request.sentAuthorization ? (request.authorization ?? 'header') : 'none'} ${request.sentCookie ? 'cookie' : ''}`;
|
|
1143
1271
|
if (seen.has(key))
|
|
1144
1272
|
continue;
|
|
1145
1273
|
seen.add(key);
|
|
1146
|
-
sentences.push(authRefusalSentence(request));
|
|
1274
|
+
sentences.push(authRefusalSentence(request, unbondedClient));
|
|
1147
1275
|
if (sentences.length === 2)
|
|
1148
1276
|
return ` ${sentences.join(' ')}`;
|
|
1149
1277
|
}
|
|
1150
1278
|
}
|
|
1151
1279
|
return sentences.length === 0 ? '' : ` ${sentences.join(' ')}`;
|
|
1152
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Whether a request went to an auth client's logout endpoint. The client's default
|
|
1283
|
+
* `logoutEndpoint` is `/users/logout` (`@molecule/app-auth`), and a project may name its own
|
|
1284
|
+
* (`/auth/logout`), so the last segment is what identifies it — never a literal path.
|
|
1285
|
+
*
|
|
1286
|
+
* @param request - One request a trace recorded.
|
|
1287
|
+
* @returns True for a POST to a path ending in `/logout`.
|
|
1288
|
+
*/
|
|
1289
|
+
function isLogoutRequest(request) {
|
|
1290
|
+
if (request.method !== 'POST')
|
|
1291
|
+
return false;
|
|
1292
|
+
return /\/(?:users\/)?logout$/i.test(requestPath(request));
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* The sentence a 4xx on the auth client's logout endpoint owes when the page then stayed put.
|
|
1296
|
+
*
|
|
1297
|
+
* `@molecule/app-auth`'s `logout()` calls the endpoint inside a `.catch` and clears the session in
|
|
1298
|
+
* `finally` (`packages/app/core/auth/src/client.ts`): a 401 there is EXPECTED on a sign-out and is
|
|
1299
|
+
* not the failure. What the test waits for is the URL moving, which the page has to do itself.
|
|
1300
|
+
* xr-molecule-flash-m156 (+24:02 → +33:52) read `POST /api/users/logout → 401` as a broken
|
|
1301
|
+
* endpoint, added a backend logout route, and paid a re-run and a redeploy — while the real cause
|
|
1302
|
+
* was the `navigate('/')` the refactor had dropped.
|
|
1303
|
+
*
|
|
1304
|
+
* @param request - The 4xx logout request.
|
|
1305
|
+
* @param pageUrl - The URL the page stayed at, when the trace recorded one.
|
|
1306
|
+
* @returns The sentence the note appends to the row.
|
|
1307
|
+
*/
|
|
1308
|
+
function logoutIgnoredSentence(request, pageUrl) {
|
|
1309
|
+
const where = pageUrl === undefined ? 'The page' : `\`${pageUrl}\``;
|
|
1310
|
+
return (`The ${request.status} on \`${requestLabel(request)}\` is ignored by the auth client — ` +
|
|
1311
|
+
'`logout()` catches that call and clears the session in `finally` (`@molecule/app-auth`), so ' +
|
|
1312
|
+
'the request is not what broke the test and the API is not the fix; do not add or change a ' +
|
|
1313
|
+
`logout route. ${where} stayed on the same URL: the page has to navigate itself after ` +
|
|
1314
|
+
"`logout()` (call `navigate('/')`, or the route the suite waits for) in the handler that " +
|
|
1315
|
+
'signs out.');
|
|
1316
|
+
}
|
|
1153
1317
|
/**
|
|
1154
1318
|
* The API path prefixes the traced requests themselves show.
|
|
1155
1319
|
*
|
|
@@ -1267,12 +1431,16 @@ function missingApiPrefixSentence(request) {
|
|
|
1267
1431
|
* transport error (`net::ERR_CONTENT_DECODING_FAILED` and friends) and the document it hit: nothing
|
|
1268
1432
|
* rendered, so no row below is app evidence (xr-molecule-flash-m118 +37:09).
|
|
1269
1433
|
*
|
|
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
|
|
1434
|
+
* When a navigation the runner reports as the failure never finished and one of the document's
|
|
1435
|
+
* own subresources (a stylesheet, a script, an asset under `/assets/`) never got a response either
|
|
1436
|
+
* — having started inside that wait — the request takes a row and the note says the page was
|
|
1437
|
+
* waiting on it. It is not an API call, but on a `page.goto` timeout it is the only request that
|
|
1438
|
+
* can explain the timeout — the deployed app was serving the page while one fetch stalled for 27 s
|
|
1439
|
+
* (xr-molecule-flash-m135 +34:10). The page shell's incidental requests (`/sw.js`, the manifest, a
|
|
1440
|
+
* favicon, a font) stay out, for m125's reason. An asset merely unfinished when a selector wait
|
|
1441
|
+
* ran out the test budget, or one a teardown/`afterEach` trace holds, is not a cause: the note
|
|
1442
|
+
* says it "had no recorded response when the test ended; that alone does not establish the cause"
|
|
1443
|
+
* (xr-molecule-flash-m153 +13:43).
|
|
1276
1444
|
*
|
|
1277
1445
|
* A 404 whose path lacks the API prefix the app's own calls carry is called out with that prefix
|
|
1278
1446
|
* ({@link missingApiPrefixRequests}): xr-molecule-flash-m145 posted `/users` to `vite preview`,
|
|
@@ -1297,17 +1465,22 @@ function missingApiPrefixSentence(request) {
|
|
|
1297
1465
|
* @param apiPrefixes - The API path prefixes the app's client config names (`['/api']`), read from
|
|
1298
1466
|
* the app by the caller. Empty when the caller could not read one; the traces' own requests may
|
|
1299
1467
|
* still supply it. Never a hard-coded literal.
|
|
1468
|
+
* @param report - The runner's own report for this run ({@link TraceRunReport}): each failing
|
|
1469
|
+
* test's declared location, the operation its failure names and the deadline it names, plus
|
|
1470
|
+
* whether the same result's `testRunNote` already points at a production build. A pending asset
|
|
1471
|
+
* is called the cause only when the runner says the failing operation for THAT test is the
|
|
1472
|
+
* navigation it was loading; without the report nothing is called a navigation stall.
|
|
1300
1473
|
* @returns The note, or null when no trace held a recorded response, a real failure or a console
|
|
1301
1474
|
* error.
|
|
1302
1475
|
*/
|
|
1303
|
-
export function failureTraceNote(items, target, earlierFailures = [], apiPrefixes = []) {
|
|
1304
|
-
const candidates = items.filter(traceRowEvidence);
|
|
1476
|
+
export function failureTraceNote(items, target, earlierFailures = [], apiPrefixes = [], report = {}) {
|
|
1477
|
+
const candidates = items.filter((item) => traceRowEvidence(item, report));
|
|
1305
1478
|
if (candidates.length === 0)
|
|
1306
1479
|
return null;
|
|
1307
1480
|
// Ranked, then deduplicated BY LOCATION — in that order, so the row kept for a location is the
|
|
1308
1481
|
// one carrying the strongest evidence (m112's duplicate `afterEach` rows).
|
|
1309
1482
|
const ranked = candidates
|
|
1310
|
-
.map((item, index) => ({ item, index, tier: traceRowTier(item, earlierFailures) }))
|
|
1483
|
+
.map((item, index) => ({ item, index, tier: traceRowTier(item, earlierFailures, report) }))
|
|
1311
1484
|
.sort((left, right) => left.tier - right.tier || left.index - right.index);
|
|
1312
1485
|
const traced = [];
|
|
1313
1486
|
const seen = new Set();
|
|
@@ -1342,17 +1515,30 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1342
1515
|
: '';
|
|
1343
1516
|
// A credential refusal (401/403 on the app's own API) is not the handler's answer: the trace's
|
|
1344
1517
|
// own `Authorization` record says whether the CLIENT attached the token at all (m148 +29:18).
|
|
1345
|
-
const authRefusals = failedRequests.filter((request) => sameOriginPath(request) !== null &&
|
|
1518
|
+
const authRefusals = failedRequests.filter((request) => sameOriginPath(request) !== null &&
|
|
1519
|
+
(request.status === 401 || request.status === 403) &&
|
|
1520
|
+
// A 4xx on the logout endpoint is not a credential refusal to diagnose: the auth client
|
|
1521
|
+
// ignores it and clears the session anyway, and the row says so itself (m156).
|
|
1522
|
+
!isLogoutRequest(request));
|
|
1346
1523
|
const everyFailureAuth = failedRequests.length > 0 && authRefusals.length === failedRequests.length;
|
|
1524
|
+
// Every recorded failure is the logout endpoint answering 4xx: the row's own sentence explains
|
|
1525
|
+
// it, and neither the handler lead nor the credential reading applies (m156 +24:02).
|
|
1526
|
+
const onlyLogoutFailures = failedRequests.length > 0 && failedRequests.every((request) => isLogoutRequest(request));
|
|
1347
1527
|
const authAdvice = authRefusalAdvice(traced);
|
|
1348
|
-
// The
|
|
1349
|
-
//
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1528
|
+
// The stalled-navigation reading, per traced test: non-null only when the RUNNER says this
|
|
1529
|
+
// test's failing operation is a navigation and a document subresource still pending at the end
|
|
1530
|
+
// started inside that wait ({@link stalledNavigation}).
|
|
1531
|
+
const stalls = traced.map((item) => stalledNavigation(item, report));
|
|
1532
|
+
// "Every failure this run traced…" is only honest when every traced test meets that bar — one
|
|
1533
|
+
// stalled trace beside a selector-wait failure is not the cause of the run (m153).
|
|
1534
|
+
const everyTracedStalls = stalls.every((stall) => stall !== null);
|
|
1535
|
+
const stalledAssets = everyTracedStalls
|
|
1536
|
+
? [...new Set(stalls.flatMap((stall) => stall?.assets.map(requestLabel) ?? []))]
|
|
1537
|
+
: [];
|
|
1538
|
+
const stalledWaits = [
|
|
1539
|
+
...new Set(stalls.flatMap((stall) => (stall === null ? [] : [stall.reported]))),
|
|
1355
1540
|
];
|
|
1541
|
+
const anyStalled = stalls.some((stall) => stall !== null);
|
|
1356
1542
|
const rows = [];
|
|
1357
1543
|
for (const [index, item] of traced.entries()) {
|
|
1358
1544
|
// The test's LAST action before the failing assertion leads the row (m148): the requests it
|
|
@@ -1360,10 +1546,24 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1360
1546
|
// page's own last call (a redirect target's `GET /api/tags`) and the shell's files do not.
|
|
1361
1547
|
const lead = item.lead;
|
|
1362
1548
|
const step = item.lastAction;
|
|
1363
|
-
// A navigation
|
|
1364
|
-
//
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1549
|
+
// A navigation the runner reported, still waiting on a stalled asset (m135), leads the row
|
|
1550
|
+
// instead of the action's own document request.
|
|
1551
|
+
const stall = stalls[index] ?? null;
|
|
1552
|
+
const stalledNav = stall !== null;
|
|
1553
|
+
// The document subresources that never finished inside the navigation the runner reported:
|
|
1554
|
+
// the page was waiting on them when the test's budget ran out (m135).
|
|
1555
|
+
const stalled = stall?.assets ?? [];
|
|
1556
|
+
// The same kind of pending asset on a trace the runner does NOT report as a navigation
|
|
1557
|
+
// failure (a selector wait that ran out the test budget, an `afterEach`/teardown trace): it is
|
|
1558
|
+
// not a cause, and it is still worth naming so the executor does not have to guess (m153).
|
|
1559
|
+
const stalledKeys = new Set(stalled.map(requestKey));
|
|
1560
|
+
const pendingAssets = item.requests.filter((request) => stalledDocumentSubresource(request) && !stalledKeys.has(requestKey(request)));
|
|
1561
|
+
const pendingKeys = new Set(pendingAssets.map(requestKey));
|
|
1562
|
+
// A pending asset says nothing about what the action got back, so it never rides the action
|
|
1563
|
+
// lead — the honest bucket below names it (m153).
|
|
1564
|
+
const stepRequests = step !== undefined && !stalledNav
|
|
1565
|
+
? step.requests.filter((request) => !pendingKeys.has(requestKey(request)))
|
|
1566
|
+
: [];
|
|
1367
1567
|
const stepKeys = new Set(stepRequests.map(requestKey));
|
|
1368
1568
|
const isLead = (request) => lead !== undefined && requestKey(request) === requestKey(lead);
|
|
1369
1569
|
// The page shell's own requests take no row, in any bucket: an aborted `/sw.js` would
|
|
@@ -1372,20 +1572,16 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1372
1572
|
const failed = requests.filter(failedRequest);
|
|
1373
1573
|
const unanswered = requests.filter(unansweredRequest);
|
|
1374
1574
|
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
|
-
: [];
|
|
1575
|
+
const budget = stall?.budgetMs ?? null;
|
|
1380
1576
|
let room = MAX_TRACE_NOTE_REQUESTS;
|
|
1381
1577
|
const parts = [];
|
|
1382
1578
|
if (step !== undefined && !stalledNav) {
|
|
1383
1579
|
const head = `the test's last action was ${step.api} at ${actionClock(item, step.start)}`;
|
|
1384
|
-
if (
|
|
1580
|
+
if (stepRequests.length === 0) {
|
|
1385
1581
|
parts.push(`${head}, and it sent no request`);
|
|
1386
1582
|
}
|
|
1387
1583
|
else {
|
|
1388
|
-
const shown =
|
|
1584
|
+
const shown = stepRequests.slice(0, room);
|
|
1389
1585
|
room -= shown.length;
|
|
1390
1586
|
// The response body is the point (m148: the register `POST /api/users` answered 201 and
|
|
1391
1587
|
// the page rendered signed-out anyway) — printed whatever the status.
|
|
@@ -1398,6 +1594,19 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1398
1594
|
? `the page then navigated to ${step.pageAfter}`
|
|
1399
1595
|
: `the page stayed at ${step.pageAfter}`);
|
|
1400
1596
|
}
|
|
1597
|
+
// A 4xx on the logout endpoint with the page unmoved is not a backend failure: the auth
|
|
1598
|
+
// client swallows the call and clears the session, and the URL can only move if the page
|
|
1599
|
+
// navigates itself (xr-molecule-flash-m156 +24:02: the refactor dropped `navigate('/')` and
|
|
1600
|
+
// the run went and added a logout route instead).
|
|
1601
|
+
const stepLast = stepRequests.length > 0 ? stepRequests[stepRequests.length - 1] : undefined;
|
|
1602
|
+
if (stepLast !== undefined &&
|
|
1603
|
+
stepLast.status >= 400 &&
|
|
1604
|
+
stepLast.status < 500 &&
|
|
1605
|
+
isLogoutRequest(stepLast) &&
|
|
1606
|
+
step.pageBefore !== undefined &&
|
|
1607
|
+
step.pageAfter !== undefined &&
|
|
1608
|
+
step.pageBefore === step.pageAfter)
|
|
1609
|
+
parts.push(logoutIgnoredSentence(stepLast, step.pageAfter));
|
|
1401
1610
|
}
|
|
1402
1611
|
else if (lead !== undefined) {
|
|
1403
1612
|
parts.push(leadRequestLine(lead));
|
|
@@ -1412,18 +1621,25 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1412
1621
|
const shown = stalled.slice(0, room);
|
|
1413
1622
|
room -= shown.length;
|
|
1414
1623
|
parts.push(`never finished: ${shown.map(requestLabel).join('; ')} — a document subresource with no ` +
|
|
1415
|
-
|
|
1624
|
+
`response, still in flight when the ${stall?.reported ?? 'navigation'} wait ran out` +
|
|
1625
|
+
(budget === null ? '' : ` of the suite's ${Math.round(budget / 1000)} s per-test budget`));
|
|
1626
|
+
}
|
|
1627
|
+
if (pendingAssets.length > 0 && room > 0) {
|
|
1628
|
+
const shown = pendingAssets.slice(0, room);
|
|
1629
|
+
room -= shown.length;
|
|
1630
|
+
parts.push(`had no recorded response when the test ended; that alone does not establish the cause: ` +
|
|
1631
|
+
shown.map(requestLabel).join('; '));
|
|
1416
1632
|
}
|
|
1417
1633
|
if (unanswered.length > 0 && room > 0) {
|
|
1418
1634
|
const shown = unanswered.slice(0, room);
|
|
1419
1635
|
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('; '));
|
|
1636
|
+
parts.push(`no response recorded — still in flight when the test ended, so not a server failure; ` +
|
|
1637
|
+
`that alone does not establish the cause: ${shown.map(requestLabel).join('; ')}`);
|
|
1422
1638
|
}
|
|
1423
1639
|
if (answered.length > 0 && room > 0) {
|
|
1424
1640
|
// An action that sent nothing leaves the page's own load as the only requests there are:
|
|
1425
1641
|
// name them for what they are, not as the action's answer.
|
|
1426
|
-
const label = step !== undefined && !stalledNav &&
|
|
1642
|
+
const label = step !== undefined && !stalledNav && stepRequests.length === 0
|
|
1427
1643
|
? 'also answered (started before the action)'
|
|
1428
1644
|
: 'answered';
|
|
1429
1645
|
parts.push(`${label}: ${answered.slice(0, room).map(requestLine).join('; ')}`);
|
|
@@ -1459,22 +1675,30 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1459
1675
|
'`MOL_E2E_PREVIEW_PLUGIN=0 npx vite preview --host 127.0.0.1 --port 4173 --strictPort`. '
|
|
1460
1676
|
: '') +
|
|
1461
1677
|
'Fix the server before the app.\n\n';
|
|
1462
|
-
// A navigation
|
|
1678
|
+
// A navigation the RUNNER reported failing, with a subresource that never got a response, and no
|
|
1463
1679
|
// transport error: the page was waiting on the asset when the budget ran out. That is the host
|
|
1464
1680
|
// or the network serving that file, not the app's state — say it before any app-side reading
|
|
1465
1681
|
// (xr-molecule-flash-m135 +34:10: three deployed goto timeouts on tests passing locally in
|
|
1466
1682
|
// 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").
|
|
1683
|
+
// the page"). Every traced test must meet the bar; one stalled trace beside a selector-wait
|
|
1684
|
+
// failure is not the run's cause (m153).
|
|
1468
1685
|
const stalledLead = transport !== null || stalledAssets.length === 0
|
|
1469
1686
|
? ''
|
|
1470
|
-
: `Every failure this run traced is a
|
|
1471
|
-
`
|
|
1687
|
+
: `Every failure this run traced is a navigation the runner reported failing ` +
|
|
1688
|
+
`(${stalledWaits.join(', ')}), and the document's own ` +
|
|
1689
|
+
`${stalledAssets.length === 1 ? 'subresource' : 'subresources'} ${stalledAssets.join(', ')} ` +
|
|
1472
1690
|
`never got a response — the navigation was waiting on ${stalledAssets.length === 1 ? 'it' : 'them'} ` +
|
|
1473
1691
|
`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
|
-
|
|
1692
|
+
`app's state or a selector: ` +
|
|
1693
|
+
(report.productionBuildAdvised
|
|
1694
|
+
? `the run's own test note already names the next measurement — the same suite against ` +
|
|
1695
|
+
`a production build, because the dev server's page load is not the built app's — take ` +
|
|
1696
|
+
`that one step first, and come back to the app only if the same navigation times out ` +
|
|
1697
|
+
`there too with a stalled asset in the trace.\n\n`
|
|
1698
|
+
: `re-run the same tests once, unchanged, against ${target} before editing anything, ` +
|
|
1699
|
+
`and if the stall repeats measure that exact fetch ` +
|
|
1700
|
+
`(\`curl -o /dev/null -w '%{time_starttransfer} %{time_total}' <url>\`) before ` +
|
|
1701
|
+
`treating it as the app's.\n\n`);
|
|
1478
1702
|
return (transportLead +
|
|
1479
1703
|
stalledLead +
|
|
1480
1704
|
`The platform read the failing tests' own Playwright traces from the run against ${target} ` +
|
|
@@ -1485,19 +1709,31 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
|
|
|
1485
1709
|
(anyFailed
|
|
1486
1710
|
? everyFailureLacksPrefix
|
|
1487
1711
|
? ''
|
|
1488
|
-
:
|
|
1489
|
-
? //
|
|
1490
|
-
//
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1712
|
+
: onlyLogoutFailures
|
|
1713
|
+
? // The rows already say it: a 4xx on the logout endpoint is ignored by the auth client
|
|
1714
|
+
// and the page's own navigation is the missing step. The handler lead would send the
|
|
1715
|
+
// run at the backend (m156 +33:52 added a logout route).
|
|
1716
|
+
''
|
|
1717
|
+
: everyFailureAuth
|
|
1718
|
+
? // Every failure was a credential refusal: the handler is not the problem, and the
|
|
1719
|
+
// auth advice already names what is (m148 was sent to the handler six walks running).
|
|
1720
|
+
authAdvice
|
|
1721
|
+
: 'A click that changed nothing is usually one of these requests failing, not the selector: ' +
|
|
1722
|
+
'read the handler this route reaches in your own source, and stop `catch`-ing the request of a ' +
|
|
1723
|
+
'user action silently — log it.' +
|
|
1724
|
+
authAdvice
|
|
1725
|
+
: anyStalled
|
|
1726
|
+
? report.productionBuildAdvised
|
|
1727
|
+
? 'No listed request failed — none got a 4xx/5xx or a network error — but a navigation ' +
|
|
1728
|
+
'the runner reported failing was still waiting on a document subresource that never ' +
|
|
1729
|
+
'answered. Do not read this as a page-state bug, and do not re-run the same tests ' +
|
|
1730
|
+
'unchanged: the run’s own test note names the production build as the next ' +
|
|
1731
|
+
'measurement — take that one step first, and change the app only if the same ' +
|
|
1732
|
+
'navigation times out there with no stalled asset in the trace.'
|
|
1733
|
+
: 'No listed request failed — none got a 4xx/5xx or a network error — but the document did ' +
|
|
1734
|
+
'not finish loading because its own subresource never answered. Do not read this as a ' +
|
|
1735
|
+
'page-state bug: re-run these tests against the same URL first, and change the app only ' +
|
|
1736
|
+
'if the same navigation times out again with no stalled asset in the trace.'
|
|
1501
1737
|
: 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
|
|
1502
1738
|
"page, not the API. Start with what the test's last action sent (the first line of each " +
|
|
1503
1739
|
'row) and what the page did next: a request that answered (2xx included) while the page ' +
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAW3E;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAEE,CAAA;AAuGhD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErD;AAKD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAsCzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CAUpF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQnE;AAuDD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,0CAA0C,CAAA;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAC3D,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB,
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|
package/dist/utilities.js
CHANGED
|
@@ -478,12 +478,18 @@ export function streamingBackgroundCommand(command) {
|
|
|
478
478
|
};
|
|
479
479
|
// A diagnosis or a re-run (capped, last-failed, grepped, or named files) walks each failure
|
|
480
480
|
// once: the suite's own `retries` doubled every failure of X1 flash-m71's first whole run,
|
|
481
|
-
// which had dropped the `--retries=0` it used one command earlier.
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
481
|
+
// which had dropped the `--retries=0` it used one command earlier. A whole run whose TARGET is
|
|
482
|
+
// local — its env names `localhost`/`127.0.0.1`/`0.0.0.0`, or names no URL at all, as the
|
|
483
|
+
// wrapper config's default does — is not the proof run either and walks them once too:
|
|
484
|
+
// xr-molecule-flash-m155 (+11:34) spent 10 minutes on its first local whole run, and the
|
|
485
|
+
// suite's `retries: 1` walked each of its 68 failures a second time (0 flaky) for nothing.
|
|
486
|
+
// The suite's retries are kept only for a run that names a non-local URL — the deployed proof
|
|
487
|
+
// (xr-molecule-flash-m89), where they absorb a network blip. Which runs are diagnoses is
|
|
488
|
+
// {@link isDiagnosisRun}'s answer and nobody else's: the platform's notes ask the same
|
|
489
|
+
// function, so a run cannot be a diagnosis to one of them and the whole suite to the other.
|
|
490
|
+
// Which targets are local is {@link suiteCommandTarget}'s answer.
|
|
486
491
|
const diagnosis = isDiagnosisRun(lastStep);
|
|
492
|
+
const localTarget = suiteCommandTarget(lastStep) === 'local';
|
|
487
493
|
// The last step's reporters: one printer, and it is `list`. Every branch below only ever moves
|
|
488
494
|
// the command TO that shape — a reporter set is never left with two printers in it.
|
|
489
495
|
const reporters = finalPlaywrightRun ? lastReporterFlag(lastStep) : null;
|
|
@@ -549,9 +555,9 @@ export function streamingBackgroundCommand(command) {
|
|
|
549
555
|
'summary at the end — so a run that is still walking already says what its failures are, ' +
|
|
550
556
|
'and you need not wait for it to end to start on them.');
|
|
551
557
|
}
|
|
552
|
-
if (finalPlaywrightRun && diagnosis && !/--retries\b/.test(lastStep)) {
|
|
558
|
+
if (finalPlaywrightRun && (diagnosis || localTarget) && !/--retries\b/.test(lastStep)) {
|
|
553
559
|
if (addToRun('--retries=0'))
|
|
554
|
-
changes.push('Added `--retries=0`: a
|
|
560
|
+
changes.push('Added `--retries=0`: a local run walks each failure once; the suite’s retries apply on the deployed proof run.');
|
|
555
561
|
}
|
|
556
562
|
return { command: out, changes };
|
|
557
563
|
}
|
|
@@ -1953,6 +1959,23 @@ function largestGroup(groups) {
|
|
|
1953
1959
|
function testKey(t) {
|
|
1954
1960
|
return `${t.where}|${t.title}`;
|
|
1955
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* One test's identity with the runner's retry marker taken off: `… a.spec.ts:32 › does x
|
|
1964
|
+
* (retry #1)` is the same test as `… a.spec.ts:32 › does x`.
|
|
1965
|
+
*
|
|
1966
|
+
* A retried failure prints twice in a `list` log — its first attempt, then the same test with
|
|
1967
|
+
* `(retry #1)` after the title — while its failure block names it without the marker. Keyed
|
|
1968
|
+
* literally, the retry row is a test no cause claims, so it fell through to the duration arm and
|
|
1969
|
+
* was listed as a "shared duration" cause beside the proven cause it repeated: on
|
|
1970
|
+
* xr-molecule-flash-m155 (+21:50) every retry row of the run became one of six extra causes
|
|
1971
|
+
* duplicating the causes above them.
|
|
1972
|
+
*
|
|
1973
|
+
* @param t - A failed test, as a result line or a failure block spelled it.
|
|
1974
|
+
* @returns The key a test's retry row shares with its failure block.
|
|
1975
|
+
*/
|
|
1976
|
+
function retrylessTestKey(t) {
|
|
1977
|
+
return `${t.where}|${t.title.replace(/\s*\(retry #\d+\)$/, '')}`;
|
|
1978
|
+
}
|
|
1956
1979
|
/**
|
|
1957
1980
|
* The same tests with each one kept once, in first-seen order.
|
|
1958
1981
|
*
|
|
@@ -2600,6 +2623,10 @@ function groupFailures(log, excluded, pageErrors = []) {
|
|
|
2600
2623
|
const listed = failedTests(text);
|
|
2601
2624
|
named.push(...listed);
|
|
2602
2625
|
const claimed = new Set(causes.flatMap((c) => c.tests.map(testKey)));
|
|
2626
|
+
// The causes above as a test can recognise itself: a retried failure's second row carries the
|
|
2627
|
+
// runner's `(retry #1)` marker, its failure block does not, and comparing the literal keys
|
|
2628
|
+
// read the same test as an unclaimed one (xr-molecule-flash-m155 +21:50).
|
|
2629
|
+
const proven = new Set(causes.flatMap((c) => c.tests.map(retrylessTestKey)));
|
|
2603
2630
|
const unclaimed = distinctTests(listed).filter((t) => t.seconds !== null &&
|
|
2604
2631
|
!claimed.has(testKey(t)) &&
|
|
2605
2632
|
// A flaky test's `✘` row is its first attempt; its `✓` retry follows it in the same log,
|
|
@@ -2615,14 +2642,21 @@ function groupFailures(log, excluded, pageErrors = []) {
|
|
|
2615
2642
|
else
|
|
2616
2643
|
byDuration.push({ seconds: t.seconds, tests: [t] });
|
|
2617
2644
|
}
|
|
2618
|
-
for (const g of byDuration)
|
|
2645
|
+
for (const g of byDuration) {
|
|
2646
|
+
// A duration band is a LEAD, not a cause: a test already carrying a proven cause above is
|
|
2647
|
+
// not named a second time here, and a band whose every member is one is not a cause at all —
|
|
2648
|
+
// it reads as one more thing to fix. m155's retry rows made six such bands.
|
|
2649
|
+
const tests = g.tests.filter((t) => !proven.has(retrylessTestKey(t)));
|
|
2650
|
+
if (tests.length === 0)
|
|
2651
|
+
continue;
|
|
2619
2652
|
causes.push({
|
|
2620
2653
|
message: null,
|
|
2621
2654
|
seconds: g.seconds,
|
|
2622
|
-
tests
|
|
2655
|
+
tests,
|
|
2623
2656
|
evidence: [],
|
|
2624
2657
|
kind: 'assertion',
|
|
2625
2658
|
});
|
|
2659
|
+
}
|
|
2626
2660
|
const ignored = distinctTests(named).filter((t) => matchingExcludedTitles(t.title, excluded).length > 0);
|
|
2627
2661
|
return {
|
|
2628
2662
|
causes: causes.sort((a, b) => b.tests.length - a.tests.length),
|