@molecule/api-ai-tools 1.8.1 → 1.8.3
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 +174 -18
- package/dist/command-position.d.ts +68 -2
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +309 -43
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/sleep-peek.d.ts +51 -0
- package/dist/sleep-peek.d.ts.map +1 -0
- package/dist/sleep-peek.js +76 -0
- package/dist/tools.d.ts +56 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +285 -48
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +24 -19
- package/dist/utilities.d.ts +38 -9
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +150 -51
- package/package.json +1 -1
package/dist/trace-artifacts.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @module
|
|
28
28
|
*/
|
|
29
|
-
import {
|
|
29
|
+
import { locatedCommandSegments } from './command-position.js';
|
|
30
30
|
import { isDiagnosisRun } from './suite-overlap.js';
|
|
31
31
|
import { isPlaywrightRunCommand } from './utilities.js';
|
|
32
32
|
/** How many traces one read reports, newest first. */
|
|
@@ -380,31 +380,36 @@ function namesTraceMode(command) {
|
|
|
380
380
|
* shell parse can place.
|
|
381
381
|
*
|
|
382
382
|
* The flag has to land INSIDE the Playwright step, not at the end of the whole command: a run
|
|
383
|
-
* Flash sends almost every time ends `… 2>&1 | tail -60` or `… > log 2>&1; echo "exit=$?";
|
|
384
|
-
*
|
|
385
|
-
*
|
|
383
|
+
* Flash sends almost every time ends `… 2>&1 | tail -60` or `… > log 2>&1; echo "exit=$?"; grep -c
|
|
384
|
+
* "" log`, and a flag appended after those is an operand of `tail`/`grep` — GNU `tail` refuses it
|
|
385
|
+
* outright (`tail: option used in invalid context -- 6`) and Playwright never sees it
|
|
386
386
|
* (xr-molecule-flash-m102 +07:40 → +07:53).
|
|
387
387
|
*
|
|
388
|
-
* The step is found by the package's own shell parse ({@link
|
|
389
|
-
* runner classifier reads: it answers which
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* null, and the caller adds nothing rather than splicing into a guess.
|
|
388
|
+
* The step is found by the package's own shell parse ({@link locatedCommandSegments}), the one
|
|
389
|
+
* every runner classifier reads: it answers which STATEMENT runs `playwright test` in command
|
|
390
|
+
* position, and where that statement's program word sits in the command. The offset is the parse's
|
|
391
|
+
* own token position, never a search for the re-joined text — that text drops grouping braces and
|
|
392
|
+
* single-space-joins the words, so a command carrying a heredoc body or a `"quoted"` program found
|
|
393
|
+
* nothing at all and the flag was not added.
|
|
395
394
|
*
|
|
396
395
|
* @param command - The shell command.
|
|
397
396
|
* @returns The offset just after `test`, or null.
|
|
398
397
|
*/
|
|
399
398
|
function playwrightTestWordEnd(command) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
399
|
+
for (const located of locatedCommandSegments(command)) {
|
|
400
|
+
const segment = located.segment;
|
|
401
|
+
if (!segment || located.programOffset === null)
|
|
402
|
+
continue;
|
|
403
|
+
if (!/^playwright\s+test\b/.test(segment.text))
|
|
404
|
+
continue;
|
|
405
|
+
// `playwright` and `test` are the statement's own first two words; the flag follows `test`.
|
|
406
|
+
const words = new RegExp(/\S+/.source, 'g');
|
|
407
|
+
words.lastIndex = located.programOffset;
|
|
408
|
+
const program = words.exec(command);
|
|
409
|
+
if (!program)
|
|
410
|
+
continue;
|
|
411
|
+
const second = words.exec(command);
|
|
412
|
+
return second ? second.index + second[0].length : null;
|
|
408
413
|
}
|
|
409
414
|
return null;
|
|
410
415
|
}
|
package/dist/utilities.d.ts
CHANGED
|
@@ -222,18 +222,44 @@ export interface SuiteFilterStrip {
|
|
|
222
222
|
* @returns The rewritten command and the stages dropped.
|
|
223
223
|
*/
|
|
224
224
|
export declare function stripTrailingSuiteFilters(command: string): SuiteFilterStrip;
|
|
225
|
+
/** Whether a finished run's exit code belongs to a stage after the suite runner. */
|
|
226
|
+
export interface SuiteRunPipelinePlacement {
|
|
227
|
+
/**
|
|
228
|
+
* True when a pipe this tool must not drop still follows the suite runner: the code in the
|
|
229
|
+
* result is that stage's, not the run's.
|
|
230
|
+
*/
|
|
231
|
+
piped: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* True when the suite (or a stage of its pipeline) sits inside a nested `sh -c '<command>'`
|
|
234
|
+
* string, so the parse cannot place it in the command's own text. The caller then says so
|
|
235
|
+
* plainly instead of implying a stage it can point at — and keeps the command's own code when
|
|
236
|
+
* no pipe follows the runner, because on that command the pipeline's last stage IS the run.
|
|
237
|
+
*/
|
|
238
|
+
unplaced: boolean;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Where the suite runner's exit code comes from: the runner itself, or a stage piped after it.
|
|
242
|
+
*
|
|
243
|
+
* {@link suiteRunPipedAfterRunner}'s one question, with the honest third answer kept apart. The
|
|
244
|
+
* code a finished run reports is its LAST pipeline stage's, so a stage after the runner makes the
|
|
245
|
+
* code the stage's — but only when such a stage EXISTS. The platform used to report "piped" for
|
|
246
|
+
* any run whose statements it could not place in the text, which one heredoc body was enough to
|
|
247
|
+
* cause: on X1 xr-molecule-flash-m105 (+08:12) a config written in the same `exec_command` made
|
|
248
|
+
* the run come back with no exit code at all, and the note under it claimed a stage after the
|
|
249
|
+
* runner that was not there. The parse now places every statement (`locatedCommandSegments`), so
|
|
250
|
+
* "unplaced" is the rare, named case rather than a guess dressed as an answer.
|
|
251
|
+
*
|
|
252
|
+
* @param command - The shell command, after the streaming rewrite when one was applied.
|
|
253
|
+
* @returns Whether a stage follows the runner, and whether the run could be placed at all.
|
|
254
|
+
*/
|
|
255
|
+
export declare function suiteRunPipelinePlacement(command: string): SuiteRunPipelinePlacement;
|
|
225
256
|
/**
|
|
226
257
|
* Whether the command's last step runs a suite whose output still passes through a stage after
|
|
227
258
|
* the runner — one {@link stripTrailingSuiteFilters} could not prove is output-only (`| tee log`,
|
|
228
259
|
* `| xargs …`). The pipeline's exit status is that stage's, not the run's.
|
|
229
260
|
*
|
|
230
|
-
* A command whose stages cannot be placed in the text (irregular whitespace inside the run) is
|
|
231
|
-
* reported as piped too: whether a pipe follows the runner is then UNKNOWN, and an unknown exit
|
|
232
|
-
* code that gets trusted is a failing run read as a pass — the failure this rule exists to stop.
|
|
233
|
-
*
|
|
234
261
|
* @param command - The shell command, after the streaming rewrite when one was applied.
|
|
235
|
-
* @returns True when a pipe this tool must not drop still follows the suite runner
|
|
236
|
-
* command runs a suite the parse cannot place.
|
|
262
|
+
* @returns True when a pipe this tool must not drop still follows the suite runner.
|
|
237
263
|
*/
|
|
238
264
|
export declare function suiteRunPipedAfterRunner(command: string): boolean;
|
|
239
265
|
/**
|
|
@@ -916,9 +942,12 @@ export declare function failuresByCause(log: string, excluded?: readonly string[
|
|
|
916
942
|
* stopped early happened to reach.
|
|
917
943
|
*
|
|
918
944
|
* A test the request excludes is not one of the causes — it is dropped and named once, at the
|
|
919
|
-
* end, so a result never asks to fix a test it also says to ignore.
|
|
920
|
-
*
|
|
921
|
-
*
|
|
945
|
+
* end, so a result never asks to fix a test it also says to ignore. A test the runner itself
|
|
946
|
+
* declared `flaky` (it failed once and passed on its config retry) is not a cause either: it is
|
|
947
|
+
* named apart from them, once, as what the tally called it ({@link flakyTestsDeclared}). Where
|
|
948
|
+
* the caller knows how the run was started, the note also hands over the narrowed re-run of
|
|
949
|
+
* exactly those tests ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the
|
|
950
|
+
* whole suite.
|
|
922
951
|
*
|
|
923
952
|
* @param log - The finished run's output.
|
|
924
953
|
* @param excluded - The test titles the request carves out, if the caller knows them.
|
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;AAcH;;;;;;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;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWnE;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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { posix } from 'path';
|
|
8
|
-
import { commandSegments } from './command-position.js';
|
|
8
|
+
import { commandSegments, locatedCommandSegments } from './command-position.js';
|
|
9
9
|
import { commandRunsNoTests } from './no-test-run.js';
|
|
10
10
|
import { isDiagnosisRun, scopedRerunCommand, suiteCommandTarget, suiteRunInTheWay, withoutHeredocs, } from './suite-overlap.js';
|
|
11
11
|
/**
|
|
@@ -430,16 +430,23 @@ export function streamingBackgroundCommand(command) {
|
|
|
430
430
|
// joins stderr to it.
|
|
431
431
|
const finalPlaywrightRun = isPlaywrightRunCommand(lastStep) && !/>/.test(lastStep.replace(/\d?>&\d/g, ''));
|
|
432
432
|
/**
|
|
433
|
-
* Add a word to the suite runner's own arguments
|
|
434
|
-
*
|
|
435
|
-
*
|
|
433
|
+
* Add a word to the suite runner's own arguments; false when the runner could not be placed.
|
|
434
|
+
*
|
|
435
|
+
* A pipe this rewrite could not drop still follows the runner (`| tee run.log`), so appending at
|
|
436
|
+
* the end of the whole command would make the word an operand of the filter — m102's dangling
|
|
437
|
+
* `--trace` flag, in the same shape. A suite inside a nested `sh -c '<command>'` string has no
|
|
438
|
+
* offsets in this command (they are positions in THAT string), so the command is left exactly as
|
|
439
|
+
* written and the caller reports no rewrite: appending at the end would hand the flag to the
|
|
440
|
+
* shell, outside the quotes.
|
|
436
441
|
*/
|
|
437
442
|
const addToRun = (flag) => {
|
|
438
443
|
const pipeline = lastSuitePipeline(out);
|
|
439
444
|
const runner = pipeline ? pipeline.located[pipeline.at] : undefined;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
445
|
+
if (runner && runner.end === null)
|
|
446
|
+
return false;
|
|
447
|
+
const at = runner?.end ?? out.trimEnd().length;
|
|
448
|
+
out = `${out.slice(0, at)} ${flag}${out.slice(at)}`;
|
|
449
|
+
return true;
|
|
443
450
|
};
|
|
444
451
|
// A diagnosis or a re-run (capped, last-failed, grepped, or named files) walks each failure
|
|
445
452
|
// once: the suite's own `retries` doubled every failure of X1 flash-m71's first whole run,
|
|
@@ -493,8 +500,7 @@ export function streamingBackgroundCommand(command) {
|
|
|
493
500
|
'write every result into the log twice (X1 flash-m83).');
|
|
494
501
|
}
|
|
495
502
|
}
|
|
496
|
-
else if (finalPlaywrightRun) {
|
|
497
|
-
addToRun('--reporter=list');
|
|
503
|
+
else if (finalPlaywrightRun && addToRun('--reporter=list')) {
|
|
498
504
|
changes.push('Added `--reporter=list` so each test result is written to the log as it finishes ' +
|
|
499
505
|
'(the config reporter may print nothing until the end).');
|
|
500
506
|
}
|
|
@@ -513,8 +519,8 @@ export function streamingBackgroundCommand(command) {
|
|
|
513
519
|
'and you need not wait for it to end to start on them.');
|
|
514
520
|
}
|
|
515
521
|
if (finalPlaywrightRun && diagnosis && !/--retries\b/.test(lastStep)) {
|
|
516
|
-
addToRun('--retries=0')
|
|
517
|
-
|
|
522
|
+
if (addToRun('--retries=0'))
|
|
523
|
+
changes.push('Added `--retries=0`: a diagnosis or re-run walks each failure once; keep the suite’s retries for the proof run.');
|
|
518
524
|
}
|
|
519
525
|
return { command: out, changes };
|
|
520
526
|
}
|
|
@@ -604,32 +610,34 @@ const OUTPUT_ONLY_FILTERS = new Set([
|
|
|
604
610
|
/** A word that sends output to a file (`> f`, `>> f`, `2> f`); `2>&1` only joins stderr, so it is not one. */
|
|
605
611
|
const OUTPUT_REDIRECT = /^(?:\d*|&)>>?(?!&)/;
|
|
606
612
|
/**
|
|
607
|
-
* The shell parse's segments with their offsets in the command
|
|
613
|
+
* The shell parse's segments with their offsets in the command.
|
|
614
|
+
*
|
|
615
|
+
* {@link commandSegments} answers which program each stage runs; this answers where it is. The
|
|
616
|
+
* offsets come from the parse's own token positions ({@link locatedCommandSegments}), never from
|
|
617
|
+
* searching the command for the re-joined text: the words are joined back with single spaces for
|
|
618
|
+
* matching, so `command.indexOf(segment.text)` comes up empty on irregular spacing, on a grouping
|
|
619
|
+
* brace, or on anything a heredoc body shifts — and the caller then cut at a guess or refused to
|
|
620
|
+
* cut at all (xr-molecule-flash-m105 +08:12, where a heredoc body turned a finished run into
|
|
621
|
+
* "piped" for the rest of the session).
|
|
608
622
|
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* command exactly as written rather than cutting into a guess.
|
|
623
|
+
* A statement inside a nested `sh -c '<command>'` string is listed with null offsets: its
|
|
624
|
+
* positions are positions in THAT string, so nothing may be spliced at them — but its separator
|
|
625
|
+
* is known, which is all a pipeline question needs.
|
|
613
626
|
*
|
|
614
627
|
* @param command - The shell command.
|
|
615
|
-
* @returns The located segments, in order
|
|
628
|
+
* @returns The located segments, in order.
|
|
616
629
|
*/
|
|
617
630
|
function locatedSegments(command) {
|
|
618
631
|
const out = [];
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const at = command.indexOf(segment.text, cursor);
|
|
623
|
-
if (at < 0)
|
|
624
|
-
return null;
|
|
632
|
+
for (const located of locatedCommandSegments(command)) {
|
|
633
|
+
if (located.segment === null)
|
|
634
|
+
continue;
|
|
625
635
|
out.push({
|
|
626
|
-
segment,
|
|
627
|
-
start:
|
|
628
|
-
end:
|
|
629
|
-
between:
|
|
636
|
+
segment: located.segment,
|
|
637
|
+
start: located.start,
|
|
638
|
+
end: located.end,
|
|
639
|
+
between: located.separator,
|
|
630
640
|
});
|
|
631
|
-
cursor = at + segment.text.length;
|
|
632
|
-
previousEnd = cursor;
|
|
633
641
|
}
|
|
634
642
|
return out;
|
|
635
643
|
}
|
|
@@ -673,8 +681,6 @@ function lastSuitePipeline(command) {
|
|
|
673
681
|
if (commandRunsNoTests(command))
|
|
674
682
|
return null;
|
|
675
683
|
const located = locatedSegments(command);
|
|
676
|
-
if (located === null)
|
|
677
|
-
return null;
|
|
678
684
|
let at = -1;
|
|
679
685
|
for (let i = 0; i < located.length; i += 1) {
|
|
680
686
|
const segment = located[i]?.segment;
|
|
@@ -724,33 +730,59 @@ export function stripTrailingSuiteFilters(command) {
|
|
|
724
730
|
const stage = located[first];
|
|
725
731
|
if (!previous || !stage)
|
|
726
732
|
return { command, dropped: '' };
|
|
733
|
+
// A stage inside a nested `sh -c` string has no offsets in this command: nothing can be cut.
|
|
734
|
+
if (previous.end === null || stage.start === null)
|
|
735
|
+
return { command, dropped: '' };
|
|
727
736
|
const pipe = stage.between.indexOf('|');
|
|
728
737
|
if (pipe < 0)
|
|
729
738
|
return { command, dropped: '' };
|
|
730
739
|
const cut = previous.end + pipe;
|
|
731
740
|
return { command: command.slice(0, cut).trimEnd(), dropped: command.slice(cut).trim() };
|
|
732
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Where the suite runner's exit code comes from: the runner itself, or a stage piped after it.
|
|
744
|
+
*
|
|
745
|
+
* {@link suiteRunPipedAfterRunner}'s one question, with the honest third answer kept apart. The
|
|
746
|
+
* code a finished run reports is its LAST pipeline stage's, so a stage after the runner makes the
|
|
747
|
+
* code the stage's — but only when such a stage EXISTS. The platform used to report "piped" for
|
|
748
|
+
* any run whose statements it could not place in the text, which one heredoc body was enough to
|
|
749
|
+
* cause: on X1 xr-molecule-flash-m105 (+08:12) a config written in the same `exec_command` made
|
|
750
|
+
* the run come back with no exit code at all, and the note under it claimed a stage after the
|
|
751
|
+
* runner that was not there. The parse now places every statement (`locatedCommandSegments`), so
|
|
752
|
+
* "unplaced" is the rare, named case rather than a guess dressed as an answer.
|
|
753
|
+
*
|
|
754
|
+
* @param command - The shell command, after the streaming rewrite when one was applied.
|
|
755
|
+
* @returns Whether a stage follows the runner, and whether the run could be placed at all.
|
|
756
|
+
*/
|
|
757
|
+
export function suiteRunPipelinePlacement(command) {
|
|
758
|
+
const located = locatedSegments(command);
|
|
759
|
+
const pipeline = lastSuitePipeline(command);
|
|
760
|
+
if (pipeline !== null) {
|
|
761
|
+
const stages = pipeline.located.slice(pipeline.at);
|
|
762
|
+
// Every stage of the pipeline has to sit somewhere in THIS command to be read as a pipeline
|
|
763
|
+
// of it: a suite inside a `sh -c '<command>'` string has no offsets here.
|
|
764
|
+
const unplaced = stages.some((stage) => stage.start === null);
|
|
765
|
+
return { piped: pipeline.located.length > pipeline.at + 1, unplaced };
|
|
766
|
+
}
|
|
767
|
+
if (commandRunsNoTests(command))
|
|
768
|
+
return { piped: false, unplaced: false };
|
|
769
|
+
const runner = located.find((stage) => SUITE_RUN_COMMANDS.some((re) => re.test(stage.segment.text)));
|
|
770
|
+
// No suite at all, or a suite that is not the command's last step (a `; echo done` follows it):
|
|
771
|
+
// either way no stage after the RUNNER produces this command's code.
|
|
772
|
+
if (!runner)
|
|
773
|
+
return { piped: false, unplaced: false };
|
|
774
|
+
return { piped: false, unplaced: runner.start === null };
|
|
775
|
+
}
|
|
733
776
|
/**
|
|
734
777
|
* Whether the command's last step runs a suite whose output still passes through a stage after
|
|
735
778
|
* the runner — one {@link stripTrailingSuiteFilters} could not prove is output-only (`| tee log`,
|
|
736
779
|
* `| xargs …`). The pipeline's exit status is that stage's, not the run's.
|
|
737
780
|
*
|
|
738
|
-
* A command whose stages cannot be placed in the text (irregular whitespace inside the run) is
|
|
739
|
-
* reported as piped too: whether a pipe follows the runner is then UNKNOWN, and an unknown exit
|
|
740
|
-
* code that gets trusted is a failing run read as a pass — the failure this rule exists to stop.
|
|
741
|
-
*
|
|
742
781
|
* @param command - The shell command, after the streaming rewrite when one was applied.
|
|
743
|
-
* @returns True when a pipe this tool must not drop still follows the suite runner
|
|
744
|
-
* command runs a suite the parse cannot place.
|
|
782
|
+
* @returns True when a pipe this tool must not drop still follows the suite runner.
|
|
745
783
|
*/
|
|
746
784
|
export function suiteRunPipedAfterRunner(command) {
|
|
747
|
-
|
|
748
|
-
if (pipeline !== null)
|
|
749
|
-
return pipeline.located.length > pipeline.at + 1;
|
|
750
|
-
// No placed pipeline: either there is no suite at all, or its stages cannot be located.
|
|
751
|
-
if (commandRunsNoTests(command) || locatedSegments(command) !== null)
|
|
752
|
-
return false;
|
|
753
|
-
return commandSegments(command).some((segment) => SUITE_RUN_COMMANDS.some((re) => re.test(segment.text)));
|
|
785
|
+
return suiteRunPipelinePlacement(command).piped;
|
|
754
786
|
}
|
|
755
787
|
/**
|
|
756
788
|
* Start a background suite run at lower CPU and IO priority, so the sandbox it
|
|
@@ -2124,17 +2156,63 @@ export function failuresByCause(log, excluded = []) {
|
|
|
2124
2156
|
return groupFailures(log, excluded).causes;
|
|
2125
2157
|
}
|
|
2126
2158
|
/**
|
|
2127
|
-
*
|
|
2159
|
+
* A finished run's own count of tests that failed their first attempt and passed on the retry:
|
|
2160
|
+
* Playwright's ` 1 flaky`, printed in its closing tally.
|
|
2161
|
+
*/
|
|
2162
|
+
const FLAKY_TALLY_LINE = /^\s*[1-9]\d*\s+flaky\b/;
|
|
2163
|
+
/**
|
|
2164
|
+
* The tests a finished run's tally declares FLAKY (` 1 flaky` followed by their headings), keyed
|
|
2165
|
+
* the way a failure block is.
|
|
2166
|
+
*
|
|
2167
|
+
* Playwright prints a numbered failure block for the FIRST attempt of a test that then passes on
|
|
2168
|
+
* its retry, and also declares the test under `N flaky` — so a reader that only looks at the
|
|
2169
|
+
* blocks counts a flake as a cause to fix. xr-molecule-flash-m105's proof run against the deployed
|
|
2170
|
+
* URL reported ten `waitForURL` timing flakes as "causes" of a red run (+24:54), and the executor
|
|
2171
|
+
* spent its next minutes re-running them one by one until three passed untouched. The
|
|
2172
|
+
* declaration under the count is the runner's own answer, and its heading carries the same words
|
|
2173
|
+
* as the failure block's ({@link splitTitlePath} reads both the same way).
|
|
2174
|
+
*
|
|
2175
|
+
* @param text - The run's output, ANSI already stripped.
|
|
2176
|
+
* @returns Each flaky test, first seen first.
|
|
2177
|
+
*/
|
|
2178
|
+
function flakyTestsDeclared(text) {
|
|
2179
|
+
const tests = [];
|
|
2180
|
+
let underFlakyCount = false;
|
|
2181
|
+
for (const line of text.split('\n')) {
|
|
2182
|
+
if (FLAKY_TALLY_LINE.test(line)) {
|
|
2183
|
+
underFlakyCount = true;
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
if (!underFlakyCount)
|
|
2187
|
+
continue;
|
|
2188
|
+
// The declaration lines are the reporter's own headings (` [chromium] › a.spec.ts:5:1 › …`);
|
|
2189
|
+
// the first line that is not one ends them (the next tally line, or the end of the report).
|
|
2190
|
+
const heading = /^\s+\[[^\]\n]+\]\s*›\s*(.+)$/.exec(line)?.[1];
|
|
2191
|
+
if (heading === undefined) {
|
|
2192
|
+
underFlakyCount = false;
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
const { where, title } = splitTitlePath(heading.replace(/[\s─]+$/, ''));
|
|
2196
|
+
tests.push({ where, title, seconds: null });
|
|
2197
|
+
}
|
|
2198
|
+
return distinctTests(tests);
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* The causes a run's log names, the failures the request excludes, and the failures the runner
|
|
2202
|
+
* itself counted as flaky out of them.
|
|
2128
2203
|
*
|
|
2129
2204
|
* One grouping pass, because a note that lists the causes must know what it left out to say so
|
|
2130
2205
|
* ({@link failureCausesNote}).
|
|
2131
2206
|
*
|
|
2132
2207
|
* @param log - The log of a finished or running run.
|
|
2133
2208
|
* @param excluded - The test titles the request carves out.
|
|
2134
|
-
* @returns The causes to fix,
|
|
2209
|
+
* @returns The causes to fix, the distinct tests dropped as excluded, and the distinct tests the
|
|
2210
|
+
* runner counted as flaky (a retried pass is not a cause to fix).
|
|
2135
2211
|
*/
|
|
2136
2212
|
function groupFailures(log, excluded) {
|
|
2137
2213
|
const text = log.replace(ANSI_SGR, '');
|
|
2214
|
+
const flakyTests = flakyTestsDeclared(text);
|
|
2215
|
+
const flaky = new Set(flakyTests.map(testKey));
|
|
2138
2216
|
const byMessage = new Map();
|
|
2139
2217
|
const named = [];
|
|
2140
2218
|
for (const block of failureBlocks(text)) {
|
|
@@ -2144,6 +2222,10 @@ function groupFailures(log, excluded) {
|
|
|
2144
2222
|
const m = FAILURE_BLOCK_HEADER.exec(block.split('\n')[0] ?? '');
|
|
2145
2223
|
const { where, title } = splitTitlePath(m?.[1] ?? '');
|
|
2146
2224
|
const test = { where, title, seconds: null };
|
|
2225
|
+
// The runner declared this test flaky: the block above is its first attempt, which it
|
|
2226
|
+
// passed on the retry, so it is neither a cause nor a failure to re-run.
|
|
2227
|
+
if (flaky.has(testKey(test)))
|
|
2228
|
+
continue;
|
|
2147
2229
|
named.push(test);
|
|
2148
2230
|
if (matchingExcludedTitles(title, excluded).length > 0)
|
|
2149
2231
|
continue;
|
|
@@ -2173,6 +2255,9 @@ function groupFailures(log, excluded) {
|
|
|
2173
2255
|
const claimed = new Set(causes.flatMap((c) => c.tests.map(testKey)));
|
|
2174
2256
|
const unclaimed = distinctTests(listed).filter((t) => t.seconds !== null &&
|
|
2175
2257
|
!claimed.has(testKey(t)) &&
|
|
2258
|
+
// A flaky test's `✘` row is its first attempt; its `✓` retry follows it in the same log,
|
|
2259
|
+
// and the runner's tally already said the test passed.
|
|
2260
|
+
!flaky.has(testKey(t)) &&
|
|
2176
2261
|
matchingExcludedTitles(t.title, excluded).length === 0);
|
|
2177
2262
|
const byDuration = [];
|
|
2178
2263
|
for (const t of unclaimed) {
|
|
@@ -2186,7 +2271,11 @@ function groupFailures(log, excluded) {
|
|
|
2186
2271
|
for (const g of byDuration)
|
|
2187
2272
|
causes.push({ message: null, seconds: g.seconds, tests: g.tests, evidence: [] });
|
|
2188
2273
|
const ignored = distinctTests(named).filter((t) => matchingExcludedTitles(t.title, excluded).length > 0);
|
|
2189
|
-
return {
|
|
2274
|
+
return {
|
|
2275
|
+
causes: causes.sort((a, b) => b.tests.length - a.tests.length),
|
|
2276
|
+
ignored,
|
|
2277
|
+
flaky: flakyTests,
|
|
2278
|
+
};
|
|
2190
2279
|
}
|
|
2191
2280
|
/**
|
|
2192
2281
|
* The one-line-per-cause summary of a finished run's failures, or null when the run named fewer
|
|
@@ -2197,9 +2286,12 @@ function groupFailures(log, excluded) {
|
|
|
2197
2286
|
* stopped early happened to reach.
|
|
2198
2287
|
*
|
|
2199
2288
|
* A test the request excludes is not one of the causes — it is dropped and named once, at the
|
|
2200
|
-
* end, so a result never asks to fix a test it also says to ignore.
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2289
|
+
* end, so a result never asks to fix a test it also says to ignore. A test the runner itself
|
|
2290
|
+
* declared `flaky` (it failed once and passed on its config retry) is not a cause either: it is
|
|
2291
|
+
* named apart from them, once, as what the tally called it ({@link flakyTestsDeclared}). Where
|
|
2292
|
+
* the caller knows how the run was started, the note also hands over the narrowed re-run of
|
|
2293
|
+
* exactly those tests ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the
|
|
2294
|
+
* whole suite.
|
|
2203
2295
|
*
|
|
2204
2296
|
* @param log - The finished run's output.
|
|
2205
2297
|
* @param excluded - The test titles the request carves out, if the caller knows them.
|
|
@@ -2209,7 +2301,7 @@ function groupFailures(log, excluded) {
|
|
|
2209
2301
|
* @returns The note, or null.
|
|
2210
2302
|
*/
|
|
2211
2303
|
export function failureCausesNote(log, excluded = [], run) {
|
|
2212
|
-
const { causes, ignored } = groupFailures(log, excluded);
|
|
2304
|
+
const { causes, ignored, flaky } = groupFailures(log, excluded);
|
|
2213
2305
|
const total = causes.reduce((n, c) => n + c.tests.length, 0);
|
|
2214
2306
|
if (total < 2)
|
|
2215
2307
|
return null;
|
|
@@ -2221,9 +2313,16 @@ export function failureCausesNote(log, excluded = [], run) {
|
|
|
2221
2313
|
const locations = [...new Set(causes.flatMap((c) => c.tests.map((t) => t.where)).filter(Boolean))];
|
|
2222
2314
|
const rerun = run ? rerunOfRunningSuite(run, locations, excluded) : null;
|
|
2223
2315
|
const ignoredTitles = [...new Set(ignored.map((t) => t.title || t.where).filter(Boolean))];
|
|
2316
|
+
const flakyTitles = [...new Set(flaky.map((t) => t.where || t.title).filter(Boolean))];
|
|
2224
2317
|
const header = `${total} failure${total === 1 ? '' : 's'} in this run, grouped by cause — fix every cause ` +
|
|
2225
2318
|
`below before the next deploy:\n`;
|
|
2319
|
+
// The flaky sentence sits before the ignored one: the ignored sentence is the LAST thing the
|
|
2320
|
+
// note says, and callers assert on that ending.
|
|
2226
2321
|
const tail = (rerun ? ` Re-run just these once they are fixed: \`${rerun}\`` : '') +
|
|
2322
|
+
(flakyTitles.length
|
|
2323
|
+
? ' Flaky (failed once, passed on the config retry — the runner counts these as flaky, not ' +
|
|
2324
|
+
`as failures): ${flakyTitles.join(', ')}.`
|
|
2325
|
+
: '') +
|
|
2227
2326
|
(ignoredTitles.length ? ` Ignored (excluded by the request): ${ignoredTitles.join(', ')}.` : '');
|
|
2228
2327
|
// This list is the part of the result that SURVIVES the platform's tool-output cut: the stdout
|
|
2229
2328
|
// above it is cut in the MIDDLE at {@link TOOL_RESULT_CHARS}, which is why the recap must fit
|