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