craftdriver 1.0.3 → 1.1.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 +25 -3
- package/README.md +80 -160
- package/dist/lib/browser.d.ts +22 -0
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +14 -1
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/chrome.d.ts +15 -3
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +4 -2
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +2 -0
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +31 -0
- package/dist/lib/driverManager.d.ts.map +1 -1
- package/dist/lib/driverManager.js +165 -31
- package/dist/lib/driverManager.js.map +1 -1
- package/dist/lib/firefox.d.ts +4 -3
- package/dist/lib/firefox.d.ts.map +1 -1
- package/dist/lib/firefox.js.map +1 -1
- package/dist/lib/http.d.ts +1 -1
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +35 -7
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/timing.d.ts +13 -0
- package/dist/lib/timing.d.ts.map +1 -1
- package/dist/lib/timing.js +13 -0
- package/dist/lib/timing.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/docs/agents.md +92 -0
- package/docs/api-reference.md +108 -46
- package/docs/browser-api.md +2 -1
- package/docs/browser-context.md +4 -7
- package/docs/browser-logs.md +158 -0
- package/docs/cli.md +26 -26
- package/docs/driver-configuration.md +103 -4
- package/docs/element-api.md +37 -6
- package/docs/emulation.md +6 -2
- package/docs/index.md +64 -0
- package/docs/keyboard-mouse.md +28 -10
- package/docs/mobile-emulation.md +5 -2
- package/docs/network.md +203 -0
- package/docs/public/craftdriver-mark.svg +15 -0
- package/docs/public/social-card.svg +27 -0
- package/docs/recipes/accessibility-gate.md +53 -0
- package/docs/recipes/console-error-gate.md +57 -0
- package/docs/recipes/file-upload-download.md +58 -0
- package/docs/recipes/login-once-reuse-session.md +73 -0
- package/docs/recipes/mobile-flow-with-network-and-logs.md +57 -0
- package/docs/recipes/mock-api-and-assert-network.md +60 -0
- package/docs/recipes/multi-user-contexts.md +58 -0
- package/docs/recipes/trace-failing-test.md +64 -0
- package/docs/recipes/virtual-clock-time-sensitive-ui.md +60 -0
- package/docs/recipes/vitest-browser-lifecycle.md +56 -0
- package/docs/recipes.md +33 -0
- package/docs/selectors.md +22 -1
- package/docs/session-management.md +18 -1
- package/docs/standards.md +40 -0
- package/docs/why-craftdriver.md +28 -0
- package/docs/zero-config-drivers.md +64 -0
- package/package.json +19 -5
- package/docs/bidi-features.md +0 -470
|
@@ -8,10 +8,11 @@ Craftdriver resolves the WebDriver binary through a chain — first match wins:
|
|
|
8
8
|
| 2 | `CRAFTDRIVER_CHROMEDRIVER_PATH` / `CRAFTDRIVER_GECKODRIVER_PATH` env var |
|
|
9
9
|
| 3 | `CRAFTDRIVER_DRIVER_PATH` env var (generic fallback for either browser) |
|
|
10
10
|
| 4 | Legacy/Selenium-compatible env vars: `CHROMEDRIVER_PATH`, `SE_CHROMEDRIVER` (chromedriver) or `GECKODRIVER_PATH`, `GECKODRIVER_FILEPATH`, `SE_GECKODRIVER` (geckodriver) |
|
|
11
|
-
| 5 |
|
|
12
|
-
| 6 |
|
|
13
|
-
| 7 | `chromedriver` / `geckodriver`
|
|
14
|
-
| 8 |
|
|
11
|
+
| 5 | Known CI-provided driver directories (e.g. GitHub Actions' `CHROMEWEBDRIVER` / `GECKOWEBDRIVER`) — see [CI platform detection](#ci-platform-detection) |
|
|
12
|
+
| 6 | **Cached auto-resolution** — the path a previous auto-resolve settled on, reused within the `CRAFTDRIVER_DRIVER_TTL` window |
|
|
13
|
+
| 7 | `chromedriver` / `geckodriver` in `node_modules/.bin` |
|
|
14
|
+
| 8 | `chromedriver` / `geckodriver` on `PATH` |
|
|
15
|
+
| 9 | **Auto-download from Chrome for Testing / GitHub** ← the zero-config default |
|
|
15
16
|
|
|
16
17
|
Downloaded drivers are cached in `~/.cache/craftdriver`, and so is the
|
|
17
18
|
*resolution itself* — which driver path to use. Within the
|
|
@@ -27,6 +28,87 @@ re-downloaded if needed. Only the driver binary is ever downloaded, never the
|
|
|
27
28
|
browser itself. Explicit configuration (steps 1–4) always takes precedence over
|
|
28
29
|
the cache.
|
|
29
30
|
|
|
31
|
+
## CI platform detection
|
|
32
|
+
|
|
33
|
+
On **GitHub Actions**, craftdriver recognizes the runner's own pre-installed,
|
|
34
|
+
version-matched driver automatically — no configuration needed. GitHub's
|
|
35
|
+
`ubuntu-latest`, `macos-latest`, and `windows-latest` images all publish a
|
|
36
|
+
driver directory via `CHROMEWEBDRIVER` / `GECKOWEBDRIVER` env vars (see
|
|
37
|
+
[actions/runner-images](https://github.com/actions/runner-images)); craftdriver
|
|
38
|
+
checks those directories as step 5 above, before falling back to its own
|
|
39
|
+
cache/PATH-probe/download machinery.
|
|
40
|
+
|
|
41
|
+
This is best-effort: if a future runner image drops or renames these vars,
|
|
42
|
+
craftdriver falls through safely to the next resolution step rather than
|
|
43
|
+
erroring. No other CI platform is auto-detected yet — set
|
|
44
|
+
`CRAFTDRIVER_CHROMEDRIVER_PATH` / `CRAFTDRIVER_GECKODRIVER_PATH` explicitly on
|
|
45
|
+
other providers (or to pin a non-default driver even on GitHub Actions;
|
|
46
|
+
explicit config always wins, per the chain above).
|
|
47
|
+
|
|
48
|
+
## Browser Binary Configuration
|
|
49
|
+
|
|
50
|
+
Everything above resolves the **driver** (chromedriver/geckodriver). A
|
|
51
|
+
separate, independent chain resolves the **browser** binary itself — useful
|
|
52
|
+
for a non-default Chrome/Chromium install or a portable Firefox build.
|
|
53
|
+
|
|
54
|
+
The `browserPath` option itself is opt-in: craftdriver never sets it for you,
|
|
55
|
+
and omitting it changes nothing. Steps 4–5 below are **not** craftdriver-opt-in
|
|
56
|
+
the same way, though — `CHROME_BIN`, `FIREFOX_BIN`, `SE_CHROME_PATH`, and
|
|
57
|
+
`SE_FIREFOX_PATH` are conventions other tools (Karma, Selenium Manager) already
|
|
58
|
+
set in CI images and local environments for their own purposes. If one of
|
|
59
|
+
those happens to already be exported in your environment, craftdriver will
|
|
60
|
+
start forwarding it as of this version — a behavior change you didn't ask
|
|
61
|
+
craftdriver for, even though you didn't touch any craftdriver config either.
|
|
62
|
+
If you don't want that, set `CRAFTDRIVER_OFFLINE`-style pinning instead, or
|
|
63
|
+
simply don't rely on those four var names for anything craftdriver-adjacent.
|
|
64
|
+
|
|
65
|
+
| Step | Source |
|
|
66
|
+
|---|---|
|
|
67
|
+
| 1 | `browserPath` option in `Browser.launch()` |
|
|
68
|
+
| 2 | `CRAFTDRIVER_CHROME_PATH` / `CRAFTDRIVER_FIREFOX_PATH` env var |
|
|
69
|
+
| 3 | `CRAFTDRIVER_BROWSER_PATH` env var (generic fallback for either browser) |
|
|
70
|
+
| 4 | `CHROME_BIN` / `FIREFOX_BIN` env var (common CI/tooling convention, e.g. Karma) |
|
|
71
|
+
| 5 | `SE_CHROME_PATH` / `SE_FIREFOX_PATH` env var (Selenium Manager convention) |
|
|
72
|
+
|
|
73
|
+
Each step is validated (the file must exist) before use; an invalid path falls
|
|
74
|
+
through to the next step rather than being forwarded to the driver as-is (and
|
|
75
|
+
logs a `[craftdriver]` note to stderr when it does, so a typo doesn't silently
|
|
76
|
+
launch the wrong browser).
|
|
77
|
+
|
|
78
|
+
`CRAFTDRIVER_BROWSER_PATH` (step 3) is forwarded verbatim regardless of
|
|
79
|
+
whether the browser being launched is Chrome or Firefox — it's a single path,
|
|
80
|
+
so it can only ever be correct for one of them. Don't set it in a process or
|
|
81
|
+
CI job that launches both browsers; use the browser-specific `CRAFTDRIVER_CHROME_PATH`
|
|
82
|
+
/ `CRAFTDRIVER_FIREFOX_PATH` instead.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Launch a custom Chromium build
|
|
86
|
+
const browser = await Browser.launch({
|
|
87
|
+
browserName: 'chrome',
|
|
88
|
+
browserPath: '/opt/chromium-custom/chrome',
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Equivalent via env var — no code change needed
|
|
94
|
+
CRAFTDRIVER_CHROME_PATH=/opt/chromium-custom/chrome npm test
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If chromedriver itself also has to be auto-downloaded (no driver pinned
|
|
98
|
+
anywhere in the chain above), its version is detected from *this* binary
|
|
99
|
+
instead of probing for system Chrome — a custom Chromium build's version
|
|
100
|
+
routinely doesn't match system Chrome, so using the right binary for version
|
|
101
|
+
detection avoids downloading a mismatched chromedriver. Firefox has no
|
|
102
|
+
equivalent concern: geckodriver's own download isn't gated on the Firefox
|
|
103
|
+
version.
|
|
104
|
+
|
|
105
|
+
Version detection runs the binary with `--version` and parses its output, the
|
|
106
|
+
same way craftdriver already probes system Chrome/Firefox — so `browserPath`
|
|
107
|
+
is supported for Chrome/Chromium and Firefox builds only. Anything that
|
|
108
|
+
doesn't answer `--version` the way those browsers do (Electron apps and other
|
|
109
|
+
Chromium embedders) is out of scope for now; point `CRAFTDRIVER_CHROMEDRIVER_PATH`
|
|
110
|
+
at a pinned driver instead of relying on auto-detection in that case.
|
|
111
|
+
|
|
30
112
|
## Environment variables
|
|
31
113
|
|
|
32
114
|
| Variable | Description | Default |
|
|
@@ -34,6 +116,9 @@ the cache.
|
|
|
34
116
|
| `CRAFTDRIVER_CHROMEDRIVER_PATH` | Absolute path to a chromedriver binary | — |
|
|
35
117
|
| `CRAFTDRIVER_GECKODRIVER_PATH` | Absolute path to a geckodriver binary | — |
|
|
36
118
|
| `CRAFTDRIVER_DRIVER_PATH` | Generic fallback path (either browser) | — |
|
|
119
|
+
| `CRAFTDRIVER_CHROME_PATH` | Absolute path to a Chrome/Chromium binary — see [Browser Binary Configuration](#browser-binary-configuration) | — |
|
|
120
|
+
| `CRAFTDRIVER_FIREFOX_PATH` | Absolute path to a Firefox binary | — |
|
|
121
|
+
| `CRAFTDRIVER_BROWSER_PATH` | Generic browser-binary fallback path (either browser) | — |
|
|
37
122
|
| `CRAFTDRIVER_CACHE_DIR` | Directory for cached driver downloads | `~/.cache/craftdriver` |
|
|
38
123
|
| `CRAFTDRIVER_OFFLINE` | Set to `1` to disable all network calls | — |
|
|
39
124
|
| `CRAFTDRIVER_DRIVER_TTL` | Driver-resolution cache lifetime, in seconds (both browsers). `0` disables the cache | `86400` (24 h) |
|
|
@@ -115,6 +200,20 @@ but the direction holds):
|
|
|
115
200
|
|
|
116
201
|
Run it yourself with `npm run bench -- launch-critical-path`.
|
|
117
202
|
|
|
203
|
+
A separate micro-benchmark (`tests/perf/driver-resolution.perf.ts`) isolates
|
|
204
|
+
just the CI-provided driver directory step from the rest of the resolution
|
|
205
|
+
chain — `resolveChromeDriver()` alone, cache isolated so both paths are
|
|
206
|
+
measured cold:
|
|
207
|
+
|
|
208
|
+
| Scenario | Time |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `CHROMEWEBDRIVER` set (new) | ~0ms (an `fs.existsSync` check) |
|
|
211
|
+
| PATH-probe + Chrome-version-detect fallback (old) | ~360ms |
|
|
212
|
+
|
|
213
|
+
That's the cost every cold `Browser.launch()` used to pay once per process on
|
|
214
|
+
a GitHub Actions runner before this step existed — now skipped entirely. Run
|
|
215
|
+
it yourself with `npm run bench -- driver-resolution`.
|
|
216
|
+
|
|
118
217
|
### Browser startup flags (advanced, opt-in)
|
|
119
218
|
|
|
120
219
|
craftdriver launches the browser with **no performance flags of its own** — it
|
package/docs/element-api.md
CHANGED
|
@@ -9,8 +9,10 @@ The `ElementHandle` provides methods for interacting with individual DOM element
|
|
|
9
9
|
const element = browser.find('#my-element');
|
|
10
10
|
const button = browser.find(By.text('Submit'));
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
12
|
+
// Element-scoped operations
|
|
13
|
+
const input = browser.find('#input');
|
|
14
|
+
await input.fill('text');
|
|
15
|
+
await input.press('Enter');
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
> **Tip:** Use `browser.locator(selector)` instead of `find()` when you need
|
|
@@ -99,6 +101,14 @@ Get the current value of an input element.
|
|
|
99
101
|
const inputValue = await browser.find('#email').value();
|
|
100
102
|
```
|
|
101
103
|
|
|
104
|
+
### tagName()
|
|
105
|
+
|
|
106
|
+
Get the element tag name.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const tag = await browser.find('#submit').tagName(); // "button"
|
|
110
|
+
```
|
|
111
|
+
|
|
102
112
|
### getAttribute(name)
|
|
103
113
|
|
|
104
114
|
Get an attribute value from the element.
|
|
@@ -166,14 +176,35 @@ await browser.find('#message').expect().toHaveText('Success');
|
|
|
166
176
|
await browser.find('#input').expect().toHaveValue('test@example.com');
|
|
167
177
|
```
|
|
168
178
|
|
|
169
|
-
|
|
179
|
+
### setInputFiles(filePaths, options?)
|
|
180
|
+
|
|
181
|
+
Set files on an `<input type="file">`.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
await browser.find('#avatar').setInputFiles('/absolute/path/avatar.png');
|
|
185
|
+
await browser.find('#photos').setInputFiles([
|
|
186
|
+
'/absolute/path/one.jpg',
|
|
187
|
+
'/absolute/path/two.jpg',
|
|
188
|
+
]);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### evaluate(fn, ...args)
|
|
170
192
|
|
|
171
|
-
|
|
193
|
+
Run JavaScript with this element as the first argument.
|
|
172
194
|
|
|
173
195
|
```typescript
|
|
174
|
-
await browser.find('#
|
|
196
|
+
const tag = await browser.find('#submit').evaluate(el => el.tagName.toLowerCase());
|
|
197
|
+
const active = await browser
|
|
198
|
+
.find('#submit')
|
|
199
|
+
.evaluate((el, cls) => el.classList.contains(cls), 'active');
|
|
200
|
+
```
|
|
175
201
|
|
|
176
|
-
|
|
202
|
+
### a11y
|
|
203
|
+
|
|
204
|
+
Run an accessibility audit scoped to this element and its descendants.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const result = await browser.find('#checkout').a11y.audit();
|
|
177
208
|
```
|
|
178
209
|
|
|
179
210
|
## Examples
|
package/docs/emulation.md
CHANGED
|
@@ -65,7 +65,9 @@ JavaScript-only patch.
|
|
|
65
65
|
await browser.emulate({ colorScheme: 'dark' });
|
|
66
66
|
await browser.navigateTo('https://app.example.com');
|
|
67
67
|
|
|
68
|
-
const bg = await browser.
|
|
68
|
+
const bg = await browser.evaluate(() =>
|
|
69
|
+
getComputedStyle(document.body).backgroundColor
|
|
70
|
+
);
|
|
69
71
|
expect(bg).toBe('rgb(17, 17, 17)');
|
|
70
72
|
```
|
|
71
73
|
|
|
@@ -108,7 +110,9 @@ Confirm animations are actually suppressed for users who request it.
|
|
|
108
110
|
await browser.emulate({ reducedMotion: 'reduce' });
|
|
109
111
|
await browser.navigateTo('https://app.example.com');
|
|
110
112
|
|
|
111
|
-
const anim = await browser.
|
|
113
|
+
const anim = await browser.evaluate(() =>
|
|
114
|
+
getComputedStyle(document.querySelector('.hero')!).animationName
|
|
115
|
+
);
|
|
112
116
|
expect(anim).toBe('none');
|
|
113
117
|
```
|
|
114
118
|
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
title: CraftDriver
|
|
4
|
+
titleTemplate: false
|
|
5
|
+
hero:
|
|
6
|
+
name: CraftDriver
|
|
7
|
+
text: Crafted browser automation for Node.js.
|
|
8
|
+
tagline: Playwright-style ergonomics. WebDriver standards. AI friendly.
|
|
9
|
+
image:
|
|
10
|
+
src: /craftdriver-mark.svg
|
|
11
|
+
alt: CraftDriver
|
|
12
|
+
actions:
|
|
13
|
+
- theme: brand
|
|
14
|
+
text: Get Started
|
|
15
|
+
link: /getting-started
|
|
16
|
+
- theme: alt
|
|
17
|
+
text: API Reference
|
|
18
|
+
link: /api-reference
|
|
19
|
+
- theme: alt
|
|
20
|
+
text: AI Agent Guide
|
|
21
|
+
link: /agents
|
|
22
|
+
features:
|
|
23
|
+
- title: Control network traffic
|
|
24
|
+
details: Mock APIs, block noisy requests, intercept calls, and wait for real requests or responses.
|
|
25
|
+
- title: Reuse browser state
|
|
26
|
+
details: Save cookies and localStorage, launch already signed in, and isolate users with browser contexts.
|
|
27
|
+
- title: Test time and quality
|
|
28
|
+
details: Use virtual time, axe-core accessibility checks, traces, screenshots, console logs, and JS error capture.
|
|
29
|
+
- title: Agent-friendly
|
|
30
|
+
details: CLI, MCP server, and assistant rules are there when you want coding agents to drive the browser too.
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## One Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install craftdriver --save-dev
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Browser } from 'craftdriver';
|
|
41
|
+
|
|
42
|
+
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
43
|
+
|
|
44
|
+
await browser.navigateTo('https://example.com/login');
|
|
45
|
+
await browser.getByLabel('Username').fill('alice');
|
|
46
|
+
await browser.getByLabel('Password').fill('hunter2');
|
|
47
|
+
await browser.getByRole('button', { name: 'Sign in' }).click();
|
|
48
|
+
await browser.expect('#result').toHaveText('Welcome alice');
|
|
49
|
+
|
|
50
|
+
await browser.quit();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Choose Your Path
|
|
54
|
+
|
|
55
|
+
| You want to... | Start |
|
|
56
|
+
| --------------------------------- | --------------------------------------- |
|
|
57
|
+
| Write browser automation | [Getting Started](./getting-started.md) |
|
|
58
|
+
| Give an AI coding agent a browser | [AI Agent Guide](./agents.md) |
|
|
59
|
+
|
|
60
|
+
## Good Stuff To Read Next
|
|
61
|
+
|
|
62
|
+
- [Zero-Config Drivers](./zero-config-drivers.md) explains driver resolution and caching.
|
|
63
|
+
- [WebDriver Standards](./standards.md) explains how Classic and BiDi work together.
|
|
64
|
+
- [Recipes](./recipes.md) collects KB-style patterns for common real-world workflows.
|
package/docs/keyboard-mouse.md
CHANGED
|
@@ -48,6 +48,15 @@ Release a held key.
|
|
|
48
48
|
await browser.keyboard.up('Shift');
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
### chord(...keys)
|
|
52
|
+
|
|
53
|
+
Press a key combination.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
await browser.keyboard.chord('Control', 'a');
|
|
57
|
+
await browser.keyboard.chord('Shift', 'Tab');
|
|
58
|
+
```
|
|
59
|
+
|
|
51
60
|
### Key Combinations
|
|
52
61
|
|
|
53
62
|
Combine `down()` and `up()` for modifier key combinations:
|
|
@@ -129,23 +138,25 @@ await browser.mouse.move({ x: 150, y: 300 });
|
|
|
129
138
|
await browser.mouse.move('#target');
|
|
130
139
|
```
|
|
131
140
|
|
|
132
|
-
### down()
|
|
141
|
+
### down(button?)
|
|
133
142
|
|
|
134
143
|
Press the mouse button.
|
|
135
144
|
|
|
136
145
|
```typescript
|
|
137
146
|
await browser.mouse.down();
|
|
147
|
+
await browser.mouse.down('right');
|
|
138
148
|
```
|
|
139
149
|
|
|
140
|
-
### up()
|
|
150
|
+
### up(button?)
|
|
141
151
|
|
|
142
152
|
Release the mouse button.
|
|
143
153
|
|
|
144
154
|
```typescript
|
|
145
155
|
await browser.mouse.up();
|
|
156
|
+
await browser.mouse.up('right');
|
|
146
157
|
```
|
|
147
158
|
|
|
148
|
-
### wheel(deltaX, deltaY)
|
|
159
|
+
### wheel(deltaX, deltaY, target?)
|
|
149
160
|
|
|
150
161
|
Scroll the page.
|
|
151
162
|
|
|
@@ -158,6 +169,9 @@ await browser.mouse.wheel(0, -100);
|
|
|
158
169
|
|
|
159
170
|
// Scroll right
|
|
160
171
|
await browser.mouse.wheel(100, 0);
|
|
172
|
+
|
|
173
|
+
// Scroll while targeting an element
|
|
174
|
+
await browser.mouse.wheel(0, 300, '#scroll-panel');
|
|
161
175
|
```
|
|
162
176
|
|
|
163
177
|
### dragAndDrop(from, to)
|
|
@@ -194,14 +208,14 @@ await browser.mouse.dragAndDrop({ x: startX, y: startY }, { x: endX, y: endY });
|
|
|
194
208
|
|
|
195
209
|
```typescript
|
|
196
210
|
// Move to start position
|
|
197
|
-
await browser.mouse.move(100, 100);
|
|
211
|
+
await browser.mouse.move({ x: 100, y: 100 });
|
|
198
212
|
await browser.mouse.down();
|
|
199
213
|
|
|
200
214
|
// Draw a line
|
|
201
|
-
await browser.mouse.move(200, 100);
|
|
202
|
-
await browser.mouse.move(200, 200);
|
|
203
|
-
await browser.mouse.move(100, 200);
|
|
204
|
-
await browser.mouse.move(100, 100);
|
|
215
|
+
await browser.mouse.move({ x: 200, y: 100 });
|
|
216
|
+
await browser.mouse.move({ x: 200, y: 200 });
|
|
217
|
+
await browser.mouse.move({ x: 100, y: 200 });
|
|
218
|
+
await browser.mouse.move({ x: 100, y: 100 });
|
|
205
219
|
|
|
206
220
|
await browser.mouse.up();
|
|
207
221
|
```
|
|
@@ -227,10 +241,14 @@ For element-scoped key presses, use the `press()` method on ElementHandle:
|
|
|
227
241
|
|
|
228
242
|
```typescript
|
|
229
243
|
// Press Enter in a specific input
|
|
230
|
-
|
|
244
|
+
const search = browser.find('#search');
|
|
245
|
+
await search.fill('query');
|
|
246
|
+
await search.press('Enter');
|
|
231
247
|
|
|
232
248
|
// Tab out of a field
|
|
233
|
-
|
|
249
|
+
const username = browser.find('#username');
|
|
250
|
+
await username.fill('user');
|
|
251
|
+
await username.press('Tab');
|
|
234
252
|
```
|
|
235
253
|
|
|
236
254
|
This is often more convenient than using the global keyboard API.
|
package/docs/mobile-emulation.md
CHANGED
|
@@ -163,9 +163,9 @@ await browser.click('#mobile-menu-button');
|
|
|
163
163
|
await browser.expect('#mobile-menu').toBeVisible();
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
### Combine
|
|
166
|
+
### Combine With Network Mocking And Logs
|
|
167
167
|
|
|
168
|
-
Mobile emulation works alongside
|
|
168
|
+
Mobile emulation works alongside network mocking and browser log capture:
|
|
169
169
|
|
|
170
170
|
```typescript
|
|
171
171
|
const browser = await Browser.launch({
|
|
@@ -186,6 +186,9 @@ const errors = browser.logs.getErrors();
|
|
|
186
186
|
expect(errors).toHaveLength(0);
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
Learn more in [Network Mocking And Request Waiting](./network.md) and
|
|
190
|
+
[Console Logs And JavaScript Errors](./browser-logs.md).
|
|
191
|
+
|
|
189
192
|
## Limitations
|
|
190
193
|
|
|
191
194
|
- **Chrome/Chromium only**: Mobile emulation uses Chrome-specific capabilities
|
package/docs/network.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Network Mocking And Request Waiting
|
|
2
|
+
|
|
3
|
+
Use CraftDriver network tools when a test needs to control or observe HTTP traffic:
|
|
4
|
+
|
|
5
|
+
- mock an API response without changing the backend
|
|
6
|
+
- block analytics, ads, or flaky third-party services
|
|
7
|
+
- add headers such as auth tokens or test markers
|
|
8
|
+
- wait for the request or response triggered by a click
|
|
9
|
+
- assert that the app sent the right method, URL, or headers
|
|
10
|
+
|
|
11
|
+
> **Requires the default browser event connection.** Network interception and
|
|
12
|
+
> request/response waiting require `enableBiDi: true` (the default) and a
|
|
13
|
+
> browser that supports WebDriver BiDi. If you launch with
|
|
14
|
+
> `enableBiDi: false`, these methods throw a clear `requires BiDi` error.
|
|
15
|
+
|
|
16
|
+
## Browser Support
|
|
17
|
+
|
|
18
|
+
CraftDriver supports these network APIs on Chrome, Chromium, and Firefox.
|
|
19
|
+
|
|
20
|
+
## Quick Mock
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
await browser.network.mock('**/api/users', {
|
|
24
|
+
status: 200,
|
|
25
|
+
body: {
|
|
26
|
+
users: [{ id: 1, name: 'Test User' }],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await browser.navigateTo('https://example.com/dashboard');
|
|
31
|
+
await browser.expect('#users').toContainText('Test User');
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The pattern uses URL globs. `**/api/users` matches that path on any host.
|
|
35
|
+
|
|
36
|
+
## Common Tasks
|
|
37
|
+
|
|
38
|
+
| Goal | API |
|
|
39
|
+
| ---- | --- |
|
|
40
|
+
| Return a fake response | `browser.network.mock(pattern, response)` |
|
|
41
|
+
| Block matching requests | `browser.network.block(pattern)` |
|
|
42
|
+
| Dynamically inspect and respond | `browser.network.intercept(pattern, handler)` |
|
|
43
|
+
| Remove one route | `browser.network.removeIntercept(id)` |
|
|
44
|
+
| Remove every route | `browser.network.removeAllIntercepts()` |
|
|
45
|
+
| Add headers to outgoing requests | `browser.network.setExtraHeaders(headers)` |
|
|
46
|
+
| Bypass or restore browser cache | `browser.network.setCacheBehavior(behavior)` |
|
|
47
|
+
| Wait until traffic settles | `browser.network.waitForNetworkIdle(opts?)` |
|
|
48
|
+
| Wait for one request | `browser.waitForRequest(patternOrPredicate, opts?)` |
|
|
49
|
+
| Wait for one response | `browser.waitForResponse(patternOrPredicate, opts?)` |
|
|
50
|
+
|
|
51
|
+
## Mock Responses
|
|
52
|
+
|
|
53
|
+
### Static response
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
await browser.network.mock('**/api/login', {
|
|
57
|
+
status: 401,
|
|
58
|
+
body: { error: 'Invalid credentials' },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await browser.getByLabel('Username').fill('baduser');
|
|
62
|
+
await browser.getByLabel('Password').fill('wrongpass');
|
|
63
|
+
await browser.getByRole('button', { name: 'Sign in' }).click();
|
|
64
|
+
|
|
65
|
+
await browser.expect('#error').toHaveText('Invalid credentials');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Dynamic response
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
await browser.network.mock('**/api/items/*', (request) => {
|
|
72
|
+
const id = request.url.split('/').pop();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
status: 200,
|
|
76
|
+
body: { id, name: `Item ${id}`, price: 9.99 },
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Block Requests
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
await browser.network.block('**/analytics/**');
|
|
85
|
+
await browser.network.block('**/tracking/**');
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This is useful when third-party services make tests slower or less predictable.
|
|
89
|
+
|
|
90
|
+
## Add Headers
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await browser.network.setExtraHeaders({
|
|
94
|
+
'X-Test-Mode': 'true',
|
|
95
|
+
Authorization: 'Bearer test-token',
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Extra headers are applied to matching browser requests after they are configured.
|
|
100
|
+
|
|
101
|
+
## Control Cache
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
await browser.network.setCacheBehavior('bypass');
|
|
105
|
+
|
|
106
|
+
// Later, restore normal browser caching.
|
|
107
|
+
await browser.network.setCacheBehavior('default');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Intercept Requests
|
|
111
|
+
|
|
112
|
+
Use `intercept()` when the response depends on request details or asynchronous
|
|
113
|
+
test setup.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const seen: string[] = [];
|
|
117
|
+
|
|
118
|
+
const interceptId = await browser.network.intercept('**/api/**', async (request) => {
|
|
119
|
+
seen.push(`${request.method} ${request.url}`);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
status: 200,
|
|
123
|
+
body: { intercepted: true, originalUrl: request.url },
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Later, remove only this intercept.
|
|
128
|
+
await browser.network.removeIntercept(interceptId);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can also delay a response to test loading UI:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
await browser.network.intercept('**/api/**', async () => {
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
136
|
+
return { status: 200, body: { data: 'delayed response' } };
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await browser.getByRole('button', { name: 'Load data' }).click();
|
|
140
|
+
await browser.expect('#loading-spinner').toBeVisible();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Wait For Real Traffic
|
|
144
|
+
|
|
145
|
+
`browser.waitForRequest()` and `browser.waitForResponse()` observe real network
|
|
146
|
+
traffic without mocking it. Register the wait before the action that triggers the
|
|
147
|
+
request. `Promise.all` is the safest pattern:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const [response] = await Promise.all([
|
|
151
|
+
browser.waitForResponse('**/api/users'),
|
|
152
|
+
browser.getByRole('button', { name: 'Load users' }).click(),
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
expect(response.status).toBe(200);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Both methods accept a URL glob or a predicate:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const [response] = await Promise.all([
|
|
162
|
+
browser.waitForResponse((res) => {
|
|
163
|
+
return res.url.includes('/api/users') && res.status === 200;
|
|
164
|
+
}),
|
|
165
|
+
browser.getByRole('button', { name: 'Load users' }).click(),
|
|
166
|
+
]);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Response shape
|
|
170
|
+
|
|
171
|
+
| Property | Type | Description |
|
|
172
|
+
| -------- | ---- | ----------- |
|
|
173
|
+
| `url` | `string` | Full request URL |
|
|
174
|
+
| `status` | `number` | HTTP status code |
|
|
175
|
+
| `statusText` | `string` | Status text such as `"OK"` |
|
|
176
|
+
| `headers` | `Record<string, string>` | Response headers |
|
|
177
|
+
| `mimeType` | `string` | Response MIME type |
|
|
178
|
+
| `fromCache` | `boolean` | Whether the browser served it from cache |
|
|
179
|
+
| `request` | `{ id, url, method, headers }` | Matching request info |
|
|
180
|
+
|
|
181
|
+
### Request shape
|
|
182
|
+
|
|
183
|
+
| Property | Type | Description |
|
|
184
|
+
| -------- | ---- | ----------- |
|
|
185
|
+
| `id` | `string` | Browser request id |
|
|
186
|
+
| `url` | `string` | Full URL |
|
|
187
|
+
| `method` | `string` | Request method, such as `"GET"` or `"POST"` |
|
|
188
|
+
| `headers` | `Record<string, string>` | Request headers |
|
|
189
|
+
|
|
190
|
+
## Timeouts
|
|
191
|
+
|
|
192
|
+
Both wait methods accept `{ timeout?: number }`. The default is the browser
|
|
193
|
+
navigation timeout, 30 seconds.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
await browser.waitForResponse('**/api/users', { timeout: 10_000 });
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
On timeout, CraftDriver throws a clear error:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
waitForResponse("**/api/users") timed out after 30000ms
|
|
203
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">CraftDriver mark</title>
|
|
3
|
+
<desc id="desc">A browser window with a command prompt and connection nodes.</desc>
|
|
4
|
+
<rect x="12" y="24" width="136" height="112" rx="18" fill="#111827"/>
|
|
5
|
+
<rect x="24" y="42" width="112" height="78" rx="10" fill="#f8fafc"/>
|
|
6
|
+
<circle cx="34" cy="34" r="5" fill="#f97316"/>
|
|
7
|
+
<circle cx="50" cy="34" r="5" fill="#f59e0b"/>
|
|
8
|
+
<circle cx="66" cy="34" r="5" fill="#22c55e"/>
|
|
9
|
+
<path d="M44 78L60 64L44 50" stroke="#b45309" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
|
+
<path d="M70 82H104" stroke="#2563eb" stroke-width="8" stroke-linecap="round"/>
|
|
11
|
+
<path d="M91 60H112C121.941 60 130 68.0589 130 78V78C130 87.9411 121.941 96 112 96H91" stroke="#94a3b8" stroke-width="6" stroke-linecap="round"/>
|
|
12
|
+
<circle cx="88" cy="60" r="8" fill="#2563eb"/>
|
|
13
|
+
<circle cx="88" cy="96" r="8" fill="#f59e0b"/>
|
|
14
|
+
<circle cx="118" cy="78" r="8" fill="#22c55e"/>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg width="1200" height="630" viewBox="0 0 1200 630" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">CraftDriver social card</title>
|
|
3
|
+
<desc id="desc">Standards-based browser automation for Node.js, tests, scripts, and AI agents.</desc>
|
|
4
|
+
<rect width="1200" height="630" fill="#0f172a"/>
|
|
5
|
+
<rect x="72" y="72" width="1056" height="486" rx="32" fill="#111827" stroke="#334155" stroke-width="2"/>
|
|
6
|
+
<rect x="116" y="122" width="396" height="276" rx="24" fill="#f8fafc"/>
|
|
7
|
+
<rect x="116" y="122" width="396" height="54" rx="24" fill="#1f2937"/>
|
|
8
|
+
<circle cx="154" cy="149" r="9" fill="#f97316"/>
|
|
9
|
+
<circle cx="184" cy="149" r="9" fill="#f59e0b"/>
|
|
10
|
+
<circle cx="214" cy="149" r="9" fill="#22c55e"/>
|
|
11
|
+
<path d="M184 274L230 232L184 190" stroke="#b45309" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
|
|
12
|
+
<path d="M264 286H382" stroke="#2563eb" stroke-width="18" stroke-linecap="round"/>
|
|
13
|
+
<path d="M349 210H414C446.585 210 473 236.415 473 269V269C473 301.585 446.585 328 414 328H349" stroke="#94a3b8" stroke-width="12" stroke-linecap="round"/>
|
|
14
|
+
<circle cx="346" cy="210" r="17" fill="#2563eb"/>
|
|
15
|
+
<circle cx="346" cy="328" r="17" fill="#f59e0b"/>
|
|
16
|
+
<circle cx="432" cy="269" r="17" fill="#22c55e"/>
|
|
17
|
+
<text x="574" y="212" fill="#f8fafc" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="82" font-weight="800">CraftDriver</text>
|
|
18
|
+
<text x="578" y="281" fill="#f59e0b" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="34" font-weight="700">WebDriver automation for humans and AI agents</text>
|
|
19
|
+
<text x="578" y="356" fill="#cbd5e1" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="30">Playwright-style ergonomics. WebDriver standards. AI friendly.</text>
|
|
20
|
+
<text x="578" y="408" fill="#cbd5e1" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="30">Library API, CLI, MCP server, and packaged agent skills.</text>
|
|
21
|
+
<rect x="578" y="462" width="156" height="48" rx="24" fill="#b45309"/>
|
|
22
|
+
<text x="614" y="495" fill="#fff7ed" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="22" font-weight="700">Node.js</text>
|
|
23
|
+
<rect x="754" y="462" width="178" height="48" rx="24" fill="#1d4ed8"/>
|
|
24
|
+
<text x="790" y="495" fill="#eff6ff" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="22" font-weight="700">WebDriver</text>
|
|
25
|
+
<rect x="952" y="462" width="102" height="48" rx="24" fill="#15803d"/>
|
|
26
|
+
<text x="984" y="495" fill="#f0fdf4" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="22" font-weight="700">MCP</text>
|
|
27
|
+
</svg>
|