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,182 @@
1
+ # Assertions
2
+
3
+ CraftDriver provides a fluent assertion API via `browser.expect()` for testing element states.
4
+
5
+ > For accessibility assertions, see [docs/accessibility.md](./accessibility.md) — `browser.a11y.check()` is the assertion form of the axe-core wrapper.
6
+
7
+ ## Basic Usage
8
+
9
+ ```typescript
10
+ // Assert on element state
11
+ await browser.expect('#message').toHaveText('Success!');
12
+ await browser.expect('#email').toHaveValue('test@example.com');
13
+ await browser.expect('#modal').toBeVisible();
14
+ ```
15
+
16
+ ## Assertion Methods
17
+
18
+ ### toHaveText(expected)
19
+
20
+ Assert that the element's text content matches exactly.
21
+
22
+ ```typescript
23
+ await browser.expect('#heading').toHaveText('Welcome');
24
+ await browser.expect('.alert').toHaveText('Form submitted successfully');
25
+ ```
26
+
27
+ ### toContainText(expected)
28
+
29
+ Assert that the element's text content contains the expected substring.
30
+
31
+ ```typescript
32
+ await browser.expect('#paragraph').toContainText('important');
33
+ await browser.expect('.notification').toContainText('saved');
34
+ ```
35
+
36
+ ### toHaveValue(expected)
37
+
38
+ Assert that an input element has the expected value.
39
+
40
+ ```typescript
41
+ await browser.expect('#username').toHaveValue('testuser');
42
+ await browser.expect('#quantity').toHaveValue('5');
43
+ ```
44
+
45
+ ### toBeVisible()
46
+
47
+ Assert that the element is visible on the page.
48
+
49
+ ```typescript
50
+ await browser.expect('#success-modal').toBeVisible();
51
+ await browser.expect('.tooltip').toBeVisible();
52
+ ```
53
+
54
+ ### toHaveAttribute(name, value)
55
+
56
+ Assert that the element has an attribute with the expected value.
57
+
58
+ ```typescript
59
+ await browser.expect('#link').toHaveAttribute('href', '/dashboard');
60
+ await browser.expect('#input').toHaveAttribute('disabled', 'true');
61
+ await browser.expect('#image').toHaveAttribute('alt', 'Product photo');
62
+ ```
63
+
64
+ ## Negation
65
+
66
+ Use `.not` to negate any assertion:
67
+
68
+ ```typescript
69
+ await browser.expect('#error').not.toBeVisible();
70
+ await browser.expect('#input').not.toHaveValue('');
71
+ await browser.expect('#button').not.toHaveAttribute('disabled', 'true');
72
+ ```
73
+
74
+ ## Element-Scoped Assertions
75
+
76
+ You can also get an assertion API scoped to an ElementHandle:
77
+
78
+ ```typescript
79
+ const message = browser.find('#message');
80
+ await message.expect().toHaveText('Success');
81
+ await message.expect().toBeVisible();
82
+ ```
83
+
84
+ This is useful when you're already working with an element reference:
85
+
86
+ ```typescript
87
+ const form = browser.find('#login-form');
88
+
89
+ // Fill the form
90
+ await browser.find('#username').fill('testuser');
91
+ await browser.find('#password').fill('secret');
92
+ await browser.find('#submit').click();
93
+
94
+ // Assert on result
95
+ await browser.find('#result').expect().toHaveText('Login successful');
96
+ ```
97
+
98
+ ## Waiting Behavior
99
+
100
+ All assertions automatically wait for the condition to be true, up to a configurable timeout.
101
+
102
+ ```typescript
103
+ // Uses the browser-level default (5000 ms unless changed)
104
+ await browser.expect('#loading').not.toBeVisible();
105
+
106
+ // Per-call timeout override
107
+ await browser.expect('#data').toHaveText('Loaded', { timeout: 10000 });
108
+ await browser.expect('#modal').toBeVisible({ timeout: 2000 });
109
+ await browser.expect('#result').toContainText('success', { timeout: 8000 });
110
+ ```
111
+
112
+ ### Changing the default timeout
113
+
114
+ Use `browser.setDefaultTimeout()` to raise or lower the default for **all**
115
+ subsequent assertions (and actions) on that browser instance:
116
+
117
+ ```typescript
118
+ browser.setDefaultTimeout(10000); // all assertions now wait up to 10 s by default
119
+
120
+ await browser.expect('#slow-widget').toBeVisible(); // uses 10 s
121
+ await browser.expect('#fast-check').toHaveText('ok', { timeout: 500 }); // per-call wins
122
+ ```
123
+
124
+ See [Browser API — Configuring timeouts](./browser-api.md#configuring-timeouts) for full details.
125
+
126
+ ## Examples
127
+
128
+ ### Form Validation
129
+
130
+ ```typescript
131
+ await browser.find('#email').fill('invalid-email');
132
+ await browser.find('#submit').click();
133
+
134
+ await browser.expect('#email-error').toBeVisible();
135
+ await browser.expect('#email-error').toHaveText('Please enter a valid email');
136
+ ```
137
+
138
+ ### Navigation Confirmation
139
+
140
+ ```typescript
141
+ await browser.find('#logout').click();
142
+
143
+ await browser.expect('#login-form').toBeVisible();
144
+ await browser.expect('.welcome-message').not.toBeVisible();
145
+ ```
146
+
147
+ ### Successful Form Submission
148
+
149
+ ```typescript
150
+ await browser.find('#name').fill('John Doe');
151
+ await browser.find('#email').fill('john@example.com');
152
+ await browser.find('#submit').click();
153
+
154
+ await browser.expect('#success-message').toBeVisible();
155
+ await browser.expect('#success-message').toContainText('Thank you');
156
+ await browser.expect('#form').not.toBeVisible();
157
+ ```
158
+
159
+ ### Attribute Verification
160
+
161
+ ```typescript
162
+ // Check link destination
163
+ await browser.expect('#dashboard-link').toHaveAttribute('href', '/dashboard');
164
+
165
+ // Check disabled state
166
+ await browser.expect('#submit').not.toHaveAttribute('disabled', 'true');
167
+
168
+ // After disabling
169
+ await browser.find('#submit').click();
170
+ await browser.expect('#submit').toHaveAttribute('disabled', 'true');
171
+ ```
172
+
173
+ ### Page Title and URL
174
+
175
+ ```typescript
176
+ // Use browser methods directly for page-level checks
177
+ const title = await browser.title();
178
+ expect(title).toBe('Dashboard'); // Using Vitest/Jest expect
179
+
180
+ const url = await browser.url();
181
+ expect(url).toContain('/dashboard');
182
+ ```
@@ -0,0 +1,462 @@
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()` | yes |
23
+ | `navigateTo(..., { waitUntil })` real load events | `browser.navigateTo()` | yes |
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
+ ### getMessages()
233
+
234
+ Get all console messages.
235
+
236
+ ```typescript
237
+ const messages = browser.logs.getMessages();
238
+
239
+ for (const msg of messages) {
240
+ console.log(`[${msg.level}] ${msg.text}`);
241
+ }
242
+ ```
243
+
244
+ Each message has:
245
+
246
+ - `type`: Always `'console'`
247
+ - `level`: `'debug'`, `'info'`, `'warn'`, or `'error'`
248
+ - `text`: The message content
249
+ - `method`: Console method used (`'log'`, `'warn'`, `'error'`, `'info'`, `'debug'`)
250
+ - `args`: Array of arguments passed to console
251
+ - `timestamp`: When the message was logged (Date object)
252
+ - `stackTrace`: Array of stack frames (optional)
253
+
254
+ ### getLogsByLevel(level)
255
+
256
+ Get logs filtered by level.
257
+
258
+ ```typescript
259
+ // Only warnings
260
+ const warnings = browser.logs.getLogsByLevel('warn');
261
+
262
+ // Only errors
263
+ const errors = browser.logs.getLogsByLevel('error');
264
+ ```
265
+
266
+ ### getErrors()
267
+
268
+ Get JavaScript errors that occurred on the page.
269
+
270
+ ```typescript
271
+ const errors = browser.logs.getErrors();
272
+
273
+ for (const error of errors) {
274
+ console.log(`Error: ${error.text}`);
275
+ if (error.stackTrace) {
276
+ for (const frame of error.stackTrace) {
277
+ console.log(` at ${frame.functionName} (${frame.url}:${frame.lineNumber})`);
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
283
+ Each error has:
284
+
285
+ - `type`: Always `'javascript'`
286
+ - `level`: Always `'error'`
287
+ - `text`: The error message
288
+ - `timestamp`: When the error occurred (Date object)
289
+ - `stackTrace`: Array of stack frames (optional), each with:
290
+ - `functionName`: Name of the function
291
+ - `url`: Source file URL
292
+ - `lineNumber`: Line number
293
+ - `columnNumber`: Column number
294
+
295
+ ### clearLogs()
296
+
297
+ Clear all collected logs (both console messages and errors).
298
+
299
+ ```typescript
300
+ browser.logs.clearLogs();
301
+ ```
302
+
303
+ ### onError(handler)
304
+
305
+ Subscribe to JavaScript errors in real-time.
306
+
307
+ ```typescript
308
+ const unsubscribe = browser.logs.onError((error) => {
309
+ console.log('JS Error detected:', error.text);
310
+ // Take screenshot, log to file, etc.
311
+ });
312
+
313
+ // Later: stop listening
314
+ unsubscribe();
315
+ ```
316
+
317
+ ### onConsole(handler)
318
+
319
+ Subscribe to console messages in real-time.
320
+
321
+ ```typescript
322
+ const unsubscribe = browser.logs.onConsole((msg) => {
323
+ if (msg.level === 'error') {
324
+ console.log('Console error:', msg.text);
325
+ }
326
+ });
327
+ ```
328
+
329
+ ### Examples
330
+
331
+ #### Verify No Console Errors
332
+
333
+ ```typescript
334
+ await browser.navigateTo('https://example.com');
335
+
336
+ // Interact with the page
337
+ await browser.find('#button').click();
338
+ await browser.pause(1000);
339
+
340
+ // Verify no errors occurred
341
+ const errors = browser.logs.getErrors();
342
+ expect(errors).toHaveLength(0);
343
+ ```
344
+
345
+ #### Check for Expected Log
346
+
347
+ ```typescript
348
+ await browser.find('#track-event').click();
349
+
350
+ const messages = browser.logs.getMessages();
351
+ const trackingLogs = messages.filter((m) => m.text.includes('Analytics event:'));
352
+
353
+ expect(trackingLogs.length).toBeGreaterThan(0);
354
+ ```
355
+
356
+ #### Debug Test Failures
357
+
358
+ ```typescript
359
+ test('form submission', async () => {
360
+ const browser = await Browser.launch({ browserName: 'chrome' });
361
+
362
+ try {
363
+ await browser.navigateTo('https://example.com/form');
364
+ await browser.find('#submit').click();
365
+ await browser.expect('#success').toBeVisible();
366
+ } catch (error) {
367
+ // On failure, log browser console output
368
+ console.log('Console messages:', browser.logs.getMessages());
369
+ console.log('JS errors:', browser.logs.getErrors());
370
+ throw error;
371
+ } finally {
372
+ await browser.quit();
373
+ }
374
+ });
375
+ ```
376
+
377
+ ---
378
+
379
+ ## Session Storage
380
+
381
+ Manage cookies and browser storage via `browser.storage`.
382
+
383
+ ### addCookie(cookie)
384
+
385
+ Add a cookie.
386
+
387
+ ```typescript
388
+ await browser.storage.addCookie({
389
+ name: 'session_id',
390
+ value: 'abc123',
391
+ domain: 'example.com',
392
+ path: '/',
393
+ secure: true,
394
+ httpOnly: true,
395
+ sameSite: 'Lax',
396
+ expiry: new Date('2027-01-01'),
397
+ });
398
+ ```
399
+
400
+ ### getCookies(filter?)
401
+
402
+ Get cookies, optionally filtered.
403
+
404
+ ```typescript
405
+ // All cookies
406
+ const cookies = await browser.storage.getCookies();
407
+
408
+ // Filter by domain
409
+ const sessionCookies = await browser.storage.getCookies({ domain: 'example.com' });
410
+
411
+ // Cookie value is a plain string
412
+ for (const cookie of cookies) {
413
+ console.log(`${cookie.name}=${cookie.value}`);
414
+ }
415
+ ```
416
+
417
+ ### clearCookies(filter?)
418
+
419
+ Clear cookies, optionally filtered.
420
+
421
+ ```typescript
422
+ // Clear all cookies
423
+ await browser.storage.clearCookies();
424
+
425
+ // Clear specific domain
426
+ await browser.storage.clearCookies({ domain: 'example.com' });
427
+ ```
428
+
429
+ ### saveState(path, options?) / loadState(path)
430
+
431
+ Save and restore session state (cookies + localStorage) - Playwright-style persistence.
432
+
433
+ ```typescript
434
+ // Save login session
435
+ await browser.navigateTo('https://example.com/login');
436
+ await browser.find('#username').fill('user');
437
+ await browser.find('#password').fill('pass');
438
+ await browser.find('#submit').click();
439
+ await browser.saveState('./auth.json');
440
+
441
+ // Later: restore session in new browser
442
+ const browser2 = await Browser.launch({
443
+ browserName: 'chrome',
444
+ storageState: './auth.json',
445
+ });
446
+ // Or manually:
447
+ await browser2.loadState('./auth.json');
448
+ ```
449
+
450
+ ### saveState options
451
+
452
+ ```typescript
453
+ await browser.saveState('./state.json', {
454
+ includeCookies: true, // default: true
455
+ includeLocalStorage: true, // default: true
456
+ includeSessionStorage: false, // default: false
457
+ origins: ['https://example.com'], // specific origins only
458
+ });
459
+ ```
460
+
461
+ ---
462
+