craftdriver 1.0.3 → 1.1.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 (63) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +80 -160
  3. package/dist/lib/browser.d.ts +22 -0
  4. package/dist/lib/browser.d.ts.map +1 -1
  5. package/dist/lib/browser.js +14 -1
  6. package/dist/lib/browser.js.map +1 -1
  7. package/dist/lib/chrome.d.ts +15 -3
  8. package/dist/lib/chrome.d.ts.map +1 -1
  9. package/dist/lib/chrome.js +4 -2
  10. package/dist/lib/chrome.js.map +1 -1
  11. package/dist/lib/driver.d.ts.map +1 -1
  12. package/dist/lib/driver.js +2 -0
  13. package/dist/lib/driver.js.map +1 -1
  14. package/dist/lib/driverManager.d.ts +31 -0
  15. package/dist/lib/driverManager.d.ts.map +1 -1
  16. package/dist/lib/driverManager.js +165 -31
  17. package/dist/lib/driverManager.js.map +1 -1
  18. package/dist/lib/firefox.d.ts +4 -3
  19. package/dist/lib/firefox.d.ts.map +1 -1
  20. package/dist/lib/firefox.js.map +1 -1
  21. package/dist/lib/http.d.ts +1 -1
  22. package/dist/lib/http.d.ts.map +1 -1
  23. package/dist/lib/http.js +35 -7
  24. package/dist/lib/http.js.map +1 -1
  25. package/dist/lib/timing.d.ts +13 -0
  26. package/dist/lib/timing.d.ts.map +1 -1
  27. package/dist/lib/timing.js +13 -0
  28. package/dist/lib/timing.js.map +1 -1
  29. package/dist/lib/types.d.ts +2 -0
  30. package/dist/lib/types.d.ts.map +1 -1
  31. package/docs/agents.md +92 -0
  32. package/docs/api-reference.md +108 -46
  33. package/docs/browser-api.md +2 -1
  34. package/docs/browser-context.md +4 -7
  35. package/docs/browser-logs.md +158 -0
  36. package/docs/cli.md +26 -26
  37. package/docs/driver-configuration.md +103 -4
  38. package/docs/element-api.md +37 -6
  39. package/docs/emulation.md +6 -2
  40. package/docs/index.md +64 -0
  41. package/docs/keyboard-mouse.md +28 -10
  42. package/docs/mobile-emulation.md +5 -2
  43. package/docs/network.md +203 -0
  44. package/docs/public/craftdriver-mark.svg +15 -0
  45. package/docs/public/social-card.svg +27 -0
  46. package/docs/recipes/accessibility-gate.md +53 -0
  47. package/docs/recipes/console-error-gate.md +57 -0
  48. package/docs/recipes/file-upload-download.md +58 -0
  49. package/docs/recipes/login-once-reuse-session.md +73 -0
  50. package/docs/recipes/mobile-flow-with-network-and-logs.md +57 -0
  51. package/docs/recipes/mock-api-and-assert-network.md +60 -0
  52. package/docs/recipes/multi-user-contexts.md +58 -0
  53. package/docs/recipes/trace-failing-test.md +64 -0
  54. package/docs/recipes/virtual-clock-time-sensitive-ui.md +60 -0
  55. package/docs/recipes/vitest-browser-lifecycle.md +56 -0
  56. package/docs/recipes.md +33 -0
  57. package/docs/selectors.md +22 -1
  58. package/docs/session-management.md +18 -1
  59. package/docs/standards.md +40 -0
  60. package/docs/why-craftdriver.md +28 -0
  61. package/docs/zero-config-drivers.md +64 -0
  62. package/package.json +19 -5
  63. package/docs/bidi-features.md +0 -470
@@ -0,0 +1,53 @@
1
+ # Run Accessibility Gates
2
+
3
+ Use this pattern when CI should reject serious accessibility regressions, while
4
+ still allowing deliberate rule exceptions in one place.
5
+
6
+ ```ts
7
+ import { afterAll, beforeAll, beforeEach, describe, it } from 'vitest';
8
+ import { Browser } from 'craftdriver';
9
+
10
+ const A11Y_OPTIONS = {
11
+ minImpact: 'serious' as const,
12
+ disableRules: [
13
+ // Example: keep temporary exceptions explicit and reviewed.
14
+ 'color-contrast',
15
+ ],
16
+ };
17
+
18
+ describe('accessibility gate', () => {
19
+ let browser: Browser;
20
+
21
+ beforeAll(async () => {
22
+ browser = await Browser.launch({ browserName: 'chrome' });
23
+ });
24
+
25
+ beforeEach(async () => {
26
+ await browser.navigateTo('http://localhost:3000/settings');
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await browser.quit();
31
+ });
32
+
33
+ it('passes page-level accessibility checks', async () => {
34
+ await browser.a11y.check(A11Y_OPTIONS);
35
+ });
36
+
37
+ it('passes modal accessibility checks', async () => {
38
+ await browser.getByRole('button', { name: 'Edit profile' }).click();
39
+ await browser.locator('#profile-modal').a11y.check(A11Y_OPTIONS);
40
+ });
41
+ });
42
+ ```
43
+
44
+ ## Notes
45
+
46
+ - Use `check()` when violations should fail the test.
47
+ - Use `audit()` when you want to write a report or inspect violations manually.
48
+ - Prefer scoped checks for dynamic UI such as dialogs, menus, and checkout panels.
49
+
50
+ ## Learn More
51
+
52
+ - [Accessibility](../accessibility.md)
53
+ - [Selectors](../selectors.md)
@@ -0,0 +1,57 @@
1
+ # Fail On Console And JavaScript Errors
2
+
3
+ Use this pattern when a flow can look correct in the DOM while still logging
4
+ client-side errors. Start log capture early, run the flow, then fail on
5
+ unexpected JavaScript errors.
6
+
7
+ ```ts
8
+ import { afterAll, beforeAll, beforeEach, describe, it } from 'vitest';
9
+ import { Browser } from 'craftdriver';
10
+
11
+ describe('critical browser flows', () => {
12
+ let browser: Browser;
13
+
14
+ beforeAll(async () => {
15
+ browser = await Browser.launch({
16
+ browserName: 'chrome',
17
+ captureLogs: true,
18
+ });
19
+ });
20
+
21
+ beforeEach(async () => {
22
+ browser.logs.clearLogs();
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await browser.quit();
27
+ });
28
+
29
+ it('saves settings without browser errors', async () => {
30
+ await browser.navigateTo('http://localhost:3000/settings');
31
+ await browser.getByLabel('Display name').fill('Alice');
32
+ await browser.getByRole('button', { name: 'Save' }).click();
33
+
34
+ await browser.expect('#toast').toContainText('Saved');
35
+ browser.logs.assertNoErrors();
36
+ });
37
+ });
38
+ ```
39
+
40
+ ## Wait For A Known Log
41
+
42
+ When the log itself is part of the expected behavior, arm the wait before the
43
+ action that emits it:
44
+
45
+ ```ts
46
+ const saved = browser.logs.waitForConsole((message) => {
47
+ return message.level === 'info' && message.text.includes('settings:saved');
48
+ });
49
+
50
+ await browser.getByRole('button', { name: 'Save' }).click();
51
+ await saved;
52
+ ```
53
+
54
+ ## Learn More
55
+
56
+ - [Console Logs And JavaScript Errors](../browser-logs.md)
57
+ - [Tracing](../tracing.md)
@@ -0,0 +1,58 @@
1
+ # Test File Uploads And Downloads
2
+
3
+ Use this pattern when a workflow imports data, uploads an attachment, exports a
4
+ report, or verifies a generated file.
5
+
6
+ ```ts
7
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
8
+ import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
9
+ import { resolve, join } from 'node:path';
10
+ import { Browser } from 'craftdriver';
11
+
12
+ describe('reports', () => {
13
+ let browser: Browser;
14
+ const downloadsDir = resolve('.tmp/downloads');
15
+ const fixture = resolve('tests/fixtures/sample.txt');
16
+
17
+ beforeAll(async () => {
18
+ mkdirSync(downloadsDir, { recursive: true });
19
+ browser = await Browser.launch({
20
+ browserName: 'chrome',
21
+ downloadsDir,
22
+ });
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await browser.quit();
27
+ rmSync(downloadsDir, { recursive: true, force: true });
28
+ });
29
+
30
+ it('uploads a source file and downloads a report', async () => {
31
+ await browser.navigateTo('http://localhost:3000/reports');
32
+
33
+ await browser.find('#source-file').setInputFiles(fixture);
34
+ await browser.expect('#upload-status').toContainText('sample.txt');
35
+
36
+ const download = await browser.waitForDownload(() => {
37
+ return browser.getByRole('button', { name: 'Export report' }).click();
38
+ });
39
+
40
+ const target = join(downloadsDir, download.suggestedFilename);
41
+ await download.saveAs(target);
42
+
43
+ expect(existsSync(target)).toBe(true);
44
+ expect(readFileSync(target, 'utf8')).toContain('Report');
45
+ });
46
+ });
47
+ ```
48
+
49
+ ## Notes
50
+
51
+ - Configure `downloadsDir` at launch so files land somewhere predictable.
52
+ - Wrap the click that triggers the download in `waitForDownload()`.
53
+ - Use `setInputFiles()` on the actual `<input type="file">` element.
54
+
55
+ ## Learn More
56
+
57
+ - [Element API](../element-api.md)
58
+ - [Browser API](../browser-api.md)
@@ -0,0 +1,73 @@
1
+ # Log In Once And Reuse The Session
2
+
3
+ Use this pattern when login is slow, rate-limited, or not the thing most tests
4
+ are trying to prove. Log in once, save cookies and localStorage, then launch
5
+ future tests already signed in.
6
+
7
+ ## Generate Auth State
8
+
9
+ Run this as a setup step before the tests that need an authenticated user.
10
+
11
+ ```ts
12
+ import { mkdir } from 'node:fs/promises';
13
+ import { Browser } from 'craftdriver';
14
+
15
+ const authState = '.auth/alice.json';
16
+
17
+ await mkdir('.auth', { recursive: true });
18
+
19
+ const browser = await Browser.launch({ browserName: 'chrome' });
20
+
21
+ try {
22
+ await browser.navigateTo('http://localhost:3000/login');
23
+ await browser.getByLabel('Email').fill('alice@example.com');
24
+ await browser.getByLabel('Password').fill(process.env.TEST_PASSWORD!);
25
+ await browser.getByRole('button', { name: 'Sign in' }).click();
26
+ await browser.expect('#account').toContainText('Alice');
27
+
28
+ await browser.saveState(authState);
29
+ } finally {
30
+ await browser.quit();
31
+ }
32
+ ```
33
+
34
+ ## Use Auth State In Tests
35
+
36
+ ```ts
37
+ import { afterAll, beforeAll, beforeEach, describe, it } from 'vitest';
38
+ import { Browser } from 'craftdriver';
39
+
40
+ describe('authenticated dashboard', () => {
41
+ let browser: Browser;
42
+
43
+ beforeAll(async () => {
44
+ browser = await Browser.launch({
45
+ browserName: 'chrome',
46
+ storageState: '.auth/alice.json',
47
+ });
48
+ });
49
+
50
+ beforeEach(async () => {
51
+ await browser.navigateTo('http://localhost:3000/dashboard');
52
+ });
53
+
54
+ afterAll(async () => {
55
+ await browser.quit();
56
+ });
57
+
58
+ it('shows account data', async () => {
59
+ await browser.expect('#account').toContainText('Alice');
60
+ });
61
+ });
62
+ ```
63
+
64
+ ## Notes
65
+
66
+ - Keep generated auth files out of source control if they contain real secrets.
67
+ - Regenerate auth state when the app changes login/session behavior.
68
+ - Use separate files such as `.auth/admin.json` and `.auth/customer.json` for different roles.
69
+
70
+ ## Learn More
71
+
72
+ - [Session Management](../session-management.md)
73
+ - [Browser Contexts](../browser-context.md)
@@ -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)
@@ -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
  ```