craftdriver 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +157 -45
  3. package/bin/craftdriver.mjs +26 -0
  4. package/dist/cli/client.d.ts +17 -0
  5. package/dist/cli/client.d.ts.map +1 -0
  6. package/dist/cli/client.js +88 -0
  7. package/dist/cli/client.js.map +1 -0
  8. package/dist/cli/daemon.d.ts +15 -0
  9. package/dist/cli/daemon.d.ts.map +1 -0
  10. package/dist/cli/daemon.js +145 -0
  11. package/dist/cli/daemon.js.map +1 -0
  12. package/dist/cli/defaults.d.ts +10 -0
  13. package/dist/cli/defaults.d.ts.map +1 -0
  14. package/dist/cli/defaults.js +42 -0
  15. package/dist/cli/defaults.js.map +1 -0
  16. package/dist/cli/dispatcher.d.ts +28 -0
  17. package/dist/cli/dispatcher.d.ts.map +1 -0
  18. package/dist/cli/dispatcher.js +319 -0
  19. package/dist/cli/dispatcher.js.map +1 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +404 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cli/init.d.ts +24 -0
  25. package/dist/cli/init.d.ts.map +1 -0
  26. package/dist/cli/init.js +192 -0
  27. package/dist/cli/init.js.map +1 -0
  28. package/dist/cli/mcp/artifacts.d.ts +43 -0
  29. package/dist/cli/mcp/artifacts.d.ts.map +1 -0
  30. package/dist/cli/mcp/artifacts.js +104 -0
  31. package/dist/cli/mcp/artifacts.js.map +1 -0
  32. package/dist/cli/mcp/server.d.ts +34 -0
  33. package/dist/cli/mcp/server.d.ts.map +1 -0
  34. package/dist/cli/mcp/server.js +214 -0
  35. package/dist/cli/mcp/server.js.map +1 -0
  36. package/dist/cli/mcp/tools.d.ts +34 -0
  37. package/dist/cli/mcp/tools.d.ts.map +1 -0
  38. package/dist/cli/mcp/tools.js +236 -0
  39. package/dist/cli/mcp/tools.js.map +1 -0
  40. package/dist/cli/parseArgs.d.ts +37 -0
  41. package/dist/cli/parseArgs.d.ts.map +1 -0
  42. package/dist/cli/parseArgs.js +215 -0
  43. package/dist/cli/parseArgs.js.map +1 -0
  44. package/dist/cli/protocol.d.ts +34 -0
  45. package/dist/cli/protocol.d.ts.map +1 -0
  46. package/dist/cli/protocol.js +2 -0
  47. package/dist/cli/protocol.js.map +1 -0
  48. package/dist/cli/selector.d.ts +29 -0
  49. package/dist/cli/selector.d.ts.map +1 -0
  50. package/dist/cli/selector.js +100 -0
  51. package/dist/cli/selector.js.map +1 -0
  52. package/dist/cli/snapshot.d.ts +48 -0
  53. package/dist/cli/snapshot.d.ts.map +1 -0
  54. package/dist/cli/snapshot.js +179 -0
  55. package/dist/cli/snapshot.js.map +1 -0
  56. package/dist/index.d.ts +14 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +15 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/lib/a11y.d.ts +81 -0
  61. package/dist/lib/a11y.d.ts.map +1 -0
  62. package/dist/lib/a11y.js +171 -0
  63. package/dist/lib/a11y.js.map +1 -0
  64. package/dist/lib/bidi/index.d.ts +26 -1
  65. package/dist/lib/bidi/index.d.ts.map +1 -1
  66. package/dist/lib/bidi/index.js +49 -0
  67. package/dist/lib/bidi/index.js.map +1 -1
  68. package/dist/lib/bidi/logs.d.ts +2 -2
  69. package/dist/lib/bidi/logs.d.ts.map +1 -1
  70. package/dist/lib/bidi/logs.js +2 -2
  71. package/dist/lib/bidi/logs.js.map +1 -1
  72. package/dist/lib/bidi/network.d.ts +74 -1
  73. package/dist/lib/bidi/network.d.ts.map +1 -1
  74. package/dist/lib/bidi/network.js +222 -4
  75. package/dist/lib/bidi/network.js.map +1 -1
  76. package/dist/lib/bidi/storage.d.ts +5 -3
  77. package/dist/lib/bidi/storage.d.ts.map +1 -1
  78. package/dist/lib/bidi/storage.js +14 -7
  79. package/dist/lib/bidi/storage.js.map +1 -1
  80. package/dist/lib/bidi/types.d.ts +27 -6
  81. package/dist/lib/bidi/types.d.ts.map +1 -1
  82. package/dist/lib/browser.d.ts +637 -16
  83. package/dist/lib/browser.d.ts.map +1 -1
  84. package/dist/lib/browser.js +1428 -102
  85. package/dist/lib/browser.js.map +1 -1
  86. package/dist/lib/browserContext.d.ts +486 -0
  87. package/dist/lib/browserContext.d.ts.map +1 -0
  88. package/dist/lib/browserContext.js +937 -0
  89. package/dist/lib/browserContext.js.map +1 -0
  90. package/dist/lib/builder.d.ts +3 -0
  91. package/dist/lib/builder.d.ts.map +1 -1
  92. package/dist/lib/builder.js +31 -4
  93. package/dist/lib/builder.js.map +1 -1
  94. package/dist/lib/by.d.ts +18 -2
  95. package/dist/lib/by.d.ts.map +1 -1
  96. package/dist/lib/by.js +20 -2
  97. package/dist/lib/by.js.map +1 -1
  98. package/dist/lib/chrome.d.ts +13 -1
  99. package/dist/lib/chrome.d.ts.map +1 -1
  100. package/dist/lib/chrome.js +13 -17
  101. package/dist/lib/chrome.js.map +1 -1
  102. package/dist/lib/clock.d.ts +115 -0
  103. package/dist/lib/clock.d.ts.map +1 -0
  104. package/dist/lib/clock.js +407 -0
  105. package/dist/lib/clock.js.map +1 -0
  106. package/dist/lib/driver.d.ts +43 -0
  107. package/dist/lib/driver.d.ts.map +1 -1
  108. package/dist/lib/driver.js +120 -0
  109. package/dist/lib/driver.js.map +1 -1
  110. package/dist/lib/driverManager.d.ts +25 -0
  111. package/dist/lib/driverManager.d.ts.map +1 -0
  112. package/dist/lib/driverManager.js +399 -0
  113. package/dist/lib/driverManager.js.map +1 -0
  114. package/dist/lib/elementHandle.d.ts +67 -3
  115. package/dist/lib/elementHandle.d.ts.map +1 -1
  116. package/dist/lib/elementHandle.js +209 -67
  117. package/dist/lib/elementHandle.js.map +1 -1
  118. package/dist/lib/errors.d.ts +71 -0
  119. package/dist/lib/errors.d.ts.map +1 -0
  120. package/dist/lib/errors.js +73 -0
  121. package/dist/lib/errors.js.map +1 -0
  122. package/dist/lib/expect.d.ts +6 -1
  123. package/dist/lib/expect.d.ts.map +1 -1
  124. package/dist/lib/expect.js +55 -37
  125. package/dist/lib/expect.js.map +1 -1
  126. package/dist/lib/firefox.d.ts +18 -0
  127. package/dist/lib/firefox.d.ts.map +1 -0
  128. package/dist/lib/firefox.js +33 -0
  129. package/dist/lib/firefox.js.map +1 -0
  130. package/dist/lib/frame.d.ts +57 -0
  131. package/dist/lib/frame.d.ts.map +1 -0
  132. package/dist/lib/frame.js +277 -0
  133. package/dist/lib/frame.js.map +1 -0
  134. package/dist/lib/keyboard.d.ts +1 -1
  135. package/dist/lib/keyboard.d.ts.map +1 -1
  136. package/dist/lib/keyboard.js +1 -1
  137. package/dist/lib/keyboard.js.map +1 -1
  138. package/dist/lib/locator.d.ts +71 -2
  139. package/dist/lib/locator.d.ts.map +1 -1
  140. package/dist/lib/locator.js +289 -31
  141. package/dist/lib/locator.js.map +1 -1
  142. package/dist/lib/mouse.d.ts +1 -1
  143. package/dist/lib/mouse.d.ts.map +1 -1
  144. package/dist/lib/mouse.js +1 -1
  145. package/dist/lib/mouse.js.map +1 -1
  146. package/dist/lib/page.d.ts +107 -0
  147. package/dist/lib/page.d.ts.map +1 -0
  148. package/dist/lib/page.js +388 -0
  149. package/dist/lib/page.js.map +1 -0
  150. package/dist/lib/tracing.d.ts +128 -0
  151. package/dist/lib/tracing.d.ts.map +1 -0
  152. package/dist/lib/tracing.js +272 -0
  153. package/dist/lib/tracing.js.map +1 -0
  154. package/dist/lib/wait.d.ts.map +1 -1
  155. package/dist/lib/wait.js +14 -1
  156. package/dist/lib/wait.js.map +1 -1
  157. package/dist/lib/webelement.d.ts +2 -0
  158. package/dist/lib/webelement.d.ts.map +1 -1
  159. package/dist/lib/webelement.js +18 -0
  160. package/dist/lib/webelement.js.map +1 -1
  161. package/docs/accessibility.md +177 -0
  162. package/docs/api-reference.md +71 -0
  163. package/docs/assertions.md +182 -0
  164. package/docs/bidi-features.md +462 -0
  165. package/docs/browser-api.md +673 -0
  166. package/docs/browser-context.md +522 -0
  167. package/docs/cli.md +233 -0
  168. package/docs/clock.md +245 -0
  169. package/docs/dialogs.md +143 -0
  170. package/docs/driver-configuration.md +64 -0
  171. package/docs/element-api.md +219 -0
  172. package/docs/emulation.md +155 -0
  173. package/docs/error-codes.md +67 -0
  174. package/docs/getting-started.md +168 -0
  175. package/docs/keyboard-mouse.md +250 -0
  176. package/docs/mcp.md +250 -0
  177. package/docs/mobile-emulation.md +194 -0
  178. package/docs/screenshots.md +146 -0
  179. package/docs/selectors.md +382 -0
  180. package/docs/session-management.md +229 -0
  181. package/docs/tracing.md +282 -0
  182. package/package.json +23 -6
  183. package/skills/craftdriver/SKILL.md +84 -0
  184. package/skills/craftdriver/cheatsheet.md +194 -0
  185. package/skills/craftdriver/cli.md +104 -0
  186. package/skills/craftdriver/patterns.md +139 -0
@@ -0,0 +1,250 @@
1
+ # Keyboard & Mouse
2
+
3
+ CraftDriver provides low-level keyboard and mouse APIs for complex interaction scenarios.
4
+
5
+ ## Keyboard
6
+
7
+ Access the keyboard API via `browser.keyboard`.
8
+
9
+ ### press(key)
10
+
11
+ Press and release a key.
12
+
13
+ ```typescript
14
+ await browser.keyboard.press('Enter');
15
+ await browser.keyboard.press('Tab');
16
+ await browser.keyboard.press('Escape');
17
+ ```
18
+
19
+ Common key names:
20
+
21
+ - Navigation: `'Tab'`, `'Enter'`, `'Escape'`, `'Backspace'`, `'Delete'`
22
+ - Arrows: `'ArrowUp'`, `'ArrowDown'`, `'ArrowLeft'`, `'ArrowRight'`
23
+ - Modifiers: `'Shift'`, `'Control'`, `'Alt'`, `'Meta'`
24
+ - Function: `'F1'` through `'F12'`
25
+ - Special: `'Home'`, `'End'`, `'PageUp'`, `'PageDown'`, `'Insert'`
26
+
27
+ ### type(text)
28
+
29
+ Type a string of text character by character.
30
+
31
+ ```typescript
32
+ await browser.keyboard.type('Hello, World!');
33
+ ```
34
+
35
+ ### down(key)
36
+
37
+ Press and hold a key.
38
+
39
+ ```typescript
40
+ await browser.keyboard.down('Shift');
41
+ ```
42
+
43
+ ### up(key)
44
+
45
+ Release a held key.
46
+
47
+ ```typescript
48
+ await browser.keyboard.up('Shift');
49
+ ```
50
+
51
+ ### Key Combinations
52
+
53
+ Combine `down()` and `up()` for modifier key combinations:
54
+
55
+ ```typescript
56
+ // Ctrl+A (select all)
57
+ await browser.keyboard.down('Control');
58
+ await browser.keyboard.press('a');
59
+ await browser.keyboard.up('Control');
60
+
61
+ // Shift+Tab (reverse tab)
62
+ await browser.keyboard.down('Shift');
63
+ await browser.keyboard.press('Tab');
64
+ await browser.keyboard.up('Shift');
65
+ ```
66
+
67
+ ### Examples
68
+
69
+ #### Search and Submit
70
+
71
+ ```typescript
72
+ await browser.find('#search').click();
73
+ await browser.keyboard.type('craftdriver');
74
+ await browser.keyboard.press('Enter');
75
+ ```
76
+
77
+ #### Form Navigation with Tab
78
+
79
+ ```typescript
80
+ await browser.find('#first-name').fill('John');
81
+ await browser.keyboard.press('Tab');
82
+ await browser.keyboard.type('Doe'); // Now in last-name field
83
+ await browser.keyboard.press('Tab');
84
+ await browser.keyboard.type('john@example.com');
85
+ ```
86
+
87
+ #### Select All and Delete
88
+
89
+ ```typescript
90
+ await browser.find('#input').click();
91
+ await browser.keyboard.down('Control');
92
+ await browser.keyboard.press('a');
93
+ await browser.keyboard.up('Control');
94
+ await browser.keyboard.press('Backspace');
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Mouse
100
+
101
+ Access the mouse API via `browser.mouse`.
102
+
103
+ ### click(x, y)
104
+
105
+ Click at specific coordinates.
106
+
107
+ ```typescript
108
+ await browser.mouse.click(100, 200);
109
+ ```
110
+
111
+ ### move(x, y)
112
+
113
+ Move the mouse to coordinates.
114
+
115
+ ```typescript
116
+ await browser.mouse.move(150, 300);
117
+ ```
118
+
119
+ ### down()
120
+
121
+ Press the mouse button.
122
+
123
+ ```typescript
124
+ await browser.mouse.down();
125
+ ```
126
+
127
+ ### up()
128
+
129
+ Release the mouse button.
130
+
131
+ ```typescript
132
+ await browser.mouse.up();
133
+ ```
134
+
135
+ ### wheel(deltaX, deltaY)
136
+
137
+ Scroll the page.
138
+
139
+ ```typescript
140
+ // Scroll down
141
+ await browser.mouse.wheel(0, 100);
142
+
143
+ // Scroll up
144
+ await browser.mouse.wheel(0, -100);
145
+
146
+ // Scroll right
147
+ await browser.mouse.wheel(100, 0);
148
+ ```
149
+
150
+ ### dragAndDrop(from, to)
151
+
152
+ Drag from one position to another.
153
+
154
+ ```typescript
155
+ await browser.mouse.dragAndDrop(
156
+ { x: 100, y: 100 }, // Start position
157
+ { x: 300, y: 300 } // End position
158
+ );
159
+ ```
160
+
161
+ ### Examples
162
+
163
+ #### Custom Drag Operation
164
+
165
+ ```typescript
166
+ // Get element positions
167
+ const source = await browser.find('#draggable').boundingBox();
168
+ const target = await browser.find('#drop-zone').boundingBox();
169
+
170
+ // Calculate centers
171
+ const startX = source.x + source.width / 2;
172
+ const startY = source.y + source.height / 2;
173
+ const endX = target.x + target.width / 2;
174
+ const endY = target.y + target.height / 2;
175
+
176
+ // Perform drag
177
+ await browser.mouse.dragAndDrop({ x: startX, y: startY }, { x: endX, y: endY });
178
+ ```
179
+
180
+ #### Canvas Drawing
181
+
182
+ ```typescript
183
+ // Move to start position
184
+ await browser.mouse.move(100, 100);
185
+ await browser.mouse.down();
186
+
187
+ // Draw a line
188
+ await browser.mouse.move(200, 100);
189
+ await browser.mouse.move(200, 200);
190
+ await browser.mouse.move(100, 200);
191
+ await browser.mouse.move(100, 100);
192
+
193
+ await browser.mouse.up();
194
+ ```
195
+
196
+ #### Scroll Through Content
197
+
198
+ ```typescript
199
+ // Navigate to page
200
+ await browser.navigateTo('https://example.com/long-page');
201
+
202
+ // Scroll down gradually
203
+ for (let i = 0; i < 5; i++) {
204
+ await browser.mouse.wheel(0, 200);
205
+ await browser.pause(500);
206
+ }
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Element-Level Keyboard Shortcuts
212
+
213
+ For element-scoped key presses, use the `press()` method on ElementHandle:
214
+
215
+ ```typescript
216
+ // Press Enter in a specific input
217
+ await browser.find('#search').fill('query').press('Enter');
218
+
219
+ // Tab out of a field
220
+ await browser.find('#username').fill('user').press('Tab');
221
+ ```
222
+
223
+ This is often more convenient than using the global keyboard API.
224
+
225
+ ---
226
+
227
+ ## Touch gestures (mobile emulation)
228
+
229
+ When running with `mobileEmulation`, two convenience gestures are
230
+ exposed under `browser.gesture`:
231
+
232
+ ```typescript
233
+ // Swipe from one point to another (px coordinates).
234
+ await browser.gesture.swipe({
235
+ from: [200, 600],
236
+ to: [200, 200],
237
+ durationMs: 300,
238
+ });
239
+
240
+ // Pinch / zoom centered on a point.
241
+ await browser.gesture.pinch({
242
+ center: [200, 400],
243
+ scale: 0.5, // < 1 zooms out, > 1 zooms in
244
+ distance: 100, // initial finger separation, px
245
+ durationMs: 250,
246
+ });
247
+ ```
248
+
249
+ These wrap the W3C Pointer Actions API with two simulated touch
250
+ pointers. They work whether or not BiDi is enabled.
package/docs/mcp.md ADDED
@@ -0,0 +1,250 @@
1
+ # MCP server
2
+
3
+ `craftdriver` ships a [Model Context Protocol](https://modelcontextprotocol.io)
4
+ server so hosted / sandboxed AI agents (Claude Desktop, Claude Code,
5
+ Cursor, Windsurf, Zed, Goose, Gemini CLI, …) can drive a real browser
6
+ without managing a daemon, a socket, or filesystem access.
7
+
8
+ It is a **peer to the CLI**, not a wrapper. Both share the same
9
+ dispatcher and error codes, but the MCP server returns a richer
10
+ post-action payload (compact a11y snapshot, diffed from the previous
11
+ turn) that text models can act on directly.
12
+
13
+ ```bash
14
+ # Start once via your MCP client — examples below
15
+ npx -y craftdriver mcp
16
+ ```
17
+
18
+ The server speaks JSON-RPC 2.0 on stdio. The browser launches lazily
19
+ on the first tool call and shuts down when the client disconnects.
20
+
21
+ ## Install snippets
22
+
23
+ ### Claude Code / Claude Desktop
24
+
25
+ ```bash
26
+ claude mcp add craftdriver -- npx -y craftdriver mcp
27
+ ```
28
+
29
+ ### Cursor / Windsurf / Zed (`.cursor/mcp.json` and similar)
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "craftdriver": {
35
+ "command": "npx",
36
+ "args": ["-y", "craftdriver", "mcp"]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Gemini CLI
43
+
44
+ ```bash
45
+ gemini mcp add craftdriver npx -y craftdriver mcp
46
+ ```
47
+
48
+ ### Goose
49
+
50
+ ```bash
51
+ goose configure # add craftdriver as a stdio server
52
+ ```
53
+
54
+ ## Tools
55
+
56
+ Compact set — 14 tools, one line each. Long help lives in the schema
57
+ description; clients render it in the model's context once per session.
58
+
59
+ | Tool | Purpose |
60
+ | ------------------------ | ---------------------------------------------------------------------- |
61
+ | `browser_navigate` | Go to a URL (waits for load). |
62
+ | `browser_click` | Click an element. Auto-waits visible+enabled. |
63
+ | `browser_fill` | Fill an input/textarea/select. |
64
+ | `browser_press` | Press a keyboard key (`Enter`, `Tab`, `Control+A`). |
65
+ | `browser_hover` | Hover over an element. |
66
+ | `browser_find` | Locate elements without acting (returns tag/text/visibility). |
67
+ | `browser_exists` | **0-wait probe.** Returns `{exists, count}` in one BiDi roundtrip. |
68
+ | `browser_wait` | Wait for selector state or load state. |
69
+ | `browser_read` | Read `text` / `attr` / `value` / `is(visible|enabled|checked)`. |
70
+ | `browser_pages` | List open pages (id, url, title). |
71
+ | `browser_snapshot` | **Sanitized DOM summary with refs.** Use `ref=eN` as the selector for subsequent calls. |
72
+ | `browser_screenshot` | Capture PNG to a file (auto-allocated under the per-session artifact dir; never inlined). |
73
+ | `browser_status` | Browser up? Which URL is active? |
74
+ | `browser_advanced_eval` | Evaluate JS in the page. Last resort. |
75
+
76
+ `browser_trace` (start/stop/explain) and trace resources are slated
77
+ for a future release alongside richer trace introspection.
78
+
79
+ ## Selector syntax
80
+
81
+ Identical to the CLI. CSS by default; switch with a `prefix=value`
82
+ form:
83
+
84
+ ```
85
+ role=button[name=Submit] text=Sign In text*=Sign
86
+ label=Email placeholder=Search… testid=login-btn
87
+ alt=Logo title=Help xpath=//div[1]
88
+ id=submit name=email tag=h1
89
+ ref=e5 (← from browser_snapshot, see below)
90
+ ```
91
+
92
+ ## Refs — the token-efficient locator
93
+
94
+ Call `browser_snapshot` (or just navigate — the post-action diff
95
+ carries refs too) and you get a sanitized accessibility-tree summary
96
+ where each visible interactive element is numbered:
97
+
98
+ ```
99
+ page: Login — http://…/login.html
100
+ e1: heading "Login"
101
+ e2: form "Username Password Sign in" #login-form
102
+ e3: label "Username"
103
+ e4: textbox "Username" #username
104
+ e5: label "Password"
105
+ e6: textbox "Password" #password
106
+ e7: button "Sign in" #submit
107
+ ```
108
+
109
+ Use `ref=eN` as the selector for the next call:
110
+
111
+ ```jsonc
112
+ { "name": "browser_fill", "arguments": { "selector": "ref=e4", "value": "alice" } }
113
+ { "name": "browser_fill", "arguments": { "selector": "ref=e6", "value": "hunter2" } }
114
+ { "name": "browser_click", "arguments": { "selector": "ref=e7" } }
115
+ ```
116
+
117
+ **Why this is a big deal for AI test generation**
118
+
119
+ - **No selector hallucination.** The agent picks a number, not a
120
+ CSS/XPath/role expression. The element is already on the page —
121
+ there is nothing to guess wrong.
122
+ - **Token efficient.** `ref=e7` is 5 characters; `role=button[name=Sign in]`
123
+ is 26. Over a 50-step flow that adds up.
124
+ - **Auto-waiting still works.** Internally `ref=eN` resolves to a CSS
125
+ attribute selector (`[data-craftdriver-ref="eN"]`); every action
126
+ takes the normal visible+enabled wait path.
127
+
128
+ **Invalidation rules**
129
+
130
+ - Refs are re-allocated on **every** `browser_snapshot` call.
131
+ - The post-action a11y diff after a mutating tool also re-runs the
132
+ snapshot, so refs renumber on every turn.
133
+ - Navigating to a new URL invalidates all refs.
134
+ - A stale ref just fails with `NO_MATCH` — take a fresh snapshot.
135
+
136
+ ## Post-action payload
137
+
138
+ Every tool returns a content array. Mutating tools (`navigate`,
139
+ `click`, `fill`, `press`, `hover`, `advanced_eval`) additionally
140
+ include a **compact a11y snapshot, diffed from the previous turn**:
141
+
142
+ ```jsonc
143
+ {
144
+ "content": [
145
+ { "type": "text", "text": "{\"ok\":true,\"selector\":\"css selector=button[type=submit]\"}" },
146
+ {
147
+ "type": "text",
148
+ "text": "page: Login — http://…/login.html\n- form \"Username Password Sign in\" #login-form\n- textbox \"Username\" #username\n- button \"Sign in\" #submit\n+ button \"Logout\" #logout"
149
+ }
150
+ ],
151
+ "structuredContent": { "result": { "ok": true, "selector": "css selector=button[type=submit]" } }
152
+ }
153
+ ```
154
+
155
+ - **First call in a session** returns the full snapshot (one line per
156
+ visible interactive element: role + accessible name + locator hint).
157
+ - **Subsequent calls** return only the lines that appeared (`+`) or
158
+ disappeared (`-`).
159
+ - **URL change** triggers a fresh full snapshot.
160
+ - Capped at 80 nodes / 80 chars per name so the payload stays bounded
161
+ regardless of page complexity.
162
+
163
+ This is the MCP server's "killer feature" over the CLI: the agent sees
164
+ what changed without a follow-up `read` call, in ~50–500 text tokens
165
+ instead of 800–1500 image tokens for a screenshot.
166
+
167
+ ## Artifact spilling (token efficiency)
168
+
169
+ MCP content blocks count against the model's context window on every
170
+ turn. To keep the per-call cost bounded, large payloads are **written
171
+ to disk** and the inline block becomes a short preview plus the
172
+ absolute path:
173
+
174
+ ```
175
+ heading "Selectors Playground"
176
+ textbox "by id" #by-id
177
+ textbox "by name" #by-name
178
+ img "Logo ALT" #by-alt
179
+ button "Click me" #by-text
180
+
181
+ (full output: /tmp/craftdriver-mcp-1234-abc/0001-snapshot.txt, 1872 bytes)
182
+ ```
183
+
184
+ Applies to:
185
+
186
+ - **Screenshots** — always written to a file. If you pass `path`, that
187
+ path is used; otherwise an artifact path is auto-allocated. The
188
+ inline block carries the absolute path and byte count — **zero**
189
+ image tokens.
190
+ - **A11y snapshot diffs** — spill when the rendered diff exceeds the
191
+ threshold (typically only the full first-call snapshot on big pages).
192
+ - **Tool results** — `browser_read`, `browser_advanced_eval`, etc. spill
193
+ when the JSON-stringified result exceeds the threshold. No more silent
194
+ truncation.
195
+
196
+ Configuration:
197
+
198
+ | Env var | Default | Effect |
199
+ | ---------------------------------- | ---------------- | ------------------------------------------------- |
200
+ | `CRAFTDRIVER_MCP_ARTIFACTS_DIR` | `os.tmpdir()` | Root directory for the per-session artifact dir. |
201
+ | `CRAFTDRIVER_MCP_SPILL_BYTES` | `2048` (~500 tk) | Inline content blocks larger than this spill. |
202
+
203
+ The per-session directory (`<root>/craftdriver-mcp-<pid>-<stamp>/`) is
204
+ not deleted on shutdown — agents may still be reading past artifacts.
205
+ Use `$CRAFTDRIVER_MCP_ARTIFACTS_DIR` to point at a dir with your own
206
+ cleanup policy.
207
+
208
+ The `structuredContent` field is unaffected by spilling — small results
209
+ still round-trip in full there for programmatic consumers.
210
+
211
+ ## Errors
212
+
213
+ Errors are returned as `isError: true` content (per MCP spec), **not**
214
+ as JSON-RPC errors. JSON-RPC errors are reserved for protocol-level
215
+ failures (unknown method, malformed request).
216
+
217
+ ```jsonc
218
+ {
219
+ "isError": true,
220
+ "content": [
221
+ { "type": "text", "text": "error: click: no element matches css selector=#nope\ncode: NO_MATCH" }
222
+ ],
223
+ "structuredContent": {
224
+ "error": { "code": "NO_MATCH", "message": "click: no element matches css selector=#nope" }
225
+ }
226
+ }
227
+ ```
228
+
229
+ Match on `structuredContent.error.code` — full list in
230
+ [error-codes.md](./error-codes.md).
231
+
232
+ ## Fail-fast defaults
233
+
234
+ Same rules as the CLI:
235
+
236
+ - Default per-call timeout: **5 s** (override per call with
237
+ `timeout_ms`, globally with `CRAFTDRIVER_AGENT_TIMEOUT`).
238
+ - `browser_exists` is a **0-wait probe**. Call it before `browser_click`
239
+ / `browser_wait` when you're guessing.
240
+ - `browser_click` / `browser_fill` reject immediately with `NO_MATCH`
241
+ when the selector matches zero elements at `t=0` — no burning the
242
+ full timeout on a typo.
243
+
244
+ ## When to use MCP vs. the CLI
245
+
246
+ - **MCP** — your agent runs in a hosted or sandboxed environment that
247
+ can't spawn child processes per call, or you want tool discovery via
248
+ `tools/list`. Schema-typed args, structured errors, snapshot diffing.
249
+ - **CLI** — your agent has a shell. Same surface, leaner per-call cost,
250
+ also great for humans.
@@ -0,0 +1,194 @@
1
+ # Mobile Emulation
2
+
3
+ CraftDriver supports mobile device emulation using Chrome's built-in mobile emulation feature. This allows you to test responsive designs and mobile-specific behavior.
4
+
5
+ > **Chrome / Chromium only.** Mobile emulation maps to the
6
+ > `goog:chromeOptions.mobileEmulation` capability — a Chromium-specific
7
+ > extension to WebDriver. Firefox / geckodriver expose no equivalent
8
+ > API, so passing `mobileEmulation` together with
9
+ > `browserName: 'firefox'` throws a clear error at launch. For mobile
10
+ > testing on Firefox, use a real device or Firefox's responsive design
11
+ > mode manually.
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { Browser } from 'craftdriver';
17
+
18
+ const browser = await Browser.launch({
19
+ browserName: 'chrome',
20
+ mobileEmulation: 'iPhone 14',
21
+ });
22
+
23
+ await browser.navigateTo('https://example.com');
24
+ // Page loads with iPhone 14 viewport, touch events, and mobile user agent
25
+ ```
26
+
27
+ ## Using Device Presets
28
+
29
+ CraftDriver includes presets for popular devices:
30
+
31
+ ```typescript
32
+ // Use preset by name
33
+ const browser = await Browser.launch({
34
+ browserName: 'chrome',
35
+ mobileEmulation: 'iPhone 14',
36
+ });
37
+ ```
38
+
39
+ ### Available Presets
40
+
41
+ | Device | Width | Height | Pixel Ratio | Platform |
42
+ | ------------------ | ----- | ------ | ----------- | -------- |
43
+ | iPhone 14 | 390 | 844 | 3 | iOS |
44
+ | iPhone 14 Pro Max | 430 | 932 | 3 | iOS |
45
+ | iPhone SE | 375 | 667 | 2 | iOS |
46
+ | Pixel 7 | 412 | 915 | 2.625 | Android |
47
+ | Pixel 7 Pro | 412 | 892 | 3.5 | Android |
48
+ | Samsung Galaxy S23 | 360 | 780 | 3 | Android |
49
+ | iPad Pro 11 | 834 | 1194 | 2 | iPadOS |
50
+ | iPad Mini | 768 | 1024 | 2 | iPadOS |
51
+
52
+ You can also access presets programmatically:
53
+
54
+ ```typescript
55
+ import { devices } from 'craftdriver';
56
+
57
+ console.log(devices['iPhone 14']);
58
+ // { deviceMetrics: { width: 390, height: 844, pixelRatio: 3, ... }, userAgent: '...' }
59
+ ```
60
+
61
+ ## Custom Device Configuration
62
+
63
+ For custom devices or specific testing needs:
64
+
65
+ ```typescript
66
+ const browser = await Browser.launch({
67
+ browserName: 'chrome',
68
+ mobileEmulation: {
69
+ deviceMetrics: {
70
+ width: 320,
71
+ height: 568,
72
+ pixelRatio: 2,
73
+ mobile: true, // Enable mobile mode
74
+ touch: true, // Enable touch events
75
+ },
76
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) ...',
77
+ },
78
+ });
79
+ ```
80
+
81
+ ### Device Metrics Options
82
+
83
+ | Option | Type | Default | Description |
84
+ | ---------- | ------- | ------- | --------------------------------------- |
85
+ | width | number | - | Viewport width in pixels |
86
+ | height | number | - | Viewport height in pixels |
87
+ | pixelRatio | number | - | Device pixel ratio (e.g., 2 for Retina) |
88
+ | mobile | boolean | true | Enable mobile mode |
89
+ | touch | boolean | true | Enable touch event emulation |
90
+
91
+ ## Using Chrome's Built-in Devices
92
+
93
+ Chrome has its own device database. You can use any device name Chrome recognizes:
94
+
95
+ ```typescript
96
+ const browser = await Browser.launch({
97
+ browserName: 'chrome',
98
+ mobileEmulation: {
99
+ deviceName: 'Nexus 5', // Any device Chrome knows
100
+ },
101
+ });
102
+ ```
103
+
104
+ > **Note:** Chrome's device names are case-sensitive. Check Chrome DevTools for available names.
105
+
106
+ ## What Gets Emulated
107
+
108
+ When mobile emulation is enabled:
109
+
110
+ - **Viewport**: Set to device dimensions
111
+ - **Device Pixel Ratio**: High DPI rendering
112
+ - **Touch Events**: `touchstart`, `touchend`, `touchmove` work
113
+ - **User Agent**: Reflects mobile browser
114
+ - **CSS Media Queries**: `@media (max-width: ...)` respond correctly
115
+ - **`navigator.maxTouchPoints`**: Reports touch capability
116
+
117
+ ## Examples
118
+
119
+ ### Test Responsive Breakpoints
120
+
121
+ ```typescript
122
+ import { Browser } from 'craftdriver';
123
+
124
+ const viewports = [
125
+ { name: 'Mobile', width: 375, height: 667 },
126
+ { name: 'Tablet', width: 768, height: 1024 },
127
+ { name: 'Desktop', width: 1280, height: 800 },
128
+ ];
129
+
130
+ for (const vp of viewports) {
131
+ const browser = await Browser.launch({
132
+ browserName: 'chrome',
133
+ mobileEmulation: {
134
+ deviceMetrics: { width: vp.width, height: vp.height, pixelRatio: 2 },
135
+ },
136
+ });
137
+
138
+ await browser.navigateTo('https://example.com');
139
+ await browser.screenshot({ path: `screenshot-${vp.name}.png` });
140
+ await browser.quit();
141
+ }
142
+ ```
143
+
144
+ ### Test Mobile Navigation Menu
145
+
146
+ ```typescript
147
+ const browser = await Browser.launch({
148
+ browserName: 'chrome',
149
+ mobileEmulation: 'iPhone 14',
150
+ });
151
+
152
+ await browser.navigateTo('https://example.com');
153
+
154
+ // Mobile hamburger menu should be visible
155
+ await browser.expect('#mobile-menu-button').toBeVisible();
156
+
157
+ // Desktop nav should be hidden
158
+ const desktopNav = await browser.find('#desktop-nav').isVisible();
159
+ expect(desktopNav).toBe(false);
160
+
161
+ // Open mobile menu
162
+ await browser.click('#mobile-menu-button');
163
+ await browser.expect('#mobile-menu').toBeVisible();
164
+ ```
165
+
166
+ ### Combine with BiDi Features
167
+
168
+ Mobile emulation works alongside BiDi features:
169
+
170
+ ```typescript
171
+ const browser = await Browser.launch({
172
+ browserName: 'chrome',
173
+ mobileEmulation: 'Pixel 7',
174
+ });
175
+
176
+ // Mock mobile-specific API
177
+ await browser.network.mock('**/api/mobile-config', {
178
+ status: 200,
179
+ body: { mobileOptimized: true },
180
+ });
181
+
182
+ await browser.navigateTo('https://example.com');
183
+
184
+ // Check for console errors
185
+ const errors = browser.logs.getErrors();
186
+ expect(errors).toHaveLength(0);
187
+ ```
188
+
189
+ ## Limitations
190
+
191
+ - **Chrome/Chromium only**: Mobile emulation uses Chrome-specific capabilities
192
+ - **Not real device**: Some mobile behaviors can't be emulated (e.g., actual Safari rendering)
193
+ - **Fixed at launch**: Device configuration is set when the browser launches and can't be changed mid-session
194
+ - **No orientation change**: To test landscape, specify a wider width than height in deviceMetrics