wtr-a11y-tree-plugin 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +155 -2
- package/dist/browser.d.ts +15 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +28 -0
- package/dist/browser.js.map +1 -0
- package/dist/chai.d.ts +27 -0
- package/dist/chai.d.ts.map +1 -0
- package/dist/chai.js +65 -0
- package/dist/chai.js.map +1 -0
- package/dist/format.d.ts +44 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +182 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/tree.d.ts +18 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +92 -0
- package/dist/tree.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Serhii Kulykov <iamkulykov@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,156 @@
|
|
|
1
|
-
#
|
|
1
|
+
# wtr-a11y-tree-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Read the Chrome accessibility tree in Web Test Runner tests.
|
|
4
|
+
|
|
5
|
+
The package has three parts:
|
|
6
|
+
|
|
7
|
+
- Server plugin that reads a11y tree through the Chrome DevTools Protocol.
|
|
8
|
+
- `getA11yTree()` browser command that returns the tree of an element.
|
|
9
|
+
- Chai assertions: `matchA11yTree()` and `equalA11yTreeSnapshot()`.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install --save-dev wtr-a11y-tree-plugin
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Pre-requisites: `@web/test-runner` and `@web/test-runner-playwright` (Chrome only).
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
Add the plugin to the Web Test Runner config:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { playwrightLauncher } from '@web/test-runner-playwright';
|
|
25
|
+
import { a11yTreePlugin } from 'wtr-a11y-tree-plugin';
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
nodeResolve: true,
|
|
29
|
+
browsers: [playwrightLauncher({ product: 'chromium' })],
|
|
30
|
+
plugins: [a11yTreePlugin()],
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Set `channel: 'chrome'` to use local Chrome e.g. in GitHub Actions:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { playwrightLauncher } from '@web/test-runner-playwright';
|
|
38
|
+
import { a11yTreePlugin } from 'wtr-a11y-tree-plugin';
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
nodeResolve: true,
|
|
42
|
+
browsers: [
|
|
43
|
+
playwrightLauncher({
|
|
44
|
+
product: 'chromium',
|
|
45
|
+
launchOptions: {
|
|
46
|
+
channel: 'chrome',
|
|
47
|
+
headless: true,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
plugins: [a11yTreePlugin()],
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Note: snapshots can differ depending on the Chrome or Playwright Chromium version.
|
|
56
|
+
|
|
57
|
+
## Getting a11y tree
|
|
58
|
+
|
|
59
|
+
Call `getA11yTree()` with an element. The result is a list of `A11yNode` objects.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { getA11yTree } from 'wtr-a11y-tree-plugin/browser';
|
|
63
|
+
|
|
64
|
+
const tree = await getA11yTree(document.querySelector('my-button'));
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each node has these fields:
|
|
68
|
+
|
|
69
|
+
- `role`: the accessibility role.
|
|
70
|
+
- `name`: the accessible name.
|
|
71
|
+
- `value`: the value, when there is one.
|
|
72
|
+
- `description`: the accessible description, when there is one.
|
|
73
|
+
- `states`: the states, such as `expanded` or `pressed`.
|
|
74
|
+
- `relations`: the targets of `controls`, `owns` and similar relations, as `{ role, name }`.
|
|
75
|
+
- `children`: the child nodes.
|
|
76
|
+
|
|
77
|
+
Text nodes have the role `text` and only a name.
|
|
78
|
+
|
|
79
|
+
## Chai assertions
|
|
80
|
+
|
|
81
|
+
Register the plugin with `chai.use()`. The plugin does not import chai. It uses the chai instance that you pass.
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import * as chai from 'chai';
|
|
85
|
+
import { chaiA11yTree } from 'wtr-a11y-tree-plugin/chai';
|
|
86
|
+
|
|
87
|
+
chai.use(chaiA11yTree);
|
|
88
|
+
const { expect } = chai;
|
|
89
|
+
|
|
90
|
+
it('exposes the combobox', async () => {
|
|
91
|
+
await expect(element).to.matchA11yTree(`
|
|
92
|
+
- combobox "Size" [expanded]:
|
|
93
|
+
- /value: Medium
|
|
94
|
+
`);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('keeps the tree', async () => {
|
|
98
|
+
await expect(element).to.equalA11yTreeSnapshot();
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
TypeScript users need `@types/chai`. The `wtr-a11y-tree-plugin/chai` module adds both methods to `Chai.Assertion`.
|
|
103
|
+
|
|
104
|
+
## Snapshot format
|
|
105
|
+
|
|
106
|
+
The format follows Playwright aria snapshots. Each line is a node.
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
- text: Size
|
|
110
|
+
- combobox "Size" [haspopup=listbox, expanded]:
|
|
111
|
+
- /value: Medium
|
|
112
|
+
- /activedescendant: option "Medium"
|
|
113
|
+
- /controls: listbox "Size"
|
|
114
|
+
- listbox "Size" [orientation=vertical]:
|
|
115
|
+
- option "Small"
|
|
116
|
+
- option "Medium" [selected]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The rules are:
|
|
120
|
+
|
|
121
|
+
- **Role and name**: A node starts with its role. The name follows in double quotes.
|
|
122
|
+
- **States**: States appear in square brackets. A true state prints its lowercase name. Other states print as `name=value`.
|
|
123
|
+
- **False states**: Only `expanded=false` and `pressed=false` print. Other false states do not.
|
|
124
|
+
- **Value**: A value prints as `/value`. A single text child prints as an inline value.
|
|
125
|
+
- **Description**: A description prints as `/description`.
|
|
126
|
+
- **Relations**: Each relation target prints as `/relation: role "name"`.
|
|
127
|
+
|
|
128
|
+
## Matching rules
|
|
129
|
+
|
|
130
|
+
`matchA11yTree()` checks that the expected tree is a subset of the actual tree. It does not require an exact match.
|
|
131
|
+
|
|
132
|
+
- **Subset**: The actual tree may have more nodes, states and properties.
|
|
133
|
+
- **Order**: Expected siblings must appear in the same order as in the actual tree.
|
|
134
|
+
- **Depth**: The first level of the expected tree may match nodes at any depth. Nested levels must match children.
|
|
135
|
+
- **Omitted parts**: A name or value that you do not write matches anything.
|
|
136
|
+
|
|
137
|
+
When the match fails, the assertion error holds the serialized actual tree. Test reporters show the difference.
|
|
138
|
+
|
|
139
|
+
## Other exports
|
|
140
|
+
|
|
141
|
+
The `wtr-a11y-tree-plugin/format` module runs in the browser and in Node. It exports these functions:
|
|
142
|
+
|
|
143
|
+
- `serializeA11yTree(nodes)`: converts `A11yNode[]` to the snapshot text.
|
|
144
|
+
- `parseDisplayTree(text)`: parses the snapshot text.
|
|
145
|
+
- `matchDisplayList(expected, actual, deep)`: applies the matching rules.
|
|
146
|
+
- `dedent(text)`: removes the common indentation of a template literal.
|
|
147
|
+
|
|
148
|
+
## Limitations
|
|
149
|
+
|
|
150
|
+
- Works in Playwright Chromium and Chrome only. Puppeteer and `@web/test-runner-chrome` are not supported.
|
|
151
|
+
- `equalA11yTreeSnapshot()` works in Mocha tests only. It uses the snapshot support of Web Test Runner.
|
|
152
|
+
- Pass `--update-snapshots` to Web Test Runner to rewrite saved snapshots.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { A11yNode } from './types.ts';
|
|
2
|
+
export type { A11yNode } from './types.ts';
|
|
3
|
+
declare global {
|
|
4
|
+
var __a11yTreeTargets: Map<string, Element> | undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns the Chromium accessibility tree of an element: the roles, names,
|
|
8
|
+
* descriptions, values, states and relations of the element and its
|
|
9
|
+
* descendants, including shadow DOM and slotted content.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const tree = await getA11yTree(document.querySelector('vaadin-text-field'));
|
|
13
|
+
*/
|
|
14
|
+
export declare function getA11yTree(element: Element): Promise<A11yNode[]>;
|
|
15
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,YAAY,CAAC;AAE5D,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD;AAID;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAWvE"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `getA11yTree()` is copied from vaadin/web-components test/test-runner-commands/index.js
|
|
3
|
+
* on proto/aria-snapshot at a9833531f6.
|
|
4
|
+
*/
|
|
5
|
+
import { executeServerCommand } from '@web/test-runner-commands';
|
|
6
|
+
let a11yTreeId = 0;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the Chromium accessibility tree of an element: the roles, names,
|
|
9
|
+
* descriptions, values, states and relations of the element and its
|
|
10
|
+
* descendants, including shadow DOM and slotted content.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const tree = await getA11yTree(document.querySelector('vaadin-text-field'));
|
|
14
|
+
*/
|
|
15
|
+
export async function getA11yTree(element) {
|
|
16
|
+
a11yTreeId += 1;
|
|
17
|
+
const id = `${a11yTreeId}`;
|
|
18
|
+
// Commands can only receive JSON, so the server reads the element from a global map.
|
|
19
|
+
globalThis.__a11yTreeTargets ||= new Map();
|
|
20
|
+
globalThis.__a11yTreeTargets.set(id, element);
|
|
21
|
+
try {
|
|
22
|
+
return (await executeServerCommand('get-a11y-tree', { id }));
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
globalThis.__a11yTreeTargets.delete(id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AASjE,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB;IAChD,UAAU,IAAI,CAAC,CAAC;IAChB,MAAM,EAAE,GAAG,GAAG,UAAU,EAAE,CAAC;IAC3B,qFAAqF;IACrF,UAAU,CAAC,iBAAiB,KAAK,IAAI,GAAG,EAAE,CAAC;IAC3C,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,oBAAoB,CAAC,eAAe,EAAE,EAAE,EAAE,EAA4B,CAAC,CAAe,CAAC;IACvG,CAAC;YAAS,CAAC;QACT,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC"}
|
package/dist/chai.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/// <reference types="chai" preserve="true" />
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Chai {
|
|
4
|
+
interface Assertion {
|
|
5
|
+
/**
|
|
6
|
+
* Compares the Chromium accessibility tree of an element with the one saved for the current test.
|
|
7
|
+
*/
|
|
8
|
+
equalA11yTreeSnapshot(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Checks that the Chromium accessibility tree of an element contains the nodes of the expected tree.
|
|
11
|
+
*/
|
|
12
|
+
matchA11yTree(expected: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Registers `matchA11yTree()` and `equalA11yTreeSnapshot()`. It does not
|
|
18
|
+
* import chai: `chai.use()` passes the instance of the consumer.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import * as chai from 'chai';
|
|
22
|
+
* import { chaiA11yTree } from 'wtr-a11y-tree-plugin/chai';
|
|
23
|
+
*
|
|
24
|
+
* chai.use(chaiA11yTree);
|
|
25
|
+
*/
|
|
26
|
+
export declare const chaiA11yTree: Chai.ChaiPlugin;
|
|
27
|
+
//# sourceMappingURL=chai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chai.d.ts","sourceRoot":"","sources":["../src/chai.ts"],"names":[],"mappings":";AAgBA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI,CAAC;QACb,UAAU,SAAS;YACjB;;eAEG;YACH,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;YAEvC;;eAEG;YACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;SAChD;KACF;CACF;AAwDD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,EAAE,IAAI,CAAC,UAG/B,CAAC"}
|
package/dist/chai.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copied from vaadin/web-components test/plugins/index.js and index.d.ts
|
|
3
|
+
* on proto/aria-snapshot at a9833531f6.
|
|
4
|
+
*/
|
|
5
|
+
/// <reference types="chai" preserve="true" />
|
|
6
|
+
import { getSnapshot, getSnapshotConfig, saveSnapshot } from '@web/test-runner-commands';
|
|
7
|
+
import { getA11yTree } from "./browser.js";
|
|
8
|
+
import { dedent, matchDisplayList, parseDisplayTree, serializeA11yTree, serializeDisplayTree, toDisplayTree, } from "./format.js";
|
|
9
|
+
/**
|
|
10
|
+
* Adds `matchA11yTree(expected)`, which checks that the Chromium accessibility
|
|
11
|
+
* tree of an element contains the nodes of the expected tree. The expected tree
|
|
12
|
+
* uses the snapshot format and lists only what the test cares about.
|
|
13
|
+
*/
|
|
14
|
+
const chaiA11yTreeMatch = (chai, utils) => {
|
|
15
|
+
async function matchA11yTree(expected) {
|
|
16
|
+
const element = utils.flag(this, 'object');
|
|
17
|
+
const actual = toDisplayTree(await getA11yTree(element));
|
|
18
|
+
if (!matchDisplayList(parseDisplayTree(expected), actual, true)) {
|
|
19
|
+
throw new chai.AssertionError('expected accessibility tree to contain the expected nodes', { actual: serializeDisplayTree(actual), expected: dedent(expected), showDiff: true }, utils.flag(this, 'ssfi'));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
utils.addMethod(chai.Assertion.prototype, 'matchA11yTree', matchA11yTree);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Adds `equalA11yTreeSnapshot()`, which compares the Chromium accessibility
|
|
26
|
+
* tree of an element with the one saved for the current test, and saves it
|
|
27
|
+
* when there is none or when the test runner is called with `--update-snapshots`.
|
|
28
|
+
*/
|
|
29
|
+
const chaiA11yTreeSnapshot = (chai, utils) => {
|
|
30
|
+
async function equalA11yTreeSnapshot() {
|
|
31
|
+
const runner = globalThis.__WTR_MOCHA_RUNNER__;
|
|
32
|
+
if (!runner) {
|
|
33
|
+
throw new Error('equalA11yTreeSnapshot() works only in Mocha tests run by Web Test Runner.');
|
|
34
|
+
}
|
|
35
|
+
const element = utils.flag(this, 'object');
|
|
36
|
+
const name = runner.test.titlePath().join(' ');
|
|
37
|
+
const snapshot = serializeA11yTree(await getA11yTree(element));
|
|
38
|
+
const currentSnapshot = await getSnapshot({ name });
|
|
39
|
+
const config = await getSnapshotConfig();
|
|
40
|
+
if (currentSnapshot && !config.updateSnapshots) {
|
|
41
|
+
if (currentSnapshot !== snapshot) {
|
|
42
|
+
throw new chai.AssertionError(`Accessibility tree snapshot ${name} does not match the saved snapshot on disk`, { actual: snapshot, expected: currentSnapshot, showDiff: true }, utils.flag(this, 'ssfi'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (currentSnapshot !== snapshot) {
|
|
46
|
+
await saveSnapshot({ name, content: snapshot });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
utils.addMethod(chai.Assertion.prototype, 'equalA11yTreeSnapshot', equalA11yTreeSnapshot);
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Registers `matchA11yTree()` and `equalA11yTreeSnapshot()`. It does not
|
|
53
|
+
* import chai: `chai.use()` passes the instance of the consumer.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* import * as chai from 'chai';
|
|
57
|
+
* import { chaiA11yTree } from 'wtr-a11y-tree-plugin/chai';
|
|
58
|
+
*
|
|
59
|
+
* chai.use(chaiA11yTree);
|
|
60
|
+
*/
|
|
61
|
+
export const chaiA11yTree = (chai, utils) => {
|
|
62
|
+
chaiA11yTreeMatch(chai, utils);
|
|
63
|
+
chaiA11yTreeSnapshot(chai, utils);
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=chai.js.map
|
package/dist/chai.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chai.js","sourceRoot":"","sources":["../src/chai.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copied from vaadin/web-components test/plugins/index.js
|
|
3
|
+
* on proto/aria-snapshot at a9833531f6.
|
|
4
|
+
*/
|
|
5
|
+
import type { A11yNode } from './types.ts';
|
|
6
|
+
export type DisplayNode = {
|
|
7
|
+
role: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
states: string[];
|
|
10
|
+
value?: string;
|
|
11
|
+
props: Array<[string, string]>;
|
|
12
|
+
children: DisplayNode[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Converts the tree returned by `getA11yTree()` into the nodes that the
|
|
16
|
+
* snapshot prints: formatted states, descriptions and relations as properties,
|
|
17
|
+
* and a single text child as the inline value.
|
|
18
|
+
*/
|
|
19
|
+
export declare const toDisplayTree: (nodes: A11yNode[]) => DisplayNode[];
|
|
20
|
+
/**
|
|
21
|
+
* Serializes an accessibility tree to YAML in the format of Playwright aria
|
|
22
|
+
* snapshots. Descriptions and relations are printed as `/description` and
|
|
23
|
+
* `/<relation>` properties, like `/url` in Playwright.
|
|
24
|
+
*/
|
|
25
|
+
export declare const serializeDisplayTree: (nodes: DisplayNode[], indent?: string) => string;
|
|
26
|
+
export declare const serializeA11yTree: (nodes: A11yNode[]) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Removes the common indentation and the blank first and last lines of a
|
|
29
|
+
* template literal.
|
|
30
|
+
*/
|
|
31
|
+
export declare const dedent: (text: string) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Parses a tree in the snapshot format into display nodes. Values that are
|
|
34
|
+
* not written, such as a missing name, are left undefined and match anything.
|
|
35
|
+
*/
|
|
36
|
+
export declare const parseDisplayTree: (text: string) => DisplayNode[];
|
|
37
|
+
/**
|
|
38
|
+
* Checks that the expected nodes appear in the actual nodes in the same order.
|
|
39
|
+
* Other nodes, states and properties are allowed. With `deep`, an expected node
|
|
40
|
+
* can match an actual node at any depth, which is used for the top level only,
|
|
41
|
+
* so that the expected tree can start below containers.
|
|
42
|
+
*/
|
|
43
|
+
export declare function matchDisplayList(expected: DisplayNode[], actual: DisplayNode[], deep?: boolean): boolean;
|
|
44
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB,CAAC;AAiCF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,QAAQ,EAAE,KAAG,WAAW,EA4BzD,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,WAAW,EAAE,EAAE,eAAW,KAAG,MAuB1D,CAAC;AAEhB,eAAO,MAAM,iBAAiB,GAAI,OAAO,QAAQ,EAAE,KAAG,MAAoD,CAAC;AAK3G;;;GAGG;AACH,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,MAIrC,CAAC;AAQF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,WAAW,EA2C1D,CAAC;AAmBF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,UAAQ,GAAG,OAAO,CAoBtG"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// States printed even when `false`, as `false` differs from a missing state.
|
|
2
|
+
const A11Y_FALSE_STATES = new Set(['expanded', 'pressed']);
|
|
3
|
+
// Snapshots are stored in template literals, which turn escape sequences
|
|
4
|
+
// into characters on read, so quote without escaping.
|
|
5
|
+
const quote = (text) => `"${text}"`;
|
|
6
|
+
const formatText = (text) => (/^[\w$.,!?()•@ ]+$/u.test(text) && !/^\d/u.test(text) ? text : quote(text));
|
|
7
|
+
const formatStates = (node) => Object.entries(node.states || {})
|
|
8
|
+
.map(([key, value]) => {
|
|
9
|
+
if (value === true || value === 'true') {
|
|
10
|
+
return key.toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
if (value === false || value === 'false') {
|
|
13
|
+
return A11Y_FALSE_STATES.has(key) ? `${key}=false` : null;
|
|
14
|
+
}
|
|
15
|
+
if (value === '' || (key === 'valuetext' && String(value) === node.value)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return `${key.toLowerCase()}=${value}`;
|
|
19
|
+
})
|
|
20
|
+
.filter((state) => state !== null);
|
|
21
|
+
/**
|
|
22
|
+
* Removes text that repeats the parent name, such as the label of a button.
|
|
23
|
+
*/
|
|
24
|
+
const normalizeChildren = (children, parentName) => (children || []).filter((child) => !(child.role === 'text' && child.name === parentName));
|
|
25
|
+
/**
|
|
26
|
+
* Converts the tree returned by `getA11yTree()` into the nodes that the
|
|
27
|
+
* snapshot prints: formatted states, descriptions and relations as properties,
|
|
28
|
+
* and a single text child as the inline value.
|
|
29
|
+
*/
|
|
30
|
+
export const toDisplayTree = (nodes) => normalizeChildren(nodes).map((node) => {
|
|
31
|
+
if (node.role === 'text') {
|
|
32
|
+
return { role: 'text', name: node.name, states: [], props: [], children: [] };
|
|
33
|
+
}
|
|
34
|
+
let children = normalizeChildren(node.children, node.name);
|
|
35
|
+
let value = node.value;
|
|
36
|
+
if (value === undefined && children.length === 1 && children[0].role === 'text') {
|
|
37
|
+
value = children[0].name;
|
|
38
|
+
children = [];
|
|
39
|
+
}
|
|
40
|
+
const props = [];
|
|
41
|
+
if (node.description) {
|
|
42
|
+
props.push(['description', node.description]);
|
|
43
|
+
}
|
|
44
|
+
Object.entries(node.relations || {}).forEach(([key, targets]) => {
|
|
45
|
+
targets.forEach(({ role, name }) => {
|
|
46
|
+
props.push([key, `${role}${name ? ` ${quote(name)}` : ''}`]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
role: node.role,
|
|
51
|
+
name: node.name,
|
|
52
|
+
states: formatStates(node),
|
|
53
|
+
value,
|
|
54
|
+
props,
|
|
55
|
+
children: toDisplayTree(children),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Serializes an accessibility tree to YAML in the format of Playwright aria
|
|
60
|
+
* snapshots. Descriptions and relations are printed as `/description` and
|
|
61
|
+
* `/<relation>` properties, like `/url` in Playwright.
|
|
62
|
+
*/
|
|
63
|
+
export const serializeDisplayTree = (nodes, indent = '') => nodes
|
|
64
|
+
.map((node) => {
|
|
65
|
+
if (node.role === 'text') {
|
|
66
|
+
return `${indent}- text: ${formatText(node.name)}`;
|
|
67
|
+
}
|
|
68
|
+
let line = `${indent}- ${node.role}`;
|
|
69
|
+
if (node.name) {
|
|
70
|
+
line += ` ${quote(node.name)}`;
|
|
71
|
+
}
|
|
72
|
+
if (node.states.length) {
|
|
73
|
+
line += ` [${node.states.join(', ')}]`;
|
|
74
|
+
}
|
|
75
|
+
const props = node.props.map(([key, value]) => `${indent} - /${key}: ${key === 'description' ? formatText(value) : value}`);
|
|
76
|
+
const nested = node.children.length ? serializeDisplayTree(node.children, `${indent} `) : '';
|
|
77
|
+
if (!props.length && !nested) {
|
|
78
|
+
return node.value === undefined ? line : `${line}: ${formatText(node.value)}`;
|
|
79
|
+
}
|
|
80
|
+
const head = node.value === undefined ? `${line}:` : `${line}:\n${indent} - /value: ${formatText(node.value)}`;
|
|
81
|
+
return [head, ...props, nested].filter(Boolean).join('\n');
|
|
82
|
+
})
|
|
83
|
+
.join('\n');
|
|
84
|
+
export const serializeA11yTree = (nodes) => serializeDisplayTree(toDisplayTree(nodes));
|
|
85
|
+
const unquote = (text) => text.length > 1 && text.startsWith('"') && text.endsWith('"') ? text.slice(1, -1) : text;
|
|
86
|
+
/**
|
|
87
|
+
* Removes the common indentation and the blank first and last lines of a
|
|
88
|
+
* template literal.
|
|
89
|
+
*/
|
|
90
|
+
export const dedent = (text) => {
|
|
91
|
+
const lines = text.replace(/^\s*\n|\n\s*$/gu, '').split('\n');
|
|
92
|
+
const indent = Math.min(...lines.filter((line) => line.trim()).map((line) => line.match(/^ */u)[0].length));
|
|
93
|
+
return lines.map((line) => line.slice(indent)).join('\n');
|
|
94
|
+
};
|
|
95
|
+
const NODE_PATTERN = /^(?<role>[\w-]+)(?: "(?<name>[^"]*)")?(?: \[(?<states>[^\]]*)\])?(?::(?: (?<value>.+))?)?$/u;
|
|
96
|
+
/**
|
|
97
|
+
* Parses a tree in the snapshot format into display nodes. Values that are
|
|
98
|
+
* not written, such as a missing name, are left undefined and match anything.
|
|
99
|
+
*/
|
|
100
|
+
export const parseDisplayTree = (text) => {
|
|
101
|
+
const root = { children: [] };
|
|
102
|
+
const stack = [{ indent: -1, node: root }];
|
|
103
|
+
dedent(text)
|
|
104
|
+
.split('\n')
|
|
105
|
+
.filter((line) => line.trim())
|
|
106
|
+
.forEach((line) => {
|
|
107
|
+
const indent = line.match(/^ */u)[0].length;
|
|
108
|
+
const content = line.trim().replace(/^- /u, '');
|
|
109
|
+
while (stack[stack.length - 1].indent >= indent) {
|
|
110
|
+
stack.pop();
|
|
111
|
+
}
|
|
112
|
+
const parent = stack[stack.length - 1].node;
|
|
113
|
+
const prop = content.match(/^\/(?<key>[\w-]+): (?<value>.+)$/u);
|
|
114
|
+
if (prop) {
|
|
115
|
+
const { key, value } = prop.groups;
|
|
116
|
+
if (key === 'value') {
|
|
117
|
+
parent.value = unquote(value);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
parent.props.push([key, key === 'description' ? unquote(value) : value]);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const match = content.match(NODE_PATTERN);
|
|
125
|
+
if (!match) {
|
|
126
|
+
throw new Error(`Cannot parse accessibility tree line: ${line}`);
|
|
127
|
+
}
|
|
128
|
+
const { role, name, states, value } = match.groups;
|
|
129
|
+
const node = role === 'text'
|
|
130
|
+
? { role, name: value === undefined ? undefined : unquote(value) }
|
|
131
|
+
: {
|
|
132
|
+
role,
|
|
133
|
+
name,
|
|
134
|
+
states: states ? states.split(', ') : [],
|
|
135
|
+
value: value === undefined ? undefined : unquote(value),
|
|
136
|
+
props: [],
|
|
137
|
+
children: [],
|
|
138
|
+
};
|
|
139
|
+
parent.children.push(node);
|
|
140
|
+
stack.push({ indent, node });
|
|
141
|
+
});
|
|
142
|
+
return root.children;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Lists the nodes of a tree in document order, each with the index after its
|
|
146
|
+
* subtree, so that a match can skip the rest of the subtree.
|
|
147
|
+
*/
|
|
148
|
+
const flattenDisplayTree = (nodes, result = []) => {
|
|
149
|
+
nodes.forEach((node) => {
|
|
150
|
+
const entry = { node, end: 0 };
|
|
151
|
+
result.push(entry);
|
|
152
|
+
flattenDisplayTree(node.children || [], result);
|
|
153
|
+
entry.end = result.length;
|
|
154
|
+
});
|
|
155
|
+
return result;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Checks that the expected nodes appear in the actual nodes in the same order.
|
|
159
|
+
* Other nodes, states and properties are allowed. With `deep`, an expected node
|
|
160
|
+
* can match an actual node at any depth, which is used for the top level only,
|
|
161
|
+
* so that the expected tree can start below containers.
|
|
162
|
+
*/
|
|
163
|
+
export function matchDisplayList(expected, actual, deep = false) {
|
|
164
|
+
const matchNode = (exp, act) => exp.role === act.role &&
|
|
165
|
+
(exp.name === undefined || exp.name === act.name) &&
|
|
166
|
+
(exp.role === 'text' ||
|
|
167
|
+
((exp.value === undefined || exp.value === act.value) &&
|
|
168
|
+
exp.states.every((state) => act.states.includes(state)) &&
|
|
169
|
+
exp.props.every(([key, value]) => act.props.some(([k, v]) => k === key && v === value)) &&
|
|
170
|
+
matchDisplayList(exp.children, act.children)));
|
|
171
|
+
const flat = deep ? flattenDisplayTree(actual) : actual.map((node, i) => ({ node, end: i + 1 }));
|
|
172
|
+
let cursor = 0;
|
|
173
|
+
return expected.every((node) => {
|
|
174
|
+
const index = flat.findIndex((entry, i) => i >= cursor && matchNode(node, entry.node));
|
|
175
|
+
if (index === -1) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
cursor = flat[index].end;
|
|
179
|
+
return true;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `a11yTreePlugin()` is copied from vaadin/web-components wtr-utils.js
|
|
3
|
+
* on proto/aria-snapshot at a9833531f6.
|
|
4
|
+
*/
|
|
5
|
+
import type { TestRunnerPlugin } from '@web/test-runner';
|
|
6
|
+
import type { A11yTreePayload } from './types.ts';
|
|
7
|
+
export type { A11yNode } from './types.ts';
|
|
8
|
+
/**
|
|
9
|
+
* A test runner command that returns the Chromium accessibility tree of the
|
|
10
|
+
* element marked by `getA11yTree()`. It works in Playwright Chromium only.
|
|
11
|
+
*/
|
|
12
|
+
export declare const a11yTreePlugin: () => TestRunnerPlugin<A11yTreePayload>;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAO,gBAAgB,CAAC,eAAe,CA6BhE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { toA11yTree } from "./tree.js";
|
|
2
|
+
/**
|
|
3
|
+
* A test runner command that returns the Chromium accessibility tree of the
|
|
4
|
+
* element marked by `getA11yTree()`. It works in Playwright Chromium only.
|
|
5
|
+
*/
|
|
6
|
+
export const a11yTreePlugin = () => ({
|
|
7
|
+
name: 'a11y-tree-command',
|
|
8
|
+
async executeCommand({ command, payload, session }) {
|
|
9
|
+
if (command !== 'get-a11y-tree') {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const { id } = payload;
|
|
13
|
+
const browser = session.browser;
|
|
14
|
+
if (browser.type !== 'playwright' || browser.name !== 'Chromium') {
|
|
15
|
+
throw new Error(`Accessibility tree is not supported for browser ${browser.name}.`);
|
|
16
|
+
}
|
|
17
|
+
const page = browser.getPage(session.id);
|
|
18
|
+
const cdp = await page.context().newCDPSession(page);
|
|
19
|
+
try {
|
|
20
|
+
// The browser side stores the element in a global map, which also works
|
|
21
|
+
// for elements inside shadow roots.
|
|
22
|
+
const { result } = await cdp.send('Runtime.evaluate', {
|
|
23
|
+
expression: `globalThis.__a11yTreeTargets.get(${JSON.stringify(id)})`,
|
|
24
|
+
});
|
|
25
|
+
if (!result.objectId) {
|
|
26
|
+
throw new Error(`Cannot find the element for accessibility tree request ${id}.`);
|
|
27
|
+
}
|
|
28
|
+
const { node } = await cdp.send('DOM.describeNode', { objectId: result.objectId });
|
|
29
|
+
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
|
|
30
|
+
return toA11yTree(nodes, node.backendNodeId);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
await cdp.detach();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAKvC;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAsC,EAAE,CAAC,CAAC;IACtE,IAAI,EAAE,mBAAmB;IACzB,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;QAChD,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;YAChC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,EAAE,EAAE,GAAG,OAAQ,CAAC;QACxB,MAAM,OAAO,GAAG,OAAO,CAAC,OAA6B,CAAC;QACtD,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACjE,MAAM,IAAI,KAAK,CAAC,mDAAmD,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC;YACH,wEAAwE;YACxE,oCAAoC;YACpC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBACpD,UAAU,EAAE,oCAAoC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG;aACtE,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,0DAA0D,EAAE,GAAG,CAAC,CAAC;YACnF,CAAC;YACD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnF,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAChE,OAAO,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/C,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
|
package/dist/tree.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `toA11yTree()` is copied from vaadin/web-components wtr-utils.js
|
|
3
|
+
* on proto/aria-snapshot at a9833531f6.
|
|
4
|
+
*/
|
|
5
|
+
import type { CDPSession } from 'playwright-core';
|
|
6
|
+
import type { A11yNode } from './types.ts';
|
|
7
|
+
declare const cdp: CDPSession;
|
|
8
|
+
declare const fullAXTree: ReturnType<typeof cdp.send<'Accessibility.getFullAXTree'>>;
|
|
9
|
+
type AXNode = Awaited<typeof fullAXTree>['nodes'][number];
|
|
10
|
+
/**
|
|
11
|
+
* Converts the CDP accessibility tree below the given DOM node into a list of
|
|
12
|
+
* plain nodes. Ignored nodes, unnamed generic containers and noisy roles are
|
|
13
|
+
* skipped, and their children move up. Relations are resolved to the role and
|
|
14
|
+
* name of the target, as generated IDs change between runs.
|
|
15
|
+
*/
|
|
16
|
+
export declare const toA11yTree: (axNodes: AXNode[], backendNodeId: number) => A11yNode[];
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,CAAC,MAAM,GAAG,EAAE,UAAU,CAAC;AAC9B,OAAO,CAAC,MAAM,UAAU,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;AACrF,KAAK,MAAM,GAAG,OAAO,CAAC,OAAO,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAyC1D;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,EAAE,eAAe,MAAM,KAAG,QAAQ,EAsD7E,CAAC"}
|
package/dist/tree.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Accessibility tree properties kept in `get-a11y-tree` results. Others,
|
|
2
|
+
// such as `focusable` or `editable`, are set on most nodes and add noise.
|
|
3
|
+
const A11Y_TREE_STATES = new Set([
|
|
4
|
+
'autocomplete',
|
|
5
|
+
'checked',
|
|
6
|
+
'disabled',
|
|
7
|
+
'expanded',
|
|
8
|
+
'hasPopup',
|
|
9
|
+
'invalid',
|
|
10
|
+
'level',
|
|
11
|
+
'modal',
|
|
12
|
+
'multiselectable',
|
|
13
|
+
'orientation',
|
|
14
|
+
'pressed',
|
|
15
|
+
'readonly',
|
|
16
|
+
'required',
|
|
17
|
+
'selected',
|
|
18
|
+
'valuemax',
|
|
19
|
+
'valuemin',
|
|
20
|
+
'valuetext',
|
|
21
|
+
]);
|
|
22
|
+
const A11Y_TREE_RELATIONS = new Set(['activedescendant', 'controls', 'details', 'errormessage', 'owns']);
|
|
23
|
+
// Roles with no meaningful descendants: their text is already in the name or value.
|
|
24
|
+
const A11Y_TREE_LEAF_ROLES = new Set([
|
|
25
|
+
'checkbox',
|
|
26
|
+
'combobox',
|
|
27
|
+
'radio',
|
|
28
|
+
'searchbox',
|
|
29
|
+
'slider',
|
|
30
|
+
'spinbutton',
|
|
31
|
+
'switch',
|
|
32
|
+
'textbox',
|
|
33
|
+
]);
|
|
34
|
+
// Roles that only add noise to the tree. Their children are kept.
|
|
35
|
+
const A11Y_TREE_SKIPPED_ROLES = new Set(['InlineTextBox', 'LabelText', 'LineBreak', 'none']);
|
|
36
|
+
/**
|
|
37
|
+
* Converts the CDP accessibility tree below the given DOM node into a list of
|
|
38
|
+
* plain nodes. Ignored nodes, unnamed generic containers and noisy roles are
|
|
39
|
+
* skipped, and their children move up. Relations are resolved to the role and
|
|
40
|
+
* name of the target, as generated IDs change between runs.
|
|
41
|
+
*/
|
|
42
|
+
export const toA11yTree = (axNodes, backendNodeId) => {
|
|
43
|
+
const byId = new Map(axNodes.map((node) => [node.nodeId, node]));
|
|
44
|
+
const byBackendId = new Map(axNodes.map((node) => [node.backendDOMNodeId, node]));
|
|
45
|
+
const describe = (node) => ({
|
|
46
|
+
role: String(node.role?.value),
|
|
47
|
+
name: node.name?.value ?? '',
|
|
48
|
+
});
|
|
49
|
+
const convert = (node) => {
|
|
50
|
+
const role = node.role?.value;
|
|
51
|
+
const name = node.name?.value ?? '';
|
|
52
|
+
const children = A11Y_TREE_LEAF_ROLES.has(role)
|
|
53
|
+
? []
|
|
54
|
+
: (node.childIds || []).flatMap((id) => (byId.has(id) ? convert(byId.get(id)) : []));
|
|
55
|
+
if (node.ignored || A11Y_TREE_SKIPPED_ROLES.has(role) || (role === 'generic' && !name)) {
|
|
56
|
+
// Join the text of one container, such as a label, into one node. Text
|
|
57
|
+
// from different containers stays separate.
|
|
58
|
+
if (children.length > 1 && children.every((child) => child.role === 'text')) {
|
|
59
|
+
return [{ role: 'text', name: children.map((child) => child.name).join(' ') }];
|
|
60
|
+
}
|
|
61
|
+
return children;
|
|
62
|
+
}
|
|
63
|
+
if (role === 'StaticText') {
|
|
64
|
+
return name ? [{ role: 'text', name }] : [];
|
|
65
|
+
}
|
|
66
|
+
const states = {};
|
|
67
|
+
const relations = {};
|
|
68
|
+
const result = { role: role, name, states, relations, children };
|
|
69
|
+
const value = node.value?.value;
|
|
70
|
+
if (value !== undefined && value !== '') {
|
|
71
|
+
result.value = String(value);
|
|
72
|
+
}
|
|
73
|
+
if (node.description?.value) {
|
|
74
|
+
result.description = node.description.value;
|
|
75
|
+
}
|
|
76
|
+
(node.properties || []).forEach(({ name: key, value: { value: stateValue, relatedNodes } }) => {
|
|
77
|
+
if (A11Y_TREE_STATES.has(key)) {
|
|
78
|
+
states[key] = stateValue;
|
|
79
|
+
}
|
|
80
|
+
else if (A11Y_TREE_RELATIONS.has(key) && relatedNodes) {
|
|
81
|
+
relations[key] = relatedNodes
|
|
82
|
+
.map((related) => byBackendId.get(related.backendDOMNodeId))
|
|
83
|
+
.filter((related) => !!related)
|
|
84
|
+
.map(describe);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return [result];
|
|
88
|
+
};
|
|
89
|
+
const root = byBackendId.get(backendNodeId);
|
|
90
|
+
return root ? convert(root) : [];
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=tree.js.map
|
package/dist/tree.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type A11yNode = {
|
|
2
|
+
role: string;
|
|
3
|
+
name: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
states?: Record<string, boolean | number | string>;
|
|
7
|
+
relations?: Record<string, Array<{
|
|
8
|
+
role: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}>>;
|
|
11
|
+
children?: A11yNode[];
|
|
12
|
+
};
|
|
13
|
+
export type A11yTreePayload = {
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wtr-a11y-tree-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Chromium accessibility tree commands and chai assertions for Web Test Runner",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./browser": {
|
|
17
|
+
"types": "./dist/browser.d.ts",
|
|
18
|
+
"default": "./dist/browser.js"
|
|
19
|
+
},
|
|
20
|
+
"./chai": {
|
|
21
|
+
"types": "./dist/chai.d.ts",
|
|
22
|
+
"default": "./dist/chai.js"
|
|
23
|
+
},
|
|
24
|
+
"./format": {
|
|
25
|
+
"types": "./dist/format.d.ts",
|
|
26
|
+
"default": "./dist/format.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"lint": "tsc && prettier --check .",
|
|
35
|
+
"test": "npm run test:unit && npm run test:browser",
|
|
36
|
+
"test:unit": "node --test \"test/*.test.ts\"",
|
|
37
|
+
"test:browser": "npm run build && web-test-runner",
|
|
38
|
+
"prepack": "npm run build"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@web/test-runner-commands": "^1.0.1"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@types/chai": "*",
|
|
45
|
+
"@web/test-runner": "^1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@types/chai": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/chai": "^5.0.0",
|
|
54
|
+
"@types/mocha": "^10.0.10",
|
|
55
|
+
"@types/node": "^24.0.0",
|
|
56
|
+
"@web/test-runner": "^1.0.0",
|
|
57
|
+
"@web/test-runner-playwright": "^1.0.0",
|
|
58
|
+
"chai": "^6.2.2",
|
|
59
|
+
"playwright": "1.64.1",
|
|
60
|
+
"playwright-core": "1.64.1",
|
|
61
|
+
"prettier": "^3.9.4",
|
|
62
|
+
"typescript": "^6.0.3"
|
|
63
|
+
}
|
|
64
|
+
}
|