@molecule/api-ai-tools 1.8.3 → 1.9.2
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 +264 -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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/pipeline-exit-note.d.ts +37 -0
- package/dist/pipeline-exit-note.d.ts.map +1 -0
- package/dist/pipeline-exit-note.js +154 -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/tools.d.ts.map +1 -1
- package/dist/tools.js +14 -0
- 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/dist/trace-artifacts.js
CHANGED
|
@@ -28,19 +28,29 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { locatedCommandSegments } from './command-position.js';
|
|
30
30
|
import { isDiagnosisRun } from './suite-overlap.js';
|
|
31
|
-
import { isPlaywrightRunCommand } from './utilities.js';
|
|
31
|
+
import { isPlaywrightRunCommand, redactSecrets } from './utilities.js';
|
|
32
32
|
/** How many traces one read reports, newest first. */
|
|
33
33
|
export const MAX_TRACE_ARTIFACTS = 12;
|
|
34
34
|
/** How many requests one test's trace contributes. */
|
|
35
35
|
export const MAX_TRACED_REQUESTS = 6;
|
|
36
36
|
/** How many console errors one test's trace contributes. */
|
|
37
37
|
export const MAX_TRACED_CONSOLE = 4;
|
|
38
|
+
/** How many of the page's own actions one test's trace contributes, the LAST ones. */
|
|
39
|
+
export const MAX_TRACED_ACTIONS = 40;
|
|
40
|
+
/** How much of a 4xx request's sent body (or its response body) the trace read keeps. */
|
|
41
|
+
export const MAX_TRACED_BODY_CHARS = 300;
|
|
38
42
|
/** How many tests the note in a run's result names. */
|
|
39
43
|
export const MAX_TRACE_NOTE_TESTS = 5;
|
|
40
44
|
/** How many requests the note names per test. */
|
|
41
45
|
export const MAX_TRACE_NOTE_REQUESTS = 4;
|
|
42
46
|
/** How long a URL the note prints, before the middle is cut. */
|
|
43
47
|
export const TRACE_NOTE_URL_CHARS = 96;
|
|
48
|
+
/** How much of the sent body the note prints — enough to see a `{"data":…}` wrapper. */
|
|
49
|
+
export const TRACE_NOTE_SENT_CHARS = 160;
|
|
50
|
+
/** How much of the response body the note prints — the API's first error line. */
|
|
51
|
+
export const TRACE_NOTE_GOT_CHARS = 120;
|
|
52
|
+
/** How much of a network error's own text (`net::ERR_CONNECTION_REFUSED`) the note prints. */
|
|
53
|
+
const TRACE_NOTE_FAILURE_CHARS = 120;
|
|
44
54
|
/**
|
|
45
55
|
* The Node program the sandbox runs to read a failed run's traces: `node <script> <dir>` prints one
|
|
46
56
|
* `TRACE <json>` line per `trace.zip` under `<dir>` (newest first, {@link MAX_TRACE_ARTIFACTS} of
|
|
@@ -52,10 +62,17 @@ export const TRACE_NOTE_URL_CHARS = 96;
|
|
|
52
62
|
* central directory and `zlib.inflateRawSync` — about forty lines, and the only thing that has to
|
|
53
63
|
* be true of a `trace.zip` is that it IS one.
|
|
54
64
|
*
|
|
55
|
-
* A request is kept over a static asset when it failed
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
65
|
+
* A request is kept over a static asset when it failed — a `status >= 400`, or no response at all
|
|
66
|
+
* together with Playwright's own error text (`-1` plus `_failureText`, a refused connection) — and
|
|
67
|
+
* over anything else when it is the page talking to the app (`fetch`/`xhr`/`document`): a failing
|
|
68
|
+
* test's trace holds the whole page load. A request with no response and NO error text was aborted
|
|
69
|
+
* or still in flight when the test ended; it is kept too, but as what it is, not as a failure.
|
|
70
|
+
*
|
|
71
|
+
* A request answered 4xx by the app's OWN API also keeps what the page SENT and the first of what
|
|
72
|
+
* came back: Playwright stores either inline (`text`) or in a resource entry (`_file`), and both
|
|
73
|
+
* are read. That is where a wrapped body shows: xr-molecule-flash-m111's helper passed
|
|
74
|
+
* `{ data: body }` as app-http's second argument, so every POST/PUT was `{"data":{…}}` and the API
|
|
75
|
+
* validated `{}`. Status alone said only "422".
|
|
59
76
|
*
|
|
60
77
|
* @returns The program's source, to write to a file and run.
|
|
61
78
|
*/
|
|
@@ -68,6 +85,8 @@ const ROOT = process.argv[2] || '/workspace'
|
|
|
68
85
|
const MAX_TRACES = ${MAX_TRACE_ARTIFACTS}
|
|
69
86
|
const MAX_REQUESTS = ${MAX_TRACED_REQUESTS}
|
|
70
87
|
const MAX_CONSOLE = ${MAX_TRACED_CONSOLE}
|
|
88
|
+
const MAX_ACTIONS = ${MAX_TRACED_ACTIONS}
|
|
89
|
+
const MAX_BODY = ${MAX_TRACED_BODY_CHARS}
|
|
71
90
|
const MAX_URL = 200
|
|
72
91
|
|
|
73
92
|
function walk(dir, depth, out) {
|
|
@@ -140,6 +159,22 @@ function messageOf(value) {
|
|
|
140
159
|
return null
|
|
141
160
|
}
|
|
142
161
|
|
|
162
|
+
// A request/response body exactly as Playwright stored it: inline on the value, or in a resource
|
|
163
|
+
// entry the value names. Bounded, and binary is dropped — a JSON body is what this is for, and a
|
|
164
|
+
// multipart upload would print bytes.
|
|
165
|
+
function bodyText(buf, entries, value, max) {
|
|
166
|
+
if (!value || typeof value !== 'object') return ''
|
|
167
|
+
const inline = typeof value.text === 'string' ? value.text : ''
|
|
168
|
+
if (inline) return inline.slice(0, max)
|
|
169
|
+
if (typeof value._file !== 'string') return ''
|
|
170
|
+
const entry = entries.find(function (e) { return e.name === value._file })
|
|
171
|
+
if (!entry) return ''
|
|
172
|
+
const data = zipData(buf, entry)
|
|
173
|
+
if (!data) return ''
|
|
174
|
+
const text = data.toString('utf8').slice(0, max)
|
|
175
|
+
return text.indexOf('\\u0000') === -1 ? text : ''
|
|
176
|
+
}
|
|
177
|
+
|
|
143
178
|
let origin = ''
|
|
144
179
|
function pathOf(url) {
|
|
145
180
|
try {
|
|
@@ -188,7 +223,10 @@ for (const one of found.slice(0, MAX_TRACES)) {
|
|
|
188
223
|
method: String(request.method).toUpperCase(),
|
|
189
224
|
path: String(request.url).slice(0, MAX_URL),
|
|
190
225
|
status: Number(response.status) || 0,
|
|
226
|
+
failure: typeof response._failureText === 'string' ? response._failureText : undefined,
|
|
191
227
|
type: type,
|
|
228
|
+
postData: request.postData,
|
|
229
|
+
content: response.content,
|
|
192
230
|
})
|
|
193
231
|
}
|
|
194
232
|
if (!origin) {
|
|
@@ -196,12 +234,27 @@ for (const one of found.slice(0, MAX_TRACES)) {
|
|
|
196
234
|
origin = first ? originOf(first.path) : ''
|
|
197
235
|
}
|
|
198
236
|
for (const r of requests) r.path = pathOf(r.path).slice(0, MAX_URL)
|
|
237
|
+
// A 4xx from the app's own API is where a body SHAPE is diagnosed: keep what the page sent and
|
|
238
|
+
// what came back (bounded). A relative path here means the same origin as the traced document.
|
|
239
|
+
for (const r of requests) {
|
|
240
|
+
if (r.status < 400 || r.status >= 500) continue
|
|
241
|
+
if (/^https?:/i.test(r.path)) continue
|
|
242
|
+
const sent = bodyText(buf, entries, r.postData, MAX_BODY)
|
|
243
|
+
const got = bodyText(buf, entries, r.content, MAX_BODY)
|
|
244
|
+
if (sent) r.sent = sent
|
|
245
|
+
if (got) r.got = got
|
|
246
|
+
}
|
|
247
|
+
// A failed request: a 4xx/5xx, or no response at all WITH Playwright's error text (a refused
|
|
248
|
+
// connection). No response and no text is an aborted/pending request, not a failure.
|
|
249
|
+
function failedRequest(r) {
|
|
250
|
+
return r.status >= 400 || (r.status <= 0 && typeof r.failure === 'string')
|
|
251
|
+
}
|
|
199
252
|
const failing = []
|
|
200
253
|
const api = []
|
|
201
254
|
const rest = []
|
|
202
255
|
for (let i = 0; i < requests.length; i++) {
|
|
203
256
|
const r = requests[i]
|
|
204
|
-
if (r
|
|
257
|
+
if (failedRequest(r)) failing.push(i)
|
|
205
258
|
else if (r.type === 'fetch' || r.type === 'xhr' || r.type === 'document') api.push(i)
|
|
206
259
|
else rest.push(i)
|
|
207
260
|
}
|
|
@@ -209,15 +262,41 @@ for (const one of found.slice(0, MAX_TRACES)) {
|
|
|
209
262
|
const chosen = []
|
|
210
263
|
const seen = new Set()
|
|
211
264
|
for (const i of keep.sort(function (a, b) {
|
|
212
|
-
const wrong = requests[a]
|
|
213
|
-
const other = requests[b]
|
|
265
|
+
const wrong = failedRequest(requests[a]) ? 0 : 1
|
|
266
|
+
const other = failedRequest(requests[b]) ? 0 : 1
|
|
214
267
|
return wrong === other ? a - b : wrong - other
|
|
215
268
|
})) {
|
|
216
269
|
const r = requests[i]
|
|
217
270
|
const key = r.method + ' ' + r.path + ' ' + r.status
|
|
218
271
|
if (seen.has(key)) continue
|
|
219
272
|
seen.add(key)
|
|
220
|
-
chosen.push({
|
|
273
|
+
chosen.push({
|
|
274
|
+
method: r.method,
|
|
275
|
+
path: r.path,
|
|
276
|
+
status: r.status,
|
|
277
|
+
failure: r.failure,
|
|
278
|
+
sent: r.sent,
|
|
279
|
+
got: r.got,
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
// The page's own actions, timed. before/after pair on the call id, and only the LIBRARY
|
|
283
|
+
// traces carry a class and a method (Frame.goto, Locator.fill); the test runner's own
|
|
284
|
+
// trace times its hooks and fixtures with class Test, which is not a page action.
|
|
285
|
+
const ended = {}
|
|
286
|
+
for (const entry of trace) {
|
|
287
|
+
if (entry && entry.type === 'after' && typeof entry.callId === 'string' && typeof entry.endTime === 'number')
|
|
288
|
+
ended[entry.callId] = entry.endTime
|
|
289
|
+
}
|
|
290
|
+
const actions = []
|
|
291
|
+
for (const entry of trace) {
|
|
292
|
+
if (!entry || entry.type !== 'before') continue
|
|
293
|
+
if (typeof entry.class !== 'string' || typeof entry.method !== 'string') continue
|
|
294
|
+
if (entry.class === 'Test' || typeof entry.startTime !== 'number') continue
|
|
295
|
+
actions.push({
|
|
296
|
+
api: entry.class + '.' + entry.method,
|
|
297
|
+
start: entry.startTime,
|
|
298
|
+
end: typeof ended[entry.callId] === 'number' ? ended[entry.callId] : null,
|
|
299
|
+
})
|
|
221
300
|
}
|
|
222
301
|
const consoleErrors = []
|
|
223
302
|
let spec = null
|
|
@@ -241,13 +320,17 @@ for (const one of found.slice(0, MAX_TRACES)) {
|
|
|
241
320
|
// of its own about it, that is the line worth reading.
|
|
242
321
|
const specific = consoleErrors.filter(function (text) { return !/^Failed to load resource/.test(text) })
|
|
243
322
|
const keptConsole = specific.length > 0 ? specific : consoleErrors
|
|
244
|
-
if (chosen.length === 0 && keptConsole.length === 0) continue
|
|
323
|
+
if (chosen.length === 0 && keptConsole.length === 0 && actions.length === 0) continue
|
|
245
324
|
process.stdout.write('TRACE ' + JSON.stringify({
|
|
246
325
|
dir: path.relative(ROOT, path.dirname(one.file)),
|
|
247
326
|
spec: spec,
|
|
248
327
|
line: line,
|
|
249
|
-
|
|
328
|
+
// Failures first, per the sort above; the keep-window is the HEAD of that order. Taking the
|
|
329
|
+
// tail here dropped every failed request as soon as a page made more than MAX_REQUESTS of them
|
|
330
|
+
// plus the answered ones (xr-molecule-flash-m112's note listed only -1s and 200s).
|
|
331
|
+
requests: chosen.slice(0, MAX_REQUESTS),
|
|
250
332
|
console: keptConsole.slice(0, MAX_CONSOLE).map(function (text) { return text.slice(0, 300) }),
|
|
333
|
+
actions: actions.slice(-MAX_ACTIONS),
|
|
251
334
|
}) + '\\n')
|
|
252
335
|
}
|
|
253
336
|
`;
|
|
@@ -269,6 +352,7 @@ export function parseTraceArtifacts(stdout) {
|
|
|
269
352
|
const raw = JSON.parse(line.slice('TRACE '.length));
|
|
270
353
|
const requests = Array.isArray(raw.requests) ? raw.requests : [];
|
|
271
354
|
const consoleMessages = Array.isArray(raw.console) ? raw.console : [];
|
|
355
|
+
const actions = Array.isArray(raw.actions) ? raw.actions : [];
|
|
272
356
|
out.push({
|
|
273
357
|
spec: typeof raw.spec === 'string' ? raw.spec : null,
|
|
274
358
|
line: typeof raw.line === 'number' ? raw.line : 0,
|
|
@@ -278,8 +362,19 @@ export function parseTraceArtifacts(stdout) {
|
|
|
278
362
|
method: String(r.method ?? ''),
|
|
279
363
|
path: String(r.path ?? ''),
|
|
280
364
|
status: Number(r.status ?? 0) || 0,
|
|
365
|
+
...(typeof r.failure === 'string' && r.failure ? { failure: r.failure } : {}),
|
|
366
|
+
...(typeof r.sent === 'string' && r.sent ? { sent: r.sent } : {}),
|
|
367
|
+
...(typeof r.got === 'string' && r.got ? { got: r.got } : {}),
|
|
281
368
|
})),
|
|
282
369
|
console: consoleMessages.map(String),
|
|
370
|
+
actions: actions
|
|
371
|
+
.filter((a) => typeof a === 'object' && a !== null)
|
|
372
|
+
.filter((a) => typeof a.api === 'string' && Number.isFinite(Number(a.start)))
|
|
373
|
+
.map((a) => ({
|
|
374
|
+
api: String(a.api),
|
|
375
|
+
start: Number(a.start),
|
|
376
|
+
end: a.end === null || a.end === undefined ? null : Number(a.end),
|
|
377
|
+
})),
|
|
283
378
|
});
|
|
284
379
|
}
|
|
285
380
|
catch (_error) {
|
|
@@ -294,12 +389,141 @@ function whereOf(item) {
|
|
|
294
389
|
return 'a failing test';
|
|
295
390
|
return item.line > 0 ? `${item.spec}:${item.line}` : item.spec;
|
|
296
391
|
}
|
|
297
|
-
/** One
|
|
298
|
-
function
|
|
392
|
+
/** One body as the note writes it: one line, bounded. */
|
|
393
|
+
function bodyLine(text, max) {
|
|
394
|
+
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
395
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
396
|
+
}
|
|
397
|
+
/** One request named the way the note names it — `GET /api/tags`, its URL bounded. */
|
|
398
|
+
function requestLabel(request) {
|
|
299
399
|
const url = request.path.length > TRACE_NOTE_URL_CHARS
|
|
300
400
|
? `${request.path.slice(0, TRACE_NOTE_URL_CHARS)}…`
|
|
301
401
|
: request.path;
|
|
302
|
-
return `${request.method} ${url}
|
|
402
|
+
return `${request.method} ${url}`;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* One request as the note writes it: `POST /api/articles/x/favorite → 401`, plus what a 4xx
|
|
406
|
+
* request sent and got — `POST /api/users → 422 sent {"data":{…}} got {"errors":{…}}`.
|
|
407
|
+
*/
|
|
408
|
+
function requestLine(request) {
|
|
409
|
+
// Playwright records `-1` for every request it never got a response for, so `-1` is never a
|
|
410
|
+
// status to print: a request that failed without one carries its own error text, and one with
|
|
411
|
+
// neither was still in flight when the test ended (xr-molecule-flash-m112 +30:45).
|
|
412
|
+
const status = request.status > 0
|
|
413
|
+
? String(request.status)
|
|
414
|
+
: request.failure
|
|
415
|
+
? `no response (${bodyLine(redactSecrets(request.failure), TRACE_NOTE_FAILURE_CHARS)})`
|
|
416
|
+
: 'no response';
|
|
417
|
+
const head = `${requestLabel(request)} → ${status}`;
|
|
418
|
+
// The body a page sends to its own API can carry a credential (a registration's password, a
|
|
419
|
+
// token in a form) — the note is egress, so it goes through the same redactor command output
|
|
420
|
+
// does. The parsed trace keeps the body; only the text the model reads is masked.
|
|
421
|
+
const sent = request.sent
|
|
422
|
+
? ` sent ${bodyLine(redactSecrets(request.sent), TRACE_NOTE_SENT_CHARS)}`
|
|
423
|
+
: '';
|
|
424
|
+
const got = request.got
|
|
425
|
+
? ` got ${bodyLine(redactSecrets(request.got), TRACE_NOTE_GOT_CHARS)}`
|
|
426
|
+
: '';
|
|
427
|
+
return `${head}${sent}${got}`;
|
|
428
|
+
}
|
|
429
|
+
/** Whether a sent body is an object whose only top-level key is `data` — the wrapped-body trap. */
|
|
430
|
+
function wrappedInData(text) {
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(text);
|
|
433
|
+
return (typeof parsed === 'object' &&
|
|
434
|
+
parsed !== null &&
|
|
435
|
+
!Array.isArray(parsed) &&
|
|
436
|
+
Object.keys(parsed).length === 1 &&
|
|
437
|
+
'data' in parsed);
|
|
438
|
+
}
|
|
439
|
+
catch (_error) {
|
|
440
|
+
// The body was cut at MAX_TRACED_BODY_CHARS, so it need not parse; the wrapper is still the
|
|
441
|
+
// first thing in it — `{"data":…}` with no other top-level key before the cut.
|
|
442
|
+
return /^\{\s*"data"\s*:/.test(text.trim());
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** Whether a trace recorded a request the server actually failed: a 4xx/5xx, or a network error. */
|
|
446
|
+
function failedRequest(request) {
|
|
447
|
+
return request.status >= 400 || (request.status <= 0 && request.failure !== undefined);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Whether a trace recorded NO response and no reason for it — a request that was aborted or still
|
|
451
|
+
* in flight when the test ended. It is not evidence that the server failed (m112 +30:45).
|
|
452
|
+
*/
|
|
453
|
+
function unansweredRequest(request) {
|
|
454
|
+
return request.status <= 0 && request.failure === undefined;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* How far a trace's last recorded step can sit after an earlier walk's failing test and still be
|
|
458
|
+
* that test. The trace names the LINE of the last step it recorded (the click, the wait), while the
|
|
459
|
+
* run's own summary names the test's DECLARATION line, so a row that repeats a failure sits a few
|
|
460
|
+
* lines below the declaration it repeats: m112's favorite test is declared at
|
|
461
|
+
* `articles.spec.ts:123` and its last step, the click that changed nothing, was on `:133`.
|
|
462
|
+
*/
|
|
463
|
+
const RECURRENCE_LINE_WINDOW = 40;
|
|
464
|
+
/** A `file:line` split, with a leading `./` dropped: the two readers spell the same file apart. */
|
|
465
|
+
function splitLocation(location) {
|
|
466
|
+
const trimmed = location.trim().replace(/^\.\//, '');
|
|
467
|
+
const match = /^(.*):(\d+)$/.exec(trimmed);
|
|
468
|
+
if (match === null)
|
|
469
|
+
return { file: trimmed, line: 0 };
|
|
470
|
+
return { file: match[1] ?? trimmed, line: Number(match[2]) };
|
|
471
|
+
}
|
|
472
|
+
/** Whether two locations name the same file — one may be absolute where the other is relative. */
|
|
473
|
+
function sameLocationFile(left, right) {
|
|
474
|
+
return left === right || left.endsWith(`/${right}`) || right.endsWith(`/${left}`);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Whether this trace's test is one an EARLIER walk of the suite failed.
|
|
478
|
+
*
|
|
479
|
+
* The earlier walk's locations come from the run's own summary — the test's DECLARATION line —
|
|
480
|
+
* while the trace names the last step's line, so a repeat is the same file with its step inside
|
|
481
|
+
* {@link RECURRENCE_LINE_WINDOW} lines after the NEAREST earlier declaration before it (the test
|
|
482
|
+
* body it belongs to). A line BEFORE the declaration is a hook, not the test: m112's
|
|
483
|
+
* `url-navigation.spec.ts:107` is the Pagination suite's `afterEach`, which four different tests
|
|
484
|
+
* share. A trace that names no spec cannot be placed at all.
|
|
485
|
+
*
|
|
486
|
+
* @param item - The trace.
|
|
487
|
+
* @param earlier - The failing `file:line`s earlier walks of the suite left.
|
|
488
|
+
* @returns True when the trace repeats one of them.
|
|
489
|
+
*/
|
|
490
|
+
function repeatsEarlierFailure(item, earlier) {
|
|
491
|
+
if (item.spec === null)
|
|
492
|
+
return false;
|
|
493
|
+
for (const location of earlier) {
|
|
494
|
+
const before = splitLocation(location);
|
|
495
|
+
if (!sameLocationFile(item.spec, before.file))
|
|
496
|
+
continue;
|
|
497
|
+
// Either side without a line is the same file alone, which is all that can be compared.
|
|
498
|
+
if (item.line === 0 || before.line === 0)
|
|
499
|
+
return true;
|
|
500
|
+
if (before.line <= item.line && item.line - before.line <= RECURRENCE_LINE_WINDOW)
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Whether a trace holds evidence the note can explain: a request that got a RECORDED response, a
|
|
507
|
+
* real failure (4xx/5xx or a network error), or a console error. A request with no response is
|
|
508
|
+
* not evidence (m112 +53:46): four of the note's five rows were traces of one `afterEach` line
|
|
509
|
+
* whose every request was `-1`, so the favorite cause — repeated in all three walks — stayed
|
|
510
|
+
* hidden in "… and 7 more traced failures".
|
|
511
|
+
*/
|
|
512
|
+
function traceRowEvidence(item) {
|
|
513
|
+
return item.console.length > 0 || item.requests.some((request) => !unansweredRequest(request));
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The note's row order: a row with a REAL failure first, then a row whose failure an earlier walk
|
|
517
|
+
* also had, then the rest. A recurrence is the only evidence a row carries when its requests were
|
|
518
|
+
* all answered (m112's favorite cause: the click's own request answered, the button never
|
|
519
|
+
* changed).
|
|
520
|
+
*/
|
|
521
|
+
function traceRowTier(item, earlier) {
|
|
522
|
+
if (item.requests.some(failedRequest))
|
|
523
|
+
return 0;
|
|
524
|
+
if (repeatsEarlierFailure(item, earlier))
|
|
525
|
+
return 1;
|
|
526
|
+
return 2;
|
|
303
527
|
}
|
|
304
528
|
/**
|
|
305
529
|
* The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
@@ -311,20 +535,75 @@ function requestLine(request) {
|
|
|
311
535
|
* {@link MAX_TRACE_NOTE_TESTS} tests with {@link MAX_TRACE_NOTE_REQUESTS} requests each, because it
|
|
312
536
|
* rides in a result the model pays for.
|
|
313
537
|
*
|
|
538
|
+
* The three kinds are kept apart, because they mean different things. A FAILED request (4xx/5xx, or
|
|
539
|
+
* a network error with Playwright's own text) is a cause. A request with NO RESPONSE RECORDED —
|
|
540
|
+
* Playwright's `-1`, no error text — was still in flight when the test ended (the page closed under
|
|
541
|
+
* it); listing it as a failure sent a run hunting an API that never answered anything wrong
|
|
542
|
+
* (xr-molecule-flash-m112 +30:45). The rest are the page's last answered requests.
|
|
543
|
+
*
|
|
544
|
+
* A 4xx request carries what the page SENT beside its status, so a wrapped body is visible in one
|
|
545
|
+
* line rather than inferred from the status; when one of those bodies is an object whose only key
|
|
546
|
+
* is `data`, the note names the call shape that produces it (xr-molecule-flash-m111 +11:36).
|
|
547
|
+
*
|
|
548
|
+
* WHICH rows it spends is evidence, not artifact order (xr-molecule-flash-m112 +53:46): a row
|
|
549
|
+
* needs a recorded response, a real failure or a console error ({@link traceRowEvidence}), rows are
|
|
550
|
+
* deduplicated by the test's location, and they are ordered real failure → repeated from an
|
|
551
|
+
* earlier walk → the rest ({@link traceRowTier}). Without that the note spent all five rows on four
|
|
552
|
+
* traces of one `afterEach` line whose every request was `-1`, and left the favorite cause — the
|
|
553
|
+
* one failure that repeated in every walk — in its "… and N more".
|
|
554
|
+
*
|
|
314
555
|
* @param items - The traces {@link parseTraceArtifacts} read.
|
|
315
556
|
* @param target - Where the run went — the deployed host, as the note names it.
|
|
316
|
-
* @
|
|
557
|
+
* @param earlierFailures - The failing `file:line`s earlier walks of this suite left, so a repeat
|
|
558
|
+
* is ranked above a one-off. Empty when the caller knows of no earlier walk.
|
|
559
|
+
* @returns The note, or null when no trace held a recorded response, a real failure or a console
|
|
560
|
+
* error.
|
|
317
561
|
*/
|
|
318
|
-
export function failureTraceNote(items, target) {
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
562
|
+
export function failureTraceNote(items, target, earlierFailures = []) {
|
|
563
|
+
const candidates = items.filter(traceRowEvidence);
|
|
564
|
+
if (candidates.length === 0)
|
|
321
565
|
return null;
|
|
566
|
+
// Ranked, then deduplicated BY LOCATION — in that order, so the row kept for a location is the
|
|
567
|
+
// one carrying the strongest evidence (m112's duplicate `afterEach` rows).
|
|
568
|
+
const ranked = candidates
|
|
569
|
+
.map((item, index) => ({ item, index, tier: traceRowTier(item, earlierFailures) }))
|
|
570
|
+
.sort((left, right) => left.tier - right.tier || left.index - right.index);
|
|
571
|
+
const traced = [];
|
|
572
|
+
const seen = new Set();
|
|
573
|
+
for (const { item, index } of ranked) {
|
|
574
|
+
// A trace that names no spec cannot be placed; it is never collapsed onto another one.
|
|
575
|
+
const where = item.spec === null ? `#${index}` : whereOf(item);
|
|
576
|
+
if (seen.has(where))
|
|
577
|
+
continue;
|
|
578
|
+
seen.add(where);
|
|
579
|
+
traced.push(item);
|
|
580
|
+
if (traced.length === MAX_TRACE_NOTE_TESTS)
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
const wrapped = traced.some((item) => item.requests.some((request) => request.sent !== undefined && wrappedInData(request.sent)));
|
|
584
|
+
// The "one of these requests failing" answer is only honest when a listed request really failed.
|
|
585
|
+
const anyFailed = traced.some((item) => item.requests.some(failedRequest));
|
|
322
586
|
const rows = [];
|
|
323
|
-
for (const [index, item] of
|
|
324
|
-
const
|
|
587
|
+
for (const [index, item] of traced.entries()) {
|
|
588
|
+
const failed = item.requests.filter(failedRequest);
|
|
589
|
+
const unanswered = item.requests.filter(unansweredRequest);
|
|
590
|
+
const answered = item.requests.filter((request) => !failedRequest(request) && !unansweredRequest(request));
|
|
591
|
+
let room = MAX_TRACE_NOTE_REQUESTS;
|
|
325
592
|
const parts = [];
|
|
326
|
-
if (
|
|
327
|
-
|
|
593
|
+
if (failed.length > 0) {
|
|
594
|
+
const shown = failed.slice(0, room);
|
|
595
|
+
room -= shown.length;
|
|
596
|
+
parts.push(`failed: ${shown.map(requestLine).join('; ')}`);
|
|
597
|
+
}
|
|
598
|
+
if (unanswered.length > 0 && room > 0) {
|
|
599
|
+
const shown = unanswered.slice(0, room);
|
|
600
|
+
room -= shown.length;
|
|
601
|
+
parts.push(`no response recorded — still in flight when the test ended, so not a server failure: ` +
|
|
602
|
+
shown.map(requestLabel).join('; '));
|
|
603
|
+
}
|
|
604
|
+
if (answered.length > 0 && room > 0) {
|
|
605
|
+
parts.push(`answered: ${answered.slice(0, room).map(requestLine).join('; ')}`);
|
|
606
|
+
}
|
|
328
607
|
if (item.console.length > 0)
|
|
329
608
|
parts.push(`console: ${item.console
|
|
330
609
|
.slice(0, 2)
|
|
@@ -332,14 +611,27 @@ export function failureTraceNote(items, target) {
|
|
|
332
611
|
.join(' | ')}`);
|
|
333
612
|
rows.push(` ${index + 1}. ${whereOf(item)} — ${parts.join(' — ')}`);
|
|
334
613
|
}
|
|
335
|
-
|
|
336
|
-
|
|
614
|
+
// Distinct locations the note did not name: the repeats collapse, so the count is what is left
|
|
615
|
+
// to look at, not how many zips Playwright wrote.
|
|
616
|
+
const distinct = new Set(candidates.map((item, index) => (item.spec === null ? `#${index}` : whereOf(item))));
|
|
617
|
+
const more = distinct.size > traced.length
|
|
618
|
+
? `\n … and ${distinct.size - traced.length} more traced failures`
|
|
337
619
|
: '';
|
|
338
620
|
return (`The platform read the failing tests' own Playwright traces from the run against ${target} ` +
|
|
339
|
-
`(
|
|
340
|
-
|
|
341
|
-
'
|
|
342
|
-
|
|
621
|
+
`(what each page sent and got: the requests that failed, the ones that got no response, and ` +
|
|
622
|
+
`its last answered ones):\n` +
|
|
623
|
+
`${rows.join('\n')}${more}\n` +
|
|
624
|
+
(anyFailed
|
|
625
|
+
? 'A click that changed nothing is usually one of these requests failing, not the selector: ' +
|
|
626
|
+
'read the handler this route reaches in your own source, and stop `catch`-ing the request of a ' +
|
|
627
|
+
'user action silently — log it.'
|
|
628
|
+
: 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
|
|
629
|
+
'page, not the API: read the state the failing test waited on.') +
|
|
630
|
+
(wrapped
|
|
631
|
+
? ' The API received a body wrapped in `data`: app-http’s `post(url, data, config)` / ' +
|
|
632
|
+
'`put(url, data, config)` / `patch(url, data, config)` take the body as their SECOND ' +
|
|
633
|
+
"argument — `post('/users', { user })`, not `post('/users', { data: { user } })`."
|
|
634
|
+
: ''));
|
|
343
635
|
}
|
|
344
636
|
/** The Playwright trace modes that leave a zip behind for a FAILING test, whatever the retries. */
|
|
345
637
|
const RECORDING_TRACE_MODES = ['on', 'retain-on-failure'];
|
package/dist/utilities.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
|
+
import { type RunFailureKind } from './failure-kind.js';
|
|
7
8
|
/**
|
|
8
9
|
* Shell-safe quoting using single quotes. Unlike JSON.stringify (double quotes),
|
|
9
10
|
* single-quoted strings prevent command substitution ($(), backticks) and variable expansion.
|
|
@@ -906,6 +907,20 @@ export interface FailureCause {
|
|
|
906
907
|
* for a duration group, whose blocks carried no bodies.
|
|
907
908
|
*/
|
|
908
909
|
evidence: string[];
|
|
910
|
+
/**
|
|
911
|
+
* The suite's own per-test budget in ms, when the cause is the test-budget kill
|
|
912
|
+
* (`Test timeout of 15000ms exceeded`). The signature normalizes the number away
|
|
913
|
+
* ({@link normalizedSignature}) so equal causes group, and that number is the one fact that
|
|
914
|
+
* predicts a test spending its budget on a repeated action (xr-molecule-flash-m110 +20:44).
|
|
915
|
+
*/
|
|
916
|
+
budgetMs?: number;
|
|
917
|
+
/**
|
|
918
|
+
* The layer the failure came from ({@link classifyRunFailure}). A `connection` cause never
|
|
919
|
+
* reached the app, so it is NOT a cause the app's code can fix: the note says to re-run it
|
|
920
|
+
* once and read the app's log on a repeat, instead of sending a fix at a handler
|
|
921
|
+
* (xr-molecule-flash-m110 +18:42 rewrote a CORS middleware for a 20 s connection timeout).
|
|
922
|
+
*/
|
|
923
|
+
kind: RunFailureKind;
|
|
909
924
|
}
|
|
910
925
|
/**
|
|
911
926
|
* Every failure a run names, grouped by the cause behind it — one group per cause, with the
|
|
@@ -949,17 +964,22 @@ export declare function failuresByCause(log: string, excluded?: readonly string[
|
|
|
949
964
|
* exactly those tests ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the
|
|
950
965
|
* whole suite.
|
|
951
966
|
*
|
|
967
|
+
* A run whose failures are ALL {@link NO_BASE_URL_ERROR} is not a set of causes at all: it is one
|
|
968
|
+
* configuration failure, said in one line ({@link noBaseUrlConfigNote}) rather than a list that
|
|
969
|
+
* reads as ten defects in the app.
|
|
970
|
+
*
|
|
952
971
|
* @param log - The finished run's output.
|
|
953
972
|
* @param excluded - The test titles the request carves out, if the caller knows them.
|
|
954
973
|
* @param run - How the run was started, when the caller knows it: the narrowed re-run is built
|
|
955
|
-
* from `command`/`cwd`, and `log` (the path the output was written to) is what the
|
|
956
|
-
* sentence greps when more causes exist than the result has room for.
|
|
974
|
+
* from `command`/`cwd`/`config`, and `log` (the path the output was written to) is what the
|
|
975
|
+
* "left out" sentence greps when more causes exist than the result has room for.
|
|
957
976
|
* @returns The note, or null.
|
|
958
977
|
*/
|
|
959
978
|
export declare function failureCausesNote(log: string, excluded?: readonly string[], run?: {
|
|
960
979
|
command: string;
|
|
961
980
|
cwd?: string;
|
|
962
981
|
log?: string;
|
|
982
|
+
config?: string | null;
|
|
963
983
|
}): string | null;
|
|
964
984
|
/**
|
|
965
985
|
* The command that re-runs the tests at `locations` the way the run IN FLIGHT ran them: the same
|
|
@@ -973,8 +993,8 @@ export declare function failureCausesNote(log: string, excluded?: readonly strin
|
|
|
973
993
|
* the same app and database, and that is exactly what is refused while the run walks
|
|
974
994
|
* ({@link suiteRunInTheWay}).
|
|
975
995
|
*
|
|
976
|
-
* @param run - The run in flight: the command it was started with, as it was run,
|
|
977
|
-
*
|
|
996
|
+
* @param run - The run in flight: the command it was started with, as it was run, the directory
|
|
997
|
+
* that command ran in, and the config it loaded when the caller knows it.
|
|
978
998
|
* @param locations - The failing `file:line`s to re-run, as the run's log printed them.
|
|
979
999
|
* @param excluded - The test titles the request carves out: the re-run inverts them out
|
|
980
1000
|
* (`--grep-invert`) so it cannot report them again.
|
|
@@ -983,6 +1003,7 @@ export declare function failureCausesNote(log: string, excluded?: readonly strin
|
|
|
983
1003
|
export declare function rerunOfRunningSuite(run: {
|
|
984
1004
|
command: string;
|
|
985
1005
|
cwd?: string;
|
|
1006
|
+
config?: string | null;
|
|
986
1007
|
}, locations: readonly string[], excluded?: readonly string[]): string | null;
|
|
987
1008
|
/**
|
|
988
1009
|
* The sentence that names a cause repeating among a running suite's failures, mid-run.
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|