craftdriver 0.1.0 → 0.2.1
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 +111 -0
- package/README.md +157 -45
- package/bin/craftdriver.mjs +26 -0
- package/dist/cli/client.d.ts +17 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +88 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon.d.ts +15 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +145 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/defaults.d.ts +10 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +42 -0
- package/dist/cli/defaults.js.map +1 -0
- package/dist/cli/dispatcher.d.ts +28 -0
- package/dist/cli/dispatcher.d.ts.map +1 -0
- package/dist/cli/dispatcher.js +319 -0
- package/dist/cli/dispatcher.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +404 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +24 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +192 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +43 -0
- package/dist/cli/mcp/artifacts.d.ts.map +1 -0
- package/dist/cli/mcp/artifacts.js +104 -0
- package/dist/cli/mcp/artifacts.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +34 -0
- package/dist/cli/mcp/server.d.ts.map +1 -0
- package/dist/cli/mcp/server.js +214 -0
- package/dist/cli/mcp/server.js.map +1 -0
- package/dist/cli/mcp/tools.d.ts +34 -0
- package/dist/cli/mcp/tools.d.ts.map +1 -0
- package/dist/cli/mcp/tools.js +236 -0
- package/dist/cli/mcp/tools.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +37 -0
- package/dist/cli/parseArgs.d.ts.map +1 -0
- package/dist/cli/parseArgs.js +215 -0
- package/dist/cli/parseArgs.js.map +1 -0
- package/dist/cli/protocol.d.ts +34 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/cli/protocol.js +2 -0
- package/dist/cli/protocol.js.map +1 -0
- package/dist/cli/selector.d.ts +29 -0
- package/dist/cli/selector.d.ts.map +1 -0
- package/dist/cli/selector.js +100 -0
- package/dist/cli/selector.js.map +1 -0
- package/dist/cli/snapshot.d.ts +48 -0
- package/dist/cli/snapshot.d.ts.map +1 -0
- package/dist/cli/snapshot.js +179 -0
- package/dist/cli/snapshot.js.map +1 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/a11y.d.ts +81 -0
- package/dist/lib/a11y.d.ts.map +1 -0
- package/dist/lib/a11y.js +171 -0
- package/dist/lib/a11y.js.map +1 -0
- package/dist/lib/bidi/index.d.ts +26 -1
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +49 -0
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +2 -2
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +2 -2
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/bidi/network.d.ts +74 -1
- package/dist/lib/bidi/network.d.ts.map +1 -1
- package/dist/lib/bidi/network.js +222 -4
- package/dist/lib/bidi/network.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +5 -3
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +14 -7
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/bidi/types.d.ts +27 -6
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +637 -16
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +1428 -102
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +486 -0
- package/dist/lib/browserContext.d.ts.map +1 -0
- package/dist/lib/browserContext.js +937 -0
- package/dist/lib/browserContext.js.map +1 -0
- package/dist/lib/builder.d.ts +3 -0
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +31 -4
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/by.d.ts +18 -2
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +20 -2
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/chrome.d.ts +13 -1
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +13 -17
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/clock.d.ts +115 -0
- package/dist/lib/clock.d.ts.map +1 -0
- package/dist/lib/clock.js +407 -0
- package/dist/lib/clock.js.map +1 -0
- package/dist/lib/driver.d.ts +43 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +120 -0
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +25 -0
- package/dist/lib/driverManager.d.ts.map +1 -0
- package/dist/lib/driverManager.js +399 -0
- package/dist/lib/driverManager.js.map +1 -0
- package/dist/lib/elementHandle.d.ts +67 -3
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +209 -67
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +71 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +73 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/expect.d.ts +6 -1
- package/dist/lib/expect.d.ts.map +1 -1
- package/dist/lib/expect.js +55 -37
- package/dist/lib/expect.js.map +1 -1
- package/dist/lib/firefox.d.ts +18 -0
- package/dist/lib/firefox.d.ts.map +1 -0
- package/dist/lib/firefox.js +33 -0
- package/dist/lib/firefox.js.map +1 -0
- package/dist/lib/frame.d.ts +57 -0
- package/dist/lib/frame.d.ts.map +1 -0
- package/dist/lib/frame.js +277 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/keyboard.d.ts +1 -1
- package/dist/lib/keyboard.d.ts.map +1 -1
- package/dist/lib/keyboard.js +1 -1
- package/dist/lib/keyboard.js.map +1 -1
- package/dist/lib/locator.d.ts +71 -2
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +289 -31
- package/dist/lib/locator.js.map +1 -1
- package/dist/lib/mouse.d.ts +1 -1
- package/dist/lib/mouse.d.ts.map +1 -1
- package/dist/lib/mouse.js +1 -1
- package/dist/lib/mouse.js.map +1 -1
- package/dist/lib/page.d.ts +107 -0
- package/dist/lib/page.d.ts.map +1 -0
- package/dist/lib/page.js +388 -0
- package/dist/lib/page.js.map +1 -0
- package/dist/lib/tracing.d.ts +128 -0
- package/dist/lib/tracing.d.ts.map +1 -0
- package/dist/lib/tracing.js +272 -0
- package/dist/lib/tracing.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +14 -1
- package/dist/lib/wait.js.map +1 -1
- package/dist/lib/webelement.d.ts +2 -0
- package/dist/lib/webelement.d.ts.map +1 -1
- package/dist/lib/webelement.js +18 -0
- package/dist/lib/webelement.js.map +1 -1
- package/docs/accessibility.md +177 -0
- package/docs/api-reference.md +71 -0
- package/docs/assertions.md +182 -0
- package/docs/bidi-features.md +462 -0
- package/docs/browser-api.md +673 -0
- package/docs/browser-context.md +522 -0
- package/docs/cli.md +233 -0
- package/docs/clock.md +245 -0
- package/docs/dialogs.md +143 -0
- package/docs/driver-configuration.md +64 -0
- package/docs/element-api.md +219 -0
- package/docs/emulation.md +155 -0
- package/docs/error-codes.md +67 -0
- package/docs/getting-started.md +168 -0
- package/docs/keyboard-mouse.md +250 -0
- package/docs/mcp.md +250 -0
- package/docs/mobile-emulation.md +194 -0
- package/docs/screenshots.md +146 -0
- package/docs/selectors.md +382 -0
- package/docs/session-management.md +229 -0
- package/docs/tracing.md +282 -0
- package/package.json +26 -8
- package/skills/craftdriver/SKILL.md +84 -0
- package/skills/craftdriver/cheatsheet.md +194 -0
- package/skills/craftdriver/cli.md +104 -0
- 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
|