craftdriver 1.7.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 +23 -11
- package/README.md +13 -4
- 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 +1 -1
- package/dist/index.d.ts.map +1 -1
- 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 +6 -3
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +140 -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 +8 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +8 -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/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/docs/agents.md +56 -61
- package/docs/api-reference.md +2 -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 +5 -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/session-management.md +64 -3
- package/docs/why-craftdriver.md +1 -1
- package/package.json +3 -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.
|
|
@@ -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 |
|
package/docs/why-craftdriver.md
CHANGED
|
@@ -16,7 +16,7 @@ CraftDriver is for writing boringly reliable automation against real browsers, w
|
|
|
16
16
|
- ๐งพ **Trace evidence** - capture actions, console output, errors, network events, and screenshots.
|
|
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,
|
|
19
|
+
- ๐ค **Agent-friendly** - CLI, a project-local skill, and optional MCP when coding agents need the browser.
|
|
20
20
|
|
|
21
21
|
## Good Fits
|
|
22
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",
|