@noego/wood 0.4.9 → 0.4.10
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/bin/axe.js +313 -98
- package/dist/automation/actions.cjs +17 -0
- package/dist/automation/actions.cjs.map +1 -0
- package/dist/automation/actions.d.cts +57 -0
- package/dist/automation/actions.d.ts +57 -0
- package/dist/automation/actions.js +1 -0
- package/dist/automation/actions.js.map +1 -0
- package/dist/automation/actions_executor.cjs +101 -0
- package/dist/automation/actions_executor.cjs.map +1 -0
- package/dist/automation/actions_executor.d.cts +16 -0
- package/dist/automation/actions_executor.d.ts +16 -0
- package/dist/automation/actions_executor.js +76 -0
- package/dist/automation/actions_executor.js.map +1 -0
- package/dist/automation/connection.cjs +86 -0
- package/dist/automation/connection.cjs.map +1 -0
- package/dist/automation/connection.d.cts +18 -0
- package/dist/automation/connection.d.ts +18 -0
- package/dist/automation/connection.js +51 -0
- package/dist/automation/connection.js.map +1 -0
- package/dist/automation/errors.cjs +50 -0
- package/dist/automation/errors.cjs.map +1 -0
- package/dist/automation/errors.d.cts +31 -0
- package/dist/automation/errors.d.ts +31 -0
- package/dist/automation/errors.js +24 -0
- package/dist/automation/errors.js.map +1 -0
- package/dist/automation/index.cjs +49 -0
- package/dist/automation/index.cjs.map +1 -0
- package/dist/automation/index.d.cts +16 -0
- package/dist/automation/index.d.ts +16 -0
- package/dist/automation/index.js +15 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/introspection_reader.cjs +75 -0
- package/dist/automation/introspection_reader.cjs.map +1 -0
- package/dist/automation/introspection_reader.d.cts +20 -0
- package/dist/automation/introspection_reader.d.ts +20 -0
- package/dist/automation/introspection_reader.js +48 -0
- package/dist/automation/introspection_reader.js.map +1 -0
- package/dist/automation/locator.cjs +102 -0
- package/dist/automation/locator.cjs.map +1 -0
- package/dist/automation/locator.d.cts +51 -0
- package/dist/automation/locator.d.ts +51 -0
- package/dist/automation/locator.js +75 -0
- package/dist/automation/locator.js.map +1 -0
- package/dist/automation/observation.cjs +17 -0
- package/dist/automation/observation.cjs.map +1 -0
- package/dist/automation/observation.d.cts +44 -0
- package/dist/automation/observation.d.ts +44 -0
- package/dist/automation/observation.js +1 -0
- package/dist/automation/observation.js.map +1 -0
- package/dist/automation/receipts.cjs +17 -0
- package/dist/automation/receipts.cjs.map +1 -0
- package/dist/automation/receipts.d.cts +27 -0
- package/dist/automation/receipts.d.ts +27 -0
- package/dist/automation/receipts.js +1 -0
- package/dist/automation/receipts.js.map +1 -0
- package/dist/automation/scenario.cjs +696 -0
- package/dist/automation/scenario.cjs.map +1 -0
- package/dist/automation/scenario.d.cts +89 -0
- package/dist/automation/scenario.d.ts +89 -0
- package/dist/automation/scenario.js +660 -0
- package/dist/automation/scenario.js.map +1 -0
- package/dist/automation/scrolling.cjs +17 -0
- package/dist/automation/scrolling.cjs.map +1 -0
- package/dist/automation/scrolling.d.cts +51 -0
- package/dist/automation/scrolling.d.ts +51 -0
- package/dist/automation/scrolling.js +1 -0
- package/dist/automation/scrolling.js.map +1 -0
- package/dist/automation/scrolling_executor.cjs +109 -0
- package/dist/automation/scrolling_executor.cjs.map +1 -0
- package/dist/automation/scrolling_executor.d.cts +13 -0
- package/dist/automation/scrolling_executor.d.ts +13 -0
- package/dist/automation/scrolling_executor.js +84 -0
- package/dist/automation/scrolling_executor.js.map +1 -0
- package/dist/automation/target_identity.cjs +17 -0
- package/dist/automation/target_identity.cjs.map +1 -0
- package/dist/automation/target_identity.d.cts +28 -0
- package/dist/automation/target_identity.d.ts +28 -0
- package/dist/automation/target_identity.js +1 -0
- package/dist/automation/target_identity.js.map +1 -0
- package/dist/automation/target_registry.cjs +148 -0
- package/dist/automation/target_registry.cjs.map +1 -0
- package/dist/automation/target_registry.d.cts +32 -0
- package/dist/automation/target_registry.d.ts +32 -0
- package/dist/automation/target_registry.js +122 -0
- package/dist/automation/target_registry.js.map +1 -0
- package/dist/automation/waits.cjs +17 -0
- package/dist/automation/waits.cjs.map +1 -0
- package/dist/automation/waits.d.cts +44 -0
- package/dist/automation/waits.d.ts +44 -0
- package/dist/automation/waits.js +1 -0
- package/dist/automation/waits.js.map +1 -0
- package/dist/automation/waits_executor.cjs +104 -0
- package/dist/automation/waits_executor.cjs.map +1 -0
- package/dist/automation/waits_executor.d.cts +14 -0
- package/dist/automation/waits_executor.d.ts +14 -0
- package/dist/automation/waits_executor.js +79 -0
- package/dist/automation/waits_executor.js.map +1 -0
- package/dist/client/index.d.cts +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/codegen/renderer_app_generator.cjs +16 -1
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +16 -1
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/navigation/automation_introspection.cjs +110 -0
- package/dist/navigation/automation_introspection.cjs.map +1 -0
- package/dist/navigation/automation_introspection.d.cts +60 -0
- package/dist/navigation/automation_introspection.d.ts +60 -0
- package/dist/navigation/automation_introspection.js +85 -0
- package/dist/navigation/automation_introspection.js.map +1 -0
- package/dist/navigation/index.cjs +7 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +4 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/testing/automation_assertions.cjs +417 -0
- package/dist/testing/automation_assertions.cjs.map +1 -0
- package/dist/testing/automation_assertions.d.cts +44 -0
- package/dist/testing/automation_assertions.d.ts +44 -0
- package/dist/testing/automation_assertions.js +385 -0
- package/dist/testing/automation_assertions.js.map +1 -0
- package/dist/testing/browser.cjs +26 -34
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +2 -1
- package/dist/testing/browser.d.ts +2 -1
- package/dist/testing/browser.js +30 -31
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +1 -0
- package/dist/testing/create_controller_harness.d.ts +1 -0
- package/dist/testing/geometry.cjs +109 -0
- package/dist/testing/geometry.cjs.map +1 -0
- package/dist/testing/geometry.d.cts +45 -0
- package/dist/testing/geometry.d.ts +45 -0
- package/dist/testing/geometry.js +79 -0
- package/dist/testing/geometry.js.map +1 -0
- package/dist/testing/index.cjs +31 -2
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +4 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +31 -1
- package/dist/testing/index.js.map +1 -1
- package/package.json +7 -1
package/bin/axe.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Thin CLI adapter over @noego/wood/automation (AXE-WP-01).
|
|
4
|
+
// Argument parsing and presentation only — connection, target resolution,
|
|
5
|
+
// locator handling, and error classification live in typed source.
|
|
6
|
+
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
3
8
|
import path from 'node:path';
|
|
4
9
|
import process from 'node:process';
|
|
5
|
-
import { readFileSync } from 'node:fs';
|
|
6
10
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
11
|
|
|
8
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -10,53 +14,95 @@ const __dirname = path.dirname(__filename);
|
|
|
10
14
|
const distDir = path.resolve(__dirname, '..', 'dist');
|
|
11
15
|
|
|
12
16
|
// Node's ESM loader requires file:// URLs for absolute paths on Windows;
|
|
13
|
-
// raw `C
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const INTERNAL_BRIDGE_KEY = '__NOEGO_WOOD_BRIDGE__';
|
|
17
|
+
// raw `C:\\...` strings throw ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
18
|
+
const automation = await import(pathToFileURL(path.join(distDir, 'automation', 'index.js')).href);
|
|
17
19
|
|
|
18
20
|
function printUsage() {
|
|
19
21
|
console.log('Usage: axe <command> [options]');
|
|
20
22
|
console.log('');
|
|
21
23
|
console.log('Commands:');
|
|
22
|
-
console.log(' targets
|
|
23
|
-
console.log('
|
|
24
|
-
console.log('
|
|
25
|
-
console.log('
|
|
26
|
-
console.log('
|
|
24
|
+
console.log(' targets List attachable Wood windows');
|
|
25
|
+
console.log(' run <scenario.json> | run --stdin Run a persistent version-1 scenario');
|
|
26
|
+
console.log(' observe [--window <id>] Print a JSON target/route observation');
|
|
27
|
+
console.log(' route [--window <id>] Print the current route or null');
|
|
28
|
+
console.log(' wait --visible <locator> [--timeout <ms>] Wait for an element to be visible');
|
|
29
|
+
console.log(' wait --hidden <locator> [--timeout <ms>] Wait for an element to be hidden');
|
|
30
|
+
console.log(' wait --route <page> [--timeout <ms>] Wait for the current route');
|
|
31
|
+
console.log(' wait --url <value> [--timeout <ms>] Wait for an exact URL');
|
|
32
|
+
console.log(' scroll [--viewport|--container <locator>] --by <dx,dy>');
|
|
33
|
+
console.log(' scroll [--viewport|--container <locator>] --to <top|bottom|left|right>');
|
|
34
|
+
console.log(' scroll [--viewport|--container <locator>] --until <locator> [--step-px <n>] [--max-steps <n>]');
|
|
35
|
+
console.log(' scroll --into-view <locator> Scroll an element into view');
|
|
36
|
+
console.log(' click <locator> [--then-visible <locator>] [--then-route <page>]');
|
|
37
|
+
console.log(' type <locator> <text> [--then-visible <locator>] [--then-route <page>]');
|
|
38
|
+
console.log(' fill <locator> --value <text> Fill an element (Playwright fill semantics)');
|
|
39
|
+
console.log(' press --keys <k1,k2> Press one or more key chords');
|
|
40
|
+
console.log(' debug js [--window <windowId>] Run JavaScript from stdin (explicit UI bypass; alias: js)');
|
|
41
|
+
console.log(' screenshot [path] [--window <id>] Save a screenshot');
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log('Global options: --window <windowId> --json');
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log('Locators: bare strings are CSS; prefixes select semantic locators:');
|
|
46
|
+
console.log(' test_id=<id> role=<role>[:<name>] label=<text> placeholder=<text> text=<text> css=<selector>');
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log('When multiple Wood windows are open, --window <windowId> is required.');
|
|
27
49
|
}
|
|
28
50
|
|
|
51
|
+
const VALUE_FLAGS = new Set([
|
|
52
|
+
'--window',
|
|
53
|
+
'--visible',
|
|
54
|
+
'--hidden',
|
|
55
|
+
'--route',
|
|
56
|
+
'--url',
|
|
57
|
+
'--timeout',
|
|
58
|
+
'--container',
|
|
59
|
+
'--by',
|
|
60
|
+
'--to',
|
|
61
|
+
'--until',
|
|
62
|
+
'--step-px',
|
|
63
|
+
'--max-steps',
|
|
64
|
+
'--into-view',
|
|
65
|
+
'--then-visible',
|
|
66
|
+
'--then-route',
|
|
67
|
+
'--keys',
|
|
68
|
+
'--value',
|
|
69
|
+
]);
|
|
70
|
+
|
|
29
71
|
function parseArgs(argv) {
|
|
30
72
|
const positional = [];
|
|
73
|
+
const flags = new Map();
|
|
31
74
|
let windowId = null;
|
|
75
|
+
let json = false;
|
|
32
76
|
|
|
33
77
|
for (let i = 0; i < argv.length; i += 1) {
|
|
34
78
|
const arg = argv[i];
|
|
35
|
-
if (arg === '--
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
if (arg === '--json') {
|
|
80
|
+
json = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (arg === '--stdin' || arg === '--viewport') {
|
|
84
|
+
flags.set(arg, true);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
88
|
+
const value = argv[i + 1];
|
|
89
|
+
if (value == null) {
|
|
90
|
+
flags.set(arg, null);
|
|
91
|
+
} else {
|
|
92
|
+
flags.set(arg, value);
|
|
93
|
+
i += 1;
|
|
94
|
+
}
|
|
95
|
+
if (arg === '--window') windowId = value ?? null;
|
|
38
96
|
continue;
|
|
39
97
|
}
|
|
40
98
|
positional.push(arg);
|
|
41
99
|
}
|
|
42
100
|
|
|
43
|
-
return { positional, windowId };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function loadPlaywright() {
|
|
47
|
-
try {
|
|
48
|
-
return await import('playwright');
|
|
49
|
-
} catch (error) {
|
|
50
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
-
throw new Error(`axe requires Playwright to be installed in this environment. ${message}`);
|
|
52
|
-
}
|
|
101
|
+
return { positional, flags, windowId, json };
|
|
53
102
|
}
|
|
54
103
|
|
|
55
104
|
async function readStdin() {
|
|
56
|
-
if (process.stdin.isTTY)
|
|
57
|
-
return '';
|
|
58
|
-
}
|
|
59
|
-
|
|
105
|
+
if (process.stdin.isTTY) return '';
|
|
60
106
|
const chunks = [];
|
|
61
107
|
for await (const chunk of process.stdin) {
|
|
62
108
|
chunks.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
|
|
@@ -64,63 +110,163 @@ async function readStdin() {
|
|
|
64
110
|
return chunks.join('');
|
|
65
111
|
}
|
|
66
112
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function listTargets(browser) {
|
|
73
|
-
const results = [];
|
|
74
|
-
for (const context of browser.contexts()) {
|
|
75
|
-
for (const page of context.pages()) {
|
|
76
|
-
let currentWindow = null;
|
|
77
|
-
try {
|
|
78
|
-
currentWindow = await page.evaluate(async (bridgeKey) => {
|
|
79
|
-
const bridge = globalThis[bridgeKey];
|
|
80
|
-
if (!bridge || typeof bridge !== 'object') {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
const windowApi = bridge.__window;
|
|
84
|
-
if (!windowApi || typeof windowApi.current !== 'function') {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
return await windowApi.current();
|
|
88
|
-
}, INTERNAL_BRIDGE_KEY);
|
|
89
|
-
} catch {
|
|
90
|
-
currentWindow = null;
|
|
91
|
-
}
|
|
113
|
+
function fail(error) {
|
|
114
|
+
console.error(`[axe] ${automation.describeAutomationError(error)}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
92
117
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
function unwrap(result) {
|
|
119
|
+
if (!result.ok) fail(result.error);
|
|
120
|
+
return result.value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function flagValue(flags, name) {
|
|
124
|
+
const value = flags.get(name);
|
|
125
|
+
if (typeof value !== 'string') {
|
|
126
|
+
fail({ code: 'ACTION_FAILED', message: `${name} requires a value.` });
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function optionalPositiveNumber(flags, name, fallback) {
|
|
132
|
+
if (!flags.has(name)) return fallback;
|
|
133
|
+
const raw = flagValue(flags, name);
|
|
134
|
+
const value = Number(raw);
|
|
135
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
136
|
+
fail({ code: 'ACTION_FAILED', message: `${name} must be a positive number.` });
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function locator(raw) {
|
|
142
|
+
return unwrap(automation.parseCliLocator(raw));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function resolveTargetPage(browser, windowId) {
|
|
146
|
+
const entries = await automation.discoverTargets(browser);
|
|
147
|
+
return unwrap(automation.selectTarget(entries, windowId));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function printReceipt(receipt, json, human) {
|
|
151
|
+
process.stdout.write(json ? `${JSON.stringify(receipt)}\n` : `${human}\n`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function postconditions(flags) {
|
|
155
|
+
const conditions = [];
|
|
156
|
+
if (flags.has('--then-route')) {
|
|
157
|
+
conditions.push({ kind: 'route', page: flagValue(flags, '--then-route') });
|
|
158
|
+
}
|
|
159
|
+
if (flags.has('--then-visible')) {
|
|
160
|
+
conditions.push({ kind: 'visible', target: locator(flagValue(flags, '--then-visible')) });
|
|
161
|
+
}
|
|
162
|
+
return conditions;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function executeWithPostconditions(page, action, flags) {
|
|
166
|
+
const conditions = postconditions(flags);
|
|
167
|
+
const result = await automation.executeAction(page, action, {
|
|
168
|
+
...(conditions[0] ? { wait: conditions[0] } : {}),
|
|
169
|
+
});
|
|
170
|
+
const receipt = unwrap(result);
|
|
171
|
+
for (const condition of conditions.slice(1)) {
|
|
172
|
+
unwrap(await automation.awaitCondition(page, condition, { timeoutMs: 5_000 }));
|
|
173
|
+
}
|
|
174
|
+
return receipt;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function scrollContainer(flags) {
|
|
178
|
+
if (flags.has('--container')) {
|
|
179
|
+
return { kind: 'locator', target: locator(flagValue(flags, '--container')) };
|
|
101
180
|
}
|
|
102
|
-
return
|
|
181
|
+
return { kind: 'viewport' };
|
|
103
182
|
}
|
|
104
183
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
throw new Error('No renderer targets found in the running Wood app.');
|
|
184
|
+
function scrollCommand(flags) {
|
|
185
|
+
if (flags.has('--into-view')) {
|
|
186
|
+
return { mode: 'into_view', target: locator(flagValue(flags, '--into-view')) };
|
|
109
187
|
}
|
|
110
188
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
189
|
+
const container = scrollContainer(flags);
|
|
190
|
+
if (flags.has('--by')) {
|
|
191
|
+
const raw = flagValue(flags, '--by');
|
|
192
|
+
const parts = raw.split(',');
|
|
193
|
+
const deltaX = Number(parts[0]);
|
|
194
|
+
const deltaY = Number(parts[1]);
|
|
195
|
+
if (parts.length !== 2 || !Number.isFinite(deltaX) || !Number.isFinite(deltaY)) {
|
|
196
|
+
fail({ code: 'ACTION_FAILED', message: '--by must be two finite numbers formatted as dx,dy.' });
|
|
197
|
+
}
|
|
198
|
+
return { mode: 'wheel', container, deltaX, deltaY };
|
|
199
|
+
}
|
|
200
|
+
if (flags.has('--to')) {
|
|
201
|
+
const edge = flagValue(flags, '--to');
|
|
202
|
+
if (!['top', 'bottom', 'left', 'right'].includes(edge)) {
|
|
203
|
+
fail({ code: 'ACTION_FAILED', message: '--to must be top, bottom, left, or right.' });
|
|
115
204
|
}
|
|
116
|
-
return
|
|
205
|
+
return { mode: 'position', container, edge };
|
|
206
|
+
}
|
|
207
|
+
if (flags.has('--until')) {
|
|
208
|
+
return {
|
|
209
|
+
mode: 'until',
|
|
210
|
+
container,
|
|
211
|
+
target: locator(flagValue(flags, '--until')),
|
|
212
|
+
direction: 'down',
|
|
213
|
+
stepPx: optionalPositiveNumber(flags, '--step-px', 400),
|
|
214
|
+
maxSteps: optionalPositiveNumber(flags, '--max-steps', 20),
|
|
215
|
+
};
|
|
117
216
|
}
|
|
217
|
+
fail({ code: 'ACTION_FAILED', message: 'axe scroll requires exactly one of --by, --to, --until, or --into-view.' });
|
|
218
|
+
}
|
|
118
219
|
|
|
119
|
-
|
|
220
|
+
function waitCondition(flags) {
|
|
221
|
+
const choices = ['--visible', '--hidden', '--route', '--url'].filter((name) => flags.has(name));
|
|
222
|
+
if (choices.length !== 1) {
|
|
223
|
+
fail({ code: 'ACTION_FAILED', message: 'axe wait requires exactly one of --visible, --hidden, --route, or --url.' });
|
|
224
|
+
}
|
|
225
|
+
const choice = choices[0];
|
|
226
|
+
const value = flagValue(flags, choice);
|
|
227
|
+
if (choice === '--visible' || choice === '--hidden') {
|
|
228
|
+
return { kind: choice.slice(2), target: locator(value) };
|
|
229
|
+
}
|
|
230
|
+
if (choice === '--route') return { kind: 'route', page: value };
|
|
231
|
+
return { kind: 'url', value };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runScenarioCommand(connection, positional, flags) {
|
|
235
|
+
const fromStdin = flags.has('--stdin');
|
|
236
|
+
const fileName = positional[1];
|
|
237
|
+
if (fromStdin === Boolean(fileName)) {
|
|
238
|
+
fail({ code: 'ACTION_FAILED', message: 'axe run requires exactly one scenario file or --stdin.' });
|
|
239
|
+
}
|
|
240
|
+
let source;
|
|
241
|
+
try {
|
|
242
|
+
source = fromStdin ? await readStdin() : await readFile(path.resolve(process.cwd(), fileName), 'utf8');
|
|
243
|
+
} catch (error) {
|
|
244
|
+
fail({
|
|
245
|
+
code: 'ACTION_FAILED',
|
|
246
|
+
message: 'Could not read scenario JSON.',
|
|
247
|
+
details: { cause: error instanceof Error ? error.message : String(error) },
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
let json;
|
|
251
|
+
try {
|
|
252
|
+
json = JSON.parse(source);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
fail({
|
|
255
|
+
code: 'ACTION_FAILED',
|
|
256
|
+
message: 'Scenario input is not valid JSON.',
|
|
257
|
+
details: { cause: error instanceof Error ? error.message : String(error) },
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const scenario = unwrap(automation.parseScenario(json));
|
|
261
|
+
const result = await automation.runScenario(connection, scenario, {
|
|
262
|
+
onEvent: (event) => process.stdout.write(`${JSON.stringify(event)}\n`),
|
|
263
|
+
evidenceDir: path.resolve(process.cwd(), 'axe-evidence'),
|
|
264
|
+
});
|
|
265
|
+
if (!result.ok) fail(result.error);
|
|
120
266
|
}
|
|
121
267
|
|
|
122
268
|
async function main() {
|
|
123
|
-
const { positional, windowId } = parseArgs(process.argv.slice(2));
|
|
269
|
+
const { positional, flags, windowId, json } = parseArgs(process.argv.slice(2));
|
|
124
270
|
const command = positional[0];
|
|
125
271
|
|
|
126
272
|
if (!command) {
|
|
@@ -128,57 +274,126 @@ async function main() {
|
|
|
128
274
|
process.exit(0);
|
|
129
275
|
}
|
|
130
276
|
|
|
131
|
-
const
|
|
132
|
-
const browser = await connectBrowser(metadata.browserUrl);
|
|
277
|
+
const connection = unwrap(await automation.connectToWoodApp(process.cwd()));
|
|
133
278
|
|
|
134
279
|
try {
|
|
135
280
|
if (command === 'targets') {
|
|
136
|
-
const
|
|
137
|
-
const lines =
|
|
281
|
+
const entries = await automation.discoverTargets(connection.browser);
|
|
282
|
+
const lines = entries.map((entry, index) => JSON.stringify({
|
|
283
|
+
// index is diagnostic only — windowId/targetId are the identity contract.
|
|
138
284
|
index,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
285
|
+
targetId: entry.target.targetId,
|
|
286
|
+
windowId: entry.target.windowId,
|
|
287
|
+
defaultRoute: entry.target.configuredDefaultRoute ?? null,
|
|
288
|
+
eligible: entry.target.eligible,
|
|
289
|
+
title: entry.target.title,
|
|
290
|
+
url: entry.target.url,
|
|
143
291
|
}));
|
|
144
292
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
145
293
|
return;
|
|
146
294
|
}
|
|
147
295
|
|
|
148
|
-
|
|
149
|
-
|
|
296
|
+
if (command === 'run') {
|
|
297
|
+
await runScenarioCommand(connection, positional, flags);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const { page, target } = await resolveTargetPage(connection.browser, windowId);
|
|
302
|
+
|
|
303
|
+
if (command === 'observe') {
|
|
304
|
+
process.stdout.write(`${JSON.stringify({ target, route: target.currentRoute ?? null })}\n`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (command === 'route') {
|
|
309
|
+
process.stdout.write(`${target.currentRoute ?? 'null'}\n`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (command === 'wait') {
|
|
314
|
+
const condition = waitCondition(flags);
|
|
315
|
+
const timeoutMs = optionalPositiveNumber(flags, '--timeout', 5_000);
|
|
316
|
+
const waited = unwrap(await automation.awaitCondition(page, condition, { timeoutMs }));
|
|
317
|
+
process.stdout.write(json ? `${JSON.stringify(waited)}\n` : `condition satisfied after ${waited.waitedMs}ms\n`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (command === 'scroll') {
|
|
322
|
+
const receipt = unwrap(await automation.executeAction(page, { kind: 'scroll', command: scrollCommand(flags) }));
|
|
323
|
+
printReceipt(receipt, json, 'scrolled');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
150
326
|
|
|
151
|
-
if (command === 'js') {
|
|
327
|
+
if (command === 'js' || (command === 'debug' && positional[1] === 'js')) {
|
|
152
328
|
const source = await readStdin();
|
|
153
329
|
if (!source.trim()) {
|
|
154
|
-
|
|
330
|
+
fail({ code: 'ACTION_FAILED', message: 'axe debug js expects JavaScript via stdin.' });
|
|
155
331
|
}
|
|
332
|
+
// Explicit diagnostic escape hatch: bypasses the real UI path (bypassedUi: true)
|
|
333
|
+
// and is excluded from release qualification scenarios.
|
|
334
|
+
process.stderr.write('[axe] debug js is a UI bypass (bypassedUi: true); not valid release-qualification evidence.\n');
|
|
156
335
|
const result = await page.evaluate(async (userSource) => {
|
|
157
336
|
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
158
|
-
|
|
337
|
+
// Bare expressions ("2+2", "document.title") are not valid as a
|
|
338
|
+
// function *body* result — wrap them in an implicit return first;
|
|
339
|
+
// fall back to body form for statement programs ("const x = ...; return x").
|
|
340
|
+
let fn;
|
|
341
|
+
try {
|
|
342
|
+
fn = new AsyncFunction(`return (${userSource}\n);`);
|
|
343
|
+
} catch {
|
|
344
|
+
fn = new AsyncFunction(userSource);
|
|
345
|
+
}
|
|
346
|
+
return await fn();
|
|
159
347
|
}, source);
|
|
160
348
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
161
349
|
return;
|
|
162
350
|
}
|
|
163
351
|
|
|
164
352
|
if (command === 'click') {
|
|
165
|
-
const
|
|
166
|
-
if (!
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
process.stdout.write(`clicked ${selector}\n`);
|
|
353
|
+
const rawLocator = positional[1];
|
|
354
|
+
if (!rawLocator) fail({ code: 'LOCATOR_INVALID', message: 'axe click requires a locator.' });
|
|
355
|
+
const targetLocator = locator(rawLocator);
|
|
356
|
+
const receipt = await executeWithPostconditions(page, { kind: 'click', target: targetLocator }, flags);
|
|
357
|
+
printReceipt(receipt, json, `clicked ${automation.describeLocator(targetLocator)}`);
|
|
171
358
|
return;
|
|
172
359
|
}
|
|
173
360
|
|
|
174
361
|
if (command === 'type') {
|
|
175
|
-
const
|
|
362
|
+
const rawLocator = positional[1];
|
|
176
363
|
const text = positional[2];
|
|
177
|
-
if (!
|
|
178
|
-
|
|
364
|
+
if (!rawLocator || text == null) {
|
|
365
|
+
fail({ code: 'LOCATOR_INVALID', message: 'axe type requires a locator and text.' });
|
|
366
|
+
}
|
|
367
|
+
const targetLocator = locator(rawLocator);
|
|
368
|
+
const receipt = await executeWithPostconditions(
|
|
369
|
+
page,
|
|
370
|
+
{ kind: 'type', target: targetLocator, value: text },
|
|
371
|
+
flags,
|
|
372
|
+
);
|
|
373
|
+
printReceipt(receipt, json, `typed into ${automation.describeLocator(targetLocator)}`);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (command === 'fill') {
|
|
378
|
+
const rawLocator = positional[1];
|
|
379
|
+
if (!rawLocator || !flags.has('--value')) {
|
|
380
|
+
fail({ code: 'LOCATOR_INVALID', message: 'axe fill requires a locator and --value <text>.' });
|
|
179
381
|
}
|
|
180
|
-
|
|
181
|
-
|
|
382
|
+
const targetLocator = locator(rawLocator);
|
|
383
|
+
const receipt = unwrap(await automation.executeAction(page, {
|
|
384
|
+
kind: 'fill',
|
|
385
|
+
target: targetLocator,
|
|
386
|
+
value: flagValue(flags, '--value'),
|
|
387
|
+
}));
|
|
388
|
+
printReceipt(receipt, json, `filled ${automation.describeLocator(targetLocator)}`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (command === 'press') {
|
|
393
|
+
const keys = flagValue(flags, '--keys').split(',').map((key) => key.trim()).filter(Boolean);
|
|
394
|
+
if (keys.length === 0) fail({ code: 'ACTION_FAILED', message: '--keys must contain at least one key.' });
|
|
395
|
+
const receipt = unwrap(await automation.executeAction(page, { kind: 'press', keys }));
|
|
396
|
+
printReceipt(receipt, json, `pressed ${keys.join(', ')}`);
|
|
182
397
|
return;
|
|
183
398
|
}
|
|
184
399
|
|
|
@@ -191,9 +406,9 @@ async function main() {
|
|
|
191
406
|
return;
|
|
192
407
|
}
|
|
193
408
|
|
|
194
|
-
|
|
409
|
+
fail({ code: 'UNSUPPORTED', message: `Unknown axe command "${command}".` });
|
|
195
410
|
} finally {
|
|
196
|
-
await
|
|
411
|
+
await connection.close();
|
|
197
412
|
}
|
|
198
413
|
}
|
|
199
414
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var actions_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(actions_exports);
|
|
17
|
+
//# sourceMappingURL=actions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/automation/actions.ts"],"sourcesContent":["/**\n * Canonical action model (frozen — Axe.md \"Canonical action model\").\n * Type surface only in WP-00/01; the executor lands in AXE-WP-03.\n *\n * `fill` and `type` are distinct: `fill` sets the field value through\n * Playwright's fill semantics; `type` emits keyboard input.\n */\n\nimport type { WoodLocator } from './locator.cjs';\nimport type { WoodScrollCommand } from './scrolling.cjs';\nimport type { WoodWaitCondition } from './waits.cjs';\n\nexport type WoodAutomationAction =\n | { kind: 'click'; target: WoodLocator; button?: 'left' | 'middle' }\n | { kind: 'double_click'; target: WoodLocator }\n | { kind: 'fill'; target: WoodLocator; value: string }\n | { kind: 'type'; target: WoodLocator; value: string; delayMs?: number }\n | { kind: 'press'; target?: WoodLocator; keys: string[] }\n | { kind: 'select'; target: WoodLocator; option: string }\n | { kind: 'toggle'; target: WoodLocator; checked: boolean }\n | { kind: 'scroll'; command: WoodScrollCommand }\n | { kind: 'back' }\n | { kind: 'forward' }\n | { kind: 'reload' }\n | { kind: 'wait'; condition: WoodWaitCondition };\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { WoodLocator } from './locator.cjs';
|
|
2
|
+
import { WoodScrollCommand } from './scrolling.cjs';
|
|
3
|
+
import { WoodWaitCondition } from './waits.cjs';
|
|
4
|
+
import 'playwright';
|
|
5
|
+
import './errors.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Canonical action model (frozen — Axe.md "Canonical action model").
|
|
9
|
+
* Type surface only in WP-00/01; the executor lands in AXE-WP-03.
|
|
10
|
+
*
|
|
11
|
+
* `fill` and `type` are distinct: `fill` sets the field value through
|
|
12
|
+
* Playwright's fill semantics; `type` emits keyboard input.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type WoodAutomationAction = {
|
|
16
|
+
kind: 'click';
|
|
17
|
+
target: WoodLocator;
|
|
18
|
+
button?: 'left' | 'middle';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'double_click';
|
|
21
|
+
target: WoodLocator;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'fill';
|
|
24
|
+
target: WoodLocator;
|
|
25
|
+
value: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'type';
|
|
28
|
+
target: WoodLocator;
|
|
29
|
+
value: string;
|
|
30
|
+
delayMs?: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'press';
|
|
33
|
+
target?: WoodLocator;
|
|
34
|
+
keys: string[];
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'select';
|
|
37
|
+
target: WoodLocator;
|
|
38
|
+
option: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'toggle';
|
|
41
|
+
target: WoodLocator;
|
|
42
|
+
checked: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'scroll';
|
|
45
|
+
command: WoodScrollCommand;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'back';
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'forward';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'reload';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'wait';
|
|
54
|
+
condition: WoodWaitCondition;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type { WoodAutomationAction };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { WoodLocator } from './locator.js';
|
|
2
|
+
import { WoodScrollCommand } from './scrolling.js';
|
|
3
|
+
import { WoodWaitCondition } from './waits.js';
|
|
4
|
+
import 'playwright';
|
|
5
|
+
import './errors.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Canonical action model (frozen — Axe.md "Canonical action model").
|
|
9
|
+
* Type surface only in WP-00/01; the executor lands in AXE-WP-03.
|
|
10
|
+
*
|
|
11
|
+
* `fill` and `type` are distinct: `fill` sets the field value through
|
|
12
|
+
* Playwright's fill semantics; `type` emits keyboard input.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type WoodAutomationAction = {
|
|
16
|
+
kind: 'click';
|
|
17
|
+
target: WoodLocator;
|
|
18
|
+
button?: 'left' | 'middle';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'double_click';
|
|
21
|
+
target: WoodLocator;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'fill';
|
|
24
|
+
target: WoodLocator;
|
|
25
|
+
value: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'type';
|
|
28
|
+
target: WoodLocator;
|
|
29
|
+
value: string;
|
|
30
|
+
delayMs?: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'press';
|
|
33
|
+
target?: WoodLocator;
|
|
34
|
+
keys: string[];
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'select';
|
|
37
|
+
target: WoodLocator;
|
|
38
|
+
option: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'toggle';
|
|
41
|
+
target: WoodLocator;
|
|
42
|
+
checked: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'scroll';
|
|
45
|
+
command: WoodScrollCommand;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'back';
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'forward';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'reload';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'wait';
|
|
54
|
+
condition: WoodWaitCondition;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type { WoodAutomationAction };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|