@a11y-oracle/core-engine 1.0.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/README.md +473 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/lib/a11y-orchestrator.d.ts +112 -0
- package/dist/lib/a11y-orchestrator.d.ts.map +1 -0
- package/dist/lib/a11y-orchestrator.js +170 -0
- package/dist/lib/role-map.d.ts +41 -0
- package/dist/lib/role-map.d.ts.map +1 -0
- package/dist/lib/role-map.js +128 -0
- package/dist/lib/speech-engine.d.ts +149 -0
- package/dist/lib/speech-engine.d.ts.map +1 -0
- package/dist/lib/speech-engine.js +219 -0
- package/dist/lib/state-map.d.ts +82 -0
- package/dist/lib/state-map.d.ts.map +1 -0
- package/dist/lib/state-map.js +86 -0
- package/dist/lib/types.d.ts +162 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +10 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# @a11y-oracle/core-engine
|
|
2
|
+
|
|
3
|
+
Framework-agnostic accessibility engine for A11y-Oracle. Provides two main APIs:
|
|
4
|
+
|
|
5
|
+
1. **`SpeechEngine`** — Reads the browser's Accessibility Tree via CDP and generates standardized speech output.
|
|
6
|
+
2. **`A11yOrchestrator`** — Unified orchestrator that combines speech, keyboard dispatch, and focus indicator analysis into a single `pressKey()` call.
|
|
7
|
+
|
|
8
|
+
This package is the foundation that the Playwright and Cypress plugins build on. You can also use it directly with any CDP-compatible client.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @a11y-oracle/core-engine
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### SpeechEngine (Speech Only)
|
|
19
|
+
|
|
20
|
+
The speech engine operates through a `CDPSessionLike` interface, which any CDP client can satisfy:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { SpeechEngine } from '@a11y-oracle/core-engine';
|
|
24
|
+
|
|
25
|
+
// Works with Playwright's CDPSession
|
|
26
|
+
const cdpSession = await page.context().newCDPSession(page);
|
|
27
|
+
const engine = new SpeechEngine(cdpSession);
|
|
28
|
+
await engine.enable();
|
|
29
|
+
|
|
30
|
+
// Get speech for the currently focused element
|
|
31
|
+
const result = await engine.getSpeech();
|
|
32
|
+
console.log(result?.speech); // "Products, button, collapsed"
|
|
33
|
+
|
|
34
|
+
// Get speech for every visible element
|
|
35
|
+
const all = await engine.getFullTreeSpeech();
|
|
36
|
+
const nav = all.find(r => r.speech.includes('navigation landmark'));
|
|
37
|
+
|
|
38
|
+
// Clean up
|
|
39
|
+
await engine.disable();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### A11yOrchestrator (Unified State)
|
|
43
|
+
|
|
44
|
+
The orchestrator coordinates the speech engine, keyboard engine, and focus analyzer:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { A11yOrchestrator } from '@a11y-oracle/core-engine';
|
|
48
|
+
|
|
49
|
+
const cdpSession = await page.context().newCDPSession(page);
|
|
50
|
+
const orchestrator = new A11yOrchestrator(cdpSession);
|
|
51
|
+
await orchestrator.enable();
|
|
52
|
+
|
|
53
|
+
// Press a key and get unified state
|
|
54
|
+
const state = await orchestrator.pressKey('Tab');
|
|
55
|
+
console.log(state.speech); // "Products, button, collapsed"
|
|
56
|
+
console.log(state.focusedElement?.tag); // "BUTTON"
|
|
57
|
+
console.log(state.focusIndicator.meetsWCAG_AA); // true
|
|
58
|
+
|
|
59
|
+
// Get state without pressing a key
|
|
60
|
+
const current = await orchestrator.getState();
|
|
61
|
+
|
|
62
|
+
// Tab order extraction
|
|
63
|
+
const report = await orchestrator.traverseTabOrder();
|
|
64
|
+
console.log(report.totalCount); // 12
|
|
65
|
+
|
|
66
|
+
// Keyboard trap detection (WCAG 2.1.2)
|
|
67
|
+
const result = await orchestrator.traverseSubTree('#modal', 20);
|
|
68
|
+
console.log(result.isTrapped); // false
|
|
69
|
+
|
|
70
|
+
await orchestrator.disable();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Configuration Options
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const engine = new SpeechEngine(cdpSession, {
|
|
77
|
+
// Include "landmark" suffix on landmark roles (default: true)
|
|
78
|
+
// true: "Main, navigation landmark"
|
|
79
|
+
// false: "Main, navigation"
|
|
80
|
+
includeLandmarks: true,
|
|
81
|
+
|
|
82
|
+
// Include accessible descriptions in output (default: false)
|
|
83
|
+
// true: "Submit, button, Submits the form"
|
|
84
|
+
// false: "Submit, button"
|
|
85
|
+
includeDescription: false,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const orchestrator = new A11yOrchestrator(cdpSession, {
|
|
89
|
+
// All SpeechEngine options, plus:
|
|
90
|
+
|
|
91
|
+
// Milliseconds to wait after key press for focus/CSS to settle (default: 50)
|
|
92
|
+
focusSettleMs: 50,
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### `SpeechEngine`
|
|
99
|
+
|
|
100
|
+
The speech engine. All methods are async and operate through the CDP session.
|
|
101
|
+
|
|
102
|
+
#### `constructor(cdp: CDPSessionLike, options?: SpeechEngineOptions)`
|
|
103
|
+
|
|
104
|
+
Create a new engine instance.
|
|
105
|
+
|
|
106
|
+
- `cdp` — Any object implementing the `CDPSessionLike` interface.
|
|
107
|
+
- `options.includeLandmarks` — Append "landmark" to landmark roles. Default `true`.
|
|
108
|
+
- `options.includeDescription` — Include `aria-describedby` text. Default `false`.
|
|
109
|
+
|
|
110
|
+
#### `enable(): Promise<void>`
|
|
111
|
+
|
|
112
|
+
Enable the CDP Accessibility domain. Must be called before any other method.
|
|
113
|
+
|
|
114
|
+
#### `disable(): Promise<void>`
|
|
115
|
+
|
|
116
|
+
Disable the CDP Accessibility domain. Call when done to free browser resources.
|
|
117
|
+
|
|
118
|
+
#### `getSpeech(): Promise<SpeechResult | null>`
|
|
119
|
+
|
|
120
|
+
Get the speech output for the currently focused element.
|
|
121
|
+
|
|
122
|
+
Returns `null` if no element has focus or the focused element is ignored (e.g., `role="presentation"`).
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const result = await engine.getSpeech();
|
|
126
|
+
if (result) {
|
|
127
|
+
console.log(result.speech); // "Products, button, collapsed"
|
|
128
|
+
console.log(result.name); // "Products"
|
|
129
|
+
console.log(result.role); // "button"
|
|
130
|
+
console.log(result.states); // ["collapsed"]
|
|
131
|
+
console.log(result.rawNode); // Full CDP AXNode object
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### `getFullTreeSpeech(): Promise<SpeechResult[]>`
|
|
136
|
+
|
|
137
|
+
Get speech for all non-ignored, non-silent nodes in the accessibility tree. Useful for asserting on landmarks, headings, or structural elements that don't have focus.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const all = await engine.getFullTreeSpeech();
|
|
141
|
+
const headings = all.filter(r => r.role.includes('heading'));
|
|
142
|
+
const landmarks = all.filter(r => r.role.includes('landmark'));
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `computeSpeech(node: AXNode): SpeechResult | null`
|
|
146
|
+
|
|
147
|
+
Compute speech for a single AXNode. Returns `null` for ignored or silent nodes.
|
|
148
|
+
|
|
149
|
+
#### `findFocusedNode(nodes: AXNode[]): AXNode | null`
|
|
150
|
+
|
|
151
|
+
Find the most specific focused node in the flat AXTree array. When multiple nodes report `focused: true` (e.g., `RootWebArea` and a `menuitem`), the deepest node is returned.
|
|
152
|
+
|
|
153
|
+
### `A11yOrchestrator`
|
|
154
|
+
|
|
155
|
+
Unified orchestrator coordinating three sub-engines:
|
|
156
|
+
|
|
157
|
+
| Engine | Responsibility |
|
|
158
|
+
|--------|---------------|
|
|
159
|
+
| `SpeechEngine` | AXTree to speech string |
|
|
160
|
+
| `KeyboardEngine` | CDP key dispatch + `document.activeElement` |
|
|
161
|
+
| `FocusAnalyzer` | CSS focus indicator + tab order + trap detection |
|
|
162
|
+
|
|
163
|
+
#### `constructor(cdp: CDPSessionLike, options?: A11yOrchestratorOptions)`
|
|
164
|
+
|
|
165
|
+
Create a new orchestrator.
|
|
166
|
+
|
|
167
|
+
- `cdp` — Any CDP-compatible session.
|
|
168
|
+
- `options.includeLandmarks` — Append "landmark" to landmark roles. Default `true`.
|
|
169
|
+
- `options.includeDescription` — Include description text. Default `false`.
|
|
170
|
+
- `options.focusSettleMs` — Delay after key press for focus/CSS to settle. Default `50`.
|
|
171
|
+
|
|
172
|
+
#### `enable(): Promise<void>`
|
|
173
|
+
|
|
174
|
+
Enable the CDP Accessibility domain. Must be called before other methods.
|
|
175
|
+
|
|
176
|
+
#### `disable(): Promise<void>`
|
|
177
|
+
|
|
178
|
+
Disable the CDP Accessibility domain.
|
|
179
|
+
|
|
180
|
+
#### `pressKey(key: string, modifiers?: ModifierKeys): Promise<A11yState>`
|
|
181
|
+
|
|
182
|
+
Dispatch a key press and return the unified accessibility state.
|
|
183
|
+
|
|
184
|
+
1. Sends `keyDown` + `keyUp` via CDP `Input.dispatchKeyEvent`
|
|
185
|
+
2. Waits `focusSettleMs` for CSS transitions and focus events
|
|
186
|
+
3. Collects speech, focused element, and focus indicator in parallel
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const state = await orchestrator.pressKey('Tab');
|
|
190
|
+
// state.speech → "Products, button, collapsed"
|
|
191
|
+
// state.focusedElement → { tag: 'BUTTON', id: 'products-btn', ... }
|
|
192
|
+
// state.focusIndicator → { isVisible: true, contrastRatio: 12.5, meetsWCAG_AA: true }
|
|
193
|
+
|
|
194
|
+
// With modifier keys
|
|
195
|
+
const prev = await orchestrator.pressKey('Tab', { shift: true });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### `getState(): Promise<A11yState>`
|
|
199
|
+
|
|
200
|
+
Get the current unified state without pressing a key. Useful after programmatic focus changes.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
await page.focus('#my-button');
|
|
204
|
+
const state = await orchestrator.getState();
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### `traverseTabOrder(): Promise<TabOrderReport>`
|
|
208
|
+
|
|
209
|
+
Extract all tabbable elements in DOM tab order.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const report = await orchestrator.traverseTabOrder();
|
|
213
|
+
console.log(report.totalCount); // 12
|
|
214
|
+
console.log(report.entries[0].tag); // "A"
|
|
215
|
+
console.log(report.entries[0].id); // "home-link"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>`
|
|
219
|
+
|
|
220
|
+
Detect whether a container traps keyboard focus (WCAG 2.1.2).
|
|
221
|
+
|
|
222
|
+
Focuses the first tabbable element in the container, presses Tab up to `maxTabs` times (default 50), and checks whether focus ever escapes.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
const result = await orchestrator.traverseSubTree('#modal-container', 20);
|
|
226
|
+
if (result.isTrapped) {
|
|
227
|
+
console.log('Keyboard trap detected!');
|
|
228
|
+
console.log(`Focus visited ${result.visitedElements.length} elements`);
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`Focus escaped to: ${result.escapeElement?.tag}`);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Types
|
|
235
|
+
|
|
236
|
+
#### `CDPSessionLike`
|
|
237
|
+
|
|
238
|
+
The abstraction boundary between the engine and test frameworks:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
interface CDPSessionLike {
|
|
242
|
+
send(method: 'Accessibility.enable'): Promise<void>;
|
|
243
|
+
send(method: 'Accessibility.disable'): Promise<void>;
|
|
244
|
+
send(
|
|
245
|
+
method: 'Accessibility.getFullAXTree',
|
|
246
|
+
params?: { depth?: number; frameId?: string }
|
|
247
|
+
): Promise<{ nodes: AXNode[] }>;
|
|
248
|
+
send(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Both Playwright's `CDPSession` and `chrome-remote-interface` clients satisfy this interface without adapters.
|
|
253
|
+
|
|
254
|
+
#### `SpeechResult`
|
|
255
|
+
|
|
256
|
+
Returned by `getSpeech()` and `getFullTreeSpeech()`:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
interface SpeechResult {
|
|
260
|
+
speech: string; // "Products, button, collapsed"
|
|
261
|
+
name: string; // "Products"
|
|
262
|
+
role: string; // "button"
|
|
263
|
+
states: string[]; // ["collapsed"]
|
|
264
|
+
rawNode: Protocol.Accessibility.AXNode;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### `A11yState`
|
|
269
|
+
|
|
270
|
+
Returned by `pressKey()` and `getState()`:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
interface A11yState {
|
|
274
|
+
speech: string; // "Products, button, collapsed"
|
|
275
|
+
speechResult: SpeechResult | null; // Full speech result with raw AXNode
|
|
276
|
+
focusedElement: A11yFocusedElement | null; // DOM info
|
|
277
|
+
focusIndicator: A11yFocusIndicator; // CSS analysis
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### `A11yFocusedElement`
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
interface A11yFocusedElement {
|
|
285
|
+
tag: string; // "BUTTON"
|
|
286
|
+
id: string; // "submit-btn"
|
|
287
|
+
className: string; // "btn primary"
|
|
288
|
+
textContent: string; // "Submit"
|
|
289
|
+
role: string; // "button"
|
|
290
|
+
ariaLabel: string; // "Submit form"
|
|
291
|
+
tabIndex: number; // 0
|
|
292
|
+
rect: { x: number; y: number; width: number; height: number };
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### `A11yFocusIndicator`
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
interface A11yFocusIndicator {
|
|
300
|
+
isVisible: boolean; // true if outline or box-shadow detected
|
|
301
|
+
contrastRatio: number | null; // null if colors unparseable
|
|
302
|
+
meetsWCAG_AA: boolean; // true if visible and contrast >= 3.0
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### `ModifierKeys`
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
interface ModifierKeys {
|
|
310
|
+
shift?: boolean;
|
|
311
|
+
ctrl?: boolean;
|
|
312
|
+
alt?: boolean;
|
|
313
|
+
meta?: boolean;
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### `TabOrderReport`
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
interface TabOrderReport {
|
|
321
|
+
entries: TabOrderEntry[];
|
|
322
|
+
totalCount: number;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### `TabOrderEntry`
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
interface TabOrderEntry {
|
|
330
|
+
index: number; // Position in tab order (0-based)
|
|
331
|
+
tag: string; // "BUTTON"
|
|
332
|
+
id: string; // "submit-btn"
|
|
333
|
+
textContent: string;
|
|
334
|
+
tabIndex: number;
|
|
335
|
+
role: string;
|
|
336
|
+
rect: { x: number; y: number; width: number; height: number };
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### `TraversalResult`
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
interface TraversalResult {
|
|
344
|
+
isTrapped: boolean; // true if focus never escaped
|
|
345
|
+
tabCount: number; // Total Tab presses attempted
|
|
346
|
+
visitedElements: TabOrderEntry[]; // Elements that received focus
|
|
347
|
+
escapeElement: TabOrderEntry | null; // First element outside container
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Role Mappings
|
|
352
|
+
|
|
353
|
+
The `ROLE_TO_SPEECH` record maps 50+ CDP role values to speech strings. Roles are grouped into four categories:
|
|
354
|
+
|
|
355
|
+
### Interactive Roles
|
|
356
|
+
|
|
357
|
+
| CDP Role | Speech |
|
|
358
|
+
|----------|--------|
|
|
359
|
+
| `button` | `button` |
|
|
360
|
+
| `link` | `link` |
|
|
361
|
+
| `checkbox` | `checkbox` |
|
|
362
|
+
| `radio` | `radio button` |
|
|
363
|
+
| `textbox` | `edit text` |
|
|
364
|
+
| `combobox` | `combo box` |
|
|
365
|
+
| `slider` | `slider` |
|
|
366
|
+
| `switch` | `switch` |
|
|
367
|
+
| `tab` | `tab` |
|
|
368
|
+
| `menuitem` | `menu item` |
|
|
369
|
+
| `menuitemcheckbox` | `menu item checkbox` |
|
|
370
|
+
| `menuitemradio` | `menu item radio` |
|
|
371
|
+
| `option` | `option` |
|
|
372
|
+
| `searchbox` | `search text` |
|
|
373
|
+
| `spinbutton` | `spin button` |
|
|
374
|
+
|
|
375
|
+
### Landmark Roles
|
|
376
|
+
|
|
377
|
+
Landmarks automatically append "landmark" when `includeLandmarks` is `true`:
|
|
378
|
+
|
|
379
|
+
| CDP Role | Speech |
|
|
380
|
+
|----------|--------|
|
|
381
|
+
| `navigation` | `navigation landmark` |
|
|
382
|
+
| `main` | `main landmark` |
|
|
383
|
+
| `banner` | `banner landmark` |
|
|
384
|
+
| `contentinfo` | `content info landmark` |
|
|
385
|
+
| `complementary` | `complementary landmark` |
|
|
386
|
+
| `search` | `search landmark` |
|
|
387
|
+
| `region` | `region landmark` |
|
|
388
|
+
| `form` | `form landmark` |
|
|
389
|
+
|
|
390
|
+
### Structure Roles
|
|
391
|
+
|
|
392
|
+
| CDP Role | Speech |
|
|
393
|
+
|----------|--------|
|
|
394
|
+
| `heading` | `heading` |
|
|
395
|
+
| `list` | `list` |
|
|
396
|
+
| `listitem` | `list item` |
|
|
397
|
+
| `img` | `image` |
|
|
398
|
+
| `table` | `table` |
|
|
399
|
+
| `row` | `row` |
|
|
400
|
+
| `cell` | `cell` |
|
|
401
|
+
| `dialog` | `dialog` |
|
|
402
|
+
| `alert` | `alert` |
|
|
403
|
+
| `menu` | `menu` |
|
|
404
|
+
| `menubar` | `menu bar` |
|
|
405
|
+
| `toolbar` | `toolbar` |
|
|
406
|
+
| `tree` | `tree` |
|
|
407
|
+
| `treeitem` | `tree item` |
|
|
408
|
+
| `tablist` | `tab list` |
|
|
409
|
+
| `tabpanel` | `tab panel` |
|
|
410
|
+
| `progressbar` | `progress bar` |
|
|
411
|
+
| `tooltip` | `tooltip` |
|
|
412
|
+
|
|
413
|
+
### Silent Roles
|
|
414
|
+
|
|
415
|
+
These roles produce no speech output:
|
|
416
|
+
|
|
417
|
+
`generic`, `none`, `presentation`, `StaticText`, `InlineTextBox`, `LineBreak`, `RootWebArea`, `WebArea`, `paragraph`, `DescriptionListDetail`, `DescriptionListTerm`, `DescriptionList`
|
|
418
|
+
|
|
419
|
+
Unknown roles pass through as-is for forward compatibility with new ARIA roles.
|
|
420
|
+
|
|
421
|
+
## State Mappings
|
|
422
|
+
|
|
423
|
+
The `STATE_MAPPINGS` array defines how boolean ARIA properties translate to spoken strings. States appear in the output in the order listed below:
|
|
424
|
+
|
|
425
|
+
| CDP Property | `true` | `false` |
|
|
426
|
+
|-------------|--------|---------|
|
|
427
|
+
| `expanded` | `expanded` | `collapsed` |
|
|
428
|
+
| `checked` | `checked` | `not checked` |
|
|
429
|
+
| `selected` | `selected` | *(silent)* |
|
|
430
|
+
| `pressed` | `pressed` | `not pressed` |
|
|
431
|
+
| `disabled` | `dimmed` | *(silent)* |
|
|
432
|
+
| `required` | `required` | *(silent)* |
|
|
433
|
+
| `invalid` | `invalid` | *(silent)* |
|
|
434
|
+
| `readonly` | `read only` | *(silent)* |
|
|
435
|
+
| `multiselectable` | `multi selectable` | *(silent)* |
|
|
436
|
+
|
|
437
|
+
Heading levels are a special case: `level` property produces `level N` (e.g., `level 2`).
|
|
438
|
+
|
|
439
|
+
When multiple states are present, they are joined in the fixed order above:
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
"Submit, button, expanded, required"
|
|
443
|
+
"Email, edit text, invalid, required"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Exports
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
// Main classes
|
|
450
|
+
export { SpeechEngine } from '@a11y-oracle/core-engine';
|
|
451
|
+
export { A11yOrchestrator } from '@a11y-oracle/core-engine';
|
|
452
|
+
|
|
453
|
+
// Core types
|
|
454
|
+
export type {
|
|
455
|
+
CDPSessionLike,
|
|
456
|
+
SpeechResult,
|
|
457
|
+
SpeechEngineOptions,
|
|
458
|
+
A11yState,
|
|
459
|
+
A11yFocusedElement,
|
|
460
|
+
A11yFocusIndicator,
|
|
461
|
+
A11yOrchestratorOptions,
|
|
462
|
+
} from '@a11y-oracle/core-engine';
|
|
463
|
+
|
|
464
|
+
// Data (for advanced customization)
|
|
465
|
+
export { ROLE_TO_SPEECH, LANDMARK_ROLES } from '@a11y-oracle/core-engine';
|
|
466
|
+
export { STATE_MAPPINGS, extractStates } from '@a11y-oracle/core-engine';
|
|
467
|
+
export type { StateMapping, AXNodeProperty } from '@a11y-oracle/core-engine';
|
|
468
|
+
|
|
469
|
+
// Re-exports from sub-engines
|
|
470
|
+
export type { ModifierKeys, FocusedElementInfo, KeyDefinition } from '@a11y-oracle/core-engine';
|
|
471
|
+
export { KEY_DEFINITIONS } from '@a11y-oracle/core-engine';
|
|
472
|
+
export type { FocusIndicator, TabOrderEntry, TabOrderReport, TraversalResult } from '@a11y-oracle/core-engine';
|
|
473
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @a11y-oracle/core-engine
|
|
3
|
+
*
|
|
4
|
+
* Core speech engine for A11y-Oracle. Connects to Chrome DevTools Protocol,
|
|
5
|
+
* fetches the Accessibility Tree, and generates standardized speech output
|
|
6
|
+
* based on W3C specifications.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { SpeechEngine } from '@a11y-oracle/core-engine';
|
|
11
|
+
* import type { CDPSessionLike } from '@a11y-oracle/core-engine';
|
|
12
|
+
*
|
|
13
|
+
* // Create engine with any CDP-compatible session
|
|
14
|
+
* const engine = new SpeechEngine(cdpSession);
|
|
15
|
+
* await engine.enable();
|
|
16
|
+
*
|
|
17
|
+
* // Get speech for the focused element
|
|
18
|
+
* const result = await engine.getSpeech();
|
|
19
|
+
* console.log(result?.speech); // "Products, button, collapsed"
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
export { SpeechEngine } from './lib/speech-engine.js';
|
|
25
|
+
export { A11yOrchestrator } from './lib/a11y-orchestrator.js';
|
|
26
|
+
export type { CDPSessionLike, SpeechResult, SpeechEngineOptions, A11yState, A11yFocusedElement, A11yFocusIndicator, A11yOrchestratorOptions, } from './lib/types.js';
|
|
27
|
+
export { ROLE_TO_SPEECH, LANDMARK_ROLES } from './lib/role-map.js';
|
|
28
|
+
export { STATE_MAPPINGS, extractStates } from './lib/state-map.js';
|
|
29
|
+
export type { StateMapping, AXNodeProperty } from './lib/state-map.js';
|
|
30
|
+
export type { ModifierKeys, FocusedElementInfo, KeyDefinition, } from '@a11y-oracle/keyboard-engine';
|
|
31
|
+
export { KEY_DEFINITIONS } from '@a11y-oracle/keyboard-engine';
|
|
32
|
+
export type { FocusIndicator, TabOrderEntry, TabOrderReport, TraversalResult, } from '@a11y-oracle/focus-analyzer';
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EACV,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGvE,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,aAAa,GACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,YAAY,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @a11y-oracle/core-engine
|
|
3
|
+
*
|
|
4
|
+
* Core speech engine for A11y-Oracle. Connects to Chrome DevTools Protocol,
|
|
5
|
+
* fetches the Accessibility Tree, and generates standardized speech output
|
|
6
|
+
* based on W3C specifications.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { SpeechEngine } from '@a11y-oracle/core-engine';
|
|
11
|
+
* import type { CDPSessionLike } from '@a11y-oracle/core-engine';
|
|
12
|
+
*
|
|
13
|
+
* // Create engine with any CDP-compatible session
|
|
14
|
+
* const engine = new SpeechEngine(cdpSession);
|
|
15
|
+
* await engine.enable();
|
|
16
|
+
*
|
|
17
|
+
* // Get speech for the focused element
|
|
18
|
+
* const result = await engine.getSpeech();
|
|
19
|
+
* console.log(result?.speech); // "Products, button, collapsed"
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
export { SpeechEngine } from './lib/speech-engine.js';
|
|
25
|
+
export { A11yOrchestrator } from './lib/a11y-orchestrator.js';
|
|
26
|
+
export { ROLE_TO_SPEECH, LANDMARK_ROLES } from './lib/role-map.js';
|
|
27
|
+
export { STATE_MAPPINGS, extractStates } from './lib/state-map.js';
|
|
28
|
+
export { KEY_DEFINITIONS } from '@a11y-oracle/keyboard-engine';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module a11y-orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates the three sub-engines (Speech, Keyboard, Focus) into a
|
|
5
|
+
* single `pressKey()` → unified-state workflow.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { A11yOrchestrator } from '@a11y-oracle/core-engine';
|
|
10
|
+
*
|
|
11
|
+
* const oracle = new A11yOrchestrator(cdpSession);
|
|
12
|
+
* await oracle.enable();
|
|
13
|
+
*
|
|
14
|
+
* const state = await oracle.pressKey('Tab');
|
|
15
|
+
* console.log(state.speech); // "Products, button, collapsed"
|
|
16
|
+
* console.log(state.focusedElement?.tag); // "BUTTON"
|
|
17
|
+
* console.log(state.focusIndicator.meetsWCAG_AA); // true
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import type { CDPSessionLike, A11yState, A11yOrchestratorOptions } from './types.js';
|
|
21
|
+
import type { ModifierKeys } from '@a11y-oracle/keyboard-engine';
|
|
22
|
+
import type { TabOrderReport, TraversalResult } from '@a11y-oracle/focus-analyzer';
|
|
23
|
+
/**
|
|
24
|
+
* Orchestrates speech, keyboard dispatch, and focus analysis into a
|
|
25
|
+
* unified accessibility testing API.
|
|
26
|
+
*
|
|
27
|
+
* A single `pressKey()` call dispatches a keystroke, waits for focus
|
|
28
|
+
* to settle, then collects speech output, focused element info, and
|
|
29
|
+
* focus indicator analysis in parallel.
|
|
30
|
+
*
|
|
31
|
+
* ## Sub-engines
|
|
32
|
+
*
|
|
33
|
+
* | Engine | Responsibility |
|
|
34
|
+
* |--------|---------------|
|
|
35
|
+
* | {@link SpeechEngine} | AXTree → speech string |
|
|
36
|
+
* | {@link KeyboardEngine} | CDP key dispatch + `document.activeElement` |
|
|
37
|
+
* | {@link FocusAnalyzer} | CSS focus indicator + tab order + trap detection |
|
|
38
|
+
*/
|
|
39
|
+
export declare class A11yOrchestrator {
|
|
40
|
+
private speech;
|
|
41
|
+
private keyboard;
|
|
42
|
+
private focusAnalyzer;
|
|
43
|
+
private options;
|
|
44
|
+
/**
|
|
45
|
+
* @param cdp - CDP session for sending protocol commands.
|
|
46
|
+
* @param options - Optional configuration for speech output and focus settling.
|
|
47
|
+
*/
|
|
48
|
+
constructor(cdp: CDPSessionLike, options?: A11yOrchestratorOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Enable the CDP Accessibility domain.
|
|
51
|
+
*
|
|
52
|
+
* Must be called before any other method.
|
|
53
|
+
*/
|
|
54
|
+
enable(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Disable the CDP Accessibility domain.
|
|
57
|
+
*
|
|
58
|
+
* Call this when done to free browser resources.
|
|
59
|
+
*/
|
|
60
|
+
disable(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Dispatch a key press and return the unified accessibility state.
|
|
63
|
+
*
|
|
64
|
+
* 1. Dispatches `keyDown` + `keyUp` via CDP `Input.dispatchKeyEvent`.
|
|
65
|
+
* 2. Waits {@link A11yOrchestratorOptions.focusSettleMs} for transitions.
|
|
66
|
+
* 3. Collects speech, focused element, and focus indicator **in parallel**.
|
|
67
|
+
*
|
|
68
|
+
* @param key - Key name (e.g. `'Tab'`, `'Enter'`, `'ArrowDown'`).
|
|
69
|
+
* @param modifiers - Optional modifier keys.
|
|
70
|
+
* @returns Unified accessibility state snapshot.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const state = await orchestrator.pressKey('Tab');
|
|
75
|
+
* expect(state.speech).toBe('Products, button, collapsed');
|
|
76
|
+
* expect(state.focusIndicator.meetsWCAG_AA).toBe(true);
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
pressKey(key: string, modifiers?: ModifierKeys): Promise<A11yState>;
|
|
80
|
+
/**
|
|
81
|
+
* Get the current unified accessibility state without pressing a key.
|
|
82
|
+
*
|
|
83
|
+
* Collects speech, focused element, and focus indicator in parallel.
|
|
84
|
+
*
|
|
85
|
+
* @returns Unified accessibility state snapshot.
|
|
86
|
+
*/
|
|
87
|
+
getState(): Promise<A11yState>;
|
|
88
|
+
/**
|
|
89
|
+
* Extract all tabbable elements in DOM tab order.
|
|
90
|
+
*
|
|
91
|
+
* @returns Report with sorted tab order entries and total count.
|
|
92
|
+
*/
|
|
93
|
+
traverseTabOrder(): Promise<TabOrderReport>;
|
|
94
|
+
/**
|
|
95
|
+
* Detect whether a container traps keyboard focus.
|
|
96
|
+
*
|
|
97
|
+
* Focuses the first tabbable element in the container, then
|
|
98
|
+
* presses Tab repeatedly. If focus never escapes after `maxTabs`
|
|
99
|
+
* presses, the container is a keyboard trap (WCAG 2.1.2 failure).
|
|
100
|
+
*
|
|
101
|
+
* @param selector - CSS selector for the container to test.
|
|
102
|
+
* @param maxTabs - Maximum Tab presses before declaring a trap. Default 50.
|
|
103
|
+
* @returns Traversal result indicating whether focus is trapped.
|
|
104
|
+
*/
|
|
105
|
+
traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Map a raw `FocusedElementInfo` from keyboard-engine to the
|
|
108
|
+
* orchestrator's `A11yFocusedElement` shape.
|
|
109
|
+
*/
|
|
110
|
+
private mapFocusedElement;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=a11y-orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-orchestrator.d.ts","sourceRoot":"","sources":["../../src/lib/a11y-orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EAGT,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,YAAY,EAAsB,MAAM,8BAA8B,CAAC;AAErF,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAoC;IAEnD;;;OAGG;gBACS,GAAG,EAAE,cAAc,EAAE,OAAO,GAAE,uBAA4B;IAWtE;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;;;;;;;;;;;;;;;;OAiBG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAazE;;;;;;OAMG;IACG,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC;IA0BpC;;;;OAIG;IACG,gBAAgB,IAAI,OAAO,CAAC,cAAc,CAAC;IAQjD;;;;;;;;;;OAUG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,CAAC;IAI3B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;CAY1B"}
|