@a11y-oracle/keyboard-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 +144 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/lib/key-map.d.ts +16 -0
- package/dist/lib/key-map.d.ts.map +1 -0
- package/dist/lib/key-map.js +28 -0
- package/dist/lib/keyboard-engine.d.ts +73 -0
- package/dist/lib/keyboard-engine.d.ts.map +1 -0
- package/dist/lib/keyboard-engine.js +153 -0
- package/dist/lib/types.d.ts +60 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +6 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @a11y-oracle/keyboard-engine
|
|
2
|
+
|
|
3
|
+
CDP-based keyboard dispatch engine for accessibility testing. Sends native hardware-level keystrokes via `Input.dispatchKeyEvent` and reads focused element information via `Runtime.evaluate`.
|
|
4
|
+
|
|
5
|
+
This is a low-level building block used internally by `@a11y-oracle/core-engine`. Most users should use the Playwright or Cypress plugin instead.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @a11y-oracle/keyboard-engine
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { KeyboardEngine } from '@a11y-oracle/keyboard-engine';
|
|
17
|
+
|
|
18
|
+
const keyboard = new KeyboardEngine(cdpSession);
|
|
19
|
+
|
|
20
|
+
// Press a key
|
|
21
|
+
await keyboard.press('Tab');
|
|
22
|
+
|
|
23
|
+
// Press with modifiers
|
|
24
|
+
await keyboard.press('Tab', { shift: true }); // Shift+Tab (backward)
|
|
25
|
+
await keyboard.press('a', { ctrl: true }); // Ctrl+A (select all)
|
|
26
|
+
|
|
27
|
+
// Get info about the currently focused element
|
|
28
|
+
const el = await keyboard.getFocusedElement();
|
|
29
|
+
if (el) {
|
|
30
|
+
console.log(el.tag); // "BUTTON"
|
|
31
|
+
console.log(el.id); // "submit-btn"
|
|
32
|
+
console.log(el.role); // "button"
|
|
33
|
+
console.log(el.tabIndex); // 0
|
|
34
|
+
console.log(el.rect); // { x: 100, y: 200, width: 120, height: 40 }
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API Reference
|
|
39
|
+
|
|
40
|
+
### `KeyboardEngine`
|
|
41
|
+
|
|
42
|
+
#### `constructor(cdp: CDPSessionLike)`
|
|
43
|
+
|
|
44
|
+
Create a new keyboard engine.
|
|
45
|
+
|
|
46
|
+
- `cdp` — Any object with a `send(method, params?)` method compatible with CDP.
|
|
47
|
+
|
|
48
|
+
#### `press(key: string, modifiers?: ModifierKeys): Promise<void>`
|
|
49
|
+
|
|
50
|
+
Dispatch a native key press via CDP `Input.dispatchKeyEvent`. Sends a `keyDown` event followed by a `keyUp` event, matching the behavior of a physical key press.
|
|
51
|
+
|
|
52
|
+
- `key` — Key name from `KEY_DEFINITIONS` (see table below).
|
|
53
|
+
- `modifiers` — Optional modifier keys to hold during the press.
|
|
54
|
+
|
|
55
|
+
Modifier keys map to the CDP bitmask: Alt = 1, Ctrl = 2, Meta = 4, Shift = 8.
|
|
56
|
+
|
|
57
|
+
Throws if the key name is not in `KEY_DEFINITIONS`.
|
|
58
|
+
|
|
59
|
+
#### `getFocusedElement(): Promise<FocusedElementInfo | null>`
|
|
60
|
+
|
|
61
|
+
Get information about the currently focused DOM element via `Runtime.evaluate`.
|
|
62
|
+
|
|
63
|
+
Returns `null` if no interactive element has focus (e.g., `document.body` or `document.documentElement` is focused).
|
|
64
|
+
|
|
65
|
+
### Supported Keys
|
|
66
|
+
|
|
67
|
+
| Key Name | Description |
|
|
68
|
+
|----------|-------------|
|
|
69
|
+
| `Tab` | Tab navigation |
|
|
70
|
+
| `Enter` | Activate / submit |
|
|
71
|
+
| `Space` or `' '` | Activate / toggle |
|
|
72
|
+
| `Escape` | Cancel / close |
|
|
73
|
+
| `ArrowUp` | Navigate up |
|
|
74
|
+
| `ArrowDown` | Navigate down |
|
|
75
|
+
| `ArrowLeft` | Navigate left |
|
|
76
|
+
| `ArrowRight` | Navigate right |
|
|
77
|
+
| `Home` | Jump to start |
|
|
78
|
+
| `End` | Jump to end |
|
|
79
|
+
| `Backspace` | Delete backward |
|
|
80
|
+
| `Delete` | Delete forward |
|
|
81
|
+
|
|
82
|
+
### Types
|
|
83
|
+
|
|
84
|
+
#### `KeyDefinition`
|
|
85
|
+
|
|
86
|
+
CDP keyboard event parameters for a single key:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface KeyDefinition {
|
|
90
|
+
key: string; // CDP key property (e.g. "Tab")
|
|
91
|
+
code: string; // CDP code property (e.g. "Tab")
|
|
92
|
+
keyCode: number; // Windows virtual key code (e.g. 9)
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `ModifierKeys`
|
|
97
|
+
|
|
98
|
+
Modifier keys for key press combinations:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
interface ModifierKeys {
|
|
102
|
+
shift?: boolean;
|
|
103
|
+
ctrl?: boolean;
|
|
104
|
+
alt?: boolean;
|
|
105
|
+
meta?: boolean;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### `FocusedElementInfo`
|
|
110
|
+
|
|
111
|
+
Information about the currently focused DOM element:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
interface FocusedElementInfo {
|
|
115
|
+
tag: string; // "BUTTON"
|
|
116
|
+
id: string; // "submit-btn"
|
|
117
|
+
className: string; // "btn primary"
|
|
118
|
+
textContent: string; // "Submit"
|
|
119
|
+
role: string; // "button" (from role attribute)
|
|
120
|
+
ariaLabel: string; // "Submit form" (from aria-label)
|
|
121
|
+
tabIndex: number; // 0
|
|
122
|
+
rect: { x: number; y: number; width: number; height: number };
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Exports
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
export { KeyboardEngine } from '@a11y-oracle/keyboard-engine';
|
|
130
|
+
export { KEY_DEFINITIONS } from '@a11y-oracle/keyboard-engine';
|
|
131
|
+
export type {
|
|
132
|
+
KeyDefinition,
|
|
133
|
+
ModifierKeys,
|
|
134
|
+
FocusedElementInfo,
|
|
135
|
+
} from '@a11y-oracle/keyboard-engine';
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## How It Works
|
|
139
|
+
|
|
140
|
+
The engine uses two CDP domains:
|
|
141
|
+
|
|
142
|
+
1. **`Input.dispatchKeyEvent`** — Sends real hardware-level keystroke events (keyDown + keyUp) directly to the browser. This bypasses synthetic JavaScript event dispatch (`element.dispatchEvent()`), so the browser handles focus management natively — just as if a user physically pressed the key.
|
|
143
|
+
|
|
144
|
+
2. **`Runtime.evaluate`** — Executes a JavaScript expression in the page context to read `document.activeElement` properties (tag, id, class, role, aria-label, tabIndex, bounding rect).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @a11y-oracle/keyboard-engine
|
|
3
|
+
*
|
|
4
|
+
* CDP-based keyboard dispatch engine for accessibility testing.
|
|
5
|
+
* Provides native hardware-level keystroke dispatch and focused
|
|
6
|
+
* element introspection.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Start
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { KeyboardEngine } from '@a11y-oracle/keyboard-engine';
|
|
12
|
+
*
|
|
13
|
+
* const keyboard = new KeyboardEngine(cdpSession);
|
|
14
|
+
* await keyboard.press('Tab');
|
|
15
|
+
* await keyboard.press('Tab', { shift: true }); // Shift+Tab
|
|
16
|
+
*
|
|
17
|
+
* const el = await keyboard.getFocusedElement();
|
|
18
|
+
* console.log(el?.tag, el?.id, el?.role);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
export { KeyboardEngine } from './lib/keyboard-engine.js';
|
|
24
|
+
export { KEY_DEFINITIONS } from './lib/key-map.js';
|
|
25
|
+
export type { KeyDefinition, ModifierKeys, FocusedElementInfo, } from './lib/types.js';
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,kBAAkB,GACnB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @a11y-oracle/keyboard-engine
|
|
3
|
+
*
|
|
4
|
+
* CDP-based keyboard dispatch engine for accessibility testing.
|
|
5
|
+
* Provides native hardware-level keystroke dispatch and focused
|
|
6
|
+
* element introspection.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick Start
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { KeyboardEngine } from '@a11y-oracle/keyboard-engine';
|
|
12
|
+
*
|
|
13
|
+
* const keyboard = new KeyboardEngine(cdpSession);
|
|
14
|
+
* await keyboard.press('Tab');
|
|
15
|
+
* await keyboard.press('Tab', { shift: true }); // Shift+Tab
|
|
16
|
+
*
|
|
17
|
+
* const el = await keyboard.getFocusedElement();
|
|
18
|
+
* console.log(el?.tag, el?.id, el?.role);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
export { KeyboardEngine } from './lib/keyboard-engine.js';
|
|
24
|
+
export { KEY_DEFINITIONS } from './lib/key-map.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module key-map
|
|
3
|
+
*
|
|
4
|
+
* Maps human-readable key names to CDP `Input.dispatchKeyEvent` parameters.
|
|
5
|
+
* Used by the {@link KeyboardEngine} to dispatch real keyboard events
|
|
6
|
+
* through the Chrome DevTools Protocol.
|
|
7
|
+
*/
|
|
8
|
+
import type { KeyDefinition } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Map of key names to their CDP Input.dispatchKeyEvent parameters.
|
|
11
|
+
*
|
|
12
|
+
* Supports all common navigation and interaction keys used in
|
|
13
|
+
* WCAG keyboard accessibility patterns.
|
|
14
|
+
*/
|
|
15
|
+
export declare const KEY_DEFINITIONS: Record<string, KeyDefinition>;
|
|
16
|
+
//# sourceMappingURL=key-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-map.d.ts","sourceRoot":"","sources":["../../src/lib/key-map.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAczD,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module key-map
|
|
3
|
+
*
|
|
4
|
+
* Maps human-readable key names to CDP `Input.dispatchKeyEvent` parameters.
|
|
5
|
+
* Used by the {@link KeyboardEngine} to dispatch real keyboard events
|
|
6
|
+
* through the Chrome DevTools Protocol.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Map of key names to their CDP Input.dispatchKeyEvent parameters.
|
|
10
|
+
*
|
|
11
|
+
* Supports all common navigation and interaction keys used in
|
|
12
|
+
* WCAG keyboard accessibility patterns.
|
|
13
|
+
*/
|
|
14
|
+
export const KEY_DEFINITIONS = {
|
|
15
|
+
Tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
|
|
16
|
+
Enter: { key: 'Enter', code: 'Enter', keyCode: 13 },
|
|
17
|
+
' ': { key: ' ', code: 'Space', keyCode: 32 },
|
|
18
|
+
Space: { key: ' ', code: 'Space', keyCode: 32 },
|
|
19
|
+
Escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
20
|
+
ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
|
|
21
|
+
ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
|
|
22
|
+
ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
|
|
23
|
+
ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
|
|
24
|
+
Home: { key: 'Home', code: 'Home', keyCode: 36 },
|
|
25
|
+
End: { key: 'End', code: 'End', keyCode: 35 },
|
|
26
|
+
Backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
|
|
27
|
+
Delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
|
|
28
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module keyboard-engine
|
|
3
|
+
*
|
|
4
|
+
* CDP-based keyboard dispatch engine. Sends native hardware-level
|
|
5
|
+
* keystrokes via `Input.dispatchKeyEvent` and reads focused element
|
|
6
|
+
* information via `Runtime.evaluate`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { KeyboardEngine } from '@a11y-oracle/keyboard-engine';
|
|
11
|
+
*
|
|
12
|
+
* const keyboard = new KeyboardEngine(cdpSession);
|
|
13
|
+
* await keyboard.press('Tab');
|
|
14
|
+
* const focused = await keyboard.getFocusedElement();
|
|
15
|
+
* console.log(focused?.tag, focused?.id);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { CDPSessionLike } from '@a11y-oracle/cdp-types';
|
|
19
|
+
import type { ModifierKeys, FocusedElementInfo } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* Engine for dispatching native keyboard events via Chrome DevTools Protocol.
|
|
22
|
+
*
|
|
23
|
+
* Uses `Input.dispatchKeyEvent` for hardware-level key dispatch (bypassing
|
|
24
|
+
* synthetic JavaScript events) and `Runtime.evaluate` for reading
|
|
25
|
+
* `document.activeElement` properties.
|
|
26
|
+
*/
|
|
27
|
+
export declare class KeyboardEngine {
|
|
28
|
+
private cdp;
|
|
29
|
+
/**
|
|
30
|
+
* Create a new KeyboardEngine.
|
|
31
|
+
*
|
|
32
|
+
* @param cdp - CDP session to send commands through.
|
|
33
|
+
*/
|
|
34
|
+
constructor(cdp: CDPSessionLike);
|
|
35
|
+
/**
|
|
36
|
+
* Press a keyboard key via CDP `Input.dispatchKeyEvent`.
|
|
37
|
+
*
|
|
38
|
+
* Dispatches a `keyDown` event followed by a `keyUp` event, matching
|
|
39
|
+
* the behavior of a physical key press.
|
|
40
|
+
*
|
|
41
|
+
* @param key - Key name from {@link KEY_DEFINITIONS}
|
|
42
|
+
* (e.g. `'Tab'`, `'Enter'`, `'ArrowDown'`).
|
|
43
|
+
* @param modifiers - Optional modifier keys to hold during the press.
|
|
44
|
+
*
|
|
45
|
+
* @throws Error if the key is not in {@link KEY_DEFINITIONS}.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* await keyboard.press('Tab');
|
|
50
|
+
* await keyboard.press('Tab', { shift: true }); // Shift+Tab
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
press(key: string, modifiers?: ModifierKeys): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Get information about the currently focused DOM element.
|
|
56
|
+
*
|
|
57
|
+
* Uses `Runtime.evaluate` to query `document.activeElement` and
|
|
58
|
+
* extract its properties.
|
|
59
|
+
*
|
|
60
|
+
* @returns The focused element info, or `null` if no interactive
|
|
61
|
+
* element has focus (e.g. body or document is focused).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* await keyboard.press('Tab');
|
|
66
|
+
* const el = await keyboard.getFocusedElement();
|
|
67
|
+
* console.log(el?.tag); // 'BUTTON'
|
|
68
|
+
* console.log(el?.id); // 'submit-btn'
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
getFocusedElement(): Promise<FocusedElementInfo | null>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=keyboard-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-engine.d.ts","sourceRoot":"","sources":["../../src/lib/keyboard-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAmDnE;;;;;;GAMG;AACH,qBAAa,cAAc;IAMb,OAAO,CAAC,GAAG;IALvB;;;;OAIG;gBACiB,GAAG,EAAE,cAAc;IAEvC;;;;;;;;;;;;;;;;;OAiBG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BjE;;;;;;;;;;;;;;;;OAgBG;IACG,iBAAiB,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAQ9D"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module keyboard-engine
|
|
3
|
+
*
|
|
4
|
+
* CDP-based keyboard dispatch engine. Sends native hardware-level
|
|
5
|
+
* keystrokes via `Input.dispatchKeyEvent` and reads focused element
|
|
6
|
+
* information via `Runtime.evaluate`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { KeyboardEngine } from '@a11y-oracle/keyboard-engine';
|
|
11
|
+
*
|
|
12
|
+
* const keyboard = new KeyboardEngine(cdpSession);
|
|
13
|
+
* await keyboard.press('Tab');
|
|
14
|
+
* const focused = await keyboard.getFocusedElement();
|
|
15
|
+
* console.log(focused?.tag, focused?.id);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { KEY_DEFINITIONS } from './key-map.js';
|
|
19
|
+
/**
|
|
20
|
+
* Compute the CDP modifier bitmask from a {@link ModifierKeys} object.
|
|
21
|
+
*
|
|
22
|
+
* Bitmask values per CDP spec:
|
|
23
|
+
* - Alt = 1
|
|
24
|
+
* - Ctrl = 2
|
|
25
|
+
* - Meta = 4
|
|
26
|
+
* - Shift = 8
|
|
27
|
+
*/
|
|
28
|
+
function computeModifiers(modifiers) {
|
|
29
|
+
if (!modifiers)
|
|
30
|
+
return 0;
|
|
31
|
+
let bitmask = 0;
|
|
32
|
+
if (modifiers.alt)
|
|
33
|
+
bitmask |= 1;
|
|
34
|
+
if (modifiers.ctrl)
|
|
35
|
+
bitmask |= 2;
|
|
36
|
+
if (modifiers.meta)
|
|
37
|
+
bitmask |= 4;
|
|
38
|
+
if (modifiers.shift)
|
|
39
|
+
bitmask |= 8;
|
|
40
|
+
return bitmask;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* JavaScript expression evaluated in the browser to extract
|
|
44
|
+
* focused element information from `document.activeElement`.
|
|
45
|
+
*
|
|
46
|
+
* Returns `null` if no element has focus or the body is focused.
|
|
47
|
+
*/
|
|
48
|
+
const GET_FOCUSED_ELEMENT_JS = `
|
|
49
|
+
(() => {
|
|
50
|
+
const el = document.activeElement;
|
|
51
|
+
if (!el || el === document.body || el === document.documentElement) return null;
|
|
52
|
+
const rect = el.getBoundingClientRect();
|
|
53
|
+
return {
|
|
54
|
+
tag: el.tagName,
|
|
55
|
+
id: el.id || '',
|
|
56
|
+
className: typeof el.className === 'string' ? el.className : '',
|
|
57
|
+
textContent: (el.textContent || '').trim().substring(0, 200),
|
|
58
|
+
role: el.getAttribute('role') || '',
|
|
59
|
+
ariaLabel: el.getAttribute('aria-label') || '',
|
|
60
|
+
tabIndex: el.tabIndex,
|
|
61
|
+
rect: {
|
|
62
|
+
x: Math.round(rect.x),
|
|
63
|
+
y: Math.round(rect.y),
|
|
64
|
+
width: Math.round(rect.width),
|
|
65
|
+
height: Math.round(rect.height),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
})()
|
|
69
|
+
`;
|
|
70
|
+
/**
|
|
71
|
+
* Engine for dispatching native keyboard events via Chrome DevTools Protocol.
|
|
72
|
+
*
|
|
73
|
+
* Uses `Input.dispatchKeyEvent` for hardware-level key dispatch (bypassing
|
|
74
|
+
* synthetic JavaScript events) and `Runtime.evaluate` for reading
|
|
75
|
+
* `document.activeElement` properties.
|
|
76
|
+
*/
|
|
77
|
+
export class KeyboardEngine {
|
|
78
|
+
cdp;
|
|
79
|
+
/**
|
|
80
|
+
* Create a new KeyboardEngine.
|
|
81
|
+
*
|
|
82
|
+
* @param cdp - CDP session to send commands through.
|
|
83
|
+
*/
|
|
84
|
+
constructor(cdp) {
|
|
85
|
+
this.cdp = cdp;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Press a keyboard key via CDP `Input.dispatchKeyEvent`.
|
|
89
|
+
*
|
|
90
|
+
* Dispatches a `keyDown` event followed by a `keyUp` event, matching
|
|
91
|
+
* the behavior of a physical key press.
|
|
92
|
+
*
|
|
93
|
+
* @param key - Key name from {@link KEY_DEFINITIONS}
|
|
94
|
+
* (e.g. `'Tab'`, `'Enter'`, `'ArrowDown'`).
|
|
95
|
+
* @param modifiers - Optional modifier keys to hold during the press.
|
|
96
|
+
*
|
|
97
|
+
* @throws Error if the key is not in {@link KEY_DEFINITIONS}.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* await keyboard.press('Tab');
|
|
102
|
+
* await keyboard.press('Tab', { shift: true }); // Shift+Tab
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
async press(key, modifiers) {
|
|
106
|
+
const keyDef = KEY_DEFINITIONS[key];
|
|
107
|
+
if (!keyDef) {
|
|
108
|
+
const supported = Object.keys(KEY_DEFINITIONS).join(', ');
|
|
109
|
+
throw new Error(`Unknown key: "${key}". Supported keys: ${supported}`);
|
|
110
|
+
}
|
|
111
|
+
const mod = computeModifiers(modifiers);
|
|
112
|
+
await this.cdp.send('Input.dispatchKeyEvent', {
|
|
113
|
+
type: 'keyDown',
|
|
114
|
+
key: keyDef.key,
|
|
115
|
+
code: keyDef.code,
|
|
116
|
+
windowsVirtualKeyCode: keyDef.keyCode,
|
|
117
|
+
nativeVirtualKeyCode: keyDef.keyCode,
|
|
118
|
+
modifiers: mod,
|
|
119
|
+
});
|
|
120
|
+
await this.cdp.send('Input.dispatchKeyEvent', {
|
|
121
|
+
type: 'keyUp',
|
|
122
|
+
key: keyDef.key,
|
|
123
|
+
code: keyDef.code,
|
|
124
|
+
windowsVirtualKeyCode: keyDef.keyCode,
|
|
125
|
+
nativeVirtualKeyCode: keyDef.keyCode,
|
|
126
|
+
modifiers: mod,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get information about the currently focused DOM element.
|
|
131
|
+
*
|
|
132
|
+
* Uses `Runtime.evaluate` to query `document.activeElement` and
|
|
133
|
+
* extract its properties.
|
|
134
|
+
*
|
|
135
|
+
* @returns The focused element info, or `null` if no interactive
|
|
136
|
+
* element has focus (e.g. body or document is focused).
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* await keyboard.press('Tab');
|
|
141
|
+
* const el = await keyboard.getFocusedElement();
|
|
142
|
+
* console.log(el?.tag); // 'BUTTON'
|
|
143
|
+
* console.log(el?.id); // 'submit-btn'
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
async getFocusedElement() {
|
|
147
|
+
const result = (await this.cdp.send('Runtime.evaluate', {
|
|
148
|
+
expression: GET_FOCUSED_ELEMENT_JS,
|
|
149
|
+
returnByValue: true,
|
|
150
|
+
}));
|
|
151
|
+
return result.result.value ?? null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the keyboard engine.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* CDP keyboard event parameter definition for a single key.
|
|
8
|
+
*/
|
|
9
|
+
export interface KeyDefinition {
|
|
10
|
+
/** The `key` property for CDP (e.g. `'Tab'`, `'Enter'`). */
|
|
11
|
+
key: string;
|
|
12
|
+
/** The `code` property for CDP (e.g. `'Tab'`, `'Enter'`). */
|
|
13
|
+
code: string;
|
|
14
|
+
/** The Windows virtual key code (e.g. `9` for Tab). */
|
|
15
|
+
keyCode: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Modifier keys that can be held during a key press.
|
|
19
|
+
*
|
|
20
|
+
* Maps to CDP's `Input.dispatchKeyEvent` `modifiers` bitmask:
|
|
21
|
+
* - Alt = 1
|
|
22
|
+
* - Ctrl = 2
|
|
23
|
+
* - Meta = 4
|
|
24
|
+
* - Shift = 8
|
|
25
|
+
*/
|
|
26
|
+
export interface ModifierKeys {
|
|
27
|
+
shift?: boolean;
|
|
28
|
+
ctrl?: boolean;
|
|
29
|
+
alt?: boolean;
|
|
30
|
+
meta?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Information about the currently focused DOM element.
|
|
34
|
+
*
|
|
35
|
+
* Obtained via `Runtime.evaluate` querying `document.activeElement`.
|
|
36
|
+
*/
|
|
37
|
+
export interface FocusedElementInfo {
|
|
38
|
+
/** The tag name (e.g. `'BUTTON'`, `'A'`, `'INPUT'`). */
|
|
39
|
+
tag: string;
|
|
40
|
+
/** The element's `id` attribute, or empty string. */
|
|
41
|
+
id: string;
|
|
42
|
+
/** The element's `className` string. */
|
|
43
|
+
className: string;
|
|
44
|
+
/** Trimmed text content of the element. */
|
|
45
|
+
textContent: string;
|
|
46
|
+
/** The element's `role` attribute, or empty string. */
|
|
47
|
+
role: string;
|
|
48
|
+
/** The element's `aria-label` attribute, or empty string. */
|
|
49
|
+
ariaLabel: string;
|
|
50
|
+
/** The element's `tabIndex` property. */
|
|
51
|
+
tabIndex: number;
|
|
52
|
+
/** The element's bounding rectangle. */
|
|
53
|
+
rect: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,EAAE;QACJ,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@a11y-oracle/keyboard-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Native CDP keyboard dispatch with modifier key support and focused element introspection",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "a11y-oracle",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/a11y-oracle/a11y-oracle.git",
|
|
10
|
+
"directory": "libs/keyboard-engine"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/a11y-oracle/a11y-oracle/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/a11y-oracle/a11y-oracle/tree/main/libs/keyboard-engine",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"accessibility",
|
|
18
|
+
"a11y",
|
|
19
|
+
"keyboard",
|
|
20
|
+
"cdp",
|
|
21
|
+
"chrome-devtools-protocol",
|
|
22
|
+
"wcag",
|
|
23
|
+
"testing"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
"./package.json": "./package.json",
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"!**/*.tsbuildinfo"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@a11y-oracle/cdp-types": "1.0.0",
|
|
46
|
+
"devtools-protocol": "^0.0.1591961",
|
|
47
|
+
"tslib": "^2.3.0"
|
|
48
|
+
}
|
|
49
|
+
}
|