craftdriver 0.0.3 → 0.2.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 (192) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +186 -10
  3. package/bin/craftdriver.mjs +26 -0
  4. package/dist/cli/client.d.ts +17 -0
  5. package/dist/cli/client.d.ts.map +1 -0
  6. package/dist/cli/client.js +88 -0
  7. package/dist/cli/client.js.map +1 -0
  8. package/dist/cli/daemon.d.ts +15 -0
  9. package/dist/cli/daemon.d.ts.map +1 -0
  10. package/dist/cli/daemon.js +145 -0
  11. package/dist/cli/daemon.js.map +1 -0
  12. package/dist/cli/defaults.d.ts +10 -0
  13. package/dist/cli/defaults.d.ts.map +1 -0
  14. package/dist/cli/defaults.js +42 -0
  15. package/dist/cli/defaults.js.map +1 -0
  16. package/dist/cli/dispatcher.d.ts +28 -0
  17. package/dist/cli/dispatcher.d.ts.map +1 -0
  18. package/dist/cli/dispatcher.js +319 -0
  19. package/dist/cli/dispatcher.js.map +1 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.d.ts.map +1 -0
  22. package/dist/cli/index.js +404 -0
  23. package/dist/cli/index.js.map +1 -0
  24. package/dist/cli/init.d.ts +24 -0
  25. package/dist/cli/init.d.ts.map +1 -0
  26. package/dist/cli/init.js +192 -0
  27. package/dist/cli/init.js.map +1 -0
  28. package/dist/cli/mcp/artifacts.d.ts +43 -0
  29. package/dist/cli/mcp/artifacts.d.ts.map +1 -0
  30. package/dist/cli/mcp/artifacts.js +104 -0
  31. package/dist/cli/mcp/artifacts.js.map +1 -0
  32. package/dist/cli/mcp/server.d.ts +34 -0
  33. package/dist/cli/mcp/server.d.ts.map +1 -0
  34. package/dist/cli/mcp/server.js +214 -0
  35. package/dist/cli/mcp/server.js.map +1 -0
  36. package/dist/cli/mcp/tools.d.ts +34 -0
  37. package/dist/cli/mcp/tools.d.ts.map +1 -0
  38. package/dist/cli/mcp/tools.js +236 -0
  39. package/dist/cli/mcp/tools.js.map +1 -0
  40. package/dist/cli/parseArgs.d.ts +37 -0
  41. package/dist/cli/parseArgs.d.ts.map +1 -0
  42. package/dist/cli/parseArgs.js +215 -0
  43. package/dist/cli/parseArgs.js.map +1 -0
  44. package/dist/cli/protocol.d.ts +34 -0
  45. package/dist/cli/protocol.d.ts.map +1 -0
  46. package/dist/cli/protocol.js +2 -0
  47. package/dist/cli/protocol.js.map +1 -0
  48. package/dist/cli/selector.d.ts +29 -0
  49. package/dist/cli/selector.d.ts.map +1 -0
  50. package/dist/cli/selector.js +100 -0
  51. package/dist/cli/selector.js.map +1 -0
  52. package/dist/cli/snapshot.d.ts +48 -0
  53. package/dist/cli/snapshot.d.ts.map +1 -0
  54. package/dist/cli/snapshot.js +179 -0
  55. package/dist/cli/snapshot.js.map +1 -0
  56. package/dist/index.d.ts +14 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +17 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/lib/a11y.d.ts +81 -0
  61. package/dist/lib/a11y.d.ts.map +1 -0
  62. package/dist/lib/a11y.js +171 -0
  63. package/dist/lib/a11y.js.map +1 -0
  64. package/dist/lib/bidi/connection.d.ts +62 -0
  65. package/dist/lib/bidi/connection.d.ts.map +1 -0
  66. package/dist/lib/bidi/connection.js +227 -0
  67. package/dist/lib/bidi/connection.js.map +1 -0
  68. package/dist/lib/bidi/index.d.ts +101 -0
  69. package/dist/lib/bidi/index.d.ts.map +1 -0
  70. package/dist/lib/bidi/index.js +163 -0
  71. package/dist/lib/bidi/index.js.map +1 -0
  72. package/dist/lib/bidi/logs.d.ts +104 -0
  73. package/dist/lib/bidi/logs.d.ts.map +1 -0
  74. package/dist/lib/bidi/logs.js +273 -0
  75. package/dist/lib/bidi/logs.js.map +1 -0
  76. package/dist/lib/bidi/network.d.ts +165 -0
  77. package/dist/lib/bidi/network.d.ts.map +1 -0
  78. package/dist/lib/bidi/network.js +477 -0
  79. package/dist/lib/bidi/network.js.map +1 -0
  80. package/dist/lib/bidi/storage.d.ts +83 -0
  81. package/dist/lib/bidi/storage.d.ts.map +1 -0
  82. package/dist/lib/bidi/storage.js +299 -0
  83. package/dist/lib/bidi/storage.js.map +1 -0
  84. package/dist/lib/bidi/types.d.ts +323 -0
  85. package/dist/lib/bidi/types.d.ts.map +1 -0
  86. package/dist/lib/bidi/types.js +6 -0
  87. package/dist/lib/bidi/types.js.map +1 -0
  88. package/dist/lib/browser.d.ts +776 -13
  89. package/dist/lib/browser.d.ts.map +1 -1
  90. package/dist/lib/browser.js +1547 -70
  91. package/dist/lib/browser.js.map +1 -1
  92. package/dist/lib/browserContext.d.ts +486 -0
  93. package/dist/lib/browserContext.d.ts.map +1 -0
  94. package/dist/lib/browserContext.js +937 -0
  95. package/dist/lib/browserContext.js.map +1 -0
  96. package/dist/lib/builder.d.ts +3 -0
  97. package/dist/lib/builder.d.ts.map +1 -1
  98. package/dist/lib/builder.js +31 -4
  99. package/dist/lib/builder.js.map +1 -1
  100. package/dist/lib/by.d.ts +18 -2
  101. package/dist/lib/by.d.ts.map +1 -1
  102. package/dist/lib/by.js +20 -2
  103. package/dist/lib/by.js.map +1 -1
  104. package/dist/lib/chrome.d.ts +13 -1
  105. package/dist/lib/chrome.d.ts.map +1 -1
  106. package/dist/lib/chrome.js +13 -17
  107. package/dist/lib/chrome.js.map +1 -1
  108. package/dist/lib/clock.d.ts +115 -0
  109. package/dist/lib/clock.d.ts.map +1 -0
  110. package/dist/lib/clock.js +407 -0
  111. package/dist/lib/clock.js.map +1 -0
  112. package/dist/lib/driver.d.ts +45 -0
  113. package/dist/lib/driver.d.ts.map +1 -1
  114. package/dist/lib/driver.js +129 -1
  115. package/dist/lib/driver.js.map +1 -1
  116. package/dist/lib/driverManager.d.ts +25 -0
  117. package/dist/lib/driverManager.d.ts.map +1 -0
  118. package/dist/lib/driverManager.js +399 -0
  119. package/dist/lib/driverManager.js.map +1 -0
  120. package/dist/lib/elementHandle.d.ts +69 -2
  121. package/dist/lib/elementHandle.d.ts.map +1 -1
  122. package/dist/lib/elementHandle.js +215 -40
  123. package/dist/lib/elementHandle.js.map +1 -1
  124. package/dist/lib/errors.d.ts +71 -0
  125. package/dist/lib/errors.d.ts.map +1 -0
  126. package/dist/lib/errors.js +73 -0
  127. package/dist/lib/errors.js.map +1 -0
  128. package/dist/lib/expect.d.ts +6 -1
  129. package/dist/lib/expect.d.ts.map +1 -1
  130. package/dist/lib/expect.js +55 -37
  131. package/dist/lib/expect.js.map +1 -1
  132. package/dist/lib/firefox.d.ts +18 -0
  133. package/dist/lib/firefox.d.ts.map +1 -0
  134. package/dist/lib/firefox.js +33 -0
  135. package/dist/lib/firefox.js.map +1 -0
  136. package/dist/lib/frame.d.ts +57 -0
  137. package/dist/lib/frame.d.ts.map +1 -0
  138. package/dist/lib/frame.js +277 -0
  139. package/dist/lib/frame.js.map +1 -0
  140. package/dist/lib/keyboard.d.ts +1 -1
  141. package/dist/lib/keyboard.d.ts.map +1 -1
  142. package/dist/lib/keyboard.js +1 -1
  143. package/dist/lib/keyboard.js.map +1 -1
  144. package/dist/lib/locator.d.ts +71 -2
  145. package/dist/lib/locator.d.ts.map +1 -1
  146. package/dist/lib/locator.js +289 -31
  147. package/dist/lib/locator.js.map +1 -1
  148. package/dist/lib/mouse.d.ts +1 -1
  149. package/dist/lib/mouse.d.ts.map +1 -1
  150. package/dist/lib/mouse.js +1 -1
  151. package/dist/lib/mouse.js.map +1 -1
  152. package/dist/lib/page.d.ts +107 -0
  153. package/dist/lib/page.d.ts.map +1 -0
  154. package/dist/lib/page.js +388 -0
  155. package/dist/lib/page.js.map +1 -0
  156. package/dist/lib/tracing.d.ts +128 -0
  157. package/dist/lib/tracing.d.ts.map +1 -0
  158. package/dist/lib/tracing.js +272 -0
  159. package/dist/lib/tracing.js.map +1 -0
  160. package/dist/lib/wait.d.ts.map +1 -1
  161. package/dist/lib/wait.js +14 -1
  162. package/dist/lib/wait.js.map +1 -1
  163. package/dist/lib/webelement.d.ts +2 -0
  164. package/dist/lib/webelement.d.ts.map +1 -1
  165. package/dist/lib/webelement.js +18 -0
  166. package/dist/lib/webelement.js.map +1 -1
  167. package/docs/accessibility.md +177 -0
  168. package/docs/api-reference.md +71 -0
  169. package/docs/assertions.md +182 -0
  170. package/docs/bidi-features.md +462 -0
  171. package/docs/browser-api.md +673 -0
  172. package/docs/browser-context.md +522 -0
  173. package/docs/cli.md +233 -0
  174. package/docs/clock.md +245 -0
  175. package/docs/dialogs.md +143 -0
  176. package/docs/driver-configuration.md +64 -0
  177. package/docs/element-api.md +219 -0
  178. package/docs/emulation.md +155 -0
  179. package/docs/error-codes.md +67 -0
  180. package/docs/getting-started.md +168 -0
  181. package/docs/keyboard-mouse.md +250 -0
  182. package/docs/mcp.md +250 -0
  183. package/docs/mobile-emulation.md +194 -0
  184. package/docs/screenshots.md +146 -0
  185. package/docs/selectors.md +382 -0
  186. package/docs/session-management.md +229 -0
  187. package/docs/tracing.md +282 -0
  188. package/package.json +25 -5
  189. package/skills/craftdriver/SKILL.md +84 -0
  190. package/skills/craftdriver/cheatsheet.md +194 -0
  191. package/skills/craftdriver/cli.md +104 -0
  192. package/skills/craftdriver/patterns.md +139 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Selector syntax for the CLI / agent surface.
3
+ *
4
+ * Agents pick a "kind" by prefixing the selector with one of the
5
+ * known schemes; otherwise the value is treated as a CSS selector
6
+ * (the default everywhere in craftdriver). Examples:
7
+ *
8
+ * #submit → By.css('#submit')
9
+ * css=button.primary → By.css('button.primary')
10
+ * xpath=//button[1] → By.xpath('//button[1]')
11
+ * text=Sign In → By.text('Sign In')
12
+ * text*=Sign → By.partialText('Sign')
13
+ * role=button → By.role('button')
14
+ * role=button[name=Submit] → By.role('button', { name: 'Submit' })
15
+ * label=Email → By.labelText('Email')
16
+ * testid=login-btn → By.testId('login-btn')
17
+ * placeholder=Search… → By.placeholder('Search…')
18
+ * alt=Logo → By.altText('Logo')
19
+ * title=Help → By.title('Help')
20
+ * ref=e5 → By.css('[data-craftdriver-ref="e5"]')
21
+ * (refs come from `craftdriver snapshot` /
22
+ * browser_snapshot; invalidated by the next
23
+ * snapshot or navigation)
24
+ */
25
+ import { By } from '../lib/by.js';
26
+ import { CraftdriverError, ErrorCode } from '../lib/errors.js';
27
+ const ROLE_RE = /^([a-z-]+)(?:\[name=([^\]]+)\])?$/i;
28
+ export function parseSelector(input) {
29
+ if (input === undefined || input === null || input === '') {
30
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, 'parseSelector: selector is empty', { hint: 'pass a CSS selector or a prefixed form like role=button or text=Sign In' });
31
+ }
32
+ const eq = input.indexOf('=');
33
+ if (eq <= 0)
34
+ return By.css(input);
35
+ const kind = input.slice(0, eq).toLowerCase();
36
+ const valueRaw = input.slice(eq + 1);
37
+ switch (kind) {
38
+ case 'css':
39
+ return By.css(valueRaw);
40
+ case 'xpath':
41
+ return By.xpath(valueRaw);
42
+ case 'id':
43
+ return By.id(valueRaw);
44
+ case 'name':
45
+ return By.name(valueRaw);
46
+ case 'tag':
47
+ case 'tagname':
48
+ return By.tagName(valueRaw);
49
+ case 'class':
50
+ case 'classname':
51
+ return By.className(valueRaw);
52
+ case 'text':
53
+ return By.text(valueRaw);
54
+ case 'text*':
55
+ case 'partialtext':
56
+ return By.partialText(valueRaw);
57
+ case 'role': {
58
+ const m = ROLE_RE.exec(valueRaw.trim());
59
+ if (!m) {
60
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `parseSelector: invalid role syntax "${valueRaw}"`, { hint: 'use role=<role> or role=<role>[name=<name>]' });
61
+ }
62
+ const [, role, name] = m;
63
+ return name ? By.role(role, { name }) : By.role(role);
64
+ }
65
+ case 'label':
66
+ case 'labeltext':
67
+ return By.labelText(valueRaw);
68
+ case 'placeholder':
69
+ return By.placeholder(valueRaw);
70
+ case 'alt':
71
+ case 'alttext':
72
+ return By.altText(valueRaw);
73
+ case 'title':
74
+ return By.title(valueRaw);
75
+ case 'testid':
76
+ case 'data-testid':
77
+ return By.testId(valueRaw);
78
+ case 'ref': {
79
+ // Refs are stamped onto elements by the snapshot evaluator as
80
+ // `data-craftdriver-ref="eN"`. They are stable until the next
81
+ // snapshot or navigation. We resolve them as a plain CSS
82
+ // attribute selector so all the usual auto-waiting paths work
83
+ // unchanged.
84
+ const v = valueRaw.trim();
85
+ if (!/^e\d+$/.test(v)) {
86
+ throw new CraftdriverError(ErrorCode.INVALID_ARGUMENT, `parseSelector: invalid ref "${valueRaw}"`, { hint: 'refs look like e1, e2, … (take a snapshot to see them)' });
87
+ }
88
+ return By.css(`[data-craftdriver-ref="${v}"]`);
89
+ }
90
+ default:
91
+ // Treat unknown prefix as part of a CSS selector (e.g. attribute
92
+ // selectors with `=` inside). This is the least-surprising default.
93
+ return By.css(input);
94
+ }
95
+ }
96
+ /** Short, log-friendly description of a By for error messages. */
97
+ export function describeSelector(by) {
98
+ return `${by.using}=${by.value}`;
99
+ }
100
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/cli/selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Compact a11y snapshot generation + per-session diffing.
3
+ *
4
+ * Why this exists: the MCP spec (§6 of the AI-productivity plan) says
5
+ * "a11y snapshot is the default, not a screenshot." Screenshots cost
6
+ * 800–1500 image tokens; a flat list of visible interactive elements
7
+ * (role + accessible name + selector hint) is ~50–500 text tokens and
8
+ * far more useful for a text model writing the next selector.
9
+ *
10
+ * Snapshot algorithm runs entirely in the page via `page.evaluate`:
11
+ * pick interactive elements + headings + landmarks, compute an
12
+ * accessible name, skip the invisible, cap at `MAX_NODES`. The
13
+ * output is a single string with one node per line. Diffing is just
14
+ * a flat line-by-line compare.
15
+ */
16
+ import type { Browser } from '../lib/browser.js';
17
+ export interface SnapshotShape {
18
+ url: string;
19
+ title: string;
20
+ /** One line per visible interactive node, prefixed with `eN: `. */
21
+ lines: string[];
22
+ }
23
+ /**
24
+ * Build a fresh snapshot of the active page. Errors swallowed so a
25
+ * partial post-action payload never breaks the protocol.
26
+ */
27
+ export declare function takeSnapshot(browser: Browser): Promise<SnapshotShape | null>;
28
+ /**
29
+ * Render a snapshot as a compact, agent-friendly string. Either the
30
+ * full snapshot (first call in a session) or a `+/-` diff vs. the
31
+ * previous snapshot in the same session.
32
+ *
33
+ * Diff format intentionally mirrors `diff -u` lines so agents
34
+ * already familiar with unified diffs read it without instruction.
35
+ *
36
+ * Ref numbers (`e1`, `e2`, …) re-allocate on every snapshot so they
37
+ * can't be used to drive the diff directly — we strip them before
38
+ * the set-difference and re-attach them when rendering, so the diff
39
+ * stays semantically stable across renumberings.
40
+ */
41
+ export declare function renderDelta(prev: SnapshotShape | null, next: SnapshotShape | null): string;
42
+ /**
43
+ * Render a fresh, full snapshot (no diff). Used by the on-demand
44
+ * `snapshot` command + `browser_snapshot` tool when the agent wants
45
+ * the current sanitized DOM regardless of session history.
46
+ */
47
+ export declare function renderFull(next: SnapshotShape | null): string;
48
+ //# sourceMappingURL=snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/cli/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAKjD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAYlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,aAAa,GAAG,IAAI,EAC1B,IAAI,EAAE,aAAa,GAAG,IAAI,GACzB,MAAM,CAqBR;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAO7D"}
@@ -0,0 +1,179 @@
1
+ const MAX_NODES = 80;
2
+ const MAX_NAME = 80;
3
+ /**
4
+ * Build a fresh snapshot of the active page. Errors swallowed so a
5
+ * partial post-action payload never breaks the protocol.
6
+ */
7
+ export async function takeSnapshot(browser) {
8
+ try {
9
+ const page = await browser.activePage();
10
+ const [url, title] = await Promise.all([
11
+ page.url().catch(() => ''),
12
+ page.title().catch(() => ''),
13
+ ]);
14
+ const lines = await page.evaluate(JS_SNAPSHOT);
15
+ return { url, title, lines: Array.isArray(lines) ? lines : [] };
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * Render a snapshot as a compact, agent-friendly string. Either the
23
+ * full snapshot (first call in a session) or a `+/-` diff vs. the
24
+ * previous snapshot in the same session.
25
+ *
26
+ * Diff format intentionally mirrors `diff -u` lines so agents
27
+ * already familiar with unified diffs read it without instruction.
28
+ *
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.
33
+ */
34
+ export function renderDelta(prev, next) {
35
+ if (!next)
36
+ return '';
37
+ const header = `page: ${next.title || '(untitled)'} — ${next.url || '(no url)'}`;
38
+ if (!prev) {
39
+ // First snapshot — print full.
40
+ if (next.lines.length === 0)
41
+ return `${header}\n(no interactive elements detected)`;
42
+ return `${header}\n${next.lines.join('\n')}`;
43
+ }
44
+ if (prev.url !== next.url) {
45
+ // Page changed — full snapshot.
46
+ return `${header}\n${next.lines.join('\n')}`;
47
+ }
48
+ 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}`);
53
+ if (removed.length === 0 && added.length === 0) {
54
+ return `${header}\n(no a11y changes)`;
55
+ }
56
+ return `${header}\n${removed.concat(added).join('\n')}`;
57
+ }
58
+ /**
59
+ * Render a fresh, full snapshot (no diff). Used by the on-demand
60
+ * `snapshot` command + `browser_snapshot` tool when the agent wants
61
+ * the current sanitized DOM regardless of session history.
62
+ */
63
+ export function renderFull(next) {
64
+ if (!next)
65
+ return '(no snapshot available)';
66
+ const header = `page: ${next.title || '(untitled)'} — ${next.url || '(no url)'}`;
67
+ if (next.lines.length === 0) {
68
+ return `${header}\n(no interactive elements detected)`;
69
+ }
70
+ return `${header}\n${next.lines.join('\n')}`;
71
+ }
72
+ /**
73
+ * JS executed in-page. Keep it self-contained — no closure capture.
74
+ *
75
+ * 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
81
+ * of page complexity.
82
+ */
83
+ const JS_SNAPSHOT = `
84
+ const MAX_NODES = ${MAX_NODES};
85
+ const MAX_NAME = ${MAX_NAME};
86
+ function visible(el) {
87
+ if (!el || !el.getClientRects) return false;
88
+ if (el.hidden) return false;
89
+ const rects = el.getClientRects();
90
+ if (!rects.length) return false;
91
+ const r = rects[0];
92
+ if (r.width === 0 || r.height === 0) return false;
93
+ const cs = getComputedStyle(el);
94
+ if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') return false;
95
+ return true;
96
+ }
97
+ function impliedRole(el) {
98
+ const t = el.tagName.toLowerCase();
99
+ const explicit = el.getAttribute('role');
100
+ if (explicit) return explicit;
101
+ if (t === 'a' && el.hasAttribute('href')) return 'link';
102
+ if (t === 'button') return 'button';
103
+ if (t === 'input') {
104
+ const it = (el.getAttribute('type') || 'text').toLowerCase();
105
+ if (it === 'submit' || it === 'button' || it === 'reset') return 'button';
106
+ if (it === 'checkbox') return 'checkbox';
107
+ if (it === 'radio') return 'radio';
108
+ return 'textbox';
109
+ }
110
+ if (t === 'textarea') return 'textbox';
111
+ if (t === 'select') return 'combobox';
112
+ if (/^h[1-6]$/.test(t)) return 'heading';
113
+ if (t === 'nav') return 'navigation';
114
+ if (t === 'main') return 'main';
115
+ if (t === 'header') return 'banner';
116
+ if (t === 'footer') return 'contentinfo';
117
+ if (t === 'form') return 'form';
118
+ if (t === 'img') return 'img';
119
+ return t;
120
+ }
121
+ function accName(el) {
122
+ const aria = el.getAttribute('aria-label');
123
+ if (aria) return aria.trim();
124
+ const labelledby = el.getAttribute('aria-labelledby');
125
+ if (labelledby) {
126
+ const ref = document.getElementById(labelledby);
127
+ if (ref) return (ref.textContent || '').trim();
128
+ }
129
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') {
130
+ if (el.id) {
131
+ const lbl = document.querySelector('label[for=' + JSON.stringify(el.id) + ']');
132
+ if (lbl) return (lbl.textContent || '').trim();
133
+ }
134
+ const parentLabel = el.closest('label');
135
+ if (parentLabel) return (parentLabel.textContent || '').trim();
136
+ const placeholder = el.getAttribute('placeholder');
137
+ if (placeholder) return placeholder.trim();
138
+ }
139
+ if (el.tagName === 'IMG') {
140
+ const alt = el.getAttribute('alt');
141
+ if (alt) return alt.trim();
142
+ }
143
+ const text = (el.textContent || '').trim().replace(/\\s+/g, ' ');
144
+ return text;
145
+ }
146
+ function locatorHint(el) {
147
+ if (el.id) return '#' + el.id;
148
+ const testid = el.getAttribute('data-testid') || el.getAttribute('data-test-id');
149
+ if (testid) return '[data-testid=' + JSON.stringify(testid) + ']';
150
+ const name = el.getAttribute('name');
151
+ if (name) return el.tagName.toLowerCase() + '[name=' + JSON.stringify(name) + ']';
152
+ return null;
153
+ }
154
+ const sel = 'a,button,input,select,textarea,h1,h2,h3,h4,h5,h6,[role],nav,main,header,footer,form,img,label';
155
+ // Wipe stale refs from a previous snapshot so numbering is deterministic.
156
+ const stale = document.querySelectorAll('[data-craftdriver-ref]');
157
+ for (let i = 0; i < stale.length; i++) stale[i].removeAttribute('data-craftdriver-ref');
158
+ const out = [];
159
+ const nodes = document.querySelectorAll(sel);
160
+ for (let i = 0; i < nodes.length && out.length < MAX_NODES; i++) {
161
+ const el = nodes[i];
162
+ if (!visible(el)) continue;
163
+ const role = impliedRole(el);
164
+ let name = accName(el);
165
+ if (name.length > MAX_NAME) name = name.slice(0, MAX_NAME - 1) + '…';
166
+ const hint = locatorHint(el);
167
+ const ref = 'e' + (out.length + 1);
168
+ el.setAttribute('data-craftdriver-ref', ref);
169
+ let line = ref + ': ' + role;
170
+ if (name) line += ' "' + name + '"';
171
+ if (hint) line += ' ' + hint;
172
+ // Annotate disabled/checked state, agents need it.
173
+ if (el.disabled) line += ' (disabled)';
174
+ if (el.checked) line += ' (checked)';
175
+ out.push(line);
176
+ }
177
+ return out;
178
+ `;
179
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/cli/snapshot.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,17 @@
1
- export { Browser, type LaunchOptions } from './lib/browser.js';
1
+ export { CraftdriverError, ErrorCode, type ErrorCodeName, type ErrorCodeValue, type CraftdriverErrorOptions, } from './lib/errors.js';
2
+ export { Browser, devices, type LaunchOptions, type LoadState, type Dialog, type DialogType, type MobileEmulation, type DeviceMetrics, type DeviceName, type Download, type EmulateOptions, } from './lib/browser.js';
2
3
  export { Key } from './lib/keys.js';
3
4
  export { By } from './lib/by.js';
5
+ export { Locator } from './lib/locator.js';
6
+ export { Frame } from './lib/frame.js';
7
+ export { Page } from './lib/page.js';
8
+ export { BrowserContext, type ClearCookiesFilter, type ContextStorageStateOptions, type BrowserContextConfig, type BrowserContextHooks, type InitScriptHandle, type RoutePattern, } from './lib/browserContext.js';
9
+ export { Keyboard } from './lib/keyboard.js';
10
+ export { Mouse } from './lib/mouse.js';
11
+ export { type TraceStartOptions, type TraceScreenshotMode, type TraceEvent, } from './lib/tracing.js';
12
+ 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 { ChromeService, type ChromeServiceOptions } from './lib/chrome.js';
14
+ export { FirefoxService, type FirefoxServiceOptions } from './lib/firefox.js';
15
+ export { Clock, type ClockTime, type ClockInstallOptions, } from './lib/clock.js';
16
+ export { A11y, A11yError, type A11yOptions, type A11yResult, type A11yViolation, type A11yViolationNode, type A11yImpact, } from './lib/a11y.js';
4
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EACL,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,UAAU,GAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,mBAAmB,EACnB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG9E,OAAO,EACL,KAAK,EACL,KAAK,SAAS,EACd,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,IAAI,EACJ,SAAS,EACT,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,21 @@
1
1
  // Public entrypoint: expose the simplified Browser API only
2
- export { Browser } from './lib/browser.js';
2
+ export { CraftdriverError, ErrorCode, } from './lib/errors.js';
3
+ export { Browser, devices, } from './lib/browser.js';
3
4
  export { Key } from './lib/keys.js';
4
5
  export { By } from './lib/by.js';
6
+ export { Locator } from './lib/locator.js';
7
+ export { Frame } from './lib/frame.js';
8
+ export { Page } from './lib/page.js';
9
+ export { BrowserContext, } from './lib/browserContext.js';
10
+ export { Keyboard } from './lib/keyboard.js';
11
+ export { Mouse } from './lib/mouse.js';
12
+ // BiDi features - network interception, logging, session state
13
+ export { NetworkInterceptor, LogMonitor, SessionStateManager, } from './lib/bidi/index.js';
14
+ // Driver services — expose for users who need custom binary paths / ports
15
+ export { ChromeService } from './lib/chrome.js';
16
+ export { FirefoxService } from './lib/firefox.js';
17
+ // Virtual clock control
18
+ export { Clock, } from './lib/clock.js';
19
+ // Accessibility audits (axe-core wrapper — axe-core ships with craftdriver)
20
+ export { A11y, A11yError, } from './lib/a11y.js';
5
21
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,OAAO,EAAE,OAAO,EAAsB,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,OAAO,EACL,gBAAgB,EAChB,SAAS,GAIV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,OAAO,GAUR,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EACL,cAAc,GAOf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAOvC,+DAA+D;AAC/D,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,mBAAmB,GAWpB,MAAM,qBAAqB,CAAC;AAE7B,0EAA0E;AAC1E,OAAO,EAAE,aAAa,EAA6B,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAA8B,MAAM,kBAAkB,CAAC;AAE9E,wBAAwB;AACxB,OAAO,EACL,KAAK,GAGN,MAAM,gBAAgB,CAAC;AAExB,4EAA4E;AAC5E,OAAO,EACL,IAAI,EACJ,SAAS,GAMV,MAAM,eAAe,CAAC"}
@@ -0,0 +1,81 @@
1
+ import type { Driver } from './driver.js';
2
+ import type { WebElement } from './webelement.js';
3
+ import { CraftdriverError } from './errors.js';
4
+ /** Severity buckets reported by axe-core. */
5
+ export type A11yImpact = 'minor' | 'moderate' | 'serious' | 'critical';
6
+ export interface A11yOptions {
7
+ /**
8
+ * Rule IDs to skip entirely. The common case — most projects maintain
9
+ * a small constant list of rules the team knowingly waives.
10
+ */
11
+ disableRules?: string[];
12
+ /** Only run these rule IDs. Mutually exclusive with `disableRules`. */
13
+ rules?: string[];
14
+ /** Minimum impact reported. Defaults to `'serious'`. */
15
+ minImpact?: A11yImpact;
16
+ /** Escape hatch: raw axe-core run options merged last (overrides everything else). */
17
+ axeOptions?: Record<string, unknown>;
18
+ }
19
+ export interface A11yViolationNode {
20
+ /** CSS selector chain identifying the violating node. */
21
+ target: string[];
22
+ /** HTML snippet of the violating node. */
23
+ html: string;
24
+ /** Human-readable summary of why this node failed. */
25
+ failureSummary: string;
26
+ }
27
+ export interface A11yViolation {
28
+ /** Rule ID, e.g. `'color-contrast'`. */
29
+ id: string;
30
+ impact: A11yImpact;
31
+ description: string;
32
+ /** Link to the axe-core rule documentation. */
33
+ helpUrl: string;
34
+ nodes: A11yViolationNode[];
35
+ }
36
+ export interface A11yResult {
37
+ violations: A11yViolation[];
38
+ passes: number;
39
+ incomplete: number;
40
+ inapplicable: number;
41
+ }
42
+ /** Thrown by `A11y.check()` when violations are found. */
43
+ export declare class A11yError extends CraftdriverError {
44
+ readonly violations: A11yViolation[];
45
+ readonly result: A11yResult;
46
+ constructor(message: string, result: A11yResult);
47
+ }
48
+ /**
49
+ * Internal configuration object describing how an `A11y` instance should
50
+ * locate its target. Not part of the public API — produced by the
51
+ * `.a11y` accessor on `Browser`, `ElementHandle`, and `Locator`.
52
+ */
53
+ export interface A11ySource {
54
+ driver: Driver;
55
+ /** Resolve the target element (omit for document-level audits). */
56
+ resolveTarget?: () => Promise<WebElement>;
57
+ /** Frame context switching for frame-bound locators. */
58
+ withContext?: <T>(fn: () => Promise<T>) => Promise<T>;
59
+ }
60
+ /**
61
+ * Ergonomic wrapper around axe-core. Obtain instances via the `.a11y`
62
+ * accessor on `Browser`, `ElementHandle`, or `Locator` — never construct
63
+ * directly.
64
+ *
65
+ * - `audit(options?)` returns the report.
66
+ * - `check(options?)` throws an `A11yError` when violations exist.
67
+ */
68
+ export declare class A11y {
69
+ private source;
70
+ constructor(source: A11ySource);
71
+ /** Run an accessibility audit and return the report. */
72
+ audit(options?: A11yOptions): Promise<A11yResult>;
73
+ /**
74
+ * Run an audit and throw an `A11yError` if any violation is found.
75
+ *
76
+ * The thrown error carries the full `result` and `violations` for
77
+ * programmatic inspection.
78
+ */
79
+ check(options?: A11yOptions): Promise<void>;
80
+ }
81
+ //# sourceMappingURL=a11y.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y.d.ts","sourceRoot":"","sources":["../../src/lib/a11y.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAa,MAAM,aAAa,CAAC;AAE1D,6CAA6C;AAC7C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvE,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wDAAwD;IACxD,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,0DAA0D;AAC1D,qBAAa,SAAU,SAAQ,gBAAgB;IAC7C,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;gBAChB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU;CAWhD;AA6ED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1C,wDAAwD;IACxD,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACvD;AAED;;;;;;;GAOG;AACH,qBAAa,IAAI;IACH,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,UAAU;IAEtC,wDAAwD;IAClD,KAAK,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IAgC3D;;;;;OAKG;IACG,KAAK,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAUtD"}
@@ -0,0 +1,171 @@
1
+ import { CraftdriverError, ErrorCode } from './errors.js';
2
+ /** Thrown by `A11y.check()` when violations are found. */
3
+ export class A11yError extends CraftdriverError {
4
+ violations;
5
+ result;
6
+ constructor(message, result) {
7
+ super(ErrorCode.A11Y_VIOLATIONS, message, {
8
+ detail: {
9
+ violationCount: result.violations.length,
10
+ rules: result.violations.map((v) => v.id),
11
+ },
12
+ });
13
+ this.name = 'A11yError';
14
+ this.violations = result.violations;
15
+ this.result = result;
16
+ }
17
+ }
18
+ // ---------------------------------------------------------------------------
19
+ // Internal — axe-core loader
20
+ // ---------------------------------------------------------------------------
21
+ const IMPACT_RANK = {
22
+ minor: 0,
23
+ moderate: 1,
24
+ serious: 2,
25
+ critical: 3,
26
+ };
27
+ let _axeSourceCache = null;
28
+ async function loadAxeSource() {
29
+ if (_axeSourceCache)
30
+ return _axeSourceCache;
31
+ let mod;
32
+ try {
33
+ mod = await import('axe-core');
34
+ }
35
+ catch (e) {
36
+ // axe-core ships as a direct dependency of craftdriver; if this fails
37
+ // the install is broken — surface the underlying error.
38
+ throw new Error(`craftdriver: failed to load axe-core. Try reinstalling dependencies. ` +
39
+ `Underlying error: ${e?.message ?? String(e)}`);
40
+ }
41
+ const candidate = mod?.source ??
42
+ mod?.default?.source;
43
+ if (typeof candidate !== 'string' || candidate.length === 0) {
44
+ throw new Error('craftdriver: the installed axe-core build does not expose a `.source` string.');
45
+ }
46
+ _axeSourceCache = candidate;
47
+ return candidate;
48
+ }
49
+ function buildAxeRunOptions(opts) {
50
+ if (opts.disableRules?.length && opts.rules?.length) {
51
+ throw new Error('craftdriver a11y: `disableRules` and `rules` are mutually exclusive. ' +
52
+ 'Pick one or use `axeOptions` directly.');
53
+ }
54
+ const out = {};
55
+ if (opts.disableRules?.length) {
56
+ out.rules = Object.fromEntries(opts.disableRules.map((id) => [id, { enabled: false }]));
57
+ }
58
+ if (opts.rules?.length) {
59
+ out.runOnly = { type: 'rule', values: opts.rules };
60
+ }
61
+ // Caller-provided axeOptions win — escape hatch by design.
62
+ return { ...out, ...(opts.axeOptions ?? {}) };
63
+ }
64
+ function formatViolations(violations) {
65
+ return violations
66
+ .map((v) => {
67
+ const nodeCount = v.nodes.length;
68
+ const nodeLabel = nodeCount === 1 ? '1 node' : `${nodeCount} nodes`;
69
+ return (` • ${v.id} (${v.impact}) — ${v.description}\n` +
70
+ ` ${nodeLabel} · ${v.helpUrl}`);
71
+ })
72
+ .join('\n');
73
+ }
74
+ /**
75
+ * Ergonomic wrapper around axe-core. Obtain instances via the `.a11y`
76
+ * accessor on `Browser`, `ElementHandle`, or `Locator` — never construct
77
+ * directly.
78
+ *
79
+ * - `audit(options?)` returns the report.
80
+ * - `check(options?)` throws an `A11yError` when violations exist.
81
+ */
82
+ export class A11y {
83
+ source;
84
+ constructor(source) {
85
+ this.source = source;
86
+ }
87
+ /** Run an accessibility audit and return the report. */
88
+ async audit(options = {}) {
89
+ const axeOptions = buildAxeRunOptions(options);
90
+ const minImpact = options.minImpact ?? 'serious';
91
+ const withContext = this.source.withContext ?? ((fn) => fn());
92
+ return withContext(async () => {
93
+ const axeSource = await loadAxeSource();
94
+ const target = this.source.resolveTarget
95
+ ? await this.source.resolveTarget()
96
+ : null;
97
+ const raw = await this.source.driver.executeAsyncScript(AUDIT_SCRIPT, [target, axeSource, JSON.stringify(axeOptions)]);
98
+ if (!raw || !raw.ok) {
99
+ throw new Error(`craftdriver a11y: audit failed in the page — ${raw?.error ?? 'unknown error'}`);
100
+ }
101
+ const threshold = IMPACT_RANK[minImpact];
102
+ const filtered = (raw.result ?? { violations: [], passes: 0, incomplete: 0, inapplicable: 0 });
103
+ return {
104
+ ...filtered,
105
+ violations: filtered.violations.filter((v) => IMPACT_RANK[v.impact] >= threshold),
106
+ };
107
+ });
108
+ }
109
+ /**
110
+ * Run an audit and throw an `A11yError` if any violation is found.
111
+ *
112
+ * The thrown error carries the full `result` and `violations` for
113
+ * programmatic inspection.
114
+ */
115
+ async check(options = {}) {
116
+ const result = await this.audit(options);
117
+ if (result.violations.length === 0)
118
+ return;
119
+ const count = result.violations.length;
120
+ const noun = count === 1 ? 'violation' : 'violations';
121
+ throw new A11yError(`Accessibility check found ${count} ${noun}:\n${formatViolations(result.violations)}`, result);
122
+ }
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Page-side script — kept as a constant so it's easy to audit.
126
+ // ---------------------------------------------------------------------------
127
+ const AUDIT_SCRIPT = `
128
+ var target = arguments[0];
129
+ var axeSource = arguments[1];
130
+ var optsJson = arguments[2];
131
+ var done = arguments[arguments.length - 1];
132
+ try {
133
+ if (!window.__craftdriverAxeLoaded) {
134
+ // eslint-disable-next-line no-new-func
135
+ (new Function(axeSource)).call(window);
136
+ window.__craftdriverAxeLoaded = true;
137
+ }
138
+ var ctx = target || document;
139
+ var opts = JSON.parse(optsJson);
140
+ window.axe.run(ctx, opts).then(function (r) {
141
+ done({
142
+ ok: true,
143
+ result: {
144
+ violations: (r.violations || []).map(function (v) {
145
+ return {
146
+ id: v.id,
147
+ impact: v.impact || 'minor',
148
+ description: v.description,
149
+ helpUrl: v.helpUrl,
150
+ nodes: (v.nodes || []).map(function (n) {
151
+ return {
152
+ target: (n.target || []).map(String),
153
+ html: n.html,
154
+ failureSummary: n.failureSummary || '',
155
+ };
156
+ }),
157
+ };
158
+ }),
159
+ passes: (r.passes || []).length,
160
+ incomplete: (r.incomplete || []).length,
161
+ inapplicable: (r.inapplicable || []).length,
162
+ },
163
+ });
164
+ }, function (err) {
165
+ done({ ok: false, error: (err && err.message) ? err.message : String(err) });
166
+ });
167
+ } catch (e) {
168
+ done({ ok: false, error: (e && e.message) ? e.message : String(e) });
169
+ }
170
+ `;
171
+ //# sourceMappingURL=a11y.js.map