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.
- package/CHANGELOG.md +104 -0
- package/README.md +186 -10
- package/bin/craftdriver.mjs +26 -0
- package/dist/cli/client.d.ts +17 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +88 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon.d.ts +15 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +145 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/defaults.d.ts +10 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +42 -0
- package/dist/cli/defaults.js.map +1 -0
- package/dist/cli/dispatcher.d.ts +28 -0
- package/dist/cli/dispatcher.d.ts.map +1 -0
- package/dist/cli/dispatcher.js +319 -0
- package/dist/cli/dispatcher.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +404 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +24 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +192 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +43 -0
- package/dist/cli/mcp/artifacts.d.ts.map +1 -0
- package/dist/cli/mcp/artifacts.js +104 -0
- package/dist/cli/mcp/artifacts.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +34 -0
- package/dist/cli/mcp/server.d.ts.map +1 -0
- package/dist/cli/mcp/server.js +214 -0
- package/dist/cli/mcp/server.js.map +1 -0
- package/dist/cli/mcp/tools.d.ts +34 -0
- package/dist/cli/mcp/tools.d.ts.map +1 -0
- package/dist/cli/mcp/tools.js +236 -0
- package/dist/cli/mcp/tools.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +37 -0
- package/dist/cli/parseArgs.d.ts.map +1 -0
- package/dist/cli/parseArgs.js +215 -0
- package/dist/cli/parseArgs.js.map +1 -0
- package/dist/cli/protocol.d.ts +34 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/cli/protocol.js +2 -0
- package/dist/cli/protocol.js.map +1 -0
- package/dist/cli/selector.d.ts +29 -0
- package/dist/cli/selector.d.ts.map +1 -0
- package/dist/cli/selector.js +100 -0
- package/dist/cli/selector.js.map +1 -0
- package/dist/cli/snapshot.d.ts +48 -0
- package/dist/cli/snapshot.d.ts.map +1 -0
- package/dist/cli/snapshot.js +179 -0
- package/dist/cli/snapshot.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/a11y.d.ts +81 -0
- package/dist/lib/a11y.d.ts.map +1 -0
- package/dist/lib/a11y.js +171 -0
- package/dist/lib/a11y.js.map +1 -0
- package/dist/lib/bidi/connection.d.ts +62 -0
- package/dist/lib/bidi/connection.d.ts.map +1 -0
- package/dist/lib/bidi/connection.js +227 -0
- package/dist/lib/bidi/connection.js.map +1 -0
- package/dist/lib/bidi/index.d.ts +101 -0
- package/dist/lib/bidi/index.d.ts.map +1 -0
- package/dist/lib/bidi/index.js +163 -0
- package/dist/lib/bidi/index.js.map +1 -0
- package/dist/lib/bidi/logs.d.ts +104 -0
- package/dist/lib/bidi/logs.d.ts.map +1 -0
- package/dist/lib/bidi/logs.js +273 -0
- package/dist/lib/bidi/logs.js.map +1 -0
- package/dist/lib/bidi/network.d.ts +165 -0
- package/dist/lib/bidi/network.d.ts.map +1 -0
- package/dist/lib/bidi/network.js +477 -0
- package/dist/lib/bidi/network.js.map +1 -0
- package/dist/lib/bidi/storage.d.ts +83 -0
- package/dist/lib/bidi/storage.d.ts.map +1 -0
- package/dist/lib/bidi/storage.js +299 -0
- package/dist/lib/bidi/storage.js.map +1 -0
- package/dist/lib/bidi/types.d.ts +323 -0
- package/dist/lib/bidi/types.d.ts.map +1 -0
- package/dist/lib/bidi/types.js +6 -0
- package/dist/lib/bidi/types.js.map +1 -0
- package/dist/lib/browser.d.ts +776 -13
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +1547 -70
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +486 -0
- package/dist/lib/browserContext.d.ts.map +1 -0
- package/dist/lib/browserContext.js +937 -0
- package/dist/lib/browserContext.js.map +1 -0
- package/dist/lib/builder.d.ts +3 -0
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +31 -4
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/by.d.ts +18 -2
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +20 -2
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/chrome.d.ts +13 -1
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +13 -17
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/clock.d.ts +115 -0
- package/dist/lib/clock.d.ts.map +1 -0
- package/dist/lib/clock.js +407 -0
- package/dist/lib/clock.js.map +1 -0
- package/dist/lib/driver.d.ts +45 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +129 -1
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +25 -0
- package/dist/lib/driverManager.d.ts.map +1 -0
- package/dist/lib/driverManager.js +399 -0
- package/dist/lib/driverManager.js.map +1 -0
- package/dist/lib/elementHandle.d.ts +69 -2
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +215 -40
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +71 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +73 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/expect.d.ts +6 -1
- package/dist/lib/expect.d.ts.map +1 -1
- package/dist/lib/expect.js +55 -37
- package/dist/lib/expect.js.map +1 -1
- package/dist/lib/firefox.d.ts +18 -0
- package/dist/lib/firefox.d.ts.map +1 -0
- package/dist/lib/firefox.js +33 -0
- package/dist/lib/firefox.js.map +1 -0
- package/dist/lib/frame.d.ts +57 -0
- package/dist/lib/frame.d.ts.map +1 -0
- package/dist/lib/frame.js +277 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/keyboard.d.ts +1 -1
- package/dist/lib/keyboard.d.ts.map +1 -1
- package/dist/lib/keyboard.js +1 -1
- package/dist/lib/keyboard.js.map +1 -1
- package/dist/lib/locator.d.ts +71 -2
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +289 -31
- package/dist/lib/locator.js.map +1 -1
- package/dist/lib/mouse.d.ts +1 -1
- package/dist/lib/mouse.d.ts.map +1 -1
- package/dist/lib/mouse.js +1 -1
- package/dist/lib/mouse.js.map +1 -1
- package/dist/lib/page.d.ts +107 -0
- package/dist/lib/page.d.ts.map +1 -0
- package/dist/lib/page.js +388 -0
- package/dist/lib/page.js.map +1 -0
- package/dist/lib/tracing.d.ts +128 -0
- package/dist/lib/tracing.d.ts.map +1 -0
- package/dist/lib/tracing.js +272 -0
- package/dist/lib/tracing.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +14 -1
- package/dist/lib/wait.js.map +1 -1
- package/dist/lib/webelement.d.ts +2 -0
- package/dist/lib/webelement.d.ts.map +1 -1
- package/dist/lib/webelement.js +18 -0
- package/dist/lib/webelement.js.map +1 -1
- package/docs/accessibility.md +177 -0
- package/docs/api-reference.md +71 -0
- package/docs/assertions.md +182 -0
- package/docs/bidi-features.md +462 -0
- package/docs/browser-api.md +673 -0
- package/docs/browser-context.md +522 -0
- package/docs/cli.md +233 -0
- package/docs/clock.md +245 -0
- package/docs/dialogs.md +143 -0
- package/docs/driver-configuration.md +64 -0
- package/docs/element-api.md +219 -0
- package/docs/emulation.md +155 -0
- package/docs/error-codes.md +67 -0
- package/docs/getting-started.md +168 -0
- package/docs/keyboard-mouse.md +250 -0
- package/docs/mcp.md +250 -0
- package/docs/mobile-emulation.md +194 -0
- package/docs/screenshots.md +146 -0
- package/docs/selectors.md +382 -0
- package/docs/session-management.md +229 -0
- package/docs/tracing.md +282 -0
- package/package.json +25 -5
- package/skills/craftdriver/SKILL.md +84 -0
- package/skills/craftdriver/cheatsheet.md +194 -0
- package/skills/craftdriver/cli.md +104 -0
- 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
|