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.
- package/CHANGELOG.md +104 -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 +23 -6
- 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,673 @@
|
|
|
1
|
+
# Browser API
|
|
2
|
+
|
|
3
|
+
The `Browser` class is the main entry point for browser automation.
|
|
4
|
+
|
|
5
|
+
> **Heads-up.** `browser.click()`, `browser.find()`, `browser.evaluate()`
|
|
6
|
+
> and the other DOM-touching shortcuts target the focused page in
|
|
7
|
+
> `browser.defaultContext` — i.e. `(await browser.activePage()).X(...)`.
|
|
8
|
+
> They never reach into pages inside a context created by
|
|
9
|
+
> `browser.newContext()`. See
|
|
10
|
+
> [Getting Started — `browser.X()` vs `page.X()`](./getting-started.md#browserx-vs-pagex--when-to-use-which).
|
|
11
|
+
|
|
12
|
+
## Launching
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { Browser } from 'craftdriver';
|
|
16
|
+
|
|
17
|
+
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
18
|
+
|
|
19
|
+
// With pre-loaded session state
|
|
20
|
+
const browser = await Browser.launch({
|
|
21
|
+
browserName: 'chrome',
|
|
22
|
+
storageState: './auth.json',
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Launch options
|
|
27
|
+
|
|
28
|
+
| Option | Type | Default | Description |
|
|
29
|
+
|--------|------|---------|-------------|
|
|
30
|
+
| `browserName` | `'chrome' \| 'chromium' \| 'firefox'` | `'chrome'` | Browser to launch |
|
|
31
|
+
| `enableBiDi` | `boolean` | `true` | Use WebDriver BiDi. Set `false` only when running against a browser that does not support it. |
|
|
32
|
+
| `storageState` | `string` | — | Path to a session-state JSON file to load on startup |
|
|
33
|
+
| `mobileEmulation` | `MobileEmulation \| DeviceName` | — | Mobile device emulation settings (Chrome/Chromium only) |
|
|
34
|
+
| `downloadsDir` | `string` | temp dir | Directory where downloaded files are saved |
|
|
35
|
+
|
|
36
|
+
## Navigation
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Navigate to URL — waits for the page load event (default)
|
|
40
|
+
await browser.navigateTo('https://example.com');
|
|
41
|
+
|
|
42
|
+
// Wait only until DOMContentLoaded (faster — skips images/fonts)
|
|
43
|
+
await browser.navigateTo('https://example.com', { waitUntil: 'domcontentloaded' });
|
|
44
|
+
|
|
45
|
+
// Wait until no requests are in-flight for 500 ms after load
|
|
46
|
+
await browser.navigateTo('https://example.com', { waitUntil: 'networkidle' });
|
|
47
|
+
|
|
48
|
+
// Fire-and-forget — does not wait for any load event
|
|
49
|
+
await browser.navigateTo('https://example.com', { waitUntil: 'none' });
|
|
50
|
+
|
|
51
|
+
// Get current URL
|
|
52
|
+
const url = await browser.url();
|
|
53
|
+
|
|
54
|
+
// Get page title
|
|
55
|
+
const title = await browser.title();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `waitForLoadState(state?, opts?)`
|
|
59
|
+
|
|
60
|
+
Wait for the page to reach a given load state after an action that triggers navigation
|
|
61
|
+
(e.g. clicking a link or submitting a form).
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// After an action that causes navigation:
|
|
65
|
+
await browser.click('#submit');
|
|
66
|
+
await browser.waitForLoadState(); // default: 'load'
|
|
67
|
+
await browser.waitForLoadState('domcontentloaded');
|
|
68
|
+
await browser.waitForLoadState('networkidle', { timeout: 10_000 });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| State | Description |
|
|
72
|
+
|-------|-------------|
|
|
73
|
+
| `'load'` | Page `load` event fired (default) |
|
|
74
|
+
| `'domcontentloaded'` | `DOMContentLoaded` fired — no waiting for images/fonts |
|
|
75
|
+
| `'networkidle'` | `load` + no in-flight requests for 500 ms |
|
|
76
|
+
|
|
77
|
+
Uses BiDi `browsingContext.load` / `browsingContext.domContentLoaded` events.
|
|
78
|
+
|
|
79
|
+
### History
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
await browser.goBack(); // history back
|
|
83
|
+
await browser.goForward(); // history forward
|
|
84
|
+
await browser.reload(); // reload the active page
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
All three proxy to the active page (`browser.activePage()`); use
|
|
88
|
+
`page.goBack()` / `page.goForward()` / `page.reload()` directly when
|
|
89
|
+
working with a specific tab.
|
|
90
|
+
|
|
91
|
+
### Page content
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Full document HTML (document.documentElement.outerHTML)
|
|
95
|
+
const html = await browser.content();
|
|
96
|
+
|
|
97
|
+
// Replace the document with synthetic HTML and wait for the load event
|
|
98
|
+
await browser.setContent('<!doctype html><h1>Hello</h1>');
|
|
99
|
+
|
|
100
|
+
// Faster — wait only for DOMContentLoaded
|
|
101
|
+
await browser.setContent(html, { waitUntil: 'domcontentloaded' });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Implementation: `setContent` navigates to a `data:text/html` URL so the
|
|
105
|
+
new document goes through a real navigation and load events fire.
|
|
106
|
+
|
|
107
|
+
### Viewport
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
await browser.setViewportSize({ width: 1280, height: 720 });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Uses BiDi `browsingContext.setViewport` to resize the **layout** viewport
|
|
114
|
+
without changing the OS window. In Classic mode it falls back to
|
|
115
|
+
`POST /session/{id}/window/rect`, which resizes the OS window — the
|
|
116
|
+
inner viewport may end up a few pixels smaller because of browser
|
|
117
|
+
chrome.
|
|
118
|
+
|
|
119
|
+
For full mobile emulation (DPR, user-agent, touch), pass
|
|
120
|
+
`mobileEmulation` to `Browser.launch` — see
|
|
121
|
+
[Mobile emulation](./mobile-emulation.md). `setViewportSize` only
|
|
122
|
+
changes the viewport box.
|
|
123
|
+
|
|
124
|
+
### Emulation
|
|
125
|
+
|
|
126
|
+
`browser.emulate({...})` overrides `prefers-color-scheme`,
|
|
127
|
+
`prefers-reduced-motion`, `forced-colors`, `navigator.language`,
|
|
128
|
+
`Intl.*` timezone, and `navigator.onLine` for the current session.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
await browser.emulate({
|
|
132
|
+
colorScheme: 'dark',
|
|
133
|
+
locale: 'de-DE',
|
|
134
|
+
timezoneId: 'Europe/Berlin',
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`locale` and `timezoneId` work on Chrome, Chromium and Firefox.
|
|
139
|
+
The CSS-media-feature fields and `offline` are Chromium-only — see
|
|
140
|
+
[Emulation](./emulation.md) for the full option table and use cases.
|
|
141
|
+
|
|
142
|
+
## Element Interaction
|
|
143
|
+
|
|
144
|
+
String selectors are CSS selectors. For semantic selectors, pass a `By.*`
|
|
145
|
+
locator or use `browser.getByRole()`, `browser.getByText()`,
|
|
146
|
+
`browser.getByLabel()`, and related helpers.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Click an element
|
|
150
|
+
await browser.click('#submit-btn');
|
|
151
|
+
await browser.click(By.text('Submit'));
|
|
152
|
+
|
|
153
|
+
// Fill text input
|
|
154
|
+
await browser.fill('#username', 'testuser');
|
|
155
|
+
|
|
156
|
+
// Clear input
|
|
157
|
+
await browser.clear('#search');
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `getValue(selector, opts?)`
|
|
161
|
+
|
|
162
|
+
Return the live `value` property of an `<input>`, `<textarea>`, or
|
|
163
|
+
`<select>`. Use this rather than `getAttribute('value')` — the
|
|
164
|
+
attribute reflects the *initial* value, the property reflects what the
|
|
165
|
+
user has typed.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const typed = await browser.getValue('#input');
|
|
169
|
+
const withTimeout = await browser.getValue('#input', { timeout: 1000 });
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Returns `string` (empty string if the property is null/undefined).
|
|
173
|
+
|
|
174
|
+
### `getAttribute(selector, name, opts?)`
|
|
175
|
+
|
|
176
|
+
Return the value of an HTML attribute, or `null` if the attribute is
|
|
177
|
+
not set. Pass a `By` locator or CSS string.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const href = await browser.getAttribute('a.link', 'href');
|
|
181
|
+
const role = await browser.getAttribute(By.testId('menu'), 'role');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Finding Elements
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Returns an ElementHandle for chained operations
|
|
188
|
+
const element = browser.find('#my-element');
|
|
189
|
+
|
|
190
|
+
// With By locators
|
|
191
|
+
const button = browser.find(By.text('Click me'));
|
|
192
|
+
const input = browser.find(By.placeholder('Enter email'));
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Playwright-style Locators
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// By role
|
|
199
|
+
browser.getByRole('button', { name: 'Submit' });
|
|
200
|
+
|
|
201
|
+
// By visible text
|
|
202
|
+
browser.getByText('Hello World');
|
|
203
|
+
browser.getByText('partial', { exact: false });
|
|
204
|
+
|
|
205
|
+
// By label
|
|
206
|
+
browser.getByLabel('Username');
|
|
207
|
+
|
|
208
|
+
// By placeholder
|
|
209
|
+
browser.getByPlaceholder('Enter email');
|
|
210
|
+
|
|
211
|
+
// By test ID
|
|
212
|
+
browser.getByTestId('submit-btn');
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Waiting
|
|
216
|
+
|
|
217
|
+
> **`attached` vs `visible`.** *Attached* means the element exists in
|
|
218
|
+
> the DOM — it may still be `display: none` or have zero size.
|
|
219
|
+
> *Visible* is stricter: attached **and** laid out with non-zero size
|
|
220
|
+
> and not `visibility: hidden`. *Hidden* and *detached* are the
|
|
221
|
+
> respective negatives. The default for `waitFor` is `'visible'`,
|
|
222
|
+
> which matches what users mean 95 % of the time.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Canonical: pick the state explicitly.
|
|
226
|
+
await browser.waitFor('#modal', { state: 'visible' });
|
|
227
|
+
await browser.waitFor('#loading', { state: 'hidden', timeout: 5000 });
|
|
228
|
+
await browser.waitFor('#dynamic', { state: 'attached' });
|
|
229
|
+
await browser.waitFor('#removed', { state: 'detached' });
|
|
230
|
+
|
|
231
|
+
// Shortcuts (one-liner wrappers):
|
|
232
|
+
await browser.waitForVisible('#modal');
|
|
233
|
+
await browser.waitForHidden('#loading');
|
|
234
|
+
await browser.waitForAttached('#dynamic-content');
|
|
235
|
+
await browser.waitForDetached('#removed-element');
|
|
236
|
+
|
|
237
|
+
// Pause for fixed time
|
|
238
|
+
await browser.pause(1000); // 1 second
|
|
239
|
+
|
|
240
|
+
// Wait for custom condition
|
|
241
|
+
await browser.waitFor(async () => {
|
|
242
|
+
const count = await browser.find('#items').text();
|
|
243
|
+
return parseInt(count) > 5;
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Assertions
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Assert text content
|
|
251
|
+
await browser.expect('#message').toHaveText('Success');
|
|
252
|
+
await browser.expect('#message').toContainText('Success');
|
|
253
|
+
|
|
254
|
+
// Assert visibility
|
|
255
|
+
await browser.expect('#modal').toBeVisible();
|
|
256
|
+
await browser.expect('#modal').not.toBeVisible();
|
|
257
|
+
|
|
258
|
+
// Assert attribute
|
|
259
|
+
await browser.expect('a').toHaveAttribute('href', '/home');
|
|
260
|
+
|
|
261
|
+
// Assert value
|
|
262
|
+
await browser.expect('#input').toHaveValue('test');
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Frames and Contexts
|
|
266
|
+
|
|
267
|
+
### Iframes
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { Browser } from 'craftdriver';
|
|
271
|
+
|
|
272
|
+
const browser = await Browser.launch();
|
|
273
|
+
await browser.navigateTo('https://example.com/page-with-iframe');
|
|
274
|
+
|
|
275
|
+
// Obtain a Frame scoped to the first matching iframe
|
|
276
|
+
const frame = await browser.frame('#stripe-card-frame');
|
|
277
|
+
|
|
278
|
+
// All element methods are scoped to the iframe
|
|
279
|
+
await frame.fill('#card-number', '4242 4242 4242 4242');
|
|
280
|
+
await frame.click('#pay-btn');
|
|
281
|
+
await frame.expect('#result').toContainText('success');
|
|
282
|
+
|
|
283
|
+
// Evaluate JavaScript inside the iframe
|
|
284
|
+
const iframeTitle = await frame.evaluate(() => document.title);
|
|
285
|
+
|
|
286
|
+
// Get all iframes on the page
|
|
287
|
+
const frames = await browser.frames();
|
|
288
|
+
console.log(`Page has ${frames.length} iframe(s)`);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Pages (tabs and popups)
|
|
292
|
+
|
|
293
|
+
A `Page` is a top-level browsing context — a tab or a popup window. Maps
|
|
294
|
+
1-to-1 onto the WebDriver BiDi `browsingContext` concept (the BiDi spec
|
|
295
|
+
calls them "top-level browsing contexts"; we call them pages because that
|
|
296
|
+
is what users mean).
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { Browser } from 'craftdriver';
|
|
300
|
+
|
|
301
|
+
const browser = await Browser.launch();
|
|
302
|
+
await browser.navigateTo('https://example.com/dashboard');
|
|
303
|
+
|
|
304
|
+
// List all open pages (tabs / windows)
|
|
305
|
+
const pages = await browser.pages();
|
|
306
|
+
console.log(`Open tabs: ${pages.length}`);
|
|
307
|
+
|
|
308
|
+
// Open a new tab proactively (BiDi-only)
|
|
309
|
+
const newTab = await browser.openPage({
|
|
310
|
+
url: 'https://example.com/help',
|
|
311
|
+
type: 'tab',
|
|
312
|
+
});
|
|
313
|
+
await newTab.waitForLoadState('load');
|
|
314
|
+
|
|
315
|
+
// Capture a popup opened by a user action
|
|
316
|
+
const popup = await browser.waitForPage(async () => {
|
|
317
|
+
await browser.click('#open-in-new-tab');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
await popup.waitForLoadState('load');
|
|
321
|
+
|
|
322
|
+
// Interact with the popup just like the main browser
|
|
323
|
+
const heading = await popup.find('h1').text();
|
|
324
|
+
console.log(heading);
|
|
325
|
+
|
|
326
|
+
await popup.evaluate(() => {
|
|
327
|
+
window.close();
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
> **Note.** `browser.openPage()` requires BiDi (the default). In Classic
|
|
332
|
+
> mode it throws — WebDriver Classic has no spec-level command for
|
|
333
|
+
> creating a top-level browsing context.
|
|
334
|
+
>
|
|
335
|
+
> For **isolated profiles** (cookies, localStorage, IndexedDB scoped to
|
|
336
|
+
> a single test user), see [Browser Contexts](./browser-context.md) —
|
|
337
|
+
> `browser.newContext()` returns a real `BrowserContext` backed by BiDi
|
|
338
|
+
> user contexts.
|
|
339
|
+
|
|
340
|
+
## Screenshots
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Capture viewport (default)
|
|
344
|
+
const buffer = await browser.screenshot();
|
|
345
|
+
|
|
346
|
+
// Capture the full scrollable document (BiDi)
|
|
347
|
+
const fullBuffer = await browser.screenshot({ fullPage: true });
|
|
348
|
+
|
|
349
|
+
// Capture specific element
|
|
350
|
+
const elementBuffer = await browser.screenshot({ selector: '#header' });
|
|
351
|
+
|
|
352
|
+
// Save to file
|
|
353
|
+
await browser.screenshot({ path: 'page.png' });
|
|
354
|
+
await browser.screenshot({ selector: '#header', path: 'element.png' });
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Session Management
|
|
358
|
+
|
|
359
|
+
See [Session Management](./session-management.md) for full details.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// Access storage manager
|
|
363
|
+
const storage = browser.storage;
|
|
364
|
+
|
|
365
|
+
// Save current session to file
|
|
366
|
+
await browser.saveState('./session.json');
|
|
367
|
+
|
|
368
|
+
// Load session from file
|
|
369
|
+
await browser.loadState('./session.json');
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Evaluate
|
|
373
|
+
|
|
374
|
+
Run JavaScript in the page and get the result back. Useful for reading app state,
|
|
375
|
+
asserting on globals, or triggering things the UI doesn't expose.
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// Function form — arguments are passed through
|
|
379
|
+
const title = await browser.evaluate(() => document.title);
|
|
380
|
+
const sum = await browser.evaluate((a, b) => a + b, 2, 3); // 5
|
|
381
|
+
|
|
382
|
+
// String form — write it like a function body
|
|
383
|
+
const href = await browser.evaluate('return location.href');
|
|
384
|
+
|
|
385
|
+
// On a specific element — receives the DOM node as the first argument
|
|
386
|
+
const tag = await browser.find('#btn').evaluate(el => el.tagName.toLowerCase());
|
|
387
|
+
const has = await browser.find('#btn').evaluate((el, cls) => el.classList.contains(cls), 'active');
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Only JSON-serializable return values are supported. Returning a DOM node or function
|
|
391
|
+
throws a clear error.
|
|
392
|
+
|
|
393
|
+
## Init scripts
|
|
394
|
+
|
|
395
|
+
Register a script that runs before any page script on every navigation:
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// Function form
|
|
399
|
+
const handle = await browser.addInitScript(() => {
|
|
400
|
+
window.__featureFlags = { darkMode: true };
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Or a script string
|
|
404
|
+
await browser.addInitScript(`window.__featureFlags = { darkMode: true };`);
|
|
405
|
+
|
|
406
|
+
// The script survives navigations automatically
|
|
407
|
+
await browser.navigateTo(url);
|
|
408
|
+
const flag = await browser.evaluate(() => window.__featureFlags.darkMode); // true
|
|
409
|
+
|
|
410
|
+
// Unregister when no longer needed
|
|
411
|
+
await handle.remove();
|
|
412
|
+
await browser.navigateTo(url);
|
|
413
|
+
// window.__featureFlags is now undefined
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
> Init scripts are scoped to the current browser session and survive navigations automatically.
|
|
417
|
+
|
|
418
|
+
## Files (upload & download)
|
|
419
|
+
|
|
420
|
+
### Upload
|
|
421
|
+
|
|
422
|
+
Set the value of an `<input type="file">` element:
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
const handle = browser.find('#file-input');
|
|
426
|
+
await handle.setInputFiles('/absolute/path/to/file.pdf');
|
|
427
|
+
|
|
428
|
+
// Multiple files (input must have the `multiple` attribute)
|
|
429
|
+
await handle.setInputFiles(['/path/to/a.pdf', '/path/to/b.pdf']);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Throws a clear error if the element is not an `<input type="file">`.
|
|
433
|
+
|
|
434
|
+
### Download
|
|
435
|
+
|
|
436
|
+
Pass a callback that triggers the download; `waitForDownload` resolves once
|
|
437
|
+
a file appears in the downloads directory:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const dl = await browser.waitForDownload(() => browser.click('#export-btn'));
|
|
441
|
+
|
|
442
|
+
console.log(dl.suggestedFilename); // 'report.csv'
|
|
443
|
+
console.log(dl.path); // absolute path on disk
|
|
444
|
+
|
|
445
|
+
// Copy to a known location
|
|
446
|
+
await dl.saveAs('/tmp/report.csv');
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Configure the directory at launch (defaults to an isolated temp directory):
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
const browser = await Browser.launch({
|
|
453
|
+
downloadsDir: '/tmp/my-test-downloads',
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Time Control
|
|
458
|
+
|
|
459
|
+
Control the clock the page sees — `Date.now()`, `new Date()`,
|
|
460
|
+
`performance.now()`, `setTimeout`, and `setInterval`.
|
|
461
|
+
|
|
462
|
+
See [Virtual Clock](./clock.md) for the full guide and method reference.
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// Freeze date for date-dependent rendering (no fake timers)
|
|
466
|
+
await browser.clock.setFixedTime('2026-06-15T23:59:00Z');
|
|
467
|
+
await browser.navigateTo('/billing');
|
|
468
|
+
await browser.expect('#trial-banner').toContainText('expires today');
|
|
469
|
+
|
|
470
|
+
// Full fake-timer installation — control every setTimeout/setInterval
|
|
471
|
+
await browser.clock.install({ time: '2026-01-01T09:00:00Z' });
|
|
472
|
+
await browser.navigateTo('/dashboard');
|
|
473
|
+
await browser.clock.fastForward('15:01'); // advance 15 min 1 s
|
|
474
|
+
await browser.expect('#login-modal').toBeVisible();
|
|
475
|
+
|
|
476
|
+
// Debounced input: advance exactly to the threshold
|
|
477
|
+
await browser.clock.install();
|
|
478
|
+
await browser.fill('#q', 'lap');
|
|
479
|
+
await browser.clock.tick(299); // debounce hasn't fired yet
|
|
480
|
+
await browser.clock.tick(2); // total 301 ms — fires exactly once
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Dialogs (alert / confirm / prompt)
|
|
484
|
+
|
|
485
|
+
> **Heads-up.** craftdriver makes unhandled dialogs **loud failures**
|
|
486
|
+
> rather than silently auto-dismissing them like Playwright does. The
|
|
487
|
+
> action that triggered the dialog will time out until something
|
|
488
|
+
> handles it. See [Dialogs](./dialogs.md) for the dedicated guide,
|
|
489
|
+
> including `waitForDialog()`, `onDialog()`, and the imperative
|
|
490
|
+
> `acceptDialog()` / `dismissDialog()` API.
|
|
491
|
+
|
|
492
|
+
Quick example:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
const [, dialog] = await Promise.all([
|
|
496
|
+
browser.click('#confirm-btn'),
|
|
497
|
+
browser.waitForDialog(),
|
|
498
|
+
]);
|
|
499
|
+
await dialog.accept();
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Cleanup
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
// Always quit the browser when done
|
|
506
|
+
await browser.quit();
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`quit()` closes the browser session and stops the WebDriver process. Use
|
|
510
|
+
it in a `finally` block (or your test runner's teardown hook) so it
|
|
511
|
+
runs even when the test throws:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
515
|
+
try {
|
|
516
|
+
// … your test …
|
|
517
|
+
} finally {
|
|
518
|
+
await browser.quit().catch(() => {
|
|
519
|
+
// The session may already be gone (browser crash, killed driver,
|
|
520
|
+
// network blip). Swallow — there is nothing useful left to clean
|
|
521
|
+
// up from the client side.
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### When `quit()` is not enough
|
|
527
|
+
|
|
528
|
+
`quit()` makes a best-effort `DELETE /session` call to the driver and
|
|
529
|
+
then kills the local driver process craftdriver spawned. It does **not**
|
|
530
|
+
cover:
|
|
531
|
+
|
|
532
|
+
- **The host process is killed** (`kill -9`, OS reboot, CI runner
|
|
533
|
+
cancellation). The driver and browser may linger as orphan processes.
|
|
534
|
+
Clean them up at the OS level (CI runners typically do this
|
|
535
|
+
automatically; on a dev machine `pkill chromedriver` /
|
|
536
|
+
`pkill geckodriver` is the easy fix).
|
|
537
|
+
- **Custom `ChromeService` / `FirefoxService` you own.** If you passed
|
|
538
|
+
one via `chromeService` / `firefoxService` and want to keep it for a
|
|
539
|
+
second `Browser.launch()` call, manage its lifetime yourself —
|
|
540
|
+
craftdriver only stops services it spawned.
|
|
541
|
+
- **Multiple browsers in one test.** Each instance must be quit
|
|
542
|
+
independently; there is no global registry.
|
|
543
|
+
|
|
544
|
+
## Advanced
|
|
545
|
+
|
|
546
|
+
### `isBiDiEnabled()`
|
|
547
|
+
|
|
548
|
+
Returns `true` if BiDi negotiation succeeded at launch. Use this to
|
|
549
|
+
guard code paths that depend on BiDi-only features (network mocking,
|
|
550
|
+
log capture, init scripts) when the same code may run against a
|
|
551
|
+
browser launched with `enableBiDi: false`.
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
if (browser.isBiDiEnabled()) {
|
|
555
|
+
await browser.network.block('**/analytics/**');
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### `actions()`
|
|
560
|
+
|
|
561
|
+
Returns a low-level W3C Actions builder for input sequences the
|
|
562
|
+
high-level `browser.keyboard` / `browser.mouse` APIs do not cover —
|
|
563
|
+
e.g. drag-and-drop with a held modifier, multi-pointer gestures, or
|
|
564
|
+
custom timing.
|
|
565
|
+
|
|
566
|
+
The builder is fluent and **all steps in a single chain are dispatched
|
|
567
|
+
in one HTTP `POST /session/{id}/actions` call**, so they execute as one
|
|
568
|
+
sequence on the browser side.
|
|
569
|
+
|
|
570
|
+
#### Example: shift-drag a marquee selection
|
|
571
|
+
|
|
572
|
+
The example page at `examples/mouse.html` includes an SVG canvas at
|
|
573
|
+
fixed coordinates. The snippet below holds **Shift**, drags from
|
|
574
|
+
`(100, 100)` to `(300, 300)`, then releases — modelling a "Shift-drag
|
|
575
|
+
to add to selection" interaction:
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
import { Browser, Key } from 'craftdriver';
|
|
579
|
+
|
|
580
|
+
const browser = await Browser.launch();
|
|
581
|
+
await browser.navigateTo('http://127.0.0.1:8080/mouse.html');
|
|
582
|
+
|
|
583
|
+
await browser
|
|
584
|
+
.actions()
|
|
585
|
+
.keyDown(Key.SHIFT) // hold Shift
|
|
586
|
+
.pointerMove(100, 100) // jump cursor to start
|
|
587
|
+
.pointerDown() // press primary button
|
|
588
|
+
.pointerMove(300, 300, { duration: 250 }) // drag over 250 ms
|
|
589
|
+
.pointerUp() // release
|
|
590
|
+
.keyUp(Key.SHIFT) // release Shift
|
|
591
|
+
.perform();
|
|
592
|
+
|
|
593
|
+
await browser.quit();
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Available steps: `keyDown(value)`, `keyUp(value)`, `pointerMove(x, y, opts?)`,
|
|
597
|
+
`pointerDown(button?)`, `pointerUp(button?)`, `pause(ms?)`,
|
|
598
|
+
`wheel(deltaX, deltaY, opts?)`, then `await .perform()`.
|
|
599
|
+
|
|
600
|
+
For routine clicks and typing prefer `browser.keyboard`,
|
|
601
|
+
`browser.mouse`, and `browser.click()` — they auto-wait for the target
|
|
602
|
+
and report failures clearly. Reach for `actions()` only when you need
|
|
603
|
+
byte-for-byte control of the input sequence.
|
|
604
|
+
|
|
605
|
+
## Configuring timeouts
|
|
606
|
+
|
|
607
|
+
All element actions, waits, and assertions use a browser-level default timeout
|
|
608
|
+
of **5000 ms**. You can change this globally or override it per call.
|
|
609
|
+
|
|
610
|
+
### Default timeouts
|
|
611
|
+
|
|
612
|
+
| Setting | Default | Applies to |
|
|
613
|
+
|---|---|---|
|
|
614
|
+
| `setDefaultTimeout(ms)` | **5 000 ms** | Element actions (`click`, `fill`, `clear`, `getValue`, `getAttribute`, …), `waitFor*`, assertions, dialog waits, locator/handle methods. |
|
|
615
|
+
| `setDefaultNavigationTimeout(ms)` | **30 000 ms** | `navigateTo()` and `waitForLoadState()`. |
|
|
616
|
+
|
|
617
|
+
Every method that accepts a `{ timeout }` option overrides the
|
|
618
|
+
appropriate default for that single call. The change made by
|
|
619
|
+
`setDefaultTimeout()` is live — it applies to `ElementHandle` and
|
|
620
|
+
`Locator` objects that already exist.
|
|
621
|
+
|
|
622
|
+
### Global defaults
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
// Change the default for all actions/waits/assertions on this browser instance
|
|
626
|
+
browser.setDefaultTimeout(10000); // 10 s
|
|
627
|
+
|
|
628
|
+
// Change the default for navigation (used by navigateTo in future BiDi mode)
|
|
629
|
+
browser.setDefaultNavigationTimeout(30000); // 30 s (this is already the default)
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Per-call override
|
|
633
|
+
|
|
634
|
+
Every method that interacts with the DOM accepts an optional `{ timeout }` option
|
|
635
|
+
that overrides the browser default for that single call:
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// One-shot override — only this click uses 2 s
|
|
639
|
+
await browser.click('#slow-button', { timeout: 2000 });
|
|
640
|
+
|
|
641
|
+
// Works on fill, clear, getValue, getAttribute, waitFor* too
|
|
642
|
+
await browser.fill('#input', 'text', { timeout: 3000 });
|
|
643
|
+
await browser.waitForVisible('#modal', { timeout: 8000 });
|
|
644
|
+
|
|
645
|
+
// Works on ElementHandle methods
|
|
646
|
+
await browser.find('#result').text({ timeout: 1000 });
|
|
647
|
+
await browser.find('#submit').click({ timeout: 2000 });
|
|
648
|
+
|
|
649
|
+
// Works on assertions
|
|
650
|
+
await browser.expect('#message').toBeVisible({ timeout: 3000 });
|
|
651
|
+
await browser.expect('#data').toHaveText('Loaded', { timeout: 10000 });
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Live updates
|
|
655
|
+
|
|
656
|
+
`setDefaultTimeout()` takes effect immediately, including for `ElementHandle`
|
|
657
|
+
and locator objects that were already created:
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
const button = browser.find('#submit');
|
|
661
|
+
browser.setDefaultTimeout(2000); // change after creating handle
|
|
662
|
+
await button.click(); // uses 2000 ms, not 5000 ms
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## Properties
|
|
666
|
+
|
|
667
|
+
| Property | Type | Description |
|
|
668
|
+
| ---------- | --------------------- | ------------------------------ |
|
|
669
|
+
| `keyboard` | `Keyboard` | Low-level keyboard control |
|
|
670
|
+
| `mouse` | `Mouse` | Low-level mouse control |
|
|
671
|
+
| `storage` | `SessionStateManager` | Cookie and storage management |
|
|
672
|
+
| `network` | `NetworkInterceptor` | Network mocking (BiDi only) |
|
|
673
|
+
| `logs` | `LogMonitor` | Console/error logs (BiDi only) |
|