craftdriver 0.1.0 → 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 +157 -45
- 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 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -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/index.d.ts +26 -1
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +49 -0
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +2 -2
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +2 -2
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/bidi/network.d.ts +74 -1
- package/dist/lib/bidi/network.d.ts.map +1 -1
- package/dist/lib/bidi/network.js +222 -4
- package/dist/lib/bidi/network.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +5 -3
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +14 -7
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/bidi/types.d.ts +27 -6
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +637 -16
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +1428 -102
- 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 +43 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +120 -0
- 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 +67 -3
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +209 -67
- 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 +23 -6
- 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,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
|
+
```
|