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.
Files changed (234) hide show
  1. package/CHANGELOG.md +25 -12
  2. package/README.md +16 -6
  3. package/dist/cli/agentSession.d.ts +62 -0
  4. package/dist/cli/agentSession.d.ts.map +1 -0
  5. package/dist/cli/agentSession.js +100 -0
  6. package/dist/cli/agentSession.js.map +1 -0
  7. package/dist/cli/artifactPaths.d.ts +42 -0
  8. package/dist/cli/artifactPaths.d.ts.map +1 -0
  9. package/dist/cli/artifactPaths.js +144 -0
  10. package/dist/cli/artifactPaths.js.map +1 -0
  11. package/dist/cli/bounds.d.ts +88 -0
  12. package/dist/cli/bounds.d.ts.map +1 -0
  13. package/dist/cli/bounds.js +128 -0
  14. package/dist/cli/bounds.js.map +1 -0
  15. package/dist/cli/client.d.ts +3 -0
  16. package/dist/cli/client.d.ts.map +1 -1
  17. package/dist/cli/client.js +24 -8
  18. package/dist/cli/client.js.map +1 -1
  19. package/dist/cli/daemon.d.ts +3 -0
  20. package/dist/cli/daemon.d.ts.map +1 -1
  21. package/dist/cli/daemon.js +152 -39
  22. package/dist/cli/daemon.js.map +1 -1
  23. package/dist/cli/defaults.d.ts +23 -3
  24. package/dist/cli/defaults.d.ts.map +1 -1
  25. package/dist/cli/defaults.js +50 -5
  26. package/dist/cli/defaults.js.map +1 -1
  27. package/dist/cli/dispatcher.d.ts +54 -11
  28. package/dist/cli/dispatcher.d.ts.map +1 -1
  29. package/dist/cli/dispatcher.js +851 -54
  30. package/dist/cli/dispatcher.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +305 -56
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/init.d.ts +14 -14
  35. package/dist/cli/init.d.ts.map +1 -1
  36. package/dist/cli/init.js +328 -180
  37. package/dist/cli/init.js.map +1 -1
  38. package/dist/cli/journal.d.ts +153 -0
  39. package/dist/cli/journal.d.ts.map +1 -0
  40. package/dist/cli/journal.js +252 -0
  41. package/dist/cli/journal.js.map +1 -0
  42. package/dist/cli/lineReader.d.ts +33 -0
  43. package/dist/cli/lineReader.d.ts.map +1 -0
  44. package/dist/cli/lineReader.js +66 -0
  45. package/dist/cli/lineReader.js.map +1 -0
  46. package/dist/cli/locatorCandidates.d.ts +44 -0
  47. package/dist/cli/locatorCandidates.d.ts.map +1 -0
  48. package/dist/cli/locatorCandidates.js +190 -0
  49. package/dist/cli/locatorCandidates.js.map +1 -0
  50. package/dist/cli/mcp/artifacts.d.ts +39 -1
  51. package/dist/cli/mcp/artifacts.d.ts.map +1 -1
  52. package/dist/cli/mcp/artifacts.js +104 -9
  53. package/dist/cli/mcp/artifacts.js.map +1 -1
  54. package/dist/cli/mcp/bounds.d.ts +65 -0
  55. package/dist/cli/mcp/bounds.d.ts.map +1 -0
  56. package/dist/cli/mcp/bounds.js +124 -0
  57. package/dist/cli/mcp/bounds.js.map +1 -0
  58. package/dist/cli/mcp/params.d.ts +38 -0
  59. package/dist/cli/mcp/params.d.ts.map +1 -0
  60. package/dist/cli/mcp/params.js +174 -0
  61. package/dist/cli/mcp/params.js.map +1 -0
  62. package/dist/cli/mcp/server.d.ts +94 -16
  63. package/dist/cli/mcp/server.d.ts.map +1 -1
  64. package/dist/cli/mcp/server.js +304 -150
  65. package/dist/cli/mcp/server.js.map +1 -1
  66. package/dist/cli/mcp/tools.d.ts +62 -17
  67. package/dist/cli/mcp/tools.d.ts.map +1 -1
  68. package/dist/cli/mcp/tools.js +373 -158
  69. package/dist/cli/mcp/tools.js.map +1 -1
  70. package/dist/cli/pageSemantics.d.ts +6 -0
  71. package/dist/cli/pageSemantics.d.ts.map +1 -0
  72. package/dist/cli/pageSemantics.js +6 -0
  73. package/dist/cli/pageSemantics.js.map +1 -0
  74. package/dist/cli/parseArgs.d.ts +6 -18
  75. package/dist/cli/parseArgs.d.ts.map +1 -1
  76. package/dist/cli/parseArgs.js +689 -45
  77. package/dist/cli/parseArgs.js.map +1 -1
  78. package/dist/cli/protocol.d.ts +8 -0
  79. package/dist/cli/protocol.d.ts.map +1 -1
  80. package/dist/cli/selector.d.ts +3 -3
  81. package/dist/cli/selector.d.ts.map +1 -1
  82. package/dist/cli/selector.js +6 -9
  83. package/dist/cli/selector.js.map +1 -1
  84. package/dist/cli/sessionRegistry.d.ts +42 -0
  85. package/dist/cli/sessionRegistry.d.ts.map +1 -0
  86. package/dist/cli/sessionRegistry.js +99 -0
  87. package/dist/cli/sessionRegistry.js.map +1 -0
  88. package/dist/cli/snapshot.d.ts +100 -19
  89. package/dist/cli/snapshot.d.ts.map +1 -1
  90. package/dist/cli/snapshot.js +295 -108
  91. package/dist/cli/snapshot.js.map +1 -1
  92. package/dist/cli/stateStore.d.ts +50 -0
  93. package/dist/cli/stateStore.d.ts.map +1 -0
  94. package/dist/cli/stateStore.js +122 -0
  95. package/dist/cli/stateStore.js.map +1 -0
  96. package/dist/index.d.ts +3 -2
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +1 -0
  99. package/dist/index.js.map +1 -1
  100. package/dist/lib/a11y.d.ts +6 -2
  101. package/dist/lib/a11y.d.ts.map +1 -1
  102. package/dist/lib/a11y.js +3 -1
  103. package/dist/lib/a11y.js.map +1 -1
  104. package/dist/lib/bidi/evaluate.d.ts +11 -0
  105. package/dist/lib/bidi/evaluate.d.ts.map +1 -0
  106. package/dist/lib/bidi/evaluate.js +25 -0
  107. package/dist/lib/bidi/evaluate.js.map +1 -0
  108. package/dist/lib/bidi/index.d.ts +1 -1
  109. package/dist/lib/bidi/index.d.ts.map +1 -1
  110. package/dist/lib/bidi/index.js +2 -2
  111. package/dist/lib/bidi/index.js.map +1 -1
  112. package/dist/lib/bidi/logs.d.ts +3 -0
  113. package/dist/lib/bidi/logs.d.ts.map +1 -1
  114. package/dist/lib/bidi/logs.js +7 -0
  115. package/dist/lib/bidi/logs.js.map +1 -1
  116. package/dist/lib/bidi/network.d.ts +14 -1
  117. package/dist/lib/bidi/network.d.ts.map +1 -1
  118. package/dist/lib/bidi/network.js +45 -7
  119. package/dist/lib/bidi/network.js.map +1 -1
  120. package/dist/lib/bidi/storage.d.ts +30 -6
  121. package/dist/lib/bidi/storage.d.ts.map +1 -1
  122. package/dist/lib/bidi/storage.js +190 -45
  123. package/dist/lib/bidi/storage.js.map +1 -1
  124. package/dist/lib/bidi/types.d.ts +9 -2
  125. package/dist/lib/bidi/types.d.ts.map +1 -1
  126. package/dist/lib/browser.d.ts +7 -4
  127. package/dist/lib/browser.d.ts.map +1 -1
  128. package/dist/lib/browser.js +186 -65
  129. package/dist/lib/browser.js.map +1 -1
  130. package/dist/lib/browserContext.d.ts +58 -24
  131. package/dist/lib/browserContext.d.ts.map +1 -1
  132. package/dist/lib/browserContext.js +300 -99
  133. package/dist/lib/browserContext.js.map +1 -1
  134. package/dist/lib/by.d.ts +68 -30
  135. package/dist/lib/by.d.ts.map +1 -1
  136. package/dist/lib/by.js +54 -23
  137. package/dist/lib/by.js.map +1 -1
  138. package/dist/lib/clearFastPath.d.ts.map +1 -1
  139. package/dist/lib/clearFastPath.js +2 -0
  140. package/dist/lib/clearFastPath.js.map +1 -1
  141. package/dist/lib/clickFastPath.d.ts.map +1 -1
  142. package/dist/lib/clickFastPath.js +2 -0
  143. package/dist/lib/clickFastPath.js.map +1 -1
  144. package/dist/lib/driver.d.ts +26 -0
  145. package/dist/lib/driver.d.ts.map +1 -1
  146. package/dist/lib/driver.js +123 -4
  147. package/dist/lib/driver.js.map +1 -1
  148. package/dist/lib/elementHandle.d.ts +13 -10
  149. package/dist/lib/elementHandle.d.ts.map +1 -1
  150. package/dist/lib/elementHandle.js +119 -50
  151. package/dist/lib/elementHandle.js.map +1 -1
  152. package/dist/lib/errors.d.ts +12 -0
  153. package/dist/lib/errors.d.ts.map +1 -1
  154. package/dist/lib/errors.js +12 -0
  155. package/dist/lib/errors.js.map +1 -1
  156. package/dist/lib/expect.d.ts +10 -0
  157. package/dist/lib/expect.d.ts.map +1 -1
  158. package/dist/lib/expect.js +76 -49
  159. package/dist/lib/expect.js.map +1 -1
  160. package/dist/lib/fillFastPath.d.ts.map +1 -1
  161. package/dist/lib/fillFastPath.js +2 -0
  162. package/dist/lib/fillFastPath.js.map +1 -1
  163. package/dist/lib/frame.d.ts.map +1 -1
  164. package/dist/lib/frame.js +29 -17
  165. package/dist/lib/frame.js.map +1 -1
  166. package/dist/lib/initScript.d.ts +9 -0
  167. package/dist/lib/initScript.d.ts.map +1 -0
  168. package/dist/lib/initScript.js +14 -0
  169. package/dist/lib/initScript.js.map +1 -0
  170. package/dist/lib/locator.d.ts +21 -54
  171. package/dist/lib/locator.d.ts.map +1 -1
  172. package/dist/lib/locator.js +165 -208
  173. package/dist/lib/locator.js.map +1 -1
  174. package/dist/lib/page.d.ts +22 -0
  175. package/dist/lib/page.d.ts.map +1 -1
  176. package/dist/lib/page.js +71 -13
  177. package/dist/lib/page.js.map +1 -1
  178. package/dist/lib/pageSemantics.d.ts +42 -0
  179. package/dist/lib/pageSemantics.d.ts.map +1 -0
  180. package/dist/lib/pageSemantics.js +216 -0
  181. package/dist/lib/pageSemantics.js.map +1 -0
  182. package/dist/lib/query.d.ts +68 -0
  183. package/dist/lib/query.d.ts.map +1 -0
  184. package/dist/lib/query.js +590 -0
  185. package/dist/lib/query.js.map +1 -0
  186. package/dist/lib/secureFile.d.ts +8 -0
  187. package/dist/lib/secureFile.d.ts.map +1 -0
  188. package/dist/lib/secureFile.js +67 -0
  189. package/dist/lib/secureFile.js.map +1 -0
  190. package/dist/lib/sessionStateValidation.d.ts +35 -0
  191. package/dist/lib/sessionStateValidation.d.ts.map +1 -0
  192. package/dist/lib/sessionStateValidation.js +223 -0
  193. package/dist/lib/sessionStateValidation.js.map +1 -0
  194. package/dist/lib/shadowRoot.d.ts +21 -0
  195. package/dist/lib/shadowRoot.d.ts.map +1 -0
  196. package/dist/lib/shadowRoot.js +29 -0
  197. package/dist/lib/shadowRoot.js.map +1 -0
  198. package/dist/lib/shadowRootLocator.d.ts +31 -0
  199. package/dist/lib/shadowRootLocator.d.ts.map +1 -0
  200. package/dist/lib/shadowRootLocator.js +55 -0
  201. package/dist/lib/shadowRootLocator.js.map +1 -0
  202. package/dist/lib/timing.d.ts +2 -2
  203. package/dist/lib/timing.js +2 -2
  204. package/dist/lib/tracing.d.ts +2 -1
  205. package/dist/lib/tracing.d.ts.map +1 -1
  206. package/dist/lib/tracing.js +12 -1
  207. package/dist/lib/tracing.js.map +1 -1
  208. package/docs/accessibility.md +9 -1
  209. package/docs/agents.md +56 -61
  210. package/docs/api-reference.md +5 -1
  211. package/docs/browser-api.md +27 -7
  212. package/docs/browser-context.md +21 -15
  213. package/docs/cli.md +353 -30
  214. package/docs/error-codes.md +7 -4
  215. package/docs/getting-started.md +6 -1
  216. package/docs/index.md +1 -1
  217. package/docs/mcp.md +136 -65
  218. package/docs/public/examples/agent-actions.html +48 -0
  219. package/docs/public/examples/agent-debug.html +86 -0
  220. package/docs/public/examples/agent-late-mutation.html +18 -0
  221. package/docs/public/examples/agent-ref-shift.html +33 -0
  222. package/docs/public/examples/iframe-child.html +12 -1
  223. package/docs/public/examples/shadow-dom.html +137 -0
  224. package/docs/recipes/login-once-reuse-session.md +33 -5
  225. package/docs/selectors.md +61 -0
  226. package/docs/session-management.md +80 -12
  227. package/docs/why-craftdriver.md +1 -1
  228. package/package.json +5 -3
  229. package/skills/craftdriver/SKILL.md +59 -75
  230. package/skills/craftdriver/agents/openai.yaml +4 -0
  231. package/skills/craftdriver/cheatsheet.md +18 -4
  232. package/skills/craftdriver/cli.md +194 -68
  233. package/skills/craftdriver/patterns.md +17 -8
  234. package/skills/craftdriver/workflow.md +149 -0
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 fill <selector> <value>
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
 
@@ -104,9 +139,10 @@ Anything else is treated as a CSS selector, so attribute selectors with
104
139
 
105
140
  ## Snapshot — sanitized DOM with refs
106
141
 
107
- `craftdriver snapshot` returns one line per visible interactive element
108
- on the active page, with a stable ref (`e1`, `e2`, …) that you can
109
- use as a selector for the next command:
142
+ `craftdriver snapshot` returns visible semantic elements on the active page,
143
+ with a stable ref (`e1`, `e2`, …) that you can use as a selector for the next
144
+ command. Open Shadow DOM is traversed recursively and shown with indented
145
+ `#shadow-root (open)` boundaries; closed roots are never inspected:
110
146
 
111
147
  ```bash
112
148
  $ craftdriver snapshot
@@ -124,12 +160,76 @@ $ craftdriver fill ref=e6 hunter2
124
160
  $ craftdriver click ref=e7
125
161
  ```
126
162
 
127
- Refs are recomputed on every `snapshot` call and invalidated on
128
- navigation. A stale ref just returns `NO_MATCH` — take a fresh
129
- snapshot.
163
+ ### Refs bind to an element, not to a position
164
+
165
+ A ref names one specific element for as long as that element lives:
166
+
167
+ - a node that survives a DOM change keeps its ref across snapshots;
168
+ - a new node always gets a fresh number — refs are never reused, not even
169
+ after a navigation;
170
+ - if the element is removed, or the page navigates or
171
+ reloads, the ref fails with `STALE_REF` instead of resolving to
172
+ whatever now sits in that position.
173
+
174
+ That last point is the reason to trust them. Take a fresh `snapshot`
175
+ when you see `STALE_REF`; craftdriver will not guess a replacement.
176
+ `error.detail.reason` says which case fired (`detached`,
177
+ `document-changed`, `unknown-ref`, `ambiguous`, `no-snapshot`).
178
+
179
+ Internally `ref=eN` returns the exact element from the page's identity registry,
180
+ including elements inside open shadow roots. The diagnostic
181
+ `data-craftdriver-ref` attribute is never used for lookup or uniqueness, so an
182
+ authored or cloned marker cannot redirect or invalidate a ref. Auto-waiting and
183
+ native WebDriver actions work unchanged after identity resolution.
184
+
185
+ ## Tabs
186
+
187
+ Commands act on one tab — the active one. A tab that opens on its own
188
+ (`window.open`, `target=_blank`) is listed but is *not* selected, so nothing
189
+ switches under you:
190
+
191
+ ```bash
192
+ $ craftdriver page list
193
+ [0] http://127.0.0.1:8080/checkout.html — Checkout (active)
194
+ [1] http://127.0.0.1:8080/help.html — Help
195
+
196
+ $ craftdriver page select 1
197
+ $ craftdriver text h1 # reads the help tab
198
+ ```
199
+
200
+ Select by index or by the id from `page list`. An unknown id or an
201
+ out-of-range index is an error rather than a fallback to the active tab —
202
+ acting on the wrong tab is the confusion this exists to remove.
203
+
204
+ Selecting, opening, or closing a tab clears the ref registry, so a ref from
205
+ the previous tab fails `STALE_REF` instead of resolving against the new
206
+ document. Take a fresh `snapshot` after switching.
207
+
208
+ ## Locators — durable selectors for a test
209
+
210
+ Refs are for exploring a live page. Never put one in a committed test:
211
+ it means nothing in a later session. `craftdriver locators` turns an
212
+ element into selectors that do survive, and re-resolves each one against
213
+ the current page before offering it:
214
+
215
+ ```bash
216
+ $ craftdriver locators ref=e7
217
+ best: role=button[name=Sign in]
218
+ ✓ role=button[name=Sign in]
219
+ By.role('button', { name: "Sign in" })
220
+ ✓ testid=login-btn
221
+ By.testId("login-btn")
222
+ ~ text=Sign in (3 matches)
223
+ By.text("Sign in")
224
+ ```
225
+
226
+ `✓` unique, `~` ambiguous, `✗` no longer matches. Candidates are ordered
227
+ by durability: role + accessible name, associated label, test id, unique
228
+ text, then minimal CSS. Obviously generated ids (`#a1b2c3d4e5f6`) are
229
+ never offered as CSS candidates — they look stable and are not.
130
230
 
131
- Internally `ref=eN` resolves to a CSS attribute selector
132
- (`[data-craftdriver-ref="eN"]`); auto-waiting works unchanged.
231
+ If nothing resolves uniquely, that is reported rather than papered over;
232
+ the fix is a `data-testid` in the application, not a cleverer selector.
133
233
 
134
234
  ## Output: pretty on a TTY, JSON when piped
135
235
 
@@ -165,10 +265,236 @@ learn from failures fast.
165
265
  - Use `exists` as a 0-wait probe before `click` / `wait` when you're not
166
266
  sure a selector matches.
167
267
 
268
+ ## Named sessions
269
+
270
+ One daemon, several independent browsers. Pass `--session <name>` and the
271
+ command runs against that session's own browser, page selection, cookies,
272
+ snapshot baseline and refs:
273
+
274
+ ```bash
275
+ npx craftdriver go http://127.0.0.1:8080/login.html --session shopper
276
+ npx craftdriver go http://127.0.0.1:8080/login.html --session admin
277
+
278
+ # separate browsers, so separate logins
279
+ npx craftdriver fill '#username' alice --session shopper
280
+ npx craftdriver fill '#username' root --session admin
281
+
282
+ npx craftdriver session list
283
+ npx craftdriver session close admin
284
+ ```
285
+
286
+ Commands with no `--session` use the session named `default`, which is why
287
+ everything above still works if you never pass the flag.
288
+
289
+ Worth knowing:
290
+
291
+ - **Sessions are created on first use.** Naming one costs nothing until a
292
+ command actually needs a browser.
293
+ - **They run independently.** Each session has its own FIFO queue, so a
294
+ slow command in one does not hold up another.
295
+ - **Up to 8 at a time**, because each is a real browser process. Past that,
296
+ creating a new one fails and tells you which are open rather than
297
+ quietly quitting someone else's browser. `session close <name>` frees a
298
+ slot; `craftdriver quit --session <name>` just closes that session's
299
+ browser and keeps the slot.
300
+ - **A ref belongs to the session that issued it.** `e4` in one session and
301
+ `e4` in another are different elements, and craftdriver cannot tell you
302
+ that you mixed them up — it sees a valid ref either way. Keep `snapshot`
303
+ and the commands that spend its refs on the same `--session`.
304
+ - **Names** are 1–32 characters of letters, digits, `-` and `_`, starting
305
+ with a letter or digit. Anything else is rejected before a socket is
306
+ opened.
307
+ - **Not available with `--ephemeral`**, which is one browser that exits
308
+ with the command; a name there would promise a continuity that does not
309
+ exist, so the combination is an error rather than a no-op.
310
+
311
+ ## Console and network history
312
+
313
+ What the page logged and requested, asked for *after* the fact:
314
+
315
+ ```bash
316
+ npx craftdriver go http://127.0.0.1:8080/checkout.html
317
+ npx craftdriver click '#pay'
318
+
319
+ npx craftdriver logs --kind error # exceptions + console.error
320
+ npx craftdriver logs --kind request,response --contains /api/
321
+ npx craftdriver logs wait --contains 'payment ok' --timeout 10000
322
+ ```
323
+
324
+ Capture starts when the browser starts, not when you ask — so an error thrown
325
+ during the first navigation is still there when you go looking. You do not
326
+ arrange capture in advance, and you do not re-run the flow with a listener
327
+ attached.
328
+
329
+ Worth knowing:
330
+
331
+ - **`--kind error` means both** an uncaught exception and a `console.error`.
332
+ They are recorded as different entries, but a caller asking for errors wants
333
+ each of them, and an empty answer on a page that loudly logged one would be
334
+ the most misleading result on offer.
335
+ - **Ask "what changed since", not "what happened".** Every entry has a
336
+ monotonic `seq`, and each result carries a `cursor`. Pass it back as
337
+ `--since` and you get only what is new:
338
+
339
+ ```bash
340
+ CURSOR=$(npx craftdriver logs --json | jq .result.cursor)
341
+ npx craftdriver click '#submit'
342
+ npx craftdriver logs --since "$CURSOR"
343
+ ```
344
+
345
+ - **`logs wait` checks what already arrived before it waits.** By the time an
346
+ agent asks, the event has usually already happened; a wait that only
347
+ subscribed would time out on the common case. It waits only if the entry is
348
+ not already there.
349
+ - **History is bounded and eviction is counted.** 500 entries or 512 KB,
350
+ oldest first. The result reports `dropped`, and `droppedBeforeCursor` is
351
+ non-zero when the page you asked for has a hole — so "no errors" and "the
352
+ errors scrolled off" never look alike.
353
+ - **Network rows are summaries, not captures.** URL, method, status, MIME type
354
+ and time. No bodies, no cookies, no headers of any kind — a journal an agent
355
+ pastes into a transcript cannot carry an `Authorization` header. URLs are
356
+ kept whole, including query strings, so a query token can still enter a
357
+ CLI/MCP transcript when logs are requested. The journal is memory-only; use
358
+ `logs clear` after inspecting sensitive development traffic.
359
+ - **Responses are attributed browser-wide.** The response event carries no
360
+ browsing context, so only requests report a page. That is the protocol's
361
+ limit, reported honestly rather than guessed.
362
+ - **Requires BiDi**, which Chrome and Firefox negotiate by default. Where it is
363
+ unavailable the result says `capturing: false` rather than returning an empty
364
+ list that reads like a quiet page.
365
+
366
+ ## Traces and network mocking
367
+
368
+ A trace records actions, console output, network events and screenshots to a
369
+ file you can read afterwards — useful when a failure is not reproducible by
370
+ staring at the page:
371
+
372
+ ```bash
373
+ npx craftdriver trace start checkout
374
+ npx craftdriver go http://127.0.0.1:8080/checkout.html
375
+ npx craftdriver click '#pay'
376
+ npx craftdriver trace stop --zip
377
+ ```
378
+
379
+ Output lands under `.craftdriver/traces/<name>/` (override with
380
+ `CRAFTDRIVER_TRACE_DIR`): `trace.ndjson` plus a `screenshots/` directory.
381
+ `--zip` also writes a Vibium/Playwright-compatible archive. `--no-screenshots`
382
+ turns off the bulk of the size when you only want the event log.
383
+
384
+ Only one trace records at a time; starting a second is an error rather than a
385
+ silent replacement. Quitting the browser aborts a running trace, so an
386
+ abandoned session leaves nothing dangling.
387
+
388
+ Mocking covers the two rule shapes that are expressible as data:
389
+
390
+ ```bash
391
+ npx craftdriver mock add '**/api/orders*' --status 500 --body '{"error":"nope"}'
392
+ npx craftdriver mock block '**/analytics/**'
393
+ npx craftdriver mock list
394
+ npx craftdriver mock clear
395
+ ```
396
+
397
+ Worth knowing:
398
+
399
+ - **`mock add` replies; `mock block` fails the request.** Both take a URL
400
+ pattern, and both return an id you can `mock remove`.
401
+ - **Everything is validated before installation.** A bad status or an
402
+ oversized body is refused up front — a mock installed wrong surfaces much
403
+ later as an unexplained page error, which is far harder to diagnose.
404
+ - **Up to 20 active at once**, each bounded to a 64 KB body. craftdriver is
405
+ not a fixture server; mock the one call you are testing.
406
+ - **No handler functions.** The library's `network.intercept()` takes a
407
+ callback, which a command line cannot express. Rather than invent a rule
408
+ language, the CLI offers only the flat cases — reach for the library API when
409
+ you need dynamic behaviour.
410
+ - **Requires BiDi**, and says so with `UNSUPPORTED` rather than silently doing
411
+ nothing.
412
+
413
+ ## Saved login state
414
+
415
+ Log in once, then reuse it instead of replaying the form on every run:
416
+
417
+ ```bash
418
+ # log in by hand, once
419
+ npx craftdriver go http://127.0.0.1:8080/login.html
420
+ npx craftdriver fill '#username' alice
421
+ npx craftdriver fill '#password' secret
422
+ npx craftdriver click '#submit'
423
+ npx craftdriver state save alice
424
+
425
+ # later, in a fresh BiDi browser — load before the first real navigation
426
+ npx craftdriver state load alice
427
+ npx craftdriver go http://127.0.0.1:8080/login.html
428
+ ```
429
+
430
+ On Chrome/Chromium and Firefox BiDi, `state load` restores cookies and all
431
+ captured localStorage origins through the same library hydrator as
432
+ `Browser.launch({ storageState })`; no navigate-before-load workaround is
433
+ needed. If a page is already open and must react to the overlay, reload it.
434
+
435
+ WebDriver Classic (including Safari) has a smaller contract: `go <url>` →
436
+ `state load <name>` → `reload`. The active page must match the snapshot's sole
437
+ storage origin and every cookie must be settable there. Broader state fails
438
+ before mutation instead of being partially ignored. A snapshot saved with
439
+ `--session-storage` also uses this active-page path because sessionStorage is
440
+ tab-scoped, even when BiDi is available.
441
+
442
+ Worth knowing:
443
+
444
+ - **State files are credentials.** They hold live session cookies. They are
445
+ written to `.craftdriver/state/` (override with `CRAFTDRIVER_STATE_DIR`,
446
+ which the daemon reads from its own working directory — every `state`
447
+ result reports the `root` it used, so check there if a file is not where
448
+ you expected),
449
+ created owner-only (`0600`) inside an owner-only directory, and written via
450
+ a temp file and a rename so a crash cannot leave a half-written one. Add
451
+ `.craftdriver/` to `.gitignore`.
452
+ - **A save captures every cookie in that browser, not just the current
453
+ site's.** Cookies are stored per browser profile, so `state save` on one
454
+ site also writes any other site you happen to be logged into in that
455
+ session. Local storage is the narrow half — only the current origin's. Use a
456
+ separate `--session` for a login you do not want mixed in.
457
+ - **Nothing is ever printed back.** `state save` reports counts and origins —
458
+ never a cookie value, token, storage value or file path contents.
459
+ - **Pass a name, not a path.** `state save alice` writes `alice.json` under the
460
+ state root. Names are letters, digits, `-` and `_`; a path, a `..`, or a
461
+ leading `-` is rejected, and a symlink inside the state directory cannot
462
+ redirect a write outside it.
463
+ - **Sessions each have their own cookies**, so `state save --session shopper`
464
+ captures that browser's login and no other's.
465
+ - **`state load` clears refs.** The document you snapshotted was a different
466
+ logged-out page; take a fresh `snapshot` afterwards.
467
+ - `--session-storage` adds `sessionStorage` to a save. It is off by default,
468
+ since most apps keep nothing durable there.
469
+
470
+ The library equivalent is `browser.saveState(path)` and
471
+ `Browser.launch({ storageState: path })`. On supported BiDi sessions that launch
472
+ form restores cookies plus multi-origin localStorage before the first real
473
+ navigation. Non-empty state at Classic launch is rejected; use the explicit
474
+ active-origin fallback above.
475
+
168
476
  ## Daemon details
169
477
 
170
- - Socket: `~/.craftdriver/sock` (override with `CRAFTDRIVER_SOCKET`).
171
- - PID file: `~/.craftdriver/pid` (override with `CRAFTDRIVER_PID`).
478
+ - One daemon per project. The socket and PID file live in
479
+ `~/.craftdriver/projects/<project>-<hash>/`, where the hash is derived from
480
+ the project root — the nearest ancestor directory holding a `package.json`
481
+ or `.git`. Running from a subdirectory reaches the same daemon; running in a
482
+ different project reaches a different one, with its own browser, cookies and
483
+ refs. Override the paths with `CRAFTDRIVER_SOCKET` / `CRAFTDRIVER_PID`.
484
+ - Saved state and traces are anchored to that same project root
485
+ (`<project>/.craftdriver/{state,traces}`), not to the working directory the
486
+ daemon was started from. Override with `CRAFTDRIVER_STATE_DIR` /
487
+ `CRAFTDRIVER_TRACE_DIR`.
488
+ - A screenshot without `-o` overwrites
489
+ `<project>/.craftdriver/screenshots/screenshot-<session>.png`, so repeated
490
+ exploratory captures stay bounded. Pass `-o` when you intentionally want to
491
+ keep multiple images.
492
+ - **Not available on Windows.** The daemon binds a Unix domain socket, which
493
+ Windows does not provide. `craftdriver daemon …` and any command that would
494
+ auto-start it exit with `UNSUPPORTED` there. The two socket-free modes work
495
+ on every platform: `craftdriver --ephemeral < script.txt` for a one-shot run,
496
+ and `craftdriver mcp` for an agent session over stdio. The library itself is
497
+ unaffected and runs on Windows normally.
172
498
  - Wire: line-delimited JSON, one request per connection.
173
499
  - First request after `daemon start` triggers the browser launch; later
174
500
  requests reuse the same browser, page and cookies.
@@ -193,27 +519,25 @@ npx craftdriver daemon start --browser firefox
193
519
  Both share the same underlying Browser API and the same error codes, so
194
520
  findings transfer directly between the two.
195
521
 
196
- ## Teach your AI assistant (`craftdriver init`)
522
+ ## Install the project skill (`craftdriver init`)
197
523
 
198
- For every supported assistant, the CLI can drop an opinionated rules
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:
524
+ Install the package-shipped CraftDriver skill in the nearest project root:
202
525
 
203
526
  ```bash
204
- npx craftdriver init copilot # .github/copilot-instructions.md
205
- npx craftdriver init claude # CLAUDE.md
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
527
+ npx craftdriver init codex
528
+ npx craftdriver init codex --dry-run
210
529
  ```
211
530
 
212
- Files are per-project (commit them to git so the team's agents share
213
- the rules). Pre-existing files are skipped unless you pass `--force`;
214
- use `--dry-run` to see what would be written. The body is identical
215
- across flavors, only the file name and any tool-specific header
216
- (e.g. Cursor's `.mdc` frontmatter) differ.
531
+ The destination is `.agents/skills/craftdriver/`. An ownership manifest makes
532
+ repeat installation deterministic and prevents updates from overwriting user
533
+ edits or unowned files. There is no destructive `--force` mode.
534
+
535
+ The installer never reads or changes `AGENTS.md`, `CLAUDE.md`, Copilot
536
+ instructions, Cursor rules, `GEMINI.md`, or `.codex/config.toml`. Legacy init
537
+ flavors return a migration error and write nothing.
538
+
539
+ To use optional MCP, run `npx craftdriver init codex --mcp`. It prints a
540
+ project-pinned snippet for manual configuration; it does not configure MCP.
217
541
 
218
542
  ## Skill pack
219
543
 
@@ -228,6 +552,5 @@ ships a tiered skill pack under `skills/craftdriver/`:
228
552
  | [`patterns.md`](https://github.com/dtopuzov/craftdriver/blob/main/skills/craftdriver/patterns.md) | Worked recipes (login, upload, network-wait, a11y, tracing, clock). |
229
553
  | [`cli.md`](https://github.com/dtopuzov/craftdriver/blob/main/skills/craftdriver/cli.md) | Agent-facing CLI reference. |
230
554
 
231
- Point your agent at `node_modules/craftdriver/skills/craftdriver/SKILL.md`
232
- or copy it into your project. The other files are referenced from
233
- `SKILL.md` and loaded on demand.
555
+ Use `npx craftdriver init codex` to install these files safely. The other files
556
+ are referenced from `SKILL.md` and loaded on demand.
@@ -45,10 +45,13 @@ 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
- | `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. | `error.detail.webDriverError` carries the W3C JSON error code and `error.detail.webDriverMessage` the driver's message; recovery loops match on `webDriverError`. Also inspect `error.cause`. |
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
+ | `NO_OPEN_SHADOW_ROOT` | A shadow host resolved, but its public `shadowRoot` getter returned `null`. This intentionally covers both an unattached root and a closed root. | Verify the host is correct and the component uses `mode: "open"`. |
51
+ | `DETACHED_SHADOW_ROOT` | A shadow root detached while resolving a query and full-plan retries could not recover. | Recreate the component or use a stable host locator; inspect `detail.queryPath` and `detail.attempts`. |
52
+ | `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. |
53
+ | `STALE_REF` | An agent-surface snapshot ref (`ref=eN`) no longer identifies one live registry 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` (corrupt/legacy identity registry), `no-snapshot` (none taken yet). Diagnostic marker attributes do not resolve refs, so cloning one cannot redirect or invalidate a live ref. |
54
+ | `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
55
  | `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
56
  | `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
57
  | `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. |
@@ -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
- storageState: './session.json', // optional: pre-load saved session state
89
+ // BiDi: restore cookies + multi-origin localStorage before first navigation.
90
+ storageState: '.auth/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, MCP server, and assistant rules are there when you want coding agents to drive the browser too.
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