craftdriver 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +157 -45
  3. package/bin/craftdriver.mjs +26 -0
  4. package/dist/cli/client.d.ts +17 -0
  5. package/dist/cli/client.d.ts.map +1 -0
  6. package/dist/cli/client.js +88 -0
  7. package/dist/cli/client.js.map +1 -0
  8. package/dist/cli/daemon.d.ts +15 -0
  9. package/dist/cli/daemon.d.ts.map +1 -0
  10. package/dist/cli/daemon.js +145 -0
  11. package/dist/cli/daemon.js.map +1 -0
  12. package/dist/cli/defaults.d.ts +10 -0
  13. package/dist/cli/defaults.d.ts.map +1 -0
  14. package/dist/cli/defaults.js +42 -0
  15. package/dist/cli/defaults.js.map +1 -0
  16. package/dist/cli/dispatcher.d.ts +28 -0
  17. package/dist/cli/dispatcher.d.ts.map +1 -0
  18. package/dist/cli/dispatcher.js +319 -0
  19. package/dist/cli/dispatcher.js.map +1 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +404 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cli/init.d.ts +24 -0
  25. package/dist/cli/init.d.ts.map +1 -0
  26. package/dist/cli/init.js +192 -0
  27. package/dist/cli/init.js.map +1 -0
  28. package/dist/cli/mcp/artifacts.d.ts +43 -0
  29. package/dist/cli/mcp/artifacts.d.ts.map +1 -0
  30. package/dist/cli/mcp/artifacts.js +104 -0
  31. package/dist/cli/mcp/artifacts.js.map +1 -0
  32. package/dist/cli/mcp/server.d.ts +34 -0
  33. package/dist/cli/mcp/server.d.ts.map +1 -0
  34. package/dist/cli/mcp/server.js +214 -0
  35. package/dist/cli/mcp/server.js.map +1 -0
  36. package/dist/cli/mcp/tools.d.ts +34 -0
  37. package/dist/cli/mcp/tools.d.ts.map +1 -0
  38. package/dist/cli/mcp/tools.js +236 -0
  39. package/dist/cli/mcp/tools.js.map +1 -0
  40. package/dist/cli/parseArgs.d.ts +37 -0
  41. package/dist/cli/parseArgs.d.ts.map +1 -0
  42. package/dist/cli/parseArgs.js +215 -0
  43. package/dist/cli/parseArgs.js.map +1 -0
  44. package/dist/cli/protocol.d.ts +34 -0
  45. package/dist/cli/protocol.d.ts.map +1 -0
  46. package/dist/cli/protocol.js +2 -0
  47. package/dist/cli/protocol.js.map +1 -0
  48. package/dist/cli/selector.d.ts +29 -0
  49. package/dist/cli/selector.d.ts.map +1 -0
  50. package/dist/cli/selector.js +100 -0
  51. package/dist/cli/selector.js.map +1 -0
  52. package/dist/cli/snapshot.d.ts +48 -0
  53. package/dist/cli/snapshot.d.ts.map +1 -0
  54. package/dist/cli/snapshot.js +179 -0
  55. package/dist/cli/snapshot.js.map +1 -0
  56. package/dist/index.d.ts +14 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +15 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/lib/a11y.d.ts +81 -0
  61. package/dist/lib/a11y.d.ts.map +1 -0
  62. package/dist/lib/a11y.js +171 -0
  63. package/dist/lib/a11y.js.map +1 -0
  64. package/dist/lib/bidi/index.d.ts +26 -1
  65. package/dist/lib/bidi/index.d.ts.map +1 -1
  66. package/dist/lib/bidi/index.js +49 -0
  67. package/dist/lib/bidi/index.js.map +1 -1
  68. package/dist/lib/bidi/logs.d.ts +2 -2
  69. package/dist/lib/bidi/logs.d.ts.map +1 -1
  70. package/dist/lib/bidi/logs.js +2 -2
  71. package/dist/lib/bidi/logs.js.map +1 -1
  72. package/dist/lib/bidi/network.d.ts +74 -1
  73. package/dist/lib/bidi/network.d.ts.map +1 -1
  74. package/dist/lib/bidi/network.js +222 -4
  75. package/dist/lib/bidi/network.js.map +1 -1
  76. package/dist/lib/bidi/storage.d.ts +5 -3
  77. package/dist/lib/bidi/storage.d.ts.map +1 -1
  78. package/dist/lib/bidi/storage.js +14 -7
  79. package/dist/lib/bidi/storage.js.map +1 -1
  80. package/dist/lib/bidi/types.d.ts +27 -6
  81. package/dist/lib/bidi/types.d.ts.map +1 -1
  82. package/dist/lib/browser.d.ts +637 -16
  83. package/dist/lib/browser.d.ts.map +1 -1
  84. package/dist/lib/browser.js +1428 -102
  85. package/dist/lib/browser.js.map +1 -1
  86. package/dist/lib/browserContext.d.ts +486 -0
  87. package/dist/lib/browserContext.d.ts.map +1 -0
  88. package/dist/lib/browserContext.js +937 -0
  89. package/dist/lib/browserContext.js.map +1 -0
  90. package/dist/lib/builder.d.ts +3 -0
  91. package/dist/lib/builder.d.ts.map +1 -1
  92. package/dist/lib/builder.js +31 -4
  93. package/dist/lib/builder.js.map +1 -1
  94. package/dist/lib/by.d.ts +18 -2
  95. package/dist/lib/by.d.ts.map +1 -1
  96. package/dist/lib/by.js +20 -2
  97. package/dist/lib/by.js.map +1 -1
  98. package/dist/lib/chrome.d.ts +13 -1
  99. package/dist/lib/chrome.d.ts.map +1 -1
  100. package/dist/lib/chrome.js +13 -17
  101. package/dist/lib/chrome.js.map +1 -1
  102. package/dist/lib/clock.d.ts +115 -0
  103. package/dist/lib/clock.d.ts.map +1 -0
  104. package/dist/lib/clock.js +407 -0
  105. package/dist/lib/clock.js.map +1 -0
  106. package/dist/lib/driver.d.ts +43 -0
  107. package/dist/lib/driver.d.ts.map +1 -1
  108. package/dist/lib/driver.js +120 -0
  109. package/dist/lib/driver.js.map +1 -1
  110. package/dist/lib/driverManager.d.ts +25 -0
  111. package/dist/lib/driverManager.d.ts.map +1 -0
  112. package/dist/lib/driverManager.js +399 -0
  113. package/dist/lib/driverManager.js.map +1 -0
  114. package/dist/lib/elementHandle.d.ts +67 -3
  115. package/dist/lib/elementHandle.d.ts.map +1 -1
  116. package/dist/lib/elementHandle.js +209 -67
  117. package/dist/lib/elementHandle.js.map +1 -1
  118. package/dist/lib/errors.d.ts +71 -0
  119. package/dist/lib/errors.d.ts.map +1 -0
  120. package/dist/lib/errors.js +73 -0
  121. package/dist/lib/errors.js.map +1 -0
  122. package/dist/lib/expect.d.ts +6 -1
  123. package/dist/lib/expect.d.ts.map +1 -1
  124. package/dist/lib/expect.js +55 -37
  125. package/dist/lib/expect.js.map +1 -1
  126. package/dist/lib/firefox.d.ts +18 -0
  127. package/dist/lib/firefox.d.ts.map +1 -0
  128. package/dist/lib/firefox.js +33 -0
  129. package/dist/lib/firefox.js.map +1 -0
  130. package/dist/lib/frame.d.ts +57 -0
  131. package/dist/lib/frame.d.ts.map +1 -0
  132. package/dist/lib/frame.js +277 -0
  133. package/dist/lib/frame.js.map +1 -0
  134. package/dist/lib/keyboard.d.ts +1 -1
  135. package/dist/lib/keyboard.d.ts.map +1 -1
  136. package/dist/lib/keyboard.js +1 -1
  137. package/dist/lib/keyboard.js.map +1 -1
  138. package/dist/lib/locator.d.ts +71 -2
  139. package/dist/lib/locator.d.ts.map +1 -1
  140. package/dist/lib/locator.js +289 -31
  141. package/dist/lib/locator.js.map +1 -1
  142. package/dist/lib/mouse.d.ts +1 -1
  143. package/dist/lib/mouse.d.ts.map +1 -1
  144. package/dist/lib/mouse.js +1 -1
  145. package/dist/lib/mouse.js.map +1 -1
  146. package/dist/lib/page.d.ts +107 -0
  147. package/dist/lib/page.d.ts.map +1 -0
  148. package/dist/lib/page.js +388 -0
  149. package/dist/lib/page.js.map +1 -0
  150. package/dist/lib/tracing.d.ts +128 -0
  151. package/dist/lib/tracing.d.ts.map +1 -0
  152. package/dist/lib/tracing.js +272 -0
  153. package/dist/lib/tracing.js.map +1 -0
  154. package/dist/lib/wait.d.ts.map +1 -1
  155. package/dist/lib/wait.js +14 -1
  156. package/dist/lib/wait.js.map +1 -1
  157. package/dist/lib/webelement.d.ts +2 -0
  158. package/dist/lib/webelement.d.ts.map +1 -1
  159. package/dist/lib/webelement.js +18 -0
  160. package/dist/lib/webelement.js.map +1 -1
  161. package/docs/accessibility.md +177 -0
  162. package/docs/api-reference.md +71 -0
  163. package/docs/assertions.md +182 -0
  164. package/docs/bidi-features.md +462 -0
  165. package/docs/browser-api.md +673 -0
  166. package/docs/browser-context.md +522 -0
  167. package/docs/cli.md +233 -0
  168. package/docs/clock.md +245 -0
  169. package/docs/dialogs.md +143 -0
  170. package/docs/driver-configuration.md +64 -0
  171. package/docs/element-api.md +219 -0
  172. package/docs/emulation.md +155 -0
  173. package/docs/error-codes.md +67 -0
  174. package/docs/getting-started.md +168 -0
  175. package/docs/keyboard-mouse.md +250 -0
  176. package/docs/mcp.md +250 -0
  177. package/docs/mobile-emulation.md +194 -0
  178. package/docs/screenshots.md +146 -0
  179. package/docs/selectors.md +382 -0
  180. package/docs/session-management.md +229 -0
  181. package/docs/tracing.md +282 -0
  182. package/package.json +23 -6
  183. package/skills/craftdriver/SKILL.md +84 -0
  184. package/skills/craftdriver/cheatsheet.md +194 -0
  185. package/skills/craftdriver/cli.md +104 -0
  186. package/skills/craftdriver/patterns.md +139 -0
@@ -1,12 +1,19 @@
1
1
  import { ChromeService } from './chrome.js';
2
- import { Driver } from './driver.js';
2
+ import { FirefoxService } from './firefox.js';
3
3
  import { By } from './by.js';
4
4
  import { Condition, WaitOptions } from './wait.js';
5
5
  import { ElementHandle } from './elementHandle.js';
6
- import { KeyboardController } from './keyboard.js';
7
- import { MouseController } from './mouse.js';
6
+ import { Locator } from './locator.js';
7
+ import { Keyboard } from './keyboard.js';
8
+ import { Mouse } from './mouse.js';
8
9
  import { ActionsBuilder } from './actions.js';
9
- import { NetworkInterceptor, LogMonitor, SessionStateManager, type SessionState, type StorageStateOptions } from './bidi/index.js';
10
+ import { NetworkInterceptor, LogMonitor, SessionStateManager, type SessionState, type StorageStateOptions, type InterceptedRequest, type InterceptedResponse } from './bidi/index.js';
11
+ import { Frame } from './frame.js';
12
+ import { Page } from './page.js';
13
+ import { BrowserContext } from './browserContext.js';
14
+ import { type TraceStartOptions } from './tracing.js';
15
+ import { A11y } from './a11y.js';
16
+ import { Clock } from './clock.js';
10
17
  /** Device metrics for custom mobile emulation */
11
18
  export interface DeviceMetrics {
12
19
  width: number;
@@ -24,6 +31,25 @@ export interface MobileEmulation {
24
31
  /** Custom user agent string */
25
32
  userAgent?: string;
26
33
  }
34
+ /**
35
+ * Options for {@link Browser.emulate}. Every field is independent; only the
36
+ * keys you pass are applied, others stay at their previous value. Passing
37
+ * `null` for a field clears the override for that field.
38
+ */
39
+ export interface EmulateOptions {
40
+ /** `prefers-color-scheme` media query value. Chromium only. */
41
+ colorScheme?: 'light' | 'dark' | 'no-preference' | null;
42
+ /** `prefers-reduced-motion` media query value. Chromium only. */
43
+ reducedMotion?: 'reduce' | 'no-preference' | null;
44
+ /** `forced-colors` media query value. Chromium only. */
45
+ forcedColors?: 'active' | 'none' | null;
46
+ /** BCP-47 language tag, e.g. `'de-DE'`. Affects `navigator.language` and `Intl.*`. */
47
+ locale?: string | null;
48
+ /** IANA timezone, e.g. `'Europe/Berlin'`. Affects `Intl.DateTimeFormat` and `Date`. */
49
+ timezoneId?: string | null;
50
+ /** When `true`, network requests fail and `navigator.onLine === false`. Chromium only. */
51
+ offline?: boolean;
52
+ }
27
53
  /** Common mobile device presets */
28
54
  export declare const devices: {
29
55
  readonly 'iPhone 14': {
@@ -109,22 +135,93 @@ export declare const devices: {
109
135
  };
110
136
  export type DeviceName = keyof typeof devices;
111
137
  export interface LaunchOptions {
112
- browserName?: 'chrome' | 'chromium' | 'firefox' | 'edge' | 'safari';
138
+ browserName?: 'chrome' | 'chromium' | 'firefox';
113
139
  chromeService?: ChromeService;
114
- /** Enable BiDi features (network interception, logs, etc.) */
140
+ firefoxService?: FirefoxService;
141
+ /**
142
+ * Enable WebDriver BiDi for network interception, logs, load events, etc.
143
+ * **Defaults to `true`** — BiDi is the primary protocol; Classic WebDriver
144
+ * is used only as a fallback when a capability is not yet available via BiDi.
145
+ * Set `false` only if your environment cannot negotiate a BiDi WebSocket.
146
+ */
115
147
  enableBiDi?: boolean;
116
- /** Load session state from file on launch */
148
+ /** Load session state from file on launch. BiDi is always used when this is set. */
117
149
  storageState?: string;
118
150
  /** Enable mobile device emulation (Chrome/Chromium only) */
119
151
  mobileEmulation?: MobileEmulation | DeviceName;
152
+ /**
153
+ * Directory where downloaded files are saved.
154
+ * Defaults to a temporary directory unique to this browser session.
155
+ */
156
+ downloadsDir?: string;
157
+ }
158
+ /**
159
+ * When to consider a navigation complete.
160
+ * - `'load'` — page `load` event has fired (default)
161
+ * - `'domcontentloaded'` — `DOMContentLoaded` has fired (faster, no waiting for images/fonts)
162
+ * - `'networkidle'` — `load` + no in-flight requests for 500 ms
163
+ * - `'none'` — do not wait; return as soon as the navigation is initiated
164
+ */
165
+ export type LoadState = 'load' | 'domcontentloaded' | 'networkidle' | 'none';
166
+ /** The type of a browser dialog. */
167
+ export type DialogType = 'alert' | 'confirm' | 'prompt' | 'beforeunload';
168
+ /**
169
+ * Represents an open browser dialog (alert / confirm / prompt).
170
+ * Passed to handlers registered with `browser.onDialog()`.
171
+ */
172
+ export interface Dialog {
173
+ /** Returns the dialog type: `'alert'`, `'confirm'`, `'prompt'`, or `'beforeunload'`. */
174
+ type(): DialogType;
175
+ /** Returns the message text shown in the dialog. */
176
+ message(): string;
177
+ /** Returns the default text pre-filled in a prompt dialog (empty string for non-prompt dialogs). */
178
+ defaultValue(): string;
179
+ /** Accept the dialog (OK button). For prompts, optionally pass `text` to fill the input first. */
180
+ accept(text?: string): Promise<void>;
181
+ /** Dismiss the dialog (Cancel button / close). */
182
+ dismiss(): Promise<void>;
183
+ }
184
+ /** A file downloaded during a `waitForDownload()` call. */
185
+ export interface Download {
186
+ /** Absolute path to the downloaded file on disk. */
187
+ path: string;
188
+ /** The filename as suggested by the server (from Content-Disposition or URL). */
189
+ suggestedFilename: string;
190
+ /** Copy the file to `targetPath`. */
191
+ saveAs(targetPath: string): Promise<void>;
120
192
  }
121
193
  export declare class Browser {
122
194
  private driver;
123
195
  private bidiSession?;
124
196
  private _network?;
197
+ private _defaultContext?;
198
+ /** Cache of {@link BrowserContext} wrappers keyed by user-context id. */
199
+ private _contextsById;
125
200
  private _logs?;
201
+ private _tracer?;
126
202
  private _storage?;
203
+ private _a11y?;
204
+ private _clock?;
205
+ private _downloadsDir?;
206
+ private _driverService?;
207
+ private _browserName;
208
+ /** Active emulation overrides, re-applied to new top-level contexts. */
209
+ private _emulation;
210
+ /** Mutable browser-level defaults. Use setDefaultTimeout() / setDefaultNavigationTimeout() to change. */
211
+ private defaults;
127
212
  private constructor();
213
+ /**
214
+ * Set the default timeout (ms) used by all element actions, waits, and assertions
215
+ * when no per-call `{ timeout }` option is provided. Default: 5000.
216
+ */
217
+ setDefaultTimeout(ms: number): void;
218
+ /**
219
+ * Set the default navigation timeout (ms) used by navigateTo and load-state waits.
220
+ * Default: 30000.
221
+ */
222
+ setDefaultNavigationTimeout(ms: number): void;
223
+ /** Returns the current default timeout. Used as a live getter passed to ElementHandle / expectSelector. */
224
+ private getDefaultTimeout;
128
225
  static launch(options?: LaunchOptions): Promise<Browser>;
129
226
  /**
130
227
  * Initialize BiDi WebSocket connection
@@ -148,6 +245,26 @@ export declare class Browser {
148
245
  * Works with both BiDi and Classic WebDriver
149
246
  */
150
247
  get storage(): SessionStateManager;
248
+ /**
249
+ * Virtual clock control — freeze or advance `Date`, `setTimeout`, and
250
+ * `setInterval` inside the page. Requires BiDi (enabled by default).
251
+ *
252
+ * ```ts
253
+ * await browser.clock.install({ time: '2026-01-01T09:00:00Z' });
254
+ * await browser.clock.fastForward('15:01');
255
+ * ```
256
+ */
257
+ get clock(): Clock;
258
+ /**
259
+ * Accessibility audits via axe-core (optional peer dependency).
260
+ *
261
+ * ```ts
262
+ * await browser.a11y.check({ disableRules: ['color-contrast'] });
263
+ * ```
264
+ *
265
+ * Requires `axe-core` to be installed: `npm install --save-dev axe-core`.
266
+ */
267
+ get a11y(): A11y;
151
268
  /**
152
269
  * Save current session state (cookies + localStorage) to file
153
270
  */
@@ -156,7 +273,152 @@ export declare class Browser {
156
273
  * Load session state from file
157
274
  */
158
275
  loadState(path: string): Promise<void>;
159
- navigateTo(url: string): Promise<void>;
276
+ navigateTo(url: string, opts?: {
277
+ waitUntil?: LoadState;
278
+ }): Promise<void>;
279
+ /**
280
+ * Navigate the active page back one step in its session history.
281
+ * Proxies to {@link Page.goBack} on {@link activePage}.
282
+ */
283
+ goBack(): Promise<void>;
284
+ /**
285
+ * Navigate the active page forward one step in its session history.
286
+ * Proxies to {@link Page.goForward} on {@link activePage}.
287
+ */
288
+ goForward(): Promise<void>;
289
+ /**
290
+ * Reload the active page. Proxies to {@link Page.reload} on
291
+ * {@link activePage}.
292
+ */
293
+ reload(): Promise<void>;
294
+ /**
295
+ * Return the full HTML serialization of the active page.
296
+ * Proxies to {@link Page.content}.
297
+ */
298
+ content(): Promise<string>;
299
+ /**
300
+ * Replace the active page contents with the given HTML.
301
+ * Proxies to {@link Page.setContent}.
302
+ */
303
+ setContent(html: string, opts?: {
304
+ waitUntil?: Exclude<LoadState, 'networkidle'>;
305
+ }): Promise<void>;
306
+ /**
307
+ * Resize the viewport at runtime.
308
+ *
309
+ * - **BiDi** path uses `browsingContext.setViewport` on the active page,
310
+ * which resizes the *layout* viewport without changing the OS window.
311
+ * - **Classic** fallback uses `POST /session/{id}/window/rect`, which
312
+ * resizes the OS window — the inner viewport may end up a few pixels
313
+ * smaller than `width`/`height` because of browser chrome.
314
+ *
315
+ * For full mobile emulation (DPR, user-agent, touch), pass
316
+ * `mobileEmulation` to {@link Browser.launch}; this method only changes
317
+ * the viewport box.
318
+ */
319
+ setViewportSize(size: {
320
+ width: number;
321
+ height: number;
322
+ }): Promise<void>;
323
+ /**
324
+ * Override one or more permissions for an origin (or for every origin
325
+ * if `origin` is omitted).
326
+ *
327
+ * Uses BiDi `permissions.setPermission` (W3C Permissions module).
328
+ * Each name in `permissions` corresponds to a `PermissionDescriptor.name`,
329
+ * for example: `'geolocation'`, `'notifications'`, `'clipboard-read'`,
330
+ * `'clipboard-write'`, `'camera'`, `'microphone'`, `'midi'`,
331
+ * `'background-sync'`, `'persistent-storage'`.
332
+ *
333
+ * @example
334
+ * await browser.grantPermissions(['geolocation', 'notifications']);
335
+ * await browser.grantPermissions(['clipboard-read'], { origin: 'https://example.com' });
336
+ */
337
+ grantPermissions(permissions: string[], opts?: {
338
+ origin?: string;
339
+ state?: 'granted' | 'denied' | 'prompt';
340
+ }): Promise<void>;
341
+ /**
342
+ * Reset all permissions for an origin (or every origin) back to the
343
+ * browser default of `'prompt'`. Convenience wrapper around
344
+ * {@link grantPermissions} with `state: 'prompt'`.
345
+ */
346
+ clearPermissions(permissions: string[], opts?: {
347
+ origin?: string;
348
+ }): Promise<void>;
349
+ /**
350
+ * Override the geolocation reported by `navigator.geolocation`.
351
+ *
352
+ * Uses BiDi `emulation.setGeolocationOverride` (W3C BiDi
353
+ * `emulation` module). Pass `null` to clear the override and return
354
+ * to the real device location.
355
+ *
356
+ * @example
357
+ * await browser.setGeolocation({ latitude: 51.5074, longitude: -0.1278 });
358
+ * await browser.setGeolocation(null); // clear
359
+ */
360
+ setGeolocation(coords: {
361
+ latitude: number;
362
+ longitude: number;
363
+ accuracy?: number;
364
+ } | null): Promise<void>;
365
+ /**
366
+ * Override what the page sees from `matchMedia`, `navigator.language`,
367
+ * `Intl.*`, and the network stack.
368
+ *
369
+ * Pass `null` for any field to clear that override; pass `{}` for a no-op.
370
+ * Settings persist across navigations and are re-applied to new top-level
371
+ * pages opened in the same session.
372
+ *
373
+ * | Field | Transport | Cross-browser |
374
+ * |---|---|---|
375
+ * | `locale` | BiDi `emulation.setLocaleOverride` | yes |
376
+ * | `timezoneId` | BiDi `emulation.setTimezoneOverride` | yes |
377
+ * | `colorScheme` / `reducedMotion` / `forcedColors` | CDP `Emulation.setEmulatedMedia` | Chromium only |
378
+ * | `offline` | CDP `Network.emulateNetworkConditions` | Chromium only |
379
+ *
380
+ * Throws with a clear message if a Chromium-only field is requested on
381
+ * Firefox, or if BiDi negotiation failed at launch.
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * // Dark mode + German formatting + Berlin time
386
+ * await browser.emulate({
387
+ * colorScheme: 'dark',
388
+ * locale: 'de-DE',
389
+ * timezoneId: 'Europe/Berlin',
390
+ * });
391
+ *
392
+ * // Offline PWA UI
393
+ * await browser.emulate({ offline: true });
394
+ * await browser.click('#refresh');
395
+ *
396
+ * // Clear a single override
397
+ * await browser.emulate({ locale: null });
398
+ * ```
399
+ */
400
+ emulate(options: EmulateOptions): Promise<void>;
401
+ /**
402
+ * Send a CDP command scoped to a single top-level browsing context.
403
+ * Uses the chromium-bidi `goog:cdp` vendor extension. Wraps protocol
404
+ * errors so callers see an actionable message.
405
+ */
406
+ private _cdpForContext;
407
+ /** Best-effort: derive the active page's origin (`scheme://host[:port]`). */
408
+ private _currentOrigin;
409
+ /**
410
+ * Wait for the page to reach a given load state.
411
+ * Call this after an action that triggers navigation (clicking a link, submitting a form).
412
+ *
413
+ * - `'load'` — page `load` event (default)
414
+ * - `'domcontentloaded'` — DOMContentLoaded (faster)
415
+ * - `'networkidle'` — load + no in-flight requests for 500 ms
416
+ *
417
+ * Uses BiDi events when available, falls back to polling `document.readyState`.
418
+ */
419
+ waitForLoadState(state?: Exclude<LoadState, 'none'>, opts?: {
420
+ timeout?: number;
421
+ }): Promise<void>;
160
422
  url(): Promise<string>;
161
423
  title(): Promise<string>;
162
424
  wait<T>(condition: Condition<T>, options?: WaitOptions & {
@@ -164,7 +426,324 @@ export declare class Browser {
164
426
  }): Promise<T>;
165
427
  wait<T>(condition: Condition<T>, timeoutMs?: number, intervalMs?: number, message?: string): Promise<T>;
166
428
  quit(): Promise<void>;
167
- click(selector: string | By): Promise<void>;
429
+ /**
430
+ * Start a streaming trace session. Writes every event synchronously to
431
+ * `<outDir>/trace.ndjson` so a thrown `expect` on the next line cannot
432
+ * lose data. Screenshots, when enabled, land in `<outDir>/screenshots/`.
433
+ *
434
+ * **BiDi-only.** Call `stopTrace()` to write the closing marker and
435
+ * close the file.
436
+ *
437
+ * @example
438
+ * await browser.startTrace({ outDir: './artefacts/login' });
439
+ * try {
440
+ * await browser.navigateTo('/checkout');
441
+ * await browser.click('#pay');
442
+ * } finally {
443
+ * await browser.stopTrace();
444
+ * }
445
+ */
446
+ startTrace(opts: TraceStartOptions): Promise<void>;
447
+ /**
448
+ * Stop the active trace: drain in-flight screenshots, write the closing
449
+ * `meta` line, and close the file. If your test threw and never reached
450
+ * here, the file is still valid NDJSON — just without the closing line.
451
+ */
452
+ stopTrace(): Promise<void>;
453
+ /**
454
+ * Execute JavaScript in the page and return the result.
455
+ *
456
+ * Accepts a function (with optional args) or a script string:
457
+ * ```ts
458
+ * await browser.evaluate(() => document.title);
459
+ * await browser.evaluate((a, b) => a + b, 2, 3); // 5
460
+ * await browser.evaluate('return document.title');
461
+ * ```
462
+ *
463
+ * Uses BiDi `script.callFunction` / `script.evaluate` when available,
464
+ * falls back to Classic WebDriver `executeScript`.
465
+ * Only JSON-serializable return values are supported.
466
+ */
467
+ evaluate<T = unknown>(fn: ((...args: unknown[]) => T) | string, ...args: unknown[]): Promise<T>;
468
+ /**
469
+ * Register a script that runs before any page script on every navigation.
470
+ * Returns a handle with a `remove()` method to unregister it.
471
+ *
472
+ * Requires BiDi (enabled by default). Throws a clear error otherwise.
473
+ *
474
+ * ```ts
475
+ * const script = await browser.addInitScript(() => {
476
+ * window.__flags = { darkMode: true };
477
+ * });
478
+ * await browser.navigateTo(url); // __flags is available
479
+ * await script.remove(); // unregister
480
+ * ```
481
+ */
482
+ addInitScript(fnOrSrc: ((...args: unknown[]) => unknown) | string): Promise<{
483
+ remove(): Promise<void>;
484
+ }>;
485
+ /**
486
+ * Wait for the first network request matching a URL glob or predicate.
487
+ * Register **before** the action that triggers the request.
488
+ */
489
+ waitForRequest(pattern: string | ((req: InterceptedRequest) => boolean), opts?: {
490
+ timeout?: number;
491
+ }): Promise<InterceptedRequest>;
492
+ /**
493
+ * Wait for the first completed response matching a URL glob or predicate.
494
+ * Register **before** the action that triggers the request.
495
+ *
496
+ * @example
497
+ * ```ts
498
+ * const [res] = await Promise.all([
499
+ * browser.waitForResponse('/api/users'),
500
+ * browser.click('#load-btn'),
501
+ * ]);
502
+ * expect(res.status).toBe(200);
503
+ * ```
504
+ */
505
+ waitForResponse(pattern: string | ((res: InterceptedResponse) => boolean), opts?: {
506
+ timeout?: number;
507
+ }): Promise<InterceptedResponse>;
508
+ /**
509
+ * Subscribe to **every** network request or response. Returns an
510
+ * `off()` function that unsubscribes the listener.
511
+ *
512
+ * Use this for fire-and-forget logging, tracing, or assertion-on-every
513
+ * patterns where you do not know in advance which request you care
514
+ * about. For one-shot waits prefer {@link waitForRequest} /
515
+ * {@link waitForResponse}.
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * const off = browser.on('response', (res) => {
520
+ * console.log(res.status, res.url);
521
+ * });
522
+ * await browser.click('#load');
523
+ * off();
524
+ * ```
525
+ */
526
+ on(event: 'request', listener: (req: InterceptedRequest) => void): () => void;
527
+ on(event: 'response', listener: (res: InterceptedResponse) => void): () => void;
528
+ /**
529
+ * Run `action`, then wait until a new file appears in the downloads directory.
530
+ * Returns a `Download` handle with `path`, `suggestedFilename`, and `saveAs()`.
531
+ *
532
+ * @example
533
+ * const dl = await browser.waitForDownload(() => browser.click('#download-btn'));
534
+ * expect(dl.suggestedFilename).toBe('report.csv');
535
+ * await dl.saveAs('/tmp/report.csv');
536
+ */
537
+ waitForDownload(action: () => Promise<void> | void, opts?: {
538
+ timeout?: number;
539
+ }): Promise<Download>;
540
+ /**
541
+ * Register a handler called every time a browser dialog opens (alert / confirm / prompt).
542
+ * The handler receives a `Dialog` object with `accept()` and `dismiss()` methods.
543
+ * Returns an unsubscribe function — call it to stop listening.
544
+ *
545
+ * If no handler is registered, dialogs are auto-dismissed so they don't hang the test.
546
+ *
547
+ * @example
548
+ * const off = browser.onDialog(async dialog => {
549
+ * expect(dialog.message).toBe('Are you sure?');
550
+ * await dialog.accept();
551
+ * });
552
+ * await browser.click('#confirm-btn');
553
+ * off(); // stop listening
554
+ */
555
+ onDialog(handler: (dialog: Dialog) => Promise<void> | void): () => void;
556
+ /**
557
+ * Accept the currently open dialog (OK / confirm).
558
+ * For prompt dialogs, pass `text` to type into the input first.
559
+ * Uses BiDi when available, Classic WebDriver otherwise.
560
+ */
561
+ acceptDialog(text?: string): Promise<void>;
562
+ /**
563
+ * Dismiss the currently open dialog (Cancel / close).
564
+ * Uses BiDi when available, Classic WebDriver otherwise.
565
+ */
566
+ dismissDialog(): Promise<void>;
567
+ /**
568
+ * Get the message text of the currently open dialog.
569
+ * Uses BiDi when available, Classic WebDriver otherwise.
570
+ */
571
+ getDialogMessage(): Promise<string>;
572
+ /**
573
+ * Wait for the next dialog (alert / confirm / prompt) to open and return it.
574
+ * The dialog object has `accept()` and `dismiss()` methods — call one of them
575
+ * or the page will be frozen waiting for the dialog.
576
+ *
577
+ * Matches Playwright's `page.waitForEvent('dialog')` pattern:
578
+ * ```ts
579
+ * const [, dialog] = await Promise.all([
580
+ * browser.click('#confirm-btn'),
581
+ * browser.waitForDialog(),
582
+ * ]);
583
+ * await dialog.accept();
584
+ * ```
585
+ */
586
+ waitForDialog(opts?: {
587
+ timeout?: number;
588
+ }): Promise<Dialog>;
589
+ private _buildDialog;
590
+ /**
591
+ * Return a `Frame` object scoped to the first iframe matching `selector`.
592
+ * All element methods on the returned `Frame` are automatically targeted
593
+ * inside that iframe's browsing context.
594
+ *
595
+ * @example
596
+ * const frame = await browser.frame('#stripe-iframe');
597
+ * await frame.fill('#card-number', '4242 4242 4242 4242');
598
+ */
599
+ frame(selector: string | By, opts?: {
600
+ timeout?: number;
601
+ }): Promise<Frame>;
602
+ /**
603
+ * Return `Frame` objects for all iframes on the current page.
604
+ */
605
+ frames(opts?: {
606
+ timeout?: number;
607
+ }): Promise<Frame[]>;
608
+ /**
609
+ * Return `Page` objects for all open top-level browsing contexts
610
+ * (tabs and popup windows).
611
+ */
612
+ pages(): Promise<Page[]>;
613
+ /**
614
+ * Open a new top-level browsing context (a tab or a window).
615
+ *
616
+ * Maps to BiDi `browsingContext.create`. **BiDi-only** — throws in Classic
617
+ * mode because WebDriver Classic has no spec-level command for creating
618
+ * top-level browsing contexts.
619
+ *
620
+ * @example
621
+ * const page = await browser.openPage({ url: 'https://example.com', type: 'tab' });
622
+ * await page.waitForLoadState();
623
+ */
624
+ openPage(opts?: {
625
+ url?: string;
626
+ type?: 'tab' | 'window';
627
+ }): Promise<Page>;
628
+ /**
629
+ * Run `action` and wait for a new top-level browsing context (tab / popup)
630
+ * to open. Returns a `Page` bound to the new context.
631
+ *
632
+ * @example
633
+ * const popup = await browser.waitForPage(() => browser.click('#open-popup'));
634
+ * await popup.waitForLoadState();
635
+ * const title = await popup.title();
636
+ */
637
+ waitForPage(action: () => Promise<void>, opts?: {
638
+ timeout?: number;
639
+ }): Promise<Page>;
640
+ /**
641
+ * Create a new isolated user context (an "incognito profile"). Each
642
+ * context has its own cookies, localStorage, and IndexedDB.
643
+ *
644
+ * Maps to BiDi `browser.createUserContext`. **BiDi-only** — throws in
645
+ * Classic mode because WebDriver Classic has no equivalent.
646
+ *
647
+ * Optionally pre-seed the context with a previously-saved
648
+ * `storageState` snapshot (object or path to JSON) — cookies are
649
+ * applied immediately and localStorage entries land on first
650
+ * navigation to each captured origin.
651
+ *
652
+ * @example
653
+ * const alice = await browser.newContext();
654
+ * const bob = await browser.newContext();
655
+ * const aPage = await alice.newPage({ url: 'https://app.example.com/login' });
656
+ * const bPage = await bob.newPage({ url: 'https://app.example.com/login' });
657
+ * // logging in as Alice in aPage does not leak into bPage.
658
+ *
659
+ * @example
660
+ * // Skip the login UI in every test by reusing a saved session.
661
+ * const ctx = await browser.newContext({ storageState: 'auth/alice.json' });
662
+ * const page = await ctx.newPage({ url: 'https://app.example.com/dashboard' });
663
+ *
664
+ * @example
665
+ * // Pre-configure a context for a staging tenant: every relative URL
666
+ * // resolves against staging, every request carries the tenant header.
667
+ * const ctx = await browser.newContext({
668
+ * baseURL: 'https://staging.example.com',
669
+ * extraHTTPHeaders: { 'X-Tenant': 'acme' },
670
+ * });
671
+ * const page = await ctx.newPage({ url: '/dashboard' }); // → staging.example.com/dashboard
672
+ */
673
+ /**
674
+ * Return the {@link BrowserContext} wrapper for a given BiDi user-context
675
+ * id, constructing it on first access and caching it thereafter. Stable
676
+ * across {@link defaultContext}, {@link newContext}, and {@link contexts}
677
+ * so route handlers, event listeners, and init scripts always live on
678
+ * the same instance the user holds.
679
+ *
680
+ * Caller must guarantee BiDi is connected (`bidiSession.isConnected()`).
681
+ */
682
+ private _wrapContext;
683
+ newContext(opts?: {
684
+ storageState?: SessionState | string;
685
+ baseURL?: string;
686
+ extraHTTPHeaders?: Record<string, string>;
687
+ /**
688
+ * Locale reported by `navigator.language`, `Intl.*`, and the
689
+ * `Accept-Language` header. Cross-browser via BiDi
690
+ * `emulation.setLocaleOverride`. Applies to every page in this
691
+ * context, including pages opened later.
692
+ */
693
+ locale?: string;
694
+ /**
695
+ * IANA timezone applied to `Date` and `Intl.DateTimeFormat`.
696
+ * Cross-browser via BiDi `emulation.setTimezoneOverride`. Applies to
697
+ * every page in this context, including pages opened later.
698
+ */
699
+ timezoneId?: string;
700
+ /**
701
+ * Coordinates returned by `navigator.geolocation`. Cross-browser via
702
+ * BiDi `emulation.setGeolocationOverride`. The page still needs the
703
+ * `geolocation` permission \u2014 use {@link BrowserContext.grantPermissions}
704
+ * once the origin is known.
705
+ */
706
+ geolocation?: {
707
+ latitude: number;
708
+ longitude: number;
709
+ accuracy?: number;
710
+ };
711
+ }): Promise<BrowserContext>;
712
+ /**
713
+ * Return all open user contexts, including the default one.
714
+ *
715
+ * Maps to BiDi `browser.getUserContexts`. **BiDi-only.**
716
+ */
717
+ contexts(): Promise<BrowserContext[]>;
718
+ /**
719
+ * The implicit default user context the browser started in (id `'default'`).
720
+ * Pages opened via `browser.openPage()` / `browser.waitForPage()` belong
721
+ * to this context. **BiDi-only.**
722
+ */
723
+ get defaultContext(): BrowserContext;
724
+ /**
725
+ * Return the currently focused top-level page in `defaultContext`.
726
+ *
727
+ * This is the page that `browser.click()`, `browser.find()`,
728
+ * `browser.evaluate()` and the other `Browser`-level DOM shortcuts
729
+ * implicitly target. It **never** crosses into pages inside a
730
+ * non-default `BrowserContext`.
731
+ *
732
+ * Use it to make multi-tab tests explicit:
733
+ *
734
+ * @example
735
+ * const page = await browser.activePage();
736
+ * await page.click('#submit'); // identical to browser.click('#submit')
737
+ *
738
+ * @example
739
+ * const popup = await browser.openPage({ url: '/help' });
740
+ * // browser.activePage() still returns the original page —
741
+ * // openPage() does not steal focus.
742
+ */
743
+ activePage(): Promise<Page>;
744
+ click(selector: string | By, opts?: {
745
+ timeout?: number;
746
+ }): Promise<void>;
168
747
  fill(selector: string | By, text: string, opts?: {
169
748
  timeout?: number;
170
749
  }): Promise<void>;
@@ -190,10 +769,41 @@ export declare class Browser {
190
769
  durationMs?: number;
191
770
  }) => Promise<void>;
192
771
  };
193
- screenshot(selectorOrBy?: string | By): Promise<Buffer>;
194
- saveScreenshot(path: string, selectorOrBy?: string | By): Promise<Buffer>;
772
+ /**
773
+ * Capture a screenshot of the active page (viewport by default), the
774
+ * full scrollable document (`fullPage: true`), or an element matching
775
+ * `selector`. Optionally write the PNG to `path`. Returns the raw PNG
776
+ * buffer.
777
+ *
778
+ * Full-page capture uses BiDi `browsingContext.captureScreenshot` with
779
+ * `origin: 'document'` and therefore requires `enableBiDi: true`
780
+ * (the default). Element capture composes auto-waiting via the same
781
+ * resolution as `find()`.
782
+ *
783
+ * @example
784
+ * const buf = await browser.screenshot();
785
+ * await browser.screenshot({ path: 'viewport.png' });
786
+ * await browser.screenshot({ fullPage: true, path: 'full.png' });
787
+ * await browser.screenshot({ selector: '#chart', path: 'chart.png' });
788
+ */
789
+ screenshot(opts?: {
790
+ path?: string;
791
+ selector?: string | By;
792
+ fullPage?: boolean;
793
+ timeout?: number;
794
+ }): Promise<Buffer>;
195
795
  expect(selector: string | By): import("./expect.js").ExpectApi;
196
796
  find(selector: string | By): ElementHandle;
797
+ /**
798
+ * Return a lazy, chainable `Locator` for the given selector.
799
+ * Prefer `locator()` over `find()` when you need composition, filtering, or indexed access.
800
+ */
801
+ locator(selector: string | By): Locator;
802
+ /**
803
+ * Return snapshot `ElementHandle`s for all elements matching `selector` at call time.
804
+ * Use `locator().all()` when you need filtering; use `findAll()` for the simple array case.
805
+ */
806
+ findAll(selector: string | By): Promise<ElementHandle[]>;
197
807
  getByRole(role: string, opts?: {
198
808
  name?: string;
199
809
  exact?: boolean;
@@ -209,8 +819,8 @@ export declare class Browser {
209
819
  exact?: boolean;
210
820
  }): ElementHandle;
211
821
  getByTestId(id: string): ElementHandle;
212
- keyboard: KeyboardController;
213
- mouse: MouseController;
822
+ keyboard: Keyboard;
823
+ mouse: Mouse;
214
824
  static Key: {
215
825
  readonly Null: string;
216
826
  readonly Cancel: string;
@@ -250,9 +860,6 @@ export declare class Browser {
250
860
  readonly F12: string;
251
861
  readonly Meta: string;
252
862
  };
253
- waitFor(check: ((driver: Driver) => Promise<any> | any) | (() => Promise<any> | any), options?: WaitOptions & {
254
- message?: string;
255
- }): Promise<any>;
256
863
  waitForVisible(selector: string | By, opts?: {
257
864
  timeout?: number;
258
865
  }): Promise<void>;
@@ -265,6 +872,20 @@ export declare class Browser {
265
872
  waitForDetached(selector: string | By, opts?: {
266
873
  timeout?: number;
267
874
  }): Promise<void>;
875
+ /**
876
+ * Wait for an element to reach the given state.
877
+ *
878
+ * Canonical wait API; the four `waitForVisible/Hidden/Attached/Detached`
879
+ * methods are kept as one-line shortcuts.
880
+ *
881
+ * @example
882
+ * await browser.waitFor('#spinner', { state: 'hidden' });
883
+ * await browser.waitFor('#toast', { state: 'visible', timeout: 2000 });
884
+ */
885
+ waitFor(selector: string | By, opts: {
886
+ state: 'visible' | 'hidden' | 'attached' | 'detached';
887
+ timeout?: number;
888
+ }): Promise<void>;
268
889
  actions(): ActionsBuilder;
269
890
  pause(ms: number): Promise<void>;
270
891
  }