craftdriver 1.2.0 → 1.3.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/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/dist/cli/dispatcher.d.ts.map +1 -1
- package/dist/cli/dispatcher.js +12 -0
- package/dist/cli/dispatcher.js.map +1 -1
- package/dist/cli/mcp/tools.d.ts +1 -2
- package/dist/cli/mcp/tools.d.ts.map +1 -1
- package/dist/cli/mcp/tools.js +18 -0
- package/dist/cli/mcp/tools.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/browser.d.ts +9 -6
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +94 -73
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/tracing.d.ts +20 -4
- package/dist/lib/tracing.d.ts.map +1 -1
- package/dist/lib/tracing.js +38 -3
- package/dist/lib/tracing.js.map +1 -1
- package/dist/lib/vibiumTrace.d.ts +14 -0
- package/dist/lib/vibiumTrace.d.ts.map +1 -0
- package/dist/lib/vibiumTrace.js +372 -0
- package/dist/lib/vibiumTrace.js.map +1 -0
- package/docs/api-reference.md +2 -1
- package/docs/mcp.md +17 -3
- package/docs/public/recipes/trace-failing-test.html +16 -0
- package/docs/public/recipes/vitest-vibium-trace.html +16 -0
- package/docs/public/traces/vitest-login.zip +0 -0
- package/docs/recipes/debug-failing-tests-with-traces.md +117 -0
- package/docs/recipes.md +1 -1
- package/docs/tracing.md +102 -24
- package/package.json +4 -8
- package/skills/craftdriver/cheatsheet.md +4 -2
- package/skills/craftdriver/patterns.md +2 -1
- package/docs/recipes/trace-failing-test.md +0 -51
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export Craftdriver's crash-resilient trace directory as the
|
|
3
|
+
* Playwright-compatible recording layout consumed by Vibium Player.
|
|
4
|
+
*/
|
|
5
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import yazl from 'yazl';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const pkg = require('../../package.json');
|
|
12
|
+
const ACTIONS = {
|
|
13
|
+
navigateTo: { class: 'Page', method: 'navigate' },
|
|
14
|
+
goBack: { class: 'Page', method: 'goBack' },
|
|
15
|
+
goForward: { class: 'Page', method: 'goForward' },
|
|
16
|
+
reload: { class: 'Page', method: 'reload' },
|
|
17
|
+
setContent: { class: 'Page', method: 'setContent' },
|
|
18
|
+
click: { class: 'Element', method: 'click' },
|
|
19
|
+
fill: { class: 'Element', method: 'fill' },
|
|
20
|
+
clear: { class: 'Element', method: 'clear' },
|
|
21
|
+
acceptDialog: { class: 'Dialog', method: 'accept' },
|
|
22
|
+
dismissDialog: { class: 'Dialog', method: 'dismiss' },
|
|
23
|
+
};
|
|
24
|
+
/** Write a Vibium/Playwright-compatible trace zip from a stopped raw trace. */
|
|
25
|
+
export async function writeVibiumTraceZip(opts) {
|
|
26
|
+
if (!opts.path)
|
|
27
|
+
throw new Error('stopTrace({ path }) requires a non-empty zip path.');
|
|
28
|
+
const raw = readRawTrace(join(opts.sourceDir, 'trace.ndjson'));
|
|
29
|
+
const started = raw.find((event) => event.type === 'meta' && typeof event.startedAt === 'string');
|
|
30
|
+
const startWallTime = started?.startedAt ? Date.parse(started.startedAt) : Date.now();
|
|
31
|
+
const id = randomBytes(6).toString('hex');
|
|
32
|
+
const contextId = `context@${id}`;
|
|
33
|
+
const pageId = `page@${id}`;
|
|
34
|
+
const resources = [];
|
|
35
|
+
const ordered = [];
|
|
36
|
+
const exportedActions = [];
|
|
37
|
+
let sequence = 0;
|
|
38
|
+
let callCounter = 0;
|
|
39
|
+
const screenshots = raw.filter((event) => event.type === 'screenshot');
|
|
40
|
+
const firstScreenshot = screenshots.find((event) => existsSync(join(opts.sourceDir, event.file)));
|
|
41
|
+
const viewport = firstScreenshot
|
|
42
|
+
? readPngSize(readFileSync(join(opts.sourceDir, firstScreenshot.file)))
|
|
43
|
+
: { width: 1280, height: 720 };
|
|
44
|
+
const actionTimes = [];
|
|
45
|
+
const actionEnds = new Map(raw.filter((event) => event.type === 'action-end').map((event) => [event.actionIndex, event]));
|
|
46
|
+
for (const event of raw) {
|
|
47
|
+
if (event.type !== 'action')
|
|
48
|
+
continue;
|
|
49
|
+
const callId = `call@${++callCounter}`;
|
|
50
|
+
const actionIndex = event.actionIndex ?? callCounter;
|
|
51
|
+
const completed = actionEnds.get(actionIndex);
|
|
52
|
+
const endTime = Math.max(event.t + 1, completed?.t ?? event.t + 1);
|
|
53
|
+
const mapping = ACTIONS[event.name] ?? { class: 'Browser', method: event.name };
|
|
54
|
+
const params = actionParams(event);
|
|
55
|
+
actionTimes.push(event.t);
|
|
56
|
+
const before = {
|
|
57
|
+
type: 'before',
|
|
58
|
+
callId,
|
|
59
|
+
startTime: event.t,
|
|
60
|
+
class: mapping.class,
|
|
61
|
+
method: mapping.method,
|
|
62
|
+
pageId,
|
|
63
|
+
params,
|
|
64
|
+
title: actionTitle(mapping, params),
|
|
65
|
+
};
|
|
66
|
+
const after = { type: 'after', callId, endTime };
|
|
67
|
+
if (completed?.error) {
|
|
68
|
+
after.error = { name: 'Error', message: completed.error };
|
|
69
|
+
}
|
|
70
|
+
exportedActions.push({ callId, before, after });
|
|
71
|
+
ordered.push({
|
|
72
|
+
time: event.t,
|
|
73
|
+
order: 3,
|
|
74
|
+
sequence: sequence++,
|
|
75
|
+
value: before,
|
|
76
|
+
});
|
|
77
|
+
// Older raw traces have no completion marker. Use WDIO's conservative
|
|
78
|
+
// 1 ms minimum for those; current traces carry the real end timestamp.
|
|
79
|
+
ordered.push({
|
|
80
|
+
time: endTime,
|
|
81
|
+
order: 1,
|
|
82
|
+
sequence: sequence++,
|
|
83
|
+
value: after,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const usedResourceNames = new Set();
|
|
87
|
+
const addFrame = (sourcePath, frameTime) => {
|
|
88
|
+
if (!existsSync(sourcePath))
|
|
89
|
+
return undefined;
|
|
90
|
+
const data = readFileSync(sourcePath);
|
|
91
|
+
const size = readPngSize(data);
|
|
92
|
+
let wallTime = startWallTime + Math.max(0, Math.floor(frameTime));
|
|
93
|
+
let resourceName = `${pageId}-${wallTime}.png`;
|
|
94
|
+
while (usedResourceNames.has(resourceName)) {
|
|
95
|
+
resourceName = `${pageId}-${++wallTime}.png`;
|
|
96
|
+
}
|
|
97
|
+
usedResourceNames.add(resourceName);
|
|
98
|
+
resources.push({ sourcePath, name: resourceName });
|
|
99
|
+
ordered.push({
|
|
100
|
+
time: frameTime,
|
|
101
|
+
order: 2,
|
|
102
|
+
sequence: sequence++,
|
|
103
|
+
value: {
|
|
104
|
+
type: 'screencast-frame',
|
|
105
|
+
pageId,
|
|
106
|
+
sha1: resourceName,
|
|
107
|
+
width: size.width,
|
|
108
|
+
height: size.height,
|
|
109
|
+
timestamp: frameTime,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
time: frameTime,
|
|
114
|
+
name: resourceName,
|
|
115
|
+
width: size.width,
|
|
116
|
+
height: size.height,
|
|
117
|
+
dataUri: `data:image/png;base64,${data.toString('base64')}`,
|
|
118
|
+
url: pageUrlAt(raw, frameTime),
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
const actionFrames = new Map();
|
|
122
|
+
let finalFrame;
|
|
123
|
+
for (const event of screenshots) {
|
|
124
|
+
const frameTime = event.actionIndex
|
|
125
|
+
? (actionTimes[event.actionIndex - 1] ?? event.t)
|
|
126
|
+
: event.t;
|
|
127
|
+
const frame = addFrame(join(opts.sourceDir, event.file), frameTime);
|
|
128
|
+
if (!frame)
|
|
129
|
+
continue;
|
|
130
|
+
if (event.actionIndex !== undefined)
|
|
131
|
+
actionFrames.set(event.actionIndex, frame);
|
|
132
|
+
if (event.reason === 'final')
|
|
133
|
+
finalFrame = frame;
|
|
134
|
+
}
|
|
135
|
+
// Backwards compatibility for traces produced by the first recipe draft.
|
|
136
|
+
const finalScreenshot = join(opts.sourceDir, 'final.png');
|
|
137
|
+
if (!finalFrame && existsSync(finalScreenshot)) {
|
|
138
|
+
const finalTime = Math.max(0, ...raw.map((event) => event.t)) + 1;
|
|
139
|
+
finalFrame = addFrame(finalScreenshot, finalTime);
|
|
140
|
+
}
|
|
141
|
+
// Vibium uses a minimal HTML document containing the screenshot as its
|
|
142
|
+
// Playwright-compatible frame snapshot. This makes the main viewer panel
|
|
143
|
+
// useful without pretending Craftdriver captured a restorable page DOM.
|
|
144
|
+
for (let index = 0; index < exportedActions.length; index++) {
|
|
145
|
+
const action = exportedActions[index];
|
|
146
|
+
const beforeFrame = actionFrames.get(index + 1);
|
|
147
|
+
const afterFrame = actionFrames.get(index + 2) ?? finalFrame;
|
|
148
|
+
if (beforeFrame) {
|
|
149
|
+
const name = `before@${action.callId}`;
|
|
150
|
+
action.before.beforeSnapshot = name;
|
|
151
|
+
addFrameSnapshot(ordered, action.callId, name, pageId, beforeFrame, sequence++);
|
|
152
|
+
}
|
|
153
|
+
if (afterFrame) {
|
|
154
|
+
const name = `after@${action.callId}`;
|
|
155
|
+
action.after.afterSnapshot = name;
|
|
156
|
+
addFrameSnapshot(ordered, action.callId, name, pageId, afterFrame, sequence++);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const event of raw) {
|
|
160
|
+
const value = bidiEvent(event);
|
|
161
|
+
if (!value)
|
|
162
|
+
continue;
|
|
163
|
+
ordered.push({ time: event.t, order: 2, sequence: sequence++, value });
|
|
164
|
+
}
|
|
165
|
+
ordered.sort((a, b) => a.time - b.time || a.order - b.order || a.sequence - b.sequence);
|
|
166
|
+
const contextOptions = {
|
|
167
|
+
version: 8,
|
|
168
|
+
type: 'context-options',
|
|
169
|
+
origin: 'library',
|
|
170
|
+
libraryName: 'craftdriver',
|
|
171
|
+
libraryVersion: pkg.version,
|
|
172
|
+
browserName: opts.browserName === 'chrome' ? 'chromium' : opts.browserName,
|
|
173
|
+
platform: process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'windows' : 'linux',
|
|
174
|
+
wallTime: startWallTime,
|
|
175
|
+
monotonicTime: 0,
|
|
176
|
+
sdkLanguage: 'javascript',
|
|
177
|
+
title: opts.title ?? 'Craftdriver trace',
|
|
178
|
+
contextId,
|
|
179
|
+
options: { viewport },
|
|
180
|
+
};
|
|
181
|
+
const traceText = [contextOptions, ...ordered.map((event) => event.value)]
|
|
182
|
+
.map((event) => JSON.stringify(event))
|
|
183
|
+
.join('\n');
|
|
184
|
+
const networkText = buildNetworkTrace(raw, startWallTime, pageId);
|
|
185
|
+
mkdirSync(dirname(opts.path), { recursive: true });
|
|
186
|
+
await writeZip(opts.path, traceText, networkText, resources);
|
|
187
|
+
}
|
|
188
|
+
function addFrameSnapshot(ordered, callId, snapshotName, pageId, frame, sequence) {
|
|
189
|
+
ordered.push({
|
|
190
|
+
time: frame.time,
|
|
191
|
+
order: 2.5,
|
|
192
|
+
sequence,
|
|
193
|
+
value: {
|
|
194
|
+
type: 'frame-snapshot',
|
|
195
|
+
snapshot: {
|
|
196
|
+
callId,
|
|
197
|
+
snapshotName,
|
|
198
|
+
pageId,
|
|
199
|
+
frameId: pageId,
|
|
200
|
+
frameUrl: frame.url,
|
|
201
|
+
doctype: 'html',
|
|
202
|
+
html: [
|
|
203
|
+
'HTML', {},
|
|
204
|
+
['HEAD', {}],
|
|
205
|
+
['BODY', { style: 'margin:0;overflow:hidden' }, [
|
|
206
|
+
'IMG', { src: frame.dataUri, style: 'width:100%' },
|
|
207
|
+
]],
|
|
208
|
+
],
|
|
209
|
+
viewport: { width: frame.width, height: frame.height },
|
|
210
|
+
timestamp: frame.time,
|
|
211
|
+
wallTime: frame.time,
|
|
212
|
+
resourceOverrides: [{ url: frame.dataUri, sha1: frame.name }],
|
|
213
|
+
isMainFrame: true,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function pageUrlAt(events, time) {
|
|
219
|
+
let url = 'about:blank';
|
|
220
|
+
for (const event of events) {
|
|
221
|
+
if (event.t > time)
|
|
222
|
+
continue;
|
|
223
|
+
if (event.type === 'navigation' && event.url)
|
|
224
|
+
url = event.url;
|
|
225
|
+
}
|
|
226
|
+
return url;
|
|
227
|
+
}
|
|
228
|
+
function readRawTrace(path) {
|
|
229
|
+
return readFileSync(path, 'utf8')
|
|
230
|
+
.split('\n')
|
|
231
|
+
.filter(Boolean)
|
|
232
|
+
.flatMap((line) => {
|
|
233
|
+
try {
|
|
234
|
+
return [JSON.parse(line)];
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function actionParams(event) {
|
|
242
|
+
if (event.name === 'navigateTo')
|
|
243
|
+
return { url: event.selector ?? event.args?.[0] };
|
|
244
|
+
if (event.name === 'fill')
|
|
245
|
+
return { selector: event.selector, value: event.args?.[0] };
|
|
246
|
+
if (event.selector)
|
|
247
|
+
return { selector: event.selector };
|
|
248
|
+
if (event.name === 'acceptDialog' && event.args?.length)
|
|
249
|
+
return { text: event.args[0] };
|
|
250
|
+
if (event.args?.length)
|
|
251
|
+
return { args: event.args };
|
|
252
|
+
return {};
|
|
253
|
+
}
|
|
254
|
+
function actionTitle(mapping, params) {
|
|
255
|
+
const label = params.selector ?? params.url ?? params.text;
|
|
256
|
+
if (label === undefined)
|
|
257
|
+
return `${mapping.class}.${mapping.method}()`;
|
|
258
|
+
return `${mapping.class}.${mapping.method}("${String(label).slice(0, 80)}")`;
|
|
259
|
+
}
|
|
260
|
+
function bidiEvent(event) {
|
|
261
|
+
if (event.type === 'console') {
|
|
262
|
+
return {
|
|
263
|
+
type: 'event',
|
|
264
|
+
method: 'log.entryAdded',
|
|
265
|
+
params: { type: 'console', level: event.level, text: event.text },
|
|
266
|
+
time: event.t,
|
|
267
|
+
class: 'BrowserContext',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (event.type === 'error') {
|
|
271
|
+
return {
|
|
272
|
+
type: 'event',
|
|
273
|
+
method: 'log.entryAdded',
|
|
274
|
+
params: { type: 'javascript', level: 'error', text: event.text },
|
|
275
|
+
time: event.t,
|
|
276
|
+
class: 'BrowserContext',
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (event.type === 'navigation') {
|
|
280
|
+
return {
|
|
281
|
+
type: 'event',
|
|
282
|
+
method: 'browsingContext.navigationStarted',
|
|
283
|
+
params: { url: event.url, context: event.context },
|
|
284
|
+
time: event.t,
|
|
285
|
+
class: 'BrowserContext',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
function buildNetworkTrace(events, startWallTime, pageId) {
|
|
291
|
+
const requests = events
|
|
292
|
+
.filter((event) => event.type === 'request')
|
|
293
|
+
.map((event) => ({ ...event }));
|
|
294
|
+
const snapshots = [];
|
|
295
|
+
for (const response of events) {
|
|
296
|
+
if (response.type !== 'response')
|
|
297
|
+
continue;
|
|
298
|
+
const request = requests.find((candidate) => !candidate.used && ((response.requestId !== undefined && candidate.requestId === response.requestId) ||
|
|
299
|
+
(response.requestId === undefined && candidate.url === response.url)));
|
|
300
|
+
if (!request)
|
|
301
|
+
continue;
|
|
302
|
+
request.used = true;
|
|
303
|
+
const duration = Math.max(0, response.t - request.t);
|
|
304
|
+
const contentTypeHeaders = response.mimeType
|
|
305
|
+
? [{ name: 'Content-Type', value: response.mimeType }]
|
|
306
|
+
: [];
|
|
307
|
+
snapshots.push({
|
|
308
|
+
type: 'resource-snapshot',
|
|
309
|
+
snapshot: {
|
|
310
|
+
startedDateTime: new Date(startWallTime + request.t).toISOString(),
|
|
311
|
+
time: duration,
|
|
312
|
+
request: {
|
|
313
|
+
method: request.method,
|
|
314
|
+
url: request.url,
|
|
315
|
+
httpVersion: 'HTTP/1.1',
|
|
316
|
+
cookies: [],
|
|
317
|
+
headers: [],
|
|
318
|
+
queryString: queryString(request.url),
|
|
319
|
+
headersSize: -1,
|
|
320
|
+
bodySize: -1,
|
|
321
|
+
},
|
|
322
|
+
response: {
|
|
323
|
+
status: response.status,
|
|
324
|
+
statusText: '',
|
|
325
|
+
httpVersion: 'HTTP/1.1',
|
|
326
|
+
cookies: [],
|
|
327
|
+
headers: contentTypeHeaders,
|
|
328
|
+
content: { size: 0, mimeType: response.mimeType ?? '' },
|
|
329
|
+
redirectURL: '',
|
|
330
|
+
headersSize: -1,
|
|
331
|
+
bodySize: -1,
|
|
332
|
+
},
|
|
333
|
+
cache: response.fromCache ? { _fromCache: true } : {},
|
|
334
|
+
timings: { send: -1, wait: duration, receive: -1 },
|
|
335
|
+
_monotonicTime: request.t,
|
|
336
|
+
_frameref: pageId,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return snapshots.map((snapshot) => JSON.stringify(snapshot)).join('\n');
|
|
341
|
+
}
|
|
342
|
+
function queryString(url) {
|
|
343
|
+
try {
|
|
344
|
+
return [...new URL(url).searchParams].map(([name, value]) => ({ name, value }));
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function readPngSize(data) {
|
|
351
|
+
const isPng = data.length >= 24 && data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
|
|
352
|
+
if (!isPng)
|
|
353
|
+
return { width: 1280, height: 720 };
|
|
354
|
+
return { width: data.readUInt32BE(16), height: data.readUInt32BE(20) };
|
|
355
|
+
}
|
|
356
|
+
function writeZip(path, traceText, networkText, resources) {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const zip = new yazl.ZipFile();
|
|
359
|
+
zip.addBuffer(Buffer.from(traceText, 'utf8'), 'trace.trace');
|
|
360
|
+
zip.addBuffer(Buffer.from(networkText, 'utf8'), 'trace.network');
|
|
361
|
+
for (const resource of resources) {
|
|
362
|
+
zip.addFile(resource.sourcePath, `resources/${resource.name}`);
|
|
363
|
+
}
|
|
364
|
+
const output = createWriteStream(path);
|
|
365
|
+
zip.outputStream.on('error', reject);
|
|
366
|
+
output.on('error', reject);
|
|
367
|
+
output.on('close', resolve);
|
|
368
|
+
zip.outputStream.pipe(output);
|
|
369
|
+
zip.end();
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=vibiumTrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vibiumTrace.js","sourceRoot":"","sources":["../../src/lib/vibiumTrace.ts"],"names":[],"mappings":"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"}
|
package/docs/api-reference.md
CHANGED
|
@@ -82,6 +82,7 @@ Capture console output, JavaScript errors, and trace artifacts.
|
|
|
82
82
|
| `JavaScriptError` | type | — | [browser-logs](browser-logs.md) |
|
|
83
83
|
| `LogMessage` | type | — | [browser-logs](browser-logs.md) |
|
|
84
84
|
| `TraceStartOptions` | type | — | [tracing](tracing.md) |
|
|
85
|
+
| `TraceStopOptions` | type | — | [tracing](tracing.md) |
|
|
85
86
|
| `TraceScreenshotMode` | type | Screenshot mode for tracing. | [tracing](tracing.md) |
|
|
86
87
|
| `TraceEvent` | type | — | [tracing](tracing.md) |
|
|
87
88
|
|
|
@@ -130,4 +131,4 @@ Handle stable CraftDriver errors or customize browser driver services.
|
|
|
130
131
|
| `FirefoxService` | class | — | [getting-started](getting-started.md) |
|
|
131
132
|
| `FirefoxServiceOptions` | type | — | [getting-started](getting-started.md) |
|
|
132
133
|
|
|
133
|
-
Total exports: **
|
|
134
|
+
Total exports: **61**.
|
package/docs/mcp.md
CHANGED
|
@@ -53,7 +53,7 @@ goose configure # add craftdriver as a stdio server
|
|
|
53
53
|
|
|
54
54
|
## Tools
|
|
55
55
|
|
|
56
|
-
Compact set —
|
|
56
|
+
Compact set — 15 tools, one line each. Long help lives in the schema
|
|
57
57
|
description; clients render it in the model's context once per session.
|
|
58
58
|
|
|
59
59
|
| Tool | Purpose |
|
|
@@ -70,11 +70,25 @@ description; clients render it in the model's context once per session.
|
|
|
70
70
|
| `browser_pages` | List open pages (id, url, title). |
|
|
71
71
|
| `browser_snapshot` | **Sanitized DOM summary with refs.** Use `ref=eN` as the selector for subsequent calls. |
|
|
72
72
|
| `browser_screenshot` | Capture PNG to a file (auto-allocated under the per-session artifact dir; never inlined). |
|
|
73
|
+
| `browser_trace` | Start/stop tracing and export a Vibium Player compatible zip. |
|
|
73
74
|
| `browser_status` | Browser up? Which URL is active? |
|
|
74
75
|
| `browser_advanced_eval` | Evaluate JS in the page. Last resort. |
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
Trace a complete agent-driven session with two calls:
|
|
78
|
+
|
|
79
|
+
```jsonc
|
|
80
|
+
{ "name": "browser_trace", "arguments": {
|
|
81
|
+
"action": "start", "out_dir": "./traces/agent-raw", "title": "Agent flow"
|
|
82
|
+
} }
|
|
83
|
+
// ...browser_navigate / browser_fill / browser_click calls...
|
|
84
|
+
{ "name": "browser_trace", "arguments": {
|
|
85
|
+
"action": "stop", "path": "./traces/agent-flow.zip"
|
|
86
|
+
} }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The zip opens at [player.vibium.dev](https://player.vibium.dev/). The raw
|
|
90
|
+
directory remains available if the MCP client disconnects before the stop
|
|
91
|
+
call; in that case there is no finalized zip.
|
|
78
92
|
|
|
79
93
|
## Selector syntax
|
|
80
94
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="refresh" content="0; url=./debug-failing-tests-with-traces">
|
|
6
|
+
<link
|
|
7
|
+
rel="canonical"
|
|
8
|
+
href="https://dtopuzov.github.io/craftdriver/recipes/debug-failing-tests-with-traces"
|
|
9
|
+
>
|
|
10
|
+
<title>Vitest traces moved</title>
|
|
11
|
+
<script>location.replace('./debug-failing-tests-with-traces')</script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<p>This recipe moved to <a href="./debug-failing-tests-with-traces">Debug With Traces</a>.</p>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="refresh" content="0; url=./debug-failing-tests-with-traces">
|
|
6
|
+
<link
|
|
7
|
+
rel="canonical"
|
|
8
|
+
href="https://dtopuzov.github.io/craftdriver/recipes/debug-failing-tests-with-traces"
|
|
9
|
+
>
|
|
10
|
+
<title>Vitest trace recipe moved</title>
|
|
11
|
+
<script>location.replace('./debug-failing-tests-with-traces')</script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<p>This recipe moved to <a href="./debug-failing-tests-with-traces">Debug With Traces</a>.</p>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Use Traces To Debug Failing Tests
|
|
2
|
+
|
|
3
|
+
A failed assertion tells you what was wrong at the end of a test, but often not
|
|
4
|
+
how the browser got there. A trace preserves the actions, screenshots, console
|
|
5
|
+
and JavaScript events, navigation, and network activity that led to the
|
|
6
|
+
failure.
|
|
7
|
+
|
|
8
|
+
## A Shared Trace Format
|
|
9
|
+
|
|
10
|
+
[Jason Huggins' SeleniumConf talk](https://www.youtube.com/watch?v=c-OGZK3F0tk&t=1530s)
|
|
11
|
+
was one of the reasons Craftdriver adopted portable browser traces instead of
|
|
12
|
+
a Craftdriver-only viewer format.
|
|
13
|
+
|
|
14
|
+
The exported zip follows the Playwright-style
|
|
15
|
+
recording layout also used by tools like
|
|
16
|
+
[Vibium](https://github.com/VibiumDev/vibium/blob/main/docs/explanation/recording-format.md)
|
|
17
|
+
and WDIO, so the same artifact can be opened in viewers such as
|
|
18
|
+
[Vibium Player](https://player.vibium.dev/).
|
|
19
|
+
|
|
20
|
+
Craftdriver still records a crash-resilient raw NDJSON trace first, then
|
|
21
|
+
packages it into the shared zip format when a test runner asks for an artifact.
|
|
22
|
+
|
|
23
|
+
## Keep A Trace When A Vitest Test Fails
|
|
24
|
+
|
|
25
|
+
Start one browser for the suite, but scope tracing to the test hooks.
|
|
26
|
+
`afterEach()` knows whether the test failed, so it can keep only the useful zip:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from 'vitest';
|
|
30
|
+
import { rmSync } from 'node:fs';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import { Browser } from 'craftdriver';
|
|
33
|
+
|
|
34
|
+
describe('login', () => {
|
|
35
|
+
let browser: Browser;
|
|
36
|
+
const rawTraceDir = path.resolve('test-results/traces/login-raw');
|
|
37
|
+
const traceZip = path.resolve('test-results/traces/login-failure.zip');
|
|
38
|
+
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
browser = await Browser.launch();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
await browser.startTrace({
|
|
45
|
+
outDir: rawTraceDir,
|
|
46
|
+
title: 'Failing login test',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(async ({ task }) => {
|
|
51
|
+
const failed = task.result?.state === 'fail';
|
|
52
|
+
await browser.stopTrace(failed ? { path: traceZip } : undefined);
|
|
53
|
+
if (!failed) rmSync(rawTraceDir, { recursive: true, force: true });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterAll(async () => browser.quit());
|
|
57
|
+
|
|
58
|
+
it('fills and submits the login form', async () => {
|
|
59
|
+
await browser.navigateTo(
|
|
60
|
+
'https://dtopuzov.github.io/craftdriver/examples/login.html',
|
|
61
|
+
);
|
|
62
|
+
await browser.fill('#username', 'alice');
|
|
63
|
+
await browser.fill('#password', 'secret');
|
|
64
|
+
await browser.click('#submit');
|
|
65
|
+
await browser.expect('#welcome').toContainText('Welcome back, alice!');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A passing run removes the raw trace and leaves no artifact. After a normal
|
|
71
|
+
Vitest failure, the evidence is kept here:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
test-results/
|
|
75
|
+
└── traces/
|
|
76
|
+
├── login-raw/ # trace.ndjson and evidence screenshots
|
|
77
|
+
└── login-failure.zip # open this in Vibium Player
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For the best online experience, drop the zip into
|
|
81
|
+
[Vibium Player](https://player.vibium.dev/). To keep the trace entirely local,
|
|
82
|
+
use Playwright Trace Viewer:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
npx playwright show-trace test-results/traces/login-failure.zip
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Craftdriver includes screenshot-backed frame snapshots, so Playwright's main
|
|
89
|
+
browser panel shows the captured page state. It is a visual snapshot rather
|
|
90
|
+
than a restorable DOM, so the DOM/locator inspector is intentionally limited.
|
|
91
|
+
|
|
92
|
+
If the test process is killed before `afterEach()` runs, a zip cannot be
|
|
93
|
+
finalized, but the append-only `trace.ndjson` and completed screenshots remain
|
|
94
|
+
in `login-raw/`.
|
|
95
|
+
|
|
96
|
+
## Try A Trace Before Creating A Failure
|
|
97
|
+
|
|
98
|
+
1. [Download the sample login trace](https://dtopuzov.github.io/craftdriver/traces/vitest-login.zip).
|
|
99
|
+
2. Open [Vibium Player](https://player.vibium.dev/).
|
|
100
|
+
3. Drop `vitest-login.zip` onto the player and step through the login.
|
|
101
|
+
|
|
102
|
+
`stopTrace()` captures the final page state, so a failed assertion is visible
|
|
103
|
+
as the last player frame. The sample was generated by the runnable Vitest proof
|
|
104
|
+
for this recipe against the same published login page. To exercise the failure
|
|
105
|
+
workflow yourself, change the expected message to `Welcome back, bob!`, run the
|
|
106
|
+
test, and open the resulting `login-failure.zip`.
|
|
107
|
+
|
|
108
|
+
Trace files can contain screenshots, URLs, console messages, network metadata,
|
|
109
|
+
selectors, and entered values. Use test credentials and review a trace before
|
|
110
|
+
publishing it outside your team.
|
|
111
|
+
|
|
112
|
+
## Learn More
|
|
113
|
+
|
|
114
|
+
- [Tracing](../tracing.md)
|
|
115
|
+
- [Use CraftDriver With Vitest Hooks](./vitest-browser-lifecycle.md)
|
|
116
|
+
- [Vibium Player](https://player.vibium.dev/)
|
|
117
|
+
- [Jason Huggins on standardized tracing](https://www.youtube.com/watch?v=c-OGZK3F0tk&t=1530s)
|
package/docs/recipes.md
CHANGED
|
@@ -49,4 +49,4 @@ For exact signatures, use the linked feature docs and the
|
|
|
49
49
|
| ----------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
50
50
|
| Accessibility regression gate | CI should fail on serious page or component accessibility issues. | [Run Accessibility Gates](./recipes/accessibility-gate.md) |
|
|
51
51
|
| Console and JavaScript errors | Tests should fail if the browser reports unexpected client-side errors. | [Fail On Console And JavaScript Errors](./recipes/console-error-gate.md) |
|
|
52
|
-
|
|
|
52
|
+
| Debug failing tests | You need the actions, screenshots, logs, and network activity behind a failure. | [Use Traces To Debug Failing Tests](./recipes/debug-failing-tests-with-traces.md) |
|