craftdriver 1.0.3 → 1.0.4

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 (50) hide show
  1. package/CHANGELOG.md +12 -3
  2. package/README.md +80 -160
  3. package/dist/lib/driver.d.ts.map +1 -1
  4. package/dist/lib/driver.js +2 -0
  5. package/dist/lib/driver.js.map +1 -1
  6. package/dist/lib/driverManager.d.ts.map +1 -1
  7. package/dist/lib/driverManager.js +67 -24
  8. package/dist/lib/driverManager.js.map +1 -1
  9. package/dist/lib/http.d.ts +1 -1
  10. package/dist/lib/http.d.ts.map +1 -1
  11. package/dist/lib/http.js +35 -7
  12. package/dist/lib/http.js.map +1 -1
  13. package/dist/lib/timing.d.ts +13 -0
  14. package/dist/lib/timing.d.ts.map +1 -1
  15. package/dist/lib/timing.js +13 -0
  16. package/dist/lib/timing.js.map +1 -1
  17. package/dist/lib/types.d.ts +2 -0
  18. package/dist/lib/types.d.ts.map +1 -1
  19. package/docs/agents.md +92 -0
  20. package/docs/api-reference.md +108 -46
  21. package/docs/browser-api.md +1 -1
  22. package/docs/browser-context.md +4 -7
  23. package/docs/browser-logs.md +158 -0
  24. package/docs/cli.md +26 -26
  25. package/docs/element-api.md +37 -6
  26. package/docs/emulation.md +6 -2
  27. package/docs/index.md +64 -0
  28. package/docs/keyboard-mouse.md +28 -10
  29. package/docs/mobile-emulation.md +5 -2
  30. package/docs/network.md +203 -0
  31. package/docs/public/craftdriver-mark.svg +15 -0
  32. package/docs/public/social-card.svg +27 -0
  33. package/docs/recipes/accessibility-gate.md +53 -0
  34. package/docs/recipes/console-error-gate.md +57 -0
  35. package/docs/recipes/file-upload-download.md +58 -0
  36. package/docs/recipes/login-once-reuse-session.md +73 -0
  37. package/docs/recipes/mobile-flow-with-network-and-logs.md +57 -0
  38. package/docs/recipes/mock-api-and-assert-network.md +60 -0
  39. package/docs/recipes/multi-user-contexts.md +58 -0
  40. package/docs/recipes/trace-failing-test.md +64 -0
  41. package/docs/recipes/virtual-clock-time-sensitive-ui.md +60 -0
  42. package/docs/recipes/vitest-browser-lifecycle.md +56 -0
  43. package/docs/recipes.md +33 -0
  44. package/docs/selectors.md +22 -1
  45. package/docs/session-management.md +18 -1
  46. package/docs/standards.md +40 -0
  47. package/docs/why-craftdriver.md +28 -0
  48. package/docs/zero-config-drivers.md +52 -0
  49. package/package.json +19 -5
  50. package/docs/bidi-features.md +0 -470
@@ -1,470 +0,0 @@
1
- # BiDi Features
2
-
3
- CraftDriver is built on the WebDriver BiDi protocol, giving you network interception, browser log capture, and precise load-state detection out of the box.
4
-
5
- > **Browser support in craftdriver:** Chrome, Chromium, and Firefox.
6
-
7
- ---
8
-
9
- ## Feature matrix
10
-
11
- Most of craftdriver works against both BiDi and Classic WebDriver. The
12
- features below are **BiDi-only** — they require `enableBiDi: true`
13
- (the default) and a browser that successfully negotiates a BiDi
14
- WebSocket. Calling them after BiDi negotiation failed throws a clear
15
- error; gate them with `browser.isBiDiEnabled()` if your code may run
16
- in Classic mode.
17
-
18
- | Capability | API | BiDi-only? |
19
- |---|---|---|
20
- | Network mocking / interception | [`browser.network.*`](#network-mocking) | yes |
21
- | Console & error log capture | [`browser.logs.*`](#console--error-logs) | yes |
22
- | `waitForLoadState('load' \| 'domcontentloaded' \| 'networkidle')` | `browser.waitForLoadState()` | no — event-driven over BiDi, polls `document.readyState` in Classic |
23
- | `navigateTo(..., { waitUntil })` real load events | `browser.navigateTo()` | no — event-driven over BiDi, best-effort settle timer in Classic |
24
- | `waitForRequest()` / `waitForResponse()` | `browser.waitForRequest()` / `waitForResponse()` | yes |
25
- | Init scripts (run before any page script) | `browser.addInitScript()` | yes |
26
- | Open new tab / popup | `browser.openPage()` | yes |
27
- | Capture popup opened by an action | `browser.waitForPage()` | yes |
28
- | Isolated user contexts (incognito profiles) | `browser.newContext()` / `browser.contexts()` | yes |
29
- | Downloads | `browser.waitForDownload()` | yes |
30
- | Tracing | `browser.startTrace()` / `browser.stopTrace()` | yes |
31
- | Storage state (cookies + localStorage) | `browser.storage.*`, `saveState()`, `loadState()` | no — works in Classic too |
32
- | Element actions, locators, assertions, frames, dialogs, screenshots, keyboard/mouse, mobile emulation | rest of the API | no — works in Classic too |
33
-
34
- ---
35
-
36
- ## Network Mocking
37
-
38
- Intercept and mock network requests using `browser.network`.
39
-
40
- ### mock(pattern, response)
41
-
42
- Return a mocked response for matching requests. Response can be an object or a function for dynamic mocking.
43
-
44
- ```typescript
45
- // Static mock
46
- await browser.network.mock('**/api/users', {
47
- status: 200,
48
- body: { users: [{ id: 1, name: 'Test User' }] },
49
- });
50
-
51
- // Dynamic mock - response based on request
52
- await browser.network.mock('**/api/items/*', (request) => {
53
- const id = request.url.split('/').pop();
54
- return {
55
- status: 200,
56
- body: { id, name: `Item ${id}`, price: 9.99 },
57
- };
58
- });
59
-
60
- // Navigate - API calls will return mocked data
61
- await browser.navigateTo('https://example.com/dashboard');
62
- ```
63
-
64
- ### block(pattern)
65
-
66
- Block all requests matching the pattern.
67
-
68
- ```typescript
69
- // Block analytics and tracking
70
- await browser.network.block('**/analytics/**');
71
- await browser.network.block('**/tracking/**');
72
- ```
73
-
74
- ### setExtraHeaders(headers)
75
-
76
- Add extra headers to all requests.
77
-
78
- ```typescript
79
- await browser.network.setExtraHeaders({
80
- 'X-Test-Mode': 'true',
81
- Authorization: 'Bearer test-token',
82
- });
83
- ```
84
-
85
- ### setCacheBehavior(behavior)
86
-
87
- Control browser caching behavior.
88
-
89
- ```typescript
90
- // Bypass cache - always fetch fresh
91
- await browser.network.setCacheBehavior('bypass');
92
-
93
- // Use default caching
94
- await browser.network.setCacheBehavior('default');
95
- ```
96
-
97
- ### intercept(pattern, handler)
98
-
99
- Intercept requests and provide custom responses. The handler receives request details and can return a mock response.
100
-
101
- ```typescript
102
- let capturedRequests: string[] = [];
103
-
104
- const interceptId = await browser.network.intercept('**/api/**', async (request) => {
105
- // Log request details
106
- capturedRequests.push(`${request.method} ${request.url}`);
107
-
108
- // Return a mock response
109
- return {
110
- status: 200,
111
- body: { intercepted: true, originalUrl: request.url },
112
- };
113
- });
114
- ```
115
-
116
- ### removeIntercept(interceptId)
117
-
118
- Remove a previously registered intercept using the ID returned from `intercept()` or `mock()`.
119
-
120
- ```typescript
121
- const interceptId = await browser.network.mock('**/api/users', { status: 200, body: {} });
122
- // ... later
123
- await browser.network.removeIntercept(interceptId);
124
- ```
125
-
126
- ### Examples
127
-
128
- #### Mock API Error
129
-
130
- ```typescript
131
- await browser.network.mock('**/api/login', {
132
- status: 401,
133
- body: { error: 'Invalid credentials' },
134
- });
135
-
136
- await browser.find('#username').fill('baduser');
137
- await browser.find('#password').fill('wrongpass');
138
- await browser.find('#submit').click();
139
-
140
- await browser.expect('#error').toHaveText('Invalid credentials');
141
- ```
142
-
143
- #### Test Slow Network
144
-
145
- ```typescript
146
- await browser.network.intercept('**/api/**', async (request) => {
147
- // Simulate slow network
148
- await new Promise((resolve) => setTimeout(resolve, 3000));
149
- // Return mock response after delay
150
- return { status: 200, body: { data: 'delayed response' } };
151
- });
152
-
153
- // Test loading state appears
154
- await browser.find('#load-data').click();
155
- await browser.expect('#loading-spinner').toBeVisible();
156
- ```
157
-
158
- ---
159
-
160
- ## Waiting for Network
161
-
162
- `browser.waitForRequest` and `browser.waitForResponse` let you observe real network
163
- traffic without intercepting it. Register them **before** the action that triggers
164
- the request — the canonical pattern is `Promise.all`:
165
-
166
- ```typescript
167
- const [response] = await Promise.all([
168
- browser.waitForResponse('**/api/users'),
169
- browser.click('#load-users'),
170
- ]);
171
- expect(response.status).toBe(200);
172
- ```
173
-
174
- Both accept a URL **glob** (same `**` syntax as `network.mock`) or a **predicate**:
175
-
176
- ```typescript
177
- // Glob — matches by pathname
178
- const [res] = await Promise.all([
179
- browser.waitForResponse('**/api/users'),
180
- browser.click('#load-users'),
181
- ]);
182
-
183
- // Predicate — full control over matching
184
- const [res2] = await Promise.all([
185
- browser.waitForResponse(r => r.url.includes('/api/users') && r.status === 200),
186
- browser.click('#load-users'),
187
- ]);
188
- ```
189
-
190
- ### waitForResponse(pattern, opts?)
191
-
192
- Resolves with an `InterceptedResponse` once a matching completed response arrives.
193
-
194
- | Property | Type | Description |
195
- | ----------- | -------------------------- | ------------------------------------- |
196
- | `url` | `string` | Full request URL |
197
- | `status` | `number` | HTTP status code |
198
- | `statusText`| `string` | E.g. `"OK"` |
199
- | `headers` | `Record<string, string>` | Response headers |
200
- | `mimeType` | `string` | E.g. `"application/json"` |
201
- | `fromCache` | `boolean` | Whether served from browser cache |
202
- | `request` | `{ id, url, method, headers }` | Matching request info |
203
-
204
- ### waitForRequest(pattern, opts?)
205
-
206
- Resolves with an `InterceptedRequest` as soon as the browser sends the request
207
- (before a response arrives). Useful for asserting that a request was made with
208
- the right method/headers without waiting for the response.
209
-
210
- | Property | Type | Description |
211
- | --------- | ------------------------ | ---------------------- |
212
- | `id` | `string` | BiDi request id |
213
- | `url` | `string` | Full URL |
214
- | `method` | `string` | `"GET"`, `"POST"`, … |
215
- | `headers` | `Record<string, string>` | Request headers |
216
-
217
- ### Timeout
218
-
219
- Both methods accept `{ timeout?: number }` (defaults to the browser navigation
220
- timeout, 30 s). On timeout a clear error is thrown:
221
-
222
- ```
223
- waitForResponse("**/api/users") timed out after 30000ms
224
- ```
225
-
226
- ---
227
-
228
- ## Console & Error Logs
229
-
230
- Access browser console output and JavaScript errors via `browser.logs`.
231
-
232
- > **Capture is lazy by default.** Craftdriver only subscribes to log events
233
- > the first time you touch `browser.logs.onLog()` / `.onConsole()` /
234
- > `.onError()` / `.on()` / `.waitForConsole()` / `.waitForError()` — messages
235
- > emitted before that first touch are not captured, so a bare
236
- > `browser.logs.getMessages()` right after `navigateTo()` can return `[]`.
237
- > Either arm a listener before the action that logs, or launch with
238
- > `Browser.launch({ captureLogs: true })` to start capture immediately.
239
-
240
- ### getMessages()
241
-
242
- Get all console messages.
243
-
244
- ```typescript
245
- const messages = browser.logs.getMessages();
246
-
247
- for (const msg of messages) {
248
- console.log(`[${msg.level}] ${msg.text}`);
249
- }
250
- ```
251
-
252
- Each message has:
253
-
254
- - `type`: Always `'console'`
255
- - `level`: `'debug'`, `'info'`, `'warn'`, or `'error'`
256
- - `text`: The message content
257
- - `method`: Console method used (`'log'`, `'warn'`, `'error'`, `'info'`, `'debug'`)
258
- - `args`: Array of arguments passed to console
259
- - `timestamp`: When the message was logged (Date object)
260
- - `stackTrace`: Array of stack frames (optional)
261
-
262
- ### getLogsByLevel(level)
263
-
264
- Get logs filtered by level.
265
-
266
- ```typescript
267
- // Only warnings
268
- const warnings = browser.logs.getLogsByLevel('warn');
269
-
270
- // Only errors
271
- const errors = browser.logs.getLogsByLevel('error');
272
- ```
273
-
274
- ### getErrors()
275
-
276
- Get JavaScript errors that occurred on the page.
277
-
278
- ```typescript
279
- const errors = browser.logs.getErrors();
280
-
281
- for (const error of errors) {
282
- console.log(`Error: ${error.text}`);
283
- if (error.stackTrace) {
284
- for (const frame of error.stackTrace) {
285
- console.log(` at ${frame.functionName} (${frame.url}:${frame.lineNumber})`);
286
- }
287
- }
288
- }
289
- ```
290
-
291
- Each error has:
292
-
293
- - `type`: Always `'javascript'`
294
- - `level`: Always `'error'`
295
- - `text`: The error message
296
- - `timestamp`: When the error occurred (Date object)
297
- - `stackTrace`: Array of stack frames (optional), each with:
298
- - `functionName`: Name of the function
299
- - `url`: Source file URL
300
- - `lineNumber`: Line number
301
- - `columnNumber`: Column number
302
-
303
- ### clearLogs()
304
-
305
- Clear all collected logs (both console messages and errors).
306
-
307
- ```typescript
308
- browser.logs.clearLogs();
309
- ```
310
-
311
- ### onError(handler)
312
-
313
- Subscribe to JavaScript errors in real-time.
314
-
315
- ```typescript
316
- const unsubscribe = browser.logs.onError((error) => {
317
- console.log('JS Error detected:', error.text);
318
- // Take screenshot, log to file, etc.
319
- });
320
-
321
- // Later: stop listening
322
- unsubscribe();
323
- ```
324
-
325
- ### onConsole(handler)
326
-
327
- Subscribe to console messages in real-time.
328
-
329
- ```typescript
330
- const unsubscribe = browser.logs.onConsole((msg) => {
331
- if (msg.level === 'error') {
332
- console.log('Console error:', msg.text);
333
- }
334
- });
335
- ```
336
-
337
- ### Examples
338
-
339
- #### Verify No Console Errors
340
-
341
- ```typescript
342
- await browser.navigateTo('https://example.com');
343
-
344
- // Interact with the page
345
- await browser.find('#button').click();
346
- await browser.pause(1000);
347
-
348
- // Verify no errors occurred
349
- const errors = browser.logs.getErrors();
350
- expect(errors).toHaveLength(0);
351
- ```
352
-
353
- #### Check for Expected Log
354
-
355
- ```typescript
356
- await browser.find('#track-event').click();
357
-
358
- const messages = browser.logs.getMessages();
359
- const trackingLogs = messages.filter((m) => m.text.includes('Analytics event:'));
360
-
361
- expect(trackingLogs.length).toBeGreaterThan(0);
362
- ```
363
-
364
- #### Debug Test Failures
365
-
366
- ```typescript
367
- test('form submission', async () => {
368
- const browser = await Browser.launch({ browserName: 'chrome' });
369
-
370
- try {
371
- await browser.navigateTo('https://example.com/form');
372
- await browser.find('#submit').click();
373
- await browser.expect('#success').toBeVisible();
374
- } catch (error) {
375
- // On failure, log browser console output
376
- console.log('Console messages:', browser.logs.getMessages());
377
- console.log('JS errors:', browser.logs.getErrors());
378
- throw error;
379
- } finally {
380
- await browser.quit();
381
- }
382
- });
383
- ```
384
-
385
- ---
386
-
387
- ## Session Storage
388
-
389
- Manage cookies and browser storage via `browser.storage`.
390
-
391
- ### addCookie(cookie)
392
-
393
- Add a cookie.
394
-
395
- ```typescript
396
- await browser.storage.addCookie({
397
- name: 'session_id',
398
- value: 'abc123',
399
- domain: 'example.com',
400
- path: '/',
401
- secure: true,
402
- httpOnly: true,
403
- sameSite: 'Lax',
404
- expiry: new Date('2027-01-01'),
405
- });
406
- ```
407
-
408
- ### getCookies(filter?)
409
-
410
- Get cookies, optionally filtered.
411
-
412
- ```typescript
413
- // All cookies
414
- const cookies = await browser.storage.getCookies();
415
-
416
- // Filter by domain
417
- const sessionCookies = await browser.storage.getCookies({ domain: 'example.com' });
418
-
419
- // Cookie value is a plain string
420
- for (const cookie of cookies) {
421
- console.log(`${cookie.name}=${cookie.value}`);
422
- }
423
- ```
424
-
425
- ### clearCookies(filter?)
426
-
427
- Clear cookies, optionally filtered.
428
-
429
- ```typescript
430
- // Clear all cookies
431
- await browser.storage.clearCookies();
432
-
433
- // Clear specific domain
434
- await browser.storage.clearCookies({ domain: 'example.com' });
435
- ```
436
-
437
- ### saveState(path, options?) / loadState(path)
438
-
439
- Save and restore session state (cookies + localStorage) - Playwright-style persistence.
440
-
441
- ```typescript
442
- // Save login session
443
- await browser.navigateTo('https://example.com/login');
444
- await browser.find('#username').fill('user');
445
- await browser.find('#password').fill('pass');
446
- await browser.find('#submit').click();
447
- await browser.saveState('./auth.json');
448
-
449
- // Later: restore session in new browser
450
- const browser2 = await Browser.launch({
451
- browserName: 'chrome',
452
- storageState: './auth.json',
453
- });
454
- // Or manually:
455
- await browser2.loadState('./auth.json');
456
- ```
457
-
458
- ### saveState options
459
-
460
- ```typescript
461
- await browser.saveState('./state.json', {
462
- includeCookies: true, // default: true
463
- includeLocalStorage: true, // default: true
464
- includeSessionStorage: false, // default: false
465
- origins: ['https://example.com'], // specific origins only
466
- });
467
- ```
468
-
469
- ---
470
-