craftdriver 1.1.1 → 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 +20 -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/bidi/index.d.ts +4 -5
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +18 -8
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +12 -1
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +20 -8
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/browser.d.ts +9 -15
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +94 -77
- 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/browser-api.md +0 -1
- package/docs/browser-logs.md +8 -23
- 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/console-error-gate.md +6 -9
- package/docs/recipes/debug-failing-tests-with-traces.md +117 -0
- package/docs/recipes/file-upload-download.md +1 -4
- package/docs/recipes/login-once-reuse-session.md +2 -5
- package/docs/recipes/mobile-flow-with-network-and-logs.md +2 -6
- package/docs/recipes/vitest-browser-lifecycle.md +1 -4
- package/docs/recipes.md +1 -1
- package/docs/tracing.md +104 -26
- package/package.json +4 -8
- package/skills/craftdriver/SKILL.md +3 -2
- package/skills/craftdriver/cheatsheet.md +6 -3
- package/skills/craftdriver/patterns.md +4 -6
- package/docs/recipes/trace-failing-test.md +0 -51
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/browser-api.md
CHANGED
|
@@ -29,7 +29,6 @@ const browser = await Browser.launch({
|
|
|
29
29
|
|--------|------|---------|-------------|
|
|
30
30
|
| `browserName` | `'chrome' \| 'chromium' \| 'firefox'` | `'chrome'` | Browser to launch |
|
|
31
31
|
| `enableBiDi` | `boolean` | `true` | Use WebDriver BiDi. Set `false` only when running against a browser that does not support it — every BiDi-only feature then throws a `requires BiDi` error if called. |
|
|
32
|
-
| `captureLogs` | `boolean` | `false` | Start console/error log capture immediately at launch instead of lazily on first `browser.logs`/`onConsole`/`onError`/`waitForConsole` touch. See [Console Logs And JavaScript Errors](./browser-logs.md). |
|
|
33
32
|
| `storageState` | `string` | — | Path to a session-state JSON file to load on startup |
|
|
34
33
|
| `mobileEmulation` | `MobileEmulation \| DeviceName` | — | Mobile device emulation settings (Chrome/Chromium only) |
|
|
35
34
|
| `downloadsDir` | `string` | temp dir | Directory where downloaded files are saved |
|
package/docs/browser-logs.md
CHANGED
|
@@ -12,35 +12,20 @@ emitted a known log message.
|
|
|
12
12
|
|
|
13
13
|
## Capture Timing
|
|
14
14
|
|
|
15
|
-
Capture is
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Messages emitted before that first touch are not captured, so this can return an
|
|
20
|
-
empty array:
|
|
15
|
+
Capture is automatic. CraftDriver subscribes to log events at launch, so
|
|
16
|
+
messages emitted at any point — including during the first navigation, before you
|
|
17
|
+
ever touch `browser.logs` — are captured. There is nothing to enable and no
|
|
18
|
+
listener to pre-arm:
|
|
21
19
|
|
|
22
20
|
```typescript
|
|
23
21
|
await browser.navigateTo('https://example.com');
|
|
24
|
-
const messages = browser.logs.getMessages();
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Start capture before the action that logs:
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
const warning = browser.logs.waitForConsole((msg) => msg.level === 'warn');
|
|
31
|
-
|
|
32
22
|
await browser.getByRole('button', { name: 'Show warning' }).click();
|
|
33
|
-
|
|
23
|
+
const messages = browser.logs.getMessages(); // includes the warning
|
|
34
24
|
```
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const browser = await Browser.launch({
|
|
40
|
-
browserName: 'chrome',
|
|
41
|
-
captureLogs: true,
|
|
42
|
-
});
|
|
43
|
-
```
|
|
26
|
+
The subscription rides the connect-time batch, so it costs no extra round trip
|
|
27
|
+
and no measurable overhead. Use `clearLogs()` if you want to assert only on
|
|
28
|
+
messages emitted after a certain point.
|
|
44
29
|
|
|
45
30
|
## Read Collected Messages
|
|
46
31
|
|
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
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
# Fail On Console And JavaScript Errors
|
|
2
2
|
|
|
3
3
|
A flow can look correct in the DOM while quietly throwing in the console — a
|
|
4
|
-
failed fetch, an undefined access, a rejected promise.
|
|
5
|
-
run the flow
|
|
6
|
-
|
|
4
|
+
failed fetch, an undefined access, a rejected promise. Logs are captured
|
|
5
|
+
automatically from launch, so you can run the flow and then assert no JavaScript
|
|
6
|
+
errors were reported, breaking the test instead of reaching users. This recipe
|
|
7
|
+
uses the live
|
|
7
8
|
[console errors example](https://dtopuzov.github.io/craftdriver/examples/console-errors.html).
|
|
8
|
-
It enables `captureLogs` at launch, so it shows the `launch` call.
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
|
-
const browser = await Browser.launch(
|
|
12
|
-
browserName: 'chrome',
|
|
13
|
-
captureLogs: true,
|
|
14
|
-
});
|
|
11
|
+
const browser = await Browser.launch();
|
|
15
12
|
|
|
16
13
|
await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/console-errors.html');
|
|
17
14
|
browser.logs.clearLogs();
|
|
@@ -38,7 +35,7 @@ await logged;
|
|
|
38
35
|
|
|
39
36
|
## Notes
|
|
40
37
|
|
|
41
|
-
- `assertNoErrors()` checks captured JavaScript errors,
|
|
38
|
+
- `assertNoErrors()` checks captured JavaScript errors — capture is automatic, nothing to enable.
|
|
42
39
|
- Call `clearLogs()` before the flow so errors from earlier tests don't bleed in.
|
|
43
40
|
- Use `waitForConsole()` / `waitForError()` when a specific message is the thing you're asserting.
|
|
44
41
|
|
|
@@ -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)
|
|
@@ -10,10 +10,7 @@ then downloads a report from the
|
|
|
10
10
|
It sets `downloadsDir` at launch, so it shows the `launch` call.
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
|
-
const browser = await Browser.launch({
|
|
14
|
-
browserName: 'chrome',
|
|
15
|
-
downloadsDir: '.tmp/downloads',
|
|
16
|
-
});
|
|
13
|
+
const browser = await Browser.launch({ downloadsDir: '.tmp/downloads' });
|
|
17
14
|
|
|
18
15
|
await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/upload.html');
|
|
19
16
|
await browser.find('#file-input').setInputFiles('tests/fixtures/sample.txt');
|
|
@@ -15,7 +15,7 @@ Run this once as a setup step. It is one of the few recipes that shows
|
|
|
15
15
|
```ts
|
|
16
16
|
import { Browser } from 'craftdriver';
|
|
17
17
|
|
|
18
|
-
const browser = await Browser.launch(
|
|
18
|
+
const browser = await Browser.launch();
|
|
19
19
|
|
|
20
20
|
await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/login.html');
|
|
21
21
|
await browser.getByLabel('Username').fill('alice');
|
|
@@ -32,10 +32,7 @@ await browser.quit();
|
|
|
32
32
|
Launch with `storageState` and the browser starts already signed in.
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
const browser = await Browser.launch({
|
|
36
|
-
browserName: 'chrome',
|
|
37
|
-
storageState: '.auth/alice.json',
|
|
38
|
-
});
|
|
35
|
+
const browser = await Browser.launch({ storageState: '.auth/alice.json' });
|
|
39
36
|
|
|
40
37
|
await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/login.html');
|
|
41
38
|
await browser.expect('#welcome').toContainText('Welcome back, alice!');
|
|
@@ -9,11 +9,7 @@ Because mobile emulation is a launch-time capability, this recipe shows the
|
|
|
9
9
|
`launch` call.
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
const browser = await Browser.launch({
|
|
13
|
-
browserName: 'chrome', // mobile emulation is Chrome/Chromium only
|
|
14
|
-
mobileEmulation: 'Pixel 7',
|
|
15
|
-
captureLogs: true,
|
|
16
|
-
});
|
|
12
|
+
const browser = await Browser.launch({ mobileEmulation: 'Pixel 7' });
|
|
17
13
|
|
|
18
14
|
// ?bidi=true makes the demo page issue real requests for interception.
|
|
19
15
|
await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/network.html?bidi=true');
|
|
@@ -33,7 +29,7 @@ browser.logs.assertNoErrors();
|
|
|
33
29
|
|
|
34
30
|
- Mobile emulation is currently Chrome/Chromium only.
|
|
35
31
|
- Mock before the action if the page reads data during that step.
|
|
36
|
-
-
|
|
32
|
+
- Console/error capture is automatic, so `assertNoErrors()` always has the full picture.
|
|
37
33
|
|
|
38
34
|
## Learn More
|
|
39
35
|
|
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) |
|
package/docs/tracing.md
CHANGED
|
@@ -14,7 +14,7 @@ process crash cannot lose the evidence that led up to it.
|
|
|
14
14
|
```ts
|
|
15
15
|
import { chromium } from 'craftdriver';
|
|
16
16
|
|
|
17
|
-
const browser = await chromium.launch(
|
|
17
|
+
const browser = await chromium.launch();
|
|
18
18
|
|
|
19
19
|
await browser.startTrace({ outDir: './artefacts/login' });
|
|
20
20
|
try {
|
|
@@ -39,6 +39,50 @@ artefacts/login/
|
|
|
39
39
|
`try/finally` is the recommended shape, but it's a courtesy — see the
|
|
40
40
|
next section.
|
|
41
41
|
|
|
42
|
+
## Export for Vibium Player
|
|
43
|
+
|
|
44
|
+
Pass a zip path when stopping the trace to create a portable recording that
|
|
45
|
+
opens in [Vibium Player](https://player.vibium.dev/) and Playwright Trace
|
|
46
|
+
Viewer:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
await browser.startTrace({
|
|
50
|
+
outDir: './artefacts/login-raw',
|
|
51
|
+
title: 'Login flow',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await browser.navigateTo('https://example.com/login');
|
|
56
|
+
await browser.fill('#user', 'alice');
|
|
57
|
+
await browser.click('#submit');
|
|
58
|
+
} finally {
|
|
59
|
+
await browser.stopTrace({ path: './artefacts/login.zip' });
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The raw `trace.ndjson` remains the crash-resilient source. The zip is produced
|
|
64
|
+
during `stopTrace()` and contains the Vibium/Playwright layout:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
login.zip
|
|
68
|
+
├── trace.trace # context-options + before/after actions + browser events
|
|
69
|
+
├── trace.network # HAR-style resource-snapshot events
|
|
70
|
+
└── resources/ # PNG frames referenced by screencast-frame events
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Vibium Player is the recommended online viewer. For local-only inspection:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
npx playwright show-trace ./artefacts/login.zip
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The export includes Craftdriver actions, screenshots, screenshot-backed frame
|
|
80
|
+
snapshots, navigation/console events, and the network metadata that WebDriver
|
|
81
|
+
BiDi exposes to the tracer. Screenshot-backed snapshots make the main panel of
|
|
82
|
+
Playwright Trace Viewer useful while staying honest: they are not a restorable
|
|
83
|
+
DOM. Response bodies and source files remain future work. Browser action spans
|
|
84
|
+
use real start/end times and preserve thrown action errors.
|
|
85
|
+
|
|
42
86
|
## What happens when a test throws
|
|
43
87
|
|
|
44
88
|
Every recorded event hits disk **before** the next line of your test
|
|
@@ -70,13 +114,14 @@ since `startTrace`) and a `type`:
|
|
|
70
114
|
| `type` | Fields |
|
|
71
115
|
| -------------- | ----------------------------------------------------------- |
|
|
72
116
|
| `meta` | `startedAt` / `endedAt`, `opts` on the start line |
|
|
73
|
-
| `action` | `name`, `args?`, `selector?`
|
|
117
|
+
| `action` | `actionIndex`, `name`, `args?`, `selector?` |
|
|
118
|
+
| `action-end` | `actionIndex`, `error?` |
|
|
74
119
|
| `console` | `level`, `text` |
|
|
75
120
|
| `error` | `text` (uncaught page errors) |
|
|
76
121
|
| `request` | `url`, `method`, `requestId?` |
|
|
77
122
|
| `response` | `url`, `status`, `mimeType?`, `fromCache?`, `requestId?` |
|
|
78
123
|
| `navigation` | `url`, `context?` |
|
|
79
|
-
| `screenshot` | `file` (relative path), `reason` (`'action'` \| `'error'`), `actionIndex?` |
|
|
124
|
+
| `screenshot` | `file` (relative path), `reason` (`'action'` \| `'error'` \| `'final'`), `actionIndex?` |
|
|
80
125
|
|
|
81
126
|
Actions currently logged: `navigateTo`, `goBack`, `goForward`, `reload`,
|
|
82
127
|
`setContent`, `click`, `fill`, `clear`, `acceptDialog`, `dismissDialog`.
|
|
@@ -88,6 +133,8 @@ Screenshots are tied to **meaningful moments**, not a timer:
|
|
|
88
133
|
* Before every logged action — answers *"what did the page look like
|
|
89
134
|
when I clicked?"*
|
|
90
135
|
* On every page error — answers *"what was on screen when it broke?"*
|
|
136
|
+
* Once when `stopTrace()` runs — preserves the state left by the final action
|
|
137
|
+
or failed assertion.
|
|
91
138
|
|
|
92
139
|
A 30-second test with 5 clicks produces 5 PNGs, not 300. Turn it off
|
|
93
140
|
when you only want the JSON log:
|
|
@@ -149,7 +196,7 @@ Drop this into your test folder (e.g. `tests/auto-trace.ts`):
|
|
|
149
196
|
|
|
150
197
|
```ts
|
|
151
198
|
import { beforeEach, afterEach } from 'vitest';
|
|
152
|
-
import { rmSync
|
|
199
|
+
import { rmSync } from 'node:fs';
|
|
153
200
|
import { join } from 'node:path';
|
|
154
201
|
import type { Browser, TraceScreenshotMode } from 'craftdriver';
|
|
155
202
|
|
|
@@ -170,19 +217,15 @@ export function autoTrace(getBrowser: () => Browser): void {
|
|
|
170
217
|
|
|
171
218
|
afterEach(async ({ task }) => {
|
|
172
219
|
const failed = task.result?.state === 'fail';
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
writeFileSync(join(currentDir, 'final.png'), buf);
|
|
179
|
-
} catch { /* browser may already be dead */ }
|
|
180
|
-
}
|
|
181
|
-
try { await getBrowser().stopTrace(); } catch { return; }
|
|
220
|
+
const keep = MODE === 'always' || failed;
|
|
221
|
+
const zipPath = `${currentDir}.zip`;
|
|
222
|
+
try {
|
|
223
|
+
await getBrowser().stopTrace(keep ? { path: zipPath } : undefined);
|
|
224
|
+
} catch { return; }
|
|
182
225
|
if (MODE === 'on-failure' && !failed) {
|
|
183
226
|
rmSync(currentDir, { recursive: true, force: true });
|
|
184
227
|
} else if (failed) {
|
|
185
|
-
console.error(`
|
|
228
|
+
console.error(` 📦 Vibium trace: ${zipPath}`);
|
|
186
229
|
}
|
|
187
230
|
});
|
|
188
231
|
}
|
|
@@ -202,7 +245,7 @@ import { autoTrace } from './auto-trace';
|
|
|
202
245
|
|
|
203
246
|
describe('Login', () => {
|
|
204
247
|
let browser: Browser;
|
|
205
|
-
beforeAll(async () => { browser = await Browser.launch(
|
|
248
|
+
beforeAll(async () => { browser = await Browser.launch(); });
|
|
206
249
|
afterAll(async () => { await browser.quit(); });
|
|
207
250
|
|
|
208
251
|
autoTrace(() => browser); // ← that's it
|
|
@@ -211,7 +254,7 @@ describe('Login', () => {
|
|
|
211
254
|
await browser.navigateTo('http://127.0.0.1:8080/login.html');
|
|
212
255
|
await browser.fill('#user', 'alice');
|
|
213
256
|
await browser.click('#submit');
|
|
214
|
-
// If this expect throws, ./traces/login/signs-in
|
|
257
|
+
// If this expect throws, ./traces/login/signs-in.zip opens in Vibium Player.
|
|
215
258
|
});
|
|
216
259
|
});
|
|
217
260
|
```
|
|
@@ -238,6 +281,42 @@ craftdriver — and the runner-specific glue (pass/fail detection, hook
|
|
|
238
281
|
order, output paths) belongs on your side. The helper is ~30 lines you
|
|
239
282
|
can read, copy, and adjust to your team's conventions.
|
|
240
283
|
|
|
284
|
+
### One trace for the whole suite
|
|
285
|
+
|
|
286
|
+
If the desired boundary is exactly `beforeAll()` → `afterAll()`, record the
|
|
287
|
+
suite continuously and remember failures in `afterEach()`. A passing suite
|
|
288
|
+
does not create a zip; a failing suite does:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
import { afterAll, afterEach, beforeAll, describe } from 'vitest';
|
|
292
|
+
import { rmSync } from 'node:fs';
|
|
293
|
+
import { Browser } from 'craftdriver';
|
|
294
|
+
|
|
295
|
+
describe('checkout', () => {
|
|
296
|
+
let browser: Browser;
|
|
297
|
+
let failed = false;
|
|
298
|
+
const rawDir = './traces/checkout-raw';
|
|
299
|
+
const zipPath = './traces/checkout-failure.zip';
|
|
300
|
+
|
|
301
|
+
beforeAll(async () => {
|
|
302
|
+
browser = await Browser.launch();
|
|
303
|
+
await browser.startTrace({ outDir: rawDir, title: 'Checkout suite' });
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
afterEach(({ task }) => {
|
|
307
|
+
failed ||= task.result?.state === 'fail';
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
afterAll(async () => {
|
|
311
|
+
await browser.stopTrace(failed ? { path: zipPath } : undefined);
|
|
312
|
+
if (!failed) rmSync(rawDir, { recursive: true, force: true });
|
|
313
|
+
await browser.quit();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// ...tests...
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
241
320
|
## API
|
|
242
321
|
|
|
243
322
|
### `browser.startTrace(opts)`
|
|
@@ -249,6 +328,7 @@ interface TraceStartOptions {
|
|
|
249
328
|
network?: boolean; // default true
|
|
250
329
|
console?: boolean; // default true
|
|
251
330
|
screenshots?: boolean | 'auto' | 'off'; // default 'auto'
|
|
331
|
+
title?: string; // viewer title
|
|
252
332
|
}
|
|
253
333
|
```
|
|
254
334
|
|
|
@@ -256,11 +336,12 @@ Creates `outDir` if missing, opens `outDir/trace.ndjson` for writing,
|
|
|
256
336
|
and emits the start `meta` line. Throws if a trace is already running
|
|
257
337
|
or BiDi is not enabled.
|
|
258
338
|
|
|
259
|
-
### `browser.stopTrace()`
|
|
339
|
+
### `browser.stopTrace(opts?)`
|
|
260
340
|
|
|
261
|
-
|
|
262
|
-
and closes the file.
|
|
263
|
-
|
|
341
|
+
Captures the final page state, drains in-flight screenshot captures, writes the closing `meta` line,
|
|
342
|
+
and closes the file. With `{ path: './trace.zip' }`, it also exports a
|
|
343
|
+
Vibium/Playwright-compatible zip. Returns `Promise<void>`. Throws if no
|
|
344
|
+
trace is active.
|
|
264
345
|
|
|
265
346
|
### Cleanup on `browser.quit()`
|
|
266
347
|
|
|
@@ -272,11 +353,8 @@ case. No file is deleted.
|
|
|
272
353
|
|
|
273
354
|
By design, the tracer does not:
|
|
274
355
|
|
|
275
|
-
* produce a self-contained `.zip` or hosted viewer,
|
|
276
356
|
* record video / fixed-interval screencast,
|
|
277
|
-
*
|
|
278
|
-
* capture DOM snapshots or sourcemaps.
|
|
357
|
+
* capture a restorable DOM or sourcemaps.
|
|
279
358
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
handful of PNGs beats a zip you can't open.
|
|
359
|
+
The raw NDJSON stays deliberately small and tail-able; richer portable
|
|
360
|
+
features can be added to the zip format without weakening crash resilience.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craftdriver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Modern WebDriver automation library for NodeJS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"test:chromium": "BROWSER_NAME=chromium vitest run",
|
|
20
20
|
"test:recipes": "HEADLESS=true vitest run --config vitest.recipes.config.ts",
|
|
21
21
|
"bench": "HEADLESS=true vitest run --config vitest.perf.config.ts",
|
|
22
|
-
"bench:realapp": "HEADLESS=true vitest run --config vitest.perf.realapp.config.ts",
|
|
23
22
|
"clean": "rimraf dist",
|
|
24
23
|
"serve": "http-server ./examples -a 127.0.0.1 -p 8080 -c-1 --cors",
|
|
25
24
|
"examples:start": "npm run serve",
|
|
@@ -82,13 +81,9 @@
|
|
|
82
81
|
"@semantic-release/git": "^10.0.1",
|
|
83
82
|
"@types/node": "^20.0.0",
|
|
84
83
|
"@types/ws": "^8.18.1",
|
|
85
|
-
"@
|
|
84
|
+
"@types/yazl": "^3.3.1",
|
|
86
85
|
"@typescript-eslint/parser": "^8.1.0",
|
|
87
86
|
"eslint": "^9.8.0",
|
|
88
|
-
"eslint-config-prettier": "^9.1.0",
|
|
89
|
-
"eslint-plugin-import": "^2.29.1",
|
|
90
|
-
"eslint-plugin-n": "^17.10.1",
|
|
91
|
-
"eslint-plugin-promise": "^6.4.0",
|
|
92
87
|
"http-server": "^14.1.1",
|
|
93
88
|
"prettier": "^3.3.3",
|
|
94
89
|
"rimraf": "^6.1.3",
|
|
@@ -99,7 +94,8 @@
|
|
|
99
94
|
},
|
|
100
95
|
"dependencies": {
|
|
101
96
|
"axe-core": "^4.12.1",
|
|
102
|
-
"ws": "^8.21.0"
|
|
97
|
+
"ws": "^8.21.0",
|
|
98
|
+
"yazl": "^3.3.1"
|
|
103
99
|
},
|
|
104
100
|
"overrides": {
|
|
105
101
|
"brace-expansion": "5.0.6",
|
|
@@ -34,8 +34,9 @@ break the moment markup shifts.
|
|
|
34
34
|
3. `instanceof CraftdriverError` is true on every public throw;
|
|
35
35
|
`instanceof Error` is also true.
|
|
36
36
|
4. BiDi features (`network`, `logs`, tracing, init scripts, true load
|
|
37
|
-
events)
|
|
38
|
-
|
|
37
|
+
events) work out of the box — `enableBiDi` defaults to `true`. They only
|
|
38
|
+
break if you explicitly pass `enableBiDi: false`; the error code on the
|
|
39
|
+
wrong transport is `UNSUPPORTED`.
|
|
39
40
|
5. Tests fetch from the example server. Start it in a separate
|
|
40
41
|
terminal: `npm run examples:start` before `npm test`.
|
|
41
42
|
|