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,194 @@
1
+ # craftdriver — cheatsheet
2
+
3
+ Compact reference for writing tests. Pair with
4
+ [docs/api-reference.md](../../docs/api-reference.md) for the full export
5
+ list.
6
+
7
+ ## Launch & teardown
8
+
9
+ ```ts
10
+ import { Browser } from 'craftdriver';
11
+
12
+ const browser = await Browser.launch({
13
+ browserName: 'chrome', // 'chrome' | 'chromium' | 'firefox'
14
+ headless: true,
15
+ enableBiDi: true, // required for network / logs / tracing / init scripts
16
+ });
17
+ try {
18
+ await browser.navigateTo('https://example.com');
19
+ // ...
20
+ } finally {
21
+ await browser.quit(); // never wrap in try/catch in tests
22
+ }
23
+ ```
24
+
25
+ ## Navigation
26
+
27
+ ```ts
28
+ await browser.navigateTo(url, { waitUntil: 'load' | 'domcontentloaded' | 'networkidle' });
29
+ await browser.goBack();
30
+ await browser.goForward();
31
+ await browser.reload();
32
+ await browser.waitForLoadState('load');
33
+ ```
34
+
35
+ ## Selectors
36
+
37
+ ```ts
38
+ import { By } from 'craftdriver';
39
+
40
+ By.testId('submit') // [data-testid="submit"] ← preferred
41
+ By.role('button', { name: /save/i }) // ARIA role + accessible name
42
+ By.labelText('Email') // form label
43
+ By.text('Sign in', { exact: true }) // visible text
44
+ By.css('button.primary') // last resort
45
+ By.xpath('//button') // never if anything else works
46
+ ```
47
+
48
+ ## Locators (Playwright-style)
49
+
50
+ ```ts
51
+ const submit = browser.locator(By.role('button', { name: 'Submit' }));
52
+
53
+ await submit.click(); // auto-waits visible
54
+ await submit.fill('hello'); // click + clear + type
55
+ await submit.hover();
56
+ await submit.expect().toBeVisible();
57
+ await submit.expect().toHaveText('Submit');
58
+ await submit.expect().toBeEnabled();
59
+
60
+ // composition
61
+ const row = browser.locator('.row').filter({ hasText: 'Acme' }).first();
62
+ const cells = await row.locator('td').all();
63
+ const count = await row.locator('td').count(); // 0-wait probe
64
+ ```
65
+
66
+ ## Element handles
67
+
68
+ ```ts
69
+ const el = await browser.find(By.css('#submit'));
70
+ await el.click();
71
+ await el.sendKeys('hello');
72
+ await el.clear();
73
+ const text = await el.getText();
74
+ const value = await el.getValue();
75
+ const tag = await el.tagName();
76
+ const isVisible = await el.isDisplayed();
77
+ ```
78
+
79
+ ## Assertions (`expect`)
80
+
81
+ ```ts
82
+ await browser.locator('h1').expect().toHaveText(/welcome/i);
83
+ await browser.locator('input').expect().toHaveValue('jane@example.com');
84
+ await browser.locator('button').expect().toBeEnabled();
85
+ await browser.locator('.spinner').expect().not.toBeVisible();
86
+ ```
87
+
88
+ Every `expect(...).to…()` auto-waits up to the default timeout. Bump
89
+ per-call with `{ timeout: 10_000 }`.
90
+
91
+ ## Errors
92
+
93
+ ```ts
94
+ import { CraftdriverError, ErrorCode } from 'craftdriver';
95
+
96
+ try {
97
+ await browser.locator('#missing').click();
98
+ } catch (err) {
99
+ if (CraftdriverError.is(err, ErrorCode.NO_MATCH)) {
100
+ // selector is wrong — see err.detail.selector / err.hint
101
+ }
102
+ }
103
+ ```
104
+
105
+ Codes: `NO_MATCH`, `TIMEOUT_WAITING_VISIBLE`, `TIMEOUT_WAITING_STATE`,
106
+ `TIMEOUT_WAITING_LOAD`, `TIMEOUT_WAITING_NETWORK`,
107
+ `TIMEOUT_WAITING_DIALOG`, `TIMEOUT`, `EXPECT_MISMATCH`,
108
+ `A11Y_VIOLATIONS`, `EVAL_THREW`, `EVAL_BAD_ARG`, `INVALID_ARGUMENT`,
109
+ `UNSUPPORTED`, `STATE_INVALID`, `DRIVER_ERROR`. Full table:
110
+ [docs/error-codes.md](../../docs/error-codes.md).
111
+
112
+ ## Pages and contexts
113
+
114
+ ```ts
115
+ const ctx = await browser.newContext(); // isolated profile (BiDi)
116
+ const page = await ctx.newPage();
117
+ await page.navigateTo(url);
118
+
119
+ const pages = browser.pages();
120
+ const fresh = await browser.waitForPage(() => browser.click('a[target=_blank]'));
121
+ ```
122
+
123
+ ## Network (BiDi)
124
+
125
+ ```ts
126
+ await browser.network.intercept({ url: '**/api/users', response: { status: 200, body: '[]' } });
127
+ const req = await browser.waitForRequest((r) => r.url.includes('/api/login'));
128
+ const res = await browser.waitForResponse((r) => r.url.includes('/api/me'));
129
+ await browser.network.waitForNetworkIdle();
130
+ ```
131
+
132
+ ## Logs (BiDi)
133
+
134
+ ```ts
135
+ const logs = browser.logs.consoleMessages();
136
+ const errors = browser.logs.javaScriptErrors();
137
+ ```
138
+
139
+ ## Input
140
+
141
+ ```ts
142
+ await browser.keyboard.press('Enter');
143
+ await browser.keyboard.type('hello');
144
+ await browser.mouse.move({ x: 100, y: 100 });
145
+ await browser.mouse.click({ x: 100, y: 100 });
146
+ await browser.actions().keyDown('Shift').click(el).keyUp('Shift').perform();
147
+ ```
148
+
149
+ ## Files
150
+
151
+ ```ts
152
+ await element.setInputFiles('./fixtures/sample.txt');
153
+ const download = await browser.waitForDownload(() => browser.click('#download'));
154
+ ```
155
+
156
+ ## Screenshots & tracing
157
+
158
+ ```ts
159
+ await browser.screenshot({ path: 'out.png', fullPage: true });
160
+
161
+ await browser.startTrace({ outDir: './artefacts/run' });
162
+ try { /* ... */ } finally { await browser.stopTrace(); }
163
+ ```
164
+
165
+ ## Accessibility
166
+
167
+ ```ts
168
+ const result = await browser.a11y.audit(); // returns violations
169
+ await browser.a11y.check(); // throws A11yError if any
170
+ ```
171
+
172
+ ## Virtual clock
173
+
174
+ ```ts
175
+ await browser.clock.install({ time: '2026-01-01T00:00:00Z' });
176
+ await browser.clock.tick(1000);
177
+ await browser.clock.fastForward('05:00'); // 5 minutes
178
+ await browser.clock.uninstall();
179
+ ```
180
+
181
+ ## Emulation
182
+
183
+ ```ts
184
+ await browser.emulate({
185
+ colorScheme: 'dark',
186
+ reducedMotion: 'reduce',
187
+ locale: 'fr-FR',
188
+ timezoneId: 'Europe/Paris',
189
+ offline: true,
190
+ });
191
+ await browser.setViewportSize({ width: 1280, height: 720 });
192
+ await browser.setGeolocation({ latitude: 48.85, longitude: 2.35 });
193
+ await browser.grantPermissions(['geolocation']);
194
+ ```
@@ -0,0 +1,104 @@
1
+ # craftdriver CLI
2
+
3
+ The `craftdriver` binary is what you reach for from a **shell**, not from
4
+ TypeScript. Use it for probing, debugging, and agent-driven exploration.
5
+
6
+ ## Install once
7
+
8
+ ```bash
9
+ npm install craftdriver
10
+ npx craftdriver --help
11
+ ```
12
+
13
+ ## Daemon model (preferred)
14
+
15
+ Long-lived browser; each command is a one-shot RPC. State (page,
16
+ cookies, storage) survives between calls.
17
+
18
+ ```bash
19
+ npx craftdriver daemon start
20
+ npx craftdriver go http://127.0.0.1:8080/login.html
21
+ npx craftdriver click 'button[type=submit]'
22
+ npx craftdriver daemon stop
23
+ ```
24
+
25
+ If no daemon is running, the first command auto-starts one. Use
26
+ `daemon start` explicitly when you want a non-default browser
27
+ (`--browser firefox`) or a specific timing.
28
+
29
+ ## Ephemeral mode (sandboxed agents)
30
+
31
+ ```bash
32
+ printf 'go http://127.0.0.1:8080/login.html
33
+ fill "#user" alice
34
+ click "button[type=submit]"
35
+ text "#result"
36
+ ' | npx craftdriver --ephemeral
37
+ ```
38
+
39
+ One short-lived browser for the whole script. No daemon, no socket.
40
+
41
+ ## Commands you actually use
42
+
43
+ ```
44
+ go <url> navigate active page
45
+ find <sel> [--all] first match (or all); pretty-prints index + tag + text
46
+ exists <sel> 0-wait probe; exit 0 = match, exit 1 = none
47
+ click <sel>
48
+ fill <sel> <value>
49
+ press <key> [sel]
50
+ hover <sel>
51
+ text [sel] page text or element text
52
+ attr <sel> <name>
53
+ value <sel>
54
+ is visible|enabled|checked <sel>
55
+ wait <sel> [--state visible|hidden|attached|detached] [--timeout ms]
56
+ wait load [--state load|domcontentloaded|networkidle]
57
+ pages
58
+ screenshot [-o out.png] [--full-page] [--selector sel]
59
+ eval <js> last resort
60
+ back | forward | reload | status | quit
61
+ daemon start|status|stop
62
+ ```
63
+
64
+ ## Selector syntax
65
+
66
+ CSS by default. Switch kind with `prefix=value`:
67
+
68
+ ```
69
+ role=button[name=Submit] text=Sign In text*=Sign
70
+ label=Email placeholder=Search… testid=login-btn
71
+ alt=Logo title=Help xpath=//div[1]
72
+ id=submit name=email tag=h1
73
+ ```
74
+
75
+ Anything else (including CSS attribute selectors like
76
+ `button[type=submit]`) is parsed as CSS.
77
+
78
+ ## Output
79
+
80
+ - TTY → human-readable text.
81
+ - Piped or redirected → `{"ok":true,"result":…}` per line.
82
+ - Force with `--json` or `--pretty`.
83
+ - Errors include a stable `code:` line (same codes as the library
84
+ — see `docs/error-codes.md`). Use the code, not the prose.
85
+
86
+ ## Exit codes
87
+
88
+ - `0` success (or `exists` matched ≥ 1)
89
+ - `1` assertion / timeout / `NO_MATCH` / `exists` matched zero
90
+ - `2` usage error
91
+
92
+ ## Defaults
93
+
94
+ - 5 s per-call timeout (override with `--timeout ms` or
95
+ `CRAFTDRIVER_AGENT_TIMEOUT`).
96
+ - Probe with `exists` before `click`/`wait` when you're guessing.
97
+ - Don't `sleep`. `wait <sel>` is auto-waiting; `wait load` waits on
98
+ document state.
99
+
100
+ ## When NOT to use the CLI
101
+
102
+ Writing a test suite. Use the library (`import { Browser } from
103
+ 'craftdriver'`) — it has 30 s default timeouts, full TS types, and
104
+ chainable `Locator` ergonomics.
@@ -0,0 +1,139 @@
1
+ # craftdriver — patterns
2
+
3
+ Worked recipes. Each is ≤ ~200 tokens; load on demand from
4
+ [SKILL.md](SKILL.md).
5
+
6
+ ## 1. Login, save storage state for reuse
7
+
8
+ ```ts
9
+ const browser = await Browser.launch({ enableBiDi: true });
10
+ await browser.navigateTo('https://app.example.com/login');
11
+ await browser.locator(By.labelText('Email')).fill('jane@example.com');
12
+ await browser.locator(By.labelText('Password')).fill(process.env.PW!);
13
+ await browser.locator(By.role('button', { name: 'Sign in' })).click();
14
+ await browser.locator(By.testId('dashboard')).expect().toBeVisible();
15
+
16
+ // Persist for fast subsequent runs.
17
+ const state = await browser.defaultContext.storageState();
18
+ await fs.writeFile('.auth/state.json', JSON.stringify(state));
19
+ await browser.quit();
20
+ ```
21
+
22
+ ## 2. Re-use saved login
23
+
24
+ ```ts
25
+ const state = JSON.parse(await fs.readFile('.auth/state.json', 'utf8'));
26
+ const browser = await Browser.launch({
27
+ enableBiDi: true,
28
+ storageState: state,
29
+ });
30
+ await browser.navigateTo('https://app.example.com/dashboard');
31
+ await browser.locator(By.testId('dashboard')).expect().toBeVisible();
32
+ ```
33
+
34
+ ## 3. Wait for a network response after a click
35
+
36
+ ```ts
37
+ const [response] = await Promise.all([
38
+ browser.waitForResponse((r) => r.url.includes('/api/checkout') && r.status === 200),
39
+ browser.locator(By.role('button', { name: 'Pay' })).click(),
40
+ ]);
41
+ const body = await response.text();
42
+ ```
43
+
44
+ ## 4. Upload a file
45
+
46
+ ```ts
47
+ const input = await browser.find(By.css('input[type=file]'));
48
+ await input.setInputFiles('./fixtures/contract.pdf');
49
+ await browser.locator(By.role('button', { name: 'Upload' })).click();
50
+ await browser.locator(By.text('Upload complete')).expect().toBeVisible();
51
+ ```
52
+
53
+ ## 5. Scoped text reads (one row of a table)
54
+
55
+ ```ts
56
+ const row = browser.locator('tr').filter({ hasText: 'Acme Inc.' }).first();
57
+ const status = await row.locator('[data-col=status]').text();
58
+ const due = await row.locator('[data-col=due]').text();
59
+ ```
60
+
61
+ ## 6. Capture a failure trace for an agent or bug report
62
+
63
+ ```ts
64
+ await browser.startTrace({
65
+ outDir: './artefacts/checkout-fail',
66
+ screenshots: 'auto',
67
+ network: true,
68
+ console: true,
69
+ });
70
+ try {
71
+ await runFlow(browser);
72
+ } catch (e) {
73
+ // Trace lands on disk regardless — thrown expects never lose data.
74
+ throw e;
75
+ } finally {
76
+ await browser.stopTrace();
77
+ }
78
+ ```
79
+
80
+ ## 7. Mock an API for deterministic tests
81
+
82
+ ```ts
83
+ await browser.network.intercept({
84
+ url: '**/api/users',
85
+ response: { status: 200, body: JSON.stringify([{ id: 1, name: 'Jane' }]) },
86
+ });
87
+ await browser.navigateTo('/users');
88
+ await browser.locator(By.text('Jane')).expect().toBeVisible();
89
+ ```
90
+
91
+ ## 8. Recovery loop on a flaky element
92
+
93
+ Don't write retry loops by hand — `expect()` already retries. Only
94
+ catch a `CraftdriverError` when you have a meaningful fallback:
95
+
96
+ ```ts
97
+ import { CraftdriverError, ErrorCode } from 'craftdriver';
98
+
99
+ try {
100
+ await browser.locator(By.testId('cookie-banner-accept')).click({ timeout: 2000 });
101
+ } catch (e) {
102
+ if (!CraftdriverError.is(e, ErrorCode.NO_MATCH)) throw e;
103
+ // Banner not shown this session — proceed.
104
+ }
105
+ ```
106
+
107
+ ## 9. Multi-page (popup or new tab)
108
+
109
+ ```ts
110
+ const popup = await browser.waitForPage(() =>
111
+ browser.locator(By.text('Open in new tab')).click()
112
+ );
113
+ await popup.locator(By.role('heading', { name: 'Details' })).expect().toBeVisible();
114
+ ```
115
+
116
+ ## 10. iframe scoping
117
+
118
+ ```ts
119
+ const frame = await browser.frame('iframe#checkout');
120
+ await frame.locator(By.labelText('Card number')).fill('4242 4242 4242 4242');
121
+ ```
122
+
123
+ ## 11. Accessibility gate in CI
124
+
125
+ ```ts
126
+ await browser.navigateTo('/checkout');
127
+ const result = await browser.a11y.audit({ minImpact: 'serious' });
128
+ expect(result.violations).toEqual([]);
129
+ ```
130
+
131
+ ## 12. Virtual clock — exercise a debounce
132
+
133
+ ```ts
134
+ await browser.clock.install({ time: '2026-01-01T00:00:00Z' });
135
+ await browser.locator(By.css('#search')).fill('jane');
136
+ await browser.clock.tick(300); // skip the 300 ms debounce
137
+ await browser.locator(By.testId('results')).expect().toBeVisible();
138
+ await browser.clock.uninstall();
139
+ ```