craftdriver 1.7.0 → 1.8.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 +23 -11
- package/README.md +13 -4
- 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 +42 -0
- package/dist/cli/pageSemantics.d.ts.map +1 -0
- package/dist/cli/pageSemantics.js +194 -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/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 -18
- package/dist/cli/snapshot.d.ts.map +1 -1
- package/dist/cli/snapshot.js +248 -102
- 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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- 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 +7 -2
- package/dist/lib/bidi/types.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +6 -3
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +140 -30
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +54 -18
- package/dist/lib/browserContext.d.ts.map +1 -1
- package/dist/lib/browserContext.js +295 -92
- package/dist/lib/browserContext.js.map +1 -1
- package/dist/lib/by.d.ts +3 -3
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +7 -3
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/driver.d.ts +7 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +12 -0
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/errors.d.ts +8 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +8 -0
- package/dist/lib/errors.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/page.d.ts +22 -0
- package/dist/lib/page.d.ts.map +1 -1
- package/dist/lib/page.js +48 -0
- package/dist/lib/page.js.map +1 -1
- 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/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/agents.md +56 -61
- package/docs/api-reference.md +2 -1
- package/docs/browser-api.md +24 -4
- package/docs/browser-context.md +18 -12
- package/docs/cli.md +346 -26
- package/docs/error-codes.md +5 -4
- package/docs/getting-started.md +6 -1
- package/docs/index.md +1 -1
- package/docs/mcp.md +129 -62
- 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/recipes/login-once-reuse-session.md +32 -4
- package/docs/session-management.md +64 -3
- package/docs/why-craftdriver.md +1 -1
- package/package.json +3 -1
- 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 +14 -8
- package/skills/craftdriver/workflow.md +149 -0
package/docs/browser-context.md
CHANGED
|
@@ -120,9 +120,8 @@ Create a new isolated user context. Backed by BiDi
|
|
|
120
120
|
`browser.createUserContext`. Throws in Classic mode.
|
|
121
121
|
|
|
122
122
|
- `opts.storageState`: a `SessionState` object **or** a path to a JSON
|
|
123
|
-
file produced by `BrowserContext.saveStorageState()`. Cookies
|
|
124
|
-
|
|
125
|
-
each captured origin via an internal preload script.
|
|
123
|
+
file produced by `BrowserContext.saveStorageState()`. Cookies and every
|
|
124
|
+
captured localStorage origin are restored before `newContext()` resolves.
|
|
126
125
|
- `opts.baseURL`: a base URL applied to every relative `url` passed to
|
|
127
126
|
`ctx.newPage()` or `page.navigateTo()` inside the context. Absolute
|
|
128
127
|
URLs pass through unchanged. Lets your tests say `'/login'` instead
|
|
@@ -230,11 +229,17 @@ snapshot for inspection. See the auth-fixture example above.
|
|
|
230
229
|
### `BrowserContext.loadStorageState(source): Promise<void>`
|
|
231
230
|
|
|
232
231
|
Apply a previously-captured snapshot. `source` is a `SessionState`
|
|
233
|
-
object or a path to JSON produced by `saveStorageState`.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
object or a path to JSON produced by `saveStorageState`. localStorage is
|
|
233
|
+
seeded once per captured origin through a private, locally fulfilled document;
|
|
234
|
+
cookies are then applied to the same user-context partition. The method resolves
|
|
235
|
+
after hydration and cleanup, so the first real page script sees the state.
|
|
236
|
+
Calling `loadStorageState` again applies another ordered overlay; application
|
|
237
|
+
changes remain owned by the application and survive reload.
|
|
238
|
+
|
|
239
|
+
Restores in one context are serialized. Different user contexts may restore in
|
|
240
|
+
parallel. A runtime protocol failure on an existing context throws
|
|
241
|
+
`DRIVER_ERROR` with `detail.phase` and `detail.partialApplied`; use a newly
|
|
242
|
+
created context when failure isolation is required.
|
|
238
243
|
|
|
239
244
|
### `BrowserContext.close(): Promise<void>`
|
|
240
245
|
|
|
@@ -479,10 +484,11 @@ A default `browser.newContext()` (no options) is always cross-browser.
|
|
|
479
484
|
|
|
480
485
|
A few things that aren't bugs but bite if you don't know them:
|
|
481
486
|
|
|
482
|
-
- **`storageState` covers cookies + localStorage only.** Not
|
|
483
|
-
sessionStorage,
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
- **`storageState` covers cookies + localStorage only.** Not reusable
|
|
488
|
+
sessionStorage, IndexedDB, or Cache Storage. A non-empty sessionStorage
|
|
489
|
+
section is rejected by context/launch APIs rather than ignored. Use the
|
|
490
|
+
active-page `browser.loadState()` fallback for one matching sessionStorage
|
|
491
|
+
origin, or a custom login step.
|
|
486
492
|
- **Browser-level vs context-level emulation race.** `browser.setGeolocation(...)`
|
|
487
493
|
is a session-wide override; `ctx.setGeolocation(...)` is a per-context
|
|
488
494
|
override. If both are set, the context-level one wins inside the
|
package/docs/cli.md
CHANGED
|
@@ -55,10 +55,24 @@ text "#result"
|
|
|
55
55
|
craftdriver go <url>
|
|
56
56
|
craftdriver find <selector> [--all] [--limit N] [--offset M]
|
|
57
57
|
craftdriver click <selector>
|
|
58
|
-
craftdriver
|
|
58
|
+
craftdriver dblclick <selector>
|
|
59
|
+
craftdriver fill <selector> <value> # clear, then enter the value
|
|
60
|
+
craftdriver type <text> # type into the focused element
|
|
61
|
+
craftdriver clear <selector>
|
|
62
|
+
craftdriver check <selector> | craftdriver uncheck <selector>
|
|
63
|
+
craftdriver select <selector> <value> # <option> by its value attribute
|
|
64
|
+
craftdriver focus <selector> # caret to end, ready to type
|
|
65
|
+
craftdriver scroll <selector>
|
|
59
66
|
craftdriver press <key> [selector]
|
|
60
67
|
craftdriver hover <selector>
|
|
61
68
|
|
|
69
|
+
craftdriver key press|down|up <key> [--selector S]
|
|
70
|
+
craftdriver mouse move|click [selector] [--x N --y N] [--button b]
|
|
71
|
+
craftdriver mouse down|up [--button left|middle|right]
|
|
72
|
+
craftdriver mouse wheel [selector] [--delta-x N] [--delta-y N]
|
|
73
|
+
craftdriver dialog inspect | craftdriver dialog accept [text] | craftdriver dialog dismiss
|
|
74
|
+
craftdriver upload <selector> <file> [file...]
|
|
75
|
+
|
|
62
76
|
craftdriver text [selector] [--limit N]
|
|
63
77
|
craftdriver attr <selector> <name>
|
|
64
78
|
craftdriver value <selector>
|
|
@@ -69,11 +83,32 @@ craftdriver wait load [--state load|domcontentloaded|networkidle]
|
|
|
69
83
|
craftdriver exists <selector> # 0-wait probe; exit 0 if any match
|
|
70
84
|
|
|
71
85
|
craftdriver pages
|
|
86
|
+
craftdriver page list # open tabs, marking the active one
|
|
87
|
+
craftdriver page open [url] # open a tab and select it
|
|
88
|
+
craftdriver page select <index|id> # send later commands to that tab
|
|
89
|
+
craftdriver page close <index|id>
|
|
72
90
|
craftdriver snapshot # sanitized DOM summary with refs
|
|
91
|
+
craftdriver locators <selector> # durable selectors, live-validated
|
|
73
92
|
craftdriver screenshot [-o file.png] [--full-page] [--selector S]
|
|
93
|
+
# without -o, lands in
|
|
94
|
+
# .craftdriver/screenshots as
|
|
95
|
+
# screenshot-<session>.png (overwritten)
|
|
74
96
|
craftdriver eval <js> # last resort
|
|
75
97
|
craftdriver back | forward | reload | status | quit
|
|
76
98
|
|
|
99
|
+
craftdriver logs [list] # console + network history
|
|
100
|
+
craftdriver logs wait --contains <text>
|
|
101
|
+
craftdriver logs clear
|
|
102
|
+
craftdriver mock add <pattern> [--status N] [--body S]
|
|
103
|
+
craftdriver mock block <pattern>
|
|
104
|
+
craftdriver mock list | remove <id> | clear
|
|
105
|
+
craftdriver trace start [name] [--no-screenshots]
|
|
106
|
+
craftdriver trace stop [--zip] # then open the NDJSON or zip
|
|
107
|
+
craftdriver trace status
|
|
108
|
+
craftdriver state save <name> [--session-storage]
|
|
109
|
+
craftdriver state load <name> # load before navigation on BiDi
|
|
110
|
+
craftdriver state list
|
|
111
|
+
|
|
77
112
|
craftdriver daemon start|status|stop
|
|
78
113
|
```
|
|
79
114
|
|
|
@@ -124,12 +159,74 @@ $ craftdriver fill ref=e6 hunter2
|
|
|
124
159
|
$ craftdriver click ref=e7
|
|
125
160
|
```
|
|
126
161
|
|
|
127
|
-
Refs
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
### Refs bind to an element, not to a position
|
|
163
|
+
|
|
164
|
+
A ref names one specific element for as long as that element lives:
|
|
165
|
+
|
|
166
|
+
- a node that survives a DOM change keeps its ref across snapshots;
|
|
167
|
+
- a new node always gets a fresh number — refs are never reused, not even
|
|
168
|
+
after a navigation;
|
|
169
|
+
- if the element is removed, duplicated, or the page navigates or
|
|
170
|
+
reloads, the ref fails with `STALE_REF` instead of resolving to
|
|
171
|
+
whatever now sits in that position.
|
|
172
|
+
|
|
173
|
+
That last point is the reason to trust them. Take a fresh `snapshot`
|
|
174
|
+
when you see `STALE_REF`; craftdriver will not guess a replacement.
|
|
175
|
+
`error.detail.reason` says which case fired (`detached`,
|
|
176
|
+
`document-changed`, `unknown-ref`, `ambiguous`, `no-snapshot`).
|
|
130
177
|
|
|
131
178
|
Internally `ref=eN` resolves to a CSS attribute selector
|
|
132
|
-
(`[data-craftdriver-ref="eN"]`)
|
|
179
|
+
(`[data-craftdriver-ref="eN"]`) after craftdriver has checked the ref
|
|
180
|
+
still identifies exactly one element; auto-waiting works unchanged.
|
|
181
|
+
|
|
182
|
+
## Tabs
|
|
183
|
+
|
|
184
|
+
Commands act on one tab — the active one. A tab that opens on its own
|
|
185
|
+
(`window.open`, `target=_blank`) is listed but is *not* selected, so nothing
|
|
186
|
+
switches under you:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
$ craftdriver page list
|
|
190
|
+
[0] http://127.0.0.1:8080/checkout.html — Checkout (active)
|
|
191
|
+
[1] http://127.0.0.1:8080/help.html — Help
|
|
192
|
+
|
|
193
|
+
$ craftdriver page select 1
|
|
194
|
+
$ craftdriver text h1 # reads the help tab
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Select by index or by the id from `page list`. An unknown id or an
|
|
198
|
+
out-of-range index is an error rather than a fallback to the active tab —
|
|
199
|
+
acting on the wrong tab is the confusion this exists to remove.
|
|
200
|
+
|
|
201
|
+
Selecting, opening, or closing a tab clears the ref registry, so a ref from
|
|
202
|
+
the previous tab fails `STALE_REF` instead of resolving against the new
|
|
203
|
+
document. Take a fresh `snapshot` after switching.
|
|
204
|
+
|
|
205
|
+
## Locators — durable selectors for a test
|
|
206
|
+
|
|
207
|
+
Refs are for exploring a live page. Never put one in a committed test:
|
|
208
|
+
it means nothing in a later session. `craftdriver locators` turns an
|
|
209
|
+
element into selectors that do survive, and re-resolves each one against
|
|
210
|
+
the current page before offering it:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
$ craftdriver locators ref=e7
|
|
214
|
+
best: role=button[name=Sign in]
|
|
215
|
+
✓ role=button[name=Sign in]
|
|
216
|
+
By.role('button', { name: "Sign in" })
|
|
217
|
+
✓ testid=login-btn
|
|
218
|
+
By.testId("login-btn")
|
|
219
|
+
~ text=Sign in (3 matches)
|
|
220
|
+
By.text("Sign in")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`✓` unique, `~` ambiguous, `✗` no longer matches. Candidates are ordered
|
|
224
|
+
by durability: role + accessible name, associated label, test id, unique
|
|
225
|
+
text, then minimal CSS. Obviously generated ids (`#a1b2c3d4e5f6`) are
|
|
226
|
+
never offered as CSS candidates — they look stable and are not.
|
|
227
|
+
|
|
228
|
+
If nothing resolves uniquely, that is reported rather than papered over;
|
|
229
|
+
the fix is a `data-testid` in the application, not a cleverer selector.
|
|
133
230
|
|
|
134
231
|
## Output: pretty on a TTY, JSON when piped
|
|
135
232
|
|
|
@@ -165,10 +262,236 @@ learn from failures fast.
|
|
|
165
262
|
- Use `exists` as a 0-wait probe before `click` / `wait` when you're not
|
|
166
263
|
sure a selector matches.
|
|
167
264
|
|
|
265
|
+
## Named sessions
|
|
266
|
+
|
|
267
|
+
One daemon, several independent browsers. Pass `--session <name>` and the
|
|
268
|
+
command runs against that session's own browser, page selection, cookies,
|
|
269
|
+
snapshot baseline and refs:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
npx craftdriver go http://127.0.0.1:8080/login.html --session shopper
|
|
273
|
+
npx craftdriver go http://127.0.0.1:8080/login.html --session admin
|
|
274
|
+
|
|
275
|
+
# separate browsers, so separate logins
|
|
276
|
+
npx craftdriver fill '#username' alice --session shopper
|
|
277
|
+
npx craftdriver fill '#username' root --session admin
|
|
278
|
+
|
|
279
|
+
npx craftdriver session list
|
|
280
|
+
npx craftdriver session close admin
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Commands with no `--session` use the session named `default`, which is why
|
|
284
|
+
everything above still works if you never pass the flag.
|
|
285
|
+
|
|
286
|
+
Worth knowing:
|
|
287
|
+
|
|
288
|
+
- **Sessions are created on first use.** Naming one costs nothing until a
|
|
289
|
+
command actually needs a browser.
|
|
290
|
+
- **They run independently.** Each session has its own FIFO queue, so a
|
|
291
|
+
slow command in one does not hold up another.
|
|
292
|
+
- **Up to 8 at a time**, because each is a real browser process. Past that,
|
|
293
|
+
creating a new one fails and tells you which are open rather than
|
|
294
|
+
quietly quitting someone else's browser. `session close <name>` frees a
|
|
295
|
+
slot; `craftdriver quit --session <name>` just closes that session's
|
|
296
|
+
browser and keeps the slot.
|
|
297
|
+
- **A ref belongs to the session that issued it.** `e4` in one session and
|
|
298
|
+
`e4` in another are different elements, and craftdriver cannot tell you
|
|
299
|
+
that you mixed them up — it sees a valid ref either way. Keep `snapshot`
|
|
300
|
+
and the commands that spend its refs on the same `--session`.
|
|
301
|
+
- **Names** are 1–32 characters of letters, digits, `-` and `_`, starting
|
|
302
|
+
with a letter or digit. Anything else is rejected before a socket is
|
|
303
|
+
opened.
|
|
304
|
+
- **Not available with `--ephemeral`**, which is one browser that exits
|
|
305
|
+
with the command; a name there would promise a continuity that does not
|
|
306
|
+
exist, so the combination is an error rather than a no-op.
|
|
307
|
+
|
|
308
|
+
## Console and network history
|
|
309
|
+
|
|
310
|
+
What the page logged and requested, asked for *after* the fact:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npx craftdriver go http://127.0.0.1:8080/checkout.html
|
|
314
|
+
npx craftdriver click '#pay'
|
|
315
|
+
|
|
316
|
+
npx craftdriver logs --kind error # exceptions + console.error
|
|
317
|
+
npx craftdriver logs --kind request,response --contains /api/
|
|
318
|
+
npx craftdriver logs wait --contains 'payment ok' --timeout 10000
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Capture starts when the browser starts, not when you ask — so an error thrown
|
|
322
|
+
during the first navigation is still there when you go looking. You do not
|
|
323
|
+
arrange capture in advance, and you do not re-run the flow with a listener
|
|
324
|
+
attached.
|
|
325
|
+
|
|
326
|
+
Worth knowing:
|
|
327
|
+
|
|
328
|
+
- **`--kind error` means both** an uncaught exception and a `console.error`.
|
|
329
|
+
They are recorded as different entries, but a caller asking for errors wants
|
|
330
|
+
each of them, and an empty answer on a page that loudly logged one would be
|
|
331
|
+
the most misleading result on offer.
|
|
332
|
+
- **Ask "what changed since", not "what happened".** Every entry has a
|
|
333
|
+
monotonic `seq`, and each result carries a `cursor`. Pass it back as
|
|
334
|
+
`--since` and you get only what is new:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
CURSOR=$(npx craftdriver logs --json | jq .result.cursor)
|
|
338
|
+
npx craftdriver click '#submit'
|
|
339
|
+
npx craftdriver logs --since "$CURSOR"
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
- **`logs wait` checks what already arrived before it waits.** By the time an
|
|
343
|
+
agent asks, the event has usually already happened; a wait that only
|
|
344
|
+
subscribed would time out on the common case. It waits only if the entry is
|
|
345
|
+
not already there.
|
|
346
|
+
- **History is bounded and eviction is counted.** 500 entries or 512 KB,
|
|
347
|
+
oldest first. The result reports `dropped`, and `droppedBeforeCursor` is
|
|
348
|
+
non-zero when the page you asked for has a hole — so "no errors" and "the
|
|
349
|
+
errors scrolled off" never look alike.
|
|
350
|
+
- **Network rows are summaries, not captures.** URL, method, status, MIME type
|
|
351
|
+
and time. No bodies, no cookies, no headers of any kind — a journal an agent
|
|
352
|
+
pastes into a transcript cannot carry an `Authorization` header. URLs are
|
|
353
|
+
kept whole, including query strings, so a query token can still enter a
|
|
354
|
+
CLI/MCP transcript when logs are requested. The journal is memory-only; use
|
|
355
|
+
`logs clear` after inspecting sensitive development traffic.
|
|
356
|
+
- **Responses are attributed browser-wide.** The response event carries no
|
|
357
|
+
browsing context, so only requests report a page. That is the protocol's
|
|
358
|
+
limit, reported honestly rather than guessed.
|
|
359
|
+
- **Requires BiDi**, which Chrome and Firefox negotiate by default. Where it is
|
|
360
|
+
unavailable the result says `capturing: false` rather than returning an empty
|
|
361
|
+
list that reads like a quiet page.
|
|
362
|
+
|
|
363
|
+
## Traces and network mocking
|
|
364
|
+
|
|
365
|
+
A trace records actions, console output, network events and screenshots to a
|
|
366
|
+
file you can read afterwards — useful when a failure is not reproducible by
|
|
367
|
+
staring at the page:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
npx craftdriver trace start checkout
|
|
371
|
+
npx craftdriver go http://127.0.0.1:8080/checkout.html
|
|
372
|
+
npx craftdriver click '#pay'
|
|
373
|
+
npx craftdriver trace stop --zip
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Output lands under `.craftdriver/traces/<name>/` (override with
|
|
377
|
+
`CRAFTDRIVER_TRACE_DIR`): `trace.ndjson` plus a `screenshots/` directory.
|
|
378
|
+
`--zip` also writes a Vibium/Playwright-compatible archive. `--no-screenshots`
|
|
379
|
+
turns off the bulk of the size when you only want the event log.
|
|
380
|
+
|
|
381
|
+
Only one trace records at a time; starting a second is an error rather than a
|
|
382
|
+
silent replacement. Quitting the browser aborts a running trace, so an
|
|
383
|
+
abandoned session leaves nothing dangling.
|
|
384
|
+
|
|
385
|
+
Mocking covers the two rule shapes that are expressible as data:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
npx craftdriver mock add '**/api/orders*' --status 500 --body '{"error":"nope"}'
|
|
389
|
+
npx craftdriver mock block '**/analytics/**'
|
|
390
|
+
npx craftdriver mock list
|
|
391
|
+
npx craftdriver mock clear
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Worth knowing:
|
|
395
|
+
|
|
396
|
+
- **`mock add` replies; `mock block` fails the request.** Both take a URL
|
|
397
|
+
pattern, and both return an id you can `mock remove`.
|
|
398
|
+
- **Everything is validated before installation.** A bad status or an
|
|
399
|
+
oversized body is refused up front — a mock installed wrong surfaces much
|
|
400
|
+
later as an unexplained page error, which is far harder to diagnose.
|
|
401
|
+
- **Up to 20 active at once**, each bounded to a 64 KB body. craftdriver is
|
|
402
|
+
not a fixture server; mock the one call you are testing.
|
|
403
|
+
- **No handler functions.** The library's `network.intercept()` takes a
|
|
404
|
+
callback, which a command line cannot express. Rather than invent a rule
|
|
405
|
+
language, the CLI offers only the flat cases — reach for the library API when
|
|
406
|
+
you need dynamic behaviour.
|
|
407
|
+
- **Requires BiDi**, and says so with `UNSUPPORTED` rather than silently doing
|
|
408
|
+
nothing.
|
|
409
|
+
|
|
410
|
+
## Saved login state
|
|
411
|
+
|
|
412
|
+
Log in once, then reuse it instead of replaying the form on every run:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
# log in by hand, once
|
|
416
|
+
npx craftdriver go http://127.0.0.1:8080/login.html
|
|
417
|
+
npx craftdriver fill '#username' alice
|
|
418
|
+
npx craftdriver fill '#password' secret
|
|
419
|
+
npx craftdriver click '#submit'
|
|
420
|
+
npx craftdriver state save alice
|
|
421
|
+
|
|
422
|
+
# later, in a fresh BiDi browser — load before the first real navigation
|
|
423
|
+
npx craftdriver state load alice
|
|
424
|
+
npx craftdriver go http://127.0.0.1:8080/login.html
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
On Chrome/Chromium and Firefox BiDi, `state load` restores cookies and all
|
|
428
|
+
captured localStorage origins through the same library hydrator as
|
|
429
|
+
`Browser.launch({ storageState })`; no navigate-before-load workaround is
|
|
430
|
+
needed. If a page is already open and must react to the overlay, reload it.
|
|
431
|
+
|
|
432
|
+
WebDriver Classic (including Safari) has a smaller contract: `go <url>` →
|
|
433
|
+
`state load <name>` → `reload`. The active page must match the snapshot's sole
|
|
434
|
+
storage origin and every cookie must be settable there. Broader state fails
|
|
435
|
+
before mutation instead of being partially ignored. A snapshot saved with
|
|
436
|
+
`--session-storage` also uses this active-page path because sessionStorage is
|
|
437
|
+
tab-scoped, even when BiDi is available.
|
|
438
|
+
|
|
439
|
+
Worth knowing:
|
|
440
|
+
|
|
441
|
+
- **State files are credentials.** They hold live session cookies. They are
|
|
442
|
+
written to `.craftdriver/state/` (override with `CRAFTDRIVER_STATE_DIR`,
|
|
443
|
+
which the daemon reads from its own working directory — every `state`
|
|
444
|
+
result reports the `root` it used, so check there if a file is not where
|
|
445
|
+
you expected),
|
|
446
|
+
created owner-only (`0600`) inside an owner-only directory, and written via
|
|
447
|
+
a temp file and a rename so a crash cannot leave a half-written one. Add
|
|
448
|
+
`.craftdriver/` to `.gitignore`.
|
|
449
|
+
- **A save captures every cookie in that browser, not just the current
|
|
450
|
+
site's.** Cookies are stored per browser profile, so `state save` on one
|
|
451
|
+
site also writes any other site you happen to be logged into in that
|
|
452
|
+
session. Local storage is the narrow half — only the current origin's. Use a
|
|
453
|
+
separate `--session` for a login you do not want mixed in.
|
|
454
|
+
- **Nothing is ever printed back.** `state save` reports counts and origins —
|
|
455
|
+
never a cookie value, token, storage value or file path contents.
|
|
456
|
+
- **Pass a name, not a path.** `state save alice` writes `alice.json` under the
|
|
457
|
+
state root. Names are letters, digits, `-` and `_`; a path, a `..`, or a
|
|
458
|
+
leading `-` is rejected, and a symlink inside the state directory cannot
|
|
459
|
+
redirect a write outside it.
|
|
460
|
+
- **Sessions each have their own cookies**, so `state save --session shopper`
|
|
461
|
+
captures that browser's login and no other's.
|
|
462
|
+
- **`state load` clears refs.** The document you snapshotted was a different
|
|
463
|
+
logged-out page; take a fresh `snapshot` afterwards.
|
|
464
|
+
- `--session-storage` adds `sessionStorage` to a save. It is off by default,
|
|
465
|
+
since most apps keep nothing durable there.
|
|
466
|
+
|
|
467
|
+
The library equivalent is `browser.saveState(path)` and
|
|
468
|
+
`Browser.launch({ storageState: path })`. On supported BiDi sessions that launch
|
|
469
|
+
form restores cookies plus multi-origin localStorage before the first real
|
|
470
|
+
navigation. Non-empty state at Classic launch is rejected; use the explicit
|
|
471
|
+
active-origin fallback above.
|
|
472
|
+
|
|
168
473
|
## Daemon details
|
|
169
474
|
|
|
170
|
-
-
|
|
171
|
-
|
|
475
|
+
- One daemon per project. The socket and PID file live in
|
|
476
|
+
`~/.craftdriver/projects/<project>-<hash>/`, where the hash is derived from
|
|
477
|
+
the project root — the nearest ancestor directory holding a `package.json`
|
|
478
|
+
or `.git`. Running from a subdirectory reaches the same daemon; running in a
|
|
479
|
+
different project reaches a different one, with its own browser, cookies and
|
|
480
|
+
refs. Override the paths with `CRAFTDRIVER_SOCKET` / `CRAFTDRIVER_PID`.
|
|
481
|
+
- Saved state and traces are anchored to that same project root
|
|
482
|
+
(`<project>/.craftdriver/{state,traces}`), not to the working directory the
|
|
483
|
+
daemon was started from. Override with `CRAFTDRIVER_STATE_DIR` /
|
|
484
|
+
`CRAFTDRIVER_TRACE_DIR`.
|
|
485
|
+
- A screenshot without `-o` overwrites
|
|
486
|
+
`<project>/.craftdriver/screenshots/screenshot-<session>.png`, so repeated
|
|
487
|
+
exploratory captures stay bounded. Pass `-o` when you intentionally want to
|
|
488
|
+
keep multiple images.
|
|
489
|
+
- **Not available on Windows.** The daemon binds a Unix domain socket, which
|
|
490
|
+
Windows does not provide. `craftdriver daemon …` and any command that would
|
|
491
|
+
auto-start it exit with `UNSUPPORTED` there. The two socket-free modes work
|
|
492
|
+
on every platform: `craftdriver --ephemeral < script.txt` for a one-shot run,
|
|
493
|
+
and `craftdriver mcp` for an agent session over stdio. The library itself is
|
|
494
|
+
unaffected and runs on Windows normally.
|
|
172
495
|
- Wire: line-delimited JSON, one request per connection.
|
|
173
496
|
- First request after `daemon start` triggers the browser launch; later
|
|
174
497
|
requests reuse the same browser, page and cookies.
|
|
@@ -193,27 +516,25 @@ npx craftdriver daemon start --browser firefox
|
|
|
193
516
|
Both share the same underlying Browser API and the same error codes, so
|
|
194
517
|
findings transfer directly between the two.
|
|
195
518
|
|
|
196
|
-
##
|
|
519
|
+
## Install the project skill (`craftdriver init`)
|
|
197
520
|
|
|
198
|
-
|
|
199
|
-
file into the **current project** so the assistant picks up
|
|
200
|
-
craftdriver conventions (selector preference, auto-waiting, error
|
|
201
|
-
codes, CLI usage) on every turn:
|
|
521
|
+
Install the package-shipped CraftDriver skill in the nearest project root:
|
|
202
522
|
|
|
203
523
|
```bash
|
|
204
|
-
npx craftdriver init
|
|
205
|
-
npx craftdriver init
|
|
206
|
-
npx craftdriver init cursor # .cursor/rules/craftdriver.mdc
|
|
207
|
-
npx craftdriver init gemini # GEMINI.md
|
|
208
|
-
npx craftdriver init agents # AGENTS.md (Codex, OpenCode, Aider, Amp, Cursor)
|
|
209
|
-
npx craftdriver init all # every file above
|
|
524
|
+
npx craftdriver init codex
|
|
525
|
+
npx craftdriver init codex --dry-run
|
|
210
526
|
```
|
|
211
527
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
528
|
+
The destination is `.agents/skills/craftdriver/`. An ownership manifest makes
|
|
529
|
+
repeat installation deterministic and prevents updates from overwriting user
|
|
530
|
+
edits or unowned files. There is no destructive `--force` mode.
|
|
531
|
+
|
|
532
|
+
The installer never reads or changes `AGENTS.md`, `CLAUDE.md`, Copilot
|
|
533
|
+
instructions, Cursor rules, `GEMINI.md`, or `.codex/config.toml`. Legacy init
|
|
534
|
+
flavors return a migration error and write nothing.
|
|
535
|
+
|
|
536
|
+
To use optional MCP, run `npx craftdriver init codex --mcp`. It prints a
|
|
537
|
+
project-pinned snippet for manual configuration; it does not configure MCP.
|
|
217
538
|
|
|
218
539
|
## Skill pack
|
|
219
540
|
|
|
@@ -228,6 +549,5 @@ ships a tiered skill pack under `skills/craftdriver/`:
|
|
|
228
549
|
| [`patterns.md`](https://github.com/dtopuzov/craftdriver/blob/main/skills/craftdriver/patterns.md) | Worked recipes (login, upload, network-wait, a11y, tracing, clock). |
|
|
229
550
|
| [`cli.md`](https://github.com/dtopuzov/craftdriver/blob/main/skills/craftdriver/cli.md) | Agent-facing CLI reference. |
|
|
230
551
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
`SKILL.md` and loaded on demand.
|
|
552
|
+
Use `npx craftdriver init codex` to install these files safely. The other files
|
|
553
|
+
are referenced from `SKILL.md` and loaded on demand.
|
package/docs/error-codes.md
CHANGED
|
@@ -45,10 +45,11 @@ try {
|
|
|
45
45
|
| `A11Y_VIOLATIONS` | `browser.a11y.check()` (or scoped variants) found axe-core violations. | Iterate over `error.violations` — each has an `id`, `impact`, and `helpUrl`. |
|
|
46
46
|
| `EVAL_THREW` | The function passed to `evaluate()` threw inside the page. Also fires for `browser.clock` methods (`tick()`, `setSystemTime()`, `runFor()`) called before `install()` — they run as in-page scripts under the hood. | The page-side exception text is in `error.detail.exception`. |
|
|
47
47
|
| `EVAL_BAD_ARG` | `evaluate()` / `addInitScript()` received a non-JSON-serializable argument (function, Symbol, DOM node…). | Pass plain JSON values. |
|
|
48
|
-
| `INVALID_ARGUMENT` | Caller passed an invalid value (bad enum, wrong shape, unparseable duration…). | Read the message; it lists the accepted forms. |
|
|
49
|
-
| `UNSUPPORTED` | Feature exists but is unavailable on this browser/transport (e.g. Chromium-only over Firefox, or a BiDi-only feature with BiDi disabled). | Enable BiDi (`enableBiDi: true`) or switch browser. |
|
|
50
|
-
| `STATE_INVALID` | Method called in the wrong state (e.g. `stopTrace()` without `startTrace()`). | Call the prerequisite first. |
|
|
51
|
-
| `
|
|
48
|
+
| `INVALID_ARGUMENT` | Caller passed an invalid value (bad enum, wrong shape, unparseable duration…). For storage-state restore this includes malformed JSON/native schema, invalid cookies/origins, or multi-origin sessionStorage. | Read the message; it lists the accepted forms. Re-save a corrupt or hand-edited state file. |
|
|
49
|
+
| `UNSUPPORTED` | Feature exists but is unavailable on this browser/transport (e.g. Chromium-only over Firefox, or a BiDi-only feature with BiDi disabled). Storage-state launch/context APIs also reject non-empty sessionStorage rather than silently dropping it, and Classic launch rejects non-empty state. | Enable BiDi (`enableBiDi: true`) or switch browser. For sessionStorage or Classic, launch without state, navigate to its sole origin, then call `browser.loadState()`. |
|
|
50
|
+
| `STATE_INVALID` | Method called in the wrong state (e.g. `stopTrace()` without `startTrace()`). A Classic/active-page state restore also uses this when there is no active HTTP(S) origin or its origin cannot accept every requested storage entry and cookie. | Call the prerequisite first. For active-page restore, navigate to the state’s sole origin before loading it. |
|
|
51
|
+
| `STALE_REF` | An agent-surface snapshot ref (`ref=eN`) no longer identifies exactly one live element. | Take a fresh `snapshot` and use the new ref. `error.detail.reason` says which case fired: `detached` (element removed), `document-changed` (page navigated or reloaded), `unknown-ref` (never issued for this document), `ambiguous` (duplicated, e.g. by `cloneNode`), `no-snapshot` (none taken yet). craftdriver never guesses a replacement element — refs are for exploration; use a durable locator (`craftdriver locators`) in committed tests. |
|
|
52
|
+
| `DRIVER_ERROR` | A WebDriver command returned a protocol error (non-200 response) — e.g. `stale element reference`, `element click intercepted`, `invalid selector` — or a transport-level failure. A live-context state overlay can also fail after some entries were applied. | `error.detail.webDriverError` carries the W3C JSON error code and `error.detail.webDriverMessage` the driver's message; recovery loops match on `webDriverError`. For storage restore inspect `phase` and `partialApplied`; use a fresh context when failure isolation matters. Also inspect `error.cause`. |
|
|
52
53
|
| `ELECTRON_DRIVER_MISMATCH` | The resolved chromedriver can't drive the Electron app, caught **before** a session is created. | `error.detail.kind` is `'chromium-major'` (driver major ≠ the app's bundled Chromium; see `driverMajor` / `expectedChromiumMajor` / `electronVersion`) or `'arch'` (`driverArch` ≠ `runtimeArch`). The `hint` names the fix. |
|
|
53
54
|
| `ELECTRON_LAUNCH_FAILED` | The Electron app process exited during session creation ("Chrome instance exited"). | `error.detail` carries the diagnosed cause (`macSigning: 'unsigned'`/`'adhoc'` on macOS, `sandboxDisabled: false` on Linux); `hint` is the top remediation; the message appends the chromedriver output tail; `error.cause` is the original driver error. |
|
|
54
55
|
| `ELECTRON_MAIN_UNAVAILABLE` | `browser.electron.executeMain(...)` couldn't reach the app's main process. | Launch with `electron: { mainProcess: true }`; ensure the app build keeps the `EnableNodeCliInspectArguments` fuse enabled (default). `hint` names the fix. |
|
package/docs/getting-started.md
CHANGED
|
@@ -86,10 +86,15 @@ Pick a browser with `browserName`:
|
|
|
86
86
|
```typescript
|
|
87
87
|
const browser = await Browser.launch({
|
|
88
88
|
browserName: 'firefox', // 'chrome' | 'chromium' | 'firefox' | 'safari'
|
|
89
|
-
|
|
89
|
+
// BiDi: restore cookies + multi-origin localStorage before first navigation.
|
|
90
|
+
storageState: './session.json', // path or in-memory SessionState
|
|
90
91
|
});
|
|
91
92
|
```
|
|
92
93
|
|
|
94
|
+
WebDriver Classic cannot restore non-empty state at launch. For its strict
|
|
95
|
+
single-origin fallback, navigate first and call `browser.loadState()`; see
|
|
96
|
+
[Session Management](./session-management.md#webdriver-classic).
|
|
97
|
+
|
|
93
98
|
To switch browsers from an environment variable in your own tests, read it
|
|
94
99
|
yourself and pass it to `browserName` — craftdriver doesn't read any browser
|
|
95
100
|
env var for you:
|
package/docs/index.md
CHANGED
|
@@ -30,7 +30,7 @@ features:
|
|
|
30
30
|
- title: Test time and quality
|
|
31
31
|
details: Use virtual time, axe-core accessibility checks, traces, screenshots, console logs, and JS error capture.
|
|
32
32
|
- title: Agent-friendly
|
|
33
|
-
details: CLI,
|
|
33
|
+
details: CLI, a safe project-local skill, and optional MCP are there when you want coding agents to drive the browser too.
|
|
34
34
|
---
|
|
35
35
|
|
|
36
36
|
## Quick Start
|