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,219 @@
1
+ # Element API
2
+
3
+ The `ElementHandle` provides methods for interacting with individual DOM elements.
4
+
5
+ ## Getting an ElementHandle
6
+
7
+ ```typescript
8
+ // Via browser.find()
9
+ const element = browser.find('#my-element');
10
+ const button = browser.find(By.text('Submit'));
11
+
12
+ // Chained operations
13
+ await browser.find('#input').fill('text').press('Enter');
14
+ ```
15
+
16
+ > **Tip:** Use `browser.locator(selector)` instead of `find()` when you need
17
+ > composition, filtering, or indexed access (`.nth()`, `.filter()`, `.all()`).
18
+ > See [Selectors & Locators](./selectors.md#locators-lazy--chainable).
19
+
20
+ ## Methods
21
+
22
+ ### click()
23
+
24
+ Click on the element.
25
+
26
+ ```typescript
27
+ await browser.find('#button').click();
28
+ ```
29
+
30
+ ### fill(text, options?)
31
+
32
+ Fill a text input with the given value.
33
+
34
+ ```typescript
35
+ await browser.find('#username').fill('testuser');
36
+
37
+ // With options
38
+ await browser.find('#email').fill('test@example.com', { timeout: 5000 });
39
+ ```
40
+
41
+ ### clear(options?)
42
+
43
+ Clear the input value.
44
+
45
+ ```typescript
46
+ await browser.find('#search').clear();
47
+ ```
48
+
49
+ ### press(key)
50
+
51
+ Press a keyboard key while focused on the element.
52
+
53
+ ```typescript
54
+ await browser.find('#input').press('Enter');
55
+ await browser.find('#textarea').press('Tab');
56
+ await browser.find('#field').press('Backspace');
57
+ ```
58
+
59
+ Available keys: `'Enter'`, `'Tab'`, `'Escape'`, `'Backspace'`, `'ArrowUp'`, `'ArrowDown'`, `'ArrowLeft'`, `'ArrowRight'`, etc.
60
+
61
+ ### hover(options?)
62
+
63
+ Move the mouse to hover over the element.
64
+
65
+ ```typescript
66
+ // Reveal a tooltip
67
+ await browser.find('#info-icon').hover();
68
+
69
+ // Open a dropdown menu on hover
70
+ await browser.find('.menu-trigger').hover();
71
+ await browser.find('.menu-item').click();
72
+ ```
73
+
74
+ ### select(value, options?)
75
+
76
+ Select an option from a `<select>` dropdown by value.
77
+
78
+ ```typescript
79
+ await browser.find('#country').select('us');
80
+ await browser.find('#language').select('en');
81
+ ```
82
+
83
+ > ⚠️ **Note:** This method only works on `<select>` elements. It will throw an error if used on other element types.
84
+
85
+ ### text()
86
+
87
+ Get the visible text content of the element.
88
+
89
+ ```typescript
90
+ const message = await browser.find('#result').text();
91
+ console.log(message); // "Success!"
92
+ ```
93
+
94
+ ### value()
95
+
96
+ Get the current value of an input element.
97
+
98
+ ```typescript
99
+ const inputValue = await browser.find('#email').value();
100
+ ```
101
+
102
+ ### getAttribute(name)
103
+
104
+ Get an attribute value from the element.
105
+
106
+ ```typescript
107
+ const href = await browser.find('a.link').getAttribute('href');
108
+ const disabled = await browser.find('#btn').getAttribute('disabled');
109
+ ```
110
+
111
+ ### isVisible()
112
+
113
+ Check if the element is visible on the page.
114
+
115
+ ```typescript
116
+ const visible = await browser.find('#modal').isVisible();
117
+ if (visible) {
118
+ await browser.find('#modal .close').click();
119
+ }
120
+ ```
121
+
122
+ ### isEnabled()
123
+
124
+ Check if the element is enabled (not disabled).
125
+
126
+ ```typescript
127
+ const enabled = await browser.find('#submit').isEnabled();
128
+ ```
129
+
130
+ ### isChecked()
131
+
132
+ Check if a checkbox or radio button is checked.
133
+
134
+ ```typescript
135
+ const checked = await browser.find('#agree-terms').isChecked();
136
+ ```
137
+
138
+ ### boundingBox()
139
+
140
+ Get the element's position and size.
141
+
142
+ ```typescript
143
+ const box = await browser.find('#target').boundingBox();
144
+ // { x: 100, y: 200, width: 300, height: 50 }
145
+ ```
146
+
147
+ ### screenshot(opts?)
148
+
149
+ Take a screenshot of just this element. Returns the PNG bytes as a
150
+ `Buffer`; pass `path` to also write the file in one step.
151
+
152
+ ```typescript
153
+ // Get as buffer
154
+ const buffer = await browser.find('#chart').screenshot();
155
+
156
+ // Save to file
157
+ await browser.find('#logo').screenshot({ path: 'logo.png' });
158
+ ```
159
+
160
+ ### expect()
161
+
162
+ Get an ExpectApi scoped to this element for assertions.
163
+
164
+ ```typescript
165
+ await browser.find('#message').expect().toHaveText('Success');
166
+ await browser.find('#input').expect().toHaveValue('test@example.com');
167
+ ```
168
+
169
+ ## Method Chaining
170
+
171
+ ElementHandle methods that don't return values can be chained:
172
+
173
+ ```typescript
174
+ await browser.find('#username').fill('testuser').press('Tab');
175
+
176
+ await browser.find('#password').fill('secret123').press('Enter');
177
+ ```
178
+
179
+ ## Examples
180
+
181
+ ### Form Filling
182
+
183
+ ```typescript
184
+ await browser.find('#first-name').fill('John');
185
+ await browser.find('#last-name').fill('Doe');
186
+ await browser.find('#email').fill('john@example.com');
187
+ await browser.find('#country').select('us');
188
+ await browser.find('#agree-terms').click();
189
+ await browser.find('#submit').click();
190
+ ```
191
+
192
+ ### Hover Interactions
193
+
194
+ ```typescript
195
+ // Reveal dropdown menu
196
+ await browser.find('#user-menu').hover();
197
+ await browser.find('#logout-link').click();
198
+ ```
199
+
200
+ ### Conditional Actions
201
+
202
+ ```typescript
203
+ const modal = browser.find('#cookie-banner');
204
+ if (await modal.isVisible()) {
205
+ await browser.find('#accept-cookies').click();
206
+ }
207
+ ```
208
+
209
+ ### Getting Element State
210
+
211
+ ```typescript
212
+ const button = browser.find('#submit');
213
+
214
+ const text = await button.text();
215
+ const enabled = await button.isEnabled();
216
+ const visible = await button.isVisible();
217
+
218
+ console.log({ text, enabled, visible });
219
+ ```
@@ -0,0 +1,155 @@
1
+ # Emulation
2
+
3
+ `browser.emulate(options)` overrides what a page sees from `matchMedia`,
4
+ `navigator.language`, `Intl.*`, and the network stack. One ergonomic method,
5
+ six independent knobs.
6
+
7
+ ```typescript
8
+ import { Browser } from 'craftdriver';
9
+
10
+ const browser = await Browser.launch({ browserName: 'chrome' });
11
+
12
+ await browser.emulate({
13
+ colorScheme: 'dark',
14
+ locale: 'de-DE',
15
+ timezoneId: 'Europe/Berlin',
16
+ });
17
+ ```
18
+
19
+ > **BiDi required.** All emulation overrides use BiDi commands
20
+ > (`enableBiDi: true`, the default). Calling `emulate()` with BiDi
21
+ > disabled throws. CSS media features and `offline` additionally require
22
+ > Chromium (see the cross-browser column below).
23
+
24
+ ## Options
25
+
26
+ | Option | What the page sees | Transport | Cross-browser |
27
+ |---|---|---|---|
28
+ | `colorScheme` | `prefers-color-scheme` media query | BiDi + CDP `Emulation.setEmulatedMedia` | Chromium only |
29
+ | `reducedMotion` | `prefers-reduced-motion` media query | BiDi + CDP `Emulation.setEmulatedMedia` | Chromium only |
30
+ | `forcedColors` | `forced-colors` media query | BiDi + CDP `Emulation.setEmulatedMedia` | Chromium only |
31
+ | `locale` | `navigator.language`, `Intl.*` formatting | BiDi `emulation.setLocaleOverride` | yes |
32
+ | `timezoneId` | `Intl.DateTimeFormat`, `Date` offsets | BiDi `emulation.setTimezoneOverride` | yes |
33
+ | `offline` | `navigator.onLine`, network stack | BiDi + CDP `Network.emulateNetworkConditions` | Chromium only |
34
+
35
+ - Pass `null` for any field to clear that override.
36
+ - Calling `emulate({})` is a no-op.
37
+ - Settings persist across navigations in the same session.
38
+
39
+ ### Type
40
+
41
+ ```typescript
42
+ interface EmulateOptions {
43
+ colorScheme?: 'light' | 'dark' | 'no-preference' | null;
44
+ reducedMotion?: 'reduce' | 'no-preference' | null;
45
+ forcedColors?: 'active' | 'none' | null;
46
+ locale?: string | null; // BCP-47, e.g. 'de-DE'
47
+ timezoneId?: string | null; // IANA, e.g. 'Europe/Berlin'
48
+ offline?: boolean;
49
+ }
50
+ ```
51
+
52
+ ## Use cases
53
+
54
+ ### Dark mode rendering
55
+
56
+ Verify your theme renders correctly without flipping the OS preference.
57
+ Real CSS `@media (prefers-color-scheme: dark)` rules apply — this is not a
58
+ JavaScript-only patch.
59
+
60
+ ```typescript
61
+ await browser.emulate({ colorScheme: 'dark' });
62
+ await browser.navigateTo('https://app.example.com');
63
+
64
+ const bg = await browser.find('body').getCssValue('background-color');
65
+ expect(bg).toBe('rgb(17, 17, 17)');
66
+ ```
67
+
68
+ ### Offline PWA / service-worker fallback
69
+
70
+ Confirm your offline banner appears and cached pages still load when the
71
+ network drops.
72
+
73
+ ```typescript
74
+ await browser.navigateTo('https://pwa.example.com');
75
+ await browser.emulate({ offline: true });
76
+
77
+ await browser.click('#refresh');
78
+ await browser.expect('#offline-banner').toBeVisible();
79
+
80
+ await browser.emulate({ offline: false });
81
+ ```
82
+
83
+ ### Locale + timezone formatting bugs
84
+
85
+ Catch number/date/currency rendering issues without spinning up a fresh
86
+ browser for each locale.
87
+
88
+ ```typescript
89
+ await browser.emulate({ locale: 'de-DE', timezoneId: 'Europe/Berlin' });
90
+ await browser.navigateTo('https://shop.example.com/cart');
91
+
92
+ // "12,50 €" instead of "$12.50"
93
+ await browser.expect('#total').toHaveText('12,50 €');
94
+
95
+ // "Liefertermin" in German, Berlin-local cutoff time
96
+ await browser.expect('#delivery-cutoff').toContainText('18:00');
97
+ ```
98
+
99
+ ### Reduced motion (a11y regression)
100
+
101
+ Confirm animations are actually suppressed for users who request it.
102
+
103
+ ```typescript
104
+ await browser.emulate({ reducedMotion: 'reduce' });
105
+ await browser.navigateTo('https://app.example.com');
106
+
107
+ const anim = await browser.find('.hero').getCssValue('animation-name');
108
+ expect(anim).toBe('none');
109
+ ```
110
+
111
+ ### Forced colors (Windows high-contrast)
112
+
113
+ Catch buttons that lose their outline or icons that disappear when the OS
114
+ forces a high-contrast palette.
115
+
116
+ ```typescript
117
+ await browser.emulate({ forcedColors: 'active' });
118
+ await browser.navigateTo('https://app.example.com');
119
+
120
+ await browser.expect('button.primary').toBeVisible();
121
+ ```
122
+
123
+ ## Clearing overrides
124
+
125
+ Pass `null` (or `false` for `offline`) to revert a single field. Other
126
+ fields stay as-is.
127
+
128
+ ```typescript
129
+ await browser.emulate({ locale: 'de-DE', timezoneId: 'Europe/Berlin' });
130
+
131
+ // Clear the timezone, keep the locale
132
+ await browser.emulate({ timezoneId: null });
133
+
134
+ // Clear everything you may have set
135
+ await browser.emulate({
136
+ colorScheme: null,
137
+ reducedMotion: null,
138
+ forcedColors: null,
139
+ locale: null,
140
+ timezoneId: null,
141
+ offline: false,
142
+ });
143
+ ```
144
+
145
+ ## Errors
146
+
147
+ - **`emulate() requires BiDi`** — you launched with `enableBiDi: false`.
148
+ Re-enable BiDi.
149
+ - **`emulate({ colorScheme }) is not supported on Firefox yet`** — the
150
+ CSS-media-feature and `offline` paths currently use the BiDi+CDP
151
+ bridge, which only Chromium exposes. Use `locale` / `timezoneId` on
152
+ Firefox, or run the test on Chrome/Chromium.
153
+ - **`CDP command \`...\` failed`** — usually means the browser is not a
154
+ Chromium build with the BiDi+CDP bridge enabled. Confirm
155
+ `browserName: 'chrome'` or `'chromium'`.
@@ -0,0 +1,67 @@
1
+ # Error codes
2
+
3
+ Every error thrown from the public craftdriver API is a
4
+ [`CraftdriverError`](../src/lib/errors.ts), carrying:
5
+
6
+ - `code` — a stable, machine-readable identifier from the table below.
7
+ - `message` — human-readable summary.
8
+ - `detail` — JSON-serializable structured context (selector, timeout,
9
+ candidates, etc.). Safe to log or send to an LLM.
10
+ - `hint` — one-line remediation when there is an obvious next step.
11
+
12
+ `CraftdriverError extends Error`, so `instanceof Error` keeps working
13
+ and stack traces are preserved.
14
+
15
+ ```ts
16
+ import { CraftdriverError, ErrorCode } from 'craftdriver';
17
+
18
+ try {
19
+ await browser.locator('#missing').click();
20
+ } catch (err) {
21
+ if (CraftdriverError.is(err, ErrorCode.NO_MATCH)) {
22
+ // Selector matched zero elements — fix the selector.
23
+ console.error(err.detail);
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## Codes
29
+
30
+ | Code | When it fires | Typical recovery |
31
+ |---|---|---|
32
+ | `NO_MATCH` | Selector matched zero elements within the timeout. | Fix the selector. Prefer `By.testId` / `By.role` / `By.labelText` over CSS for resilience. |
33
+ | `TIMEOUT_WAITING_VISIBLE` | Selector matched an element, but it never became visible within the timeout. | Open the containing view first (modal, accordion, tab). The element is in the DOM but not displayed. |
34
+ | `TIMEOUT_WAITING_STATE` | Element visible but never reached the requested state (`enabled`, `checked`, `attached`, `detached`, `hidden`). | Wait for the precondition that drives the state transition. |
35
+ | `TIMEOUT_WAITING_LOAD` | Page never reached the requested load state (`load` / `domcontentloaded` / `networkidle`). | Bump the navigation timeout, or wait on a stable DOM signal instead. |
36
+ | `TIMEOUT_WAITING_NETWORK` | A specific request / response / network-idle predicate did not resolve. | Verify the predicate matches the real traffic; widen with a regex. |
37
+ | `TIMEOUT_WAITING_DIALOG` | `waitForDialog()` did not see a dialog of the expected type. | Confirm the action under test actually opens the dialog. |
38
+ | `TIMEOUT` | Generic `WebDriverWait.until(...)` timeout with no more specific code. | Same as above — check the condition. |
39
+ | `EXPECT_MISMATCH` | An `expect(locator).to…()` assertion failed after auto-waiting. | Inspect `error.detail` for the selector and observed value. |
40
+ | `A11Y_VIOLATIONS` | `browser.a11y.check()` (or scoped variants) found axe-core violations. | Iterate over `error.violations` — each has an `id`, `impact`, and `helpUrl`. |
41
+ | `EVAL_THREW` | The function passed to `evaluate()` threw inside the page. | The page-side exception text is in `error.detail.exception`. |
42
+ | `EVAL_BAD_ARG` | `evaluate()` / `addInitScript()` received a non-JSON-serializable argument (function, Symbol, DOM node…). | Pass plain JSON values. |
43
+ | `INVALID_ARGUMENT` | Caller passed an invalid value (bad enum, wrong shape, unparseable duration…). | Read the message; it lists the accepted forms. |
44
+ | `UNSUPPORTED` | Feature exists but is unavailable on this browser/transport (e.g. Chromium-only over Firefox, or a BiDi-only feature with BiDi disabled). | Enable BiDi (`enableBiDi: true`) or switch browser. |
45
+ | `STATE_INVALID` | Method called in the wrong state (e.g. `stopTrace()` without `startTrace()`, `clock.tick()` before `install()`). | Call the prerequisite first. |
46
+ | `DRIVER_ERROR` | Driver-level / transport-level failure surfaced to the caller. | Inspect `error.cause`. |
47
+
48
+ ## Stability
49
+
50
+ - Codes are **append-only**. New ones may be added; existing ones are
51
+ never renamed or repurposed.
52
+ - `code` strings are case-sensitive and stable across patch releases.
53
+ - `detail` keys are additive: new keys may appear; existing keys keep
54
+ their meaning.
55
+
56
+ ## Distinguishing the three common probe failures
57
+
58
+ This is the highest-leverage distinction for agents:
59
+
60
+ - **`NO_MATCH`** — your selector is wrong.
61
+ - **`TIMEOUT_WAITING_VISIBLE`** — the element is there; you opened the
62
+ page too early or skipped a UI step that reveals it.
63
+ - **`TIMEOUT_WAITING_STATE`** — the element is visible; you tried to
64
+ act on it before it was ready (still disabled, still loading…).
65
+
66
+ Each one points at a different fix. Today's prose-only error collapses
67
+ all three, which is exactly why naive recovery loops loop.
@@ -0,0 +1,168 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install craftdriver --save-dev
7
+ ```
8
+
9
+ ## Your First Test
10
+
11
+ ```typescript
12
+ import { Browser } from 'craftdriver';
13
+
14
+ async function main() {
15
+ // Launch browser
16
+ const browser = await Browser.launch({ browserName: 'chrome' });
17
+
18
+ try {
19
+ // Navigate to a page
20
+ await browser.navigateTo('https://example.com');
21
+
22
+ // Get page title
23
+ const title = await browser.title();
24
+ console.log('Page title:', title);
25
+
26
+ // Click a link
27
+ await browser.click('a');
28
+
29
+ // Fill a form
30
+ await browser.fill('#email', 'test@example.com');
31
+ await browser.click('#submit');
32
+
33
+ // Assert result
34
+ await browser.expect('#message').toHaveText('Success!');
35
+ } finally {
36
+ await browser.quit();
37
+ }
38
+ }
39
+
40
+ main();
41
+ ```
42
+
43
+ ## `browser.X()` vs `page.X()` — when to use which
44
+
45
+ `browser.click()`, `browser.find()`, `browser.fill()`, `browser.evaluate()`
46
+ and the other DOM-touching shortcuts on `Browser` are sugar for
47
+ `(await browser.activePage()).X(...)`. They always target the focused
48
+ page in `browser.defaultContext`. They never cross into a `Page` that
49
+ lives inside a context returned by `browser.newContext()`.
50
+
51
+ For single-tab tests this is exactly what you want — the shortcuts keep
52
+ your code short:
53
+
54
+ ```typescript
55
+ await browser.navigateTo('https://example.com');
56
+ await browser.click('#submit');
57
+ ```
58
+
59
+ For multi-tab or multi-user tests, get a `Page` explicitly:
60
+
61
+ ```typescript
62
+ // Multi-tab
63
+ const page = await browser.openPage({ url: '/help', type: 'tab' });
64
+ await page.click('#close'); // unambiguous
65
+
66
+ // Multi-user (isolated profiles)
67
+ const alice = await browser.newContext();
68
+ const aPage = await alice.newPage({ url: '/login' });
69
+ await aPage.fill('#user', 'alice'); // browser.fill() would target the
70
+ // default context, NOT alice.
71
+ ```
72
+
73
+ If you need to be explicit even in single-tab code, `browser.activePage()`
74
+ returns the same `Page` the shortcuts would target.
75
+
76
+ ## Launch Options
77
+
78
+ ```typescript
79
+ // Chrome (default)
80
+ const browser = await Browser.launch({
81
+ browserName: 'chrome', // 'chrome' | 'chromium' | 'firefox'
82
+ storageState: './session.json', // optional: pre-load saved session state
83
+ });
84
+
85
+ // Firefox
86
+ const browser = await Browser.launch({
87
+ browserName: 'firefox',
88
+ });
89
+ ```
90
+
91
+ ### Running tests on Firefox
92
+
93
+ Set `BROWSER_NAME=firefox` before running your test command:
94
+
95
+ ```bash
96
+ BROWSER_NAME=firefox npx vitest run
97
+ # or use the built-in script if your package.json has it:
98
+ npm run test:firefox
99
+ ```
100
+
101
+ ### Custom WebDriver binary or port
102
+
103
+ `Browser.launch()` resolves the driver binary automatically. To pin a
104
+ specific binary, change the host/port, or pass extra command-line
105
+ arguments to the driver, construct a `ChromeService` or `FirefoxService`
106
+ and pass it via `chromeService` / `firefoxService`:
107
+
108
+ ```typescript
109
+ import { Browser, ChromeService, FirefoxService } from 'craftdriver';
110
+
111
+ // Pin a specific chromedriver binary on a fixed port
112
+ const browser = await Browser.launch({
113
+ browserName: 'chrome',
114
+ chromeService: new ChromeService({
115
+ binaryPath: '/opt/chromedriver/chromedriver',
116
+ port: 9515,
117
+ hostname: '127.0.0.1',
118
+ args: ['--log-level=ALL'],
119
+ }),
120
+ });
121
+
122
+ // Custom geckodriver path for Firefox
123
+ const ff = await Browser.launch({
124
+ browserName: 'firefox',
125
+ firefoxService: new FirefoxService({
126
+ binaryPath: '/usr/local/bin/geckodriver',
127
+ }),
128
+ });
129
+ ```
130
+
131
+ Without an explicit `binaryPath`, both services check (in order) the
132
+ matching env var (`CHROMEDRIVER_PATH` / `GECKODRIVER_PATH`),
133
+ `node_modules/.bin/`, then `$PATH`. If `port` is omitted, a free port
134
+ is picked automatically.
135
+
136
+ ## With Vitest
137
+
138
+ ```typescript
139
+ import { describe, it, beforeEach, afterEach } from 'vitest';
140
+ import { Browser } from 'craftdriver';
141
+
142
+ describe('My App', () => {
143
+ let browser: Browser;
144
+
145
+ beforeEach(async () => {
146
+ browser = await Browser.launch({ browserName: 'chrome' });
147
+ });
148
+
149
+ afterEach(async () => {
150
+ await browser.quit();
151
+ });
152
+
153
+ it('shows welcome message after login', async () => {
154
+ await browser.navigateTo('http://localhost:3000/login');
155
+ await browser.fill('#username', 'testuser');
156
+ await browser.fill('#password', 'password123');
157
+ await browser.click('#login-btn');
158
+
159
+ await browser.expect('#welcome').toContainText('Hello, testuser');
160
+ });
161
+ });
162
+ ```
163
+
164
+ ## Next Steps
165
+
166
+ - [Browser API](./browser-api.md) - Full browser control reference
167
+ - [Selectors](./selectors.md) - Finding elements with CSS, XPath, and semantic locators
168
+ - [Session Management](./session-management.md) - Save/restore login sessions