@molecule/api-ai-tools 1.8.3 → 1.9.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 +240 -14
- package/dist/failure-kind.d.ts +66 -0
- package/dist/failure-kind.d.ts.map +1 -0
- package/dist/failure-kind.js +134 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/suite-overlap.d.ts +35 -1
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +57 -2
- package/dist/trace-artifacts.d.ts +77 -7
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +320 -28
- package/dist/utilities.d.ts +25 -4
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +127 -13
- 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-17T07:58:25.504Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -197,6 +197,23 @@ interface CutRun {
|
|
|
197
197
|
}
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
+
#### `RunFailureClass`
|
|
201
|
+
|
|
202
|
+
One failure a runner printed, classified by the layer that failed.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
interface RunFailureClass {
|
|
206
|
+
/** The layer that failed. */
|
|
207
|
+
kind: RunFailureKind
|
|
208
|
+
/** For a `connection` failure, what the transport did. */
|
|
209
|
+
connection?: ConnectionFailureKind
|
|
210
|
+
/** How long the request waited before the transport gave up, in seconds, when printed. */
|
|
211
|
+
seconds?: number
|
|
212
|
+
/** The `file:line` the runner printed for the failure, when it printed one. */
|
|
213
|
+
at?: string
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
200
217
|
#### `NormalizableSchema`
|
|
201
218
|
|
|
202
219
|
The JSON-Schema-ish shape every tool in this package declares.
|
|
@@ -313,8 +330,43 @@ interface TraceRequest {
|
|
|
313
330
|
method: string
|
|
314
331
|
/** The request's path (`/api/articles/x/favorite`), or the whole URL when it is cross-origin. */
|
|
315
332
|
path: string
|
|
316
|
-
/**
|
|
333
|
+
/**
|
|
334
|
+
* The response status as the trace recorded it. Playwright writes `-1` when a request was never
|
|
335
|
+
* answered (its HAR entry starts at `-1` and only a response event overwrites it); `0` is the
|
|
336
|
+
* same absence when the snapshot carried no response at all. Neither is a server failure.
|
|
337
|
+
*/
|
|
317
338
|
status: number
|
|
339
|
+
/**
|
|
340
|
+
* Playwright's own error text for a request that failed WITHOUT a response
|
|
341
|
+
* (`net::ERR_CONNECTION_REFUSED`) — a real failure. A request that got no response and recorded
|
|
342
|
+
* no text was aborted or still in flight when the test ended (the page closed under it), so it
|
|
343
|
+
* says nothing about the server (xr-molecule-flash-m112 +30:45).
|
|
344
|
+
*/
|
|
345
|
+
failure?: string
|
|
346
|
+
/**
|
|
347
|
+
* What the page SENT, for a 4xx answered by the app's own API — bounded (see
|
|
348
|
+
* {@link MAX_TRACED_BODY_CHARS}). A body wrapped as `{"data":{…}}` is visible here in one line:
|
|
349
|
+
* app-http's verbs take the body as their second argument, so a helper that passes
|
|
350
|
+
* `{ data: body }` there ships the wrapper (xr-molecule-flash-m111 +11:36).
|
|
351
|
+
*/
|
|
352
|
+
sent?: string
|
|
353
|
+
/** What came back for that 4xx, bounded the same way — the API's own error body. */
|
|
354
|
+
got?: string
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### `TraceAction`
|
|
359
|
+
|
|
360
|
+
One action the traced page performed, with the trace's own timing for it.
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
interface TraceAction {
|
|
364
|
+
/** The Playwright API the entry names: `Frame.goto`, `Locator.fill`, `Locator.click`. */
|
|
365
|
+
api: string
|
|
366
|
+
/** When the action started, on the trace's own monotonic clock (ms). */
|
|
367
|
+
start: number
|
|
368
|
+
/** When it finished, or null when the run ended before it did. */
|
|
369
|
+
end: number | null
|
|
318
370
|
}
|
|
319
371
|
```
|
|
320
372
|
|
|
@@ -332,6 +384,12 @@ interface TraceArtifact {
|
|
|
332
384
|
requests: TraceRequest[]
|
|
333
385
|
/** The console errors and page errors the page raised. */
|
|
334
386
|
console: string[]
|
|
387
|
+
/**
|
|
388
|
+
* The page's own actions in the order the trace recorded them, bounded — what a test that ran
|
|
389
|
+
* out of a per-test budget spends its time on. A test that repeats one action prints it once per
|
|
390
|
+
* repeat, so the gap between two starts is one repeat's measured cost.
|
|
391
|
+
*/
|
|
392
|
+
actions: TraceAction[]
|
|
335
393
|
}
|
|
336
394
|
```
|
|
337
395
|
|
|
@@ -685,6 +743,20 @@ interface FailureCause {
|
|
|
685
743
|
* for a duration group, whose blocks carried no bodies.
|
|
686
744
|
*/
|
|
687
745
|
evidence: string[]
|
|
746
|
+
/**
|
|
747
|
+
* The suite's own per-test budget in ms, when the cause is the test-budget kill
|
|
748
|
+
* (`Test timeout of 15000ms exceeded`). The signature normalizes the number away
|
|
749
|
+
* ({@link normalizedSignature}) so equal causes group, and that number is the one fact that
|
|
750
|
+
* predicts a test spending its budget on a repeated action (xr-molecule-flash-m110 +20:44).
|
|
751
|
+
*/
|
|
752
|
+
budgetMs?: number
|
|
753
|
+
/**
|
|
754
|
+
* The layer the failure came from ({@link classifyRunFailure}). A `connection` cause never
|
|
755
|
+
* reached the app, so it is NOT a cause the app's code can fix: the note says to re-run it
|
|
756
|
+
* once and read the app's log on a repeat, instead of sending a fix at a handler
|
|
757
|
+
* (xr-molecule-flash-m110 +18:42 rewrote a CORS middleware for a 20 s connection timeout).
|
|
758
|
+
*/
|
|
759
|
+
kind: RunFailureKind
|
|
688
760
|
}
|
|
689
761
|
```
|
|
690
762
|
|
|
@@ -716,6 +788,23 @@ type CutBy =
|
|
|
716
788
|
| 'process-stop'
|
|
717
789
|
```
|
|
718
790
|
|
|
791
|
+
#### `RunFailureKind`
|
|
792
|
+
|
|
793
|
+
The layer a failure came from: the transport (no answer at all), the response's status, or an
|
|
794
|
+
assertion over a response that arrived.
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
797
|
+
type RunFailureKind = 'connection' | 'status' | 'assertion'
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
#### `ConnectionFailureKind`
|
|
801
|
+
|
|
802
|
+
What the transport did when a request never got an answer.
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
type ConnectionFailureKind = 'timeout' | 'refused' | 'reset' | 'dns' | 'tls' | 'empty'
|
|
806
|
+
```
|
|
807
|
+
|
|
719
808
|
#### `DiscoveredSkill` _(deprecated)_
|
|
720
809
|
|
|
721
810
|
Re-export SkillEntry as DiscoveredSkill for backwards compatibility.
|
|
@@ -1001,6 +1090,36 @@ function cutRunSentence(run: CutRun): string
|
|
|
1001
1090
|
|
|
1002
1091
|
**Returns:** The sentence.
|
|
1003
1092
|
|
|
1093
|
+
#### `classifyRunFailure(text)`
|
|
1094
|
+
|
|
1095
|
+
Classify ONE failure's text — a Hurl `error:` block, or a Playwright failure message/body.
|
|
1096
|
+
|
|
1097
|
+
The transport is read first: a block that carries a connection error AND an assertion is a
|
|
1098
|
+
request whose assertion never ran, so it is a connection failure. Only a block that carries no
|
|
1099
|
+
failure signature at all answers null — unknown is never a connection failure, because a
|
|
1100
|
+
misread transport hides a real app failure behind "just re-run it".
|
|
1101
|
+
|
|
1102
|
+
```typescript
|
|
1103
|
+
function classifyRunFailure(text: string): RunFailureClass | null
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
- `text` — One failure's text, as the runner printed it.
|
|
1107
|
+
|
|
1108
|
+
**Returns:** The classification, or null when the text carries no failure this reads.
|
|
1109
|
+
|
|
1110
|
+
#### `allConnectionFailures(failures)`
|
|
1111
|
+
|
|
1112
|
+
Whether every one of the classified failures is a connection failure — the only shape in which
|
|
1113
|
+
no response the app produced was measured.
|
|
1114
|
+
|
|
1115
|
+
```typescript
|
|
1116
|
+
function allConnectionFailures(failures: readonly (RunFailureClass | null)[]): boolean
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
- `failures` — The run's classifications, in printed order.
|
|
1120
|
+
|
|
1121
|
+
**Returns:** True when there is at least one and none of them is the app's.
|
|
1122
|
+
|
|
1004
1123
|
#### `normalizeToolInput(schema, raw)`
|
|
1005
1124
|
|
|
1006
1125
|
Move known aliases onto the declared parameter names and report what is still
|
|
@@ -1160,24 +1279,68 @@ function suiteCommandTarget(command: string): string
|
|
|
1160
1279
|
|
|
1161
1280
|
**Returns:** The target key.
|
|
1162
1281
|
|
|
1163
|
-
#### `
|
|
1282
|
+
#### `namesSuiteConfig(command)`
|
|
1283
|
+
|
|
1284
|
+
Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
|
|
1285
|
+
{@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
|
|
1286
|
+
|
|
1287
|
+
```typescript
|
|
1288
|
+
function namesSuiteConfig(command: string): boolean
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
- `command` — A command, or the Playwright statement inside it.
|
|
1292
|
+
|
|
1293
|
+
**Returns:** True when the statement loads a config of its own.
|
|
1294
|
+
|
|
1295
|
+
#### `withSuiteConfig(command, config)`
|
|
1296
|
+
|
|
1297
|
+
The command with a `--config` added to the Playwright invocation it starts — or unchanged when
|
|
1298
|
+
it already names one, or starts no Playwright run.
|
|
1299
|
+
|
|
1300
|
+
The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
|
|
1301
|
+
it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
|
|
1302
|
+
|
|
1303
|
+
xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
|
|
1304
|
+
`--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
|
|
1305
|
+
NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
|
|
1306
|
+
that says nothing about the app. The platform composes commands back to the executor too (the
|
|
1307
|
+
narrowed re-run of a finished run's failures), and a command it hands back has to carry the
|
|
1308
|
+
config the run was measured under, never the shape that caused that finding.
|
|
1309
|
+
|
|
1310
|
+
```typescript
|
|
1311
|
+
function withSuiteConfig(command: string, config: string | null | undefined): string
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
- `command` — The command as the executor sent it.
|
|
1315
|
+
- `config` — The config the run loads, when the caller knows it.
|
|
1316
|
+
|
|
1317
|
+
**Returns:** The command with the flag, or the command unchanged.
|
|
1318
|
+
|
|
1319
|
+
#### `scopedRerunCommand(command, locations, excluded, config)`
|
|
1164
1320
|
|
|
1165
1321
|
The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
1166
1322
|
invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
1167
1323
|
suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
1168
1324
|
`--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
1169
1325
|
|
|
1326
|
+
`config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
|
|
1327
|
+
carries it whenever the command itself does not, so a run that dropped `--config` cannot be
|
|
1328
|
+
handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
|
|
1329
|
+
`Cannot navigate to invalid URL` because the re-run loaded no base URL).
|
|
1330
|
+
|
|
1170
1331
|
```typescript
|
|
1171
1332
|
function scopedRerunCommand(
|
|
1172
1333
|
command: string,
|
|
1173
1334
|
locations: readonly string[],
|
|
1174
1335
|
excluded?: readonly string[],
|
|
1336
|
+
config?: string | null,
|
|
1175
1337
|
): string
|
|
1176
1338
|
```
|
|
1177
1339
|
|
|
1178
1340
|
- `command` — The whole-suite run's command.
|
|
1179
1341
|
- `locations` — The failing `file:line`s to re-run, as the run's own log printed them — or, for a run that started outside the suite, the absolute form Playwright matches each file argument against.
|
|
1180
1342
|
- `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.
|
|
1343
|
+
- `config` — The config the run loaded, when the caller knows it; added when the command does not name one.
|
|
1181
1344
|
|
|
1182
1345
|
**Returns:** The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
1183
1346
|
|
|
@@ -1395,10 +1558,17 @@ on `unzip` would make the read fail on every sandbox that has none. The zip is r
|
|
|
1395
1558
|
central directory and `zlib.inflateRawSync` — about forty lines, and the only thing that has to
|
|
1396
1559
|
be true of a `trace.zip` is that it IS one.
|
|
1397
1560
|
|
|
1398
|
-
A request is kept over a static asset when it failed
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1561
|
+
A request is kept over a static asset when it failed — a `status >= 400`, or no response at all
|
|
1562
|
+
together with Playwright's own error text (`-1` plus `_failureText`, a refused connection) — and
|
|
1563
|
+
over anything else when it is the page talking to the app (`fetch`/`xhr`/`document`): a failing
|
|
1564
|
+
test's trace holds the whole page load. A request with no response and NO error text was aborted
|
|
1565
|
+
or still in flight when the test ended; it is kept too, but as what it is, not as a failure.
|
|
1566
|
+
|
|
1567
|
+
A request answered 4xx by the app's OWN API also keeps what the page SENT and the first of what
|
|
1568
|
+
came back: Playwright stores either inline (`text`) or in a resource entry (`_file`), and both
|
|
1569
|
+
are read. That is where a wrapped body shows: xr-molecule-flash-m111's helper passed
|
|
1570
|
+
|
|
1571
|
+
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/trace-artifacts.d.ts`.)_
|
|
1402
1572
|
|
|
1403
1573
|
```typescript
|
|
1404
1574
|
function traceArtifactsScript(): string
|
|
@@ -1421,7 +1591,7 @@ function parseTraceArtifacts(stdout: string): TraceArtifact[]
|
|
|
1421
1591
|
|
|
1422
1592
|
**Returns:** One entry per trace it printed.
|
|
1423
1593
|
|
|
1424
|
-
#### `failureTraceNote(items, target)`
|
|
1594
|
+
#### `failureTraceNote(items, target, earlierFailures)`
|
|
1425
1595
|
|
|
1426
1596
|
The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
1427
1597
|
what came back.
|
|
@@ -1432,14 +1602,34 @@ with them: read the handler the route reaches, not the selector. It is BOUNDED t
|
|
|
1432
1602
|
{@link MAX_TRACE_NOTE_TESTS} tests with {@link MAX_TRACE_NOTE_REQUESTS} requests each, because it
|
|
1433
1603
|
rides in a result the model pays for.
|
|
1434
1604
|
|
|
1605
|
+
The three kinds are kept apart, because they mean different things. A FAILED request (4xx/5xx, or
|
|
1606
|
+
a network error with Playwright's own text) is a cause. A request with NO RESPONSE RECORDED —
|
|
1607
|
+
Playwright's `-1`, no error text — was still in flight when the test ended (the page closed under
|
|
1608
|
+
it); listing it as a failure sent a run hunting an API that never answered anything wrong
|
|
1609
|
+
(xr-molecule-flash-m112 +30:45). The rest are the page's last answered requests.
|
|
1610
|
+
|
|
1611
|
+
A 4xx request carries what the page SENT beside its status, so a wrapped body is visible in one
|
|
1612
|
+
line rather than inferred from the status; when one of those bodies is an object whose only key
|
|
1613
|
+
is `data`, the note names the call shape that produces it (xr-molecule-flash-m111 +11:36).
|
|
1614
|
+
|
|
1615
|
+
WHICH rows it spends is evidence, not artifact order (xr-molecule-flash-m112 +53:46): a row
|
|
1616
|
+
needs a recorded response, a real failure or a console error ({@link traceRowEvidence}), rows are
|
|
1617
|
+
|
|
1618
|
+
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/trace-artifacts.d.ts`.)_
|
|
1619
|
+
|
|
1435
1620
|
```typescript
|
|
1436
|
-
function failureTraceNote(
|
|
1621
|
+
function failureTraceNote(
|
|
1622
|
+
items: readonly TraceArtifact[],
|
|
1623
|
+
target: string,
|
|
1624
|
+
earlierFailures?: readonly string[],
|
|
1625
|
+
): string | null
|
|
1437
1626
|
```
|
|
1438
1627
|
|
|
1439
1628
|
- `items` — The traces {@link parseTraceArtifacts} read.
|
|
1440
1629
|
- `target` — Where the run went — the deployed host, as the note names it.
|
|
1630
|
+
- `earlierFailures` — The failing `file:line`s earlier walks of this suite left, so a repeat is ranked above a one-off. Empty when the caller knows of no earlier walk.
|
|
1441
1631
|
|
|
1442
|
-
**Returns:** The note, or null when no trace held a
|
|
1632
|
+
**Returns:** The note, or null when no trace held a recorded response, a real failure or a console error.
|
|
1443
1633
|
|
|
1444
1634
|
#### `suiteRecordsTraces(configText)`
|
|
1445
1635
|
|
|
@@ -2382,17 +2572,21 @@ the caller knows how the run was started, the note also hands over the narrowed
|
|
|
2382
2572
|
exactly those tests ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the
|
|
2383
2573
|
whole suite.
|
|
2384
2574
|
|
|
2575
|
+
A run whose failures are ALL {@link NO_BASE_URL_ERROR} is not a set of causes at all: it is one
|
|
2576
|
+
configuration failure, said in one line ({@link noBaseUrlConfigNote}) rather than a list that
|
|
2577
|
+
reads as ten defects in the app.
|
|
2578
|
+
|
|
2385
2579
|
```typescript
|
|
2386
2580
|
function failureCausesNote(
|
|
2387
2581
|
log: string,
|
|
2388
2582
|
excluded?: readonly string[],
|
|
2389
|
-
run?: { command: string; cwd?: string; log?: string },
|
|
2583
|
+
run?: { command: string; cwd?: string; log?: string; config?: string | null },
|
|
2390
2584
|
): string | null
|
|
2391
2585
|
```
|
|
2392
2586
|
|
|
2393
2587
|
- `log` — The finished run's output.
|
|
2394
2588
|
- `excluded` — The test titles the request carves out, if the caller knows them.
|
|
2395
|
-
- `run` — How the run was started, when the caller knows it: the narrowed re-run is built from `command`/`cwd`, and `log` (the path the output was written to) is what the "left out" sentence greps when more causes exist than the result has room for.
|
|
2589
|
+
- `run` — How the run was started, when the caller knows it: the narrowed re-run is built from `command`/`cwd`/`config`, and `log` (the path the output was written to) is what the "left out" sentence greps when more causes exist than the result has room for.
|
|
2396
2590
|
|
|
2397
2591
|
**Returns:** The note, or null.
|
|
2398
2592
|
|
|
@@ -2411,13 +2605,13 @@ the same app and database, and that is exactly what is refused while the run wal
|
|
|
2411
2605
|
|
|
2412
2606
|
```typescript
|
|
2413
2607
|
function rerunOfRunningSuite(
|
|
2414
|
-
run: { command: string; cwd?: string },
|
|
2608
|
+
run: { command: string; cwd?: string; config?: string | null },
|
|
2415
2609
|
locations: readonly string[],
|
|
2416
2610
|
excluded?: readonly string[],
|
|
2417
2611
|
): string | null
|
|
2418
2612
|
```
|
|
2419
2613
|
|
|
2420
|
-
- `run` — The run in flight: the command it was started with, as it was run,
|
|
2614
|
+
- `run` — The run in flight: the command it was started with, as it was run, the directory that command ran in, and the config it loaded when the caller knows it.
|
|
2421
2615
|
- `locations` — The failing `file:line`s to re-run, as the run's log printed them.
|
|
2422
2616
|
- `excluded` — The test titles the request carves out: the re-run inverts them out (`--grep-invert`) so it cannot report them again.
|
|
2423
2617
|
|
|
@@ -2622,6 +2816,22 @@ How many console errors one test's trace contributes.
|
|
|
2622
2816
|
const MAX_TRACED_CONSOLE: 4
|
|
2623
2817
|
```
|
|
2624
2818
|
|
|
2819
|
+
#### `MAX_TRACED_ACTIONS`
|
|
2820
|
+
|
|
2821
|
+
How many of the page's own actions one test's trace contributes, the LAST ones.
|
|
2822
|
+
|
|
2823
|
+
```typescript
|
|
2824
|
+
const MAX_TRACED_ACTIONS: 40
|
|
2825
|
+
```
|
|
2826
|
+
|
|
2827
|
+
#### `MAX_TRACED_BODY_CHARS`
|
|
2828
|
+
|
|
2829
|
+
How much of a 4xx request's sent body (or its response body) the trace read keeps.
|
|
2830
|
+
|
|
2831
|
+
```typescript
|
|
2832
|
+
const MAX_TRACED_BODY_CHARS: 300
|
|
2833
|
+
```
|
|
2834
|
+
|
|
2625
2835
|
#### `MAX_TRACE_NOTE_TESTS`
|
|
2626
2836
|
|
|
2627
2837
|
How many tests the note in a run's result names.
|
|
@@ -2646,6 +2856,22 @@ How long a URL the note prints, before the middle is cut.
|
|
|
2646
2856
|
const TRACE_NOTE_URL_CHARS: 96
|
|
2647
2857
|
```
|
|
2648
2858
|
|
|
2859
|
+
#### `TRACE_NOTE_SENT_CHARS`
|
|
2860
|
+
|
|
2861
|
+
How much of the sent body the note prints — enough to see a `{"data":…}` wrapper.
|
|
2862
|
+
|
|
2863
|
+
```typescript
|
|
2864
|
+
const TRACE_NOTE_SENT_CHARS: 160
|
|
2865
|
+
```
|
|
2866
|
+
|
|
2867
|
+
#### `TRACE_NOTE_GOT_CHARS`
|
|
2868
|
+
|
|
2869
|
+
How much of the response body the note prints — the API's first error line.
|
|
2870
|
+
|
|
2871
|
+
```typescript
|
|
2872
|
+
const TRACE_NOTE_GOT_CHARS: 120
|
|
2873
|
+
```
|
|
2874
|
+
|
|
2649
2875
|
#### `DIAGNOSIS_TRACE_FLAG`
|
|
2650
2876
|
|
|
2651
2877
|
The `--trace` flag this platform adds, and the only value it adds it with.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a failing request actually hit, read off the runner's own output.
|
|
3
|
+
*
|
|
4
|
+
* Three layers fail in three different ways, and only one of them is the app's code:
|
|
5
|
+
*
|
|
6
|
+
* - **connection** — the request never got an answer at all: the transport timed out, was
|
|
7
|
+
* refused, reset, or could not resolve the name or negotiate TLS. Nothing about a handler,
|
|
8
|
+
* a header or CORS was measured, so a re-run is the only thing that tells a cold start (a
|
|
9
|
+
* machine deployed minutes ago whose boot finishes after the URL is live) from a real outage.
|
|
10
|
+
* - **status** — a response arrived and the HTTP status disagreed with what the suite asserts.
|
|
11
|
+
* - **assertion** — a response arrived and an assertion over its body failed.
|
|
12
|
+
*
|
|
13
|
+
* X0 xr-molecule-flash-m110 (+18:42 → +20:09) is the case this exists for: one Hurl file on the
|
|
14
|
+
* freshly deployed URL failed with `(28) Operation timed out … 0 bytes received` while the other
|
|
15
|
+
* twelve passed and 138 requests executed, and every note on the result read it as an assertion.
|
|
16
|
+
* The executor rewrote a CORS middleware, bisected a second server and latency-compared the two,
|
|
17
|
+
* then re-ran the same file — which passed in 3 s. A single re-run was the whole fix, and the one
|
|
18
|
+
* fact that would have said so is the failure's LAYER.
|
|
19
|
+
*
|
|
20
|
+
* ONE classifier, used by the causes note ({@link ./utilities.ts} `failureCausesNote`) and by the
|
|
21
|
+
* platform's own proof result (`molecule-dev` `suite-proof.ts`), so the two can never disagree
|
|
22
|
+
* about whether a failure was the app's to fix.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* The layer a failure came from: the transport (no answer at all), the response's status, or an
|
|
28
|
+
* assertion over a response that arrived.
|
|
29
|
+
*/
|
|
30
|
+
export type RunFailureKind = 'connection' | 'status' | 'assertion';
|
|
31
|
+
/** What the transport did when a request never got an answer. */
|
|
32
|
+
export type ConnectionFailureKind = 'timeout' | 'refused' | 'reset' | 'dns' | 'tls' | 'empty';
|
|
33
|
+
/** One failure a runner printed, classified by the layer that failed. */
|
|
34
|
+
export interface RunFailureClass {
|
|
35
|
+
/** The layer that failed. */
|
|
36
|
+
kind: RunFailureKind;
|
|
37
|
+
/** For a `connection` failure, what the transport did. */
|
|
38
|
+
connection?: ConnectionFailureKind;
|
|
39
|
+
/** How long the request waited before the transport gave up, in seconds, when printed. */
|
|
40
|
+
seconds?: number;
|
|
41
|
+
/** The `file:line` the runner printed for the failure, when it printed one. */
|
|
42
|
+
at?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Classify ONE failure's text — a Hurl `error:` block, or a Playwright failure message/body.
|
|
46
|
+
*
|
|
47
|
+
* The transport is read first: a block that carries a connection error AND an assertion is a
|
|
48
|
+
* request whose assertion never ran, so it is a connection failure. Only a block that carries no
|
|
49
|
+
* failure signature at all answers null — unknown is never a connection failure, because a
|
|
50
|
+
* misread transport hides a real app failure behind "just re-run it".
|
|
51
|
+
*
|
|
52
|
+
* @param text - One failure's text, as the runner printed it.
|
|
53
|
+
* @returns The classification, or null when the text carries no failure this reads.
|
|
54
|
+
*/
|
|
55
|
+
export declare function classifyRunFailure(text: string): RunFailureClass | null;
|
|
56
|
+
/**
|
|
57
|
+
* Whether every one of the classified failures is a connection failure — the only shape in which
|
|
58
|
+
* no response the app produced was measured.
|
|
59
|
+
*
|
|
60
|
+
* @param failures - The run's classifications, in printed order.
|
|
61
|
+
* @returns True when there is at least one and none of them is the app's.
|
|
62
|
+
*/
|
|
63
|
+
export declare function allConnectionFailures(failures: ReadonlyArray<RunFailureClass | null>): failures is ReadonlyArray<RunFailureClass & {
|
|
64
|
+
kind: 'connection';
|
|
65
|
+
}>;
|
|
66
|
+
//# sourceMappingURL=failure-kind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-kind.d.ts","sourceRoot":"","sources":["../src/failure-kind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAA;AAElE,iEAAiE;AACjE,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAA;AAE7F,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,IAAI,EAAE,cAAc,CAAA;IACpB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,qBAAqB,CAAA;IAClC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AA0ED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAoBvE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,aAAa,CAAC,eAAe,GAAG,IAAI,CAAC,GAC9C,QAAQ,IAAI,aAAa,CAAC,eAAe,GAAG;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAErE"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a failing request actually hit, read off the runner's own output.
|
|
3
|
+
*
|
|
4
|
+
* Three layers fail in three different ways, and only one of them is the app's code:
|
|
5
|
+
*
|
|
6
|
+
* - **connection** — the request never got an answer at all: the transport timed out, was
|
|
7
|
+
* refused, reset, or could not resolve the name or negotiate TLS. Nothing about a handler,
|
|
8
|
+
* a header or CORS was measured, so a re-run is the only thing that tells a cold start (a
|
|
9
|
+
* machine deployed minutes ago whose boot finishes after the URL is live) from a real outage.
|
|
10
|
+
* - **status** — a response arrived and the HTTP status disagreed with what the suite asserts.
|
|
11
|
+
* - **assertion** — a response arrived and an assertion over its body failed.
|
|
12
|
+
*
|
|
13
|
+
* X0 xr-molecule-flash-m110 (+18:42 → +20:09) is the case this exists for: one Hurl file on the
|
|
14
|
+
* freshly deployed URL failed with `(28) Operation timed out … 0 bytes received` while the other
|
|
15
|
+
* twelve passed and 138 requests executed, and every note on the result read it as an assertion.
|
|
16
|
+
* The executor rewrote a CORS middleware, bisected a second server and latency-compared the two,
|
|
17
|
+
* then re-ran the same file — which passed in 3 s. A single re-run was the whole fix, and the one
|
|
18
|
+
* fact that would have said so is the failure's LAYER.
|
|
19
|
+
*
|
|
20
|
+
* ONE classifier, used by the causes note ({@link ./utilities.ts} `failureCausesNote`) and by the
|
|
21
|
+
* platform's own proof result (`molecule-dev` `suite-proof.ts`), so the two can never disagree
|
|
22
|
+
* about whether a failure was the app's to fix.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Transport-level failures, most specific first: the patterns curl/Hurl and Chromium/Node print
|
|
28
|
+
* for a request that never got an answer. A message can carry more than one (`page.goto: Timeout
|
|
29
|
+
* 30000ms exceeded … net::ERR_CONNECTION_REFUSED`), and the more specific reading wins.
|
|
30
|
+
*/
|
|
31
|
+
const CONNECTION_FAILURES = [
|
|
32
|
+
// Hurl/curl's numbered errors (curl's exit codes, printed as `(28) Operation timed out …`).
|
|
33
|
+
['empty', /\(\s*52\)\s*Empty reply from server/],
|
|
34
|
+
['timeout', /\(\s*28\)\s*Operation timed out|Operation timed out after \d+/],
|
|
35
|
+
['dns', /\(\s*6\)\s*Could not resolve host|Could not resolve host/],
|
|
36
|
+
[
|
|
37
|
+
'tls',
|
|
38
|
+
/\(\s*(?:35|51|58|59|60|77|83|90|91)\)\s|SSL connect error|SSL certificate problem|self[- ]signed certificate|UNABLE_TO_VERIFY_LEAF_SIGNATURE|CERT_HAS_EXPIRED/i,
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
'refused',
|
|
42
|
+
/\(\s*7\)\s*(?:Failed to connect|Couldn't connect)|Failed to connect to\s|Couldn't connect to server/,
|
|
43
|
+
],
|
|
44
|
+
['reset', /\(\s*5[56]\)\s*(?:Recv|Send) failure|Connection reset by peer|socket hang up/],
|
|
45
|
+
// The browser's own net errors, and the Node errors underneath them.
|
|
46
|
+
['timeout', /net::ERR_(?:CONNECTION_)?TIMED_OUT|\bETIMEDOUT\b/],
|
|
47
|
+
['dns', /net::ERR_NAME_(?:NOT_RESOLVED|RESOLUTION_FAILED)|\bENOTFOUND\b|\bEAI_AGAIN\b/],
|
|
48
|
+
['refused', /net::ERR_CONNECTION_REFUSED|\bECONNREFUSED\b/],
|
|
49
|
+
['empty', /net::ERR_EMPTY_RESPONSE/],
|
|
50
|
+
['reset', /net::ERR_CONNECTION_(?:RESET|CLOSED|ABORTED)|\bECONNRESET\b/],
|
|
51
|
+
['tls', /net::ERR_(?:CERT|SSL|BAD_SSL)\w*/i],
|
|
52
|
+
];
|
|
53
|
+
/** A status assertion: the response arrived and its code is what failed. */
|
|
54
|
+
const STATUS_FAILURE = /^\s*error:\s*Assert status code\b|actual value is <\d{3}>|\bHTTP(?:\/[\d.]+)?\s+[1-5]\d{2}\b|Expected[: ]+[1-5]\d{2}\b|\.status\(\)/m;
|
|
55
|
+
/** Any other assertion over a response that arrived. */
|
|
56
|
+
const ASSERTION_FAILURE = /^\s*error:\s*Assert\b|\bAssertionError\b|\bexpect\(|\bExpected[: ]|\bReceived[: ]|\bjsonpath\b|^\s*Error:/m;
|
|
57
|
+
/** Hurl's `--> file:line:col`, the location of the failing request or assertion. */
|
|
58
|
+
const HURL_AT = /-->\s+(\S+?):(\d+)(?::\d+)?\s*$/m;
|
|
59
|
+
/** A spec file and line, the location a Playwright block names. */
|
|
60
|
+
const SPEC_AT = /([\w./~-]+\.(?:spec|test)\.[cm]?[jt]sx?):(\d+)(?::\d+)?/;
|
|
61
|
+
/** How long a request waited, as curl prints it (`Operation timed out after 20002 milliseconds`). */
|
|
62
|
+
const WAITED_MS = /Operation timed out after (\d+) milliseconds/;
|
|
63
|
+
/** How long a request waited, as Playwright prints it (`Timeout 30000ms exceeded`). */
|
|
64
|
+
const WAITED_PLAYWRIGHT = /(?<!Test )\bTimeout (\d+)ms exceeded/;
|
|
65
|
+
/**
|
|
66
|
+
* The `file:line` a failure block names, when it names one.
|
|
67
|
+
*
|
|
68
|
+
* @param text - The failure block.
|
|
69
|
+
* @returns The location, or undefined when the block carries none.
|
|
70
|
+
*/
|
|
71
|
+
function failureLocation(text) {
|
|
72
|
+
const hurl = HURL_AT.exec(text);
|
|
73
|
+
if (hurl)
|
|
74
|
+
return `${hurl[1]}:${hurl[2]}`;
|
|
75
|
+
const spec = SPEC_AT.exec(text);
|
|
76
|
+
return spec ? `${spec[1]}:${spec[2]}` : undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* How long the request waited before it gave up, in seconds, when the runner printed it.
|
|
80
|
+
*
|
|
81
|
+
* @param text - The failure block.
|
|
82
|
+
* @returns The seconds, rounded, or undefined.
|
|
83
|
+
*/
|
|
84
|
+
function waitedSeconds(text) {
|
|
85
|
+
const ms = WAITED_MS.exec(text) ?? WAITED_PLAYWRIGHT.exec(text);
|
|
86
|
+
return ms ? Math.round(Number(ms[1]) / 1000) : undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Classify ONE failure's text — a Hurl `error:` block, or a Playwright failure message/body.
|
|
90
|
+
*
|
|
91
|
+
* The transport is read first: a block that carries a connection error AND an assertion is a
|
|
92
|
+
* request whose assertion never ran, so it is a connection failure. Only a block that carries no
|
|
93
|
+
* failure signature at all answers null — unknown is never a connection failure, because a
|
|
94
|
+
* misread transport hides a real app failure behind "just re-run it".
|
|
95
|
+
*
|
|
96
|
+
* @param text - One failure's text, as the runner printed it.
|
|
97
|
+
* @returns The classification, or null when the text carries no failure this reads.
|
|
98
|
+
*/
|
|
99
|
+
export function classifyRunFailure(text) {
|
|
100
|
+
const body = String(text ?? '');
|
|
101
|
+
if (body.trim().length === 0)
|
|
102
|
+
return null;
|
|
103
|
+
for (const [connection, pattern] of CONNECTION_FAILURES) {
|
|
104
|
+
if (!pattern.test(body))
|
|
105
|
+
continue;
|
|
106
|
+
const result = { kind: 'connection', connection };
|
|
107
|
+
const at = failureLocation(body);
|
|
108
|
+
if (at !== undefined)
|
|
109
|
+
result.at = at;
|
|
110
|
+
const seconds = waitedSeconds(body);
|
|
111
|
+
if (seconds !== undefined)
|
|
112
|
+
result.seconds = seconds;
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
const at = failureLocation(body);
|
|
116
|
+
const kind = STATUS_FAILURE.test(body)
|
|
117
|
+
? 'status'
|
|
118
|
+
: ASSERTION_FAILURE.test(body)
|
|
119
|
+
? 'assertion'
|
|
120
|
+
: null;
|
|
121
|
+
if (kind === null)
|
|
122
|
+
return null;
|
|
123
|
+
return at === undefined ? { kind } : { kind, at };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether every one of the classified failures is a connection failure — the only shape in which
|
|
127
|
+
* no response the app produced was measured.
|
|
128
|
+
*
|
|
129
|
+
* @param failures - The run's classifications, in printed order.
|
|
130
|
+
* @returns True when there is at least one and none of them is the app's.
|
|
131
|
+
*/
|
|
132
|
+
export function allConnectionFailures(failures) {
|
|
133
|
+
return failures.length > 0 && failures.every((failure) => failure?.kind === 'connection');
|
|
134
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -134,6 +134,7 @@ export * from './backends/sandbox.js';
|
|
|
134
134
|
export * from './browser-guard.js';
|
|
135
135
|
export * from './command-position.js';
|
|
136
136
|
export * from './cut-run.js';
|
|
137
|
+
export * from './failure-kind.js';
|
|
137
138
|
export * from './input-normalizer.js';
|
|
138
139
|
export * from './no-test-run.js';
|
|
139
140
|
export * from './schemas.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkIG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,sBAAsB,CAAA;AACpC,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkIG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,sBAAsB,CAAA;AACpC,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -134,6 +134,7 @@ export * from './backends/sandbox.js';
|
|
|
134
134
|
export * from './browser-guard.js';
|
|
135
135
|
export * from './command-position.js';
|
|
136
136
|
export * from './cut-run.js';
|
|
137
|
+
export * from './failure-kind.js';
|
|
137
138
|
export * from './input-normalizer.js';
|
|
138
139
|
export * from './no-test-run.js';
|
|
139
140
|
export * from './schemas.js';
|