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.
- package/CHANGELOG.md +104 -0
- package/README.md +186 -10
- package/bin/craftdriver.mjs +26 -0
- package/dist/cli/client.d.ts +17 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +88 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon.d.ts +15 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +145 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/defaults.d.ts +10 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +42 -0
- package/dist/cli/defaults.js.map +1 -0
- package/dist/cli/dispatcher.d.ts +28 -0
- package/dist/cli/dispatcher.d.ts.map +1 -0
- package/dist/cli/dispatcher.js +319 -0
- package/dist/cli/dispatcher.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +404 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +24 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +192 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +43 -0
- package/dist/cli/mcp/artifacts.d.ts.map +1 -0
- package/dist/cli/mcp/artifacts.js +104 -0
- package/dist/cli/mcp/artifacts.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +34 -0
- package/dist/cli/mcp/server.d.ts.map +1 -0
- package/dist/cli/mcp/server.js +214 -0
- package/dist/cli/mcp/server.js.map +1 -0
- package/dist/cli/mcp/tools.d.ts +34 -0
- package/dist/cli/mcp/tools.d.ts.map +1 -0
- package/dist/cli/mcp/tools.js +236 -0
- package/dist/cli/mcp/tools.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +37 -0
- package/dist/cli/parseArgs.d.ts.map +1 -0
- package/dist/cli/parseArgs.js +215 -0
- package/dist/cli/parseArgs.js.map +1 -0
- package/dist/cli/protocol.d.ts +34 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/cli/protocol.js +2 -0
- package/dist/cli/protocol.js.map +1 -0
- package/dist/cli/selector.d.ts +29 -0
- package/dist/cli/selector.d.ts.map +1 -0
- package/dist/cli/selector.js +100 -0
- package/dist/cli/selector.js.map +1 -0
- package/dist/cli/snapshot.d.ts +48 -0
- package/dist/cli/snapshot.d.ts.map +1 -0
- package/dist/cli/snapshot.js +179 -0
- package/dist/cli/snapshot.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/a11y.d.ts +81 -0
- package/dist/lib/a11y.d.ts.map +1 -0
- package/dist/lib/a11y.js +171 -0
- package/dist/lib/a11y.js.map +1 -0
- package/dist/lib/bidi/connection.d.ts +62 -0
- package/dist/lib/bidi/connection.d.ts.map +1 -0
- package/dist/lib/bidi/connection.js +227 -0
- package/dist/lib/bidi/connection.js.map +1 -0
- package/dist/lib/bidi/index.d.ts +101 -0
- package/dist/lib/bidi/index.d.ts.map +1 -0
- package/dist/lib/bidi/index.js +163 -0
- package/dist/lib/bidi/index.js.map +1 -0
- package/dist/lib/bidi/logs.d.ts +104 -0
- package/dist/lib/bidi/logs.d.ts.map +1 -0
- package/dist/lib/bidi/logs.js +273 -0
- package/dist/lib/bidi/logs.js.map +1 -0
- package/dist/lib/bidi/network.d.ts +165 -0
- package/dist/lib/bidi/network.d.ts.map +1 -0
- package/dist/lib/bidi/network.js +477 -0
- package/dist/lib/bidi/network.js.map +1 -0
- package/dist/lib/bidi/storage.d.ts +83 -0
- package/dist/lib/bidi/storage.d.ts.map +1 -0
- package/dist/lib/bidi/storage.js +299 -0
- package/dist/lib/bidi/storage.js.map +1 -0
- package/dist/lib/bidi/types.d.ts +323 -0
- package/dist/lib/bidi/types.d.ts.map +1 -0
- package/dist/lib/bidi/types.js +6 -0
- package/dist/lib/bidi/types.js.map +1 -0
- package/dist/lib/browser.d.ts +776 -13
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +1547 -70
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +486 -0
- package/dist/lib/browserContext.d.ts.map +1 -0
- package/dist/lib/browserContext.js +937 -0
- package/dist/lib/browserContext.js.map +1 -0
- package/dist/lib/builder.d.ts +3 -0
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +31 -4
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/by.d.ts +18 -2
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +20 -2
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/chrome.d.ts +13 -1
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +13 -17
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/clock.d.ts +115 -0
- package/dist/lib/clock.d.ts.map +1 -0
- package/dist/lib/clock.js +407 -0
- package/dist/lib/clock.js.map +1 -0
- package/dist/lib/driver.d.ts +45 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +129 -1
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +25 -0
- package/dist/lib/driverManager.d.ts.map +1 -0
- package/dist/lib/driverManager.js +399 -0
- package/dist/lib/driverManager.js.map +1 -0
- package/dist/lib/elementHandle.d.ts +69 -2
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +215 -40
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +71 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +73 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/expect.d.ts +6 -1
- package/dist/lib/expect.d.ts.map +1 -1
- package/dist/lib/expect.js +55 -37
- package/dist/lib/expect.js.map +1 -1
- package/dist/lib/firefox.d.ts +18 -0
- package/dist/lib/firefox.d.ts.map +1 -0
- package/dist/lib/firefox.js +33 -0
- package/dist/lib/firefox.js.map +1 -0
- package/dist/lib/frame.d.ts +57 -0
- package/dist/lib/frame.d.ts.map +1 -0
- package/dist/lib/frame.js +277 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/keyboard.d.ts +1 -1
- package/dist/lib/keyboard.d.ts.map +1 -1
- package/dist/lib/keyboard.js +1 -1
- package/dist/lib/keyboard.js.map +1 -1
- package/dist/lib/locator.d.ts +71 -2
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +289 -31
- package/dist/lib/locator.js.map +1 -1
- package/dist/lib/mouse.d.ts +1 -1
- package/dist/lib/mouse.d.ts.map +1 -1
- package/dist/lib/mouse.js +1 -1
- package/dist/lib/mouse.js.map +1 -1
- package/dist/lib/page.d.ts +107 -0
- package/dist/lib/page.d.ts.map +1 -0
- package/dist/lib/page.js +388 -0
- package/dist/lib/page.js.map +1 -0
- package/dist/lib/tracing.d.ts +128 -0
- package/dist/lib/tracing.d.ts.map +1 -0
- package/dist/lib/tracing.js +272 -0
- package/dist/lib/tracing.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +14 -1
- package/dist/lib/wait.js.map +1 -1
- package/dist/lib/webelement.d.ts +2 -0
- package/dist/lib/webelement.d.ts.map +1 -1
- package/dist/lib/webelement.js +18 -0
- package/dist/lib/webelement.js.map +1 -1
- package/docs/accessibility.md +177 -0
- package/docs/api-reference.md +71 -0
- package/docs/assertions.md +182 -0
- package/docs/bidi-features.md +462 -0
- package/docs/browser-api.md +673 -0
- package/docs/browser-context.md +522 -0
- package/docs/cli.md +233 -0
- package/docs/clock.md +245 -0
- package/docs/dialogs.md +143 -0
- package/docs/driver-configuration.md +64 -0
- package/docs/element-api.md +219 -0
- package/docs/emulation.md +155 -0
- package/docs/error-codes.md +67 -0
- package/docs/getting-started.md +168 -0
- package/docs/keyboard-mouse.md +250 -0
- package/docs/mcp.md +250 -0
- package/docs/mobile-emulation.md +194 -0
- package/docs/screenshots.md +146 -0
- package/docs/selectors.md +382 -0
- package/docs/session-management.md +229 -0
- package/docs/tracing.md +282 -0
- package/package.json +25 -5
- package/skills/craftdriver/SKILL.md +84 -0
- package/skills/craftdriver/cheatsheet.md +194 -0
- package/skills/craftdriver/cli.md +104 -0
- 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 {
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,
|
|
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 {
|
|
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,
|
|
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"}
|
package/dist/lib/a11y.js
ADDED
|
@@ -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
|