@molecule/api-ai-tools 1.6.6 → 1.7.0
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 +290 -28
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +213 -70
- package/dist/trace-artifacts.d.ts +147 -0
- package/dist/trace-artifacts.d.ts.map +1 -0
- package/dist/trace-artifacts.js +467 -0
- package/dist/utilities.d.ts +77 -18
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +112 -26
- package/package.json +2 -2
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a failed Playwright run's own trace files say about the requests its page made — read
|
|
3
|
+
* headlessly, in the sandbox, from the `trace.zip` files the run left behind.
|
|
4
|
+
*
|
|
5
|
+
* X1 xr-molecule-flash-m100 (+51:19 → +53:00, 2026-10-16) ended with three failures against the
|
|
6
|
+
* deployed URL — `social.spec.ts:22` and `:153` waited for a "Unfollow" button that never appeared
|
|
7
|
+
* after the click, and `url-navigation.spec.ts:234` was bounced to `/login` mid-test — and nothing
|
|
8
|
+
* in the run's result said why. The app's own `toggleFollow` swallowed the error of its request
|
|
9
|
+
* with a bare `catch` and a comment, so the page showed nothing; the ONE artifact
|
|
10
|
+
* that held the answer, the failing tests' own traces, was never read. The executor spent the
|
|
11
|
+
* last minutes of the run writing throwaway Chromium probes against localhost — where the same
|
|
12
|
+
* tests passed — and finished chasing an artifact of its own probe
|
|
13
|
+
* (`waitForURL('/')` resolved against a `baseURL` its script did not have).
|
|
14
|
+
*
|
|
15
|
+
* A trace is not a debugging aid that has to be enabled by hand: Playwright writes the request
|
|
16
|
+
* log and the console into `test-results/<test>/trace.zip` whenever a run retains traces, and the
|
|
17
|
+
* evidence a swallowed failure hides is exactly one line of it — `POST /api/articles/x/favorite →
|
|
18
|
+
* 401`. So the platform reads it (bounded, per test) and puts it in the run's result
|
|
19
|
+
* ({@link failureTraceNote}), instead of leaving the executor to build its own browser to find out.
|
|
20
|
+
*
|
|
21
|
+
* Everything here is text in, text out: the sandbox runs {@link traceArtifactsScript} (Node, no
|
|
22
|
+
* dependencies — Playwright's zip reader is not importable from a project) and prints one
|
|
23
|
+
* `TRACE <json>` line per trace; the platform parses those lines ({@link parseTraceArtifacts}) and
|
|
24
|
+
* writes the note ({@link failureTraceNote}). Both halves are covered by a test that runs the
|
|
25
|
+
* script against a `trace.zip` produced by a real Playwright run, in the sandbox's own shape.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { isDiagnosisRun } from './suite-overlap.js';
|
|
30
|
+
import { isPlaywrightRunCommand } from './utilities.js';
|
|
31
|
+
/** How many traces one read reports, newest first. */
|
|
32
|
+
export const MAX_TRACE_ARTIFACTS = 12;
|
|
33
|
+
/** How many requests one test's trace contributes. */
|
|
34
|
+
export const MAX_TRACED_REQUESTS = 6;
|
|
35
|
+
/** How many console errors one test's trace contributes. */
|
|
36
|
+
export const MAX_TRACED_CONSOLE = 4;
|
|
37
|
+
/** How many tests the note in a run's result names. */
|
|
38
|
+
export const MAX_TRACE_NOTE_TESTS = 5;
|
|
39
|
+
/** How many requests the note names per test. */
|
|
40
|
+
export const MAX_TRACE_NOTE_REQUESTS = 4;
|
|
41
|
+
/** How long a URL the note prints, before the middle is cut. */
|
|
42
|
+
export const TRACE_NOTE_URL_CHARS = 96;
|
|
43
|
+
/**
|
|
44
|
+
* The Node program the sandbox runs to read a failed run's traces: `node <script> <dir>` prints one
|
|
45
|
+
* `TRACE <json>` line per `trace.zip` under `<dir>` (newest first, {@link MAX_TRACE_ARTIFACTS} of
|
|
46
|
+
* them), each with the test it belongs to, the requests its page made and its console errors.
|
|
47
|
+
*
|
|
48
|
+
* It is a standalone program, not an import, for the reason `@molecule/api-code-sandbox` runs it:
|
|
49
|
+
* Playwright's own zip reader is not reachable from a project's `node_modules`, and a dependency
|
|
50
|
+
* on `unzip` would make the read fail on every sandbox that has none. The zip is read with the
|
|
51
|
+
* central directory and `zlib.inflateRawSync` — about forty lines, and the only thing that has to
|
|
52
|
+
* be true of a `trace.zip` is that it IS one.
|
|
53
|
+
*
|
|
54
|
+
* A request is kept over a static asset when it failed (`status >= 400`) or never answered
|
|
55
|
+
* (`status === 0`), and over anything else when it is the page talking to the app
|
|
56
|
+
* (`fetch`/`xhr`/`document`): a failing test's trace holds the whole page load, and the answer is
|
|
57
|
+
* always at the end of it.
|
|
58
|
+
*
|
|
59
|
+
* @returns The program's source, to write to a file and run.
|
|
60
|
+
*/
|
|
61
|
+
export const traceArtifactsScript = () => `
|
|
62
|
+
const fs = require('node:fs')
|
|
63
|
+
const path = require('node:path')
|
|
64
|
+
const zlib = require('node:zlib')
|
|
65
|
+
|
|
66
|
+
const ROOT = process.argv[2] || '/workspace'
|
|
67
|
+
const MAX_TRACES = ${MAX_TRACE_ARTIFACTS}
|
|
68
|
+
const MAX_REQUESTS = ${MAX_TRACED_REQUESTS}
|
|
69
|
+
const MAX_CONSOLE = ${MAX_TRACED_CONSOLE}
|
|
70
|
+
const MAX_URL = 200
|
|
71
|
+
|
|
72
|
+
function walk(dir, depth, out) {
|
|
73
|
+
if (depth < 0) return
|
|
74
|
+
let entries
|
|
75
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch (e) { return }
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue
|
|
78
|
+
const full = path.join(dir, entry.name)
|
|
79
|
+
if (entry.isDirectory()) { walk(full, depth - 1, out); continue }
|
|
80
|
+
if (entry.name !== 'trace.zip') continue
|
|
81
|
+
let mtime = 0
|
|
82
|
+
try { mtime = fs.statSync(full).mtimeMs } catch (e) { mtime = 0 }
|
|
83
|
+
out.push({ file: full, mtime: mtime })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function zipEntries(buf) {
|
|
88
|
+
let eocd = -1
|
|
89
|
+
const floor = Math.max(0, buf.length - 66000)
|
|
90
|
+
for (let i = buf.length - 22; i >= floor; i--) {
|
|
91
|
+
if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break }
|
|
92
|
+
}
|
|
93
|
+
if (eocd === -1) return []
|
|
94
|
+
const count = buf.readUInt16LE(eocd + 10)
|
|
95
|
+
let at = buf.readUInt32LE(eocd + 16)
|
|
96
|
+
const out = []
|
|
97
|
+
for (let n = 0; n < count; n++) {
|
|
98
|
+
if (at + 46 > buf.length || buf.readUInt32LE(at) !== 0x02014b50) break
|
|
99
|
+
const nameLen = buf.readUInt16LE(at + 28)
|
|
100
|
+
const extraLen = buf.readUInt16LE(at + 30)
|
|
101
|
+
const commentLen = buf.readUInt16LE(at + 32)
|
|
102
|
+
out.push({
|
|
103
|
+
name: buf.toString('utf8', at + 46, at + 46 + nameLen),
|
|
104
|
+
method: buf.readUInt16LE(at + 10),
|
|
105
|
+
size: buf.readUInt32LE(at + 20),
|
|
106
|
+
at: buf.readUInt32LE(at + 42),
|
|
107
|
+
})
|
|
108
|
+
at += 46 + nameLen + extraLen + commentLen
|
|
109
|
+
}
|
|
110
|
+
return out
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function zipData(buf, entry) {
|
|
114
|
+
const at = entry.at
|
|
115
|
+
if (at + 30 > buf.length || buf.readUInt32LE(at) !== 0x04034b50) return null
|
|
116
|
+
const start = at + 30 + buf.readUInt16LE(at + 26) + buf.readUInt16LE(at + 28)
|
|
117
|
+
const raw = buf.subarray(start, start + entry.size)
|
|
118
|
+
if (entry.method === 0) return raw
|
|
119
|
+
if (entry.method === 8) { try { return zlib.inflateRawSync(raw) } catch (e) { return null } }
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ndjson(buf) {
|
|
124
|
+
const out = []
|
|
125
|
+
if (!buf) return out
|
|
126
|
+
for (const line of buf.toString('utf8').split('\\n')) {
|
|
127
|
+
if (!line) continue
|
|
128
|
+
try { out.push(JSON.parse(line)) } catch (e) { /* a partial final line */ }
|
|
129
|
+
}
|
|
130
|
+
return out
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function messageOf(value) {
|
|
134
|
+
let at = value
|
|
135
|
+
for (let i = 0; i < 4 && at && typeof at === 'object'; i++) {
|
|
136
|
+
if (typeof at.message === 'string') return at.message
|
|
137
|
+
at = at.error
|
|
138
|
+
}
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let origin = ''
|
|
143
|
+
function pathOf(url) {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = new URL(url)
|
|
146
|
+
return parsed.origin === origin ? parsed.pathname + parsed.search : url
|
|
147
|
+
} catch (e) { return url }
|
|
148
|
+
}
|
|
149
|
+
function originOf(url) {
|
|
150
|
+
try { return new URL(url).origin } catch (e) { return '' }
|
|
151
|
+
}
|
|
152
|
+
const SUITE_DIRS = ['e2e', 'specs', 'tests', 'test', '__tests__']
|
|
153
|
+
function specPathOf(file) {
|
|
154
|
+
const parts = file.split('/')
|
|
155
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
156
|
+
if (SUITE_DIRS.indexOf(parts[i]) !== -1) return parts.slice(i).join('/')
|
|
157
|
+
}
|
|
158
|
+
const rel = path.relative(ROOT, file)
|
|
159
|
+
return rel && rel.slice(0, 2) !== '..' ? rel : file
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const found = []
|
|
163
|
+
walk(ROOT, 5, found)
|
|
164
|
+
found.sort(function (a, b) { return b.mtime - a.mtime })
|
|
165
|
+
|
|
166
|
+
for (const one of found.slice(0, MAX_TRACES)) {
|
|
167
|
+
let buf
|
|
168
|
+
try { buf = fs.readFileSync(one.file) } catch (e) { continue }
|
|
169
|
+
let entries
|
|
170
|
+
try { entries = zipEntries(buf) } catch (e) { continue }
|
|
171
|
+
const network = []
|
|
172
|
+
const trace = []
|
|
173
|
+
for (const entry of entries) {
|
|
174
|
+
if (/\\.network$/.test(entry.name)) network.push.apply(network, ndjson(zipData(buf, entry)))
|
|
175
|
+
else if (/\\.trace$/.test(entry.name)) trace.push.apply(trace, ndjson(zipData(buf, entry)))
|
|
176
|
+
}
|
|
177
|
+
const requests = []
|
|
178
|
+
for (const line of network) {
|
|
179
|
+
if (!line || line.type !== 'resource-snapshot') continue
|
|
180
|
+
const snapshot = line.snapshot || {}
|
|
181
|
+
const request = snapshot.request || {}
|
|
182
|
+
if (!request.url || !request.method) continue
|
|
183
|
+
const response = snapshot.response || {}
|
|
184
|
+
const type = String(snapshot._resourceType || '')
|
|
185
|
+
if (!origin) origin = type === 'document' ? originOf(String(request.url)) : ''
|
|
186
|
+
requests.push({
|
|
187
|
+
method: String(request.method).toUpperCase(),
|
|
188
|
+
path: String(request.url).slice(0, MAX_URL),
|
|
189
|
+
status: Number(response.status) || 0,
|
|
190
|
+
type: type,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
if (!origin) {
|
|
194
|
+
const first = requests.find(function (r) { return r.type === 'document' }) || requests[0]
|
|
195
|
+
origin = first ? originOf(first.path) : ''
|
|
196
|
+
}
|
|
197
|
+
for (const r of requests) r.path = pathOf(r.path).slice(0, MAX_URL)
|
|
198
|
+
const failing = []
|
|
199
|
+
const api = []
|
|
200
|
+
const rest = []
|
|
201
|
+
for (let i = 0; i < requests.length; i++) {
|
|
202
|
+
const r = requests[i]
|
|
203
|
+
if (r.status === 0 || r.status >= 400) failing.push(i)
|
|
204
|
+
else if (r.type === 'fetch' || r.type === 'xhr' || r.type === 'document') api.push(i)
|
|
205
|
+
else rest.push(i)
|
|
206
|
+
}
|
|
207
|
+
const keep = failing.slice(-MAX_REQUESTS).concat(api.slice(-MAX_REQUESTS)).concat(rest.slice(-2))
|
|
208
|
+
const chosen = []
|
|
209
|
+
const seen = new Set()
|
|
210
|
+
for (const i of keep.sort(function (a, b) {
|
|
211
|
+
const wrong = requests[a].status === 0 || requests[a].status >= 400 ? 0 : 1
|
|
212
|
+
const other = requests[b].status === 0 || requests[b].status >= 400 ? 0 : 1
|
|
213
|
+
return wrong === other ? a - b : wrong - other
|
|
214
|
+
})) {
|
|
215
|
+
const r = requests[i]
|
|
216
|
+
const key = r.method + ' ' + r.path + ' ' + r.status
|
|
217
|
+
if (seen.has(key)) continue
|
|
218
|
+
seen.add(key)
|
|
219
|
+
chosen.push({ method: r.method, path: r.path, status: r.status })
|
|
220
|
+
}
|
|
221
|
+
const consoleErrors = []
|
|
222
|
+
let spec = null
|
|
223
|
+
let line = 0
|
|
224
|
+
for (const entry of trace) {
|
|
225
|
+
if (!entry) continue
|
|
226
|
+
if (entry.type === 'console' && entry.messageType === 'error' && typeof entry.text === 'string')
|
|
227
|
+
consoleErrors.push(entry.text)
|
|
228
|
+
else if (entry.type === 'event' && entry.method === 'pageError') {
|
|
229
|
+
const message = messageOf(entry.params)
|
|
230
|
+
if (message) consoleErrors.push(message)
|
|
231
|
+
} else if (entry.type === 'before' && Array.isArray(entry.stack) && entry.stack.length) {
|
|
232
|
+
const frame = entry.stack[0] || {}
|
|
233
|
+
if (typeof frame.file === 'string' && /\\.(?:spec|test)\\.[cm]?[jt]sx?$/.test(frame.file)) {
|
|
234
|
+
spec = specPathOf(frame.file)
|
|
235
|
+
line = Number(frame.line) || 0
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Chromium reports every failed request as a console error too; where the page logged something
|
|
240
|
+
// of its own about it, that is the line worth reading.
|
|
241
|
+
const specific = consoleErrors.filter(function (text) { return !/^Failed to load resource/.test(text) })
|
|
242
|
+
const keptConsole = specific.length > 0 ? specific : consoleErrors
|
|
243
|
+
if (chosen.length === 0 && keptConsole.length === 0) continue
|
|
244
|
+
process.stdout.write('TRACE ' + JSON.stringify({
|
|
245
|
+
dir: path.relative(ROOT, path.dirname(one.file)),
|
|
246
|
+
spec: spec,
|
|
247
|
+
line: line,
|
|
248
|
+
requests: chosen.slice(-MAX_REQUESTS),
|
|
249
|
+
console: keptConsole.slice(0, MAX_CONSOLE).map(function (text) { return text.slice(0, 300) }),
|
|
250
|
+
}) + '\\n')
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
/**
|
|
254
|
+
* The traces {@link traceArtifactsScript} printed, parsed.
|
|
255
|
+
*
|
|
256
|
+
* A line that is not a complete `TRACE <json>` (a truncated read, a program that died mid-write) is
|
|
257
|
+
* dropped: a partial trace is not evidence about the test it came from.
|
|
258
|
+
*
|
|
259
|
+
* @param stdout - The program's output.
|
|
260
|
+
* @returns One entry per trace it printed.
|
|
261
|
+
*/
|
|
262
|
+
export function parseTraceArtifacts(stdout) {
|
|
263
|
+
const out = [];
|
|
264
|
+
for (const line of stdout.split('\n')) {
|
|
265
|
+
if (!line.startsWith('TRACE '))
|
|
266
|
+
continue;
|
|
267
|
+
try {
|
|
268
|
+
const raw = JSON.parse(line.slice('TRACE '.length));
|
|
269
|
+
const requests = Array.isArray(raw.requests) ? raw.requests : [];
|
|
270
|
+
const consoleMessages = Array.isArray(raw.console) ? raw.console : [];
|
|
271
|
+
out.push({
|
|
272
|
+
spec: typeof raw.spec === 'string' ? raw.spec : null,
|
|
273
|
+
line: typeof raw.line === 'number' ? raw.line : 0,
|
|
274
|
+
requests: requests
|
|
275
|
+
.filter((r) => typeof r === 'object' && r !== null)
|
|
276
|
+
.map((r) => ({
|
|
277
|
+
method: String(r.method ?? ''),
|
|
278
|
+
path: String(r.path ?? ''),
|
|
279
|
+
status: Number(r.status ?? 0) || 0,
|
|
280
|
+
})),
|
|
281
|
+
console: consoleMessages.map(String),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch (_error) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
/** Where a trace's test is: the spec and line it names, else the folder Playwright wrote it in. */
|
|
291
|
+
function whereOf(item) {
|
|
292
|
+
if (!item.spec)
|
|
293
|
+
return 'a failing test';
|
|
294
|
+
return item.line > 0 ? `${item.spec}:${item.line}` : item.spec;
|
|
295
|
+
}
|
|
296
|
+
/** One request as the note writes it: `POST /api/articles/x/favorite → 401`. */
|
|
297
|
+
function requestLine(request) {
|
|
298
|
+
const url = request.path.length > TRACE_NOTE_URL_CHARS
|
|
299
|
+
? `${request.path.slice(0, TRACE_NOTE_URL_CHARS)}…`
|
|
300
|
+
: request.path;
|
|
301
|
+
return `${request.method} ${url} → ${request.status === 0 ? 'no response' : request.status}`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
305
|
+
* what came back.
|
|
306
|
+
*
|
|
307
|
+
* This is the answer to the question a swallowed error hides — a click that changed nothing is
|
|
308
|
+
* usually a request that failed — so the note leads with the failing requests and says what to do
|
|
309
|
+
* with them: read the handler the route reaches, not the selector. It is BOUNDED to
|
|
310
|
+
* {@link MAX_TRACE_NOTE_TESTS} tests with {@link MAX_TRACE_NOTE_REQUESTS} requests each, because it
|
|
311
|
+
* rides in a result the model pays for.
|
|
312
|
+
*
|
|
313
|
+
* @param items - The traces {@link parseTraceArtifacts} read.
|
|
314
|
+
* @param target - Where the run went — the deployed host, as the note names it.
|
|
315
|
+
* @returns The note, or null when no trace held a request or a console error.
|
|
316
|
+
*/
|
|
317
|
+
export function failureTraceNote(items, target) {
|
|
318
|
+
const withEvidence = items.filter((item) => item.requests.length > 0 || item.console.length > 0);
|
|
319
|
+
if (withEvidence.length === 0)
|
|
320
|
+
return null;
|
|
321
|
+
const rows = [];
|
|
322
|
+
for (const [index, item] of withEvidence.slice(0, MAX_TRACE_NOTE_TESTS).entries()) {
|
|
323
|
+
const requests = item.requests.slice(-MAX_TRACE_NOTE_REQUESTS).map(requestLine);
|
|
324
|
+
const parts = [];
|
|
325
|
+
if (requests.length > 0)
|
|
326
|
+
parts.push(requests.join('; '));
|
|
327
|
+
if (item.console.length > 0)
|
|
328
|
+
parts.push(`console: ${item.console
|
|
329
|
+
.slice(0, 2)
|
|
330
|
+
.map((text) => text.replace(/\s+/g, ' ').trim().slice(0, 160))
|
|
331
|
+
.join(' | ')}`);
|
|
332
|
+
rows.push(` ${index + 1}. ${whereOf(item)} — ${parts.join(' — ')}`);
|
|
333
|
+
}
|
|
334
|
+
const more = withEvidence.length > MAX_TRACE_NOTE_TESTS
|
|
335
|
+
? `\n … and ${withEvidence.length - MAX_TRACE_NOTE_TESTS} more traced failures`
|
|
336
|
+
: '';
|
|
337
|
+
return (`The platform read the failing tests' own Playwright traces from the run against ${target} ` +
|
|
338
|
+
`(the last requests each page made and what came back):\n${rows.join('\n')}${more}\n` +
|
|
339
|
+
'A click that changed nothing is usually one of these requests failing, not the selector: ' +
|
|
340
|
+
'read the handler this route reaches in your own source, and stop `catch`-ing the request of a ' +
|
|
341
|
+
'user action silently — log it.');
|
|
342
|
+
}
|
|
343
|
+
/** The Playwright trace modes that leave a zip behind for a FAILING test, whatever the retries. */
|
|
344
|
+
const RECORDING_TRACE_MODES = ['on', 'retain-on-failure'];
|
|
345
|
+
/**
|
|
346
|
+
* A `trace:` setting and its value, however the config spells it (`trace: 'on'`,
|
|
347
|
+
* `trace: "on-first-retry"`, `trace: { mode: 'retain-on-failure', screenshots: true }`). The value
|
|
348
|
+
* is captured from the object form's `mode` or from the plain one.
|
|
349
|
+
*/
|
|
350
|
+
const TRACE_SETTING = /trace:\s*(?:\{[^{}]*?mode:\s*)?['"]?(on-first-retry|on-all-retries|retain-on-failure|on|off)(?![\w-])/g;
|
|
351
|
+
/**
|
|
352
|
+
* Whether a Playwright config records traces for the tests that fail.
|
|
353
|
+
*
|
|
354
|
+
* Only `on` and `retain-on-failure` keep a `trace.zip` for a failure whatever the retries do: the
|
|
355
|
+
* two retry modes need a retry to happen at all, and a diagnosis run is given `--retries=0`
|
|
356
|
+
* (`streamingBackgroundCommand`), so on X1 xr-molecule-flash-m100 the run's own
|
|
357
|
+
* `trace: 'on-first-retry'` recorded nothing — which is what left the failing tests' network log
|
|
358
|
+
* unreadable. A config that sets no `trace:` at all records nothing either (Playwright's default
|
|
359
|
+
* is off); so does one that cannot be read.
|
|
360
|
+
*
|
|
361
|
+
* @param configText - The config's source, or '' when none could be read.
|
|
362
|
+
* @returns True when the config records traces for failures.
|
|
363
|
+
*/
|
|
364
|
+
export function suiteRecordsTraces(configText) {
|
|
365
|
+
const text = configText.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/[^\n]*/g, '$1');
|
|
366
|
+
return [...text.matchAll(TRACE_SETTING)].some((m) => RECORDING_TRACE_MODES.includes(m[1] ?? ''));
|
|
367
|
+
}
|
|
368
|
+
/** The `--trace` flag this platform adds, and the only value it adds it with. */
|
|
369
|
+
export const DIAGNOSIS_TRACE_FLAG = '--trace=retain-on-failure';
|
|
370
|
+
/**
|
|
371
|
+
* Whether a command already decides its own tracing: `--trace=on`, `--trace retain-on-failure`.
|
|
372
|
+
* A run that names one is left exactly as the executor wrote it.
|
|
373
|
+
*/
|
|
374
|
+
function namesTraceMode(command) {
|
|
375
|
+
return /(?:^|[\s;&|(])--trace(?:=|\s)/.test(command);
|
|
376
|
+
}
|
|
377
|
+
/** The config paths a candidate directory could hold, in the order Playwright itself looks. */
|
|
378
|
+
const CONFIG_NAMES = [
|
|
379
|
+
'playwright.config.ts',
|
|
380
|
+
'playwright.config.mts',
|
|
381
|
+
'playwright.config.js',
|
|
382
|
+
'playwright.config.mjs',
|
|
383
|
+
'playwright.config.cjs',
|
|
384
|
+
];
|
|
385
|
+
/** The directories a command changes into, in order, as it writes them. */
|
|
386
|
+
function cdDirectories(command) {
|
|
387
|
+
const out = [];
|
|
388
|
+
for (const m of command.matchAll(/(?:^|[\s;&|(])cd\s+(?:'([^']+)'|"([^"]+)"|([^\s;&|)]+))/g)) {
|
|
389
|
+
const dir = m[1] ?? m[2] ?? m[3];
|
|
390
|
+
if (dir && !out.includes(dir))
|
|
391
|
+
out.push(dir);
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
/** A path joined under a base, unless it is already absolute. */
|
|
396
|
+
function under(base, path) {
|
|
397
|
+
if (path.startsWith('/'))
|
|
398
|
+
return path;
|
|
399
|
+
return `${base.replace(/\/+$/, '')}/${path.replace(/^\.\//, '')}`;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The config a Playwright command reads, read from the backend — the file its own `--config`
|
|
403
|
+
* names (resolved against the directory the command starts in, then against each `cd` it does
|
|
404
|
+
* first), else the first `playwright.config.*` in those directories.
|
|
405
|
+
*
|
|
406
|
+
* Best-effort and bounded: at most four candidate files are read, and a read that fails is a
|
|
407
|
+
* config that records nothing — the caller's `--trace` flag is what makes a missing trace
|
|
408
|
+
* impossible either way.
|
|
409
|
+
*
|
|
410
|
+
* @param command - The shell command.
|
|
411
|
+
* @param cwd - The directory the command starts in.
|
|
412
|
+
* @param readFile - The backend's file read.
|
|
413
|
+
* @returns The config's text, or '' when none could be read.
|
|
414
|
+
*/
|
|
415
|
+
async function readRunConfig(command, cwd, readFile) {
|
|
416
|
+
const bases = [cwd, ...cdDirectories(command).map((dir) => under(cwd, dir))].slice(0, 2);
|
|
417
|
+
const config = /(?:^|[\s;&|(])--config(?:=|\s+)(?:'([^']+)'|"([^"]+)"|([^\s;&|)]+))/.exec(command);
|
|
418
|
+
const explicit = config?.[1] ?? config?.[2] ?? config?.[3] ?? null;
|
|
419
|
+
const candidates = explicit
|
|
420
|
+
? bases.map((base) => under(base, explicit))
|
|
421
|
+
: bases.flatMap((base) => CONFIG_NAMES.map((name) => `${base.replace(/\/+$/, '')}/${name}`));
|
|
422
|
+
for (const candidate of candidates.slice(0, 4)) {
|
|
423
|
+
try {
|
|
424
|
+
const text = await readFile(candidate);
|
|
425
|
+
if (text)
|
|
426
|
+
return text;
|
|
427
|
+
}
|
|
428
|
+
catch (_error) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return '';
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The command a DIAGNOSIS run needs to leave a readable trace behind, when the suite's config
|
|
436
|
+
* would record none.
|
|
437
|
+
*
|
|
438
|
+
* The failing tests' requests are only readable if the run wrote them down (see the module doc),
|
|
439
|
+
* and the config is where the decision about that lives — the platform does not edit the suite's
|
|
440
|
+
* config, so it passes the flag instead, and only where it can do no harm: on a run narrowed to a
|
|
441
|
+
* part of the suite or capped ({@link isDiagnosisRun}), never on the whole-suite proof run, and
|
|
442
|
+
* never over a `--trace` the executor wrote itself. A capped walk is exactly the run whose
|
|
443
|
+
* failures someone is about to work on; the proof run is graded as it is.
|
|
444
|
+
*
|
|
445
|
+
* @param args - The command, the directory it starts in, and the backend's file read.
|
|
446
|
+
* @returns The command to run and the sentence to report, or the command unchanged.
|
|
447
|
+
*/
|
|
448
|
+
export async function diagnosisTraceCommand(args) {
|
|
449
|
+
const unchanged = { command: args.command, change: null };
|
|
450
|
+
// The proof run is the one run this must never touch: its flags are graded.
|
|
451
|
+
if (!isDiagnosisRun(args.command))
|
|
452
|
+
return unchanged;
|
|
453
|
+
if (namesTraceMode(args.command))
|
|
454
|
+
return unchanged;
|
|
455
|
+
// Only a Playwright run leaves a trace to read.
|
|
456
|
+
if (!isPlaywrightRunCommand(args.command))
|
|
457
|
+
return unchanged;
|
|
458
|
+
const config = await readRunConfig(args.command, args.cwd, args.readFile);
|
|
459
|
+
if (suiteRecordsTraces(config))
|
|
460
|
+
return unchanged;
|
|
461
|
+
return {
|
|
462
|
+
command: `${args.command.trimEnd()} ${DIAGNOSIS_TRACE_FLAG}`,
|
|
463
|
+
change: `Added \`${DIAGNOSIS_TRACE_FLAG}\`: the suite's config records no trace for a failure` +
|
|
464
|
+
`${/trace:/.test(config) ? '' : ' (it sets none)'}, and this diagnosis run's failures are ` +
|
|
465
|
+
'what the platform reads the network log from. The proof run keeps the suite’s own settings.',
|
|
466
|
+
};
|
|
467
|
+
}
|
package/dist/utilities.d.ts
CHANGED
|
@@ -110,6 +110,20 @@ export declare function outputIsWithheldUntilExit(command: string): boolean;
|
|
|
110
110
|
* @returns The target as written, or null (`/dev/null` and `&1`/`&2` are not files).
|
|
111
111
|
*/
|
|
112
112
|
export declare function outputRedirectTarget(command: string): string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Playwright's switch for printing each failure's own error as that test fails, instead of only in
|
|
115
|
+
* the summary at the end: `list` reads it in its constructor (`ListReporterOptions.printFailuresInline`,
|
|
116
|
+
* `playwright/lib/reporters/list.js` — 1.63, the version the toolchain pins), and when it prints
|
|
117
|
+
* inline the end-of-run repeat is suppressed with it (`epilogue(!printFailuresInline)`), so one
|
|
118
|
+
* reporter still prints each failure exactly once.
|
|
119
|
+
*
|
|
120
|
+
* A background suite is put on `list` (see {@link streamingBackgroundCommand}) and this with it, so
|
|
121
|
+
* the log names its causes WHILE it walks. Without it the log's only failure lines are `✘` rows:
|
|
122
|
+
* X1 xr-molecule-flash-m99 (+19:27 → +21:00, 2026-10-16) was told to `grep -n -A 20 '<test name>'`
|
|
123
|
+
* the log, got nothing back through four greps, inferred causes from the spec prose instead, and
|
|
124
|
+
* edited `app/src` while the run was still walking — which voids the run.
|
|
125
|
+
*/
|
|
126
|
+
export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE";
|
|
113
127
|
/**
|
|
114
128
|
* Rewrite a command about to run in the background so its log grows while it
|
|
115
129
|
* runs. Two shapes left the log empty until exit, and an empty log is what
|
|
@@ -135,6 +149,11 @@ export declare function outputRedirectTarget(command: string): string | null;
|
|
|
135
149
|
* `junit`) is the
|
|
136
150
|
* executor's own and is left exactly as it is — so is any reporter this build does not know.
|
|
137
151
|
*
|
|
152
|
+
* A run put on `list` is also given {@link PLAYWRIGHT_FAILURES_INLINE_ENV}, so each failure's own
|
|
153
|
+
* error is in the log as that test fails rather than only at the end: a suite that is still walking
|
|
154
|
+
* is exactly when the executor reads the log, and without it a running log carries `✘` rows and
|
|
155
|
+
* nothing else (xr-molecule-flash-m99 spent ~90 s on greps of one, then fixed what it guessed).
|
|
156
|
+
*
|
|
138
157
|
* Only the command's last step is touched, and only its reporter, and only when that step runs
|
|
139
158
|
* something: `npx playwright test --list` takes two seconds and writes nothing per test, so
|
|
140
159
|
* neither reporter nor `--retries=0` is added to it ({@link isPlaywrightRunCommand}).
|
|
@@ -602,10 +621,12 @@ export declare const RUNNING_FAILURE_LINE_RE = "^[[:space:]]*(\u2718|\u2717|\u00
|
|
|
602
621
|
* The shell command whose stdout is the interesting part of a log that is still being written:
|
|
603
622
|
* the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
604
623
|
*
|
|
605
|
-
* A Playwright `list` run prints one line per test as it finishes
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
624
|
+
* A Playwright `list` run prints one line per test as it finishes, so the LAST bytes of a running
|
|
625
|
+
* log are its least informative part — in X1 flash-m61 the last 3 000 bytes of a 139-test walk
|
|
626
|
+
* were the skip list, and all 61 failures were above them, which cost the run the grep it had to
|
|
627
|
+
* make anyway. A background run also carries each failure's own error as it fails
|
|
628
|
+
* ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}), and {@link RUNNING_FAILURE_LINE_RE} already matches
|
|
629
|
+
* those blocks' headers and their `Error:` lines, so this slice shows the causes too.
|
|
609
630
|
*
|
|
610
631
|
* @param log - The task log path.
|
|
611
632
|
* @param own - A file the command redirects its own output to, if any.
|
|
@@ -661,9 +682,11 @@ export interface RepeatingFailure {
|
|
|
661
682
|
* The cause repeating among a running suite's failures, or null.
|
|
662
683
|
*
|
|
663
684
|
* Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
|
|
664
|
-
* path and how long it ran — and
|
|
665
|
-
*
|
|
666
|
-
*
|
|
685
|
+
* path and how long it ran — and, on a run the platform started in the background, each failure's
|
|
686
|
+
* own error with it ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}); a `list` run with no error bodies in
|
|
687
|
+
* its log (an older Playwright, a log the bodies have been cut from) carries only the duration as
|
|
688
|
+
* a signature. Where the log carries the failure bodies (either inline, or in a finished run's
|
|
689
|
+
* summary) the signature is the
|
|
667
690
|
* error itself — {@link failureMessage}: the error class plus the locator or the values that
|
|
668
691
|
* differ — which is stronger (it needs no assumption about how long a failure should take), so
|
|
669
692
|
* the message is looked for first and the duration is then left with the tests whose bodies the
|
|
@@ -726,6 +749,38 @@ export interface UniversalCause {
|
|
|
726
749
|
* @returns The cause's count and files, or null when the run is not failing whole.
|
|
727
750
|
*/
|
|
728
751
|
export declare function universalCause(log: string, found: RepeatingFailure): UniversalCause | null;
|
|
752
|
+
/**
|
|
753
|
+
* The ONE finding that ends a wait early, and the only one a note may hand a stop command over
|
|
754
|
+
* for: a PROVEN cause that is failing the whole run.
|
|
755
|
+
*
|
|
756
|
+
* Both halves are load-bearing, each of them learned from a run that lost minutes to the other.
|
|
757
|
+
*
|
|
758
|
+
* - **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across
|
|
759
|
+
* {@link UNIVERSAL_CAUSE_FILES} spec files or more, with nothing passing since the first — the
|
|
760
|
+
* dont-stop-runs rule. X1 flash-m83 (2026-10-16) stopped eight local runs at their first
|
|
761
|
+
* repeating cause in 14.5 minutes and never finished one, and a run that still has other causes
|
|
762
|
+
* left to name is the only thing that will name them.
|
|
763
|
+
* - **A PROVEN cause** is an error repeated VERBATIM. A shared DURATION is a LEAD, never a cause:
|
|
764
|
+
* a suite whose config sets a timeout spends that timeout on every failure that waits for
|
|
765
|
+
* anything, so three unrelated tests timing out together look exactly like one broken run
|
|
766
|
+
* (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}). X1
|
|
767
|
+
* xr-molecule-flash-m99 (2026-10-16) was told `status: 'repeating_failure'` twice on that
|
|
768
|
+
* shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
|
|
769
|
+
* — read the early return as "stop waiting, edit now", and voided a run that was still walking;
|
|
770
|
+
* the second cost a 224 s wait that ended with no result at all.
|
|
771
|
+
*
|
|
772
|
+
* Neither half alone is enough, and {@link repeatingFailure} finding a signature is not either:
|
|
773
|
+
* that answers "is a cause repeating among this run's failures", which is worth telling the
|
|
774
|
+
* executor mid-run as a NOTE while the wait goes on. This answers the narrower question the wait
|
|
775
|
+
* needs — is the run over as a measuring instrument — and it is THE one predicate behind both
|
|
776
|
+
* that decision and the note's wording, so the two cannot come to different verdicts.
|
|
777
|
+
*
|
|
778
|
+
* @param found - What {@link repeatingFailure} found mid-run.
|
|
779
|
+
* @param universal - What {@link universalCause} found for it, if anything.
|
|
780
|
+
* @returns The cause to stop the run over, or null — in which case nothing may be stopped, the
|
|
781
|
+
* wait keeps going, and the finding rides the run's result as a note.
|
|
782
|
+
*/
|
|
783
|
+
export declare function earlyReturnCause(found: RepeatingFailure, universal: UniversalCause | null | undefined): UniversalCause | null;
|
|
729
784
|
/** One cause of failure in a run, and the tests it hit. */
|
|
730
785
|
export interface FailureCause {
|
|
731
786
|
/** The error line its tests all reported, or null when only the duration ties them together. */
|
|
@@ -744,11 +799,11 @@ export interface FailureCause {
|
|
|
744
799
|
* bodies to read; X1 flash-m83 (2026-10-16) spent three deploys (6.7 minutes) each fixing a
|
|
745
800
|
* single cause, two of them found by runs stopped before they walked the rest.
|
|
746
801
|
*
|
|
747
|
-
* The signature is the cause itself where the log carries the failures' bodies (a finished run,
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
* why a duration group is labelled as a duration and not as a
|
|
751
|
-
* printed twice, or failed twice, is one test.
|
|
802
|
+
* The signature is the cause itself where the log carries the failures' bodies (a finished run, a
|
|
803
|
+
* background `list` run — whose failures print as they fail — or a `line` reporter) —
|
|
804
|
+
* {@link failureMessage}: the error class plus the locator or the values that differ — and how long
|
|
805
|
+
* they took where it does not, which is why a duration group is labelled as a duration and not as a
|
|
806
|
+
* proven cause. A test the log printed twice, or failed twice, is one test.
|
|
752
807
|
*
|
|
753
808
|
* @param log - The log of a finished or running run.
|
|
754
809
|
* @returns The causes, the one with the most tests first.
|
|
@@ -795,11 +850,15 @@ export declare function rerunOfRunningSuite(run: {
|
|
|
795
850
|
* tests, so the note never names one or two tests as a cause.
|
|
796
851
|
*
|
|
797
852
|
* The note names the cause early so the executor can START FIXING while the run keeps walking,
|
|
798
|
-
* and it
|
|
799
|
-
*
|
|
800
|
-
*
|
|
801
|
-
*
|
|
802
|
-
*
|
|
853
|
+
* and it hands a stop command over only when {@link earlyReturnCause} says so — the SAME
|
|
854
|
+
* predicate the wait uses to decide whether to return early, so a finding the wait keeps waiting
|
|
855
|
+
* on is a finding this note never asks to stop for. X1 flash-m76 (2026-10-16) spent 4.0 minutes
|
|
856
|
+
* walking 15 tests whose one cause was certain at the third, 47 s in, and the note answers
|
|
857
|
+
* exactly that; X1 flash-m83 then stopped eight runs at their first repeating cause in 14.5
|
|
858
|
+
* minutes and never finished one, so the note must not say to stop when the run still has other
|
|
859
|
+
* causes to name. X1 xr-molecule-flash-m99 (2026-10-16) is the third shape: a shared duration is
|
|
860
|
+
* not a cause at all, so a lead reaches the executor as a note on a result it is still waiting
|
|
861
|
+
* for — never as a verdict with a stop command in it.
|
|
803
862
|
*
|
|
804
863
|
* The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
|
|
805
864
|
* re-run of one of its tests is a second run against the same app and database, and the platform
|
|
@@ -813,7 +872,7 @@ export declare function rerunOfRunningSuite(run: {
|
|
|
813
872
|
*
|
|
814
873
|
* @param found - What {@link repeatingFailure} found.
|
|
815
874
|
* @param o - The run's facts: its `taskId`, its `log` path, `stop` — the shell command that
|
|
816
|
-
* stops it, printed only for
|
|
875
|
+
* stops it, printed only for an {@link earlyReturnCause} — `universal`, what
|
|
817
876
|
* {@link universalCause} found (absent means the run is not failing whole), and `run`, how the
|
|
818
877
|
* run was started ({@link rerunOfRunningSuite}) when the caller knows it.
|
|
819
878
|
* @returns The note.
|
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;AAcH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAEE,CAAA;AAuGhD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErD;AAKD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAsCzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CAUpF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWnE;AAuDD
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|