craftdriver 1.0.2 โ 1.0.4
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 +25 -3
- package/README.md +80 -160
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +3 -4
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/clearFastPath.d.ts +7 -0
- package/dist/lib/clearFastPath.d.ts.map +1 -0
- package/dist/lib/clearFastPath.js +37 -0
- package/dist/lib/clearFastPath.js.map +1 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +19 -1
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts.map +1 -1
- package/dist/lib/driverManager.js +67 -24
- package/dist/lib/driverManager.js.map +1 -1
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +44 -35
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +26 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/http.d.ts +1 -1
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +35 -7
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/timing.d.ts +13 -0
- package/dist/lib/timing.d.ts.map +1 -1
- package/dist/lib/timing.js +13 -0
- package/dist/lib/timing.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/docs/agents.md +92 -0
- package/docs/api-reference.md +108 -46
- package/docs/browser-api.md +1 -1
- package/docs/browser-context.md +4 -7
- package/docs/browser-logs.md +158 -0
- package/docs/cli.md +26 -26
- package/docs/element-api.md +37 -6
- package/docs/emulation.md +6 -2
- package/docs/index.md +64 -0
- package/docs/keyboard-mouse.md +28 -10
- package/docs/mobile-emulation.md +5 -2
- package/docs/network.md +203 -0
- package/docs/public/craftdriver-mark.svg +15 -0
- package/docs/public/social-card.svg +27 -0
- package/docs/recipes/accessibility-gate.md +53 -0
- package/docs/recipes/console-error-gate.md +57 -0
- package/docs/recipes/file-upload-download.md +58 -0
- package/docs/recipes/login-once-reuse-session.md +73 -0
- package/docs/recipes/mobile-flow-with-network-and-logs.md +57 -0
- package/docs/recipes/mock-api-and-assert-network.md +60 -0
- package/docs/recipes/multi-user-contexts.md +58 -0
- package/docs/recipes/trace-failing-test.md +64 -0
- package/docs/recipes/virtual-clock-time-sensitive-ui.md +60 -0
- package/docs/recipes/vitest-browser-lifecycle.md +56 -0
- package/docs/recipes.md +33 -0
- package/docs/selectors.md +22 -1
- package/docs/session-management.md +18 -1
- package/docs/standards.md +40 -0
- package/docs/why-craftdriver.md +28 -0
- package/docs/zero-config-drivers.md +52 -0
- package/package.json +19 -5
- package/docs/bidi-features.md +0 -470
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Test A Mobile Flow With API Mocks And Logs
|
|
2
|
+
|
|
3
|
+
Use this pattern when mobile layout depends on a device preset and a backend
|
|
4
|
+
configuration response. This combines mobile emulation, network mocking, and log
|
|
5
|
+
capture in one test.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { afterAll, beforeAll, describe, it } from 'vitest';
|
|
9
|
+
import { Browser } from 'craftdriver';
|
|
10
|
+
|
|
11
|
+
describe('mobile navigation', () => {
|
|
12
|
+
let browser: Browser;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
browser = await Browser.launch({
|
|
16
|
+
browserName: 'chrome',
|
|
17
|
+
mobileEmulation: 'Pixel 7',
|
|
18
|
+
captureLogs: true,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await browser.quit();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('shows the mobile menu from mocked config', async () => {
|
|
27
|
+
browser.logs.clearLogs();
|
|
28
|
+
|
|
29
|
+
await browser.network.mock('**/api/mobile-config', {
|
|
30
|
+
status: 200,
|
|
31
|
+
body: {
|
|
32
|
+
navigation: 'bottom-tabs',
|
|
33
|
+
showInstallPrompt: false,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await browser.navigateTo('http://localhost:3000');
|
|
38
|
+
await browser.getByRole('button', { name: 'Menu' }).click();
|
|
39
|
+
|
|
40
|
+
await browser.expect('#mobile-menu').toBeVisible();
|
|
41
|
+
await browser.expect('#desktop-nav').not.toBeVisible();
|
|
42
|
+
browser.logs.assertNoErrors();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- Mobile emulation is currently Chrome/Chromium only.
|
|
50
|
+
- Mock before navigation when the page reads mobile config during startup.
|
|
51
|
+
- Keep `captureLogs: true` if startup logs matter.
|
|
52
|
+
|
|
53
|
+
## Learn More
|
|
54
|
+
|
|
55
|
+
- [Mobile Emulation](../mobile-emulation.md)
|
|
56
|
+
- [Network Mocking And Request Waiting](../network.md)
|
|
57
|
+
- [Console Logs And JavaScript Errors](../browser-logs.md)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Mock APIs And Assert Network Traffic
|
|
2
|
+
|
|
3
|
+
Use this pattern when the UI should be tested independently from unstable,
|
|
4
|
+
slow, or expensive backend services. Mock the response the app needs, then wait
|
|
5
|
+
for the request or response caused by the user action.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { expect, it } from 'vitest';
|
|
9
|
+
import { Browser } from 'craftdriver';
|
|
10
|
+
|
|
11
|
+
it('loads mocked users and verifies the request', async () => {
|
|
12
|
+
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await browser.navigateTo('http://localhost:3000/users');
|
|
16
|
+
|
|
17
|
+
await browser.network.mock('**/api/users', {
|
|
18
|
+
status: 200,
|
|
19
|
+
body: {
|
|
20
|
+
users: [{ id: 1, name: 'Alice', plan: 'Pro' }],
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const [request, response] = await Promise.all([
|
|
25
|
+
browser.waitForRequest((req) => {
|
|
26
|
+
return req.url.includes('/api/users') && req.method === 'GET';
|
|
27
|
+
}),
|
|
28
|
+
browser.waitForResponse('**/api/users'),
|
|
29
|
+
browser.getByRole('button', { name: 'Load users' }).click(),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
expect(request.method).toBe('GET');
|
|
33
|
+
expect(response.status).toBe(200);
|
|
34
|
+
await browser.expect('#user-list').toContainText('Alice');
|
|
35
|
+
} finally {
|
|
36
|
+
await browser.quit();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Keep Mocks Isolated
|
|
42
|
+
|
|
43
|
+
If you share a browser across tests, clear intercepts in `afterEach()`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
await browser.network.removeAllIntercepts();
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Notes
|
|
52
|
+
|
|
53
|
+
- Register waits before the click that triggers the request.
|
|
54
|
+
- Use a glob for simple URL matching and a predicate when method, status, or headers matter.
|
|
55
|
+
- Mock before navigation if the page fetches data during initial load.
|
|
56
|
+
|
|
57
|
+
## Learn More
|
|
58
|
+
|
|
59
|
+
- [Network Mocking And Request Waiting](../network.md)
|
|
60
|
+
- [Console Logs And JavaScript Errors](../browser-logs.md)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Test Multi-User Workflows
|
|
2
|
+
|
|
3
|
+
Use this pattern for chat, permissions, approvals, collaboration, admin/customer
|
|
4
|
+
flows, or any test where two signed-in users must exist at the same time.
|
|
5
|
+
|
|
6
|
+
Browser contexts isolate cookies and storage while sharing one launched browser.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { afterAll, beforeAll, describe, it } from 'vitest';
|
|
10
|
+
import { Browser } from 'craftdriver';
|
|
11
|
+
|
|
12
|
+
describe('team invitation', () => {
|
|
13
|
+
let browser: Browser;
|
|
14
|
+
const baseUrl = 'http://localhost:3000';
|
|
15
|
+
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
browser = await Browser.launch({ browserName: 'chrome' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
await browser.quit();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('admin invites a teammate who accepts in another context', async () => {
|
|
25
|
+
const admin = await browser.newContext({ storageState: '.auth/admin.json' });
|
|
26
|
+
const teammate = await browser.newContext({ storageState: '.auth/alice.json' });
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const adminPage = await admin.newPage({ url: `${baseUrl}/team` });
|
|
30
|
+
await adminPage.find('#invite-member').click();
|
|
31
|
+
await adminPage.find('#invite-email').fill('alice@example.com');
|
|
32
|
+
await adminPage.find('#send-invite').click();
|
|
33
|
+
await adminPage.expect('#invite-status').toContainText('Sent');
|
|
34
|
+
|
|
35
|
+
const alicePage = await teammate.newPage({ url: `${baseUrl}/invites` });
|
|
36
|
+
await alicePage.find('#accept-invite').click();
|
|
37
|
+
await alicePage.expect('#membership').toContainText('Member');
|
|
38
|
+
|
|
39
|
+
await adminPage.find('#refresh-members').click();
|
|
40
|
+
await adminPage.expect('#member-list').toContainText('alice@example.com');
|
|
41
|
+
} finally {
|
|
42
|
+
await admin.close();
|
|
43
|
+
await teammate.close();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Notes
|
|
50
|
+
|
|
51
|
+
- Use one context per user or role.
|
|
52
|
+
- Put context cleanup in `finally` so failed assertions do not leak sessions.
|
|
53
|
+
- Combine with saved storage state when users should start already signed in.
|
|
54
|
+
|
|
55
|
+
## Learn More
|
|
56
|
+
|
|
57
|
+
- [Browser Contexts](../browser-context.md)
|
|
58
|
+
- [Session Management](../session-management.md)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Capture Failure Evidence With Tracing
|
|
2
|
+
|
|
3
|
+
Use this pattern when a flaky or complex test needs evidence: actions,
|
|
4
|
+
navigation, console output, JavaScript errors, network events, and screenshots.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { afterAll, beforeAll, describe, it } from 'vitest';
|
|
8
|
+
import { Browser } from 'craftdriver';
|
|
9
|
+
|
|
10
|
+
describe('checkout', () => {
|
|
11
|
+
let browser: Browser;
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
browser = await Browser.launch({ browserName: 'chrome' });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await browser.quit();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function withTrace(name: string, run: () => Promise<void>) {
|
|
22
|
+
const outDir = `./traces/${name}-${Date.now()}`;
|
|
23
|
+
await browser.startTrace({ outDir });
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await run();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(`Trace kept at ${outDir}`);
|
|
29
|
+
throw error;
|
|
30
|
+
} finally {
|
|
31
|
+
await browser.stopTrace().catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('places an order', async () => {
|
|
36
|
+
await withTrace('checkout-order', async () => {
|
|
37
|
+
await browser.navigateTo('http://localhost:3000/cart');
|
|
38
|
+
await browser.getByRole('button', { name: 'Checkout' }).click();
|
|
39
|
+
await browser.getByLabel('Card number').fill('4242424242424242');
|
|
40
|
+
await browser.getByRole('button', { name: 'Pay' }).click();
|
|
41
|
+
await browser.expect('#order-status').toContainText('Confirmed');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- The trace file is NDJSON, so it remains useful even if the test throws before cleanup.
|
|
50
|
+
- Screenshots are stored under the trace directory.
|
|
51
|
+
- For high-volume suites, turn screenshots off unless the test is under investigation.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
await browser.startTrace({
|
|
55
|
+
outDir: './traces/smoke',
|
|
56
|
+
screenshots: 'off',
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Learn More
|
|
61
|
+
|
|
62
|
+
- [Tracing](../tracing.md)
|
|
63
|
+
- [Screenshots](../screenshots.md)
|
|
64
|
+
- [Console Logs And JavaScript Errors](../browser-logs.md)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Test Time-Sensitive UI With The Virtual Clock
|
|
2
|
+
|
|
3
|
+
Use this pattern for debounced search, trial banners, idle logout, countdowns,
|
|
4
|
+
and other UI that normally makes tests sleep.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from 'vitest';
|
|
8
|
+
import { Browser } from 'craftdriver';
|
|
9
|
+
|
|
10
|
+
describe('debounced search', () => {
|
|
11
|
+
let browser: Browser;
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
browser = await Browser.launch({ browserName: 'chrome' });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
await browser.clock.install({ time: '2030-01-01T00:00:00Z' });
|
|
19
|
+
await browser.navigateTo('http://localhost:3000/search');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(async () => {
|
|
23
|
+
await browser.clock.uninstall();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
await browser.quit();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('fires search only after the debounce window', async () => {
|
|
31
|
+
await browser.network.mock('**/api/search?q=lap', {
|
|
32
|
+
status: 200,
|
|
33
|
+
body: { results: ['Laptop stand'] },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await browser.getByLabel('Search').fill('lap');
|
|
37
|
+
|
|
38
|
+
await browser.clock.tick(299);
|
|
39
|
+
await browser.expect('#results').toHaveText('');
|
|
40
|
+
|
|
41
|
+
await browser.clock.tick(1);
|
|
42
|
+
await browser.expect('#results').toContainText('Laptop stand');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Fixed Dates
|
|
48
|
+
|
|
49
|
+
For date-dependent UI, freeze the wall clock before navigation:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
await browser.clock.setFixedTime('2026-06-15T23:59:00Z');
|
|
53
|
+
await browser.navigateTo('http://localhost:3000/billing');
|
|
54
|
+
await browser.expect('#trial-banner').toContainText('expires today');
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Learn More
|
|
58
|
+
|
|
59
|
+
- [Virtual Clock](../clock.md)
|
|
60
|
+
- [Network Mocking And Request Waiting](../network.md)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Use CraftDriver With Vitest Hooks
|
|
2
|
+
|
|
3
|
+
Use this pattern when a test file should launch one browser, navigate to a clean
|
|
4
|
+
page before each test, and fail if the page reports unexpected JavaScript errors.
|
|
5
|
+
|
|
6
|
+
This keeps tests fast without sharing dirty page state.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from 'vitest';
|
|
10
|
+
import { Browser } from 'craftdriver';
|
|
11
|
+
|
|
12
|
+
describe('settings page', () => {
|
|
13
|
+
let browser: Browser;
|
|
14
|
+
const baseUrl = 'http://localhost:3000';
|
|
15
|
+
|
|
16
|
+
beforeAll(async () => {
|
|
17
|
+
browser = await Browser.launch({
|
|
18
|
+
browserName: 'chrome',
|
|
19
|
+
captureLogs: true,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
browser.logs.clearLogs();
|
|
25
|
+
await browser.network.removeAllIntercepts();
|
|
26
|
+
await browser.navigateTo(`${baseUrl}/settings`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
browser.logs.assertNoErrors();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(async () => {
|
|
34
|
+
await browser.quit();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('updates the display name', async () => {
|
|
38
|
+
await browser.getByLabel('Display name').fill('Alice');
|
|
39
|
+
await browser.getByRole('button', { name: 'Save' }).click();
|
|
40
|
+
await browser.expect('#toast').toContainText('Saved');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Notes
|
|
46
|
+
|
|
47
|
+
- Launch in `beforeAll()` when tests in the file can share one browser process.
|
|
48
|
+
- Navigate in `beforeEach()` so each test starts from a known URL.
|
|
49
|
+
- Clear network mocks and logs before each test so one test cannot influence the next.
|
|
50
|
+
- Use `afterAll()` for `browser.quit()` so local driver and browser processes are cleaned up.
|
|
51
|
+
|
|
52
|
+
## Learn More
|
|
53
|
+
|
|
54
|
+
- [Browser API](../browser-api.md)
|
|
55
|
+
- [Console Logs And JavaScript Errors](../browser-logs.md)
|
|
56
|
+
- [Network Mocking And Request Waiting](../network.md)
|
package/docs/recipes.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
Recipes are short, real-world patterns that combine CraftDriver features into
|
|
4
|
+
common testing workflows. Use this page as the index; each recipe has its own
|
|
5
|
+
page so the list can grow without turning into a wall of code.
|
|
6
|
+
|
|
7
|
+
For exact signatures, use the linked feature docs and the
|
|
8
|
+
[API reference](./api-reference.md).
|
|
9
|
+
|
|
10
|
+
## Test Structure
|
|
11
|
+
|
|
12
|
+
| Scenario | Use when | Recipe |
|
|
13
|
+
| ------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
14
|
+
| Vitest browser lifecycle | You want one browser per test file and a fresh page per test. | [Use CraftDriver With Vitest Hooks](./recipes/vitest-browser-lifecycle.md) |
|
|
15
|
+
| Login once, reuse session | Login UI is slow or noisy and most tests start signed in. | [Log In Once And Reuse The Session](./recipes/login-once-reuse-session.md) |
|
|
16
|
+
| Multi-user flows | You need Alice and Bob signed in at the same time without leaking cookies. | [Test Multi-User Workflows](./recipes/multi-user-contexts.md) |
|
|
17
|
+
|
|
18
|
+
## App Behavior
|
|
19
|
+
|
|
20
|
+
| Scenario | Use when | Recipe |
|
|
21
|
+
| ---------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
22
|
+
| Mock APIs and assert traffic | A UI flow depends on backend responses or request payloads. | [Mock APIs And Assert Network Traffic](./recipes/mock-api-and-assert-network.md) |
|
|
23
|
+
| Time-sensitive UI | Debounces, trial banners, idle logout, or scheduled jobs make tests slow. | [Test Time-Sensitive UI With The Virtual Clock](./recipes/virtual-clock-time-sensitive-ui.md) |
|
|
24
|
+
| Mobile-specific behavior | Mobile layout depends on viewport, device headers, API config, or logs. | [Test A Mobile Flow With API Mocks And Logs](./recipes/mobile-flow-with-network-and-logs.md) |
|
|
25
|
+
| File upload and download | A flow uploads a file, exports a report, or verifies downloaded content. | [Test File Uploads And Downloads](./recipes/file-upload-download.md) |
|
|
26
|
+
|
|
27
|
+
## Quality Gates And Debugging
|
|
28
|
+
|
|
29
|
+
| Scenario | Use when | Recipe |
|
|
30
|
+
| ----------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
31
|
+
| Accessibility regression gate | CI should fail on serious page or component accessibility issues. | [Run Accessibility Gates](./recipes/accessibility-gate.md) |
|
|
32
|
+
| Console and JavaScript errors | Tests should fail if the browser reports unexpected client-side errors. | [Fail On Console And JavaScript Errors](./recipes/console-error-gate.md) |
|
|
33
|
+
| Evidence on failure | You want a replayable trail of actions, network, logs, errors, and screenshots. | [Capture Failure Evidence With Tracing](./recipes/trace-failing-test.md) |
|
package/docs/selectors.md
CHANGED
|
@@ -343,6 +343,28 @@ const texts = await Promise.all(handles.map(h => h.text()));
|
|
|
343
343
|
const allButtons = await browser.findAll('.buy-btn');
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
+
### Locator actions and state
|
|
347
|
+
|
|
348
|
+
| Method | Description |
|
|
349
|
+
| ------ | ----------- |
|
|
350
|
+
| `click(options?)` | Click the first matching visible element |
|
|
351
|
+
| `fill(text, options?)` | Fill the first matching visible element |
|
|
352
|
+
| `text(options?)` | Get visible text |
|
|
353
|
+
| `textContent(options?)` | Alias for `text()` |
|
|
354
|
+
| `isVisible(options?)` | Return `true` if a matching element is visible |
|
|
355
|
+
| `count()` | Count current matches without auto-wait |
|
|
356
|
+
| `all()` | Return snapshot `ElementHandle`s for current matches |
|
|
357
|
+
| `waitFor({ state, timeout? })` | Wait for `attached`, `detached`, `visible`, or `hidden` |
|
|
358
|
+
| `expect()` | Element assertions scoped to this locator |
|
|
359
|
+
| `a11y` | Accessibility audit scoped to this locator |
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
await browser.locator('.toast').waitFor({ state: 'visible' });
|
|
363
|
+
const shown = await browser.locator('.toast').isVisible();
|
|
364
|
+
const text = await browser.locator('.toast').textContent();
|
|
365
|
+
const audit = await browser.locator('#checkout').a11y.audit();
|
|
366
|
+
```
|
|
367
|
+
|
|
346
368
|
### Assertions on locators
|
|
347
369
|
|
|
348
370
|
```typescript
|
|
@@ -378,5 +400,4 @@ await browser.find('nav a[href="/about"]').click();
|
|
|
378
400
|
```typescript
|
|
379
401
|
await browser.getByRole('button', { name: 'Submit' }).click();
|
|
380
402
|
await browser.getByText('Cancel').click();
|
|
381
|
-
await browser.find('button:contains("Save")').click();
|
|
382
403
|
```
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# Session Management
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CraftDriver supports Playwright-style session persistence, allowing you to save and restore browser state including cookies and localStorage. This is perfect for:
|
|
4
4
|
|
|
5
5
|
- **Skipping login in tests** - Log in once, reuse session across test runs
|
|
6
6
|
- **Sharing auth state** - Generate auth state in setup, use in parallel tests
|
|
7
7
|
- **Debugging** - Capture session state at any point
|
|
8
8
|
|
|
9
|
+
Session management is the user-facing feature. Under the hood, CraftDriver uses
|
|
10
|
+
the best available WebDriver transport for the browser you launched.
|
|
11
|
+
|
|
9
12
|
## Saving Session State
|
|
10
13
|
|
|
11
14
|
```typescript
|
|
@@ -51,6 +54,15 @@ The saved file contains:
|
|
|
51
54
|
await browser.loadState('./session.json');
|
|
52
55
|
```
|
|
53
56
|
|
|
57
|
+
## Working With State Objects
|
|
58
|
+
|
|
59
|
+
Use the state object directly when you do not want to write a file:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const state = await browser.storage.getState();
|
|
63
|
+
await browser.storage.setState(state);
|
|
64
|
+
```
|
|
65
|
+
|
|
54
66
|
## Launching with Pre-loaded State
|
|
55
67
|
|
|
56
68
|
The most common pattern - launch a browser with existing session:
|
|
@@ -89,6 +101,11 @@ const cookies = await browser.storage.getCookies();
|
|
|
89
101
|
// Get cookies for specific domain
|
|
90
102
|
const domainCookies = await browser.storage.getCookies({ domain: 'example.com' });
|
|
91
103
|
|
|
104
|
+
// Set multiple cookies
|
|
105
|
+
await browser.storage.setCookies([
|
|
106
|
+
{ name: 'theme', value: 'dark', domain: 'example.com', path: '/' },
|
|
107
|
+
]);
|
|
108
|
+
|
|
92
109
|
// Clear all cookies
|
|
93
110
|
await browser.storage.clearCookies();
|
|
94
111
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# WebDriver Standards
|
|
2
|
+
|
|
3
|
+
CraftDriver is built on W3C WebDriver protocols:
|
|
4
|
+
|
|
5
|
+
- **WebDriver Classic** for broadly supported browser automation commands
|
|
6
|
+
- **WebDriver BiDi** for modern bidirectional browser capabilities
|
|
7
|
+
|
|
8
|
+
That protocol choice is the center of the project. CraftDriver tries to provide a modern developer experience without making browser-private protocols the foundation of user code.
|
|
9
|
+
|
|
10
|
+
## Classic And BiDi Together
|
|
11
|
+
|
|
12
|
+
| Protocol | Used for |
|
|
13
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| WebDriver Classic | Stable navigation, element actions, browser sessions, window operations, and widely supported commands. |
|
|
15
|
+
| WebDriver BiDi | Network interception, console and page errors, screenshots, storage, contexts, init scripts, downloads, tracing, and other event-driven capabilities. |
|
|
16
|
+
|
|
17
|
+
CraftDriver picks the practical protocol path behind a single public API. You call `browser.click()`, `browser.navigateTo()`, `browser.network.mock()`, or `browser.startTrace()`; the library handles the transport details.
|
|
18
|
+
|
|
19
|
+
## Why It Matters
|
|
20
|
+
|
|
21
|
+
Standards-based automation gives projects a few useful properties:
|
|
22
|
+
|
|
23
|
+
- browser behavior can converge across engines instead of depending on a single vendor protocol
|
|
24
|
+
- Firefox support is a first-class target, not an afterthought
|
|
25
|
+
- protocol errors can be mapped into stable CraftDriver error codes
|
|
26
|
+
- browser control can be exposed safely to CLIs and MCP tools
|
|
27
|
+
|
|
28
|
+
## Honest Limits
|
|
29
|
+
|
|
30
|
+
WebDriver BiDi is still evolving. Some capabilities are browser-specific today, especially around emulation and mobile behavior. CraftDriver documents those limits instead of silently pretending every browser supports every feature.
|
|
31
|
+
|
|
32
|
+
Use these pages when you need exact capability details:
|
|
33
|
+
|
|
34
|
+
- [Network Mocking And Request Waiting](./network.md)
|
|
35
|
+
- [Console Logs And JavaScript Errors](./browser-logs.md)
|
|
36
|
+
- [Session Management](./session-management.md)
|
|
37
|
+
- [Browser Contexts](./browser-context.md)
|
|
38
|
+
- [Emulation](./emulation.md)
|
|
39
|
+
- [Mobile Emulation](./mobile-emulation.md)
|
|
40
|
+
- [Error Codes](./error-codes.md)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Why CraftDriver?
|
|
2
|
+
|
|
3
|
+
CraftDriver is for writing boringly reliable automation against real browsers, with code that reads nicely.
|
|
4
|
+
|
|
5
|
+
## What It Cares About
|
|
6
|
+
|
|
7
|
+
- ๐บ **Focused Node.js API** - browser automation without a giant framework around it.
|
|
8
|
+
- ๐งญ **Real browsers** - drives installed Chrome, Chromium, and Firefox instead of patched browser engine builds.
|
|
9
|
+
- ๐ **Standards that age well** - W3C WebDriver standards stay stable while browser-private protocols change.
|
|
10
|
+
- ๐ฆ **Readable, auto-waited flows** - role, label, text, test id, CSS, XPath, click, fill, and expect.
|
|
11
|
+
- ๐ก **Network control** - mock, block, intercept, and wait for browser requests and responses.
|
|
12
|
+
- ๐ **Reusable sessions** - save cookies and localStorage, then launch already signed in.
|
|
13
|
+
- โฑ๏ธ **Virtual clock** - freeze or fast-forward `Date`, timers, and time-sensitive UI.
|
|
14
|
+
- โฟ **Accessibility audits** - run axe-core checks on pages, elements, and locators.
|
|
15
|
+
- ๐งพ **Trace evidence** - capture actions, console output, errors, network events, and screenshots.
|
|
16
|
+
- ๐ค **Agent-friendly** - CLI, MCP, and assistant rules when coding agents need the browser.
|
|
17
|
+
|
|
18
|
+
## Good Fits
|
|
19
|
+
|
|
20
|
+
- End-to-end tests in Node.js.
|
|
21
|
+
- Browser scripts and diagnostics.
|
|
22
|
+
- Projects that want installed Chrome, Chromium, and Firefox through WebDriver.
|
|
23
|
+
- Tests that need network mocks, saved auth state, virtual time, or accessibility gates.
|
|
24
|
+
- Coding agents that should use the same browser automation primitives as humans.
|
|
25
|
+
|
|
26
|
+
## Not The Goal
|
|
27
|
+
|
|
28
|
+
CraftDriver is not trying to be a giant testing universe. It is the browser control layer: launch, navigate, click, fill, assert, inspect, debug, and get on with your day.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Zero-Config Drivers
|
|
2
|
+
|
|
3
|
+
CraftDriver is designed so a normal install is enough:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install craftdriver --save-dev
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Then launch an installed browser:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { Browser } from 'craftdriver';
|
|
13
|
+
|
|
14
|
+
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You do not need to add `chromedriver`, `geckodriver`, or browser downloads to most projects. CraftDriver resolves the matching WebDriver binary, starts it on a free port, and caches resolution details so later launches are faster.
|
|
18
|
+
|
|
19
|
+
## What Happens At Launch?
|
|
20
|
+
|
|
21
|
+
CraftDriver checks for the driver in this order:
|
|
22
|
+
|
|
23
|
+
| Step | Source |
|
|
24
|
+
| ---- | ----------------------------------------------------------------------- |
|
|
25
|
+
| 1 | Explicit `ChromeService` / `FirefoxService` configuration |
|
|
26
|
+
| 2 | Environment variables such as `CHROMEDRIVER_PATH` or `GECKODRIVER_PATH` |
|
|
27
|
+
| 3 | Project-local binaries in `node_modules/.bin` |
|
|
28
|
+
| 4 | Binaries already available on `PATH` |
|
|
29
|
+
| 5 | Auto-resolved and cached driver matching the installed browser |
|
|
30
|
+
|
|
31
|
+
If a browser update leaves a cached driver stale, CraftDriver retries once with a refreshed driver instead of making you clear the cache manually.
|
|
32
|
+
|
|
33
|
+
## CI Friendly
|
|
34
|
+
|
|
35
|
+
The default setup works well on CI runners that already include Chrome or Firefox. For stricter environments, you can pin the driver path or run offline:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
CRAFTDRIVER_CHROMEDRIVER_PATH=/usr/bin/chromedriver npm test
|
|
39
|
+
CRAFTDRIVER_OFFLINE=1 npm test
|
|
40
|
+
CRAFTDRIVER_CACHE_DIR=/tmp/craftdriver-cache npm test
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## When To Configure Manually
|
|
44
|
+
|
|
45
|
+
Manual configuration is useful when:
|
|
46
|
+
|
|
47
|
+
- your CI image pins a specific browser and driver pair
|
|
48
|
+
- network access is blocked
|
|
49
|
+
- you need custom driver logging
|
|
50
|
+
- you want to avoid any version detection during launch
|
|
51
|
+
|
|
52
|
+
See [Driver Configuration](./driver-configuration.md) for the full reference.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craftdriver",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Modern WebDriver automation library for NodeJS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"examples:start": "npm run serve",
|
|
25
25
|
"docs:api": "node scripts/gen-api-reference.mjs",
|
|
26
26
|
"docs:api:check": "node scripts/gen-api-reference.mjs --check",
|
|
27
|
+
"docs:dev": "vitepress dev docs",
|
|
28
|
+
"docs:build": "vitepress build docs",
|
|
29
|
+
"docs:preview": "vitepress preview docs",
|
|
30
|
+
"docs:check": "npm run docs:api:check && npm run docs:build",
|
|
27
31
|
"prepublishOnly": "npm run build && npm run lint"
|
|
28
32
|
},
|
|
29
33
|
"keywords": [
|
|
@@ -34,11 +38,16 @@
|
|
|
34
38
|
"chromium",
|
|
35
39
|
"automation",
|
|
36
40
|
"browser-automation",
|
|
41
|
+
"e2e",
|
|
42
|
+
"e2e-testing",
|
|
37
43
|
"testing",
|
|
38
44
|
"selenium",
|
|
45
|
+
"mcp",
|
|
39
46
|
"cli",
|
|
40
47
|
"agent",
|
|
41
|
-
"ai"
|
|
48
|
+
"ai",
|
|
49
|
+
"ai-agents",
|
|
50
|
+
"web-testing"
|
|
42
51
|
],
|
|
43
52
|
"author": "Dimitar Topuzov",
|
|
44
53
|
"license": "MIT",
|
|
@@ -49,14 +58,15 @@
|
|
|
49
58
|
"bugs": {
|
|
50
59
|
"url": "https://github.com/dtopuzov/craftdriver/issues"
|
|
51
60
|
},
|
|
52
|
-
"homepage": "https://github.
|
|
61
|
+
"homepage": "https://dtopuzov.github.io/craftdriver/",
|
|
53
62
|
"engines": {
|
|
54
63
|
"node": ">=18"
|
|
55
64
|
},
|
|
56
65
|
"files": [
|
|
57
66
|
"dist",
|
|
58
67
|
"bin",
|
|
59
|
-
"docs",
|
|
68
|
+
"docs/**/*.md",
|
|
69
|
+
"docs/public",
|
|
60
70
|
"skills",
|
|
61
71
|
"README.md",
|
|
62
72
|
"CHANGELOG.md",
|
|
@@ -80,6 +90,7 @@
|
|
|
80
90
|
"rimraf": "^6.1.3",
|
|
81
91
|
"semantic-release": "^25.0.5",
|
|
82
92
|
"typescript": "^5.0.0",
|
|
93
|
+
"vitepress": "^1.6.4",
|
|
83
94
|
"vitest": "^4.0.18"
|
|
84
95
|
},
|
|
85
96
|
"dependencies": {
|
|
@@ -90,6 +101,9 @@
|
|
|
90
101
|
"brace-expansion": "5.0.6",
|
|
91
102
|
"ip-address": "10.1.1",
|
|
92
103
|
"npm": "^11.18.0",
|
|
93
|
-
"undici": "7.28.0"
|
|
104
|
+
"undici": "7.28.0",
|
|
105
|
+
"vitepress": {
|
|
106
|
+
"vite": "6.4.3"
|
|
107
|
+
}
|
|
94
108
|
}
|
|
95
109
|
}
|