@molecule/api-ai-tools 1.10.10 → 1.11.1
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 +171 -1
- package/dist/command-position.d.ts +37 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +164 -5
- package/dist/cut-run.d.ts.map +1 -1
- package/dist/cut-run.js +10 -6
- package/dist/trace-artifacts.d.ts +103 -5
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +700 -37
- package/dist/utilities.d.ts +9 -0
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +115 -17
- 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-21T08:55:11.822Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -105,6 +105,28 @@ interface CommandSegment {
|
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
#### `ExecutedCommand`
|
|
109
|
+
|
|
110
|
+
One command a shell command actually executes, wherever the shell runs it.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
interface ExecutedCommand {
|
|
114
|
+
/**
|
|
115
|
+
* The word in command position, its directory removed (`/usr/bin/printenv` → `printenv`) — or
|
|
116
|
+
* the launcher itself when it runs alone and nothing follows it (`x | env` → `env`).
|
|
117
|
+
*/
|
|
118
|
+
program: string
|
|
119
|
+
/**
|
|
120
|
+
* Its arguments, as written with quotes kept — the statement's own words AFTER the program,
|
|
121
|
+
* including an input redirect's path (`< /proc/self/environ`), so a reader of secret files sees
|
|
122
|
+
* the file it reads.
|
|
123
|
+
*/
|
|
124
|
+
args: readonly string[]
|
|
125
|
+
/** The program and its arguments as one line, what a pattern anchored at the program matches. */
|
|
126
|
+
text: string
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
108
130
|
#### `LocatedCommandSegment`
|
|
109
131
|
|
|
110
132
|
One statement of a shell command, with where it sits and the variables it sets for itself.
|
|
@@ -705,6 +727,81 @@ interface TraceHiddenBox {
|
|
|
705
727
|
}
|
|
706
728
|
```
|
|
707
729
|
|
|
730
|
+
#### `TraceCountMatch`
|
|
731
|
+
|
|
732
|
+
One element a failing locator count matched, read from the trace's own failure snapshot.
|
|
733
|
+
|
|
734
|
+
A count assertion reports only `Expected: 3 / Received: 4`; the element that made it 4 is not
|
|
735
|
+
in the error. Playwright's trace does hold it — either marked `__playwright_target__` in the
|
|
736
|
+
failure snapshot (`expect(locator).toHaveCount(n)`), or recoverable by replaying the recorded
|
|
737
|
+
selector against the snapshot's DOM in a browser (`locator.count()` then `expect(n).toBe(k)`) —
|
|
738
|
+
so each match is named by what it is and where it sits, never by a guess.
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
interface TraceCountMatch {
|
|
742
|
+
/** The element's tag, lowercased (`div`). */
|
|
743
|
+
tag: string
|
|
744
|
+
/** Its `class` attribute, bounded (`card-block`). */
|
|
745
|
+
classes?: string
|
|
746
|
+
/** Its own text, whitespace-collapsed and bounded. */
|
|
747
|
+
text?: string
|
|
748
|
+
/** Its ancestors, outermost first (`body > div.comment-form > div.card`), bounded. */
|
|
749
|
+
path?: string
|
|
750
|
+
/** True when a `<form>` is one of its ancestors — the m321 extra match's shape. */
|
|
751
|
+
inForm?: true
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
#### `TraceCountMismatch`
|
|
756
|
+
|
|
757
|
+
A failing assertion that compared a LOCATOR COUNT (`expect(await locator.count()).toBe(n)` or
|
|
758
|
+
`expect(locator).toHaveCount(n)`), with the locator the trace recorded and, when the failure
|
|
759
|
+
snapshot can supply them, the elements it matched.
|
|
760
|
+
|
|
761
|
+
xr-molecule-flash-m321's `comments.spec.ts:86` sat on `Expected: 3 / Received: 4` for ~30
|
|
762
|
+
minutes and three whole suite walks: the helper counted `.card:not(.comment-form) .card-block`,
|
|
763
|
+
the app's comment form was a `.comment-form` wrapper around a plain `.card`, and the form's own
|
|
764
|
+
`.card-block` made the fourth. The trace had the selector (its `Frame.queryCount` entry) and the
|
|
765
|
+
four elements (its failure snapshot); the note printed neither. This reading prints both, and
|
|
766
|
+
fails closed — a selector outside the browser's own CSS support, a missing browser, or a replay
|
|
767
|
+
that disagrees with the recorded count names no elements rather than inventing them.
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
interface TraceCountMismatch {
|
|
771
|
+
/** The locator as the trace or the assertion printed it (`locator('.card:not(…')`). */
|
|
772
|
+
locator: string
|
|
773
|
+
/** The selector inside that locator, when the trace recorded one. */
|
|
774
|
+
selector?: string
|
|
775
|
+
/** The number the assertion expected. */
|
|
776
|
+
expected: number
|
|
777
|
+
/** The number the page matched. */
|
|
778
|
+
received: number
|
|
779
|
+
/**
|
|
780
|
+
* Where the locator came from: the assertion's own message, or the `locator.count()` entry the
|
|
781
|
+
* trace recorded just before the failing assertion.
|
|
782
|
+
*/
|
|
783
|
+
source: 'assertion' | 'locator-count'
|
|
784
|
+
/** The matched elements, bounded to {@link MAX_COUNT_MATCHES} in document order. */
|
|
785
|
+
matched?: TraceCountMatch[]
|
|
786
|
+
/** How many elements matched in total, before the display bound. */
|
|
787
|
+
matchedTotal?: number
|
|
788
|
+
/**
|
|
789
|
+
* How the elements were read: Playwright's own `__playwright_target__` marks in the failure
|
|
790
|
+
* snapshot, or the recorded selector replayed against that snapshot in a browser.
|
|
791
|
+
*/
|
|
792
|
+
matchedFrom?: 'snapshot-targets' | 'selector-replay'
|
|
793
|
+
/**
|
|
794
|
+
* Why no elements are named, when they are not. `unsupported-selector` is a selector the
|
|
795
|
+
* browser's `querySelectorAll` rejects (a `getByRole`/`getByText` engine, `>>` chaining);
|
|
796
|
+
* `no-snapshot` is a trace with no readable DOM snapshot at the failure; `count-mismatch` is a
|
|
797
|
+
* replay that disagreed with the recorded count, so the snapshot is not evidence for this
|
|
798
|
+
* assertion; `browser-unavailable` is a project whose Playwright module or recorded engine
|
|
799
|
+
* executable could not be resolved, so the selector could not be replayed at all.
|
|
800
|
+
*/
|
|
801
|
+
unreadable?: 'unsupported-selector' | 'no-snapshot' | 'count-mismatch' | 'browser-unavailable'
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
708
805
|
#### `TraceArtifact`
|
|
709
806
|
|
|
710
807
|
What one trace says: the test it belongs to, the requests it made, and its console lines.
|
|
@@ -801,6 +898,13 @@ interface TraceArtifact {
|
|
|
801
898
|
* leaving the run to probe computed styles (xr-molecule-flash-m197).
|
|
802
899
|
*/
|
|
803
900
|
hiddenBox?: TraceHiddenBox
|
|
901
|
+
/**
|
|
902
|
+
* A failing assertion that compared a locator count, with the locator the trace recorded and the
|
|
903
|
+
* elements its failure snapshot matched ({@link TraceCountMismatch}). The row prints them, so a
|
|
904
|
+
* count that is off by one names the extra element instead of leaving the run to probe for it
|
|
905
|
+
* (xr-molecule-flash-m321's 30-minute `Expected: 3 / Received: 4`).
|
|
906
|
+
*/
|
|
907
|
+
countMismatch?: TraceCountMismatch
|
|
804
908
|
/**
|
|
805
909
|
* The method+path groups this test's page sent {@link TRACE_NOTE_LOOP_REPEATS} times or more,
|
|
806
910
|
* most-repeated first — a render loop the note leads with (xr-molecule-flash-m215: 2,106
|
|
@@ -1588,6 +1692,32 @@ function commandSegments(command: string): CommandSegment[]
|
|
|
1588
1692
|
|
|
1589
1693
|
**Returns:** One entry per statement or pipeline stage that runs something, in order.
|
|
1590
1694
|
|
|
1695
|
+
#### `executedCommands(command)`
|
|
1696
|
+
|
|
1697
|
+
Every command a shell command actually executes, at any depth — the statements themselves, the
|
|
1698
|
+
body of a `sh -c '<command>'`, and the body of every command substitution (`$(…)`, backticks).
|
|
1699
|
+
|
|
1700
|
+
This is the reading a policy that asks "does this command run X" needs, and it is deliberately
|
|
1701
|
+
not a text match. The command-position parser already knows what runs and what is only quoted
|
|
1702
|
+
data ({@link commandSegments}), so a pattern never reads a `|` inside a quoted grep alternation
|
|
1703
|
+
as a pipeline: `grep -E "USER CALLS|ENV|BUTTON" app.log` prints log lines, and the `ENV` in
|
|
1704
|
+
its pattern started nothing (X0 xr-molecule-flash-m331 blocked exactly that call as an
|
|
1705
|
+
environment dump). What the shell DOES run stays visible through the same walk: a bare
|
|
1706
|
+
launcher (`ls | env`), a shell string (`bash -c 'env'`), a substitution
|
|
1707
|
+
(`echo "$(printenv)"`) and a nested one (`sh -c 'x; env'`) are all commands here.
|
|
1708
|
+
|
|
1709
|
+
`commandSegments` answers what a pipeline stage runs for a caller matching a runner; this
|
|
1710
|
+
answers with the launcher left standing when it runs alone, since that is what the shell
|
|
1711
|
+
executes then.
|
|
1712
|
+
|
|
1713
|
+
```typescript
|
|
1714
|
+
function executedCommands(command: string): ExecutedCommand[]
|
|
1715
|
+
```
|
|
1716
|
+
|
|
1717
|
+
- `command` — The shell command.
|
|
1718
|
+
|
|
1719
|
+
**Returns:** Each command the shell executes, in the order the shell reaches them.
|
|
1720
|
+
|
|
1591
1721
|
#### `locatedCommandSegments(command)`
|
|
1592
1722
|
|
|
1593
1723
|
Every statement of a command, with its own assignments and where it sits — assignment-only
|
|
@@ -2671,6 +2801,37 @@ function hiddenBoxNote(items: readonly TraceArtifact[]): string | null
|
|
|
2671
2801
|
|
|
2672
2802
|
**Returns:** The sentence, or null when no failure carries a hidden-box reading.
|
|
2673
2803
|
|
|
2804
|
+
#### `countMismatchSentence(mismatch)`
|
|
2805
|
+
|
|
2806
|
+
The row's sentence for a failing count assertion: the locator, both numbers, and the elements
|
|
2807
|
+
the failure snapshot held.
|
|
2808
|
+
|
|
2809
|
+
xr-molecule-flash-m321's `comments.spec.ts:86` printed only `Expected: 3 / Received: 4`. The
|
|
2810
|
+
helper counted `.card:not(.comment-form) .card-block`, and the app's comment form was a
|
|
2811
|
+
`.comment-form` wrapper around a plain `.card`, so the form's own `.card-block` was the fourth
|
|
2812
|
+
match. The trace held the selector and the four elements; the note held neither.
|
|
2813
|
+
|
|
2814
|
+
```typescript
|
|
2815
|
+
function countMismatchSentence(mismatch: TraceCountMismatch): string
|
|
2816
|
+
```
|
|
2817
|
+
|
|
2818
|
+
- `mismatch` — The trace's count reading ({@link TraceCountMismatch}).
|
|
2819
|
+
|
|
2820
|
+
**Returns:** The sentence.
|
|
2821
|
+
|
|
2822
|
+
#### `countMismatchNote(items)`
|
|
2823
|
+
|
|
2824
|
+
The sentence a count assertion earns when every traced failure is one — the next read before
|
|
2825
|
+
the API or the request is changed.
|
|
2826
|
+
|
|
2827
|
+
```typescript
|
|
2828
|
+
function countMismatchNote(items: readonly TraceArtifact[]): string | null
|
|
2829
|
+
```
|
|
2830
|
+
|
|
2831
|
+
- `items` — The traces {@link parseTraceArtifacts} read.
|
|
2832
|
+
|
|
2833
|
+
**Returns:** The sentence, or null when not every traced failure is a count mismatch.
|
|
2834
|
+
|
|
2674
2835
|
#### `failureTraceNote(items, target, earlierFailures, apiPrefixes, report)`
|
|
2675
2836
|
|
|
2676
2837
|
The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
@@ -3210,6 +3371,15 @@ function suiteRunPriorityLaunch(command: string, launch: string): { launch: stri
|
|
|
3210
3371
|
|
|
3211
3372
|
Check if a command is blocked for security reasons. Returns error message or null if allowed.
|
|
3212
3373
|
|
|
3374
|
+
The first check reads the commands the shell actually RUNS — the statements, a `sh -c`
|
|
3375
|
+
string's body, and every command substitution ({@link executedCommands}) — so a `|` inside a
|
|
3376
|
+
quoted grep alternation is data, not a pipeline: `grep -E "USER CALLS|ENV|BUTTON|passed|failed"
|
|
3377
|
+
app.log` prints log lines, and the `ENV` in its pattern started nothing (X0
|
|
3378
|
+
xr-molecule-flash-m331 blocked exactly that call as an environment dump, twice). What the
|
|
3379
|
+
shell DOES run stays visible through the same walk: `ls | env`, `bash -c 'env'`,
|
|
3380
|
+
`echo "$(printenv)"` and a nested `sh -c 'x; env'` are all commands, and so is `env` itself
|
|
3381
|
+
when nothing follows it.
|
|
3382
|
+
|
|
3213
3383
|
```typescript
|
|
3214
3384
|
function checkBlockedCommand(command: string): string | null
|
|
3215
3385
|
```
|
|
@@ -74,6 +74,43 @@ export declare function endsInBackground(command: string): boolean;
|
|
|
74
74
|
* @returns One entry per statement or pipeline stage that runs something, in order.
|
|
75
75
|
*/
|
|
76
76
|
export declare function commandSegments(command: string): CommandSegment[];
|
|
77
|
+
/** One command a shell command actually executes, wherever the shell runs it. */
|
|
78
|
+
export interface ExecutedCommand {
|
|
79
|
+
/**
|
|
80
|
+
* The word in command position, its directory removed (`/usr/bin/printenv` → `printenv`) — or
|
|
81
|
+
* the launcher itself when it runs alone and nothing follows it (`x | env` → `env`).
|
|
82
|
+
*/
|
|
83
|
+
program: string;
|
|
84
|
+
/**
|
|
85
|
+
* Its arguments, as written with quotes kept — the statement's own words AFTER the program,
|
|
86
|
+
* including an input redirect's path (`< /proc/self/environ`), so a reader of secret files sees
|
|
87
|
+
* the file it reads.
|
|
88
|
+
*/
|
|
89
|
+
args: readonly string[];
|
|
90
|
+
/** The program and its arguments as one line, what a pattern anchored at the program matches. */
|
|
91
|
+
text: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Every command a shell command actually executes, at any depth — the statements themselves, the
|
|
95
|
+
* body of a `sh -c '<command>'`, and the body of every command substitution (`$(…)`, backticks).
|
|
96
|
+
*
|
|
97
|
+
* This is the reading a policy that asks "does this command run X" needs, and it is deliberately
|
|
98
|
+
* not a text match. The command-position parser already knows what runs and what is only quoted
|
|
99
|
+
* data ({@link commandSegments}), so a pattern never reads a `|` inside a quoted grep alternation
|
|
100
|
+
* as a pipeline: `grep -E "USER CALLS|ENV|BUTTON" app.log` prints log lines, and the `ENV` in
|
|
101
|
+
* its pattern started nothing (X0 xr-molecule-flash-m331 blocked exactly that call as an
|
|
102
|
+
* environment dump). What the shell DOES run stays visible through the same walk: a bare
|
|
103
|
+
* launcher (`ls | env`), a shell string (`bash -c 'env'`), a substitution
|
|
104
|
+
* (`echo "$(printenv)"`) and a nested one (`sh -c 'x; env'`) are all commands here.
|
|
105
|
+
*
|
|
106
|
+
* `commandSegments` answers what a pipeline stage runs for a caller matching a runner; this
|
|
107
|
+
* answers with the launcher left standing when it runs alone, since that is what the shell
|
|
108
|
+
* executes then.
|
|
109
|
+
*
|
|
110
|
+
* @param command - The shell command.
|
|
111
|
+
* @returns Each command the shell executes, in the order the shell reaches them.
|
|
112
|
+
*/
|
|
113
|
+
export declare function executedCommands(command: string): ExecutedCommand[];
|
|
77
114
|
/**
|
|
78
115
|
* One statement of a shell command, with where it sits and the variables it sets for itself.
|
|
79
116
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;
|
|
1
|
+
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAynBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AA8GD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;CACb;AAmFD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAGnE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AA4GD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|
package/dist/command-position.js
CHANGED
|
@@ -694,14 +694,23 @@ function blankHeredocBodies(command) {
|
|
|
694
694
|
* `molecule-dev/api/src/ai/supplied-tree-guard.ts` — so a script that does not classify records
|
|
695
695
|
* no runner, and every run of it reads as nothing).
|
|
696
696
|
*
|
|
697
|
+
* The walk also RECORDS the command substitutions it enters (`$(…)` and backticks), because the
|
|
698
|
+
* shell runs those too: a reader that only saw the outer statements could not tell that
|
|
699
|
+
* `echo "$(printenv)"` prints the environment ({@link executedCommands}).
|
|
700
|
+
*
|
|
697
701
|
* @param command - The shell command.
|
|
698
702
|
* @returns Each statement's text, the offset its first character sits at, and the separator that
|
|
699
703
|
* ended it (`|`, `&&`, `;`, a newline, … — '' after the last one), in order, with empty ones
|
|
700
|
-
* dropped.
|
|
704
|
+
* dropped — and the bodies of the command substitutions this level enters, as offsets.
|
|
701
705
|
*/
|
|
702
|
-
function
|
|
706
|
+
function splitCommand(command) {
|
|
703
707
|
const source = blankHeredocBodies(command);
|
|
704
708
|
const parts = [];
|
|
709
|
+
const substitutions = [];
|
|
710
|
+
/** Where each open `$(` body starts; -1 for a bare `(` opened inside one. */
|
|
711
|
+
const subOpens = [];
|
|
712
|
+
/** Where each open backtick body starts. */
|
|
713
|
+
const tickOpens = [];
|
|
705
714
|
let buffer = '';
|
|
706
715
|
/** The offset in the command of the current buffer's first character. */
|
|
707
716
|
let bufferStart = 0;
|
|
@@ -739,6 +748,32 @@ function splitSegments(command) {
|
|
|
739
748
|
stack.pop();
|
|
740
749
|
continue;
|
|
741
750
|
}
|
|
751
|
+
// Backticks open a command substitution wherever one runs — outside quotes, inside double
|
|
752
|
+
// quotes, and inside another substitution or a parameter expansion — but never inside single
|
|
753
|
+
// quotes (handled above). Its body is a command of its own: nothing in it splits THIS command,
|
|
754
|
+
// and a `'` or `"` in it is the nested command's, not this one's.
|
|
755
|
+
if (top !== 'tick' && char === '`') {
|
|
756
|
+
stack.push('tick');
|
|
757
|
+
tickOpens.push(i + 1);
|
|
758
|
+
put(char, -1);
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (top === 'tick') {
|
|
762
|
+
if (char === '\\' && next !== undefined) {
|
|
763
|
+
// An escaped character is literal: `\`` does not close the substitution.
|
|
764
|
+
put(char + next, -1);
|
|
765
|
+
i += 1;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (char === '`') {
|
|
769
|
+
stack.pop();
|
|
770
|
+
const opened = tickOpens.pop();
|
|
771
|
+
if (opened !== undefined)
|
|
772
|
+
substitutions.push({ start: opened, end: i });
|
|
773
|
+
}
|
|
774
|
+
put(char, -1);
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
742
777
|
// Comments are shell syntax only in a command context, including an unquoted `$(`.
|
|
743
778
|
// Blank their bytes so quotes/operators cannot affect this walk, while every later
|
|
744
779
|
// position and depth index stays aligned with the original command. Keep the newline.
|
|
@@ -753,6 +788,7 @@ function splitSegments(command) {
|
|
|
753
788
|
// inside single quotes (handled above). The `(` is consumed here so the depth is counted once.
|
|
754
789
|
if (char === '$' && next === '(') {
|
|
755
790
|
stack.push('sub');
|
|
791
|
+
subOpens.push(i + 2);
|
|
756
792
|
put('$(', -1);
|
|
757
793
|
i += 1;
|
|
758
794
|
continue;
|
|
@@ -802,10 +838,16 @@ function splitSegments(command) {
|
|
|
802
838
|
// Inside a substitution or a subshell of one: its own parentheses nest, its `|`/`;`/`&&`
|
|
803
839
|
// belong to the nested command, and its newlines are that command's too.
|
|
804
840
|
if (top === 'sub') {
|
|
805
|
-
if (char === '(')
|
|
841
|
+
if (char === '(') {
|
|
806
842
|
stack.push('sub');
|
|
807
|
-
|
|
843
|
+
subOpens.push(-1);
|
|
844
|
+
}
|
|
845
|
+
else if (char === ')') {
|
|
808
846
|
stack.pop();
|
|
847
|
+
const opened = subOpens.pop();
|
|
848
|
+
if (opened !== undefined && opened >= 0)
|
|
849
|
+
substitutions.push({ start: opened, end: i });
|
|
850
|
+
}
|
|
809
851
|
put(char, -1);
|
|
810
852
|
continue;
|
|
811
853
|
}
|
|
@@ -861,7 +903,16 @@ function splitSegments(command) {
|
|
|
861
903
|
});
|
|
862
904
|
lastAfter = part.after;
|
|
863
905
|
}
|
|
864
|
-
return out;
|
|
906
|
+
return { parts: out, substitutions };
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* A command's statements and pipeline stages — {@link splitCommand}'s parts on their own.
|
|
910
|
+
*
|
|
911
|
+
* @param command - The shell command.
|
|
912
|
+
* @returns Each statement, in order, with empty ones dropped.
|
|
913
|
+
*/
|
|
914
|
+
function splitSegments(command) {
|
|
915
|
+
return splitCommand(command).parts;
|
|
865
916
|
}
|
|
866
917
|
/**
|
|
867
918
|
* Whether the outer shell's final statement starts a background job.
|
|
@@ -983,6 +1034,114 @@ function leadingAssignments(text) {
|
|
|
983
1034
|
export function commandSegments(command) {
|
|
984
1035
|
return locatedSegmentsOf(command, 0).flatMap((located) => located.segment === null ? [] : [located.segment]);
|
|
985
1036
|
}
|
|
1037
|
+
/**
|
|
1038
|
+
* A statement's words with its redirections taken out, so the walk that finds the program does
|
|
1039
|
+
* not read a redirect's target as the program: `env > out.txt` RUNS `env`, and `cat > f` runs
|
|
1040
|
+
* `cat` with no file argument. Each word kept carries its index in `words`, because a caller
|
|
1041
|
+
* reading the statement's ARGUMENTS wants it as written — an input redirect's path is one.
|
|
1042
|
+
*
|
|
1043
|
+
* @param words - One statement's words, quotes kept.
|
|
1044
|
+
* @returns The words that are not redirections, and each one's index in `words`.
|
|
1045
|
+
*/
|
|
1046
|
+
function withoutRedirects(words) {
|
|
1047
|
+
const kept = [];
|
|
1048
|
+
const index = [];
|
|
1049
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
1050
|
+
const word = words[i] ?? '';
|
|
1051
|
+
// An operator written on its own owns the word after it (`> out.txt`, `< /proc/self/environ`).
|
|
1052
|
+
if (REDIRECT_OPERATOR.test(word)) {
|
|
1053
|
+
i += 1;
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
// An operator with its target attached (`2>&1`, `>out.txt`, `&>log`) is one word.
|
|
1057
|
+
if (REDIRECT_WORD.test(word))
|
|
1058
|
+
continue;
|
|
1059
|
+
kept.push(word);
|
|
1060
|
+
index.push(i);
|
|
1061
|
+
}
|
|
1062
|
+
return { words: kept, index };
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* The index of the last launcher a statement names (`x | env`, `sudo nice`), or null when it
|
|
1066
|
+
* names none.
|
|
1067
|
+
*
|
|
1068
|
+
* A statement that only NAMES a launcher runs that launcher: `x | env` pipes x's output into
|
|
1069
|
+
* `env`, which prints the environment, and the walk that reports what a command runs has to say
|
|
1070
|
+
* so ({@link executedCommands}).
|
|
1071
|
+
*
|
|
1072
|
+
* @param words - The statement's words, redirections already taken out.
|
|
1073
|
+
* @returns The launcher's index, or null.
|
|
1074
|
+
*/
|
|
1075
|
+
function lastLauncherIndex(words) {
|
|
1076
|
+
for (let i = words.length - 1; i >= 0; i -= 1) {
|
|
1077
|
+
if (LAUNCHERS.has(programName(words[i] ?? '')))
|
|
1078
|
+
return i;
|
|
1079
|
+
}
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* {@link executedCommands}'s recursion: a statement's own `sh -c '<command>'`, and every command
|
|
1084
|
+
* substitution it carries, is a command of its own.
|
|
1085
|
+
*
|
|
1086
|
+
* @param command - The shell command, or one nested command string.
|
|
1087
|
+
* @param depth - How many nested command strings this one already sits inside.
|
|
1088
|
+
* @returns Each command the shell executes, in order.
|
|
1089
|
+
*/
|
|
1090
|
+
function executedCommandsOf(command, depth) {
|
|
1091
|
+
if (typeof command !== 'string' || depth > MAX_NESTING)
|
|
1092
|
+
return [];
|
|
1093
|
+
const out = [];
|
|
1094
|
+
const { parts, substitutions } = splitCommand(command);
|
|
1095
|
+
for (const part of parts) {
|
|
1096
|
+
const words = segmentWords(part.text);
|
|
1097
|
+
const placed = withoutRedirects(words);
|
|
1098
|
+
const start = commandStart(placed.words);
|
|
1099
|
+
// A shell's `-c '<command>'` is the command: what the shell itself does is run it.
|
|
1100
|
+
if (start.nested !== null) {
|
|
1101
|
+
out.push(...executedCommandsOf(start.nested, depth + 1));
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
const at = placed.words[start.at] === undefined ? lastLauncherIndex(placed.words) : start.at;
|
|
1105
|
+
if (at === null)
|
|
1106
|
+
continue;
|
|
1107
|
+
const program = start.program ?? programName(placed.words[at] ?? '');
|
|
1108
|
+
if (!program)
|
|
1109
|
+
continue;
|
|
1110
|
+
// Arguments from the statement as written, from the word after the program: `cat < /proc/…`
|
|
1111
|
+
// reads that file, and a reader of secret files has to see it.
|
|
1112
|
+
const args = words.slice((placed.index[at] ?? 0) + 1);
|
|
1113
|
+
out.push({ program, args, text: [program, ...args].join(' ') });
|
|
1114
|
+
}
|
|
1115
|
+
for (const substitution of substitutions) {
|
|
1116
|
+
out.push(...executedCommandsOf(command.slice(substitution.start, substitution.end), depth + 1));
|
|
1117
|
+
}
|
|
1118
|
+
return out;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Every command a shell command actually executes, at any depth — the statements themselves, the
|
|
1122
|
+
* body of a `sh -c '<command>'`, and the body of every command substitution (`$(…)`, backticks).
|
|
1123
|
+
*
|
|
1124
|
+
* This is the reading a policy that asks "does this command run X" needs, and it is deliberately
|
|
1125
|
+
* not a text match. The command-position parser already knows what runs and what is only quoted
|
|
1126
|
+
* data ({@link commandSegments}), so a pattern never reads a `|` inside a quoted grep alternation
|
|
1127
|
+
* as a pipeline: `grep -E "USER CALLS|ENV|BUTTON" app.log` prints log lines, and the `ENV` in
|
|
1128
|
+
* its pattern started nothing (X0 xr-molecule-flash-m331 blocked exactly that call as an
|
|
1129
|
+
* environment dump). What the shell DOES run stays visible through the same walk: a bare
|
|
1130
|
+
* launcher (`ls | env`), a shell string (`bash -c 'env'`), a substitution
|
|
1131
|
+
* (`echo "$(printenv)"`) and a nested one (`sh -c 'x; env'`) are all commands here.
|
|
1132
|
+
*
|
|
1133
|
+
* `commandSegments` answers what a pipeline stage runs for a caller matching a runner; this
|
|
1134
|
+
* answers with the launcher left standing when it runs alone, since that is what the shell
|
|
1135
|
+
* executes then.
|
|
1136
|
+
*
|
|
1137
|
+
* @param command - The shell command.
|
|
1138
|
+
* @returns Each command the shell executes, in the order the shell reaches them.
|
|
1139
|
+
*/
|
|
1140
|
+
export function executedCommands(command) {
|
|
1141
|
+
if (typeof command !== 'string' || !command.trim())
|
|
1142
|
+
return [];
|
|
1143
|
+
return executedCommandsOf(command, 0);
|
|
1144
|
+
}
|
|
986
1145
|
/**
|
|
987
1146
|
* Where a statement's word sits in its own raw text, as {@link segmentWords} counts the words.
|
|
988
1147
|
*
|
package/dist/cut-run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AAiBD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAkDD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAIf;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK;AACf,sEAAsE;AACpE,aAAa;AACf,oDAAoD;GAClD,iBAAiB;AACnB;;;;GAIG;GACD,cAAc,CAAA;AAElB,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAMH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AAiBD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAkDD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAIf;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK;AACf,sEAAsE;AACpE,aAAa;AACf,oDAAoD;GAClD,iBAAiB;AACnB;;;;GAIG;GACD,cAAc,CAAA;AAElB,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAgGf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmB1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAyClD"}
|
package/dist/cut-run.js
CHANGED
|
@@ -345,12 +345,16 @@ export function commandCutRun(command, result, elapsedMs, budgetSeconds = 0, raw
|
|
|
345
345
|
inlineScript,
|
|
346
346
|
};
|
|
347
347
|
}
|
|
348
|
-
//
|
|
349
|
-
// bound above fired: the process was stopped from
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
|
|
348
|
+
// A runner's summary that does not account for the total it announced, in a command that
|
|
349
|
+
// actually RUNS that runner, and neither bound above fired: the process was stopped from
|
|
350
|
+
// outside the run. The command must run the runner — a reader of a LIVE run's log prints the
|
|
351
|
+
// same announcement and partial tally as text it copies, and reporting that as a stopped run
|
|
352
|
+
// told the executor a suite it had only grepped had been killed (X0 xr-molecule-flash-m345,
|
|
353
|
+
// +50:00: `grep -B2 -A12 … /tmp/mol-bg-….log | head -40` came back `stoppedBy: process-stop`
|
|
354
|
+
// after 1 s). A run that used the whole budget is the backend's deadline (which sits just past
|
|
355
|
+
// it); anything else is a stop this tool does not own. Either way the summary counts are all
|
|
356
|
+
// that ran, and a pipeline's 0 is not the run's code — the run never reached its own verdict.
|
|
357
|
+
if (suite && summary !== null && summary.announced !== null) {
|
|
354
358
|
const usedBudget = budgetSeconds > 0 && elapsedMs >= atBoundMs(budgetSeconds);
|
|
355
359
|
return {
|
|
356
360
|
seconds: usedBudget ? budgetSeconds : Math.max(1, Math.round(elapsedMs / 1000)),
|
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
* 401`. So the platform reads it (bounded, per test) and puts it in the run's result
|
|
19
19
|
* ({@link failureTraceNote}), instead of leaving the executor to build its own browser to find out.
|
|
20
20
|
*
|
|
21
|
-
* Everything here is text in, text out: the sandbox runs {@link traceArtifactsScript} (Node
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* Everything here is text in, text out: the sandbox runs {@link traceArtifactsScript} (plain Node —
|
|
22
|
+
* the zip is read without a dependency) and prints one `TRACE <json>` line per trace; the platform
|
|
23
|
+
* parses those lines ({@link parseTraceArtifacts}) and writes the note ({@link failureTraceNote}).
|
|
24
|
+
* The one exception is the selector replay behind a `locator.count()` reading: it resolves the
|
|
25
|
+
* project's own installed Playwright, replays the recorded selector against the recorded snapshot
|
|
26
|
+
* in that browser's DOM, and reports `browser-unavailable` (naming no elements) when it cannot.
|
|
27
|
+
* Both halves are covered by a test that runs the script against a `trace.zip` produced by a real
|
|
28
|
+
* Playwright run, in the sandbox's own shape.
|
|
26
29
|
*
|
|
27
30
|
* @module
|
|
28
31
|
*/
|
|
@@ -263,6 +266,73 @@ export interface TraceHiddenBox {
|
|
|
263
266
|
/** True when the element has no text and no element children at all. */
|
|
264
267
|
empty?: true;
|
|
265
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* One element a failing locator count matched, read from the trace's own failure snapshot.
|
|
271
|
+
*
|
|
272
|
+
* A count assertion reports only `Expected: 3 / Received: 4`; the element that made it 4 is not
|
|
273
|
+
* in the error. Playwright's trace does hold it — either marked `__playwright_target__` in the
|
|
274
|
+
* failure snapshot (`expect(locator).toHaveCount(n)`), or recoverable by replaying the recorded
|
|
275
|
+
* selector against the snapshot's DOM in a browser (`locator.count()` then `expect(n).toBe(k)`) —
|
|
276
|
+
* so each match is named by what it is and where it sits, never by a guess.
|
|
277
|
+
*/
|
|
278
|
+
export interface TraceCountMatch {
|
|
279
|
+
/** The element's tag, lowercased (`div`). */
|
|
280
|
+
tag: string;
|
|
281
|
+
/** Its `class` attribute, bounded (`card-block`). */
|
|
282
|
+
classes?: string;
|
|
283
|
+
/** Its own text, whitespace-collapsed and bounded. */
|
|
284
|
+
text?: string;
|
|
285
|
+
/** Its ancestors, outermost first (`body > div.comment-form > div.card`), bounded. */
|
|
286
|
+
path?: string;
|
|
287
|
+
/** True when a `<form>` is one of its ancestors — the m321 extra match's shape. */
|
|
288
|
+
inForm?: true;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A failing assertion that compared a LOCATOR COUNT (`expect(await locator.count()).toBe(n)` or
|
|
292
|
+
* `expect(locator).toHaveCount(n)`), with the locator the trace recorded and, when the failure
|
|
293
|
+
* snapshot can supply them, the elements it matched.
|
|
294
|
+
*
|
|
295
|
+
* xr-molecule-flash-m321's `comments.spec.ts:86` sat on `Expected: 3 / Received: 4` for ~30
|
|
296
|
+
* minutes and three whole suite walks: the helper counted `.card:not(.comment-form) .card-block`,
|
|
297
|
+
* the app's comment form was a `.comment-form` wrapper around a plain `.card`, and the form's own
|
|
298
|
+
* `.card-block` made the fourth. The trace had the selector (its `Frame.queryCount` entry) and the
|
|
299
|
+
* four elements (its failure snapshot); the note printed neither. This reading prints both, and
|
|
300
|
+
* fails closed — a selector outside the browser's own CSS support, a missing browser, or a replay
|
|
301
|
+
* that disagrees with the recorded count names no elements rather than inventing them.
|
|
302
|
+
*/
|
|
303
|
+
export interface TraceCountMismatch {
|
|
304
|
+
/** The locator as the trace or the assertion printed it (`locator('.card:not(…')`). */
|
|
305
|
+
locator: string;
|
|
306
|
+
/** The selector inside that locator, when the trace recorded one. */
|
|
307
|
+
selector?: string;
|
|
308
|
+
/** The number the assertion expected. */
|
|
309
|
+
expected: number;
|
|
310
|
+
/** The number the page matched. */
|
|
311
|
+
received: number;
|
|
312
|
+
/**
|
|
313
|
+
* Where the locator came from: the assertion's own message, or the `locator.count()` entry the
|
|
314
|
+
* trace recorded just before the failing assertion.
|
|
315
|
+
*/
|
|
316
|
+
source: 'assertion' | 'locator-count';
|
|
317
|
+
/** The matched elements, bounded to {@link MAX_COUNT_MATCHES} in document order. */
|
|
318
|
+
matched?: TraceCountMatch[];
|
|
319
|
+
/** How many elements matched in total, before the display bound. */
|
|
320
|
+
matchedTotal?: number;
|
|
321
|
+
/**
|
|
322
|
+
* How the elements were read: Playwright's own `__playwright_target__` marks in the failure
|
|
323
|
+
* snapshot, or the recorded selector replayed against that snapshot in a browser.
|
|
324
|
+
*/
|
|
325
|
+
matchedFrom?: 'snapshot-targets' | 'selector-replay';
|
|
326
|
+
/**
|
|
327
|
+
* Why no elements are named, when they are not. `unsupported-selector` is a selector the
|
|
328
|
+
* browser's `querySelectorAll` rejects (a `getByRole`/`getByText` engine, `>>` chaining);
|
|
329
|
+
* `no-snapshot` is a trace with no readable DOM snapshot at the failure; `count-mismatch` is a
|
|
330
|
+
* replay that disagreed with the recorded count, so the snapshot is not evidence for this
|
|
331
|
+
* assertion; `browser-unavailable` is a project whose Playwright module or recorded engine
|
|
332
|
+
* executable could not be resolved, so the selector could not be replayed at all.
|
|
333
|
+
*/
|
|
334
|
+
unreadable?: 'unsupported-selector' | 'no-snapshot' | 'count-mismatch' | 'browser-unavailable';
|
|
335
|
+
}
|
|
266
336
|
/**
|
|
267
337
|
* One method+path a test's page sent many times — the trace of a render loop.
|
|
268
338
|
*
|
|
@@ -376,6 +446,13 @@ export interface TraceArtifact {
|
|
|
376
446
|
* leaving the run to probe computed styles (xr-molecule-flash-m197).
|
|
377
447
|
*/
|
|
378
448
|
hiddenBox?: TraceHiddenBox;
|
|
449
|
+
/**
|
|
450
|
+
* A failing assertion that compared a locator count, with the locator the trace recorded and the
|
|
451
|
+
* elements its failure snapshot matched ({@link TraceCountMismatch}). The row prints them, so a
|
|
452
|
+
* count that is off by one names the extra element instead of leaving the run to probe for it
|
|
453
|
+
* (xr-molecule-flash-m321's 30-minute `Expected: 3 / Received: 4`).
|
|
454
|
+
*/
|
|
455
|
+
countMismatch?: TraceCountMismatch;
|
|
379
456
|
/**
|
|
380
457
|
* The method+path groups this test's page sent {@link TRACE_NOTE_LOOP_REPEATS} times or more,
|
|
381
458
|
* most-repeated first — a render loop the note leads with (xr-molecule-flash-m215: 2,106
|
|
@@ -601,6 +678,27 @@ export declare function isCredentialExchangePath(path: string, authEndpoints?: r
|
|
|
601
678
|
* @returns The sentence, or null when no failure carries a hidden-box reading.
|
|
602
679
|
*/
|
|
603
680
|
export declare function hiddenBoxNote(items: readonly TraceArtifact[]): string | null;
|
|
681
|
+
/**
|
|
682
|
+
* The row's sentence for a failing count assertion: the locator, both numbers, and the elements
|
|
683
|
+
* the failure snapshot held.
|
|
684
|
+
*
|
|
685
|
+
* xr-molecule-flash-m321's `comments.spec.ts:86` printed only `Expected: 3 / Received: 4`. The
|
|
686
|
+
* helper counted `.card:not(.comment-form) .card-block`, and the app's comment form was a
|
|
687
|
+
* `.comment-form` wrapper around a plain `.card`, so the form's own `.card-block` was the fourth
|
|
688
|
+
* match. The trace held the selector and the four elements; the note held neither.
|
|
689
|
+
*
|
|
690
|
+
* @param mismatch - The trace's count reading ({@link TraceCountMismatch}).
|
|
691
|
+
* @returns The sentence.
|
|
692
|
+
*/
|
|
693
|
+
export declare function countMismatchSentence(mismatch: TraceCountMismatch): string;
|
|
694
|
+
/**
|
|
695
|
+
* The sentence a count assertion earns when every traced failure is one — the next read before
|
|
696
|
+
* the API or the request is changed.
|
|
697
|
+
*
|
|
698
|
+
* @param items - The traces {@link parseTraceArtifacts} read.
|
|
699
|
+
* @returns The sentence, or null when not every traced failure is a count mismatch.
|
|
700
|
+
*/
|
|
701
|
+
export declare function countMismatchNote(items: readonly TraceArtifact[]): string | null;
|
|
604
702
|
/**
|
|
605
703
|
* The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
606
704
|
* what came back.
|