@molecule/api-ai-tools 1.9.2 → 1.9.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 +187 -28
- package/dist/suite-overlap.d.ts +7 -4
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +154 -7
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +81 -17
- package/dist/trace-artifacts.d.ts +40 -0
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +169 -10
- package/dist/utilities.d.ts +83 -21
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +289 -48
- 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-17T20:38:26.582Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -352,6 +352,12 @@ interface TraceRequest {
|
|
|
352
352
|
sent?: string
|
|
353
353
|
/** What came back for that 4xx, bounded the same way — the API's own error body. */
|
|
354
354
|
got?: string
|
|
355
|
+
/**
|
|
356
|
+
* The browser's own resource type (`document`, `fetch`, `xhr`, `font`, `image`, `stylesheet`,
|
|
357
|
+
* `script`, …). `document` when the request was the page load itself; when it ended in a
|
|
358
|
+
* `net::ERR_*`, nothing rendered and no selector or app request was measured.
|
|
359
|
+
*/
|
|
360
|
+
type?: string
|
|
355
361
|
}
|
|
356
362
|
```
|
|
357
363
|
|
|
@@ -757,6 +763,28 @@ interface FailureCause {
|
|
|
757
763
|
* (xr-molecule-flash-m110 +18:42 rewrote a CORS middleware for a 20 s connection timeout).
|
|
758
764
|
*/
|
|
759
765
|
kind: RunFailureKind
|
|
766
|
+
/**
|
|
767
|
+
* What the page itself showed at the moment of the failure, when the caller read it from
|
|
768
|
+
* Playwright's `error-context.md` (the alert / error list). The failure's own error line names
|
|
769
|
+
* the symptom — `waitForURL` timed out — and never what the page was saying: xr-molecule-flash-
|
|
770
|
+
* m122 +23:39 grouped eight `waitForURL` timeouts under one line while the page snapshots named
|
|
771
|
+
* the cause (`email can't be blank`, `username can't be blank` — the settings form submitted
|
|
772
|
+
* before its values had loaded). Attached to the cause so the fix needs no `cat` of the file.
|
|
773
|
+
*/
|
|
774
|
+
pageShowed?: string[]
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
#### `CausePageErrors`
|
|
779
|
+
|
|
780
|
+
One failing test's page errors, as read from its `error-context.md`.
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
interface CausePageErrors {
|
|
784
|
+
/** The test's location as the log prints it (`settings.spec.ts:107`). */
|
|
785
|
+
where: string
|
|
786
|
+
/** The alert / error items the page showed at the moment it failed. */
|
|
787
|
+
items: readonly string[]
|
|
760
788
|
}
|
|
761
789
|
```
|
|
762
790
|
|
|
@@ -905,6 +933,37 @@ function locatedCommandSegments(command: string): LocatedCommandSegment[]
|
|
|
905
933
|
|
|
906
934
|
**Returns:** One entry per statement or pipeline stage, in order.
|
|
907
935
|
|
|
936
|
+
#### `statementArgumentEnd(command, start, end)`
|
|
937
|
+
|
|
938
|
+
Where an option of a statement's program belongs: one past the statement's last ARGUMENT.
|
|
939
|
+
|
|
940
|
+
A flag written "at the end of the command" is not the runner's: a statement can end on a
|
|
941
|
+
redirection (`… 2>&1`), and a statement can sit inside a group whose closer belongs to the
|
|
942
|
+
shell, not to the words (`( … )`, `{ …; }`). Appending after that closer is a different shell
|
|
943
|
+
word that the group has already closed — `… --config=x 2>&1) --reporter=list` is
|
|
944
|
+
`sh: Syntax error: word unexpected`, which killed a backgrounded walk at once and pushed the
|
|
945
|
+
model off the platform's runner shape (xr-molecule-flash-m129 +34:29). Write the option where
|
|
946
|
+
the program's own arguments end and the rest of the statement still parses:
|
|
947
|
+
`… --config=x --reporter=list 2>&1)`.
|
|
948
|
+
|
|
949
|
+
The statement must be one {@link locatedCommandSegments} placed in THIS command: a statement
|
|
950
|
+
inside a nested `sh -c '<command>'` string has no offsets here (its positions are positions in
|
|
951
|
+
that string), so null is returned rather than a splice into the outer text.
|
|
952
|
+
|
|
953
|
+
```typescript
|
|
954
|
+
function statementArgumentEnd(
|
|
955
|
+
command: string,
|
|
956
|
+
start: number | null,
|
|
957
|
+
end: number | null,
|
|
958
|
+
): number | null
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
- `command` — The shell command.
|
|
962
|
+
- `start` — The statement's first character ({@link LocatedCommandSegment.start}).
|
|
963
|
+
- `end` — One past the statement's last character ({@link LocatedCommandSegment.end}).
|
|
964
|
+
|
|
965
|
+
**Returns:** The offset to write the option at, or null when the statement cannot be placed here.
|
|
966
|
+
|
|
908
967
|
#### `commandTimeoutSeconds(command)`
|
|
909
968
|
|
|
910
969
|
The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
@@ -1340,7 +1399,7 @@ function withSuiteConfig(command: string, config: string | null | undefined): st
|
|
|
1340
1399
|
|
|
1341
1400
|
**Returns:** The command with the flag, or the command unchanged.
|
|
1342
1401
|
|
|
1343
|
-
#### `scopedRerunCommand(command, locations, excluded, config)`
|
|
1402
|
+
#### `scopedRerunCommand(command, locations, excluded, config, cwd)`
|
|
1344
1403
|
|
|
1345
1404
|
The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
1346
1405
|
invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
@@ -1358,13 +1417,15 @@ function scopedRerunCommand(
|
|
|
1358
1417
|
locations: readonly string[],
|
|
1359
1418
|
excluded?: readonly string[],
|
|
1360
1419
|
config?: string | null,
|
|
1420
|
+
cwd?: string | null,
|
|
1361
1421
|
): string
|
|
1362
1422
|
```
|
|
1363
1423
|
|
|
1364
1424
|
- `command` — The whole-suite run's command.
|
|
1365
|
-
- `locations` — The failing `file:line`s to re-run, as the run's own log printed them
|
|
1425
|
+
- `locations` — The failing `file:line`s to re-run, as the run's own log printed them. A location the reporter printed relative to the runner's own directory (`../../fixtures/…`, which no absolute path contains) is resolved here ({@link playwrightLocations}), so the command this returns is one Playwright can match.
|
|
1366
1426
|
- `excluded` — The test titles the request carves out. They are inverted out (`--grep-invert`) so a re-run of the failing locations cannot report one of them again — xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about a test the request had already said does not count.
|
|
1367
1427
|
- `config` — The config the run loaded, when the caller knows it; added when the command does not name one.
|
|
1428
|
+
- `cwd` — The directory `exec_command` was given, when it was; the base a location is resolved against when the command does not `cd` anywhere.
|
|
1368
1429
|
|
|
1369
1430
|
**Returns:** The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
1370
1431
|
|
|
@@ -1615,6 +1676,51 @@ function parseTraceArtifacts(stdout: string): TraceArtifact[]
|
|
|
1615
1676
|
|
|
1616
1677
|
**Returns:** One entry per trace it printed.
|
|
1617
1678
|
|
|
1679
|
+
#### `isStaticAssetRequest(request)`
|
|
1680
|
+
|
|
1681
|
+
Whether a request is one of the page shell's, not the test's (`STATIC_ASSET_PATH`).
|
|
1682
|
+
|
|
1683
|
+
The document itself is never incidental — a page that never loaded IS the failure — and a
|
|
1684
|
+
request whose type is the app's own API (`fetch`/`xhr`) is judged by its path alone.
|
|
1685
|
+
|
|
1686
|
+
```typescript
|
|
1687
|
+
function isStaticAssetRequest(request: TraceRequest): boolean
|
|
1688
|
+
```
|
|
1689
|
+
|
|
1690
|
+
- `request` — One request a trace recorded.
|
|
1691
|
+
|
|
1692
|
+
**Returns:** True when the request is incidental to the step under test.
|
|
1693
|
+
|
|
1694
|
+
#### `failedRequest(request)`
|
|
1695
|
+
|
|
1696
|
+
Whether a trace recorded a request the server actually failed: a 4xx/5xx, or a network error.
|
|
1697
|
+
|
|
1698
|
+
A 404 on a static asset is excepted: `/sw.js` and `/fonts/*` 404 on an app that never shipped
|
|
1699
|
+
them, and naming one as a failure sent m125 into writing the caching worker it had asked about.
|
|
1700
|
+
A 5xx on an asset, or a network error, still counts — the server did fail.
|
|
1701
|
+
|
|
1702
|
+
```typescript
|
|
1703
|
+
function failedRequest(request: TraceRequest): boolean
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
- `request` — One request a trace recorded.
|
|
1707
|
+
|
|
1708
|
+
**Returns:** True when the request failed in a way the note should name.
|
|
1709
|
+
|
|
1710
|
+
#### `unansweredRequest(request)`
|
|
1711
|
+
|
|
1712
|
+
Whether a trace recorded NO response and no reason for it — a request that was aborted or still
|
|
1713
|
+
in flight when the test ended. It is not evidence that the server failed (m112 +30:45), and one
|
|
1714
|
+
the page shell made is not evidence about the step at all (m125 +29:33).
|
|
1715
|
+
|
|
1716
|
+
```typescript
|
|
1717
|
+
function unansweredRequest(request: TraceRequest): boolean
|
|
1718
|
+
```
|
|
1719
|
+
|
|
1720
|
+
- `request` — One request a trace recorded.
|
|
1721
|
+
|
|
1722
|
+
**Returns:** True when the request is still in flight and worth naming.
|
|
1723
|
+
|
|
1618
1724
|
#### `failureTraceNote(items, target, earlierFailures)`
|
|
1619
1725
|
|
|
1620
1726
|
The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
@@ -1935,6 +2041,12 @@ are output-only ({@link isOutputOnlyFilter}): a `| tee` or `| xargs` keeps its p
|
|
|
1935
2041
|
caller that reports the run's exit code must then treat it as unknown
|
|
1936
2042
|
({@link suiteRunPipedAfterRunner}).
|
|
1937
2043
|
|
|
2044
|
+
A statement after the run's own pipeline (`; echo done`, `&& echo ok`) does not protect the
|
|
2045
|
+
filters before it: the filter is thrown away and the trailing statement is kept, so the log the
|
|
2046
|
+
run writes still holds every failure (xr-molecule-flash-m121 — a `| tail -5; echo done` kept its
|
|
2047
|
+
tail, and a 95-second walk's log held only the tally while the platform's own hold had handed
|
|
2048
|
+
that command back).
|
|
2049
|
+
|
|
1938
2050
|
```typescript
|
|
1939
2051
|
function stripTrailingSuiteFilters(command: string): SuiteFilterStrip
|
|
1940
2052
|
```
|
|
@@ -2461,28 +2573,65 @@ function repeatingFailure(log: string): RepeatingFailure | null
|
|
|
2461
2573
|
|
|
2462
2574
|
**Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} distinct tests behind it.
|
|
2463
2575
|
|
|
2464
|
-
#### `
|
|
2576
|
+
#### `specFilesSelected(command)`
|
|
2465
2577
|
|
|
2466
|
-
|
|
2467
|
-
|
|
2578
|
+
How many test FILES a Playwright run's own command narrows it to, or null when it selects the
|
|
2579
|
+
whole suite (or its selection cannot be read).
|
|
2468
2580
|
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2581
|
+
{@link universalCause}'s file threshold is written for a whole-suite run, which a narrowed run
|
|
2582
|
+
can never reach. xr-molecule-flash-m119 (+42:08) re-ran ONE spec file against a hand-started
|
|
2583
|
+
`vite preview` the browser could not reach, and every test in it failed `page.goto: Timeout` —
|
|
2584
|
+
but one file is below {@link UNIVERSAL_CAUSE_FILES}, so the wait sat out its entire 280 s
|
|
2585
|
+
budget on a run that had already measured all it could.
|
|
2586
|
+
|
|
2587
|
+
The command is read through {@link commandSegments}, so a `playwright test` named as an
|
|
2588
|
+
argument is not a run. A {@link PLAYWRIGHT_VALUE_FLAGS} flag written without `=` takes the next
|
|
2589
|
+
word as its value, so a `--config app/e2e-suite.config.ts` is never counted as a spec file; a
|
|
2590
|
+
directory or a bare substring filter names files the command does not list, which answers null
|
|
2591
|
+
(the whole-suite threshold) rather than a count of the wrong files.
|
|
2479
2592
|
|
|
2480
2593
|
```typescript
|
|
2481
|
-
function
|
|
2594
|
+
function specFilesSelected(command: string): number | null
|
|
2595
|
+
```
|
|
2596
|
+
|
|
2597
|
+
- `command` — The run's command.
|
|
2598
|
+
|
|
2599
|
+
**Returns:** The number of distinct test files it names, or null when it names none (the whole suite) or the selection cannot be told.
|
|
2600
|
+
|
|
2601
|
+
#### `universalCause(log, found, command)`
|
|
2602
|
+
|
|
2603
|
+
Whether a repeating cause is failing the whole run — the one case where stopping it is the
|
|
2604
|
+
right call.
|
|
2605
|
+
|
|
2606
|
+
The test is: the cause accounts for every failure the log names, it has hit the spec-file
|
|
2607
|
+
threshold, and nothing has passed since its first failure. The threshold is
|
|
2608
|
+
{@link UNIVERSAL_CAUSE_FILES} for a whole-suite run but the number of files the command
|
|
2609
|
+
SELECTED for a narrowed one ({@link specFilesSelected}): a one-file run can never span three,
|
|
2610
|
+
and xr-molecule-flash-m119 (+42:08) waited out its whole budget on exactly that shape. An app
|
|
2611
|
+
that is not up, a 5xx on every request, or a selector that is missing on every page all look
|
|
2612
|
+
like that, and every remaining test will fail the same way — so the run's remaining minutes
|
|
2613
|
+
measure nothing. A CONNECTION-class cause ({@link CONNECTION_CAUSE}) needs no file span at all:
|
|
2614
|
+
three tests that could not reach the app, with nothing passing since, is the app-unreachable
|
|
2615
|
+
case however few files the run walks.
|
|
2616
|
+
|
|
2617
|
+
Anything less is NOT this: a cause that stops at one file of a multi-file run, or that leaves
|
|
2618
|
+
other tests passing, or that shares the run with failures of another shape, leaves the run with
|
|
2619
|
+
something to say, and the run is the only thing that will say it. X1 flash-m83 (2026-10-16)
|
|
2620
|
+
stopped eight local runs at their first repeating cause over 14.5 minutes and never finished
|
|
2621
|
+
one; three of its deploys each fixed a single cause that a finished run would have listed
|
|
2622
|
+
alongside the others.
|
|
2623
|
+
|
|
2624
|
+
```typescript
|
|
2625
|
+
function universalCause(
|
|
2626
|
+
log: string,
|
|
2627
|
+
found: RepeatingFailure,
|
|
2628
|
+
command?: string | null,
|
|
2629
|
+
): UniversalCause | null
|
|
2482
2630
|
```
|
|
2483
2631
|
|
|
2484
2632
|
- `log` — The log so far.
|
|
2485
2633
|
- `found` — The repeating cause {@link repeatingFailure} found in it.
|
|
2634
|
+
- `command` — How the run was started, when the caller knows it: its spec-file selection scales the file threshold ({@link specFilesSelected}).
|
|
2486
2635
|
|
|
2487
2636
|
**Returns:** The cause's count and files, or null when the run is not failing whole.
|
|
2488
2637
|
|
|
@@ -2493,8 +2642,10 @@ for: a PROVEN cause that is failing the whole run.
|
|
|
2493
2642
|
|
|
2494
2643
|
Both halves are load-bearing, each of them learned from a run that lost minutes to the other.
|
|
2495
2644
|
|
|
2496
|
-
- **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across
|
|
2497
|
-
{@link UNIVERSAL_CAUSE_FILES}
|
|
2645
|
+
- **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across the
|
|
2646
|
+
spec-file threshold — {@link UNIVERSAL_CAUSE_FILES}, or the files a narrowed run's own
|
|
2647
|
+
command selects, or {@link CONNECTION_CAUSE_TESTS} tests of an app that never answered — with
|
|
2648
|
+
nothing passing since the first. That is the
|
|
2498
2649
|
dont-stop-runs rule. X1 flash-m83 (2026-10-16) stopped eight local runs at their first
|
|
2499
2650
|
repeating cause in 14.5 minutes and never finished one, and a run that still has other causes
|
|
2500
2651
|
left to name is the only thing that will name them.
|
|
@@ -2505,9 +2656,6 @@ Both halves are load-bearing, each of them learned from a run that lost minutes
|
|
|
2505
2656
|
xr-molecule-flash-m99 (2026-10-16) was told `status: 'repeating_failure'` twice on that
|
|
2506
2657
|
shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
|
|
2507
2658
|
— read the early return as "stop waiting, edit now", and voided a run that was still walking;
|
|
2508
|
-
the second cost a 224 s wait that ended with no result at all.
|
|
2509
|
-
|
|
2510
|
-
Neither half alone is enough, and {@link repeatingFailure} finding a signature is not either:
|
|
2511
2659
|
|
|
2512
2660
|
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
|
|
2513
2661
|
|
|
@@ -2605,6 +2753,7 @@ function failureCausesNote(
|
|
|
2605
2753
|
log: string,
|
|
2606
2754
|
excluded?: readonly string[],
|
|
2607
2755
|
run?: { command: string; cwd?: string; log?: string; config?: string | null },
|
|
2756
|
+
pageErrors?: readonly CausePageErrors[],
|
|
2608
2757
|
): string | null
|
|
2609
2758
|
```
|
|
2610
2759
|
|
|
@@ -2621,11 +2770,11 @@ invocation, in the same directory, narrowed to those tests — or null when the
|
|
|
2621
2770
|
is not a Playwright run this can narrow.
|
|
2622
2771
|
|
|
2623
2772
|
The `locations` are the paths the run's own log printed, which are relative to the directory
|
|
2624
|
-
that run started in, so
|
|
2625
|
-
unless it already changes into one of its own, because the
|
|
2626
|
-
wherever it starts. The returned command is for AFTER the run
|
|
2627
|
-
the same app and database, and that is exactly what is
|
|
2628
|
-
({@link suiteRunInTheWay}).
|
|
2773
|
+
that run started in, so the composer resolves them there ({@link scopedRerunCommand}): the
|
|
2774
|
+
command names that directory unless it already changes into one of its own, because the
|
|
2775
|
+
executor's next command starts wherever it starts. The returned command is for AFTER the run
|
|
2776
|
+
ends — it is a second run against the same app and database, and that is exactly what is
|
|
2777
|
+
refused while the run walks ({@link suiteRunInTheWay}).
|
|
2629
2778
|
|
|
2630
2779
|
```typescript
|
|
2631
2780
|
function rerunOfRunningSuite(
|
|
@@ -2675,12 +2824,13 @@ function repeatingFailureNote(
|
|
|
2675
2824
|
stop: string
|
|
2676
2825
|
universal?: UniversalCause | null
|
|
2677
2826
|
run?: { command: string; cwd?: string }
|
|
2827
|
+
editsInvalidateRun?: boolean
|
|
2678
2828
|
},
|
|
2679
2829
|
): string
|
|
2680
2830
|
```
|
|
2681
2831
|
|
|
2682
2832
|
- `found` — What {@link repeatingFailure} found.
|
|
2683
|
-
- `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for an {@link earlyReturnCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole),
|
|
2833
|
+
- `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for an {@link earlyReturnCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole), `run`, how the run was started ({@link rerunOfRunningSuite}) when the caller knows it, and `editsInvalidateRun`, true when the run measures the sandbox's own app (a local run, where an edit now changes what the suite is testing and its result stops counting).
|
|
2684
2834
|
|
|
2685
2835
|
**Returns:** The note.
|
|
2686
2836
|
|
|
@@ -3213,6 +3363,15 @@ property of the run rather than of a page.
|
|
|
3213
3363
|
const UNIVERSAL_CAUSE_FILES: 3
|
|
3214
3364
|
```
|
|
3215
3365
|
|
|
3366
|
+
#### `CONNECTION_CAUSE_TESTS`
|
|
3367
|
+
|
|
3368
|
+
How many tests a connection-class cause must cover before the run is stopped even when they
|
|
3369
|
+
are all in one file — see {@link universalCause}.
|
|
3370
|
+
|
|
3371
|
+
```typescript
|
|
3372
|
+
const CONNECTION_CAUSE_TESTS: 3
|
|
3373
|
+
```
|
|
3374
|
+
|
|
3216
3375
|
#### `PARSE_CHECKABLE`
|
|
3217
3376
|
|
|
3218
3377
|
Files a parse check can vouch for cheaply right after a write.
|
package/dist/suite-overlap.d.ts
CHANGED
|
@@ -89,18 +89,21 @@ export declare function withSuiteConfig(command: string, config: string | null |
|
|
|
89
89
|
* `Cannot navigate to invalid URL` because the re-run loaded no base URL).
|
|
90
90
|
*
|
|
91
91
|
* @param command - The whole-suite run's command.
|
|
92
|
-
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them
|
|
93
|
-
*
|
|
94
|
-
*
|
|
92
|
+
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them. A
|
|
93
|
+
* location the reporter printed relative to the runner's own directory (`../../fixtures/…`,
|
|
94
|
+
* which no absolute path contains) is resolved here ({@link playwrightLocations}), so the
|
|
95
|
+
* command this returns is one Playwright can match.
|
|
95
96
|
* @param excluded - The test titles the request carves out. They are inverted out
|
|
96
97
|
* (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
|
|
97
98
|
* xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
|
|
98
99
|
* a test the request had already said does not count.
|
|
99
100
|
* @param config - The config the run loaded, when the caller knows it; added when the command
|
|
100
101
|
* does not name one.
|
|
102
|
+
* @param cwd - The directory `exec_command` was given, when it was; the base a location is
|
|
103
|
+
* resolved against when the command does not `cd` anywhere.
|
|
101
104
|
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
102
105
|
*/
|
|
103
|
-
export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null): string;
|
|
106
|
+
export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null, cwd?: string | null): string;
|
|
104
107
|
/**
|
|
105
108
|
* One suite run, as the rule that decides whether a second may start beside it reads it.
|
|
106
109
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAClB,MAAM,CAqCR;AA8ID;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
|
package/dist/suite-overlap.js
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
*
|
|
29
29
|
* @module
|
|
30
30
|
*/
|
|
31
|
-
import {
|
|
31
|
+
import { posix } from 'node:path';
|
|
32
|
+
import { commandSegments, locatedCommandSegments } from './command-position.js';
|
|
32
33
|
import { commandRunsNoTests } from './no-test-run.js';
|
|
33
34
|
import { shellQuote } from './utilities.js';
|
|
34
35
|
/**
|
|
@@ -131,18 +132,21 @@ export function withSuiteConfig(command, config) {
|
|
|
131
132
|
* `Cannot navigate to invalid URL` because the re-run loaded no base URL).
|
|
132
133
|
*
|
|
133
134
|
* @param command - The whole-suite run's command.
|
|
134
|
-
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them
|
|
135
|
-
*
|
|
136
|
-
*
|
|
135
|
+
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them. A
|
|
136
|
+
* location the reporter printed relative to the runner's own directory (`../../fixtures/…`,
|
|
137
|
+
* which no absolute path contains) is resolved here ({@link playwrightLocations}), so the
|
|
138
|
+
* command this returns is one Playwright can match.
|
|
137
139
|
* @param excluded - The test titles the request carves out. They are inverted out
|
|
138
140
|
* (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
|
|
139
141
|
* xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
|
|
140
142
|
* a test the request had already said does not count.
|
|
141
143
|
* @param config - The config the run loaded, when the caller knows it; added when the command
|
|
142
144
|
* does not name one.
|
|
145
|
+
* @param cwd - The directory `exec_command` was given, when it was; the base a location is
|
|
146
|
+
* resolved against when the command does not `cd` anywhere.
|
|
143
147
|
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
144
148
|
*/
|
|
145
|
-
export function scopedRerunCommand(command, locations, excluded = [], config) {
|
|
149
|
+
export function scopedRerunCommand(command, locations, excluded = [], config, cwd) {
|
|
146
150
|
const at = command.search(/\bplaywright\s+test\b/);
|
|
147
151
|
if (at === -1)
|
|
148
152
|
return command;
|
|
@@ -165,12 +169,155 @@ export function scopedRerunCommand(command, locations, excluded = [], config) {
|
|
|
165
169
|
// The run's own config: the command's `--config` when it has one, else the config the caller
|
|
166
170
|
// recorded for the run. A re-run without it measures nothing (m113).
|
|
167
171
|
const carry = config && !namesSuiteConfig(statement) ? ` --config ${shellQuote(config)}` : '';
|
|
168
|
-
|
|
172
|
+
// Playwright matches each positional argument as a regex against the ABSOLUTE path of every test
|
|
173
|
+
// file it collects (`playwright test --help`: "Each argument is treated as a regular expression.
|
|
174
|
+
// Matching is performed against the absolute file paths."), while the reporter prints a failure
|
|
175
|
+
// relative to the runner's own directory. A `../../fixtures/…` location therefore matches
|
|
176
|
+
// nothing — "No tests found" — so every location is rendered in a form Playwright can match
|
|
177
|
+
// ({@link playwrightLocations}).
|
|
178
|
+
const narrowed = playwrightLocations(locations, command, cwd);
|
|
179
|
+
const scoped = dropSupersededSpecFiles(statement, narrowed)
|
|
169
180
|
.replace(/\s*--last-failed\b/g, '')
|
|
170
181
|
.replace(/\s*--retries(?:[= ]\d+)?/g, '')
|
|
171
|
-
.replace(/^(playwright\s+test)\b/, `$1 ${
|
|
182
|
+
.replace(/^(playwright\s+test)\b/, `$1 ${narrowed.join(' ')} --retries=0${carry}${invert}`);
|
|
172
183
|
return `${head}${scoped}${tail}`;
|
|
173
184
|
}
|
|
185
|
+
/** A directory a `cd` can name literally: no `~`, no variable, no glob. */
|
|
186
|
+
const LITERAL_DIR = /^[A-Za-z0-9_./+-]+$/;
|
|
187
|
+
/** A word without its shell quotes. */
|
|
188
|
+
const unquoted = (word) => word.replace(/^['"]|['"]$/g, '');
|
|
189
|
+
/**
|
|
190
|
+
* The absolute directory the Playwright statement in a command runs in, or null when the command
|
|
191
|
+
* does not say.
|
|
192
|
+
*
|
|
193
|
+
* The reporter prints a failing test's file relative to the process directory the runner started
|
|
194
|
+
* in (`../fixtures/…` from a run whose suite lives outside its own tree), so the directory has to
|
|
195
|
+
* come from the command itself: `exec_command`'s `cwd` when it was given, then each `cd` before
|
|
196
|
+
* the runner. Only a literal `cd` is followed — a target holding a variable, a glob or `~`
|
|
197
|
+
* resolves nowhere useful, and a guessed absolute path is worse than the relative one the
|
|
198
|
+
* reporter printed.
|
|
199
|
+
*
|
|
200
|
+
* @param command - The shell command.
|
|
201
|
+
* @param cwd - The `cwd` the tool was given, when it was.
|
|
202
|
+
* @returns The absolute directory, or null.
|
|
203
|
+
*/
|
|
204
|
+
function suiteRunDirectory(command, cwd) {
|
|
205
|
+
let dir = typeof cwd === 'string' && cwd.startsWith('/') ? posix.normalize(cwd) : null;
|
|
206
|
+
for (const { segment } of locatedCommandSegments(command)) {
|
|
207
|
+
if (segment === null)
|
|
208
|
+
continue;
|
|
209
|
+
if (segment.program === 'cd') {
|
|
210
|
+
const target = unquoted(segment.args[0] ?? '').replace(/\/+$/, '');
|
|
211
|
+
if (!target || target.startsWith('-') || target.startsWith('~') || !LITERAL_DIR.test(target))
|
|
212
|
+
return null;
|
|
213
|
+
// A shell `cd /abs` DISCARDS the directory before it; only a relative target resolves
|
|
214
|
+
// against the one the run started in.
|
|
215
|
+
if (target.startsWith('/'))
|
|
216
|
+
dir = posix.normalize(target);
|
|
217
|
+
else if (dir === null)
|
|
218
|
+
return null;
|
|
219
|
+
else
|
|
220
|
+
dir = posix.normalize(posix.join(dir, target));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (segment.program === 'playwright' && segment.args[0] === 'test')
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
return dir;
|
|
227
|
+
}
|
|
228
|
+
/** A failure location's path and its `:line[:column]` suffix, split the way Playwright does. */
|
|
229
|
+
const LOCATION_LINE = /^(.*?)(:\d+(?::\d+)?)$/;
|
|
230
|
+
/**
|
|
231
|
+
* Each location in a form Playwright matches, so a re-run composed from a run's own report can
|
|
232
|
+
* actually run.
|
|
233
|
+
*
|
|
234
|
+
* Playwright compiles every positional file argument into a regex and tests it against the
|
|
235
|
+
* absolute path of each collected test file (`playwright test --help`; `createFiltersFromArguments`
|
|
236
|
+
* → `forceRegExp` → `createFileMatcher`, verified against Playwright 1.63 with `--list`: the
|
|
237
|
+
* reporter's `../../fixtures/…/articles.spec.ts:3` lists 0 tests, the absolute path lists 1). A
|
|
238
|
+
* location the reporter printed relative to the runner's directory is resolved against it; where
|
|
239
|
+
* that directory cannot be known, only the leading `./`/`../` segments are dropped, which leaves a
|
|
240
|
+
* suffix the absolute path still contains (`fixtures/…/articles.spec.ts:3` matches).
|
|
241
|
+
*
|
|
242
|
+
* A location without a leading dot is left alone: it is either absolute or already a suffix of the
|
|
243
|
+
* absolute path, and rewriting it against a mis-guessed base could only break it
|
|
244
|
+
* (xr-molecule-flash-m118 +16:34 "No tests found"; m124 +18:38 the platform's own "Re-run just
|
|
245
|
+
* these" command).
|
|
246
|
+
*
|
|
247
|
+
* @param locations - The failing `file:line`s as the run's log printed them.
|
|
248
|
+
* @param command - The run's command, for its own `cd`s.
|
|
249
|
+
* @param cwd - The `cwd` the tool was given, when it was.
|
|
250
|
+
* @returns The locations, matchable by Playwright.
|
|
251
|
+
*/
|
|
252
|
+
function playwrightLocations(locations, command, cwd) {
|
|
253
|
+
const base = suiteRunDirectory(command, cwd);
|
|
254
|
+
return locations.map((location) => {
|
|
255
|
+
const [, file = '', suffix = ''] = LOCATION_LINE.exec(location) ?? [];
|
|
256
|
+
if (!file.startsWith('./') && !file.startsWith('../'))
|
|
257
|
+
return location;
|
|
258
|
+
const resolved = base === null ? file.replace(/^(?:\.{1,2}\/)+/, '') : posix.resolve(base, file);
|
|
259
|
+
return `${resolved}${suffix}`;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/** A spec-file argument Playwright accepts as its positional filter — never a flag. */
|
|
263
|
+
const SPEC_FILE_ARGUMENT = /^(?!-)[^\s]*\.(?:spec|test)\.[cm]?[jt]sx?(?::\d+(?::\d+)?)?$/;
|
|
264
|
+
/**
|
|
265
|
+
* A `file:line` location's own file part: Playwright prints failures as
|
|
266
|
+
* `url-navigation.spec.ts:13:7`, and the file is the argument that supersedes.
|
|
267
|
+
*
|
|
268
|
+
* @param location - A location (`file.spec.ts:13`, `file.spec.ts:13:7`) or a spec-file argument.
|
|
269
|
+
* @returns The path without its line numbers.
|
|
270
|
+
*/
|
|
271
|
+
function locationFile(location) {
|
|
272
|
+
return location.replace(/:\d+(?::\d+)?$/, '');
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Whether two spellings name the same spec file: equal, or one the absolute (or longer) form of
|
|
276
|
+
* the other. The command and the reporter routinely spell the same file differently — the model
|
|
277
|
+
* names `url-navigation.spec.ts`, the summary prints `specs/e2e/url-navigation.spec.ts:13`, and a
|
|
278
|
+
* hold hands back `/workspace/fixtures/realworld/specs/e2e/url-navigation.spec.ts:13`.
|
|
279
|
+
*
|
|
280
|
+
* @param argument - The spec-file argument as the command wrote it.
|
|
281
|
+
* @param location - The failing location.
|
|
282
|
+
* @returns True when the location is inside that file.
|
|
283
|
+
*/
|
|
284
|
+
function sameSpecFile(argument, location) {
|
|
285
|
+
const a = locationFile(argument)
|
|
286
|
+
.replace(/^['"]|['"]$/g, '')
|
|
287
|
+
.replace(/^\.\//, '');
|
|
288
|
+
const l = locationFile(location)
|
|
289
|
+
.replace(/^['"]|['"]$/g, '')
|
|
290
|
+
.replace(/^\.\//, '');
|
|
291
|
+
return a === l || a.endsWith(`/${l}`) || l.endsWith(`/${a}`);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The statement with every spec-file argument its OWN failure locations already name taken out.
|
|
295
|
+
*
|
|
296
|
+
* A `file:line` inside a named file supersedes the whole file, so keeping both runs the file plus
|
|
297
|
+
* the locations — the whole suite again, with the failures duplicated inside it. xr-molecule-flash-m118
|
|
298
|
+
* (+45:24) composed just that: the model's `npx playwright test … url-navigation.spec.ts` was
|
|
299
|
+
* narrowed to the 14 `file:line`s AND the bare file, "~297s" for a file and fourteen duplicates.
|
|
300
|
+
*
|
|
301
|
+
* @param statement - The Playwright statement, starting at its own `playwright test`.
|
|
302
|
+
* @param locations - The failing `file:line`s the re-run names.
|
|
303
|
+
* @returns The statement without those superseded file arguments.
|
|
304
|
+
*/
|
|
305
|
+
function dropSupersededSpecFiles(statement, locations) {
|
|
306
|
+
if (locations.length === 0)
|
|
307
|
+
return statement;
|
|
308
|
+
const runner = /^(playwright\s+test)\b/.exec(statement);
|
|
309
|
+
if (runner === null)
|
|
310
|
+
return statement;
|
|
311
|
+
const head = runner[1];
|
|
312
|
+
const kept = statement
|
|
313
|
+
.slice(head.length)
|
|
314
|
+
.replace(/(^|\s+)([^\s]+)(?=\s|$)/g, (whole, _space, token) => {
|
|
315
|
+
if (!SPEC_FILE_ARGUMENT.test(token))
|
|
316
|
+
return whole;
|
|
317
|
+
return locations.every((location) => sameSpecFile(token, location)) ? '' : whole;
|
|
318
|
+
});
|
|
319
|
+
return `${head}${kept}`;
|
|
320
|
+
}
|
|
174
321
|
/**
|
|
175
322
|
* The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
176
323
|
*
|
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;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAiJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAiJ9C;;;;;;;;;;;;;;;;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;AAmEnE;;;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;AA8ED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAk6ExF"}
|