craftdriver 1.7.0 → 1.9.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 +25 -12
- package/README.md +16 -6
- package/dist/cli/agentSession.d.ts +62 -0
- package/dist/cli/agentSession.d.ts.map +1 -0
- package/dist/cli/agentSession.js +100 -0
- package/dist/cli/agentSession.js.map +1 -0
- package/dist/cli/artifactPaths.d.ts +42 -0
- package/dist/cli/artifactPaths.d.ts.map +1 -0
- package/dist/cli/artifactPaths.js +144 -0
- package/dist/cli/artifactPaths.js.map +1 -0
- package/dist/cli/bounds.d.ts +88 -0
- package/dist/cli/bounds.d.ts.map +1 -0
- package/dist/cli/bounds.js +128 -0
- package/dist/cli/bounds.js.map +1 -0
- package/dist/cli/client.d.ts +3 -0
- package/dist/cli/client.d.ts.map +1 -1
- package/dist/cli/client.js +24 -8
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/daemon.d.ts +3 -0
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +152 -39
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/defaults.d.ts +23 -3
- package/dist/cli/defaults.d.ts.map +1 -1
- package/dist/cli/defaults.js +50 -5
- package/dist/cli/defaults.js.map +1 -1
- package/dist/cli/dispatcher.d.ts +54 -11
- package/dist/cli/dispatcher.d.ts.map +1 -1
- package/dist/cli/dispatcher.js +851 -54
- package/dist/cli/dispatcher.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +305 -56
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +14 -14
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +328 -180
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/journal.d.ts +153 -0
- package/dist/cli/journal.d.ts.map +1 -0
- package/dist/cli/journal.js +252 -0
- package/dist/cli/journal.js.map +1 -0
- package/dist/cli/lineReader.d.ts +33 -0
- package/dist/cli/lineReader.d.ts.map +1 -0
- package/dist/cli/lineReader.js +66 -0
- package/dist/cli/lineReader.js.map +1 -0
- package/dist/cli/locatorCandidates.d.ts +44 -0
- package/dist/cli/locatorCandidates.d.ts.map +1 -0
- package/dist/cli/locatorCandidates.js +190 -0
- package/dist/cli/locatorCandidates.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +39 -1
- package/dist/cli/mcp/artifacts.d.ts.map +1 -1
- package/dist/cli/mcp/artifacts.js +104 -9
- package/dist/cli/mcp/artifacts.js.map +1 -1
- package/dist/cli/mcp/bounds.d.ts +65 -0
- package/dist/cli/mcp/bounds.d.ts.map +1 -0
- package/dist/cli/mcp/bounds.js +124 -0
- package/dist/cli/mcp/bounds.js.map +1 -0
- package/dist/cli/mcp/params.d.ts +38 -0
- package/dist/cli/mcp/params.d.ts.map +1 -0
- package/dist/cli/mcp/params.js +174 -0
- package/dist/cli/mcp/params.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +94 -16
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +304 -150
- package/dist/cli/mcp/server.js.map +1 -1
- package/dist/cli/mcp/tools.d.ts +62 -17
- package/dist/cli/mcp/tools.d.ts.map +1 -1
- package/dist/cli/mcp/tools.js +373 -158
- package/dist/cli/mcp/tools.js.map +1 -1
- package/dist/cli/pageSemantics.d.ts +6 -0
- package/dist/cli/pageSemantics.d.ts.map +1 -0
- package/dist/cli/pageSemantics.js +6 -0
- package/dist/cli/pageSemantics.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +6 -18
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +689 -45
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/cli/protocol.d.ts +8 -0
- package/dist/cli/protocol.d.ts.map +1 -1
- package/dist/cli/selector.d.ts +3 -3
- package/dist/cli/selector.d.ts.map +1 -1
- package/dist/cli/selector.js +6 -9
- package/dist/cli/selector.js.map +1 -1
- package/dist/cli/sessionRegistry.d.ts +42 -0
- package/dist/cli/sessionRegistry.d.ts.map +1 -0
- package/dist/cli/sessionRegistry.js +99 -0
- package/dist/cli/sessionRegistry.js.map +1 -0
- package/dist/cli/snapshot.d.ts +100 -19
- package/dist/cli/snapshot.d.ts.map +1 -1
- package/dist/cli/snapshot.js +295 -108
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/cli/stateStore.d.ts +50 -0
- package/dist/cli/stateStore.d.ts.map +1 -0
- package/dist/cli/stateStore.js +122 -0
- package/dist/cli/stateStore.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/a11y.d.ts +6 -2
- package/dist/lib/a11y.d.ts.map +1 -1
- package/dist/lib/a11y.js +3 -1
- package/dist/lib/a11y.js.map +1 -1
- package/dist/lib/bidi/evaluate.d.ts +11 -0
- package/dist/lib/bidi/evaluate.d.ts.map +1 -0
- package/dist/lib/bidi/evaluate.js +25 -0
- package/dist/lib/bidi/evaluate.js.map +1 -0
- package/dist/lib/bidi/index.d.ts +1 -1
- package/dist/lib/bidi/index.d.ts.map +1 -1
- package/dist/lib/bidi/index.js +2 -2
- package/dist/lib/bidi/index.js.map +1 -1
- package/dist/lib/bidi/logs.d.ts +3 -0
- package/dist/lib/bidi/logs.d.ts.map +1 -1
- package/dist/lib/bidi/logs.js +7 -0
- package/dist/lib/bidi/logs.js.map +1 -1
- package/dist/lib/bidi/network.d.ts +14 -1
- package/dist/lib/bidi/network.d.ts.map +1 -1
- package/dist/lib/bidi/network.js +45 -7
- package/dist/lib/bidi/network.js.map +1 -1
- package/dist/lib/bidi/storage.d.ts +30 -6
- package/dist/lib/bidi/storage.d.ts.map +1 -1
- package/dist/lib/bidi/storage.js +190 -45
- package/dist/lib/bidi/storage.js.map +1 -1
- package/dist/lib/bidi/types.d.ts +9 -2
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +7 -4
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +186 -65
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +58 -24
- package/dist/lib/browserContext.d.ts.map +1 -1
- package/dist/lib/browserContext.js +300 -99
- package/dist/lib/browserContext.js.map +1 -1
- package/dist/lib/by.d.ts +68 -30
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +54 -23
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/clearFastPath.d.ts.map +1 -1
- package/dist/lib/clearFastPath.js +2 -0
- package/dist/lib/clearFastPath.js.map +1 -1
- package/dist/lib/clickFastPath.d.ts.map +1 -1
- package/dist/lib/clickFastPath.js +2 -0
- package/dist/lib/clickFastPath.js.map +1 -1
- package/dist/lib/driver.d.ts +26 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +123 -4
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/elementHandle.d.ts +13 -10
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +119 -50
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +12 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/expect.d.ts +10 -0
- package/dist/lib/expect.d.ts.map +1 -1
- package/dist/lib/expect.js +76 -49
- package/dist/lib/expect.js.map +1 -1
- package/dist/lib/fillFastPath.d.ts.map +1 -1
- package/dist/lib/fillFastPath.js +2 -0
- package/dist/lib/fillFastPath.js.map +1 -1
- package/dist/lib/frame.d.ts.map +1 -1
- package/dist/lib/frame.js +29 -17
- package/dist/lib/frame.js.map +1 -1
- package/dist/lib/initScript.d.ts +9 -0
- package/dist/lib/initScript.d.ts.map +1 -0
- package/dist/lib/initScript.js +14 -0
- package/dist/lib/initScript.js.map +1 -0
- package/dist/lib/locator.d.ts +21 -54
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +165 -208
- package/dist/lib/locator.js.map +1 -1
- package/dist/lib/page.d.ts +22 -0
- package/dist/lib/page.d.ts.map +1 -1
- package/dist/lib/page.js +71 -13
- package/dist/lib/page.js.map +1 -1
- package/dist/lib/pageSemantics.d.ts +42 -0
- package/dist/lib/pageSemantics.d.ts.map +1 -0
- package/dist/lib/pageSemantics.js +216 -0
- package/dist/lib/pageSemantics.js.map +1 -0
- package/dist/lib/query.d.ts +68 -0
- package/dist/lib/query.d.ts.map +1 -0
- package/dist/lib/query.js +590 -0
- package/dist/lib/query.js.map +1 -0
- package/dist/lib/secureFile.d.ts +8 -0
- package/dist/lib/secureFile.d.ts.map +1 -0
- package/dist/lib/secureFile.js +67 -0
- package/dist/lib/secureFile.js.map +1 -0
- package/dist/lib/sessionStateValidation.d.ts +35 -0
- package/dist/lib/sessionStateValidation.d.ts.map +1 -0
- package/dist/lib/sessionStateValidation.js +223 -0
- package/dist/lib/sessionStateValidation.js.map +1 -0
- package/dist/lib/shadowRoot.d.ts +21 -0
- package/dist/lib/shadowRoot.d.ts.map +1 -0
- package/dist/lib/shadowRoot.js +29 -0
- package/dist/lib/shadowRoot.js.map +1 -0
- package/dist/lib/shadowRootLocator.d.ts +31 -0
- package/dist/lib/shadowRootLocator.d.ts.map +1 -0
- package/dist/lib/shadowRootLocator.js +55 -0
- package/dist/lib/shadowRootLocator.js.map +1 -0
- package/dist/lib/timing.d.ts +2 -2
- package/dist/lib/timing.js +2 -2
- package/dist/lib/tracing.d.ts +2 -1
- package/dist/lib/tracing.d.ts.map +1 -1
- package/dist/lib/tracing.js +12 -1
- package/dist/lib/tracing.js.map +1 -1
- package/docs/accessibility.md +9 -1
- package/docs/agents.md +56 -61
- package/docs/api-reference.md +5 -1
- package/docs/browser-api.md +27 -7
- package/docs/browser-context.md +21 -15
- package/docs/cli.md +353 -30
- package/docs/error-codes.md +7 -4
- package/docs/getting-started.md +6 -1
- package/docs/index.md +1 -1
- package/docs/mcp.md +136 -65
- package/docs/public/examples/agent-actions.html +48 -0
- package/docs/public/examples/agent-debug.html +86 -0
- package/docs/public/examples/agent-late-mutation.html +18 -0
- package/docs/public/examples/agent-ref-shift.html +33 -0
- package/docs/public/examples/iframe-child.html +12 -1
- package/docs/public/examples/shadow-dom.html +137 -0
- package/docs/recipes/login-once-reuse-session.md +33 -5
- package/docs/selectors.md +61 -0
- package/docs/session-management.md +80 -12
- package/docs/why-craftdriver.md +1 -1
- package/package.json +5 -3
- package/skills/craftdriver/SKILL.md +59 -75
- package/skills/craftdriver/agents/openai.yaml +4 -0
- package/skills/craftdriver/cheatsheet.md +18 -4
- package/skills/craftdriver/cli.md +194 -68
- package/skills/craftdriver/patterns.md +17 -8
- package/skills/craftdriver/workflow.md +149 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Log In Once And Reuse The Session
|
|
2
2
|
|
|
3
3
|
Signing in through the UI in every test is slow, and for most tests logging in
|
|
4
|
-
is not the thing being proven. Do it once, save the resulting
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
is not the thing being proven. Do it once, save the resulting session, then
|
|
5
|
+
launch later tests already authenticated with `storageState`. Both halves below
|
|
6
|
+
run against the live
|
|
7
7
|
[login example](https://dtopuzov.github.io/craftdriver/examples/login.html),
|
|
8
|
-
which persists
|
|
8
|
+
which persists auth in a cookie and user preferences in localStorage.
|
|
9
9
|
|
|
10
10
|
## Generate Auth State
|
|
11
11
|
|
|
@@ -38,9 +38,37 @@ await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/login.
|
|
|
38
38
|
await browser.expect('#welcome').toContainText('Welcome back, alice!');
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## What `storageState` Restores
|
|
42
|
+
|
|
43
|
+
On supported WebDriver BiDi sessions, launch restores cookies **and every
|
|
44
|
+
captured localStorage origin** before the first real navigation. CraftDriver
|
|
45
|
+
uses a private, locally fulfilled same-origin document because BiDi has no
|
|
46
|
+
out-of-band localStorage command. The state is written once, so application
|
|
47
|
+
changes survive reload.
|
|
48
|
+
|
|
49
|
+
`storageState` does not restore IndexedDB, Cache Storage, service workers, or
|
|
50
|
+
reusable sessionStorage. Context and launch APIs reject non-empty
|
|
51
|
+
sessionStorage rather than silently dropping it.
|
|
52
|
+
|
|
53
|
+
WebDriver Classic cannot restore arbitrary-origin localStorage at launch, so a
|
|
54
|
+
non-empty launch `storageState` is rejected. Its strict single-origin fallback
|
|
55
|
+
is explicit:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const browser = await Browser.launch({ enableBiDi: false });
|
|
59
|
+
|
|
60
|
+
await browser.navigateTo('https://example.test/app'); // reach the origin
|
|
61
|
+
await browser.loadState('.auth/alice.json'); // now storage can land
|
|
62
|
+
await browser.reload(); // let the app read it
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The fallback validates the complete snapshot before applying anything. A
|
|
66
|
+
second origin or a cookie that cannot be set from the active page fails loudly.
|
|
67
|
+
Chrome/Chromium and Firefox BiDi use the simpler launch form above.
|
|
68
|
+
|
|
41
69
|
## Notes
|
|
42
70
|
|
|
43
|
-
-
|
|
71
|
+
- Add `.auth/` to `.gitignore`; generated state contains live session cookies.
|
|
44
72
|
- Regenerate auth state when the app changes its login or session behavior.
|
|
45
73
|
- Use separate files such as `.auth/admin.json` and `.auth/customer.json` for different roles.
|
|
46
74
|
|
package/docs/selectors.md
CHANGED
|
@@ -435,6 +435,67 @@ await card.getByRole('button', { name: 'Buy' }).click();
|
|
|
435
435
|
await card.getByText('In stock').expect().toBeVisible();
|
|
436
436
|
```
|
|
437
437
|
|
|
438
|
+
## Open Shadow DOM
|
|
439
|
+
|
|
440
|
+
CraftDriver crosses Shadow DOM with an explicit, lazy `shadowRoot()` boundary.
|
|
441
|
+
Call it on the locator or element handle for the shadow host, then locate within
|
|
442
|
+
the returned `ShadowRootLocator`:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
const card = browser.locator('user-card#account').shadowRoot();
|
|
446
|
+
|
|
447
|
+
await card.getByRole('button', { name: 'Edit' }).click();
|
|
448
|
+
await card.getByLabel('Display name').fill('Alice');
|
|
449
|
+
await card.locator('.status').expect().toHaveText('Saved');
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
The boundary is explicit: `browser.locator('user-card .status')` does not pierce
|
|
453
|
+
the root and ordinary CSS keeps its normal tree scope. Both API styles work:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// Lazy Locator host — re-resolves the host and root for every operation.
|
|
457
|
+
const lazyRoot = browser.locator('#account').shadowRoot();
|
|
458
|
+
|
|
459
|
+
// ElementHandle style — browser.find() is locator-backed and auto-waits too.
|
|
460
|
+
const handleRoot = browser.find('#account').shadowRoot();
|
|
461
|
+
await handleRoot.find(By.testId('save')).click();
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Nested open roots compose by crossing each boundary in order:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
const address = browser
|
|
468
|
+
.locator('checkout-shell')
|
|
469
|
+
.shadowRoot()
|
|
470
|
+
.locator('address-form')
|
|
471
|
+
.shadowRoot();
|
|
472
|
+
|
|
473
|
+
await address.getByPlaceholder('City').fill('Sofia');
|
|
474
|
+
await address.getByRole('button', { name: 'Save address' }).click();
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
`ShadowRootLocator` is a search context, not an element. It provides
|
|
478
|
+
`locator()`, `find()`, `findAll()`, and all `getBy*()` helpers; actions and
|
|
479
|
+
assertions live on the descendant `Locator`/`ElementHandle`. Filters, indexing,
|
|
480
|
+
auto-waiting, assertions, locator-scoped accessibility audits, `Page`, and
|
|
481
|
+
`Frame` contexts retain the complete root-aware query chain.
|
|
482
|
+
|
|
483
|
+
The host follows normal locator cardinality: when it matches more than one
|
|
484
|
+
element, use `first()`, `last()`, or `nth()` before `shadowRoot()` to select the
|
|
485
|
+
intended host. Descendant `count()` returns `0` for no current matches, while
|
|
486
|
+
actions and positive assertions auto-wait using the configured timeout.
|
|
487
|
+
|
|
488
|
+
Only roots exposed by the page's public `host.shadowRoot` getter are supported.
|
|
489
|
+
A missing or closed root throws `NO_OPEN_SHADOW_ROOT`; CraftDriver does not use
|
|
490
|
+
WebDriver as a closed-root backdoor. Detached roots restart the complete lazy
|
|
491
|
+
query and end with `DETACHED_SHADOW_ROOT` only when a stable root cannot be
|
|
492
|
+
established. Raw XPath inside a shadow root is transport-dependent; CSS and the
|
|
493
|
+
semantic `getBy*`/`By.*` helpers are the portable choices.
|
|
494
|
+
|
|
495
|
+
The same API is covered on Chrome, Firefox, and Safari. CraftDriver uses
|
|
496
|
+
WebDriver BiDi shadow primitives when the session supports them and falls back
|
|
497
|
+
to the standard WebDriver Classic shadow-root endpoints otherwise.
|
|
498
|
+
|
|
438
499
|
### Locator actions and state
|
|
439
500
|
|
|
440
501
|
| Method | Description |
|
|
@@ -9,14 +9,25 @@ CraftDriver supports Playwright-style session persistence, allowing you to save
|
|
|
9
9
|
Session management is the user-facing feature. Under the hood, CraftDriver uses
|
|
10
10
|
the best available WebDriver transport for the browser you launched.
|
|
11
11
|
|
|
12
|
+
`storageState` is CraftDriver's native cookies-plus-localStorage format. It is
|
|
13
|
+
not a promise of Playwright JSON compatibility and does not include IndexedDB,
|
|
14
|
+
Cache Storage, service workers, or reusable sessionStorage.
|
|
15
|
+
|
|
16
|
+
State files contain live session cookies. Keep them in a dedicated ignored
|
|
17
|
+
directory:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
.auth/
|
|
21
|
+
```
|
|
22
|
+
|
|
12
23
|
## Saving Session State
|
|
13
24
|
|
|
14
25
|
```typescript
|
|
15
26
|
// Save all cookies and localStorage to a file
|
|
16
|
-
await browser.saveState('
|
|
27
|
+
await browser.saveState('.auth/session.json');
|
|
17
28
|
|
|
18
29
|
// Save with options
|
|
19
|
-
await browser.saveState('
|
|
30
|
+
await browser.saveState('.auth/session.json', {
|
|
20
31
|
includeCookies: true, // default: true
|
|
21
32
|
includeLocalStorage: true, // default: true
|
|
22
33
|
includeSessionStorage: false, // default: false
|
|
@@ -51,7 +62,10 @@ The saved file contains:
|
|
|
51
62
|
|
|
52
63
|
```typescript
|
|
53
64
|
// Load state into current browser
|
|
54
|
-
await browser.loadState('
|
|
65
|
+
await browser.loadState('.auth/session.json');
|
|
66
|
+
|
|
67
|
+
// Paths and in-memory state objects are equivalent
|
|
68
|
+
await browser.loadState(await browser.storage.getState());
|
|
55
69
|
```
|
|
56
70
|
|
|
57
71
|
## Working With State Objects
|
|
@@ -63,6 +77,11 @@ const state = await browser.storage.getState();
|
|
|
63
77
|
await browser.storage.setState(state);
|
|
64
78
|
```
|
|
65
79
|
|
|
80
|
+
`browser.storage.setState()` is an active-page operation: navigate to the
|
|
81
|
+
state's sole HTTP(S) origin first. `browser.loadState()` uses the full
|
|
82
|
+
multi-origin hydrator on BiDi when the state has no sessionStorage; otherwise it
|
|
83
|
+
uses the same strict active-page rules.
|
|
84
|
+
|
|
66
85
|
## Launching with Pre-loaded State
|
|
67
86
|
|
|
68
87
|
The most common pattern - launch a browser with existing session:
|
|
@@ -70,7 +89,7 @@ The most common pattern - launch a browser with existing session:
|
|
|
70
89
|
```typescript
|
|
71
90
|
const browser = await Browser.launch({
|
|
72
91
|
browserName: 'chrome',
|
|
73
|
-
storageState: '
|
|
92
|
+
storageState: '.auth/session.json',
|
|
74
93
|
});
|
|
75
94
|
|
|
76
95
|
// Navigate directly to authenticated page
|
|
@@ -78,6 +97,55 @@ await browser.navigateTo('https://example.com/dashboard');
|
|
|
78
97
|
// Already logged in!
|
|
79
98
|
```
|
|
80
99
|
|
|
100
|
+
On supported BiDi sessions, cookies and every captured localStorage origin are
|
|
101
|
+
ready before the first real navigation. A private intercepted document seeds
|
|
102
|
+
each origin once; application changes are not overwritten on reload.
|
|
103
|
+
|
|
104
|
+
## Browser and Transport Support
|
|
105
|
+
|
|
106
|
+
| Surface | Chrome/Chromium BiDi | Firefox BiDi | Chrome/Firefox/Safari Classic |
|
|
107
|
+
| --- | --- | --- | --- |
|
|
108
|
+
| `Browser.launch({ storageState })` | Full cookies + multi-origin localStorage | Full cookies + multi-origin localStorage | Non-empty state rejected before mutation |
|
|
109
|
+
| `browser.newContext({ storageState })` | Full isolated restore | Full isolated restore | Unavailable (Classic has no user contexts) |
|
|
110
|
+
| `context.loadStorageState()` | Full multi-origin overlay | Full multi-origin overlay | Unavailable |
|
|
111
|
+
| `browser.loadState()` | Full multi-origin overlay without sessionStorage; strict active page with it | Same | Strict single-active-origin restore |
|
|
112
|
+
| Save | Cookies + current/open-page localStorage origins | Same | Cookies + current-page localStorage |
|
|
113
|
+
|
|
114
|
+
Chrome and Firefox BiDi and the Chrome/Firefox Classic fallback are covered by
|
|
115
|
+
the integration suite. Chromium uses the same Chrome-family BiDi implementation.
|
|
116
|
+
Safari is Classic-only and therefore receives the standards-only active-origin
|
|
117
|
+
contract, not launch-time restore.
|
|
118
|
+
|
|
119
|
+
### WebDriver Classic
|
|
120
|
+
|
|
121
|
+
Classic cannot write arbitrary-origin localStorage at launch. Use the explicit
|
|
122
|
+
fallback instead:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const browser = await Browser.launch({ enableBiDi: false });
|
|
126
|
+
await browser.navigateTo('https://example.com');
|
|
127
|
+
await browser.loadState('.auth/session.json');
|
|
128
|
+
await browser.reload(); // the app can now read the restored state
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
CraftDriver validates the entire snapshot before mutation. `about:blank`,
|
|
132
|
+
multiple storage origins, a mismatched cookie domain, or a secure cookie that
|
|
133
|
+
cannot be set from the active page fails with `STATE_INVALID`. Non-empty
|
|
134
|
+
`storageState` at Classic launch fails with `UNSUPPORTED` instead of pretending
|
|
135
|
+
that a partial restore succeeded.
|
|
136
|
+
|
|
137
|
+
### sessionStorage and runtime failure semantics
|
|
138
|
+
|
|
139
|
+
`sessionStorage` capture is opt-in diagnostic state. Context and launch APIs
|
|
140
|
+
reject a non-empty `sessionStorage` section because it cannot be transferred to
|
|
141
|
+
future tabs. Active-page APIs can restore one matching origin after navigation.
|
|
142
|
+
|
|
143
|
+
Fresh launch and `newContext` restoration clean up the new session/context on
|
|
144
|
+
failure. An overlay into an existing context is not rollback-safe across
|
|
145
|
+
multiple protocol calls. A runtime failure throws `DRIVER_ERROR` with
|
|
146
|
+
`detail.phase` and `detail.partialApplied`; use a fresh context when failure
|
|
147
|
+
isolation matters.
|
|
148
|
+
|
|
81
149
|
## Cookie Management API
|
|
82
150
|
|
|
83
151
|
Direct cookie manipulation is also available:
|
|
@@ -134,10 +202,10 @@ async function generateAuth() {
|
|
|
134
202
|
await browser.expect('#dashboard').toBeVisible();
|
|
135
203
|
|
|
136
204
|
// Save the authenticated state
|
|
137
|
-
await browser.saveState('
|
|
205
|
+
await browser.saveState('.auth/session.json');
|
|
138
206
|
|
|
139
207
|
await browser.quit();
|
|
140
|
-
console.log('Auth state saved to auth.json');
|
|
208
|
+
console.log('Auth state saved to .auth/session.json');
|
|
141
209
|
}
|
|
142
210
|
|
|
143
211
|
generateAuth();
|
|
@@ -157,7 +225,7 @@ describe('Dashboard', () => {
|
|
|
157
225
|
// Launch with saved auth - already logged in!
|
|
158
226
|
browser = await Browser.launch({
|
|
159
227
|
browserName: 'chrome',
|
|
160
|
-
storageState: '
|
|
228
|
+
storageState: '.auth/session.json',
|
|
161
229
|
});
|
|
162
230
|
});
|
|
163
231
|
|
|
@@ -196,13 +264,13 @@ async function testLoginPersistence() {
|
|
|
196
264
|
await browser.click('#submit');
|
|
197
265
|
await browser.expect('#welcome').toBeVisible();
|
|
198
266
|
|
|
199
|
-
await browser.saveState('
|
|
267
|
+
await browser.saveState('.auth/session.json');
|
|
200
268
|
await browser.quit();
|
|
201
269
|
|
|
202
270
|
// Second browser: Load state and verify logged in
|
|
203
271
|
browser = await Browser.launch({
|
|
204
272
|
browserName: 'chrome',
|
|
205
|
-
storageState: '
|
|
273
|
+
storageState: '.auth/session.json',
|
|
206
274
|
});
|
|
207
275
|
|
|
208
276
|
await browser.navigateTo('http://localhost:3000/login');
|
|
@@ -221,7 +289,7 @@ async function testLoginPersistence() {
|
|
|
221
289
|
| Method | Description |
|
|
222
290
|
| --------------------------- | ---------------------------------- |
|
|
223
291
|
| `saveState(path, options?)` | Save cookies and storage to file |
|
|
224
|
-
| `loadState(
|
|
292
|
+
| `loadState(source)` | Load cookies and storage from a path or object |
|
|
225
293
|
|
|
226
294
|
### StorageStateOptions
|
|
227
295
|
|
|
@@ -241,6 +309,6 @@ async function testLoginPersistence() {
|
|
|
241
309
|
| `setCookies(cookies)` | Set multiple cookies |
|
|
242
310
|
| `clearCookies(filter?)` | Clear cookies, optionally filtered |
|
|
243
311
|
| `getState(options?)` | Get current state as object |
|
|
244
|
-
| `setState(
|
|
312
|
+
| `setState(source)` | Set active-page state from path or object |
|
|
245
313
|
| `saveState(path, options?)` | Save state to file |
|
|
246
|
-
| `loadState(
|
|
314
|
+
| `loadState(source)` | Load active-page state from path or object |
|
package/docs/why-craftdriver.md
CHANGED
|
@@ -16,7 +16,7 @@ CraftDriver is for writing boringly reliable automation against real browsers, w
|
|
|
16
16
|
- 🧾 **Trace evidence** - capture actions, console output, errors, network events, and screenshots.
|
|
17
17
|
- ☁️ **Remote WebDriver** - use the Browser API with a self-hosted Selenium Grid or a cloud provider like [BrowserStack](https://www.browserstack.com/).
|
|
18
18
|
- ⚛️ **Electron apps** - drive packaged Electron desktop apps and mock native OS dialogs (open/save, message boxes).
|
|
19
|
-
- 🤖 **Agent-friendly** - CLI,
|
|
19
|
+
- 🤖 **Agent-friendly** - CLI, a project-local skill, and optional MCP when coding agents need the browser.
|
|
20
20
|
|
|
21
21
|
## Good Fits
|
|
22
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craftdriver",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Modern WebDriver automation library for NodeJS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"lint": "eslint . --ext .ts",
|
|
15
15
|
"format": "prettier --write .",
|
|
16
16
|
"test": "HEADLESS=true vitest run",
|
|
17
|
-
"test:firefox": "BROWSER_NAME=firefox HEADLESS=true vitest run
|
|
17
|
+
"test:firefox": "BROWSER_NAME=firefox HEADLESS=true vitest run",
|
|
18
18
|
"test:chrome": "BROWSER_NAME=chrome vitest run",
|
|
19
19
|
"test:chromium": "BROWSER_NAME=chromium vitest run",
|
|
20
20
|
"test:safari": "BROWSER_NAME=safari vitest run tests/safari.test.ts --maxWorkers=1",
|
|
21
|
+
"test:cli": "HEADLESS=true vitest run --config vitest.cli.config.ts",
|
|
22
|
+
"test:release": "vitest run --config vitest.release.config.ts",
|
|
21
23
|
"test:recipes": "HEADLESS=true vitest run --config vitest.recipes.config.ts",
|
|
22
24
|
"test:electron": "vitest run --config vitest.electron.config.ts",
|
|
23
25
|
"test:browserstack": "vitest run --config vitest.browserstack.config.ts",
|
|
@@ -108,7 +110,7 @@
|
|
|
108
110
|
"yazl": "^3.3.1"
|
|
109
111
|
},
|
|
110
112
|
"overrides": {
|
|
111
|
-
"brace-expansion": "5.0.
|
|
113
|
+
"brace-expansion": "5.0.7",
|
|
112
114
|
"ip-address": "10.1.1",
|
|
113
115
|
"npm": "^11.18.0",
|
|
114
116
|
"undici": "7.28.0",
|
|
@@ -1,89 +1,73 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
name: craftdriver
|
|
3
|
+
description: Explore live web applications with the CraftDriver CLI, validate durable selectors, and write, run, and debug CraftDriver tests. Use when a coding agent needs to inspect a page, drive browser flows, choose locators, author browser automation, or diagnose a failing CraftDriver test.
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
# CraftDriver browser workflow
|
|
7
|
+
|
|
8
|
+
Use CraftDriver's CLI to inspect and exercise the running application, then use
|
|
9
|
+
the public TypeScript API to write and run durable tests. Chrome is the default
|
|
10
|
+
agent workflow.
|
|
6
11
|
|
|
7
12
|
## Core loop
|
|
8
13
|
|
|
9
|
-
```
|
|
10
|
-
navigate →
|
|
14
|
+
```text
|
|
15
|
+
start app → navigate → snapshot → inspect → act → validate selector
|
|
16
|
+
→ write test → run focused test → debug from fresh evidence
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`sleep()` / `setTimeout()` — it's a footgun.
|
|
19
|
+
Actions and `expect(locator).to…()` auto-wait. Do not add sleeps or hand-written
|
|
20
|
+
polling loops.
|
|
16
21
|
|
|
17
|
-
## Selector
|
|
22
|
+
## Selector order
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
Prefer evidence from the live page in this order:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
By.role({ name }) / By.labelText / By.testId
|
|
28
|
+
→ exact visible text → stable CSS → XPath only as a last resort
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## Five rules
|
|
28
|
-
|
|
29
|
-
1. Use `expect(locator).to…()` for assertions. Never hand-roll
|
|
30
|
-
`while (...) { ... sleep(100) }` retry loops — they auto-wait.
|
|
31
|
-
2. Read errors by `code`, not by prose. Every public throw is a
|
|
32
|
-
`CraftdriverError` with a stable `code`. See
|
|
33
|
-
[docs/error-codes.md](../../docs/error-codes.md).
|
|
34
|
-
3. `instanceof CraftdriverError` is true on every public throw;
|
|
35
|
-
`instanceof Error` is also true.
|
|
36
|
-
4. BiDi features (`network`, `logs`, tracing, init scripts, true load
|
|
37
|
-
events) work out of the box — `enableBiDi` defaults to `true`. They only
|
|
38
|
-
break if you explicitly pass `enableBiDi: false`; the error code on the
|
|
39
|
-
wrong transport is `UNSUPPORTED`. Exception: `browserName: 'safari'`
|
|
40
|
-
defaults `enableBiDi` to `false` and rejects `true` — Safari is
|
|
41
|
-
Classic-only (macOS, headed, one session at a time). See
|
|
42
|
-
[docs/safari.md](../../docs/safari.md).
|
|
43
|
-
5. Tests fetch from the example server. Start it in a separate
|
|
44
|
-
terminal: `npm run examples:start` before `npm test`.
|
|
45
|
-
|
|
46
|
-
## When you reach for more
|
|
47
|
-
|
|
48
|
-
- **Writing tests, looking for a method** → read
|
|
49
|
-
[skills/craftdriver/cheatsheet.md](cheatsheet.md). Also see
|
|
50
|
-
[docs/api-reference.md](../../docs/api-reference.md) — every public
|
|
51
|
-
export, one row each.
|
|
52
|
-
- **Login, upload, wait-for-network, etc.** → read
|
|
53
|
-
[skills/craftdriver/patterns.md](patterns.md).
|
|
54
|
-
- **Driving the browser from a shell or agent loop** → read
|
|
55
|
-
[skills/craftdriver/cli.md](cli.md). Same Browser API, exposed as a
|
|
56
|
-
`craftdriver` binary with daemon + ephemeral modes.
|
|
57
|
-
- **Driving from an MCP-aware AI host** (Claude Desktop / Code, Cursor,
|
|
58
|
-
Windsurf, Zed, Goose, Gemini CLI) → `craftdriver mcp` is a stdio
|
|
59
|
-
JSON-RPC server with 14 schema-typed tools. Mutating tools return a
|
|
60
|
-
compact a11y snapshot **diffed from the previous turn** — you see
|
|
61
|
-
what changed without a follow-up read. See
|
|
62
|
-
[docs/mcp.md](../../docs/mcp.md).
|
|
63
|
-
- **An error code you don't recognise** → read
|
|
64
|
-
[docs/error-codes.md](../../docs/error-codes.md).
|
|
65
|
-
|
|
66
|
-
## Probing rule
|
|
67
|
-
|
|
68
|
-
When unsure a selector exists, check before acting. Today: call
|
|
69
|
-
`await locator.count()` (zero-wait, returns the current match count).
|
|
70
|
-
Acting on a hallucinated selector wastes the whole auto-wait budget.
|
|
71
|
-
|
|
72
|
-
For the **CLI and MCP**, the cheapest way to drive a page is to take a
|
|
73
|
-
snapshot first and use refs:
|
|
31
|
+
Confirm a selector against the live page with `craftdriver exists` or
|
|
32
|
+
`craftdriver find` before putting it in a test.
|
|
74
33
|
|
|
75
|
-
|
|
76
|
-
$ craftdriver snapshot
|
|
77
|
-
e4: textbox "Username" #username
|
|
78
|
-
e7: button "Sign in" #submit
|
|
79
|
-
$ craftdriver fill ref=e4 alice
|
|
80
|
-
$ craftdriver click ref=e7
|
|
81
|
-
```
|
|
34
|
+
## Snapshot refs are exploration-only
|
|
82
35
|
|
|
83
|
-
`ref=eN
|
|
84
|
-
|
|
36
|
+
CLI snapshots show `ref=eN`. A ref binds to one element for as long as that
|
|
37
|
+
element lives, and is never reassigned to another one: if the element is removed
|
|
38
|
+
or duplicated, or the page navigates or reloads, the command fails with
|
|
39
|
+
`STALE_REF`. Take a fresh snapshot when that happens — CraftDriver will not
|
|
40
|
+
guess a replacement.
|
|
85
41
|
|
|
86
|
-
|
|
42
|
+
A ref still means nothing outside the current session, so never copy one into
|
|
43
|
+
test source. Convert the element into a durable locator and let CraftDriver
|
|
44
|
+
check it against the live page:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx craftdriver locators ref=e7
|
|
48
|
+
```
|
|
87
49
|
|
|
88
|
-
|
|
89
|
-
|
|
50
|
+
Use a candidate reported `unique` (ordered by durability: role + accessible
|
|
51
|
+
name, label, test ID, unique text, minimal CSS). If none is unique, add a
|
|
52
|
+
`data-testid` to the application instead of committing a positional selector.
|
|
53
|
+
|
|
54
|
+
## Test rules
|
|
55
|
+
|
|
56
|
+
1. Inspect the repository's existing tests and package scripts before choosing
|
|
57
|
+
a test location or command.
|
|
58
|
+
2. Import only from `craftdriver`, never from `craftdriver/src/...`.
|
|
59
|
+
3. Use public locators and `expect(locator)` assertions.
|
|
60
|
+
4. Read failures by stable `CraftdriverError.code`, then gather a fresh
|
|
61
|
+
snapshot and focused page evidence.
|
|
62
|
+
5. Make ordinary reviewable source changes. Never hide a failure with runtime
|
|
63
|
+
locator repair.
|
|
64
|
+
6. Always close the browser in `finally` or the repository's existing fixture.
|
|
65
|
+
|
|
66
|
+
## Focused references
|
|
67
|
+
|
|
68
|
+
- Explore and write a test: [workflow.md](workflow.md)
|
|
69
|
+
- Shell commands: [cli.md](cli.md)
|
|
70
|
+
- TypeScript API cheatsheet: [cheatsheet.md](cheatsheet.md)
|
|
71
|
+
- Worked library recipes: [patterns.md](patterns.md)
|
|
72
|
+
- Full installed package docs: `node_modules/craftdriver/docs/`
|
|
73
|
+
- Optional MCP adapter: `node_modules/craftdriver/docs/mcp.md`
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# craftdriver — cheatsheet
|
|
2
2
|
|
|
3
|
-
Compact reference for writing tests.
|
|
4
|
-
|
|
5
|
-
list.
|
|
3
|
+
Compact reference for writing tests. The full installed API reference is
|
|
4
|
+
`node_modules/craftdriver/docs/api-reference.md`.
|
|
6
5
|
|
|
7
6
|
## Launch & teardown
|
|
8
7
|
|
|
@@ -111,7 +110,7 @@ Codes: `NO_MATCH`, `TIMEOUT_WAITING_VISIBLE`, `TIMEOUT_WAITING_STATE`,
|
|
|
111
110
|
`TIMEOUT_WAITING_DIALOG`, `TIMEOUT`, `EXPECT_MISMATCH`,
|
|
112
111
|
`A11Y_VIOLATIONS`, `EVAL_THREW`, `EVAL_BAD_ARG`, `INVALID_ARGUMENT`,
|
|
113
112
|
`UNSUPPORTED`, `STATE_INVALID`, `DRIVER_ERROR`. Full table:
|
|
114
|
-
|
|
113
|
+
`node_modules/craftdriver/docs/error-codes.md`.
|
|
115
114
|
|
|
116
115
|
## Pages and contexts
|
|
117
116
|
|
|
@@ -124,6 +123,21 @@ const pages = browser.pages();
|
|
|
124
123
|
const fresh = await browser.waitForPage(() => browser.click('a[target=_blank]'));
|
|
125
124
|
```
|
|
126
125
|
|
|
126
|
+
## Reusable login state
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// Save after login (atomic file, mode 0600 where supported).
|
|
130
|
+
await browser.saveState('.auth/alice.json');
|
|
131
|
+
|
|
132
|
+
// BiDi: cookies + multi-origin localStorage are ready before navigation.
|
|
133
|
+
const reused = await Browser.launch({ storageState: '.auth/alice.json' });
|
|
134
|
+
await reused.navigateTo('https://app.example.com/dashboard');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Classic launch rejects non-empty state. Use `Browser.launch()`, navigate to the
|
|
138
|
+
sole captured HTTP(S) origin, then `browser.loadState(...)`. State containing
|
|
139
|
+
sessionStorage always uses that active-origin flow.
|
|
140
|
+
|
|
127
141
|
## Network (BiDi)
|
|
128
142
|
|
|
129
143
|
```ts
|