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,23 +1,96 @@
1
+ import { PAGE_SEMANTICS_JS } from './pageSemantics.js';
1
2
  const MAX_NODES = 80;
2
3
  const MAX_NAME = 80;
4
+ /**
5
+ * Upper bound on refs issued per document. Reaching it resets the registry
6
+ * under a fresh `documentId`, which invalidates every outstanding ref
7
+ * rather than letting numbers wrap onto live elements.
8
+ */
9
+ const MAX_REFS = 2000;
3
10
  /**
4
11
  * Build a fresh snapshot of the active page. Errors swallowed so a
5
12
  * partial post-action payload never breaks the protocol.
6
13
  */
7
- export async function takeSnapshot(browser) {
14
+ export async function takeSnapshot(browser, minRef = 0) {
8
15
  try {
9
16
  const page = await browser.activePage();
10
17
  const [url, title] = await Promise.all([
11
18
  page.url().catch(() => ''),
12
19
  page.title().catch(() => ''),
13
20
  ]);
14
- const lines = await page.evaluate(JS_SNAPSHOT);
15
- return { url, title, lines: Array.isArray(lines) ? lines : [] };
21
+ const raw = await page.evaluate(jsSnapshot(minRef));
22
+ if (!raw || typeof raw !== 'object')
23
+ return null;
24
+ const shaped = raw;
25
+ return {
26
+ url,
27
+ title,
28
+ lines: Array.isArray(shaped.lines) ? shaped.lines : [],
29
+ documentId: typeof shaped.documentId === 'string' ? shaped.documentId : '',
30
+ revision: 0,
31
+ refs: Array.isArray(shaped.refs) ? shaped.refs : [],
32
+ nextRef: typeof shaped.nextRef === 'number' ? shaped.nextRef : minRef + 1,
33
+ };
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Return the text of an open JavaScript dialog, or null if none is open.
41
+ *
42
+ * Cheap (~2ms) and, unlike almost every other command, safe to call while
43
+ * a modal dialog is blocking the page. That matters: a modal blocks script
44
+ * execution, so an unguarded post-action snapshot sits there until the
45
+ * WebDriver script timeout — a measured 60 seconds — before failing.
46
+ */
47
+ export async function peekDialog(browser) {
48
+ try {
49
+ return await browser.getDialogMessage();
16
50
  }
17
51
  catch {
52
+ // The driver reports "no such alert" by throwing; that is the common case.
18
53
  return null;
19
54
  }
20
55
  }
56
+ /**
57
+ * Ask the page whether `ref` still identifies exactly one live element.
58
+ *
59
+ * `expectedDocumentId` is the document the ref was issued against; a
60
+ * mismatch means the page navigated and every outstanding ref is stale.
61
+ */
62
+ export async function probeRef(browser, ref, expectedDocumentId) {
63
+ if (!/^e\d+$/.test(ref))
64
+ return { status: 'unknown-ref', documentId: null };
65
+ try {
66
+ const page = await browser.activePage();
67
+ const js = jsProbeRef(ref, expectedDocumentId);
68
+ const raw = await page.evaluate(js);
69
+ if (!raw || typeof raw !== 'object')
70
+ return { status: 'no-registry', documentId: null };
71
+ const shaped = raw;
72
+ return {
73
+ status: (typeof shaped.status === 'string' ? shaped.status : 'no-registry'),
74
+ documentId: typeof shaped.documentId === 'string' ? shaped.documentId : null,
75
+ ...(typeof shaped.count === 'number' ? { count: shaped.count } : {}),
76
+ };
77
+ }
78
+ catch {
79
+ return { status: 'no-registry', documentId: null };
80
+ }
81
+ }
82
+ /** Human-readable reason attached to a STALE_REF failure. */
83
+ export function describeRefStatus(status) {
84
+ switch (status) {
85
+ case 'no-snapshot': return 'no snapshot has been taken in this session yet';
86
+ case 'no-registry': return 'the page has no ref registry (it navigated or was replaced)';
87
+ case 'document-changed': return 'the page navigated or reloaded after the ref was issued';
88
+ case 'unknown-ref': return 'the ref was never issued for the current document';
89
+ case 'detached': return 'the element was removed from the document';
90
+ case 'ambiguous': return 'the ref no longer identifies exactly one element';
91
+ default: return 'the ref is no longer valid';
92
+ }
93
+ }
21
94
  /**
22
95
  * Render a snapshot as a compact, agent-friendly string. Either the
23
96
  * full snapshot (first call in a session) or a `+/-` diff vs. the
@@ -26,10 +99,8 @@ export async function takeSnapshot(browser) {
26
99
  * Diff format intentionally mirrors `diff -u` lines so agents
27
100
  * already familiar with unified diffs read it without instruction.
28
101
  *
29
- * Ref numbers (`e1`, `e2`, …) re-allocate on every snapshot so they
30
- * can't be used to drive the diff directly we strip them before
31
- * the set-difference and re-attach them when rendering, so the diff
32
- * stays semantically stable across renumberings.
102
+ * Refs are stripped before the set-difference and re-attached when
103
+ * rendering, so a diff reports semantic change rather than renumbering.
33
104
  */
34
105
  export function renderDelta(prev, next) {
35
106
  if (!next)
@@ -41,15 +112,41 @@ export function renderDelta(prev, next) {
41
112
  return `${header}\n(no interactive elements detected)`;
42
113
  return `${header}\n${next.lines.join('\n')}`;
43
114
  }
44
- if (prev.url !== next.url) {
45
- // Page changed full snapshot.
115
+ // A new document means the old lines describe a page that no longer
116
+ // exists; a diff against them would be noise. Same-URL reloads count.
117
+ if (prev.documentId !== next.documentId || prev.url !== next.url) {
46
118
  return `${header}\n${next.lines.join('\n')}`;
47
119
  }
48
120
  const strip = (l) => l.replace(/^e\d+:\s*/, '');
49
- const prevBodies = new Set(prev.lines.map(strip));
50
- const nextBodies = new Set(next.lines.map(strip));
51
- const removed = prev.lines.filter((l) => !nextBodies.has(strip(l))).map((l) => `- ${l}`);
52
- const added = next.lines.filter((l) => !prevBodies.has(strip(l))).map((l) => `+ ${l}`);
121
+ // Count occurrences rather than testing membership: a page with three
122
+ // identical "Remove" buttons must still report a change when one of them
123
+ // goes, and a set-difference silently calls that "no changes".
124
+ const tally = (lines) => {
125
+ const counts = new Map();
126
+ for (const line of lines) {
127
+ const body = strip(line);
128
+ counts.set(body, (counts.get(body) ?? 0) + 1);
129
+ }
130
+ return counts;
131
+ };
132
+ const prevCounts = tally(prev.lines);
133
+ const nextCounts = tally(next.lines);
134
+ const take = (lines, budget) => {
135
+ const out = [];
136
+ const seen = new Map();
137
+ for (const line of lines) {
138
+ const body = strip(line);
139
+ const used = seen.get(body) ?? 0;
140
+ // Report the surplus copies only — the first N matching lines are
141
+ // accounted for by the other side.
142
+ if (used >= (budget.get(body) ?? 0))
143
+ out.push(line);
144
+ seen.set(body, used + 1);
145
+ }
146
+ return out;
147
+ };
148
+ const removed = take(prev.lines, nextCounts).map((l) => `- ${l}`);
149
+ const added = take(next.lines, prevCounts).map((l) => `+ ${l}`);
53
150
  if (removed.length === 0 && added.length === 0) {
54
151
  return `${header}\n(no a11y changes)`;
55
152
  }
@@ -69,20 +166,139 @@ export function renderFull(next) {
69
166
  }
70
167
  return `${header}\n${next.lines.join('\n')}`;
71
168
  }
169
+ /**
170
+ * Owns the one snapshot baseline and ref-issuing document for a session.
171
+ *
172
+ * Both explicit `snapshot` and the automatic post-action snapshot record
173
+ * through here, so an explicit snapshot the agent has already been shown
174
+ * is never re-reported as a change caused by the next action.
175
+ */
176
+ export class SnapshotTracker {
177
+ baseline = null;
178
+ revision = 0;
179
+ highWater = 0;
180
+ /** The document outstanding refs were issued against, if any. */
181
+ get documentId() {
182
+ return this.baseline ? this.baseline.documentId : null;
183
+ }
184
+ get current() {
185
+ return this.baseline;
186
+ }
187
+ /**
188
+ * Highest ref number issued anywhere in this session.
189
+ *
190
+ * Seeding each new document above this is what stops a reload from
191
+ * reissuing `e2` to a different element: a ref the agent still holds
192
+ * would otherwise silently resolve against the new page.
193
+ */
194
+ get minRef() {
195
+ return this.highWater;
196
+ }
197
+ /** Stamp a session-monotonic revision and make this the new baseline. */
198
+ record(snap) {
199
+ if (!snap)
200
+ return null;
201
+ const stamped = { ...snap, revision: ++this.revision };
202
+ this.baseline = stamped;
203
+ this.highWater = Math.max(this.highWater, stamped.nextRef - 1);
204
+ return stamped;
205
+ }
206
+ /** Diff against the baseline and advance it in one step. */
207
+ advance(snap) {
208
+ if (!snap)
209
+ return '';
210
+ const delta = renderDelta(this.baseline, { ...snap, revision: this.revision + 1 });
211
+ this.record(snap);
212
+ return delta;
213
+ }
214
+ reset() {
215
+ this.baseline = null;
216
+ }
217
+ }
218
+ /**
219
+ * Ref-validation probe. `ref` is `^e\d+$`-validated before it gets here and
220
+ * both values are JSON-encoded into the source, so neither can break out.
221
+ */
222
+ function jsProbeRef(ref, expectedDocumentId) {
223
+ return `
224
+ const reg = window.__craftdriverRefs;
225
+ const ref = ${JSON.stringify(ref)};
226
+ const expected = ${JSON.stringify(expectedDocumentId)};
227
+ if (!reg || !reg.map) return { status: 'no-registry', documentId: null };
228
+ if (expected !== null && reg.doc !== expected) {
229
+ return { status: 'document-changed', documentId: reg.doc };
230
+ }
231
+ const el = reg.map.get(ref);
232
+ if (!el) return { status: 'unknown-ref', documentId: reg.doc };
233
+ if (!document.contains(el)) return { status: 'detached', documentId: reg.doc };
234
+ const marked = document.querySelectorAll('[data-craftdriver-ref="' + ref + '"]');
235
+ if (marked.length !== 1 || marked[0] !== el) {
236
+ return { status: 'ambiguous', documentId: reg.doc, count: marked.length };
237
+ }
238
+ return { status: 'ok', documentId: reg.doc };
239
+ `;
240
+ }
72
241
  /**
73
242
  * JS executed in-page. Keep it self-contained — no closure capture.
74
243
  *
75
244
  * Strategy:
76
- * 1. Walk a curated set of selectors (interactive + structural).
77
- * 2. Skip nodes outside the viewport-ish bounds and zero-sized nodes.
78
- * 3. For each, compute role + accessible name + a stable selector hint
79
- * (prefer #id, then [data-testid], then [name], then tag+text).
80
- * 4. Cap output at MAX_NODES so the snapshot stays bounded regardless
245
+ * 1. Get or create the per-document ref registry.
246
+ * 2. Walk a curated set of selectors (interactive + structural).
247
+ * 3. Skip nodes outside the viewport-ish bounds and zero-sized nodes.
248
+ * 4. Reuse each node's existing ref, or issue the next unused one.
249
+ * 5. Cap output at MAX_NODES so the snapshot stays bounded regardless
81
250
  * of page complexity.
82
251
  */
83
- const JS_SNAPSHOT = `
252
+ function jsSnapshot(minRef) {
253
+ const floor = Number.isSafeInteger(minRef) && minRef >= 0 ? minRef : 0;
254
+ return `
84
255
  const MAX_NODES = ${MAX_NODES};
85
256
  const MAX_NAME = ${MAX_NAME};
257
+ const MAX_REFS = ${MAX_REFS};
258
+ const MIN_REF = ${floor};
259
+ function newDocId(epoch) {
260
+ return 'd' + epoch + '-' + Math.random().toString(36).slice(2, 10);
261
+ }
262
+ function registry() {
263
+ let reg = window.__craftdriverRefs;
264
+ if (!reg || !reg.map) {
265
+ reg = { doc: newDocId(1), epoch: 1, next: 1, issued: 0, map: new Map() };
266
+ window.__craftdriverRefs = reg;
267
+ }
268
+ // Never reissue a number the session has already handed out. A fresh
269
+ // document starts its counter above the session high-water mark, so a
270
+ // ref captured before a navigation can never match a new element.
271
+ if (reg.next <= MIN_REF) reg.next = MIN_REF + 1;
272
+ // Bounded: at the cap, drop every marker and re-key the document so all
273
+ // outstanding refs fail as stale instead of wrapping onto live elements.
274
+ //
275
+ // The cap counts refs issued *by this registry*, not the counter value.
276
+ // Counting the counter would compare against the session-wide high-water
277
+ // mark, which passes the cap after enough navigations and would then
278
+ // re-key on every single snapshot — invalidating refs the instant they
279
+ // were handed out.
280
+ if (reg.issued >= MAX_REFS) {
281
+ const marked = document.querySelectorAll('[data-craftdriver-ref]');
282
+ for (let i = 0; i < marked.length; i++) marked[i].removeAttribute('data-craftdriver-ref');
283
+ reg.epoch += 1;
284
+ reg.doc = newDocId(reg.epoch);
285
+ reg.issued = 0;
286
+ reg.map = new Map();
287
+ }
288
+ return reg;
289
+ }
290
+ function refFor(reg, el) {
291
+ const existing = el.getAttribute('data-craftdriver-ref');
292
+ // Trust the attribute only when the registry agrees it names this node;
293
+ // that rejects authored markup and cloneNode duplicates.
294
+ if (existing && reg.map.get(existing) === el) return existing;
295
+ const ref = 'e' + reg.next;
296
+ reg.next += 1;
297
+ reg.issued += 1;
298
+ reg.map.set(ref, el);
299
+ el.setAttribute('data-craftdriver-ref', ref);
300
+ return ref;
301
+ }
86
302
  function visible(el) {
87
303
  if (!el || !el.getClientRects) return false;
88
304
  if (el.hidden) return false;
@@ -94,81 +310,7 @@ function visible(el) {
94
310
  if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') return false;
95
311
  return true;
96
312
  }
97
- function firstRoleToken(el) {
98
- const role = (el.getAttribute('role') || '').trim();
99
- return role ? role.split(/\\s+/)[0] : null;
100
- }
101
- function hasSectioningAncestor(el) {
102
- for (let p = el.parentElement; p; p = p.parentElement) {
103
- const t = p.tagName.toLowerCase();
104
- if (t === 'article' || t === 'aside' || t === 'main' || t === 'nav' || t === 'section') {
105
- return true;
106
- }
107
- const role = firstRoleToken(p);
108
- if (
109
- role === 'article' ||
110
- role === 'complementary' ||
111
- role === 'main' ||
112
- role === 'navigation' ||
113
- role === 'region'
114
- ) {
115
- return true;
116
- }
117
- }
118
- return false;
119
- }
120
- function impliedRole(el) {
121
- const t = el.tagName.toLowerCase();
122
- const explicit = firstRoleToken(el);
123
- if (explicit) return explicit;
124
- if (t === 'a' && el.hasAttribute('href')) return 'link';
125
- if (t === 'button') return 'button';
126
- if (t === 'input') {
127
- const it = (el.getAttribute('type') || 'text').toLowerCase();
128
- if (it === 'submit' || it === 'button' || it === 'reset') return 'button';
129
- if (it === 'checkbox') return 'checkbox';
130
- if (it === 'radio') return 'radio';
131
- return 'textbox';
132
- }
133
- if (t === 'textarea') return 'textbox';
134
- if (t === 'select') {
135
- const size = Number(el.getAttribute('size') || '0');
136
- return el.multiple || size > 1 ? 'listbox' : 'combobox';
137
- }
138
- if (/^h[1-6]$/.test(t)) return 'heading';
139
- if (t === 'nav') return 'navigation';
140
- if (t === 'main') return 'main';
141
- if (t === 'header') return hasSectioningAncestor(el) ? null : 'banner';
142
- if (t === 'footer') return hasSectioningAncestor(el) ? null : 'contentinfo';
143
- if (t === 'form') return 'form';
144
- if (t === 'img') return 'img';
145
- return t;
146
- }
147
- function accName(el) {
148
- const aria = el.getAttribute('aria-label');
149
- if (aria) return aria.trim();
150
- const labelledby = el.getAttribute('aria-labelledby');
151
- if (labelledby) {
152
- const ref = document.getElementById(labelledby);
153
- if (ref) return (ref.textContent || '').trim();
154
- }
155
- if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') {
156
- if (el.id) {
157
- const lbl = document.querySelector('label[for=' + JSON.stringify(el.id) + ']');
158
- if (lbl) return (lbl.textContent || '').trim();
159
- }
160
- const parentLabel = el.closest('label');
161
- if (parentLabel) return (parentLabel.textContent || '').trim();
162
- const placeholder = el.getAttribute('placeholder');
163
- if (placeholder) return placeholder.trim();
164
- }
165
- if (el.tagName === 'IMG') {
166
- const alt = el.getAttribute('alt');
167
- if (alt) return alt.trim();
168
- }
169
- const text = (el.textContent || '').trim().replace(/\\s+/g, ' ');
170
- return text;
171
- }
313
+ ${PAGE_SEMANTICS_JS}
172
314
  function locatorHint(el) {
173
315
  if (el.id) return '#' + el.id;
174
316
  const testid = el.getAttribute('data-testid') || el.getAttribute('data-test-id');
@@ -177,22 +319,25 @@ function locatorHint(el) {
177
319
  if (name) return el.tagName.toLowerCase() + '[name=' + JSON.stringify(name) + ']';
178
320
  return null;
179
321
  }
322
+ const reg = registry();
180
323
  const sel = 'a,button,input,select,textarea,h1,h2,h3,h4,h5,h6,[role],nav,main,header,footer,form,img,label';
181
- // Wipe stale refs from a previous snapshot so numbering is deterministic.
182
- const stale = document.querySelectorAll('[data-craftdriver-ref]');
183
- for (let i = 0; i < stale.length; i++) stale[i].removeAttribute('data-craftdriver-ref');
184
324
  const out = [];
325
+ const refs = [];
185
326
  const nodes = document.querySelectorAll(sel);
186
327
  for (let i = 0; i < nodes.length && out.length < MAX_NODES; i++) {
187
328
  const el = nodes[i];
188
329
  if (!visible(el)) continue;
189
- const role = impliedRole(el);
190
- if (!role) continue;
330
+ // A <header>/<footer> inside sectioning content is not a landmark, and
331
+ // listing it as a bare tag would be pure noise. Everything else keeps a
332
+ // line even without an ARIA role, so controls stay addressable by ref.
333
+ const tag = el.tagName.toLowerCase();
334
+ if (!ariaRole(el) && (tag === 'header' || tag === 'footer')) continue;
335
+ const role = displayRole(el);
191
336
  let name = accName(el);
192
337
  if (name.length > MAX_NAME) name = name.slice(0, MAX_NAME - 1) + '…';
193
338
  const hint = locatorHint(el);
194
- const ref = 'e' + (out.length + 1);
195
- el.setAttribute('data-craftdriver-ref', ref);
339
+ const ref = refFor(reg, el);
340
+ refs.push(ref);
196
341
  let line = ref + ': ' + role;
197
342
  if (name) line += ' "' + name + '"';
198
343
  if (hint) line += ' ' + hint;
@@ -201,6 +346,7 @@ for (let i = 0; i < nodes.length && out.length < MAX_NODES; i++) {
201
346
  if (el.checked) line += ' (checked)';
202
347
  out.push(line);
203
348
  }
204
- return out;
349
+ return { lines: out, refs: refs, documentId: reg.doc, nextRef: reg.next };
205
350
  `;
351
+ }
206
352
  //# sourceMappingURL=snapshot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/cli/snapshot.ts"],"names":[],"mappings":"AAiBA,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,QAAQ,GAAG,EAAE,CAAC;AASpB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IACjD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACrC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;SAC7B,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC/C,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CACzB,IAA0B,EAC1B,IAA0B;IAE1B,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,KAAK,IAAI,YAAY,MAAM,IAAI,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;IACjF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,+BAA+B;QAC/B,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,MAAM,sCAAsC,CAAC;QACpF,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAC1B,gCAAgC;QAChC,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,GAAG,MAAM,qBAAqB,CAAC;IACxC,CAAC;IACD,OAAO,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAA0B;IACnD,IAAI,CAAC,IAAI;QAAE,OAAO,yBAAyB,CAAC;IAC5C,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,KAAK,IAAI,YAAY,MAAM,IAAI,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;IACjF,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,MAAM,sCAAsC,CAAC;IACzD,CAAC;IACD,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,GAAG;oBACA,SAAS;mBACV,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwH1B,CAAC"}
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/cli/snapshot.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB;;;;GAIG;AACH,MAAM,QAAQ,GAAG,IAAI,CAAC;AAoCtB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAgB,EAChB,MAAM,GAAG,CAAC;IAEV,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACrC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;SAC7B,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QACpD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACjD,MAAM,MAAM,GAAG,GAEd,CAAC;QACF,OAAO;YACL,GAAG;YACH,KAAK;YACL,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,MAAM,CAAC,KAAkB,CAAC,CAAC,CAAC,EAAE;YACpE,UAAU,EAAE,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;YAC1E,QAAQ,EAAE,CAAC;YACX,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,MAAM,CAAC,IAAiB,CAAC,CAAC,CAAC,EAAE;YACjE,OAAO,EAAE,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC;SAC1E,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAgB;IAC/C,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAgB,EAChB,GAAW,EACX,kBAAiC;IAEjC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC5E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QACxF,MAAM,MAAM,GAAG,GAAkE,CAAC;QAClF,OAAO;YACL,MAAM,EAAE,CAAC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAc;YACxF,UAAU,EAAE,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;YAC5E,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACrD,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,aAAa,CAAC,CAAC,OAAO,gDAAgD,CAAC;QAC5E,KAAK,aAAa,CAAC,CAAC,OAAO,6DAA6D,CAAC;QACzF,KAAK,kBAAkB,CAAC,CAAC,OAAO,yDAAyD,CAAC;QAC1F,KAAK,aAAa,CAAC,CAAC,OAAO,mDAAmD,CAAC;QAC/E,KAAK,UAAU,CAAC,CAAC,OAAO,2CAA2C,CAAC;QACpE,KAAK,WAAW,CAAC,CAAC,OAAO,kDAAkD,CAAC;QAC5E,OAAO,CAAC,CAAC,OAAO,4BAA4B,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,IAA0B,EAC1B,IAA0B;IAE1B,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,KAAK,IAAI,YAAY,MAAM,IAAI,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;IACjF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,+BAA+B;QAC/B,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,MAAM,sCAAsC,CAAC;QACpF,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,oEAAoE;IACpE,sEAAsE;IACtE,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACjE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAChE,sEAAsE;IACtE,yEAAyE;IACzE,+DAA+D;IAC/D,MAAM,KAAK,GAAG,CAAC,KAAe,EAAuB,EAAE;QACrD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,CAAC,KAAe,EAAE,MAA2B,EAAY,EAAE;QACtE,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,kEAAkE;YAClE,mCAAmC;YACnC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,GAAG,MAAM,qBAAqB,CAAC;IACxC,CAAC;IACD,OAAO,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAA0B;IACnD,IAAI,CAAC,IAAI;QAAE,OAAO,yBAAyB,CAAC;IAC5C,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,KAAK,IAAI,YAAY,MAAM,IAAI,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;IACjF,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,MAAM,sCAAsC,CAAC;IACzD,CAAC;IACD,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,eAAe;IAClB,QAAQ,GAAyB,IAAI,CAAC;IACtC,QAAQ,GAAG,CAAC,CAAC;IACb,SAAS,GAAG,CAAC,CAAC;IAEtB,iEAAiE;IACjE,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;;;;;OAMG;IACH,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,yEAAyE;IACzE,MAAM,CAAC,IAA0B;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,MAAM,OAAO,GAAkB,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAC/D,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,4DAA4D;IAC5D,OAAO,CAAC,IAA0B;QAChC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK;QACH,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACvB,CAAC;CACF;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,GAAW,EAAE,kBAAiC;IAChE,OAAO;;cAEK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;mBACd,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC;;;;;;;;;;;;;CAapD,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,UAAU,CAAC,MAAc;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO;oBACW,SAAS;mBACV,QAAQ;mBACR,QAAQ;kBACT,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuDrB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqClB,CAAC;AACF,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { SessionState } from '../lib/bidi/types.js';
2
+ import { prepareTempPath, commitFile, discardTempFile } from './artifactPaths.js';
3
+ /** Owned root for state files, overridable for tests and CI sandboxes. */
4
+ export declare function stateRoot(env?: NodeJS.ProcessEnv): string;
5
+ /** Longest accepted state name. Kept exported for the error-message tests. */
6
+ export declare const MAX_STATE_NAME = 64;
7
+ /**
8
+ * Normalize and validate an untrusted state name.
9
+ *
10
+ * The rule is shared with every other CLI-owned artifact — see
11
+ * `artifactPaths.ts` — so a name that is safe as a path component is safe for
12
+ * all of them, and there is only one place to get it wrong.
13
+ */
14
+ export declare function validateStateName(raw: unknown): string;
15
+ /**
16
+ * Full path for a state name, guaranteed to sit inside the owned root.
17
+ */
18
+ export declare function resolveStatePath(name: string, env?: NodeJS.ProcessEnv): Promise<string>;
19
+ /** @see prepareTempPath */
20
+ export declare const prepareTempStatePath: typeof prepareTempPath;
21
+ /** @see commitFile */
22
+ export declare const commitStateFile: typeof commitFile;
23
+ /** @see discardTempFile */
24
+ export declare const discardTempStateFile: typeof discardTempFile;
25
+ /**
26
+ * Read and structurally validate a state file.
27
+ *
28
+ * The file is JSON that will be fed back into a browser, so a malformed or
29
+ * hand-edited one should fail here with a clear message rather than deep
30
+ * inside cookie restoration.
31
+ */
32
+ export declare function readStateFile(target: string, name: string): Promise<SessionState>;
33
+ /** Origins whose storage a state file carries. */
34
+ export declare function stateOrigins(state: SessionState): string[];
35
+ export interface StateSummary {
36
+ cookies: number;
37
+ origins: string[];
38
+ storageKeys: number;
39
+ }
40
+ /**
41
+ * Reportable shape of a state file: counts and origins, never values.
42
+ *
43
+ * Cookie names and storage keys are omitted too. They are not secrets in the
44
+ * way values are, but they are unnecessary to the caller and land in agent
45
+ * transcripts and CI logs, so the useful-to-leaky trade is not worth it.
46
+ */
47
+ export declare function summarizeState(state: SessionState): StateSummary;
48
+ /** Saved state names under the root, without the `.json` suffix. */
49
+ export declare function listStateNames(env?: NodeJS.ProcessEnv): Promise<string[]>;
50
+ //# sourceMappingURL=stateStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateStore.d.ts","sourceRoot":"","sources":["../../src/cli/stateStore.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAIL,eAAe,EACf,UAAU,EACV,eAAe,EAEhB,MAAM,oBAAoB,CAAC;AAE5B,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAEtE;AAED,8EAA8E;AAC9E,eAAO,MAAM,cAAc,KAAoB,CAAC;AAEhD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7F;AAED,2BAA2B;AAC3B,eAAO,MAAM,oBAAoB,wBAAkB,CAAC;AACpD,sBAAsB;AACtB,eAAO,MAAM,eAAe,mBAAa,CAAC;AAC1C,2BAA2B;AAC3B,eAAO,MAAM,oBAAoB,wBAAkB,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAevF;AAED,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE,CAK1D;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAYhE;AAED,oEAAoE;AACpE,wBAAsB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAW/E"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Safe on-disk storage for authentication/session state.
3
+ *
4
+ * A saved state file holds live session cookies — it is a credential, not a
5
+ * fixture. Three properties follow from that, and each is enforced here rather
6
+ * than at the call site:
7
+ *
8
+ * - **Files live under one owned root.** Callers name a file, not a path, so a
9
+ * command can never be talked into writing over `~/.ssh/config` or reading
10
+ * an arbitrary file back into a browser. The root is `CRAFTDRIVER_STATE_DIR`
11
+ * when set, else `.craftdriver/state` under the working directory.
12
+ * - **Symlinks cannot escape it.** Containment is checked against the resolved
13
+ * real path of the deepest existing ancestor, so a symlink planted inside
14
+ * the root does not widen it.
15
+ * - **Nothing is half-written and nothing is world-readable.** Writes land on
16
+ * a temp file created `0600` and are renamed into place, so a reader sees
17
+ * either the old file or the new one, never a truncated JSON document.
18
+ *
19
+ * Callers must also never print what these files contain — see
20
+ * `summarizeState`, which is deliberately the only reporting path.
21
+ */
22
+ import fs from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import { CraftdriverError, ErrorCode } from '../lib/errors.js';
25
+ import { parseSessionState } from '../lib/sessionStateValidation.js';
26
+ import { artifactRoot, resolveArtifactPath, validateArtifactName, prepareTempPath, commitFile, discardTempFile, MAX_ARTIFACT_NAME, } from './artifactPaths.js';
27
+ /** Owned root for state files, overridable for tests and CI sandboxes. */
28
+ export function stateRoot(env = process.env) {
29
+ return artifactRoot('state', 'CRAFTDRIVER_STATE_DIR', env);
30
+ }
31
+ /** Longest accepted state name. Kept exported for the error-message tests. */
32
+ export const MAX_STATE_NAME = MAX_ARTIFACT_NAME;
33
+ /**
34
+ * Normalize and validate an untrusted state name.
35
+ *
36
+ * The rule is shared with every other CLI-owned artifact — see
37
+ * `artifactPaths.ts` — so a name that is safe as a path component is safe for
38
+ * all of them, and there is only one place to get it wrong.
39
+ */
40
+ export function validateStateName(raw) {
41
+ return validateArtifactName(raw, 'state', stateRoot());
42
+ }
43
+ /**
44
+ * Full path for a state name, guaranteed to sit inside the owned root.
45
+ */
46
+ export async function resolveStatePath(name, env) {
47
+ return resolveArtifactPath({
48
+ root: stateRoot(env),
49
+ name,
50
+ suffix: '.json',
51
+ kind: 'state',
52
+ });
53
+ }
54
+ /** @see prepareTempPath */
55
+ export const prepareTempStatePath = prepareTempPath;
56
+ /** @see commitFile */
57
+ export const commitStateFile = commitFile;
58
+ /** @see discardTempFile */
59
+ export const discardTempStateFile = discardTempFile;
60
+ /**
61
+ * Read and structurally validate a state file.
62
+ *
63
+ * The file is JSON that will be fed back into a browser, so a malformed or
64
+ * hand-edited one should fail here with a clear message rather than deep
65
+ * inside cookie restoration.
66
+ */
67
+ export async function readStateFile(target, name) {
68
+ try {
69
+ await fs.access(target);
70
+ }
71
+ catch {
72
+ throw new CraftdriverError(ErrorCode.STATE_INVALID, `state: no saved state named ${JSON.stringify(name)}`, {
73
+ detail: { root: path.dirname(target) },
74
+ hint: 'save one first with `craftdriver state save <name>`, or `state list` to see what exists',
75
+ });
76
+ }
77
+ return parseSessionState(target, { operation: 'cli.state.load' });
78
+ }
79
+ /** Origins whose storage a state file carries. */
80
+ export function stateOrigins(state) {
81
+ const origins = new Set();
82
+ for (const key of Object.keys(state.localStorage ?? {}))
83
+ origins.add(key);
84
+ for (const key of Object.keys(state.sessionStorage ?? {}))
85
+ origins.add(key);
86
+ return [...origins];
87
+ }
88
+ /**
89
+ * Reportable shape of a state file: counts and origins, never values.
90
+ *
91
+ * Cookie names and storage keys are omitted too. They are not secrets in the
92
+ * way values are, but they are unnecessary to the caller and land in agent
93
+ * transcripts and CI logs, so the useful-to-leaky trade is not worth it.
94
+ */
95
+ export function summarizeState(state) {
96
+ let storageKeys = 0;
97
+ for (const bucket of [state.localStorage, state.sessionStorage]) {
98
+ for (const entries of Object.values(bucket ?? {})) {
99
+ storageKeys += Object.keys(entries ?? {}).length;
100
+ }
101
+ }
102
+ return {
103
+ cookies: state.cookies?.length ?? 0,
104
+ origins: stateOrigins(state),
105
+ storageKeys,
106
+ };
107
+ }
108
+ /** Saved state names under the root, without the `.json` suffix. */
109
+ export async function listStateNames(env) {
110
+ const root = stateRoot(env);
111
+ try {
112
+ const entries = await fs.readdir(root);
113
+ return entries
114
+ .filter((entry) => entry.endsWith('.json'))
115
+ .map((entry) => entry.slice(0, -'.json'.length))
116
+ .sort();
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ }
122
+ //# sourceMappingURL=stateStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stateStore.js","sourceRoot":"","sources":["../../src/cli/stateStore.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { BrowserContext, type ClearCookiesFilter, type ContextStorageStateOption
10
10
  export { Keyboard } from './lib/keyboard.js';
11
11
  export { Mouse } from './lib/mouse.js';
12
12
  export { type TraceStartOptions, type TraceStopOptions, type TraceScreenshotMode, type TraceEvent, } from './lib/tracing.js';
13
- export { NetworkInterceptor, LogMonitor, SessionStateManager, type Cookie, type CookieInput, type SessionState, type StorageStateOptions, type MockResponse, type InterceptedRequest, type InterceptedResponse, type ConsoleMessage, type JavaScriptError, type LogMessage, } from './lib/bidi/index.js';
13
+ export { NetworkInterceptor, LogMonitor, SessionStateManager, type Cookie, type CookieInput, type SessionState, type SessionStateCookie, type StorageStateOptions, type MockResponse, type InterceptedRequest, type InterceptedResponse, type ConsoleMessage, type JavaScriptError, type LogMessage, } from './lib/bidi/index.js';
14
14
  export { ChromeService, type ChromeServiceOptions } from './lib/chrome.js';
15
15
  export { ElectronService, type ElectronServiceOptions } from './lib/electron.js';
16
16
  export { FirefoxService, type FirefoxServiceOptions } from './lib/firefox.js';
@@ -20,5 +20,6 @@ export { type ElectronDialogMock, type ElectronDialogMethod, type ElectronDialog
20
20
  export { type ElectronMock, type ElectronMockCall } from './lib/electronMock.js';
21
21
  export { ElectronMainLogMonitor, type ElectronMainLog, type ElectronMainLogLevel, type ElectronMainLogHandler, } from './lib/electronMainLogs.js';
22
22
  export { Clock, type ClockTime, type ClockInstallOptions, } from './lib/clock.js';
23
+ export { compareScreenshots, VisualMismatchError, type ScreenshotCompareOptions, type ExpectScreenshotOptions, type VisualScreenshotOptions, type VisualComparisonResult, type ScreenshotMatchResult, } from './lib/visual/index.js';
23
24
  export { A11y, A11yError, type A11yOptions, type A11yResult, type A11yViolation, type A11yViolationNode, type A11yImpact, } from './lib/a11y.js';
24
25
  //# sourceMappingURL=index.d.ts.map