craftdriver 1.6.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.
Files changed (229) hide show
  1. package/CHANGELOG.md +35 -6
  2. package/README.md +17 -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 +42 -0
  71. package/dist/cli/pageSemantics.d.ts.map +1 -0
  72. package/dist/cli/pageSemantics.js +194 -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/sessionRegistry.d.ts +42 -0
  81. package/dist/cli/sessionRegistry.d.ts.map +1 -0
  82. package/dist/cli/sessionRegistry.js +99 -0
  83. package/dist/cli/sessionRegistry.js.map +1 -0
  84. package/dist/cli/snapshot.d.ts +100 -18
  85. package/dist/cli/snapshot.d.ts.map +1 -1
  86. package/dist/cli/snapshot.js +248 -102
  87. package/dist/cli/snapshot.js.map +1 -1
  88. package/dist/cli/stateStore.d.ts +50 -0
  89. package/dist/cli/stateStore.d.ts.map +1 -0
  90. package/dist/cli/stateStore.js +122 -0
  91. package/dist/cli/stateStore.js.map +1 -0
  92. package/dist/index.d.ts +2 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +2 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/lib/bidi/index.d.ts +1 -1
  97. package/dist/lib/bidi/index.d.ts.map +1 -1
  98. package/dist/lib/bidi/index.js +2 -2
  99. package/dist/lib/bidi/index.js.map +1 -1
  100. package/dist/lib/bidi/logs.d.ts +3 -0
  101. package/dist/lib/bidi/logs.d.ts.map +1 -1
  102. package/dist/lib/bidi/logs.js +7 -0
  103. package/dist/lib/bidi/logs.js.map +1 -1
  104. package/dist/lib/bidi/network.d.ts +14 -1
  105. package/dist/lib/bidi/network.d.ts.map +1 -1
  106. package/dist/lib/bidi/network.js +45 -7
  107. package/dist/lib/bidi/network.js.map +1 -1
  108. package/dist/lib/bidi/storage.d.ts +30 -6
  109. package/dist/lib/bidi/storage.d.ts.map +1 -1
  110. package/dist/lib/bidi/storage.js +190 -45
  111. package/dist/lib/bidi/storage.js.map +1 -1
  112. package/dist/lib/bidi/types.d.ts +7 -2
  113. package/dist/lib/bidi/types.d.ts.map +1 -1
  114. package/dist/lib/browser.d.ts +38 -3
  115. package/dist/lib/browser.d.ts.map +1 -1
  116. package/dist/lib/browser.js +196 -30
  117. package/dist/lib/browser.js.map +1 -1
  118. package/dist/lib/browserContext.d.ts +54 -18
  119. package/dist/lib/browserContext.d.ts.map +1 -1
  120. package/dist/lib/browserContext.js +295 -92
  121. package/dist/lib/browserContext.js.map +1 -1
  122. package/dist/lib/by.d.ts +3 -3
  123. package/dist/lib/by.d.ts.map +1 -1
  124. package/dist/lib/by.js +7 -3
  125. package/dist/lib/by.js.map +1 -1
  126. package/dist/lib/driver.d.ts +7 -0
  127. package/dist/lib/driver.d.ts.map +1 -1
  128. package/dist/lib/driver.js +12 -0
  129. package/dist/lib/driver.js.map +1 -1
  130. package/dist/lib/errors.d.ts +15 -0
  131. package/dist/lib/errors.d.ts.map +1 -1
  132. package/dist/lib/errors.js +15 -0
  133. package/dist/lib/errors.js.map +1 -1
  134. package/dist/lib/initScript.d.ts +9 -0
  135. package/dist/lib/initScript.d.ts.map +1 -0
  136. package/dist/lib/initScript.js +14 -0
  137. package/dist/lib/initScript.js.map +1 -0
  138. package/dist/lib/page.d.ts +22 -0
  139. package/dist/lib/page.d.ts.map +1 -1
  140. package/dist/lib/page.js +48 -0
  141. package/dist/lib/page.js.map +1 -1
  142. package/dist/lib/secureFile.d.ts +8 -0
  143. package/dist/lib/secureFile.d.ts.map +1 -0
  144. package/dist/lib/secureFile.js +67 -0
  145. package/dist/lib/secureFile.js.map +1 -0
  146. package/dist/lib/sessionStateValidation.d.ts +35 -0
  147. package/dist/lib/sessionStateValidation.d.ts.map +1 -0
  148. package/dist/lib/sessionStateValidation.js +223 -0
  149. package/dist/lib/sessionStateValidation.js.map +1 -0
  150. package/dist/lib/timing.d.ts +8 -0
  151. package/dist/lib/timing.d.ts.map +1 -1
  152. package/dist/lib/timing.js +8 -0
  153. package/dist/lib/timing.js.map +1 -1
  154. package/dist/lib/tracing.d.ts +2 -1
  155. package/dist/lib/tracing.d.ts.map +1 -1
  156. package/dist/lib/tracing.js +12 -1
  157. package/dist/lib/tracing.js.map +1 -1
  158. package/dist/lib/visual/antialias.d.ts +41 -0
  159. package/dist/lib/visual/antialias.d.ts.map +1 -0
  160. package/dist/lib/visual/antialias.js +126 -0
  161. package/dist/lib/visual/antialias.js.map +1 -0
  162. package/dist/lib/visual/codec.d.ts +24 -0
  163. package/dist/lib/visual/codec.d.ts.map +1 -0
  164. package/dist/lib/visual/codec.js +11 -0
  165. package/dist/lib/visual/codec.js.map +1 -0
  166. package/dist/lib/visual/compare.d.ts +38 -0
  167. package/dist/lib/visual/compare.d.ts.map +1 -0
  168. package/dist/lib/visual/compare.js +209 -0
  169. package/dist/lib/visual/compare.js.map +1 -0
  170. package/dist/lib/visual/diff.d.ts +18 -0
  171. package/dist/lib/visual/diff.d.ts.map +1 -0
  172. package/dist/lib/visual/diff.js +23 -0
  173. package/dist/lib/visual/diff.js.map +1 -0
  174. package/dist/lib/visual/expectScreenshot.d.ts +65 -0
  175. package/dist/lib/visual/expectScreenshot.d.ts.map +1 -0
  176. package/dist/lib/visual/expectScreenshot.js +287 -0
  177. package/dist/lib/visual/expectScreenshot.js.map +1 -0
  178. package/dist/lib/visual/index.d.ts +10 -0
  179. package/dist/lib/visual/index.d.ts.map +1 -0
  180. package/dist/lib/visual/index.js +9 -0
  181. package/dist/lib/visual/index.js.map +1 -0
  182. package/dist/lib/visual/pixelMath.d.ts +32 -0
  183. package/dist/lib/visual/pixelMath.d.ts.map +1 -0
  184. package/dist/lib/visual/pixelMath.js +70 -0
  185. package/dist/lib/visual/pixelMath.js.map +1 -0
  186. package/dist/lib/visual/pngHeader.d.ts +27 -0
  187. package/dist/lib/visual/pngHeader.d.ts.map +1 -0
  188. package/dist/lib/visual/pngHeader.js +63 -0
  189. package/dist/lib/visual/pngHeader.js.map +1 -0
  190. package/dist/lib/visual/pngjsCodec.d.ts +22 -0
  191. package/dist/lib/visual/pngjsCodec.d.ts.map +1 -0
  192. package/dist/lib/visual/pngjsCodec.js +70 -0
  193. package/dist/lib/visual/pngjsCodec.js.map +1 -0
  194. package/dist/lib/visual/types.d.ts +89 -0
  195. package/dist/lib/visual/types.d.ts.map +1 -0
  196. package/dist/lib/visual/types.js +20 -0
  197. package/dist/lib/visual/types.js.map +1 -0
  198. package/dist/lib/visual/visualError.d.ts +32 -0
  199. package/dist/lib/visual/visualError.d.ts.map +1 -0
  200. package/dist/lib/visual/visualError.js +59 -0
  201. package/dist/lib/visual/visualError.js.map +1 -0
  202. package/dist/lib/wait.d.ts.map +1 -1
  203. package/dist/lib/wait.js +7 -1
  204. package/dist/lib/wait.js.map +1 -1
  205. package/docs/agents.md +56 -61
  206. package/docs/api-reference.md +16 -1
  207. package/docs/browser-api.md +24 -4
  208. package/docs/browser-context.md +18 -12
  209. package/docs/cli.md +346 -26
  210. package/docs/error-codes.md +6 -4
  211. package/docs/getting-started.md +6 -1
  212. package/docs/index.md +1 -1
  213. package/docs/mcp.md +129 -62
  214. package/docs/public/examples/agent-actions.html +48 -0
  215. package/docs/public/examples/agent-debug.html +86 -0
  216. package/docs/public/examples/agent-late-mutation.html +18 -0
  217. package/docs/public/examples/agent-ref-shift.html +33 -0
  218. package/docs/recipes/login-once-reuse-session.md +32 -4
  219. package/docs/screenshots.md +15 -6
  220. package/docs/session-management.md +64 -3
  221. package/docs/visual-testing.md +254 -0
  222. package/docs/why-craftdriver.md +4 -3
  223. package/package.json +5 -1
  224. package/skills/craftdriver/SKILL.md +59 -75
  225. package/skills/craftdriver/agents/openai.yaml +4 -0
  226. package/skills/craftdriver/cheatsheet.md +18 -4
  227. package/skills/craftdriver/cli.md +194 -68
  228. package/skills/craftdriver/patterns.md +14 -8
  229. package/skills/craftdriver/workflow.md +149 -0
@@ -1,104 +1,230 @@
1
- # craftdriver CLI
1
+ # CraftDriver CLI for agent exploration
2
2
 
3
- The `craftdriver` binary is what you reach for from a **shell**, not from
4
- TypeScript. Use it for probing, debugging, and agent-driven exploration.
3
+ Use the CLI to learn the live page and exercise a flow. Use the TypeScript
4
+ library for committed tests.
5
5
 
6
- ## Install once
6
+ ## Safe setup
7
7
 
8
8
  ```bash
9
- npm install craftdriver
9
+ npm install --save-dev craftdriver
10
+ npx craftdriver init codex
10
11
  npx craftdriver --help
11
12
  ```
12
13
 
13
- ## Daemon model (preferred)
14
+ The init command installs `.agents/skills/craftdriver/`. It never reads or
15
+ changes `AGENTS.md`, `CLAUDE.md`, other assistant instructions, or
16
+ `.codex/config.toml`. `--dry-run` previews the installation. `--mcp` only prints
17
+ an optional project-pinned MCP snippet.
14
18
 
15
- Long-lived browser; each command is a one-shot RPC. State (page,
16
- cookies, storage) survives between calls.
19
+ ## Persistent browser
17
20
 
18
21
  ```bash
19
22
  npx craftdriver daemon start
20
23
  npx craftdriver go http://127.0.0.1:8080/login.html
21
- npx craftdriver click 'button[type=submit]'
24
+ npx craftdriver snapshot
22
25
  npx craftdriver daemon stop
23
26
  ```
24
27
 
25
- If no daemon is running, the first command auto-starts one. Use
26
- `daemon start` explicitly when you want a non-default browser
27
- (`--browser firefox`) or a specific timing.
28
+ The first ordinary command auto-starts the daemon when needed. State, cookies,
29
+ and the active page survive between commands.
30
+
31
+ The daemon uses a Unix socket and is not available on Windows. On Windows, use
32
+ the configured MCP server for a persistent agent session, or send the whole
33
+ flow in one `--ephemeral` script.
34
+
35
+ ## Current commands
36
+
37
+ ```text
38
+ go <url>
39
+ find <selector> [--all] [--limit N] [--offset N]
40
+ exists <selector>
41
+ click <selector>
42
+ dblclick <selector>
43
+ fill <selector> <value>
44
+ type <text>
45
+ clear <selector>
46
+ check <selector> | uncheck <selector>
47
+ select <selector> <value>
48
+ focus <selector> | scroll <selector>
49
+ press <key> [selector]
50
+ hover <selector>
51
+ key press|down|up <key> [--selector S]
52
+ mouse move|click [selector] [--x N --y N] [--button left|middle|right]
53
+ mouse down|up [--button left|middle|right]
54
+ mouse wheel [selector] [--delta-x N] [--delta-y N]
55
+ dialog inspect | dialog accept [text] | dialog dismiss
56
+ upload <selector> <file> [file...]
57
+ text [selector] [--limit N]
58
+ attr <selector> <name>
59
+ value <selector>
60
+ is visible|enabled|checked <selector>
61
+ wait <selector> [--state visible|hidden|attached|detached] [--timeout ms]
62
+ wait load [--state load|domcontentloaded|networkidle]
63
+ pages
64
+ page list | open [url] | select <index|id> | close <index|id>
65
+ snapshot
66
+ locators <selector>
67
+ screenshot [-o out.png] [--full-page] [--selector selector]
68
+ eval <javascript>
69
+ back | forward | reload | status | quit
70
+ daemon start | status | stop
71
+ session list | close [name]
72
+ state save <name> [--session-storage] | load <name> | list
73
+ logs [list] [--kind console,error,request,response] [--level l]
74
+ [--contains s] [--since N] [--limit N] | wait ... | clear
75
+ mock add <pattern> [--status N] [--body S] | block <pattern>
76
+ | list | remove <id> | clear
77
+ trace start [name] [--no-screenshots] | stop [--zip] | status
78
+ ```
79
+
80
+ ## Named sessions
28
81
 
29
- ## Ephemeral mode (sandboxed agents)
82
+ Add `--session <name>` to any command to run it against its own browser,
83
+ cookies, page selection, snapshot baseline and refs. Omitting the flag means
84
+ the session named `default`.
30
85
 
31
86
  ```bash
32
- printf 'go http://127.0.0.1:8080/login.html
33
- fill "#user" alice
34
- click "button[type=submit]"
35
- text "#result"
36
- ' | npx craftdriver --ephemeral
87
+ npx craftdriver go http://127.0.0.1:8080/login.html --session shopper
88
+ npx craftdriver go http://127.0.0.1:8080/admin.html --session admin
89
+ npx craftdriver session list
90
+ npx craftdriver session close admin
37
91
  ```
38
92
 
39
- One short-lived browser for the whole script. No daemon, no socket.
93
+ Use this when one workflow needs two logged-in identities at once, not as a
94
+ default habit — each session is a real browser process and only 8 may be open
95
+ at a time. Sessions start on first use and run independently, so a slow
96
+ command in one does not block another.
40
97
 
41
- ## Commands you actually use
98
+ **Refs are per-session.** `e4` in one session and `e4` in another are
99
+ different elements, and nothing will warn you if you mix them up. Run
100
+ `snapshot` and the commands that use its refs with the same `--session`.
42
101
 
43
- ```
44
- go <url> navigate active page
45
- find <sel> [--all] first match (or all); pretty-prints index + tag + text
46
- exists <sel> 0-wait probe; exit 0 = match, exit 1 = none
47
- click <sel>
48
- fill <sel> <value>
49
- press <key> [sel]
50
- hover <sel>
51
- text [sel] page text or element text
52
- attr <sel> <name>
53
- value <sel>
54
- is visible|enabled|checked <sel>
55
- wait <sel> [--state visible|hidden|attached|detached] [--timeout ms]
56
- wait load [--state load|domcontentloaded|networkidle]
57
- pages
58
- screenshot [-o out.png] [--full-page] [--selector sel]
59
- eval <js> last resort
60
- back | forward | reload | status | quit
61
- daemon start|status|stop
62
- ```
102
+ `--session` is not available with `--ephemeral`, which is a single browser
103
+ that exits with the command.
104
+
105
+ `select` matches an `<option>` by its `value` attribute, not its label.
106
+
107
+ Use `fill <selector> <value>` to put a value in a field — it clears first and
108
+ delivers real key events, so fields with key handlers work. `type <text>` takes
109
+ no selector and types into whatever holds focus, mapping onto
110
+ `browser.keyboard.type()`; reach for `focus <selector>` then `type` only when
111
+ you need to append to existing text. `focus` leaves the caret at the end.
112
+
113
+ `eval` is a last resort for inspection, not a replacement for public test APIs.
63
114
 
64
115
  ## Selector syntax
65
116
 
66
- CSS by default. Switch kind with `prefix=value`:
117
+ CSS is the default. Prefix semantic selectors:
67
118
 
119
+ ```text
120
+ role=button[name=Submit] label=Email testid=login-btn
121
+ text=Sign In text*=Sign placeholder=Search…
122
+ alt=Logo title=Help xpath=//div[1]
123
+ id=submit name=email tag=h1
68
124
  ```
69
- role=button[name=Submit] text=Sign In text*=Sign
70
- label=Email placeholder=Search… testid=login-btn
71
- alt=Logo title=Help xpath=//div[1]
72
- id=submit name=email tag=h1
73
- ```
74
125
 
75
- Anything else (including CSS attribute selectors like
76
- `button[type=submit]`) is parsed as CSS.
126
+ Use `exists` as a zero-wait uniqueness probe and `find --all` to inspect
127
+ ambiguous matches.
128
+
129
+ ## Tabs
130
+
131
+ Commands act on the active tab. A tab the application opens by itself is
132
+ listed by `page list` but is not selected, so nothing switches under you —
133
+ use `page select <index|id>` first. An unknown target is an error rather than
134
+ a fallback to the active tab. Switching, opening, or closing a tab clears
135
+ refs, so take a fresh `snapshot` afterwards.
136
+
137
+ ## Console and network evidence
77
138
 
78
- ## Output
139
+ `logs` answers what the page logged and requested, after the fact — capture is
140
+ already running before your first command, so you never arrange it in advance
141
+ or re-run a flow to observe it.
79
142
 
80
- - TTY human-readable text.
81
- - Piped or redirected `{"ok":true,"result":…}` per line.
82
- - Force with `--json` or `--pretty`.
83
- - Errors include a stable `code:` line (same codes as the library
84
- — see `docs/error-codes.md`). Use the code, not the prose.
143
+ Use it to check your work: after a click, `logs --kind error` tells you whether
144
+ the app threw, and `logs --kind request,response --contains /api/` shows what it
145
+ called. This is the evidence to cite when explaining a failure.
85
146
 
86
- ## Exit codes
147
+ Ask for deltas, not dumps: every result carries a `cursor`; pass it back as
148
+ `--since` to get only what happened since. `logs wait --contains <text>` scans
149
+ what already arrived before waiting, so it resolves immediately when the event
150
+ has already happened.
87
151
 
88
- - `0` success (or `exists` matched 1)
89
- - `1` assertion / timeout / `NO_MATCH` / `exists` matched zero
90
- - `2` usage error
152
+ History is bounded (500 entries / 512 KB, oldest evicted). Check `dropped` and
153
+ `droppedBeforeCursor` before concluding that nothing happened a non-zero
154
+ `droppedBeforeCursor` means the page you asked for has a hole in it.
155
+
156
+ Network rows carry URL, method, status, MIME type and time only. No bodies,
157
+ cookies or headers — do not report them as if they were a full capture. URLs
158
+ include query strings, so avoid echoing sensitive query tokens into a report.
159
+
160
+ ## Traces and mocking
161
+
162
+ `trace start <name>` … `trace stop --zip` records actions, console, network and
163
+ screenshots under `.craftdriver/traces/`. Reach for it when a failure is not
164
+ explicable from a snapshot — not by default, since it costs size and time.
165
+ One trace at a time; a second `start` is an error.
166
+
167
+ `mock add <pattern> --status N --body S` serves a fixed response and
168
+ `mock block <pattern>` fails matching requests, so you can drive error paths
169
+ without touching the application. `mock list` and `mock clear` manage them;
170
+ clear them when done, since they outlive the command that added them.
171
+
172
+ Only these flat shapes exist. The library's `network.intercept()` takes a
173
+ handler function that no command line can express — if you need dynamic
174
+ behaviour, write it in a test with the library API instead of asking for a
175
+ CLI flag that does not exist.
176
+
177
+ ## Saved login state
178
+
179
+ `state save <name>` captures the current browser's cookies and local storage;
180
+ `state load <name>` puts them back. Use it so you log in once instead of
181
+ replaying the form for every exploration.
182
+
183
+ On Chrome/Chromium and Firefox BiDi, load before the first real navigation:
184
+ `state load <name>` → `go <url>`. Cookies plus all captured localStorage origins
185
+ are ready for the page's first script. If a page is already open, reload it
186
+ after the overlay.
187
+
188
+ Classic browsers (including Safari) require `go <url>` → `state load <name>` →
189
+ `reload`, and only a single matching origin is supported. State saved with
190
+ `--session-storage` also requires that active-page order because sessionStorage
191
+ is tab-scoped. Unsupported combinations fail before mutation rather than
192
+ silently restoring only cookies.
193
+
194
+ State files are credentials — cookies included. They live under
195
+ `.craftdriver/state/`, owner-only. Never print one, commit one, or paste its
196
+ contents into a test; pass a bare name (`alice`), never a path. `state save`
197
+ reports counts and origins only, and that is all you should report onward.
198
+
199
+ `state load` clears refs, so take a fresh `snapshot` after it.
200
+
201
+ ## Refs vs. durable locators
202
+
203
+ A `ref=eN` from `snapshot` names one element for as long as it lives. It cannot
204
+ drift onto a different element: if the element is removed or duplicated, or the
205
+ page navigates or reloads, the command fails with `STALE_REF` and you take a
206
+ fresh snapshot. Refs are never reused, so an old ref never matches a new page.
207
+
208
+ Refs are still exploration state and never belong in test code. To write a test,
209
+ turn the element into a durable selector and let CraftDriver check it against
210
+ the live page first:
211
+
212
+ ```bash
213
+ npx craftdriver locators ref=e7
214
+ ```
91
215
 
92
- ## Defaults
216
+ Each candidate is reported `unique`, `ambiguous`, or `missing`, ordered by
217
+ durability (role + name, label, test id, unique text, minimal CSS). Use one
218
+ reported `unique`. If none is, add a `data-testid` to the application rather
219
+ than committing a positional selector.
93
220
 
94
- - 5 s per-call timeout (override with `--timeout ms` or
95
- `CRAFTDRIVER_AGENT_TIMEOUT`).
96
- - Probe with `exists` before `click`/`wait` when you're guessing.
97
- - Don't `sleep`. `wait <sel>` is auto-waiting; `wait load` waits on
98
- document state.
221
+ ## Output and failures
99
222
 
100
- ## When NOT to use the CLI
223
+ - TTY output is concise text; piped output is JSON per line.
224
+ - `--json` and `--pretty` force the output format.
225
+ - Exit `0` is success, `1` is action/assertion/no-match failure, and `2` is
226
+ invalid CLI usage.
227
+ - Errors carry the same stable codes as the public library.
101
228
 
102
- Writing a test suite. Use the library (`import { Browser } from
103
- 'craftdriver'`) — it has 30 s default timeouts, full TS types, and
104
- chainable `Locator` ergonomics.
229
+ Do not add sleeps. CLI actions already auto-wait, and `wait` expresses explicit
230
+ page conditions.
@@ -1,7 +1,6 @@
1
- # craftdriver — patterns
1
+ # CraftDriver — patterns
2
2
 
3
- Worked recipes. Each is ~200 tokens; load on demand from
4
- [SKILL.md](SKILL.md).
3
+ Worked recipes, each kept compact. Load on demand from [SKILL.md](SKILL.md).
5
4
 
6
5
  ## 1. Login, save storage state for reuse
7
6
 
@@ -13,21 +12,28 @@ await browser.locator(By.labelText('Password')).fill(process.env.PW!);
13
12
  await browser.locator(By.role('button', { name: 'Sign in' })).click();
14
13
  await browser.locator(By.testId('dashboard')).expect().toBeVisible();
15
14
 
16
- // Persist for fast subsequent runs.
17
- const state = await browser.defaultContext.storageState();
18
- await fs.writeFile('.auth/state.json', JSON.stringify(state));
15
+ // Persist atomically with owner-only permissions where supported.
16
+ await browser.saveState('.auth/state.json');
19
17
  await browser.quit();
20
18
  ```
21
19
 
22
20
  ## 2. Re-use saved login
23
21
 
22
+ On a BiDi session, launch restores cookies and every captured localStorage
23
+ origin before the first real navigation. Application changes then survive
24
+ reload because the state is seeded once, not replayed on every document.
25
+
24
26
  ```ts
25
- const state = JSON.parse(await fs.readFile('.auth/state.json', 'utf8'));
26
- const browser = await Browser.launch({ storageState: state });
27
+ const browser = await Browser.launch({ storageState: '.auth/state.json' });
27
28
  await browser.navigateTo('https://app.example.com/dashboard');
28
29
  await browser.locator(By.testId('dashboard')).expect().toBeVisible();
29
30
  ```
30
31
 
32
+ Classic WebDriver cannot seed arbitrary origins before navigation: launch
33
+ rejects non-empty state. Launch first, navigate to the sole captured origin,
34
+ then call `browser.loadState('.auth/state.json')`. Snapshots containing
35
+ sessionStorage also require that active-origin flow, including on BiDi.
36
+
31
37
  ## 3. Wait for a network response after a click
32
38
 
33
39
  ```ts
@@ -0,0 +1,149 @@
1
+ # Explore an application and write a CraftDriver test
2
+
3
+ This workflow turns live browser evidence into an ordinary, reviewable test.
4
+
5
+ ## 1. Learn the project
6
+
7
+ Before editing, inspect `package.json`, existing test files, fixtures, and the
8
+ commands the repository already uses. Reuse its test runner, naming, setup, URL,
9
+ and browser lifecycle. Do not create a second test framework or edit repository
10
+ assistant instructions.
11
+
12
+ ## 2. Start the application and browser
13
+
14
+ Run the project's existing app command in its own process. Then:
15
+
16
+ ```bash
17
+ npx craftdriver daemon start
18
+ npx craftdriver go http://127.0.0.1:3000
19
+ npx craftdriver snapshot
20
+ ```
21
+
22
+ Use the actual project URL and wait for a clear page condition when necessary.
23
+ On Windows the daemon is unavailable; use the configured MCP server, or put
24
+ the complete exploration flow into one `--ephemeral` script.
25
+
26
+ ## 3. Inspect before acting
27
+
28
+ Read role/name, labels, test IDs, visible text, and nearby DOM structure from the
29
+ snapshot. Prefer a semantic selector and prove it:
30
+
31
+ ```bash
32
+ npx craftdriver exists 'role=button[name=Sign in]'
33
+ npx craftdriver find 'label=Email' --all
34
+ ```
35
+
36
+ Refs from the snapshot are safe to act on: a ref binds to one element, and if
37
+ that element is gone or the page navigated, the command fails with `STALE_REF`
38
+ instead of hitting something else. Take a fresh snapshot when that happens.
39
+
40
+ Never copy `ref=eN` into test code. When you have the right element, ask for a
41
+ durable selector and let CraftDriver validate it against the live page:
42
+
43
+ ```bash
44
+ npx craftdriver locators ref=e7
45
+ ```
46
+
47
+ Use a candidate it reports as `unique`. If none is unique, add a `data-testid`
48
+ to the application rather than committing a positional selector.
49
+
50
+ ## 4. Exercise the user flow
51
+
52
+ ```bash
53
+ npx craftdriver fill 'label=Email' alice@example.test
54
+ npx craftdriver fill 'label=Password' secret
55
+ npx craftdriver click 'role=button[name=Sign in]'
56
+ npx craftdriver text 'testid=welcome'
57
+ ```
58
+
59
+ After each meaningful mutation, inspect fresh output or snapshot state. If a
60
+ selector is ambiguous, refine it from observed structure and validate again.
61
+
62
+ **The DOM is not the whole story.** An application can fail while showing a
63
+ generic message, or succeed while logging an error you should not ship. Check
64
+ what the page actually did:
65
+
66
+ ```bash
67
+ npx craftdriver logs --kind error # exceptions + console.error
68
+ npx craftdriver logs --kind request,response --contains /api/
69
+ ```
70
+
71
+ Capture is already running before your first command, so this works after the
72
+ fact — you never re-run a flow to observe it. Assert on what you find, not on
73
+ what the page happens to render.
74
+
75
+ ## 5. Write the smallest durable test
76
+
77
+ Follow the repository's style. A direct test may look like:
78
+
79
+ ```ts
80
+ import { Browser, By, expect } from 'craftdriver';
81
+
82
+ const browser = await Browser.launch({ browserName: 'chrome' });
83
+ try {
84
+ await browser.navigateTo('http://127.0.0.1:3000');
85
+ await browser.locator(By.labelText('Email')).fill('alice@example.test');
86
+ await browser.locator(By.labelText('Password')).fill('secret');
87
+ await browser.locator(By.role('button', { name: 'Sign in' })).click();
88
+ await expect(browser.locator(By.testId('welcome'))).toHaveText(/welcome/i);
89
+ } finally {
90
+ await browser.quit();
91
+ }
92
+ ```
93
+
94
+ Use the exact public API patterns already present in the repository when they
95
+ differ from this standalone example.
96
+
97
+ ## 6. Run and debug
98
+
99
+ Run the narrowest existing test command. On failure:
100
+
101
+ 1. read the stable CraftDriver error code;
102
+ 2. reproduce against the current app;
103
+ 3. take a fresh snapshot and re-check the locator with `locators`;
104
+ 4. inspect only the relevant page value/text/attribute;
105
+ 5. **read the evidence before theorising** — see below;
106
+ 6. change application or test source through an explicit diff;
107
+ 7. rerun the focused test, then the repository's required checks.
108
+
109
+ ### Read the evidence
110
+
111
+ Most failures that look mysterious in the DOM are explicit in the console or
112
+ the network journal:
113
+
114
+ ```bash
115
+ npx craftdriver logs --kind error # exceptions and console.error
116
+ npx craftdriver logs --kind response --contains /api/ # status codes
117
+ ```
118
+
119
+ A page that renders "Something went wrong" tells you nothing; a 4xx on
120
+ `/api/checkout` and a matching `console.error` tell you exactly where to look.
121
+ `--kind error` covers both uncaught exceptions and `console.error`, since a
122
+ caller asking for errors means both.
123
+ Quote that evidence when you explain the failure, rather than guessing from the
124
+ rendered text.
125
+
126
+ For a failure that a snapshot cannot explain — a race, or something that only
127
+ happens mid-flow — record the whole run and inspect it afterwards:
128
+
129
+ ```bash
130
+ npx craftdriver trace start bug
131
+ # …reproduce…
132
+ npx craftdriver trace stop --zip
133
+ ```
134
+
135
+ To confirm a diagnosis, drive the branch directly instead of waiting for the
136
+ real backend to misbehave:
137
+
138
+ ```bash
139
+ npx craftdriver mock add '**/api/checkout*' --status 500
140
+ npx craftdriver mock clear # always clean up; mocks outlive the command
141
+ ```
142
+
143
+ ### Never heal a test at runtime
144
+
145
+ Do not make a failing test green through sleeps, broad catch blocks, weaker
146
+ assertions, or silent runtime selector substitution. If the application
147
+ changed, change the test in an explicit source diff and say what changed and
148
+ why. A test that hides a real application change is worse than a failing one,
149
+ because it removes the signal without fixing the cause.