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
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Isolated pngjs adapter — the single place that touches the CommonJS-shaped
3
+ * `pngjs` API. The rest of craftdriver is ESM and works only with the
4
+ * promise-based {@link PngCodec} and normalized {@link DecodedImage} defined in
5
+ * `./codec`; pngjs types never leak past this file.
6
+ *
7
+ * Decode and encode both use pngjs's asynchronous stream APIs (`parse` /
8
+ * `pack`) rather than the synchronous variants, so a large image cannot block
9
+ * the event loop inside a single call. CRC checking is left at pngjs's default
10
+ * (enabled) so corrupt chunks are rejected rather than silently decoded.
11
+ *
12
+ * Maintenance risk (recorded, not hidden): `pngjs@7.0.0` is pinned exactly. It
13
+ * had no reported npm/OSV advisory as of 2026-07-17, but that is a timestamped
14
+ * check, not proof of safety — its last release was 2023 and it publishes no
15
+ * security policy. It relies on Node's built-in `zlib`, so keeping Node patched
16
+ * is part of the posture. CI runs `npm audit --omit=dev`; this seam is kept
17
+ * narrow (see `./codec`) so the codec can be replaced without touching callers
18
+ * if the project goes unmaintained or an advisory lands.
19
+ */
20
+ import { PNG } from 'pngjs';
21
+ import { CraftdriverError, ErrorCode } from '../errors.js';
22
+ function wrapDecodeError(cause) {
23
+ return new CraftdriverError(ErrorCode.INVALID_ARGUMENT, 'Failed to decode PNG image.', {
24
+ cause,
25
+ hint: 'The buffer is not a valid PNG, or its chunks failed CRC validation.',
26
+ });
27
+ }
28
+ /**
29
+ * Decode a compressed PNG buffer to a fresh, tightly-packed 8-bit RGBA view.
30
+ *
31
+ * pngjs always normalizes palette/grayscale/16-bit/interlaced inputs to 8-bit
32
+ * RGBA in `png.data`, but that buffer may be a view into a larger pool. We copy
33
+ * into a right-sized `Uint8Array` so downstream code can rely on
34
+ * `data.length === width * height * 4` and on a predictable backing buffer.
35
+ */
36
+ function decode(buffer) {
37
+ return new Promise((resolve, reject) => {
38
+ const png = new PNG();
39
+ const input = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
40
+ png.parse(input, (err, decoded) => {
41
+ if (err || !decoded) {
42
+ reject(wrapDecodeError(err ?? new Error('pngjs produced no image')));
43
+ return;
44
+ }
45
+ const { width, height } = decoded;
46
+ const expected = width * height * 4;
47
+ const data = new Uint8Array(expected);
48
+ data.set(decoded.data.subarray(0, expected));
49
+ resolve({ width, height, data });
50
+ });
51
+ });
52
+ }
53
+ /** Encode normalized 8-bit RGBA back to a compressed PNG buffer. */
54
+ function encode(image) {
55
+ return new Promise((resolve, reject) => {
56
+ const png = new PNG({ width: image.width, height: image.height });
57
+ // pngjs owns a Buffer at png.data of the right length; fill it in place.
58
+ png.data = Buffer.from(image.data.buffer, image.data.byteOffset, image.data.byteLength);
59
+ const chunks = [];
60
+ png
61
+ .pack()
62
+ .on('data', (chunk) => chunks.push(chunk))
63
+ .on('end', () => resolve(Buffer.concat(chunks)))
64
+ .on('error', (err) => reject(new CraftdriverError(ErrorCode.DRIVER_ERROR, 'Failed to encode diff PNG image.', {
65
+ cause: err,
66
+ })));
67
+ });
68
+ }
69
+ export const pngjsCodec = { decode, encode };
70
+ //# sourceMappingURL=pngjsCodec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pngjsCodec.js","sourceRoot":"","sources":["../../../src/lib/visual/pngjsCodec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG3D,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,IAAI,gBAAgB,CAAC,SAAS,CAAC,gBAAgB,EAAE,6BAA6B,EAAE;QACrF,KAAK;QACL,IAAI,EAAE,qEAAqE;KAC5E,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,MAAM,CAAC,MAAkB;IAChC,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;YAChC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,CAAC,eAAe,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC;gBACrE,OAAO;YACT,CAAC;YACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;YAClC,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;YACpC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC7C,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oEAAoE;AACpE,SAAS,MAAM,CAAC,KAAmB;IACjC,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAClE,yEAAyE;QACzE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxF,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,GAAG;aACA,IAAI,EAAE;aACN,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACjD,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;aAC/C,EAAE,CAAC,OAAO,EAAE,CAAC,GAAY,EAAE,EAAE,CAC5B,MAAM,CACJ,IAAI,gBAAgB,CAAC,SAAS,CAAC,YAAY,EAAE,kCAAkC,EAAE;YAC/E,KAAK,EAAE,GAAG;SACX,CAAC,CACH,CACF,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAa,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Public and internal types for visual screenshot assertions.
3
+ *
4
+ * The `(actual, expected)` argument order is used consistently across every
5
+ * public and internal helper (`compareScreenshots`, `compareDecoded`,
6
+ * `createDiff`). Both buffers share a type, so reversing the order would be a
7
+ * silent bug — never deviate from it.
8
+ */
9
+ import type { By } from '../by.js';
10
+ /**
11
+ * Default upper bound on decoded image size, in pixels, enforced before RGBA
12
+ * allocation. RGBA is ≥4 bytes/pixel, so expected + actual + final raw diff is
13
+ * ~240 MB of raw arrays at this limit before compressed buffers and pngjs
14
+ * working memory — and parallel assertions multiply that. Raise it explicitly
15
+ * for unusually long full-page screenshots; lower it when many workers run
16
+ * concurrently. A 100M default would permit ≥1.2 GB and is not a real guard.
17
+ */
18
+ export declare const DEFAULT_MAX_IMAGE_PIXELS = 20000000;
19
+ /** Default upper bound on a compressed PNG input buffer, in bytes (50 MiB). */
20
+ export declare const DEFAULT_MAX_INPUT_BYTES: number;
21
+ /**
22
+ * Mutually exclusive capture scope forwarded to `browser.screenshot()`.
23
+ * `fullPage` and `selector` cannot be combined (enforced by the union here and
24
+ * re-checked at runtime for JavaScript callers).
25
+ */
26
+ export type VisualScreenshotOptions = {
27
+ fullPage: true;
28
+ selector?: never;
29
+ } | {
30
+ selector: string | By;
31
+ fullPage?: false;
32
+ } | {
33
+ fullPage?: false;
34
+ selector?: never;
35
+ };
36
+ /** Policies applied to a single buffer-to-buffer comparison. */
37
+ export interface ScreenshotCompareOptions {
38
+ /** Inclusive maximum difference for each visible RGB channel. Integer 0..255. Default: 0. */
39
+ pixelTolerance?: number;
40
+ /** Maximum counted different pixels. Non-negative integer. */
41
+ maxDiffPixels?: number;
42
+ /** Maximum counted different pixels as a percentage of all pixels. 0..100. */
43
+ maxDiffPercentage?: number;
44
+ /** Exclude pixels classified as anti-aliasing from diff totals. Default: false. */
45
+ ignoreAntialiasing?: boolean;
46
+ /** Reject images above this decoded size before allocation. Default: 20 million pixels. */
47
+ maxImagePixels?: number;
48
+ /** Reject compressed PNG input above this size. Default: 50 MiB. */
49
+ maxInputBytes?: number;
50
+ }
51
+ /** Options for the retrying `browser.expectScreenshot()` assertion. */
52
+ export interface ExpectScreenshotOptions extends ScreenshotCompareOptions {
53
+ /** Overall retry time in ms. Defaults to the browser's default timeout (5000 ms). */
54
+ timeout?: number;
55
+ /** Delay between failed attempts in ms. Integer >= 10. Default: 50. */
56
+ interval?: number;
57
+ /** Mutually exclusive capture scope passed to `browser.screenshot()`. */
58
+ screenshot?: VisualScreenshotOptions;
59
+ }
60
+ /** Result of one buffer-to-buffer comparison. */
61
+ export interface VisualComparisonResult {
62
+ matches: boolean;
63
+ diffPixels: number;
64
+ /** Raw, unrounded value in 0..100. */
65
+ diffPercentage: number;
66
+ ignoredAntialiasPixels: number;
67
+ expectedWidth: number;
68
+ expectedHeight: number;
69
+ actualWidth: number;
70
+ actualHeight: number;
71
+ dimensionMismatch: boolean;
72
+ }
73
+ /** Result of a successful `browser.expectScreenshot()` assertion. */
74
+ export interface ScreenshotMatchResult extends VisualComparisonResult {
75
+ attempts: number;
76
+ elapsedMs: number;
77
+ /**
78
+ * How the on-disk baseline was resolved:
79
+ * - `'matched'` — an existing baseline was compared and passed.
80
+ * - `'created'` — no baseline existed; the captured screenshot was written as
81
+ * the new baseline (always on; the diff fields are zero).
82
+ * - `'updated'` — an existing baseline differed and was overwritten with the
83
+ * captured screenshot (only under `CRAFTDRIVER_UPDATE_VISUAL_BASELINES=true`;
84
+ * the diff fields describe state relative to the new baseline, i.e. zero —
85
+ * the magnitude of the accepted change is reported to stderr).
86
+ */
87
+ baseline: 'matched' | 'created' | 'updated';
88
+ }
89
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,WAAa,CAAC;AAEnD,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,QAAmB,CAAC;AAExD;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAC/B;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,GAC3C;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAE3C,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,yEAAyE;IACzE,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAED,iDAAiD;AACjD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAsB,SAAQ,sBAAsB;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;;;;OASG;IACH,QAAQ,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CAC7C"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Public and internal types for visual screenshot assertions.
3
+ *
4
+ * The `(actual, expected)` argument order is used consistently across every
5
+ * public and internal helper (`compareScreenshots`, `compareDecoded`,
6
+ * `createDiff`). Both buffers share a type, so reversing the order would be a
7
+ * silent bug — never deviate from it.
8
+ */
9
+ /**
10
+ * Default upper bound on decoded image size, in pixels, enforced before RGBA
11
+ * allocation. RGBA is ≥4 bytes/pixel, so expected + actual + final raw diff is
12
+ * ~240 MB of raw arrays at this limit before compressed buffers and pngjs
13
+ * working memory — and parallel assertions multiply that. Raise it explicitly
14
+ * for unusually long full-page screenshots; lower it when many workers run
15
+ * concurrently. A 100M default would permit ≥1.2 GB and is not a real guard.
16
+ */
17
+ export const DEFAULT_MAX_IMAGE_PIXELS = 20_000_000;
18
+ /** Default upper bound on a compressed PNG input buffer, in bytes (50 MiB). */
19
+ export const DEFAULT_MAX_INPUT_BYTES = 50 * 1024 * 1024;
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/visual/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,UAAU,CAAC;AAEnD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Typed failure for a visual assertion.
3
+ *
4
+ * JavaScript cannot both throw and return a normal value, so the final actual
5
+ * screenshot and generated diff PNG travel on the error as `Buffer`
6
+ * properties, while a JSON-serializable summary lives in `detail` for agents.
7
+ */
8
+ import { CraftdriverError } from '../errors.js';
9
+ import type { VisualComparisonResult } from './types.js';
10
+ export interface VisualMismatchInfo {
11
+ expectedPath: string;
12
+ timeout: number;
13
+ attempts: number;
14
+ elapsedMs: number;
15
+ comparison: VisualComparisonResult;
16
+ }
17
+ /**
18
+ * Thrown by `browser.expectScreenshot()` when no screenshot matched the
19
+ * baseline before the timeout. Carries the final actual and diff PNG buffers
20
+ * plus the comparison summary; `code` is `VISUAL_MISMATCH`.
21
+ */
22
+ export declare class VisualMismatchError extends CraftdriverError {
23
+ /** Final browser screenshot (compressed PNG). */
24
+ readonly actual: Buffer;
25
+ /** Final generated diff (compressed PNG). */
26
+ readonly diff: Buffer;
27
+ readonly comparison: VisualComparisonResult;
28
+ readonly attempts: number;
29
+ readonly elapsedMs: number;
30
+ constructor(actual: Buffer, diff: Buffer, info: VisualMismatchInfo);
31
+ }
32
+ //# sourceMappingURL=visualError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visualError.d.ts","sourceRoot":"","sources":["../../../src/lib/visual/visualError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAa,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,sBAAsB,CAAC;CACpC;AAgBD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,gBAAgB;IACvD,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;CAyBnE"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Typed failure for a visual assertion.
3
+ *
4
+ * JavaScript cannot both throw and return a normal value, so the final actual
5
+ * screenshot and generated diff PNG travel on the error as `Buffer`
6
+ * properties, while a JSON-serializable summary lives in `detail` for agents.
7
+ */
8
+ import { CraftdriverError, ErrorCode } from '../errors.js';
9
+ function buildMessage(info) {
10
+ const { comparison, timeout, attempts } = info;
11
+ if (comparison.dimensionMismatch) {
12
+ return (`Screenshot did not match after ${timeout} ms: dimensions differ ` +
13
+ `(expected ${comparison.expectedWidth}x${comparison.expectedHeight}, ` +
14
+ `actual ${comparison.actualWidth}x${comparison.actualHeight}) across ${attempts} attempts`);
15
+ }
16
+ const pixels = comparison.diffPixels.toLocaleString('en-US');
17
+ const pct = comparison.diffPercentage.toFixed(4);
18
+ return `Screenshot did not match after ${timeout} ms: ${pixels} pixels differed (${pct}%) across ${attempts} attempts`;
19
+ }
20
+ /**
21
+ * Thrown by `browser.expectScreenshot()` when no screenshot matched the
22
+ * baseline before the timeout. Carries the final actual and diff PNG buffers
23
+ * plus the comparison summary; `code` is `VISUAL_MISMATCH`.
24
+ */
25
+ export class VisualMismatchError extends CraftdriverError {
26
+ /** Final browser screenshot (compressed PNG). */
27
+ actual;
28
+ /** Final generated diff (compressed PNG). */
29
+ diff;
30
+ comparison;
31
+ attempts;
32
+ elapsedMs;
33
+ constructor(actual, diff, info) {
34
+ super(ErrorCode.VISUAL_MISMATCH, buildMessage(info), {
35
+ detail: {
36
+ expectedPath: info.expectedPath,
37
+ timeout: info.timeout,
38
+ attempts: info.attempts,
39
+ elapsedMs: info.elapsedMs,
40
+ diffPixels: info.comparison.diffPixels,
41
+ diffPercentage: info.comparison.diffPercentage,
42
+ ignoredAntialiasPixels: info.comparison.ignoredAntialiasPixels,
43
+ expectedWidth: info.comparison.expectedWidth,
44
+ expectedHeight: info.comparison.expectedHeight,
45
+ actualWidth: info.comparison.actualWidth,
46
+ actualHeight: info.comparison.actualHeight,
47
+ dimensionMismatch: info.comparison.dimensionMismatch,
48
+ },
49
+ hint: 'Inspect error.actual and error.diff (PNG buffers) to see the regression. If the change is intentional, re-run with CRAFTDRIVER_UPDATE_VISUAL_BASELINES=true to overwrite the baseline.',
50
+ });
51
+ this.name = 'VisualMismatchError';
52
+ this.actual = actual;
53
+ this.diff = diff;
54
+ this.comparison = info.comparison;
55
+ this.attempts = info.attempts;
56
+ this.elapsedMs = info.elapsedMs;
57
+ }
58
+ }
59
+ //# sourceMappingURL=visualError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visualError.js","sourceRoot":"","sources":["../../../src/lib/visual/visualError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAW3D,SAAS,YAAY,CAAC,IAAwB;IAC5C,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAC/C,IAAI,UAAU,CAAC,iBAAiB,EAAE,CAAC;QACjC,OAAO,CACL,kCAAkC,OAAO,yBAAyB;YAClE,aAAa,UAAU,CAAC,aAAa,IAAI,UAAU,CAAC,cAAc,IAAI;YACtE,UAAU,UAAU,CAAC,WAAW,IAAI,UAAU,CAAC,YAAY,YAAY,QAAQ,WAAW,CAC3F,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,kCAAkC,OAAO,QAAQ,MAAM,qBAAqB,GAAG,aAAa,QAAQ,WAAW,CAAC;AACzH,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,gBAAgB;IACvD,iDAAiD;IACxC,MAAM,CAAS;IACxB,6CAA6C;IACpC,IAAI,CAAS;IACb,UAAU,CAAyB;IACnC,QAAQ,CAAS;IACjB,SAAS,CAAS;IAE3B,YAAY,MAAc,EAAE,IAAY,EAAE,IAAwB;QAChE,KAAK,CAAC,SAAS,CAAC,eAAe,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE;YACnD,MAAM,EAAE;gBACN,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU;gBACtC,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc;gBAC9C,sBAAsB,EAAE,IAAI,CAAC,UAAU,CAAC,sBAAsB;gBAC9D,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa;gBAC5C,cAAc,EAAE,IAAI,CAAC,UAAU,CAAC,cAAc;gBAC9C,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW;gBACxC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,YAAY;gBAC1C,iBAAiB,EAAE,IAAI,CAAC,UAAU,CAAC,iBAAiB;aACrD;YACD,IAAI,EAAE,wLAAwL;SAC/L,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAClC,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD,qBAAa,aAAa;IAMtB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;gBAGlB,MAAM,EAAE,MAAM,EACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,WAAW,EACvC,UAAU,CAAC,EAAE,MAAM;IAYf,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CA8BtE;AAOD,eAAO,MAAM,KAAK;8BACU,EAAE,MAAY,QAAQ,MAAM;6BAI7B,EAAE,MAAY,QAAQ,MAAM;gCASzB,EAAE,MAAY,QAAQ,MAAM;+BAS7B,EAAE,GAAG,UAAU,MAAY,QAAQ,MAAM;kCAUtC,EAAE,GAAG,UAAU,MAAY,QAAQ,MAAM;CAaxE,CAAC"}
1
+ {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD,qBAAa,aAAa;IAMtB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;gBAGlB,MAAM,EAAE,MAAM,EACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,WAAW,EACvC,UAAU,CAAC,EAAE,MAAM;IAYf,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CAmCtE;AAOD,eAAO,MAAM,KAAK;8BACU,EAAE,MAAY,QAAQ,MAAM;6BAI7B,EAAE,MAAY,QAAQ,MAAM;gCASzB,EAAE,MAAY,QAAQ,MAAM;+BAS7B,EAAE,GAAG,UAAU,MAAY,QAAQ,MAAM;kCAUtC,EAAE,GAAG,UAAU,MAAY,QAAQ,MAAM;CAaxE,CAAC"}
package/dist/lib/wait.js CHANGED
@@ -27,7 +27,11 @@ export class WebDriverWait {
27
27
  async until(condition, message) {
28
28
  const deadline = Date.now() + this.timeoutMs;
29
29
  let lastErr;
30
- while (Date.now() < deadline) {
30
+ // Check-first: the condition is always evaluated at least once, even with a
31
+ // zero (or already-elapsed) timeout. A `while (Date.now() < deadline)` loop
32
+ // would run zero iterations at timeout 0 and report an already-satisfiable
33
+ // condition as a spurious timeout.
34
+ for (;;) {
31
35
  try {
32
36
  const result = await condition(this.driver);
33
37
  // Resolve on truthy or non-null/undefined result
@@ -38,6 +42,8 @@ export class WebDriverWait {
38
42
  catch (e) {
39
43
  lastErr = e;
40
44
  }
45
+ if (Date.now() >= deadline)
46
+ break;
41
47
  await new Promise((r) => setTimeout(r, this.intervalMs));
42
48
  }
43
49
  const errMsg = message ?? this.timeoutMsg ?? `Wait timed out after ${this.timeoutMs}ms`;
@@ -1 +1 @@
1
- {"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wait.js","sourceRoot":"","sources":["../../src/lib/wait.ts"],"names":[],"mappings":"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"}
package/docs/agents.md CHANGED
@@ -1,24 +1,37 @@
1
1
  # AI Agent Guide
2
2
 
3
- CraftDriver ships agent-facing tooling in the npm package. You can use the library directly, drive the browser from the shell, expose tools through MCP, or drop assistant rules into a project.
3
+ CraftDriver's recommended agent surface is the CLI plus a project-local skill.
4
+ The agent explores the running application, learns its DOM and accessibility
5
+ structure, validates durable selectors, and then writes and runs ordinary
6
+ CraftDriver tests. MCP is optional.
4
7
 
5
- ## Which Surface Should I Use?
8
+ ## Install safely
6
9
 
7
- | Agent environment | Best surface | Why |
8
- | -------------------------- | ------------------ | -------------------------------------------------------------------------------- |
9
- | Shell-capable coding agent | CLI | Simple commands, persistent daemon, JSON output when piped. |
10
- | MCP-aware host | MCP server | Typed tools, compact accessibility snapshots, artifact spilling for screenshots. |
11
- | Code-writing assistant | `craftdriver init` | Project rules teach selector preference, auto-waiting, and error-code handling. |
12
- | Custom orchestrator | Skill pack | Small always-on `SKILL.md` with deeper references loaded on demand. |
10
+ From a project that already has a `package.json`:
13
11
 
14
- ## CLI
12
+ ```bash
13
+ npm install --save-dev craftdriver
14
+ npx craftdriver init codex
15
+ ```
16
+
17
+ This installs `.agents/skills/craftdriver/` with an ownership manifest. It does
18
+ not read or change `AGENTS.md`, `CLAUDE.md`, Copilot instructions, Cursor rules,
19
+ `GEMINI.md`, or `.codex/config.toml`. It refuses user-edited, unowned, extra, or
20
+ symlinked destination content instead of overwriting it.
15
21
 
16
- The CLI is good for agents that can run shell commands.
22
+ Preview without writes:
23
+
24
+ ```bash
25
+ npx craftdriver init codex --dry-run
26
+ ```
27
+
28
+ ## Explore, then write the test
17
29
 
18
30
  ```bash
19
31
  npx craftdriver daemon start
20
32
  npx craftdriver go http://127.0.0.1:8080/login.html
21
33
  npx craftdriver snapshot
34
+ npx craftdriver find 'label=Username' --all
22
35
  npx craftdriver fill 'label=Username' alice
23
36
  npx craftdriver fill 'label=Password' hunter2
24
37
  npx craftdriver click 'role=button[name=Sign in]'
@@ -26,67 +39,49 @@ npx craftdriver text '#result'
26
39
  npx craftdriver daemon stop
27
40
  ```
28
41
 
29
- State survives between calls while the daemon is running, so an agent can explore, act, inspect, and refine.
30
-
31
- Read the full [CLI reference](./cli.md).
32
-
33
- ## MCP Server
42
+ The daemon keeps the page and cookies between commands. Snapshot refs are
43
+ ephemeral exploration state in the current release: take a fresh snapshot
44
+ immediately before one ref action, and never copy `ref=eN` into a test. Validate
45
+ role/name, label, test ID, text, or stable CSS selectors against the live page,
46
+ then use those durable locators in test source.
34
47
 
35
- MCP-aware hosts can use `craftdriver mcp`.
48
+ The daemon uses a Unix socket and is not available on Windows. Windows agents
49
+ should use the optional MCP server for a persistent session, or run the whole
50
+ flow as one `craftdriver --ephemeral` script.
36
51
 
37
- ```bash
38
- claude mcp add craftdriver -- npx -y craftdriver mcp
39
- ```
40
-
41
- ```jsonc
42
- {
43
- "mcpServers": {
44
- "craftdriver": {
45
- "command": "npx",
46
- "args": ["-y", "craftdriver", "mcp"],
47
- },
48
- },
49
- }
50
- ```
51
-
52
- Mutating tools return compact accessibility snapshot diffs, so the agent can see what changed without a separate read. Large artifacts such as screenshots are written to disk instead of inlined into model context.
52
+ The installed `workflow.md` guides the full loop: inspect existing project test
53
+ conventions, explore, validate selectors, write the smallest test, run the
54
+ focused command, and debug from fresh browser evidence.
53
55
 
54
- Read the full [MCP reference](./mcp.md).
56
+ Read the [CLI reference](./cli.md) for all current commands.
55
57
 
56
- ## Assistant Bootstrap Files
58
+ ## Optional MCP
57
59
 
58
- Use `craftdriver init` inside a project that uses CraftDriver:
60
+ CLI + Skill does not require MCP. For a host that prefers structured tool
61
+ calls, print the local project-pinned configuration:
59
62
 
60
63
  ```bash
61
- npx craftdriver init agents
62
- npx craftdriver init copilot
63
- npx craftdriver init claude
64
- npx craftdriver init cursor
65
- npx craftdriver init gemini
66
- npx craftdriver init all
64
+ npx craftdriver init codex --mcp
67
65
  ```
68
66
 
69
- The generated files tell assistants to:
67
+ The command prints:
70
68
 
71
- - prefer semantic selectors before brittle CSS
72
- - rely on auto-waiting instead of sleeps
73
- - inspect stable `CraftdriverError.code` values
74
- - use the CLI or MCP server for live browser work
75
- - keep test code aligned with the public TypeScript API
76
-
77
- ## Skill Pack
78
-
79
- The npm tarball includes a tiered skill pack under `skills/craftdriver/`:
69
+ ```toml
70
+ [mcp_servers.craftdriver]
71
+ command = "npx"
72
+ args = ["--no-install", "craftdriver", "mcp"]
73
+ ```
80
74
 
81
- | File | Purpose |
82
- | --------------- | -------------------------------------------------------------------------------------- |
83
- | `SKILL.md` | Always-on rules and routing, kept small for model context. |
84
- | `cheatsheet.md` | Fast command and API reference for tests. |
85
- | `patterns.md` | Worked recipes such as login, uploads, network waits, a11y, tracing, and virtual time. |
86
- | `cli.md` | Agent-facing CLI reference. |
75
+ Add it manually to the host configuration. CraftDriver never reads or writes
76
+ that configuration. See the [MCP reference](./mcp.md) for the current optional
77
+ tool surface and protocol bounds.
87
78
 
88
- Point compatible agents at:
79
+ ## Installed skill files
89
80
 
90
- ```text
91
- node_modules/craftdriver/skills/craftdriver/SKILL.md
92
- ```
81
+ | File | Purpose |
82
+ | --- | --- |
83
+ | `SKILL.md` | Short browser-to-test rules and routing. |
84
+ | `workflow.md` | Exploration, selector validation, test authoring, and debugging loop. |
85
+ | `cli.md` | Current shell command reference for agent exploration. |
86
+ | `cheatsheet.md` | Public TypeScript API reference for writing tests. |
87
+ | `patterns.md` | Focused library recipes loaded on demand. |
@@ -124,6 +124,20 @@ Launch packaged Electron applications, drive their renderer, and opt in to main-
124
124
  | `ElectronMainLogHandler` | type | — | [electron](electron.md) |
125
125
  | `ElectronMainLogLevel` | type | Normalized level for a main-process log entry. | [electron](electron.md) |
126
126
 
127
+ ## Visual Testing
128
+
129
+ Assert screenshots against baselines with pixel/percentage tolerances and optional anti-alias handling.
130
+
131
+ | Symbol | Kind | Summary | Docs |
132
+ |---|---|---|---|
133
+ | `compareScreenshots` | function | Compare two compressed PNG buffers without disk access, browser capture, retries, or throwing on a normal mismatch. Applies the same byte/pixel input limits as the retrying assertion. | [visual-testing](visual-testing.md) |
134
+ | `VisualMismatchError` | class | Thrown by `browser.expectScreenshot()` when no screenshot matched the baseline before the timeout. Carries the final actual and diff PNG buffers plus the comparison summary; `code` is `VISUAL_MISMATCH`. | [visual-testing](visual-testing.md) |
135
+ | `ScreenshotCompareOptions` | type | Policies applied to a single buffer-to-buffer comparison. | [visual-testing](visual-testing.md) |
136
+ | `ExpectScreenshotOptions` | type | Options for the retrying `browser.expectScreenshot()` assertion. | [visual-testing](visual-testing.md) |
137
+ | `VisualScreenshotOptions` | type | Mutually exclusive capture scope forwarded to `browser.screenshot()`. `fullPage` and `selector` cannot be combined (enforced by the union here and re-checked at runtime for JavaScript callers). | [visual-testing](visual-testing.md) |
138
+ | `VisualComparisonResult` | type | Result of one buffer-to-buffer comparison. | [visual-testing](visual-testing.md) |
139
+ | `ScreenshotMatchResult` | type | Result of a successful `browser.expectScreenshot()` assertion. | [visual-testing](visual-testing.md) |
140
+
127
141
  ## Accessibility
128
142
 
129
143
  Run axe-core accessibility checks and inspect violation details.
@@ -169,5 +183,6 @@ Exports that are public but not yet assigned to a feature group.
169
183
  | `PageMatcher` | type | Selects a top-level page by `url` and/or `title` for {@link Browser.waitForPage}. A string matches as a substring; a `RegExp` is tested. When both fields are given, both must match. | [browser-api](browser-api.md) |
170
184
  | `RemoteAuth` | type | — | — |
171
185
  | `RemoteWebDriverOptions` | type | Options for connecting to any W3C-compatible remote WebDriver endpoint — a self-hosted Selenium Grid, BrowserStack, or another cloud provider. Provider-specific detail (BrowserStack's `bstack:options`, etc.) is forwarded through `capabilities` without schema conversion. | — |
186
+ | `SessionStateCookie` | type | — | — |
172
187
 
173
- Total exports: **86**.
188
+ Total exports: **94**.
@@ -29,7 +29,7 @@ const browser = await Browser.launch({
29
29
  |--------|------|---------|-------------|
30
30
  | `browserName` | `'chrome' \| 'chromium' \| 'firefox' \| 'safari'`; any non-empty `string` remotely | `'chrome'` | Local browser to launch or provider-facing remote browser name |
31
31
  | `enableBiDi` | `boolean` | browsers: `true`; Electron: `false` | Use WebDriver BiDi. Electron requires an explicit opt-in because negotiation resets its initial renderer to `about:blank`. |
32
- | `storageState` | `string` | — | Path to a session-state JSON file to load on startup |
32
+ | `storageState` | `string \| SessionState` | — | Native session-state path/object. Full cookies + multi-origin localStorage on supported BiDi; non-empty state is rejected at Classic launch. |
33
33
  | `mobileEmulation` | `MobileEmulation \| DeviceName` | — | Mobile device emulation settings (Chrome/Chromium only) |
34
34
  | `downloadsDir` | `string` | temp dir | Directory where downloaded files are saved |
35
35
  | `browserPath` | `string` | — | Custom browser binary to launch (Chrome/Chromium/Firefox) — see [Driver Configuration → Browser Binary Configuration](./driver-configuration.md#browser-binary-configuration) |
@@ -334,9 +334,20 @@ await popup.waitForLoadState('load');
334
334
  const heading = await popup.find('h1').text();
335
335
  console.log(heading);
336
336
 
337
- await popup.evaluate(() => {
338
- window.close();
339
- });
337
+ // Close it. Prefer this over `popup.evaluate(() => window.close())`, which
338
+ // leaves no window focused — every later command then fails with
339
+ // "no such window". `close()` switches to a surviving page for you.
340
+ await popup.close();
341
+ ```
342
+
343
+ Use `page.activate()` to make a page the target of browser-level helpers.
344
+ Most page methods switch to their window and switch back, so they never
345
+ disturb `browser.activePage()`; `activate()` is the deliberate exception:
346
+
347
+ ```ts
348
+ const [, second] = await browser.pages();
349
+ await second.activate();
350
+ await browser.click('#confirm'); // acts on the second tab
340
351
  ```
341
352
 
342
353
  > **Note.** `browser.openPage()` requires BiDi (the default). In Classic
@@ -378,8 +389,17 @@ await browser.saveState('./session.json');
378
389
 
379
390
  // Load session from file
380
391
  await browser.loadState('./session.json');
392
+
393
+ // Or use an in-memory SessionState object
394
+ await browser.loadState(await browser.storage.getState());
381
395
  ```
382
396
 
397
+ On Chrome/Chromium and Firefox BiDi, launch and `browser.loadState()` restore
398
+ cookies plus multi-origin localStorage before the first real navigation. In
399
+ Classic mode, launch rejects non-empty state; navigate to the sole origin, call
400
+ `browser.loadState()`, then reload. See
401
+ [Session Management](./session-management.md#browser-and-transport-support).
402
+
383
403
  ## Evaluate
384
404
 
385
405
  Run JavaScript in the page and get the result back. Useful for reading app state,
@@ -120,9 +120,8 @@ Create a new isolated user context. Backed by BiDi
120
120
  `browser.createUserContext`. Throws in Classic mode.
121
121
 
122
122
  - `opts.storageState`: a `SessionState` object **or** a path to a JSON
123
- file produced by `BrowserContext.saveStorageState()`. Cookies are
124
- applied immediately; localStorage entries land on first navigation to
125
- each captured origin via an internal preload script.
123
+ file produced by `BrowserContext.saveStorageState()`. Cookies and every
124
+ captured localStorage origin are restored before `newContext()` resolves.
126
125
  - `opts.baseURL`: a base URL applied to every relative `url` passed to
127
126
  `ctx.newPage()` or `page.navigateTo()` inside the context. Absolute
128
127
  URLs pass through unchanged. Lets your tests say `'/login'` instead
@@ -230,11 +229,17 @@ snapshot for inspection. See the auth-fixture example above.
230
229
  ### `BrowserContext.loadStorageState(source): Promise<void>`
231
230
 
232
231
  Apply a previously-captured snapshot. `source` is a `SessionState`
233
- object or a path to JSON produced by `saveStorageState`. Cookies are
234
- applied immediately; localStorage entries are installed via a preload
235
- script that runs on first navigation to each captured origin. Calling
236
- `loadStorageState` again replaces the previous preload it does not
237
- stack.
232
+ object or a path to JSON produced by `saveStorageState`. localStorage is
233
+ seeded once per captured origin through a private, locally fulfilled document;
234
+ cookies are then applied to the same user-context partition. The method resolves
235
+ after hydration and cleanup, so the first real page script sees the state.
236
+ Calling `loadStorageState` again applies another ordered overlay; application
237
+ changes remain owned by the application and survive reload.
238
+
239
+ Restores in one context are serialized. Different user contexts may restore in
240
+ parallel. A runtime protocol failure on an existing context throws
241
+ `DRIVER_ERROR` with `detail.phase` and `detail.partialApplied`; use a newly
242
+ created context when failure isolation is required.
238
243
 
239
244
  ### `BrowserContext.close(): Promise<void>`
240
245
 
@@ -479,10 +484,11 @@ A default `browser.newContext()` (no options) is always cross-browser.
479
484
 
480
485
  A few things that aren't bugs but bite if you don't know them:
481
486
 
482
- - **`storageState` covers cookies + localStorage only.** Not
483
- sessionStorage, not IndexedDB, not Cache Storage. Apps that put their
484
- auth token in sessionStorage (some Auth0 / MSAL fallback configs) will
485
- need a custom restore step.
487
+ - **`storageState` covers cookies + localStorage only.** Not reusable
488
+ sessionStorage, IndexedDB, or Cache Storage. A non-empty sessionStorage
489
+ section is rejected by context/launch APIs rather than ignored. Use the
490
+ active-page `browser.loadState()` fallback for one matching sessionStorage
491
+ origin, or a custom login step.
486
492
  - **Browser-level vs context-level emulation race.** `browser.setGeolocation(...)`
487
493
  is a session-wide override; `ctx.setGeolocation(...)` is a per-context
488
494
  override. If both are set, the context-level one wins inside the