craftdriver 1.6.0 → 1.8.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 +35 -6
- package/README.md +17 -6
- package/dist/cli/agentSession.d.ts +62 -0
- package/dist/cli/agentSession.d.ts.map +1 -0
- package/dist/cli/agentSession.js +100 -0
- package/dist/cli/agentSession.js.map +1 -0
- package/dist/cli/artifactPaths.d.ts +42 -0
- package/dist/cli/artifactPaths.d.ts.map +1 -0
- package/dist/cli/artifactPaths.js +144 -0
- package/dist/cli/artifactPaths.js.map +1 -0
- package/dist/cli/bounds.d.ts +88 -0
- package/dist/cli/bounds.d.ts.map +1 -0
- package/dist/cli/bounds.js +128 -0
- package/dist/cli/bounds.js.map +1 -0
- package/dist/cli/client.d.ts +3 -0
- package/dist/cli/client.d.ts.map +1 -1
- package/dist/cli/client.js +24 -8
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/daemon.d.ts +3 -0
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +152 -39
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/defaults.d.ts +23 -3
- package/dist/cli/defaults.d.ts.map +1 -1
- package/dist/cli/defaults.js +50 -5
- package/dist/cli/defaults.js.map +1 -1
- package/dist/cli/dispatcher.d.ts +54 -11
- package/dist/cli/dispatcher.d.ts.map +1 -1
- package/dist/cli/dispatcher.js +851 -54
- package/dist/cli/dispatcher.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +305 -56
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +14 -14
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +328 -180
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/journal.d.ts +153 -0
- package/dist/cli/journal.d.ts.map +1 -0
- package/dist/cli/journal.js +252 -0
- package/dist/cli/journal.js.map +1 -0
- package/dist/cli/lineReader.d.ts +33 -0
- package/dist/cli/lineReader.d.ts.map +1 -0
- package/dist/cli/lineReader.js +66 -0
- package/dist/cli/lineReader.js.map +1 -0
- package/dist/cli/locatorCandidates.d.ts +44 -0
- package/dist/cli/locatorCandidates.d.ts.map +1 -0
- package/dist/cli/locatorCandidates.js +190 -0
- package/dist/cli/locatorCandidates.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +39 -1
- package/dist/cli/mcp/artifacts.d.ts.map +1 -1
- package/dist/cli/mcp/artifacts.js +104 -9
- package/dist/cli/mcp/artifacts.js.map +1 -1
- package/dist/cli/mcp/bounds.d.ts +65 -0
- package/dist/cli/mcp/bounds.d.ts.map +1 -0
- package/dist/cli/mcp/bounds.js +124 -0
- package/dist/cli/mcp/bounds.js.map +1 -0
- package/dist/cli/mcp/params.d.ts +38 -0
- package/dist/cli/mcp/params.d.ts.map +1 -0
- package/dist/cli/mcp/params.js +174 -0
- package/dist/cli/mcp/params.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +94 -16
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +304 -150
- package/dist/cli/mcp/server.js.map +1 -1
- package/dist/cli/mcp/tools.d.ts +62 -17
- package/dist/cli/mcp/tools.d.ts.map +1 -1
- package/dist/cli/mcp/tools.js +373 -158
- package/dist/cli/mcp/tools.js.map +1 -1
- package/dist/cli/pageSemantics.d.ts +42 -0
- package/dist/cli/pageSemantics.d.ts.map +1 -0
- package/dist/cli/pageSemantics.js +194 -0
- package/dist/cli/pageSemantics.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +6 -18
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +689 -45
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/cli/protocol.d.ts +8 -0
- package/dist/cli/protocol.d.ts.map +1 -1
- package/dist/cli/sessionRegistry.d.ts +42 -0
- package/dist/cli/sessionRegistry.d.ts.map +1 -0
- package/dist/cli/sessionRegistry.js +99 -0
- package/dist/cli/sessionRegistry.js.map +1 -0
- package/dist/cli/snapshot.d.ts +100 -18
- package/dist/cli/snapshot.d.ts.map +1 -1
- package/dist/cli/snapshot.js +248 -102
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/cli/stateStore.d.ts +50 -0
- package/dist/cli/stateStore.d.ts.map +1 -0
- package/dist/cli/stateStore.js +122 -0
- package/dist/cli/stateStore.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/bidi/index.d.ts +1 -1
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +2 -2
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +3 -0
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +7 -0
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/bidi/network.d.ts +14 -1
- package/dist/lib/bidi/network.d.ts.map +1 -1
- package/dist/lib/bidi/network.js +45 -7
- package/dist/lib/bidi/network.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +30 -6
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +190 -45
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/bidi/types.d.ts +7 -2
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +38 -3
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +196 -30
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +54 -18
- package/dist/lib/browserContext.d.ts.map +1 -1
- package/dist/lib/browserContext.js +295 -92
- package/dist/lib/browserContext.js.map +1 -1
- package/dist/lib/by.d.ts +3 -3
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +7 -3
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/driver.d.ts +7 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +12 -0
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/errors.d.ts +15 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +15 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/initScript.d.ts +9 -0
- package/dist/lib/initScript.d.ts.map +1 -0
- package/dist/lib/initScript.js +14 -0
- package/dist/lib/initScript.js.map +1 -0
- package/dist/lib/page.d.ts +22 -0
- package/dist/lib/page.d.ts.map +1 -1
- package/dist/lib/page.js +48 -0
- package/dist/lib/page.js.map +1 -1
- package/dist/lib/secureFile.d.ts +8 -0
- package/dist/lib/secureFile.d.ts.map +1 -0
- package/dist/lib/secureFile.js +67 -0
- package/dist/lib/secureFile.js.map +1 -0
- package/dist/lib/sessionStateValidation.d.ts +35 -0
- package/dist/lib/sessionStateValidation.d.ts.map +1 -0
- package/dist/lib/sessionStateValidation.js +223 -0
- package/dist/lib/sessionStateValidation.js.map +1 -0
- package/dist/lib/timing.d.ts +8 -0
- package/dist/lib/timing.d.ts.map +1 -1
- package/dist/lib/timing.js +8 -0
- package/dist/lib/timing.js.map +1 -1
- package/dist/lib/tracing.d.ts +2 -1
- package/dist/lib/tracing.d.ts.map +1 -1
- package/dist/lib/tracing.js +12 -1
- package/dist/lib/tracing.js.map +1 -1
- package/dist/lib/visual/antialias.d.ts +41 -0
- package/dist/lib/visual/antialias.d.ts.map +1 -0
- package/dist/lib/visual/antialias.js +126 -0
- package/dist/lib/visual/antialias.js.map +1 -0
- package/dist/lib/visual/codec.d.ts +24 -0
- package/dist/lib/visual/codec.d.ts.map +1 -0
- package/dist/lib/visual/codec.js +11 -0
- package/dist/lib/visual/codec.js.map +1 -0
- package/dist/lib/visual/compare.d.ts +38 -0
- package/dist/lib/visual/compare.d.ts.map +1 -0
- package/dist/lib/visual/compare.js +209 -0
- package/dist/lib/visual/compare.js.map +1 -0
- package/dist/lib/visual/diff.d.ts +18 -0
- package/dist/lib/visual/diff.d.ts.map +1 -0
- package/dist/lib/visual/diff.js +23 -0
- package/dist/lib/visual/diff.js.map +1 -0
- package/dist/lib/visual/expectScreenshot.d.ts +65 -0
- package/dist/lib/visual/expectScreenshot.d.ts.map +1 -0
- package/dist/lib/visual/expectScreenshot.js +287 -0
- package/dist/lib/visual/expectScreenshot.js.map +1 -0
- package/dist/lib/visual/index.d.ts +10 -0
- package/dist/lib/visual/index.d.ts.map +1 -0
- package/dist/lib/visual/index.js +9 -0
- package/dist/lib/visual/index.js.map +1 -0
- package/dist/lib/visual/pixelMath.d.ts +32 -0
- package/dist/lib/visual/pixelMath.d.ts.map +1 -0
- package/dist/lib/visual/pixelMath.js +70 -0
- package/dist/lib/visual/pixelMath.js.map +1 -0
- package/dist/lib/visual/pngHeader.d.ts +27 -0
- package/dist/lib/visual/pngHeader.d.ts.map +1 -0
- package/dist/lib/visual/pngHeader.js +63 -0
- package/dist/lib/visual/pngHeader.js.map +1 -0
- package/dist/lib/visual/pngjsCodec.d.ts +22 -0
- package/dist/lib/visual/pngjsCodec.d.ts.map +1 -0
- package/dist/lib/visual/pngjsCodec.js +70 -0
- package/dist/lib/visual/pngjsCodec.js.map +1 -0
- package/dist/lib/visual/types.d.ts +89 -0
- package/dist/lib/visual/types.d.ts.map +1 -0
- package/dist/lib/visual/types.js +20 -0
- package/dist/lib/visual/types.js.map +1 -0
- package/dist/lib/visual/visualError.d.ts +32 -0
- package/dist/lib/visual/visualError.d.ts.map +1 -0
- package/dist/lib/visual/visualError.js +59 -0
- package/dist/lib/visual/visualError.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +7 -1
- package/dist/lib/wait.js.map +1 -1
- package/docs/agents.md +56 -61
- package/docs/api-reference.md +16 -1
- package/docs/browser-api.md +24 -4
- package/docs/browser-context.md +18 -12
- package/docs/cli.md +346 -26
- package/docs/error-codes.md +6 -4
- package/docs/getting-started.md +6 -1
- package/docs/index.md +1 -1
- package/docs/mcp.md +129 -62
- package/docs/public/examples/agent-actions.html +48 -0
- package/docs/public/examples/agent-debug.html +86 -0
- package/docs/public/examples/agent-late-mutation.html +18 -0
- package/docs/public/examples/agent-ref-shift.html +33 -0
- package/docs/recipes/login-once-reuse-session.md +32 -4
- package/docs/screenshots.md +15 -6
- package/docs/session-management.md +64 -3
- package/docs/visual-testing.md +254 -0
- package/docs/why-craftdriver.md +4 -3
- package/package.json +5 -1
- package/skills/craftdriver/SKILL.md +59 -75
- package/skills/craftdriver/agents/openai.yaml +4 -0
- package/skills/craftdriver/cheatsheet.md +18 -4
- package/skills/craftdriver/cli.md +194 -68
- package/skills/craftdriver/patterns.md +14 -8
- package/skills/craftdriver/workflow.md +149 -0
|
@@ -9,6 +9,10 @@ CraftDriver supports Playwright-style session persistence, allowing you to save
|
|
|
9
9
|
Session management is the user-facing feature. Under the hood, CraftDriver uses
|
|
10
10
|
the best available WebDriver transport for the browser you launched.
|
|
11
11
|
|
|
12
|
+
`storageState` is CraftDriver's native cookies-plus-localStorage format. It is
|
|
13
|
+
not a promise of Playwright JSON compatibility and does not include IndexedDB,
|
|
14
|
+
Cache Storage, service workers, or reusable sessionStorage.
|
|
15
|
+
|
|
12
16
|
## Saving Session State
|
|
13
17
|
|
|
14
18
|
```typescript
|
|
@@ -52,6 +56,9 @@ The saved file contains:
|
|
|
52
56
|
```typescript
|
|
53
57
|
// Load state into current browser
|
|
54
58
|
await browser.loadState('./session.json');
|
|
59
|
+
|
|
60
|
+
// Paths and in-memory state objects are equivalent
|
|
61
|
+
await browser.loadState(await browser.storage.getState());
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
## Working With State Objects
|
|
@@ -63,6 +70,11 @@ const state = await browser.storage.getState();
|
|
|
63
70
|
await browser.storage.setState(state);
|
|
64
71
|
```
|
|
65
72
|
|
|
73
|
+
`browser.storage.setState()` is an active-page operation: navigate to the
|
|
74
|
+
state's sole HTTP(S) origin first. `browser.loadState()` uses the full
|
|
75
|
+
multi-origin hydrator on BiDi when the state has no sessionStorage; otherwise it
|
|
76
|
+
uses the same strict active-page rules.
|
|
77
|
+
|
|
66
78
|
## Launching with Pre-loaded State
|
|
67
79
|
|
|
68
80
|
The most common pattern - launch a browser with existing session:
|
|
@@ -78,6 +90,55 @@ await browser.navigateTo('https://example.com/dashboard');
|
|
|
78
90
|
// Already logged in!
|
|
79
91
|
```
|
|
80
92
|
|
|
93
|
+
On supported BiDi sessions, cookies and every captured localStorage origin are
|
|
94
|
+
ready before the first real navigation. A private intercepted document seeds
|
|
95
|
+
each origin once; application changes are not overwritten on reload.
|
|
96
|
+
|
|
97
|
+
## Browser and Transport Support
|
|
98
|
+
|
|
99
|
+
| Surface | Chrome/Chromium BiDi | Firefox BiDi | Chrome/Firefox/Safari Classic |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| `Browser.launch({ storageState })` | Full cookies + multi-origin localStorage | Full cookies + multi-origin localStorage | Non-empty state rejected before mutation |
|
|
102
|
+
| `browser.newContext({ storageState })` | Full isolated restore | Full isolated restore | Unavailable (Classic has no user contexts) |
|
|
103
|
+
| `context.loadStorageState()` | Full multi-origin overlay | Full multi-origin overlay | Unavailable |
|
|
104
|
+
| `browser.loadState()` | Full multi-origin overlay without sessionStorage; strict active page with it | Same | Strict single-active-origin restore |
|
|
105
|
+
| Save | Cookies + current/open-page localStorage origins | Same | Cookies + current-page localStorage |
|
|
106
|
+
|
|
107
|
+
Chrome and Firefox BiDi and the Chrome/Firefox Classic fallback are covered by
|
|
108
|
+
the integration suite. Chromium uses the same Chrome-family BiDi implementation.
|
|
109
|
+
Safari is Classic-only and therefore receives the standards-only active-origin
|
|
110
|
+
contract, not launch-time restore.
|
|
111
|
+
|
|
112
|
+
### WebDriver Classic
|
|
113
|
+
|
|
114
|
+
Classic cannot write arbitrary-origin localStorage at launch. Use the explicit
|
|
115
|
+
fallback instead:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
const browser = await Browser.launch({ enableBiDi: false });
|
|
119
|
+
await browser.navigateTo('https://example.com');
|
|
120
|
+
await browser.loadState('./session.json');
|
|
121
|
+
await browser.reload(); // the app can now read the restored state
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
CraftDriver validates the entire snapshot before mutation. `about:blank`,
|
|
125
|
+
multiple storage origins, a mismatched cookie domain, or a secure cookie that
|
|
126
|
+
cannot be set from the active page fails with `STATE_INVALID`. Non-empty
|
|
127
|
+
`storageState` at Classic launch fails with `UNSUPPORTED` instead of pretending
|
|
128
|
+
that a partial restore succeeded.
|
|
129
|
+
|
|
130
|
+
### sessionStorage and runtime failure semantics
|
|
131
|
+
|
|
132
|
+
`sessionStorage` capture is opt-in diagnostic state. Context and launch APIs
|
|
133
|
+
reject a non-empty `sessionStorage` section because it cannot be transferred to
|
|
134
|
+
future tabs. Active-page APIs can restore one matching origin after navigation.
|
|
135
|
+
|
|
136
|
+
Fresh launch and `newContext` restoration clean up the new session/context on
|
|
137
|
+
failure. An overlay into an existing context is not rollback-safe across
|
|
138
|
+
multiple protocol calls. A runtime failure throws `DRIVER_ERROR` with
|
|
139
|
+
`detail.phase` and `detail.partialApplied`; use a fresh context when failure
|
|
140
|
+
isolation matters.
|
|
141
|
+
|
|
81
142
|
## Cookie Management API
|
|
82
143
|
|
|
83
144
|
Direct cookie manipulation is also available:
|
|
@@ -221,7 +282,7 @@ async function testLoginPersistence() {
|
|
|
221
282
|
| Method | Description |
|
|
222
283
|
| --------------------------- | ---------------------------------- |
|
|
223
284
|
| `saveState(path, options?)` | Save cookies and storage to file |
|
|
224
|
-
| `loadState(
|
|
285
|
+
| `loadState(source)` | Load cookies and storage from a path or object |
|
|
225
286
|
|
|
226
287
|
### StorageStateOptions
|
|
227
288
|
|
|
@@ -241,6 +302,6 @@ async function testLoginPersistence() {
|
|
|
241
302
|
| `setCookies(cookies)` | Set multiple cookies |
|
|
242
303
|
| `clearCookies(filter?)` | Clear cookies, optionally filtered |
|
|
243
304
|
| `getState(options?)` | Get current state as object |
|
|
244
|
-
| `setState(
|
|
305
|
+
| `setState(source)` | Set active-page state from path or object |
|
|
245
306
|
| `saveState(path, options?)` | Save state to file |
|
|
246
|
-
| `loadState(
|
|
307
|
+
| `loadState(source)` | Load active-page state from path or object |
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Visual testing
|
|
2
|
+
|
|
3
|
+
`browser.expectScreenshot()` compares a fresh browser screenshot against a PNG
|
|
4
|
+
baseline in your repository, retrying until it matches or a timeout elapses. On
|
|
5
|
+
a mismatch it throws a typed error carrying the final screenshot and a generated
|
|
6
|
+
diff image so you can see exactly what changed.
|
|
7
|
+
|
|
8
|
+
Baselines are managed for you, so the first run of a new test just works:
|
|
9
|
+
|
|
10
|
+
- **No baseline yet** — the screenshot is captured until two consecutive frames
|
|
11
|
+
are identical (so a mid-animation frame isn't enshrined) or `timeout` elapses,
|
|
12
|
+
then written to the path you gave, and the assertion passes. Commit that PNG.
|
|
13
|
+
If the page never settled before the timeout, the write is an arbitrary frame
|
|
14
|
+
and the stderr line says so (`… page did not settle …`) — review it first.
|
|
15
|
+
- **Baseline matches** — passes.
|
|
16
|
+
- **Baseline differs** — throws `VisualMismatchError`. It is **never** rewritten
|
|
17
|
+
unless you explicitly ask (see [Updating baselines](#updating-baselines)).
|
|
18
|
+
|
|
19
|
+
Behaviour is identical on your laptop and in CI — there is no "CI mode" that
|
|
20
|
+
does something different. One tradeoff comes with the convenience: if a
|
|
21
|
+
committed baseline is *deleted*, the next run recreates it and passes. Every
|
|
22
|
+
create and update is printed to stderr (`[craftdriver] Created visual
|
|
23
|
+
baseline: …`) so it shows up in your logs, but the real guard is committing your
|
|
24
|
+
baselines and reviewing the diff whenever one changes.
|
|
25
|
+
|
|
26
|
+
## The shortest thing that works
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// First run creates baselines/home.png; commit it. Every run after asserts
|
|
30
|
+
// against it — same call, no separate bootstrap step.
|
|
31
|
+
await browser.expectScreenshot('baselines/home.png', {
|
|
32
|
+
screenshot: { fullPage: true },
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`expectScreenshot()` returns a result object on success (`baseline`, `matches`,
|
|
37
|
+
`diffPixels`, `diffPercentage`, `attempts`, `elapsedMs`, and both images'
|
|
38
|
+
dimensions) and throws [`VisualMismatchError`](./error-codes.md) on failure.
|
|
39
|
+
`result.baseline` is `'created'`, `'matched'`, or `'updated'` — what happened to
|
|
40
|
+
the file on disk this run.
|
|
41
|
+
|
|
42
|
+
## Capture scope
|
|
43
|
+
|
|
44
|
+
`screenshot` chooses what to capture and is passed straight through to
|
|
45
|
+
`browser.screenshot()`. The three forms mirror that method and are mutually
|
|
46
|
+
exclusive:
|
|
47
|
+
|
|
48
|
+
| `screenshot` | Captures | Notes |
|
|
49
|
+
| ---------------------------- | ------------------------------- | -------------------------------------- |
|
|
50
|
+
| omitted | the visible viewport | Classic or BiDi. |
|
|
51
|
+
| `{ fullPage: true }` | the full scrollable document | Requires BiDi (`enableBiDi: true`, the default). |
|
|
52
|
+
| `{ selector: '#chart' }` | one element | Auto-waits for the element like `find()`. |
|
|
53
|
+
|
|
54
|
+
`fullPage` and `selector` together are rejected — by the TypeScript types and,
|
|
55
|
+
for JavaScript callers, at runtime with `INVALID_ARGUMENT`. Always baseline and
|
|
56
|
+
assert with the same scope; a full-page baseline compared against a viewport
|
|
57
|
+
capture is just a dimension mismatch.
|
|
58
|
+
|
|
59
|
+
## Thresholds
|
|
60
|
+
|
|
61
|
+
By default the comparison is exact: any differing pixel fails. Loosen it with
|
|
62
|
+
three independent policies.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
await browser.expectScreenshot('baselines/home.png', {
|
|
66
|
+
pixelTolerance: 5, // per-channel RGB slack, 0..255 (default 0)
|
|
67
|
+
maxDiffPixels: 100, // allow up to 100 differing pixels
|
|
68
|
+
maxDiffPercentage: 0.1, // …and no more than 0.1% of all pixels
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **`pixelTolerance`** — a pixel counts as equal when the absolute difference of
|
|
73
|
+
every visible R, G and B channel is `<= pixelTolerance`. Use it to absorb tiny
|
|
74
|
+
codec/renderer noise.
|
|
75
|
+
- **`maxDiffPixels`** / **`maxDiffPercentage`** — budgets for how many pixels may
|
|
76
|
+
still differ after `pixelTolerance` is applied. Each boundary is **inclusive**.
|
|
77
|
+
- If you set **neither** budget, the image must have zero differing pixels.
|
|
78
|
+
- If you set **both**, **both** must pass.
|
|
79
|
+
- Dimensions are never resized to match. Different dimensions always fail,
|
|
80
|
+
regardless of tolerance — resizing would hide the layout regressions you are
|
|
81
|
+
trying to catch.
|
|
82
|
+
|
|
83
|
+
All values are validated, never clamped: a non-integer `pixelTolerance`, a
|
|
84
|
+
percentage above 100, an `interval` below 10 ms, and similar out-of-range inputs
|
|
85
|
+
throw `INVALID_ARGUMENT`.
|
|
86
|
+
|
|
87
|
+
### Ignoring anti-aliasing
|
|
88
|
+
|
|
89
|
+
Anti-aliasing along text and edges is the most common source of
|
|
90
|
+
platform-to-platform noise. `ignoreAntialiasing` classifies edge-smoothing
|
|
91
|
+
pixels and excludes them from the difference count (reporting them separately as
|
|
92
|
+
`ignoredAntialiasPixels`):
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
await browser.expectScreenshot('baselines/home.png', {
|
|
96
|
+
ignoreAntialiasing: true,
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
It is **off by default on purpose**. Silently ignoring pixels is a policy
|
|
101
|
+
decision that can hide thin, genuine regressions, so you opt in explicitly. It
|
|
102
|
+
ignores edge smoothing but still counts moved edges, missing glyphs, and
|
|
103
|
+
one-pixel layout shifts.
|
|
104
|
+
|
|
105
|
+
## Handling a mismatch
|
|
106
|
+
|
|
107
|
+
`VisualMismatchError` carries the two things you actually want when a visual
|
|
108
|
+
test fails: the final screenshot and a diff image. Persist them as CI artifacts:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { writeFile } from 'node:fs/promises';
|
|
112
|
+
import { VisualMismatchError } from 'craftdriver';
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await browser.expectScreenshot('baselines/home.png', { timeout: 3_000 });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error instanceof VisualMismatchError) {
|
|
118
|
+
await writeFile('artifacts/home.actual.png', error.actual);
|
|
119
|
+
await writeFile('artifacts/home.diff.png', error.diff);
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
In the diff image, unchanged areas appear as a dimmed grayscale copy of the
|
|
126
|
+
actual screenshot, counted differences are red, ignored anti-aliased pixels are
|
|
127
|
+
yellow, and regions present in only one image (a dimension mismatch) are
|
|
128
|
+
magenta. `error.detail` holds the JSON-safe summary (path, dimensions, counts,
|
|
129
|
+
percentage, attempts, elapsed time) for logs and agents; the two `Buffer`s live
|
|
130
|
+
on `error.actual` / `error.diff`.
|
|
131
|
+
|
|
132
|
+
Baseline read/decode failures are treated as configuration errors, not visual
|
|
133
|
+
mismatches — they are not retried, and their cause is preserved.
|
|
134
|
+
|
|
135
|
+
## Updating baselines
|
|
136
|
+
|
|
137
|
+
When a change is intentional, re-run with the update flag set and craftdriver
|
|
138
|
+
overwrites the differing baselines with the new screenshots instead of failing:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
CRAFTDRIVER_UPDATE_VISUAL_BASELINES=true npm test
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
There is no separate "save" API and no CLI flag — craftdriver has no test runner
|
|
145
|
+
of its own, so the switch is one environment variable you set for a single run.
|
|
146
|
+
While it is set:
|
|
147
|
+
|
|
148
|
+
- A **missing** baseline is still created and passes (that never needed the flag).
|
|
149
|
+
- A **matching** baseline passes untouched.
|
|
150
|
+
- A **differing** baseline is captured until the deadline, then overwritten with
|
|
151
|
+
the final screenshot; the assertion passes with `result.baseline === 'updated'`.
|
|
152
|
+
A page that settles back into a match passes *without* rewriting, so you don't
|
|
153
|
+
get spurious baseline churn.
|
|
154
|
+
|
|
155
|
+
Every rewrite is printed — `[craftdriver] Updated visual baseline: <path> (<n>
|
|
156
|
+
px, <p>% changed)`. Review the resulting diff in version control before you
|
|
157
|
+
commit it; that review is the safety net this flag deliberately leaves to you.
|
|
158
|
+
|
|
159
|
+
Rewrites are atomic: the new baseline is written to a temporary file and renamed
|
|
160
|
+
into place, so an interrupted run or disk error can never leave a truncated or
|
|
161
|
+
half-updated committed baseline — the existing one survives intact.
|
|
162
|
+
|
|
163
|
+
Parsing is strict, because the flag mutates files on disk: `true` turns it on,
|
|
164
|
+
`false` or unset leaves it off, and any other value (`1`, `yes`, …) throws
|
|
165
|
+
`INVALID_ARGUMENT` rather than being quietly ignored. A baseline that exists but
|
|
166
|
+
can't be decoded stays a hard error even here — it is never overwritten. To
|
|
167
|
+
replace a corrupt baseline, delete it and let the next run recreate it.
|
|
168
|
+
|
|
169
|
+
## Retries and timing
|
|
170
|
+
|
|
171
|
+
Each assertion reads the baseline once, then screenshots and compares on a loop
|
|
172
|
+
until it matches or `timeout` (default: the browser's default timeout, 5000 ms)
|
|
173
|
+
expires, waiting `interval` ms (default 50) between attempts. The first
|
|
174
|
+
screenshot is always taken immediately, even with `timeout: 0`. This absorbs a
|
|
175
|
+
page that is still settling — a chart finishing its layout, a font swapping in.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
await browser.expectScreenshot('baselines/chart.png', {
|
|
179
|
+
screenshot: { selector: '#chart' },
|
|
180
|
+
timeout: 3_000,
|
|
181
|
+
interval: 100,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The happy path is cheap: an identical screenshot matches on a byte comparison
|
|
186
|
+
without ever decoding a PNG. But **byte equality is only a fast _positive_
|
|
187
|
+
path** — two genuinely-equivalent renders can still produce different bytes, so
|
|
188
|
+
a mismatch always falls back to a real pixel comparison.
|
|
189
|
+
|
|
190
|
+
## Making CI deterministic
|
|
191
|
+
|
|
192
|
+
Most visual-test pain is non-determinism, not craftdriver. The same page renders
|
|
193
|
+
differently across machines — **macOS and Linux genuinely rasterize different
|
|
194
|
+
pixels**, so a baseline captured on your Mac will not match a Linux CI runner.
|
|
195
|
+
Pin the environment:
|
|
196
|
+
|
|
197
|
+
- Capture baselines in the **same OS image** you run CI on (or keep per-platform
|
|
198
|
+
baselines). This is the big one.
|
|
199
|
+
- Fix the **browser and version**, the **viewport size**, and the **device pixel
|
|
200
|
+
ratio** (a HiDPI runner captures twice the pixels of a 1× one).
|
|
201
|
+
- Freeze **fonts** (ship them with the app/test image so font substitution can't
|
|
202
|
+
shift text), **locale**, and **timezone**.
|
|
203
|
+
- Disable **animations** and honor **reduced motion**; freeze time with the
|
|
204
|
+
[virtual clock](./clock.md) when content is time-dependent.
|
|
205
|
+
- Prefer a headless, consistent rendering path over whatever is on a developer
|
|
206
|
+
laptop.
|
|
207
|
+
|
|
208
|
+
Start strict (`pixelTolerance: 0`) and only add tolerance once you understand
|
|
209
|
+
which pixels move and why. A large blanket tolerance turns a visual test into a
|
|
210
|
+
test that passes no matter what.
|
|
211
|
+
|
|
212
|
+
## Comparing buffers directly
|
|
213
|
+
|
|
214
|
+
If you already have two PNG buffers and just want the comparison — no disk, no
|
|
215
|
+
browser, no retries — use `compareScreenshots(actual, expected, options)`. It
|
|
216
|
+
applies the same threshold semantics and input limits and returns the result
|
|
217
|
+
instead of throwing on a normal mismatch:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { compareScreenshots } from 'craftdriver';
|
|
221
|
+
|
|
222
|
+
const result = await compareScreenshots(actualPng, expectedPng, {
|
|
223
|
+
pixelTolerance: 3,
|
|
224
|
+
maxDiffPixels: 50,
|
|
225
|
+
});
|
|
226
|
+
console.log(result.matches, result.diffPixels, result.diffPercentage);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The argument order is always `(actual, expected)` — everywhere in the API.
|
|
230
|
+
|
|
231
|
+
## Input limits and scope
|
|
232
|
+
|
|
233
|
+
Before decoding, both images are checked against `maxImagePixels` (default 20
|
|
234
|
+
million pixels) and `maxInputBytes` (default 50 MiB), with the size validated
|
|
235
|
+
from the PNG header before any large allocation. Decoded RGBA is ~4 bytes per
|
|
236
|
+
pixel, and an assertion may hold the expected image, the latest actual image, and
|
|
237
|
+
a diff at once, so raise `maxImagePixels` deliberately for unusually long
|
|
238
|
+
full-page screenshots — and consider lowering it when many test workers run in
|
|
239
|
+
parallel.
|
|
240
|
+
|
|
241
|
+
This feature is built for a developer-controlled baseline and a screenshot
|
|
242
|
+
produced by the browser under test. The low-level comparator is **not** a
|
|
243
|
+
hardened service for arbitrary user-uploaded images; if that ever becomes a
|
|
244
|
+
requirement, decoding needs to move to a terminable worker with a hard CPU
|
|
245
|
+
deadline (an in-process timeout cannot interrupt a synchronous parser).
|
|
246
|
+
|
|
247
|
+
## Under the hood
|
|
248
|
+
|
|
249
|
+
PNG decoding and encoding go through a single pinned dependency, `pngjs`, behind
|
|
250
|
+
an internal codec seam. It is a pure-JavaScript package with no native addon,
|
|
251
|
+
WASM, install script, or transitive runtime dependencies — chosen so the
|
|
252
|
+
security and packaging story stays simple. The optional anti-alias detector is
|
|
253
|
+
adapted from [Pixelmatch](https://github.com/mapbox/pixelmatch) (ISC licensed;
|
|
254
|
+
attribution is preserved in the source).
|
package/docs/why-craftdriver.md
CHANGED
|
@@ -11,11 +11,12 @@ CraftDriver is for writing boringly reliable automation against real browsers, w
|
|
|
11
11
|
- 📡 **Network control** - mock, block, intercept, and wait for browser requests and responses.
|
|
12
12
|
- 🔐 **Reusable sessions** - save cookies and localStorage, then launch already signed in.
|
|
13
13
|
- ⏱️ **Virtual clock** - freeze or fast-forward `Date`, timers, and time-sensitive UI.
|
|
14
|
-
- ♿ **Accessibility audits** -
|
|
14
|
+
- ♿ **Accessibility audits** - find WCAG 2.2 and Section 508 violations with built-in axe-core checks.
|
|
15
|
+
- 🖼️ **Visual testing** - catch visual regressions against baselines, without fighting anti-aliasing flakiness.
|
|
15
16
|
- 🧾 **Trace evidence** - capture actions, console output, errors, network events, and screenshots.
|
|
16
|
-
- ⚛️ **Electron apps** - drive packaged Electron desktop app renderers, matched to the app's bundled Chromium.
|
|
17
17
|
- ☁️ **Remote WebDriver** - use the Browser API with a self-hosted Selenium Grid or a cloud provider like [BrowserStack](https://www.browserstack.com/).
|
|
18
|
-
-
|
|
18
|
+
- ⚛️ **Electron apps** - drive packaged Electron desktop apps and mock native OS dialogs (open/save, message boxes).
|
|
19
|
+
- 🤖 **Agent-friendly** - CLI, a project-local skill, and optional MCP when coding agents need the browser.
|
|
19
20
|
|
|
20
21
|
## Good Fits
|
|
21
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craftdriver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Modern WebDriver automation library for NodeJS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"test:chrome": "BROWSER_NAME=chrome vitest run",
|
|
19
19
|
"test:chromium": "BROWSER_NAME=chromium vitest run",
|
|
20
20
|
"test:safari": "BROWSER_NAME=safari vitest run tests/safari.test.ts --maxWorkers=1",
|
|
21
|
+
"test:cli": "HEADLESS=true vitest run --config vitest.cli.config.ts",
|
|
22
|
+
"test:release": "vitest run --config vitest.release.config.ts",
|
|
21
23
|
"test:recipes": "HEADLESS=true vitest run --config vitest.recipes.config.ts",
|
|
22
24
|
"test:electron": "vitest run --config vitest.electron.config.ts",
|
|
23
25
|
"test:browserstack": "vitest run --config vitest.browserstack.config.ts",
|
|
@@ -88,6 +90,7 @@
|
|
|
88
90
|
"@semantic-release/changelog": "^6.0.3",
|
|
89
91
|
"@semantic-release/git": "^10.0.1",
|
|
90
92
|
"@types/node": "^20.0.0",
|
|
93
|
+
"@types/pngjs": "6.0.5",
|
|
91
94
|
"@types/ws": "^8.18.1",
|
|
92
95
|
"@types/yazl": "^3.3.1",
|
|
93
96
|
"@typescript-eslint/parser": "^8.1.0",
|
|
@@ -102,6 +105,7 @@
|
|
|
102
105
|
},
|
|
103
106
|
"dependencies": {
|
|
104
107
|
"axe-core": "^4.12.1",
|
|
108
|
+
"pngjs": "7.0.0",
|
|
105
109
|
"ws": "^8.21.0",
|
|
106
110
|
"yazl": "^3.3.1"
|
|
107
111
|
},
|
|
@@ -1,89 +1,73 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
name: craftdriver
|
|
3
|
+
description: Explore live web applications with the CraftDriver CLI, validate durable selectors, and write, run, and debug CraftDriver tests. Use when a coding agent needs to inspect a page, drive browser flows, choose locators, author browser automation, or diagnose a failing CraftDriver test.
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
# CraftDriver browser workflow
|
|
7
|
+
|
|
8
|
+
Use CraftDriver's CLI to inspect and exercise the running application, then use
|
|
9
|
+
the public TypeScript API to write and run durable tests. Chrome is the default
|
|
10
|
+
agent workflow.
|
|
6
11
|
|
|
7
12
|
## Core loop
|
|
8
13
|
|
|
9
|
-
```
|
|
10
|
-
navigate →
|
|
14
|
+
```text
|
|
15
|
+
start app → navigate → snapshot → inspect → act → validate selector
|
|
16
|
+
→ write test → run focused test → debug from fresh evidence
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`sleep()` / `setTimeout()` — it's a footgun.
|
|
19
|
+
Actions and `expect(locator).to…()` auto-wait. Do not add sleeps or hand-written
|
|
20
|
+
polling loops.
|
|
16
21
|
|
|
17
|
-
## Selector
|
|
22
|
+
## Selector order
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
Prefer evidence from the live page in this order:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
By.role({ name }) / By.labelText / By.testId
|
|
28
|
+
→ exact visible text → stable CSS → XPath only as a last resort
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## Five rules
|
|
28
|
-
|
|
29
|
-
1. Use `expect(locator).to…()` for assertions. Never hand-roll
|
|
30
|
-
`while (...) { ... sleep(100) }` retry loops — they auto-wait.
|
|
31
|
-
2. Read errors by `code`, not by prose. Every public throw is a
|
|
32
|
-
`CraftdriverError` with a stable `code`. See
|
|
33
|
-
[docs/error-codes.md](../../docs/error-codes.md).
|
|
34
|
-
3. `instanceof CraftdriverError` is true on every public throw;
|
|
35
|
-
`instanceof Error` is also true.
|
|
36
|
-
4. BiDi features (`network`, `logs`, tracing, init scripts, true load
|
|
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`. Exception: `browserName: 'safari'`
|
|
40
|
-
defaults `enableBiDi` to `false` and rejects `true` — Safari is
|
|
41
|
-
Classic-only (macOS, headed, one session at a time). See
|
|
42
|
-
[docs/safari.md](../../docs/safari.md).
|
|
43
|
-
5. Tests fetch from the example server. Start it in a separate
|
|
44
|
-
terminal: `npm run examples:start` before `npm test`.
|
|
45
|
-
|
|
46
|
-
## When you reach for more
|
|
47
|
-
|
|
48
|
-
- **Writing tests, looking for a method** → read
|
|
49
|
-
[skills/craftdriver/cheatsheet.md](cheatsheet.md). Also see
|
|
50
|
-
[docs/api-reference.md](../../docs/api-reference.md) — every public
|
|
51
|
-
export, one row each.
|
|
52
|
-
- **Login, upload, wait-for-network, etc.** → read
|
|
53
|
-
[skills/craftdriver/patterns.md](patterns.md).
|
|
54
|
-
- **Driving the browser from a shell or agent loop** → read
|
|
55
|
-
[skills/craftdriver/cli.md](cli.md). Same Browser API, exposed as a
|
|
56
|
-
`craftdriver` binary with daemon + ephemeral modes.
|
|
57
|
-
- **Driving from an MCP-aware AI host** (Claude Desktop / Code, Cursor,
|
|
58
|
-
Windsurf, Zed, Goose, Gemini CLI) → `craftdriver mcp` is a stdio
|
|
59
|
-
JSON-RPC server with 14 schema-typed tools. Mutating tools return a
|
|
60
|
-
compact a11y snapshot **diffed from the previous turn** — you see
|
|
61
|
-
what changed without a follow-up read. See
|
|
62
|
-
[docs/mcp.md](../../docs/mcp.md).
|
|
63
|
-
- **An error code you don't recognise** → read
|
|
64
|
-
[docs/error-codes.md](../../docs/error-codes.md).
|
|
65
|
-
|
|
66
|
-
## Probing rule
|
|
67
|
-
|
|
68
|
-
When unsure a selector exists, check before acting. Today: call
|
|
69
|
-
`await locator.count()` (zero-wait, returns the current match count).
|
|
70
|
-
Acting on a hallucinated selector wastes the whole auto-wait budget.
|
|
71
|
-
|
|
72
|
-
For the **CLI and MCP**, the cheapest way to drive a page is to take a
|
|
73
|
-
snapshot first and use refs:
|
|
31
|
+
Confirm a selector against the live page with `craftdriver exists` or
|
|
32
|
+
`craftdriver find` before putting it in a test.
|
|
74
33
|
|
|
75
|
-
|
|
76
|
-
$ craftdriver snapshot
|
|
77
|
-
e4: textbox "Username" #username
|
|
78
|
-
e7: button "Sign in" #submit
|
|
79
|
-
$ craftdriver fill ref=e4 alice
|
|
80
|
-
$ craftdriver click ref=e7
|
|
81
|
-
```
|
|
34
|
+
## Snapshot refs are exploration-only
|
|
82
35
|
|
|
83
|
-
`ref=eN
|
|
84
|
-
|
|
36
|
+
CLI snapshots show `ref=eN`. A ref binds to one element for as long as that
|
|
37
|
+
element lives, and is never reassigned to another one: if the element is removed
|
|
38
|
+
or duplicated, or the page navigates or reloads, the command fails with
|
|
39
|
+
`STALE_REF`. Take a fresh snapshot when that happens — CraftDriver will not
|
|
40
|
+
guess a replacement.
|
|
85
41
|
|
|
86
|
-
|
|
42
|
+
A ref still means nothing outside the current session, so never copy one into
|
|
43
|
+
test source. Convert the element into a durable locator and let CraftDriver
|
|
44
|
+
check it against the live page:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx craftdriver locators ref=e7
|
|
48
|
+
```
|
|
87
49
|
|
|
88
|
-
|
|
89
|
-
|
|
50
|
+
Use a candidate reported `unique` (ordered by durability: role + accessible
|
|
51
|
+
name, label, test ID, unique text, minimal CSS). If none is unique, add a
|
|
52
|
+
`data-testid` to the application instead of committing a positional selector.
|
|
53
|
+
|
|
54
|
+
## Test rules
|
|
55
|
+
|
|
56
|
+
1. Inspect the repository's existing tests and package scripts before choosing
|
|
57
|
+
a test location or command.
|
|
58
|
+
2. Import only from `craftdriver`, never from `craftdriver/src/...`.
|
|
59
|
+
3. Use public locators and `expect(locator)` assertions.
|
|
60
|
+
4. Read failures by stable `CraftdriverError.code`, then gather a fresh
|
|
61
|
+
snapshot and focused page evidence.
|
|
62
|
+
5. Make ordinary reviewable source changes. Never hide a failure with runtime
|
|
63
|
+
locator repair.
|
|
64
|
+
6. Always close the browser in `finally` or the repository's existing fixture.
|
|
65
|
+
|
|
66
|
+
## Focused references
|
|
67
|
+
|
|
68
|
+
- Explore and write a test: [workflow.md](workflow.md)
|
|
69
|
+
- Shell commands: [cli.md](cli.md)
|
|
70
|
+
- TypeScript API cheatsheet: [cheatsheet.md](cheatsheet.md)
|
|
71
|
+
- Worked library recipes: [patterns.md](patterns.md)
|
|
72
|
+
- Full installed package docs: `node_modules/craftdriver/docs/`
|
|
73
|
+
- Optional MCP adapter: `node_modules/craftdriver/docs/mcp.md`
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# craftdriver — cheatsheet
|
|
2
2
|
|
|
3
|
-
Compact reference for writing tests.
|
|
4
|
-
|
|
5
|
-
list.
|
|
3
|
+
Compact reference for writing tests. The full installed API reference is
|
|
4
|
+
`node_modules/craftdriver/docs/api-reference.md`.
|
|
6
5
|
|
|
7
6
|
## Launch & teardown
|
|
8
7
|
|
|
@@ -111,7 +110,7 @@ Codes: `NO_MATCH`, `TIMEOUT_WAITING_VISIBLE`, `TIMEOUT_WAITING_STATE`,
|
|
|
111
110
|
`TIMEOUT_WAITING_DIALOG`, `TIMEOUT`, `EXPECT_MISMATCH`,
|
|
112
111
|
`A11Y_VIOLATIONS`, `EVAL_THREW`, `EVAL_BAD_ARG`, `INVALID_ARGUMENT`,
|
|
113
112
|
`UNSUPPORTED`, `STATE_INVALID`, `DRIVER_ERROR`. Full table:
|
|
114
|
-
|
|
113
|
+
`node_modules/craftdriver/docs/error-codes.md`.
|
|
115
114
|
|
|
116
115
|
## Pages and contexts
|
|
117
116
|
|
|
@@ -124,6 +123,21 @@ const pages = browser.pages();
|
|
|
124
123
|
const fresh = await browser.waitForPage(() => browser.click('a[target=_blank]'));
|
|
125
124
|
```
|
|
126
125
|
|
|
126
|
+
## Reusable login state
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// Save after login (atomic file, mode 0600 where supported).
|
|
130
|
+
await browser.saveState('.auth/alice.json');
|
|
131
|
+
|
|
132
|
+
// BiDi: cookies + multi-origin localStorage are ready before navigation.
|
|
133
|
+
const reused = await Browser.launch({ storageState: '.auth/alice.json' });
|
|
134
|
+
await reused.navigateTo('https://app.example.com/dashboard');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Classic launch rejects non-empty state. Use `Browser.launch()`, navigate to the
|
|
138
|
+
sole captured HTTP(S) origin, then `browser.loadState(...)`. State containing
|
|
139
|
+
sessionStorage always uses that active-origin flow.
|
|
140
|
+
|
|
127
141
|
## Network (BiDi)
|
|
128
142
|
|
|
129
143
|
```ts
|