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
package/docs/mcp.md
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# MCP server
|
|
2
2
|
|
|
3
|
-
`craftdriver` ships
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
without managing a daemon, a socket, or filesystem access.
|
|
3
|
+
`craftdriver` ships an optional [Model Context Protocol](https://modelcontextprotocol.io)
|
|
4
|
+
STDIO adapter so MCP-aware coding agents can drive the same local session and
|
|
5
|
+
dispatcher as the CLI.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
post-action payload (compact a11y snapshot, diffed from the previous
|
|
11
|
-
turn) that text models can act on directly.
|
|
7
|
+
CLI plus the installed CraftDriver skill is the recommended workflow. MCP is
|
|
8
|
+
not required for exploration or test authoring.
|
|
12
9
|
|
|
13
10
|
```bash
|
|
14
11
|
# Start once via your MCP client — examples below
|
|
15
|
-
npx -
|
|
12
|
+
npx --no-install craftdriver mcp
|
|
16
13
|
```
|
|
17
14
|
|
|
18
15
|
The server speaks JSON-RPC 2.0 on stdio. The browser launches lazily
|
|
19
16
|
on the first tool call and shuts down when the client disconnects.
|
|
17
|
+
Each newline-delimited input frame is limited to 1 MiB (1,048,576 UTF-8
|
|
18
|
+
bytes); oversized frames return a parse error and are discarded.
|
|
20
19
|
|
|
21
|
-
##
|
|
20
|
+
## Manual project-pinned setup
|
|
21
|
+
|
|
22
|
+
Run `npx craftdriver init codex --mcp` to print the exact snippet. The installer
|
|
23
|
+
does not read or change MCP configuration.
|
|
22
24
|
|
|
23
25
|
### Claude Code / Claude Desktop
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
claude mcp add craftdriver -- npx -
|
|
28
|
+
claude mcp add craftdriver -- npx --no-install craftdriver mcp
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
### Cursor / Windsurf / Zed (`.cursor/mcp.json` and similar)
|
|
@@ -33,7 +35,7 @@ claude mcp add craftdriver -- npx -y craftdriver mcp
|
|
|
33
35
|
"mcpServers": {
|
|
34
36
|
"craftdriver": {
|
|
35
37
|
"command": "npx",
|
|
36
|
-
"args": ["-
|
|
38
|
+
"args": ["--no-install", "craftdriver", "mcp"]
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
}
|
|
@@ -42,7 +44,7 @@ claude mcp add craftdriver -- npx -y craftdriver mcp
|
|
|
42
44
|
### Gemini CLI
|
|
43
45
|
|
|
44
46
|
```bash
|
|
45
|
-
gemini mcp add craftdriver npx -
|
|
47
|
+
gemini mcp add craftdriver npx --no-install craftdriver mcp
|
|
46
48
|
```
|
|
47
49
|
|
|
48
50
|
### Goose
|
|
@@ -53,42 +55,90 @@ goose configure # add craftdriver as a stdio server
|
|
|
53
55
|
|
|
54
56
|
## Tools
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
One line each; the long help lives in the schema description, which clients
|
|
59
|
+
render into the model's context once per session. Every tool dispatches a
|
|
60
|
+
command the CLI also has — there are no MCP-only browser semantics.
|
|
61
|
+
|
|
62
|
+
| Tool | Purpose |
|
|
63
|
+
| ----------------------- | ---------------------------------------------------------------------- |
|
|
64
|
+
| `browser_navigate` | Go to a URL (waits for load). |
|
|
65
|
+
| `browser_click` | Click an element; set `double` for a double-click. |
|
|
66
|
+
| `browser_fill` | Fill an input/textarea (clears first, real key events). |
|
|
67
|
+
| `browser_type` | Type into whatever holds focus (no selector). |
|
|
68
|
+
| `browser_element` | `dblclick`/`focus`/`scroll`/`clear`/`check`/`uncheck`/`select`. |
|
|
69
|
+
| `browser_press` | Press a key (`Enter`, `Tab`, `Control+A`). |
|
|
70
|
+
| `browser_key` | Low-level `press`/`down`/`up` for modifier combinations. |
|
|
71
|
+
| `browser_mouse` | `move`/`click`/`down`/`up`/`wheel`, by element or coordinate. |
|
|
72
|
+
| `browser_hover` | Hover over an element. |
|
|
73
|
+
| `browser_upload` | Set files on a file input (bounded; paths never echoed). |
|
|
74
|
+
| `browser_dialog` | `inspect`/`accept`/`dismiss` a native dialog. |
|
|
75
|
+
| `browser_find` | Locate elements without acting (tag/text/visibility). |
|
|
76
|
+
| `browser_exists` | **0-wait probe.** Returns `{exists, count}` in one roundtrip. |
|
|
77
|
+
| `browser_wait` | Wait for a selector state or a load state. |
|
|
78
|
+
| `browser_read` | Read `text` / `attr` / `value` / `is(visible\|enabled\|checked)`. |
|
|
79
|
+
| `browser_snapshot` | **Sanitized DOM summary with refs.** Use `ref=eN` as a selector. |
|
|
80
|
+
| `browser_locators` | **Turn an element into durable selectors for a test.** Never a ref. |
|
|
81
|
+
| `browser_page` | `list`/`open`/`select`/`close` tabs. |
|
|
82
|
+
| `browser_logs` | Console + network history, with cursors. See below. |
|
|
83
|
+
| `browser_mock` | Serve a fixed response or block matching requests. |
|
|
84
|
+
| `browser_state` | Save/restore cookies and local storage (a login, once). |
|
|
85
|
+
| `browser_trace` | Record a run to an owned directory; `zip` for a Vibium archive. |
|
|
86
|
+
| `browser_screenshot` | Capture PNG to a file under the artifact dir; never inlined. |
|
|
87
|
+
| `browser_status` | Browser up? Which URL is active? |
|
|
88
|
+
| `browser_advanced_eval` | Evaluate JS in the page. Last resort. |
|
|
89
|
+
|
|
90
|
+
Each tool carries MCP `annotations` — `title`, `readOnlyHint`,
|
|
91
|
+
`destructiveHint`, `idempotentHint`, `openWorldHint` — and they are accurate:
|
|
92
|
+
a tool marked read-only never dispatches a command the dispatcher treats as
|
|
93
|
+
page-mutating, which is asserted by test rather than by review.
|
|
94
|
+
|
|
95
|
+
### Debugging with evidence
|
|
96
|
+
|
|
97
|
+
`browser_logs` captures console and network from launch, so an error thrown
|
|
98
|
+
during the first navigation is still answerable afterwards. Every result
|
|
99
|
+
carries a `cursor`; pass it back as `since` for only what is new. `kind=error`
|
|
100
|
+
covers both uncaught exceptions and `console.error`. Network rows are
|
|
101
|
+
summaries — url, method, status, mime type — and never carry bodies, cookies,
|
|
102
|
+
or headers.
|
|
78
103
|
|
|
79
104
|
```jsonc
|
|
80
|
-
{ "name": "
|
|
81
|
-
|
|
105
|
+
{ "name": "browser_logs", "arguments": { "action": "list", "kind": "error" } }
|
|
106
|
+
{ "name": "browser_logs", "arguments": {
|
|
107
|
+
"action": "wait", "contains": "checkout ok", "timeout_ms": 10000
|
|
82
108
|
} }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Tracing
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
{ "name": "browser_trace", "arguments": { "action": "start", "name": "agentflow" } }
|
|
83
115
|
// ...browser_navigate / browser_fill / browser_click calls...
|
|
84
|
-
{ "name": "browser_trace", "arguments": {
|
|
85
|
-
"action": "stop", "path": "./traces/agent-flow.zip"
|
|
86
|
-
} }
|
|
116
|
+
{ "name": "browser_trace", "arguments": { "action": "stop", "zip": true } }
|
|
87
117
|
```
|
|
88
118
|
|
|
89
|
-
The zip opens at
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
The response reports where the trace and archive landed; the zip opens at
|
|
120
|
+
[player.vibium.dev](https://player.vibium.dev/). Output goes to an owned
|
|
121
|
+
directory (`CRAFTDRIVER_TRACE_DIR`), and `name` is a bare name rather than a
|
|
122
|
+
path — an earlier version accepted an arbitrary filesystem path straight off
|
|
123
|
+
the wire. If the client disconnects before `stop`, the raw NDJSON is still
|
|
124
|
+
valid, just without a finalized zip.
|
|
125
|
+
|
|
126
|
+
## Argument validation
|
|
127
|
+
|
|
128
|
+
Every tool declares its arguments once; that declaration produces both the
|
|
129
|
+
advertised `inputSchema` and the runtime check, so a tool cannot promise a
|
|
130
|
+
constraint it does not enforce. Invalid arguments are rejected as JSON-RPC
|
|
131
|
+
`-32602` **before** anything reaches the browser:
|
|
132
|
+
|
|
133
|
+
- an unknown field (rather than being silently ignored — a misspelled required
|
|
134
|
+
argument would otherwise look like a successful call that did something else);
|
|
135
|
+
- a wrong type, a non-finite number, an out-of-range number, an invalid enum;
|
|
136
|
+
- an oversized string or array.
|
|
137
|
+
|
|
138
|
+
`-32602` also covers an unknown tool name. Ordinary browser failures — a
|
|
139
|
+
missing element, a timeout — are **not** protocol errors; they come back as
|
|
140
|
+
successful responses with `isError: true`, which is what keeps the two
|
|
141
|
+
distinguishable.
|
|
92
142
|
|
|
93
143
|
## Selector syntax
|
|
94
144
|
|
|
@@ -128,11 +178,14 @@ Use `ref=eN` as the selector for the next call:
|
|
|
128
178
|
{ "name": "browser_click", "arguments": { "selector": "ref=e7" } }
|
|
129
179
|
```
|
|
130
180
|
|
|
131
|
-
**
|
|
181
|
+
**Use refs only for immediate exploration**
|
|
132
182
|
|
|
133
|
-
- **
|
|
134
|
-
|
|
135
|
-
|
|
183
|
+
- **A ref names one element for as long as it lives.** A surviving node keeps
|
|
184
|
+
its ref across snapshots, and refs are never reused — so a ref cannot drift
|
|
185
|
+
onto a different element. If it is removed, duplicated, or the page
|
|
186
|
+
navigates, the call fails `STALE_REF`; take a fresh snapshot then.
|
|
187
|
+
- **Never copy refs into test code.** Convert live role/name, label, test ID,
|
|
188
|
+
text, or DOM evidence into a durable selector and validate it.
|
|
136
189
|
- **Token efficient.** `ref=e7` is 5 characters; `role=button[name=Sign in]`
|
|
137
190
|
is 26. Over a 50-step flow that adds up.
|
|
138
191
|
- **Auto-waiting still works.** Internally `ref=eN` resolves to a CSS
|
|
@@ -141,11 +194,16 @@ Use `ref=eN` as the selector for the next call:
|
|
|
141
194
|
|
|
142
195
|
**Invalidation rules**
|
|
143
196
|
|
|
144
|
-
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
- A
|
|
197
|
+
- A ref binds to one element. An element that survives a DOM change keeps
|
|
198
|
+
its ref across snapshots; snapshots do **not** renumber it.
|
|
199
|
+
- New elements get fresh numbers. Refs are never reused, including after a
|
|
200
|
+
navigation or reload.
|
|
201
|
+
- A ref whose element was removed or duplicated, or that was issued before
|
|
202
|
+
the page navigated or reloaded, fails with `STALE_REF` — take a fresh
|
|
203
|
+
snapshot. It never resolves to a different element.
|
|
204
|
+
- `error.detail.reason` distinguishes `detached`, `document-changed`,
|
|
205
|
+
`unknown-ref`, `ambiguous`, and `no-snapshot`.
|
|
206
|
+
- Refs are exploration state and must never appear in committed tests.
|
|
149
207
|
|
|
150
208
|
## Post-action payload
|
|
151
209
|
|
|
@@ -197,10 +255,10 @@ button "Click me" #by-text
|
|
|
197
255
|
|
|
198
256
|
Applies to:
|
|
199
257
|
|
|
200
|
-
- **Screenshots** — always written to a file
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
258
|
+
- **Screenshots** — always written to a server-allocated file under the
|
|
259
|
+
per-session artifact directory. The inline block carries the absolute path
|
|
260
|
+
and byte count — **zero** image tokens. The tool accepts no destination
|
|
261
|
+
path, so it cannot write outside that directory.
|
|
204
262
|
- **A11y snapshot diffs** — spill when the rendered diff exceeds the
|
|
205
263
|
threshold (typically only the full first-call snapshot on big pages).
|
|
206
264
|
- **Tool results** — `browser_read`, `browser_advanced_eval`, etc. spill
|
|
@@ -213,20 +271,29 @@ Configuration:
|
|
|
213
271
|
| ---------------------------------- | ---------------- | ------------------------------------------------- |
|
|
214
272
|
| `CRAFTDRIVER_MCP_ARTIFACTS_DIR` | `os.tmpdir()` | Root directory for the per-session artifact dir. |
|
|
215
273
|
| `CRAFTDRIVER_MCP_SPILL_BYTES` | `2048` (~500 tk) | Inline content blocks larger than this spill. |
|
|
274
|
+
| `CRAFTDRIVER_MCP_MAX_RESPONSE_BYTES` | `32768` | Maximum serialized result from one tool call. |
|
|
216
275
|
|
|
217
276
|
The per-session directory (`<root>/craftdriver-mcp-<pid>-<stamp>/`) is
|
|
218
277
|
not deleted on shutdown — agents may still be reading past artifacts.
|
|
219
278
|
Use `$CRAFTDRIVER_MCP_ARTIFACTS_DIR` to point at a dir with your own
|
|
220
|
-
cleanup policy.
|
|
279
|
+
cleanup policy. Each server process refuses more than 500 artifacts or 256 MiB
|
|
280
|
+
in its session directory; browser-written screenshots are counted by their
|
|
281
|
+
actual file size after capture.
|
|
221
282
|
|
|
222
|
-
|
|
223
|
-
|
|
283
|
+
Small results still round-trip in full through `structuredContent`. When the
|
|
284
|
+
complete result would exceed the response cap, it becomes explicit truncation
|
|
285
|
+
metadata and a bounded preview rather than duplicating the full spilled value.
|
|
224
286
|
|
|
225
287
|
## Errors
|
|
226
288
|
|
|
227
|
-
|
|
228
|
-
as JSON-RPC errors. JSON-RPC errors are reserved for
|
|
229
|
-
failures
|
|
289
|
+
Browser and action failures are returned as `isError: true` content (per MCP
|
|
290
|
+
spec), **not** as JSON-RPC errors. JSON-RPC errors are reserved for
|
|
291
|
+
protocol-level failures: a malformed request (`-32700`/`-32600`), an unknown
|
|
292
|
+
method (`-32601`), and an unknown tool or invalid arguments (`-32602`).
|
|
293
|
+
|
|
294
|
+
The split matters: "the element was not there" is a fact about the page that an
|
|
295
|
+
agent should reason about, while "you sent an argument that does not exist" is
|
|
296
|
+
a mistake about the protocol that it should correct.
|
|
230
297
|
|
|
231
298
|
```jsonc
|
|
232
299
|
{
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
Agent-surface fixture for the common action set and for durable locator
|
|
4
|
+
candidates. Deliberately mixes elements that offer good evidence (role +
|
|
5
|
+
name, label, test id) with ones that offer only generated attributes, so
|
|
6
|
+
tests can prove `locators` tells the truth in both cases.
|
|
7
|
+
-->
|
|
8
|
+
<html><head><title>Agent actions</title></head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>Account settings</h1>
|
|
11
|
+
|
|
12
|
+
<label for="nickname">Nickname</label>
|
|
13
|
+
<input id="nickname" name="nickname" type="text" value="preset" />
|
|
14
|
+
|
|
15
|
+
<label for="newsletter">Newsletter</label>
|
|
16
|
+
<input id="newsletter" name="newsletter" type="checkbox" />
|
|
17
|
+
|
|
18
|
+
<label for="plan">Plan</label>
|
|
19
|
+
<select id="plan" name="plan">
|
|
20
|
+
<option value="free">Free</option>
|
|
21
|
+
<option value="pro">Pro</option>
|
|
22
|
+
</select>
|
|
23
|
+
|
|
24
|
+
<button id="save" data-testid="save-button" type="button">Save changes</button>
|
|
25
|
+
<button id="a1b2c3d4e5f6" type="button">Generated id</button>
|
|
26
|
+
|
|
27
|
+
<p id="dbl-count">0</p>
|
|
28
|
+
<button id="counter" type="button">Count me</button>
|
|
29
|
+
|
|
30
|
+
<div style="height: 1500px"></div>
|
|
31
|
+
<button id="far-below" type="button">Far below</button>
|
|
32
|
+
|
|
33
|
+
<p id="log"></p>
|
|
34
|
+
<script>
|
|
35
|
+
const log = document.getElementById('log');
|
|
36
|
+
let dbl = 0;
|
|
37
|
+
document.getElementById('counter').addEventListener('dblclick', () => {
|
|
38
|
+
dbl += 1;
|
|
39
|
+
document.getElementById('dbl-count').textContent = String(dbl);
|
|
40
|
+
});
|
|
41
|
+
document.getElementById('nickname').addEventListener('keydown', () => {
|
|
42
|
+
log.textContent = 'keydown seen';
|
|
43
|
+
});
|
|
44
|
+
document.getElementById('save').addEventListener('click', () => {
|
|
45
|
+
log.textContent = 'saved';
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
</body></html>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Craftdriver Debug Example</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { font-family: system-ui, sans-serif; max-width: 34rem; margin: 3rem auto; padding: 0 1rem; }
|
|
8
|
+
fieldset { border: 1px solid #ccc; border-radius: 6px; padding: 1rem 1.25rem; }
|
|
9
|
+
label { display: block; margin: 0.75rem 0 0.25rem; font-weight: 600; }
|
|
10
|
+
input { width: 100%; padding: 0.5rem; font-size: 1rem; box-sizing: border-box; }
|
|
11
|
+
button { margin-top: 1rem; padding: 0.6rem 1.2rem; font-size: 1rem; cursor: pointer; }
|
|
12
|
+
#status { margin-top: 1rem; padding: 0.75rem; border-radius: 6px; }
|
|
13
|
+
#status.error { background: #fdecea; color: #611a15; }
|
|
14
|
+
#status.ok { background: #edf7ed; color: #1e4620; }
|
|
15
|
+
.note { color: #555; font-size: 0.9rem; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<h1>Checkout</h1>
|
|
20
|
+
<p class="note">
|
|
21
|
+
A deliberately opaque failure: the page reports only that something went
|
|
22
|
+
wrong. What actually failed is visible in the console and network journal,
|
|
23
|
+
not in the DOM.
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<form id="checkout-form">
|
|
27
|
+
<fieldset>
|
|
28
|
+
<legend>Order details</legend>
|
|
29
|
+
<label for="email">Email</label>
|
|
30
|
+
<input id="email" name="email" type="email" placeholder="you@example.test" />
|
|
31
|
+
|
|
32
|
+
<label for="card">Card number</label>
|
|
33
|
+
<input id="card" name="card" type="text" placeholder="4242 4242 4242 4242" />
|
|
34
|
+
|
|
35
|
+
<button id="place-order" type="submit">Place order</button>
|
|
36
|
+
</fieldset>
|
|
37
|
+
</form>
|
|
38
|
+
|
|
39
|
+
<div id="status" hidden></div>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
// The endpoint does not exist on the static example server, so this always
|
|
43
|
+
// fails with the same status. Nothing here is timing- or order-dependent:
|
|
44
|
+
// the same click produces the same evidence every run.
|
|
45
|
+
const ENDPOINT = '/api/checkout';
|
|
46
|
+
|
|
47
|
+
const form = document.getElementById('checkout-form');
|
|
48
|
+
const status = document.getElementById('status');
|
|
49
|
+
|
|
50
|
+
function show(text, kind) {
|
|
51
|
+
status.hidden = false;
|
|
52
|
+
status.className = kind;
|
|
53
|
+
status.textContent = text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
form.addEventListener('submit', async (event) => {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
show('Placing order…', '');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(ENDPOINT, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'content-type': 'application/json' },
|
|
64
|
+
body: JSON.stringify({ email: document.getElementById('email').value }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
// The detail goes to the console, not to the page — which is exactly
|
|
69
|
+
// the situation `craftdriver logs` exists for.
|
|
70
|
+
console.error(
|
|
71
|
+
'checkout failed: HTTP ' + response.status + ' from ' + ENDPOINT
|
|
72
|
+
);
|
|
73
|
+
show('Something went wrong. Please try again.', 'error');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const order = await response.json();
|
|
78
|
+
show('Order ' + order.orderId + ' confirmed', 'ok');
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error('checkout request threw: ' + err.message);
|
|
81
|
+
show('Something went wrong. Please try again.', 'error');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
</script>
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
Agent-surface fixture: the page mutates itself after load with no agent
|
|
4
|
+
action involved. Used to prove explicit and automatic snapshots share one
|
|
5
|
+
baseline — an explicit snapshot must not leave the next post-action diff
|
|
6
|
+
re-reporting changes the agent has already been shown.
|
|
7
|
+
-->
|
|
8
|
+
<html><head><title>Timer repro</title></head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>Dashboard</h1>
|
|
11
|
+
<div id="slot"></div>
|
|
12
|
+
<button id="go">Go</button>
|
|
13
|
+
<script>
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
document.getElementById('slot').innerHTML = '<button id="late">Late arrival</button>';
|
|
16
|
+
}, 800);
|
|
17
|
+
</script>
|
|
18
|
+
</body></html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
Agent-surface fixture: a DOM reorder that shifts every element's document
|
|
4
|
+
position. Used to prove snapshot refs bind to element identity, not to
|
|
5
|
+
document order (a stale ref must fail, never act on a replacement node).
|
|
6
|
+
-->
|
|
7
|
+
<html><head><title>Shift repro</title></head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Checkout</h1>
|
|
10
|
+
<div id="banner"></div>
|
|
11
|
+
<form id="f">
|
|
12
|
+
<label for="coupon">Coupon</label>
|
|
13
|
+
<input id="coupon" name="coupon" type="text" />
|
|
14
|
+
<button id="apply" type="button">Apply coupon</button>
|
|
15
|
+
<label for="email">Email</label>
|
|
16
|
+
<input id="email" name="email" type="text" />
|
|
17
|
+
<button id="pay" type="button">Pay now</button>
|
|
18
|
+
<button id="remove-pay" type="button">Remove pay button</button>
|
|
19
|
+
</form>
|
|
20
|
+
<script>
|
|
21
|
+
// Realistic: applying an invalid coupon renders an inline error with a
|
|
22
|
+
// help link ABOVE the rest of the form, shifting everything below it.
|
|
23
|
+
document.getElementById('apply').addEventListener('click', () => {
|
|
24
|
+
document.getElementById('banner').innerHTML =
|
|
25
|
+
'<a href="/help" id="help">Why was my coupon rejected?</a>';
|
|
26
|
+
});
|
|
27
|
+
// Removing the pay button lets a test prove a removed ref fails rather
|
|
28
|
+
// than silently resolving to whatever took its place.
|
|
29
|
+
document.getElementById('remove-pay').addEventListener('click', () => {
|
|
30
|
+
document.getElementById('pay').remove();
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
</body></html>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Log In Once And Reuse The Session
|
|
2
2
|
|
|
3
3
|
Signing in through the UI in every test is slow, and for most tests logging in
|
|
4
|
-
is not the thing being proven. Do it once, save the resulting
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
is not the thing being proven. Do it once, save the resulting session, then
|
|
5
|
+
launch later tests already authenticated with `storageState`. Both halves below
|
|
6
|
+
run against the live
|
|
7
7
|
[login example](https://dtopuzov.github.io/craftdriver/examples/login.html),
|
|
8
|
-
which persists
|
|
8
|
+
which persists auth in a cookie and user preferences in localStorage.
|
|
9
9
|
|
|
10
10
|
## Generate Auth State
|
|
11
11
|
|
|
@@ -38,6 +38,34 @@ await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/login.
|
|
|
38
38
|
await browser.expect('#welcome').toContainText('Welcome back, alice!');
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## What `storageState` Restores
|
|
42
|
+
|
|
43
|
+
On supported WebDriver BiDi sessions, launch restores cookies **and every
|
|
44
|
+
captured localStorage origin** before the first real navigation. CraftDriver
|
|
45
|
+
uses a private, locally fulfilled same-origin document because BiDi has no
|
|
46
|
+
out-of-band localStorage command. The state is written once, so application
|
|
47
|
+
changes survive reload.
|
|
48
|
+
|
|
49
|
+
`storageState` does not restore IndexedDB, Cache Storage, service workers, or
|
|
50
|
+
reusable sessionStorage. Context and launch APIs reject non-empty
|
|
51
|
+
sessionStorage rather than silently dropping it.
|
|
52
|
+
|
|
53
|
+
WebDriver Classic cannot restore arbitrary-origin localStorage at launch, so a
|
|
54
|
+
non-empty launch `storageState` is rejected. Its strict single-origin fallback
|
|
55
|
+
is explicit:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const browser = await Browser.launch({ enableBiDi: false });
|
|
59
|
+
|
|
60
|
+
await browser.navigateTo('https://example.test/app'); // reach the origin
|
|
61
|
+
await browser.loadState('.auth/alice.json'); // now storage can land
|
|
62
|
+
await browser.reload(); // let the app read it
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The fallback validates the complete snapshot before applying anything. A
|
|
66
|
+
second origin or a cookie that cannot be set from the active page fails loudly.
|
|
67
|
+
Chrome/Chromium and Firefox BiDi use the simpler launch form above.
|
|
68
|
+
|
|
41
69
|
## Notes
|
|
42
70
|
|
|
43
71
|
- Keep generated auth files out of source control if they contain real secrets.
|
package/docs/screenshots.md
CHANGED
|
@@ -17,6 +17,10 @@ craftdriver exposes a single options-bag `screenshot()` method on both
|
|
|
17
17
|
|
|
18
18
|
`fullPage` and `selector` are mutually exclusive.
|
|
19
19
|
|
|
20
|
+
> To **compare** a screenshot against a committed baseline (with retries and
|
|
21
|
+
> pixel tolerances) rather than just capture one, see
|
|
22
|
+
> [Visual testing](./visual-testing.md) and `browser.expectScreenshot()`.
|
|
23
|
+
|
|
20
24
|
```typescript
|
|
21
25
|
// Viewport only (default), buffer
|
|
22
26
|
const buffer = await browser.screenshot();
|
|
@@ -78,15 +82,20 @@ afterEach(async (context) => {
|
|
|
78
82
|
|
|
79
83
|
### Visual comparison baseline
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
For visual regression testing you don't manage baseline and candidate PNGs by
|
|
86
|
+
hand or reach for a separate image-diff library. `browser.expectScreenshot()`
|
|
87
|
+
captures, compares against a committed baseline, retries until it settles, and
|
|
88
|
+
throws a typed diff on a real change — creating the baseline on the first run:
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
await browser.
|
|
87
|
-
|
|
90
|
+
```typescript
|
|
91
|
+
await browser.expectScreenshot('baselines/homepage.png', {
|
|
92
|
+
screenshot: { fullPage: true },
|
|
93
|
+
});
|
|
88
94
|
```
|
|
89
95
|
|
|
96
|
+
See [Visual testing](./visual-testing.md) for baselines, thresholds, ignoring
|
|
97
|
+
anti-aliasing, updating baselines, and making CI deterministic.
|
|
98
|
+
|
|
90
99
|
### Component screenshots
|
|
91
100
|
|
|
92
101
|
```typescript
|