craftdriver 0.0.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +186 -10
  3. package/bin/craftdriver.mjs +26 -0
  4. package/dist/cli/client.d.ts +17 -0
  5. package/dist/cli/client.d.ts.map +1 -0
  6. package/dist/cli/client.js +88 -0
  7. package/dist/cli/client.js.map +1 -0
  8. package/dist/cli/daemon.d.ts +15 -0
  9. package/dist/cli/daemon.d.ts.map +1 -0
  10. package/dist/cli/daemon.js +145 -0
  11. package/dist/cli/daemon.js.map +1 -0
  12. package/dist/cli/defaults.d.ts +10 -0
  13. package/dist/cli/defaults.d.ts.map +1 -0
  14. package/dist/cli/defaults.js +42 -0
  15. package/dist/cli/defaults.js.map +1 -0
  16. package/dist/cli/dispatcher.d.ts +28 -0
  17. package/dist/cli/dispatcher.d.ts.map +1 -0
  18. package/dist/cli/dispatcher.js +319 -0
  19. package/dist/cli/dispatcher.js.map +1 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +404 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cli/init.d.ts +24 -0
  25. package/dist/cli/init.d.ts.map +1 -0
  26. package/dist/cli/init.js +192 -0
  27. package/dist/cli/init.js.map +1 -0
  28. package/dist/cli/mcp/artifacts.d.ts +43 -0
  29. package/dist/cli/mcp/artifacts.d.ts.map +1 -0
  30. package/dist/cli/mcp/artifacts.js +104 -0
  31. package/dist/cli/mcp/artifacts.js.map +1 -0
  32. package/dist/cli/mcp/server.d.ts +34 -0
  33. package/dist/cli/mcp/server.d.ts.map +1 -0
  34. package/dist/cli/mcp/server.js +214 -0
  35. package/dist/cli/mcp/server.js.map +1 -0
  36. package/dist/cli/mcp/tools.d.ts +34 -0
  37. package/dist/cli/mcp/tools.d.ts.map +1 -0
  38. package/dist/cli/mcp/tools.js +236 -0
  39. package/dist/cli/mcp/tools.js.map +1 -0
  40. package/dist/cli/parseArgs.d.ts +37 -0
  41. package/dist/cli/parseArgs.d.ts.map +1 -0
  42. package/dist/cli/parseArgs.js +215 -0
  43. package/dist/cli/parseArgs.js.map +1 -0
  44. package/dist/cli/protocol.d.ts +34 -0
  45. package/dist/cli/protocol.d.ts.map +1 -0
  46. package/dist/cli/protocol.js +2 -0
  47. package/dist/cli/protocol.js.map +1 -0
  48. package/dist/cli/selector.d.ts +29 -0
  49. package/dist/cli/selector.d.ts.map +1 -0
  50. package/dist/cli/selector.js +100 -0
  51. package/dist/cli/selector.js.map +1 -0
  52. package/dist/cli/snapshot.d.ts +48 -0
  53. package/dist/cli/snapshot.d.ts.map +1 -0
  54. package/dist/cli/snapshot.js +179 -0
  55. package/dist/cli/snapshot.js.map +1 -0
  56. package/dist/index.d.ts +14 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +17 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/lib/a11y.d.ts +81 -0
  61. package/dist/lib/a11y.d.ts.map +1 -0
  62. package/dist/lib/a11y.js +171 -0
  63. package/dist/lib/a11y.js.map +1 -0
  64. package/dist/lib/bidi/connection.d.ts +62 -0
  65. package/dist/lib/bidi/connection.d.ts.map +1 -0
  66. package/dist/lib/bidi/connection.js +227 -0
  67. package/dist/lib/bidi/connection.js.map +1 -0
  68. package/dist/lib/bidi/index.d.ts +101 -0
  69. package/dist/lib/bidi/index.d.ts.map +1 -0
  70. package/dist/lib/bidi/index.js +163 -0
  71. package/dist/lib/bidi/index.js.map +1 -0
  72. package/dist/lib/bidi/logs.d.ts +104 -0
  73. package/dist/lib/bidi/logs.d.ts.map +1 -0
  74. package/dist/lib/bidi/logs.js +273 -0
  75. package/dist/lib/bidi/logs.js.map +1 -0
  76. package/dist/lib/bidi/network.d.ts +165 -0
  77. package/dist/lib/bidi/network.d.ts.map +1 -0
  78. package/dist/lib/bidi/network.js +477 -0
  79. package/dist/lib/bidi/network.js.map +1 -0
  80. package/dist/lib/bidi/storage.d.ts +83 -0
  81. package/dist/lib/bidi/storage.d.ts.map +1 -0
  82. package/dist/lib/bidi/storage.js +299 -0
  83. package/dist/lib/bidi/storage.js.map +1 -0
  84. package/dist/lib/bidi/types.d.ts +323 -0
  85. package/dist/lib/bidi/types.d.ts.map +1 -0
  86. package/dist/lib/bidi/types.js +6 -0
  87. package/dist/lib/bidi/types.js.map +1 -0
  88. package/dist/lib/browser.d.ts +776 -13
  89. package/dist/lib/browser.d.ts.map +1 -1
  90. package/dist/lib/browser.js +1547 -70
  91. package/dist/lib/browser.js.map +1 -1
  92. package/dist/lib/browserContext.d.ts +486 -0
  93. package/dist/lib/browserContext.d.ts.map +1 -0
  94. package/dist/lib/browserContext.js +937 -0
  95. package/dist/lib/browserContext.js.map +1 -0
  96. package/dist/lib/builder.d.ts +3 -0
  97. package/dist/lib/builder.d.ts.map +1 -1
  98. package/dist/lib/builder.js +31 -4
  99. package/dist/lib/builder.js.map +1 -1
  100. package/dist/lib/by.d.ts +18 -2
  101. package/dist/lib/by.d.ts.map +1 -1
  102. package/dist/lib/by.js +20 -2
  103. package/dist/lib/by.js.map +1 -1
  104. package/dist/lib/chrome.d.ts +13 -1
  105. package/dist/lib/chrome.d.ts.map +1 -1
  106. package/dist/lib/chrome.js +13 -17
  107. package/dist/lib/chrome.js.map +1 -1
  108. package/dist/lib/clock.d.ts +115 -0
  109. package/dist/lib/clock.d.ts.map +1 -0
  110. package/dist/lib/clock.js +407 -0
  111. package/dist/lib/clock.js.map +1 -0
  112. package/dist/lib/driver.d.ts +45 -0
  113. package/dist/lib/driver.d.ts.map +1 -1
  114. package/dist/lib/driver.js +129 -1
  115. package/dist/lib/driver.js.map +1 -1
  116. package/dist/lib/driverManager.d.ts +25 -0
  117. package/dist/lib/driverManager.d.ts.map +1 -0
  118. package/dist/lib/driverManager.js +399 -0
  119. package/dist/lib/driverManager.js.map +1 -0
  120. package/dist/lib/elementHandle.d.ts +69 -2
  121. package/dist/lib/elementHandle.d.ts.map +1 -1
  122. package/dist/lib/elementHandle.js +215 -40
  123. package/dist/lib/elementHandle.js.map +1 -1
  124. package/dist/lib/errors.d.ts +71 -0
  125. package/dist/lib/errors.d.ts.map +1 -0
  126. package/dist/lib/errors.js +73 -0
  127. package/dist/lib/errors.js.map +1 -0
  128. package/dist/lib/expect.d.ts +6 -1
  129. package/dist/lib/expect.d.ts.map +1 -1
  130. package/dist/lib/expect.js +55 -37
  131. package/dist/lib/expect.js.map +1 -1
  132. package/dist/lib/firefox.d.ts +18 -0
  133. package/dist/lib/firefox.d.ts.map +1 -0
  134. package/dist/lib/firefox.js +33 -0
  135. package/dist/lib/firefox.js.map +1 -0
  136. package/dist/lib/frame.d.ts +57 -0
  137. package/dist/lib/frame.d.ts.map +1 -0
  138. package/dist/lib/frame.js +277 -0
  139. package/dist/lib/frame.js.map +1 -0
  140. package/dist/lib/keyboard.d.ts +1 -1
  141. package/dist/lib/keyboard.d.ts.map +1 -1
  142. package/dist/lib/keyboard.js +1 -1
  143. package/dist/lib/keyboard.js.map +1 -1
  144. package/dist/lib/locator.d.ts +71 -2
  145. package/dist/lib/locator.d.ts.map +1 -1
  146. package/dist/lib/locator.js +289 -31
  147. package/dist/lib/locator.js.map +1 -1
  148. package/dist/lib/mouse.d.ts +1 -1
  149. package/dist/lib/mouse.d.ts.map +1 -1
  150. package/dist/lib/mouse.js +1 -1
  151. package/dist/lib/mouse.js.map +1 -1
  152. package/dist/lib/page.d.ts +107 -0
  153. package/dist/lib/page.d.ts.map +1 -0
  154. package/dist/lib/page.js +388 -0
  155. package/dist/lib/page.js.map +1 -0
  156. package/dist/lib/tracing.d.ts +128 -0
  157. package/dist/lib/tracing.d.ts.map +1 -0
  158. package/dist/lib/tracing.js +272 -0
  159. package/dist/lib/tracing.js.map +1 -0
  160. package/dist/lib/wait.d.ts.map +1 -1
  161. package/dist/lib/wait.js +14 -1
  162. package/dist/lib/wait.js.map +1 -1
  163. package/dist/lib/webelement.d.ts +2 -0
  164. package/dist/lib/webelement.d.ts.map +1 -1
  165. package/dist/lib/webelement.js +18 -0
  166. package/dist/lib/webelement.js.map +1 -1
  167. package/docs/accessibility.md +177 -0
  168. package/docs/api-reference.md +71 -0
  169. package/docs/assertions.md +182 -0
  170. package/docs/bidi-features.md +462 -0
  171. package/docs/browser-api.md +673 -0
  172. package/docs/browser-context.md +522 -0
  173. package/docs/cli.md +233 -0
  174. package/docs/clock.md +245 -0
  175. package/docs/dialogs.md +143 -0
  176. package/docs/driver-configuration.md +64 -0
  177. package/docs/element-api.md +219 -0
  178. package/docs/emulation.md +155 -0
  179. package/docs/error-codes.md +67 -0
  180. package/docs/getting-started.md +168 -0
  181. package/docs/keyboard-mouse.md +250 -0
  182. package/docs/mcp.md +250 -0
  183. package/docs/mobile-emulation.md +194 -0
  184. package/docs/screenshots.md +146 -0
  185. package/docs/selectors.md +382 -0
  186. package/docs/session-management.md +229 -0
  187. package/docs/tracing.md +282 -0
  188. package/package.json +25 -5
  189. package/skills/craftdriver/SKILL.md +84 -0
  190. package/skills/craftdriver/cheatsheet.md +194 -0
  191. package/skills/craftdriver/cli.md +104 -0
  192. 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) |