@molecule/api-ai-tools 1.9.16 → 1.9.19
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 +336 -27
- package/dist/command-position.d.ts +3 -1
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +95 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/output-filter.d.ts +18 -7
- package/dist/output-filter.d.ts.map +1 -1
- package/dist/output-filter.js +46 -18
- package/dist/server-command.d.ts +73 -0
- package/dist/server-command.d.ts.map +1 -0
- package/dist/server-command.js +180 -0
- package/dist/suite-overlap.d.ts +24 -1
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +154 -36
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +136 -17
- package/dist/trace-artifacts.d.ts +90 -5
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +513 -27
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities.d.ts +75 -9
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +237 -51
- package/package.json +1 -1
package/dist/utilities.js
CHANGED
|
@@ -350,6 +350,25 @@ const PRINTING_REPORTERS = new Set(['list', 'line', 'dot', 'basic', 'spec']);
|
|
|
350
350
|
* edited `app/src` while the run was still walking — which voids the run.
|
|
351
351
|
*/
|
|
352
352
|
export const PLAYWRIGHT_FAILURES_INLINE_ENV = 'PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE';
|
|
353
|
+
/**
|
|
354
|
+
* Whether a command step only reports or discards the status of the steps before it: `echo EXIT
|
|
355
|
+
* $?`, `echo $?`, `printf …`, `true`, `exit 0`.
|
|
356
|
+
*
|
|
357
|
+
* The shape models and the platform's own re-run notes send routinely ends on one of these —
|
|
358
|
+
* `npx playwright test … > /tmp/pw.log 2>&1; echo EXIT $?` — and reading the split's last part as
|
|
359
|
+
* the run withheld the reporter, the inline-failure switch and `--retries=0` from every one of
|
|
360
|
+
* them: the log then carried `✘` rows and nothing else while the wait note promised each failure's
|
|
361
|
+
* error under them (xr-molecule-flash-m173 +17:07, +39:14). Whitespace and a group's closing
|
|
362
|
+
* punctuation are not part of the step; {@link streamingBackgroundCommand} strips those before
|
|
363
|
+
* asking, and strips them here too for the split it is given.
|
|
364
|
+
*
|
|
365
|
+
* @param step - One statement of the command, as the `&&`/`||`/`;` split left it.
|
|
366
|
+
* @returns True when the step runs nothing but a status report.
|
|
367
|
+
*/
|
|
368
|
+
function isStatusOnlyStep(step) {
|
|
369
|
+
const text = step.replace(/[\s)}]+$/, '').trim();
|
|
370
|
+
return /^(?:echo|printf)\b/.test(text) || /^true$/.test(text) || /^exit(?:\s+\S+)?$/.test(text);
|
|
371
|
+
}
|
|
353
372
|
/**
|
|
354
373
|
* Rewrite a command about to run in the background so its log grows while it
|
|
355
374
|
* runs. Two shapes left the log empty until exit, and an empty log is what
|
|
@@ -384,24 +403,32 @@ export const PLAYWRIGHT_FAILURES_INLINE_ENV = 'PLAYWRIGHT_LIST_PRINT_FAILURES_IN
|
|
|
384
403
|
* is exactly when the executor reads the log, and without it a running log carries `✘` rows and
|
|
385
404
|
* nothing else (xr-molecule-flash-m99 spent ~90 s on greps of one, then fixed what it guessed).
|
|
386
405
|
*
|
|
387
|
-
* Only the
|
|
388
|
-
* something: `npx playwright test --list` takes two seconds and writes nothing per test, so
|
|
389
|
-
* neither reporter nor `--retries=0` is added to it ({@link isPlaywrightRunCommand}).
|
|
406
|
+
* Only the step that runs the suite is touched, and only its reporter, and only when that step
|
|
407
|
+
* runs something: `npx playwright test --list` takes two seconds and writes nothing per test, so
|
|
408
|
+
* neither reporter nor `--retries=0` is added to it ({@link isPlaywrightRunCommand}). A trailing
|
|
409
|
+
* status step (`; echo EXIT $?`, `&& true`) is not the run and is skipped when the run step is
|
|
410
|
+
* found ({@link isStatusOnlyStep}); a run whose output is redirected to a file is still readable
|
|
411
|
+
* through that file, which the launch records as the task's own output.
|
|
390
412
|
*
|
|
391
413
|
* A command that starts the suite MORE THAN ONCE is not launched at all: every run it carries is
|
|
392
414
|
* a whole walk of the same suite against the same app, and only the last step is rewritten, so the
|
|
393
415
|
* earlier walks run unmeasured for nothing. X1 xr-molecule-flash-m157 (+14:02) chained two whole
|
|
394
416
|
* walks of one 138-test suite with `;` — 4.5 minutes each — and the second measured nothing the
|
|
395
|
-
* first had not. The platform
|
|
417
|
+
* first had not. The platform refuses a second CONCURRENT run against one app and database by the
|
|
418
|
+
* ONE rule ({@link suiteRunInTheWay}) — for every pair, whatever uid a run mints — and this is the
|
|
396
419
|
* same hazard inside one command ({@link suiteRunStatements}). `error` then carries the refusal,
|
|
397
420
|
* naming each suite step and the ONE command to send instead (the last suite step, rewritten as
|
|
398
421
|
* this function rewrites a run), and nothing is launched.
|
|
399
422
|
*
|
|
400
423
|
* @param command - The command as the executor sent it.
|
|
424
|
+
* @param context - What the caller knows beyond the command: the tests the person's request
|
|
425
|
+
* excludes by name ({@link DiagnosisRunContext}). A `--grep-invert` of exactly those is the
|
|
426
|
+
* request's own scope, not a diagnosis, so it keeps the suite's retries on a deployed proof
|
|
427
|
+
* (xr-molecule-flash-m170 +33:16).
|
|
401
428
|
* @returns The command to run and one sentence per change made (empty when none), or `error` when
|
|
402
429
|
* the command starts the suite more than once.
|
|
403
430
|
*/
|
|
404
|
-
export function streamingBackgroundCommand(command) {
|
|
431
|
+
export function streamingBackgroundCommand(command, context) {
|
|
405
432
|
// A command that runs no test is not a run to keep a growing log for: nothing about it is
|
|
406
433
|
// rewritten (flash-m79's `--list` was read as a whole-suite run end to end, and got a ~4 KB
|
|
407
434
|
// refusal to send itself again with `--max-failures=15`).
|
|
@@ -442,10 +469,15 @@ export function streamingBackgroundCommand(command) {
|
|
|
442
469
|
}
|
|
443
470
|
// The last step that RUNS something. A trailing group closer is punctuation, not a step: a
|
|
444
471
|
// brace group writes it after a `;` (`{ …; }`), so reading the split's last part literally
|
|
445
|
-
// made `}` the step and withheld every rewrite from a run inside a group.
|
|
472
|
+
// made `}` the step and withheld every rewrite from a run inside a group. A trailing status
|
|
473
|
+
// step is not the run either: `… > /tmp/pw.log 2>&1; echo EXIT $?` ends on the echo, and
|
|
474
|
+
// treating THAT as the run withheld the reporter, the inline-failure switch and `--retries=0`
|
|
475
|
+
// from the exact shape models and the platform's re-run notes send (xr-molecule-flash-m173
|
|
476
|
+
// +17:07, +39:14).
|
|
446
477
|
const parts = out.split(/&&|\|\||;/);
|
|
447
478
|
let stepIndex = parts.length - 1;
|
|
448
|
-
while (stepIndex > 0 &&
|
|
479
|
+
while (stepIndex > 0 &&
|
|
480
|
+
(/^[\s)}]*$/.test(parts[stepIndex] ?? '') || isStatusOnlyStep(parts[stepIndex] ?? '')))
|
|
449
481
|
stepIndex -= 1;
|
|
450
482
|
const rawLastStep = parts[stepIndex] ?? '';
|
|
451
483
|
// The step's own text, with the punctuation that closes the group it sits in taken off — the
|
|
@@ -455,34 +487,28 @@ export function streamingBackgroundCommand(command) {
|
|
|
455
487
|
// A command that starts the suite more than once is not launched: each statement is a whole walk
|
|
456
488
|
// of the same suite against the same app, only the last of them is rewritten, and the earlier
|
|
457
489
|
// ones measure nothing the first did. X1 xr-molecule-flash-m157 (+14:02) chained two whole walks
|
|
458
|
-
// of one 138-test suite with `;` (4.5 minutes each); the platform
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.reverse()
|
|
466
|
-
.map((p) => p.trim())
|
|
467
|
-
.find((p) => isSuiteRunCommand(p));
|
|
468
|
-
const single = lastSuitePart && suiteRunStatements(lastSuitePart).length === 1
|
|
469
|
-
? streamingBackgroundCommand(lastSuitePart).command
|
|
470
|
-
: (lastSuitePart ?? '');
|
|
471
|
-
return {
|
|
472
|
-
command: out,
|
|
473
|
-
changes: [],
|
|
474
|
-
error: `This command runs the suite ${suiteSteps.length === 2 ? 'twice' : `${suiteSteps.length} times`} ` +
|
|
475
|
-
`(${suiteSteps.map((s, i) => `step ${i + 1}: \`${s}\``).join(', ')}). One walk is the ` +
|
|
476
|
-
`measurement; run exactly one: \`${single}\``,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
490
|
+
// of one 138-test suite with `;` (4.5 minutes each); the platform refuses a second CONCURRENT
|
|
491
|
+
// run against one app and database by the ONE rule ({@link suiteRunInTheWay}) — for every pair,
|
|
492
|
+
// whatever uid a run mints — and this is the same hazard inside one command. The reading is
|
|
493
|
+
// {@link repeatedSuiteRunError}'s, so a foreground `exec_command` refuses the same commands.
|
|
494
|
+
const repeated = repeatedSuiteRunError(out);
|
|
495
|
+
if (repeated)
|
|
496
|
+
return { command: out, changes: [], error: repeated };
|
|
479
497
|
// The last step runs Playwright — whether or not anything is piped from it. Keying this on
|
|
480
498
|
// "nothing is piped out of the last step" withheld the reporter, the inline-failure switch and
|
|
481
499
|
// `--retries=0` from exactly the runs whose output was filtered (xr-molecule-flash-m101
|
|
482
500
|
// +25:54); what the runner writes into its log does not depend on what reads the pipe. A
|
|
483
|
-
// REDIRECT
|
|
484
|
-
//
|
|
485
|
-
|
|
501
|
+
// REDIRECT used to withhold them too (`> out.txt`), but a redirect to a FILE is readable: the
|
|
502
|
+
// launch records that file as the task's own output (`outputFileByTask`, resolved from this
|
|
503
|
+
// same reader) and every wait reads it back. `> /tmp/pw-full.log 2>&1; echo EXIT $?` is the
|
|
504
|
+
// shape models and the platform's re-run notes send, so a run whose own redirect target is the
|
|
505
|
+
// one the launch will read is a readable run (xr-molecule-flash-m173 +17:07, +39:14). A target
|
|
506
|
+
// that is not a file (`/dev/null`, `2>&1`) still leaves the output unreadable.
|
|
507
|
+
const runRedirect = outputRedirectTarget(lastStep);
|
|
508
|
+
const finalPlaywrightRun = isPlaywrightRunCommand(lastStep) &&
|
|
509
|
+
(runRedirect === null
|
|
510
|
+
? !/>/.test(lastStep.replace(/\d?>&\d/g, ''))
|
|
511
|
+
: runRedirect === outputRedirectTarget(out));
|
|
486
512
|
/**
|
|
487
513
|
* Add a word to the suite runner's own arguments; false when the runner could not be placed.
|
|
488
514
|
*
|
|
@@ -494,14 +520,17 @@ export function streamingBackgroundCommand(command) {
|
|
|
494
520
|
* platform's runner shape (xr-molecule-flash-m129 +34:29); appending after a pipe hands the
|
|
495
521
|
* word to the filter (m102's dangling `--trace` flag, in the same shape).
|
|
496
522
|
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
523
|
+
* The suite's own segment is found wherever it sits ({@link lastSuiteRun}), because a trailing
|
|
524
|
+
* status step is not another command's output: in `… > /tmp/pw.log 2>&1; echo EXIT $?` the run
|
|
525
|
+
* is what the launcher measures, and the echo only repeats its status
|
|
526
|
+
* ({@link isStatusOnlyStep}). A suite the parse could not place — one inside a nested
|
|
527
|
+
* `sh -c '<command>'` string, whose offsets are positions in THAT string — is left exactly as
|
|
528
|
+
* written and the caller reports no rewrite. There is nowhere in this command the word
|
|
529
|
+
* belongs, and guessing at the text is what produced both failures above.
|
|
501
530
|
*/
|
|
502
531
|
const addToRun = (flag) => {
|
|
503
|
-
const
|
|
504
|
-
const runner =
|
|
532
|
+
const run = lastSuiteRun(out);
|
|
533
|
+
const runner = run ? run.located[run.at] : undefined;
|
|
505
534
|
if (!runner)
|
|
506
535
|
return false;
|
|
507
536
|
const at = statementArgumentEnd(out, runner.start, runner.end);
|
|
@@ -522,7 +551,7 @@ export function streamingBackgroundCommand(command) {
|
|
|
522
551
|
// {@link isDiagnosisRun}'s answer and nobody else's: the platform's notes ask the same
|
|
523
552
|
// function, so a run cannot be a diagnosis to one of them and the whole suite to the other.
|
|
524
553
|
// Which targets are local is {@link suiteCommandTarget}'s answer.
|
|
525
|
-
const diagnosis = isDiagnosisRun(lastStep);
|
|
554
|
+
const diagnosis = isDiagnosisRun(lastStep, context);
|
|
526
555
|
const localTarget = suiteCommandTarget(lastStep) === 'local';
|
|
527
556
|
// The last step's reporters: one printer, and it is `list`. Every branch below only ever moves
|
|
528
557
|
// the command TO that shape — a reporter set is never left with two printers in it.
|
|
@@ -590,8 +619,14 @@ export function streamingBackgroundCommand(command) {
|
|
|
590
619
|
'and you need not wait for it to end to start on them.');
|
|
591
620
|
}
|
|
592
621
|
if (finalPlaywrightRun && (diagnosis || localTarget) && !/--retries\b/.test(lastStep)) {
|
|
622
|
+
// The reason named is the one that fired: a narrowed diagnosis against a DEPLOYED URL is not a
|
|
623
|
+
// local run, and calling it one (xr-molecule-flash-m170's note did) reads as the platform having
|
|
624
|
+
// misread the target.
|
|
625
|
+
const why = diagnosis
|
|
626
|
+
? 'a diagnosis or re-run walks each failure once; the suite’s retries apply on the deployed proof run'
|
|
627
|
+
: 'a local run walks each failure once; the suite’s retries apply on the deployed proof run';
|
|
593
628
|
if (addToRun('--retries=0'))
|
|
594
|
-
changes.push(
|
|
629
|
+
changes.push(`Added \`--retries=0\`: ${why}.`);
|
|
595
630
|
}
|
|
596
631
|
return { command: out, changes };
|
|
597
632
|
}
|
|
@@ -613,6 +648,15 @@ const SUITE_RUN_COMMANDS = [
|
|
|
613
648
|
/^vitest\b/,
|
|
614
649
|
// `hurl --test …` or `hurl … x.hurl`.
|
|
615
650
|
/^hurl\s+[^|\n]*(?:--test|\.hurl)/,
|
|
651
|
+
// The suite's own runner SCRIPT, whoever wrote it: the supplied trees ship
|
|
652
|
+
// `run-api-tests-hurl.sh` and `run-hurl-tests.sh`, and the command-position parser puts the
|
|
653
|
+
// script's own NAME where the program goes (`bash fixtures/…/run-api-tests-hurl.sh` →
|
|
654
|
+
// `run-api-tests-hurl.sh`). xr-molecule-flash-m170 (+06:02) chained two invocations of one such
|
|
655
|
+
// script in the FOREGROUND, which no launch site counted: the first walk's tally was the only
|
|
656
|
+
// evidence the note had, and it reported a passing run as 13 failures. A script named for the
|
|
657
|
+
// suite is a whole walk of it; `head -30 run-api-tests-hurl.sh` is not in command position and
|
|
658
|
+
// stays a read ({@link commandSegments}).
|
|
659
|
+
/^run-(?:api-)?(?:hurl-)?tests?[\w-]*\.sh\b/,
|
|
616
660
|
];
|
|
617
661
|
/**
|
|
618
662
|
* Every statement of a command that starts a whole test suite, in command order.
|
|
@@ -649,6 +693,122 @@ export function suiteRunStatements(command) {
|
|
|
649
693
|
export function isSuiteRunCommand(command) {
|
|
650
694
|
return suiteRunStatements(command).length > 0;
|
|
651
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* The refusal for a command that starts the suite MORE THAN ONCE — the ONE reading every launch
|
|
698
|
+
* site asks, foreground and background alike.
|
|
699
|
+
*
|
|
700
|
+
* Each statement is a whole walk of the same suite against the same app, and only the last of them
|
|
701
|
+
* is rewritten, so the earlier ones measure nothing the first did. X1 xr-molecule-flash-m157
|
|
702
|
+
* (+14:02) chained two whole walks of one 138-test suite with `;` (4.5 minutes each) and the
|
|
703
|
+
* background launcher refused it; X1 xr-molecule-flash-m170 (+06:02) chained two invocations of the
|
|
704
|
+
* suite's own runner script in the foreground, where nothing counted them, and the note reported
|
|
705
|
+
* the passing second walk as the first walk's 13 failures.
|
|
706
|
+
*
|
|
707
|
+
* The one run handed back is the LAST suite statement, rewritten exactly as
|
|
708
|
+
* {@link streamingBackgroundCommand} rewrites a run (a filter or `| tail` after the runner is
|
|
709
|
+
* dropped, and a Playwright run gets the reporter that prints failures). Its own count is one —
|
|
710
|
+
* guarded, so this cannot recurse.
|
|
711
|
+
*
|
|
712
|
+
* @param command - The shell command as the executor sent it.
|
|
713
|
+
* @returns The refusal sentence, or null when the command starts the suite zero or one time.
|
|
714
|
+
*/
|
|
715
|
+
export function repeatedSuiteRunError(command) {
|
|
716
|
+
if (commandRunsNoTests(command))
|
|
717
|
+
return null;
|
|
718
|
+
const suiteSteps = suiteRunStatements(command);
|
|
719
|
+
if (suiteSteps.length <= 1)
|
|
720
|
+
return null;
|
|
721
|
+
const lastSuitePart = command
|
|
722
|
+
.split(/&&|\|\||;/)
|
|
723
|
+
.reverse()
|
|
724
|
+
.map((p) => p.trim())
|
|
725
|
+
.find((p) => isSuiteRunCommand(p));
|
|
726
|
+
const single = lastSuitePart && suiteRunStatements(lastSuitePart).length === 1
|
|
727
|
+
? streamingBackgroundCommand(lastSuitePart).command
|
|
728
|
+
: (lastSuitePart ?? '');
|
|
729
|
+
return (`This command runs the suite ${suiteSteps.length === 2 ? 'twice' : `${suiteSteps.length} times`} ` +
|
|
730
|
+
`(${suiteSteps.map((s, i) => `step ${i + 1}: \`${s}\``).join(', ')}). One walk is the ` +
|
|
731
|
+
`measurement; run exactly one: \`${single}\``);
|
|
732
|
+
}
|
|
733
|
+
/** A flag with its value, as {@link suiteRunBudgetCommand} writes it: `--timeout=15000`. */
|
|
734
|
+
const BUDGET_FLAG = /^(--[A-Za-z][\w-]*)=(\S+)$/;
|
|
735
|
+
/**
|
|
736
|
+
* The statement the suite runner occupies, and where its own arguments end.
|
|
737
|
+
*
|
|
738
|
+
* The LAST suite statement, wherever it sits: unlike a reporter or `--retries=0` (which only make
|
|
739
|
+
* sense on the run a launcher measures), the suite's budget belongs on the run itself even when
|
|
740
|
+
* another command follows it (`… ; npm run build`).
|
|
741
|
+
*/
|
|
742
|
+
function suiteRunnerArguments(command) {
|
|
743
|
+
const pipeline = lastSuiteRun(command);
|
|
744
|
+
const runner = pipeline ? pipeline.located[pipeline.at] : undefined;
|
|
745
|
+
if (!runner || runner.start === null || runner.end === null)
|
|
746
|
+
return null;
|
|
747
|
+
const start = runner.start;
|
|
748
|
+
const end = runner.end;
|
|
749
|
+
const at = statementArgumentEnd(command, start, end);
|
|
750
|
+
if (at === null)
|
|
751
|
+
return null;
|
|
752
|
+
return { start, end, at };
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* A command with the supplied suite's own settings written back onto its runner as Playwright
|
|
756
|
+
* flags, and the flags that could not be placed.
|
|
757
|
+
*
|
|
758
|
+
* The grader walks the suite under its OWN budget, so a run whose runner config sets a different
|
|
759
|
+
* timeout or worker count measures a run the grader never sees (xr-molecule-flash-m169: a 60 s
|
|
760
|
+
* wrapper config produced a green local proof the official 15 s grader then failed). Playwright
|
|
761
|
+
* lets a command-line flag win over every config file, so the suite's values are put on the
|
|
762
|
+
* runner's own arguments — written where {@link statementArgumentEnd} places them, never after a
|
|
763
|
+
* pipe, redirect or group closer. Only the command's LAST suite step is touched: that is the run
|
|
764
|
+
* every launch site measures.
|
|
765
|
+
*
|
|
766
|
+
* A flag the command already carries is REWRITTEN in place (last occurrence wins, as it does for
|
|
767
|
+
* Playwright); a flag it does not carry is inserted at the end of the runner's arguments. A
|
|
768
|
+
* command whose runner cannot be placed is returned exactly as written, with every flag reported
|
|
769
|
+
* as unplaced, so the caller states the fact instead of claiming a pin it did not make.
|
|
770
|
+
*
|
|
771
|
+
* @param command - The shell command about to run.
|
|
772
|
+
* @param flags - The flags to put on it, each as `--name=value`.
|
|
773
|
+
* @returns The command to run, the flags placed on it, and the ones it could not carry.
|
|
774
|
+
*/
|
|
775
|
+
export function suiteRunBudgetCommand(command, flags) {
|
|
776
|
+
const placed = [];
|
|
777
|
+
const unplaced = [];
|
|
778
|
+
let out = command;
|
|
779
|
+
for (const flag of flags) {
|
|
780
|
+
const parts = BUDGET_FLAG.exec(flag);
|
|
781
|
+
if (parts === null) {
|
|
782
|
+
unplaced.push(flag);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const name = parts[1] ?? '';
|
|
786
|
+
const value = parts[2] ?? '';
|
|
787
|
+
const run = suiteRunnerArguments(out);
|
|
788
|
+
if (run === null) {
|
|
789
|
+
unplaced.push(flag);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
// `--name=value` and `--name value` alike; the last occurrence is the one Playwright uses.
|
|
793
|
+
// The value is digits (milliseconds, worker counts), so a percentage or an expression is
|
|
794
|
+
// left alone rather than half-rewritten.
|
|
795
|
+
const written = new RegExp(`(?:^|[\\s;&|(])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:=|\\s+)(['"]?)(\\d[\\d_]*)(?![\\w%.])`, 'g');
|
|
796
|
+
const text = out.slice(run.start, run.at);
|
|
797
|
+
let last = null;
|
|
798
|
+
for (let m = written.exec(text); m !== null; m = written.exec(text))
|
|
799
|
+
last = m;
|
|
800
|
+
if (last !== null) {
|
|
801
|
+
const digits = last[2] ?? '';
|
|
802
|
+
const at = run.start + last.index + last[0].length - digits.length;
|
|
803
|
+
out = `${out.slice(0, at)}${value}${out.slice(at + digits.length)}`;
|
|
804
|
+
placed.push(`${name}=${value}`);
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
out = `${out.slice(0, run.at)} ${flag}${out.slice(run.at)}`;
|
|
808
|
+
placed.push(flag);
|
|
809
|
+
}
|
|
810
|
+
return { command: out, placed, unplaced };
|
|
811
|
+
}
|
|
652
812
|
/**
|
|
653
813
|
* The command with the `timeout <N>` wrapper lifted off the statement that runs the suite, and the
|
|
654
814
|
* bound that was lifted.
|
|
@@ -2224,10 +2384,21 @@ function failureBlockTests(text) {
|
|
|
2224
2384
|
export const UNIVERSAL_CAUSE_FILES = 3;
|
|
2225
2385
|
/**
|
|
2226
2386
|
* How many tests a run must have walked — with nothing passing since its first failure — before
|
|
2227
|
-
* "the page renders nothing" can be its one cause.
|
|
2228
|
-
*
|
|
2229
|
-
|
|
2230
|
-
|
|
2387
|
+
* "the page renders nothing" can be its one cause.
|
|
2388
|
+
*
|
|
2389
|
+
* THREE, and the number is the cost of the alternative. The old floor of 15 was chosen when the
|
|
2390
|
+
* reading was only offered at a completed run; once it STOPS the walk, every test waited for is
|
|
2391
|
+
* a test whose result is thrown away, and a blank page costs its per-test budget each time
|
|
2392
|
+
* (m177: 18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
|
|
2393
|
+
* `pageerror`). Three is the smallest count that is not one page's own bug: the cause additionally
|
|
2394
|
+
* needs three DISTINCT spec files (`UNIVERSAL_CAUSE_FILES`, or the files a narrowed run selected),
|
|
2395
|
+
* each spec waiting for its OWN first element, and no test passing after the first failure. A page
|
|
2396
|
+
* that renders misses elements, but three unrelated specs in three files failing at three
|
|
2397
|
+
* different first locators with nothing passing in between is the page, not three bugs. At the
|
|
2398
|
+
* suite's own 5 s action timeout that verdict lands ~20 s into the walk, so the early stop costs
|
|
2399
|
+
* about one test's worth of time instead of fifteen.
|
|
2400
|
+
*/
|
|
2401
|
+
export const EMPTY_PAGE_CAUSE_TESTS = 3;
|
|
2231
2402
|
/**
|
|
2232
2403
|
* A failure body whose whole complaint is a wait for an element the page never rendered:
|
|
2233
2404
|
* Playwright's `waiting for locator(…)` call log, its `element(s) not found` line, or the
|
|
@@ -2251,22 +2422,32 @@ function pageErrorInLog(text) {
|
|
|
2251
2422
|
return m?.[1]?.trim() || null;
|
|
2252
2423
|
}
|
|
2253
2424
|
/**
|
|
2254
|
-
* The "nothing has passed and nothing renders" cause:
|
|
2255
|
-
*
|
|
2256
|
-
*
|
|
2425
|
+
* The "nothing has passed and nothing renders" cause: the run's FIRST consecutive failures — from
|
|
2426
|
+
* its first failed test, with no pass since — are all locator waits, at least
|
|
2427
|
+
* {@link EMPTY_PAGE_CAUSE_TESTS} of them across {@link UNIVERSAL_CAUSE_FILES} different spec
|
|
2428
|
+
* files, and each waits for its own spec's first element.
|
|
2429
|
+
*
|
|
2430
|
+
* The file span stays at {@link UNIVERSAL_CAUSE_FILES} whatever the run selected: "the page
|
|
2431
|
+
* renders nothing" is a claim about the APP, and one file's failures can be that file's own setup
|
|
2432
|
+
* (a broken login helper fails every test of one spec at its first locator). A run narrowed to one
|
|
2433
|
+
* or two files still has the signature and connection causes for a whole-run stop.
|
|
2434
|
+
*
|
|
2435
|
+
* Stops the walk as soon as that verdict is readable, not after a fixed floor of failures have
|
|
2436
|
+
* been burned: the page error arrives with the first test, so every further test only repeats it.
|
|
2257
2437
|
*
|
|
2258
2438
|
* This is ONE cause even though the signatures differ — each spec asks for its own first element
|
|
2259
2439
|
* (`input[name="username"]`, `a.navbar-brand`, `h1`), so {@link repeatingFailure} cannot group
|
|
2260
2440
|
* them and {@link universalCause}'s signature test cannot see the run is over. xr-molecule-flash-
|
|
2261
2441
|
* m141 (+20:30) walked a built app that rendered nothing: 36 tests had failed, every one at its
|
|
2262
|
-
* first locator, and the note told the run to keep walking for another ~9 minutes.
|
|
2442
|
+
* first locator, and the note told the run to keep walking for another ~9 minutes. xr-molecule-
|
|
2443
|
+
* flash-m177 (+08:38 → +12:52) repeated it: 18 tests, one whole walk, on a page whose
|
|
2444
|
+
* `PAGEERROR No 'auth-client' provider bonded` was in the log from the first one.
|
|
2263
2445
|
*
|
|
2264
2446
|
* @param text - The log (ANSI already stripped).
|
|
2265
|
-
* @param threshold - The spec-file threshold this run is judged against ({@link universalCause}).
|
|
2266
2447
|
* @param found - The repeating cause {@link repeatingFailure} found, when it found one.
|
|
2267
2448
|
* @returns The cause, or null when the log is not this shape.
|
|
2268
2449
|
*/
|
|
2269
|
-
function emptyPageCause(text,
|
|
2450
|
+
function emptyPageCause(text, found) {
|
|
2270
2451
|
const marks = resultMarks(text);
|
|
2271
2452
|
const firstFailure = marks.indexOf('✘');
|
|
2272
2453
|
if (firstFailure === -1 || marks.slice(firstFailure).includes('✓'))
|
|
@@ -2284,10 +2465,15 @@ function emptyPageCause(text, threshold, found) {
|
|
|
2284
2465
|
// (which names the repeated error) says more than "the page renders nothing" would.
|
|
2285
2466
|
if (found !== null && found.message !== null && found.count >= tests.length)
|
|
2286
2467
|
return null;
|
|
2468
|
+
// The cause is the FIRST consecutive failures: `tests` starts at the first ✘ and no ✓ follows
|
|
2469
|
+
// it, so a page that has failed three tests in a row across three files has already said
|
|
2470
|
+
// everything the rest of the walk can say (m177 paid 4.2 minutes learning that).
|
|
2287
2471
|
if (tests.length < EMPTY_PAGE_CAUSE_TESTS)
|
|
2288
2472
|
return null;
|
|
2289
2473
|
const files = [...new Set(tests.map((t) => specFile(t.where)).filter(Boolean))];
|
|
2290
|
-
|
|
2474
|
+
// The blank-page claim is cross-file by construction (see the doc): a selected-file count
|
|
2475
|
+
// below the span does not shrink it, it removes the claim.
|
|
2476
|
+
if (files.length < UNIVERSAL_CAUSE_FILES)
|
|
2291
2477
|
return null;
|
|
2292
2478
|
const pageError = pageErrorInLog(text);
|
|
2293
2479
|
return {
|
|
@@ -2424,7 +2610,7 @@ export function universalCause(log, found, command) {
|
|
|
2424
2610
|
const threshold = selected === null ? UNIVERSAL_CAUSE_FILES : Math.min(UNIVERSAL_CAUSE_FILES, selected);
|
|
2425
2611
|
// Nothing has passed and nothing renders: EVERY spec so far is waiting for its own first
|
|
2426
2612
|
// element, so the signatures differ while the cause does not (xr-molecule-flash-m141).
|
|
2427
|
-
const emptyPage = emptyPageCause(text,
|
|
2613
|
+
const emptyPage = emptyPageCause(text, found);
|
|
2428
2614
|
if (emptyPage)
|
|
2429
2615
|
return emptyPage;
|
|
2430
2616
|
if (!found)
|