craftdriver 1.2.0 → 1.4.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.
Files changed (113) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -1
  3. package/dist/cli/dispatcher.d.ts.map +1 -1
  4. package/dist/cli/dispatcher.js +13 -1
  5. package/dist/cli/dispatcher.js.map +1 -1
  6. package/dist/cli/mcp/tools.d.ts +1 -2
  7. package/dist/cli/mcp/tools.d.ts.map +1 -1
  8. package/dist/cli/mcp/tools.js +18 -0
  9. package/dist/cli/mcp/tools.js.map +1 -1
  10. package/dist/index.d.ts +7 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/browser.d.ts +156 -27
  15. package/dist/lib/browser.d.ts.map +1 -1
  16. package/dist/lib/browser.js +474 -212
  17. package/dist/lib/browser.js.map +1 -1
  18. package/dist/lib/capabilities.d.ts +35 -0
  19. package/dist/lib/capabilities.d.ts.map +1 -0
  20. package/dist/lib/capabilities.js +58 -0
  21. package/dist/lib/capabilities.js.map +1 -0
  22. package/dist/lib/chrome.d.ts +7 -2
  23. package/dist/lib/chrome.d.ts.map +1 -1
  24. package/dist/lib/chrome.js +10 -1
  25. package/dist/lib/chrome.js.map +1 -1
  26. package/dist/lib/driverManager.d.ts +92 -0
  27. package/dist/lib/driverManager.d.ts.map +1 -1
  28. package/dist/lib/driverManager.js +229 -10
  29. package/dist/lib/driverManager.js.map +1 -1
  30. package/dist/lib/electron.d.ts +60 -0
  31. package/dist/lib/electron.d.ts.map +1 -0
  32. package/dist/lib/electron.js +85 -0
  33. package/dist/lib/electron.js.map +1 -0
  34. package/dist/lib/electronDeeplink.d.ts +22 -0
  35. package/dist/lib/electronDeeplink.d.ts.map +1 -0
  36. package/dist/lib/electronDeeplink.js +118 -0
  37. package/dist/lib/electronDeeplink.js.map +1 -0
  38. package/dist/lib/electronDiagnostics.d.ts +49 -0
  39. package/dist/lib/electronDiagnostics.d.ts.map +1 -0
  40. package/dist/lib/electronDiagnostics.js +195 -0
  41. package/dist/lib/electronDiagnostics.js.map +1 -0
  42. package/dist/lib/electronDialogMock.d.ts +51 -0
  43. package/dist/lib/electronDialogMock.d.ts.map +1 -0
  44. package/dist/lib/electronDialogMock.js +106 -0
  45. package/dist/lib/electronDialogMock.js.map +1 -0
  46. package/dist/lib/electronFuses.d.ts +13 -0
  47. package/dist/lib/electronFuses.d.ts.map +1 -0
  48. package/dist/lib/electronFuses.js +118 -0
  49. package/dist/lib/electronFuses.js.map +1 -0
  50. package/dist/lib/electronMainBridge.d.ts +28 -0
  51. package/dist/lib/electronMainBridge.d.ts.map +1 -0
  52. package/dist/lib/electronMainBridge.js +270 -0
  53. package/dist/lib/electronMainBridge.js.map +1 -0
  54. package/dist/lib/electronMainLogs.d.ts +81 -0
  55. package/dist/lib/electronMainLogs.d.ts.map +1 -0
  56. package/dist/lib/electronMainLogs.js +176 -0
  57. package/dist/lib/electronMainLogs.js.map +1 -0
  58. package/dist/lib/electronMock.d.ts +52 -0
  59. package/dist/lib/electronMock.d.ts.map +1 -0
  60. package/dist/lib/electronMock.js +96 -0
  61. package/dist/lib/electronMock.js.map +1 -0
  62. package/dist/lib/electronRemote.d.ts +106 -0
  63. package/dist/lib/electronRemote.d.ts.map +1 -0
  64. package/dist/lib/electronRemote.js +406 -0
  65. package/dist/lib/electronRemote.js.map +1 -0
  66. package/dist/lib/electronVersions.d.ts +34 -0
  67. package/dist/lib/electronVersions.d.ts.map +1 -0
  68. package/dist/lib/electronVersions.js +90 -0
  69. package/dist/lib/electronVersions.js.map +1 -0
  70. package/dist/lib/errors.d.ts +27 -0
  71. package/dist/lib/errors.d.ts.map +1 -1
  72. package/dist/lib/errors.js +27 -0
  73. package/dist/lib/errors.js.map +1 -1
  74. package/dist/lib/launchTarget.d.ts +30 -0
  75. package/dist/lib/launchTarget.d.ts.map +1 -0
  76. package/dist/lib/launchTarget.js +112 -0
  77. package/dist/lib/launchTarget.js.map +1 -0
  78. package/dist/lib/page.d.ts +1 -0
  79. package/dist/lib/page.d.ts.map +1 -1
  80. package/dist/lib/page.js +1 -1
  81. package/dist/lib/page.js.map +1 -1
  82. package/dist/lib/service.d.ts +21 -0
  83. package/dist/lib/service.d.ts.map +1 -1
  84. package/dist/lib/service.js +94 -6
  85. package/dist/lib/service.js.map +1 -1
  86. package/dist/lib/tracing.d.ts +20 -4
  87. package/dist/lib/tracing.d.ts.map +1 -1
  88. package/dist/lib/tracing.js +38 -3
  89. package/dist/lib/tracing.js.map +1 -1
  90. package/dist/lib/vibiumTrace.d.ts +14 -0
  91. package/dist/lib/vibiumTrace.d.ts.map +1 -0
  92. package/dist/lib/vibiumTrace.js +372 -0
  93. package/dist/lib/vibiumTrace.js.map +1 -0
  94. package/docs/api-reference.md +38 -2
  95. package/docs/browser-api.md +7 -1
  96. package/docs/electron.md +419 -0
  97. package/docs/error-codes.md +5 -0
  98. package/docs/index.md +4 -0
  99. package/docs/mcp.md +17 -3
  100. package/docs/public/recipes/trace-failing-test.html +16 -0
  101. package/docs/public/recipes/vitest-vibium-trace.html +16 -0
  102. package/docs/public/traces/vitest-login.zip +0 -0
  103. package/docs/recipes/debug-failing-tests-with-traces.md +117 -0
  104. package/docs/recipes/electron-app-from-another-repo.md +136 -0
  105. package/docs/recipes/electron-deep-link.md +117 -0
  106. package/docs/recipes/electron-mock-apis.md +88 -0
  107. package/docs/recipes/electron-native-dialog.md +105 -0
  108. package/docs/recipes.md +29 -10
  109. package/docs/tracing.md +102 -24
  110. package/package.json +8 -8
  111. package/skills/craftdriver/cheatsheet.md +4 -2
  112. package/skills/craftdriver/patterns.md +2 -1
  113. package/docs/recipes/trace-failing-test.md +0 -51
package/docs/tracing.md CHANGED
@@ -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, writeFileSync } from 'node:fs';
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
- // Snap the page as it looked at the failed assertion (not just
174
- // before the last click).
175
- if (failed) {
176
- try {
177
- const buf = await getBrowser().screenshot();
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(` 📁 trace: ${currentDir}`);
228
+ console.error(` 📦 Vibium trace: ${zipPath}`);
186
229
  }
187
230
  });
188
231
  }
@@ -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/trace.ndjson is on disk.
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
- Drains in-flight screenshot captures, writes the closing `meta` line,
262
- and closes the file. Returns `Promise<void>`. Throws if no trace is
263
- running.
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
- * emit HAR (the NDJSON already holds request/response events),
278
- * capture DOM snapshots or sourcemaps.
357
+ * capture a restorable DOM or sourcemaps.
279
358
 
280
- Those features make sense at much larger scale (Playwright Trace
281
- Viewer). For a single failing test, a tail-able NDJSON log plus a
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.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Modern WebDriver automation library for NodeJS",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,8 +18,8 @@
18
18
  "test:chrome": "BROWSER_NAME=chrome vitest run",
19
19
  "test:chromium": "BROWSER_NAME=chromium vitest run",
20
20
  "test:recipes": "HEADLESS=true vitest run --config vitest.recipes.config.ts",
21
+ "test:electron": "vitest run --config vitest.electron.config.ts",
21
22
  "bench": "HEADLESS=true vitest run --config vitest.perf.config.ts",
22
- "bench:realapp": "HEADLESS=true vitest run --config vitest.perf.realapp.config.ts",
23
23
  "clean": "rimraf dist",
24
24
  "serve": "http-server ./examples -a 127.0.0.1 -p 8080 -c-1 --cors",
25
25
  "examples:start": "npm run serve",
@@ -40,6 +40,9 @@
40
40
  "firefox",
41
41
  "chrome",
42
42
  "chromium",
43
+ "electron",
44
+ "electron-testing",
45
+ "desktop-app-testing",
43
46
  "automation",
44
47
  "browser-automation",
45
48
  "e2e",
@@ -82,13 +85,9 @@
82
85
  "@semantic-release/git": "^10.0.1",
83
86
  "@types/node": "^20.0.0",
84
87
  "@types/ws": "^8.18.1",
85
- "@typescript-eslint/eslint-plugin": "^8.1.0",
88
+ "@types/yazl": "^3.3.1",
86
89
  "@typescript-eslint/parser": "^8.1.0",
87
90
  "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
91
  "http-server": "^14.1.1",
93
92
  "prettier": "^3.3.3",
94
93
  "rimraf": "^6.1.3",
@@ -99,7 +98,8 @@
99
98
  },
100
99
  "dependencies": {
101
100
  "axe-core": "^4.12.1",
102
- "ws": "^8.21.0"
101
+ "ws": "^8.21.0",
102
+ "yazl": "^3.3.1"
103
103
  },
104
104
  "overrides": {
105
105
  "brace-expansion": "5.0.6",
@@ -159,8 +159,10 @@ const download = await browser.waitForDownload(() => browser.click('#download'))
159
159
  ```ts
160
160
  await browser.screenshot({ path: 'out.png', fullPage: true });
161
161
 
162
- await browser.startTrace({ outDir: './artefacts/run' });
163
- try { /* ... */ } finally { await browser.stopTrace(); }
162
+ await browser.startTrace({ outDir: './artefacts/run', title: 'Smoke flow' });
163
+ try { /* ... */ } finally {
164
+ await browser.stopTrace({ path: './artefacts/run.zip' }); // player.vibium.dev
165
+ }
164
166
  ```
165
167
 
166
168
  ## Accessibility
@@ -60,6 +60,7 @@ const due = await row.locator('[data-col=due]').text();
60
60
  ```ts
61
61
  await browser.startTrace({
62
62
  outDir: './artefacts/checkout-fail',
63
+ title: 'Checkout failure',
63
64
  screenshots: 'auto',
64
65
  network: true,
65
66
  console: true,
@@ -70,7 +71,7 @@ try {
70
71
  // Trace lands on disk regardless — thrown expects never lose data.
71
72
  throw e;
72
73
  } finally {
73
- await browser.stopTrace();
74
+ await browser.stopTrace({ path: './artefacts/checkout-fail.zip' });
74
75
  }
75
76
  ```
76
77
 
@@ -1,51 +0,0 @@
1
- # Capture Failure Evidence With Tracing
2
-
3
- When a complex or occasionally-flaky test fails, the message alone rarely tells
4
- you why. A trace records the actions, navigation, console output, JavaScript
5
- errors, network events, and screenshots so you can replay what happened. Wrap
6
- the flow in a helper that starts a trace, keeps it on failure, and always stops
7
- it. This recipe traces a sign-in against the live
8
- [login example](https://dtopuzov.github.io/craftdriver/examples/login.html).
9
-
10
- The `try/catch` here is deliberate — it is what keeps the trace directory when
11
- the flow throws.
12
-
13
- ```ts
14
- async function withTrace(name: string, run: () => Promise<void>) {
15
- const outDir = `./traces/${name}-${Date.now()}`;
16
- await browser.startTrace({ outDir });
17
-
18
- try {
19
- await run();
20
- } catch (error) {
21
- console.error(`Trace kept at ${outDir}`);
22
- throw error;
23
- } finally {
24
- await browser.stopTrace().catch(() => undefined);
25
- }
26
- }
27
-
28
- await withTrace('login', async () => {
29
- await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/login.html');
30
- await browser.getByLabel('Username').fill('alice');
31
- await browser.getByLabel('Password').fill('secret');
32
- await browser.getByRole('button', { name: 'Sign in' }).click();
33
- await browser.expect('#welcome').toContainText('Welcome back, alice!');
34
- });
35
- ```
36
-
37
- ## Notes
38
-
39
- - The trace file is NDJSON, so it stays useful even if the test throws before `stopTrace()`.
40
- - Screenshots are stored under the trace directory.
41
- - For high-volume suites, turn screenshots off unless a test is under investigation:
42
-
43
- ```ts
44
- await browser.startTrace({ outDir: './traces/smoke', screenshots: 'off' });
45
- ```
46
-
47
- ## Learn More
48
-
49
- - [Tracing](../tracing.md)
50
- - [Screenshots](../screenshots.md)
51
- - [Console Logs And JavaScript Errors](../browser-logs.md)