@web/test-runner-commands 1.0.3 → 1.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/dist/a11ySnapshotPlugin.d.ts.map +1 -1
- package/dist/a11ySnapshotPlugin.js +8 -2
- package/dist/a11ySnapshotPlugin.js.map +1 -1
- package/dist/chromiumA11ySnapshot.d.ts +36 -0
- package/dist/chromiumA11ySnapshot.d.ts.map +1 -0
- package/dist/chromiumA11ySnapshot.js +251 -0
- package/dist/chromiumA11ySnapshot.js.map +1 -0
- package/package.json +1 -1
- package/src/a11ySnapshotPlugin.ts +9 -2
- package/src/chromiumA11ySnapshot.ts +289 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a11ySnapshotPlugin.d.ts","sourceRoot":"","sources":["../src/a11ySnapshotPlugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"a11ySnapshotPlugin.d.ts","sourceRoot":"","sources":["../src/a11ySnapshotPlugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKzD,MAAM,MAAM,mBAAmB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,wBAAgB,kBAAkB,IAAI,gBAAgB,CAAC,mBAAmB,CAAC,CA+D1E"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.a11ySnapshotPlugin = a11ySnapshotPlugin;
|
|
4
|
+
const chromiumA11ySnapshot_js_1 = require("./chromiumA11ySnapshot.js");
|
|
4
5
|
function a11ySnapshotPlugin() {
|
|
5
6
|
return {
|
|
6
7
|
name: 'a11y-snapshot-command',
|
|
@@ -12,8 +13,13 @@ function a11ySnapshotPlugin() {
|
|
|
12
13
|
// Playwright 1.57 removed page.accessibility
|
|
13
14
|
const { accessibility } = page;
|
|
14
15
|
if (!accessibility) {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
// Chromium exposes the same tree through the Chrome DevTools Protocol
|
|
17
|
+
if (session.browser.name === 'Chromium') {
|
|
18
|
+
const root = payload && payload.selector ? await page.$(payload.selector) : null;
|
|
19
|
+
return (0, chromiumA11ySnapshot_js_1.chromiumA11ySnapshot)(page, root);
|
|
20
|
+
}
|
|
21
|
+
throw new Error('Accessibility snapshot is not supported for Firefox and WebKit in Playwright 1.57 and later. ' +
|
|
22
|
+
'Use Chromium, or Playwright 1.56 or earlier.');
|
|
17
23
|
}
|
|
18
24
|
const options = {};
|
|
19
25
|
if (payload && payload.selector) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a11ySnapshotPlugin.js","sourceRoot":"","sources":["../src/a11ySnapshotPlugin.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"a11ySnapshotPlugin.js","sourceRoot":"","sources":["../src/a11ySnapshotPlugin.ts"],"names":[],"mappings":";;AAQA,gDA+DC;AAnED,uEAAiE;AAIjE,SAAgB,kBAAkB;IAChC,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YAChD,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;gBAChC,0CAA0C;gBAC1C,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBAC1C,MAAM,IAAI,GAAI,OAAO,CAAC,OAA8B,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACzE,6CAA6C;oBAC7C,MAAM,EAAE,aAAa,EAAE,GAAG,IAIzB,CAAC;oBACF,IAAI,CAAC,aAAa,EAAE,CAAC;wBACnB,sEAAsE;wBACtE,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;4BACxC,MAAM,IAAI,GAAG,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;4BACjF,OAAO,IAAA,8CAAoB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;wBAC1C,CAAC;wBACD,MAAM,IAAI,KAAK,CACb,+FAA+F;4BAC7F,8CAA8C,CACjD,CAAC;oBACJ,CAAC;oBACD,MAAM,OAAO,GAET,EAAE,CAAC;oBACP,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;wBAChC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;wBAC5C,IAAI,IAAI,EAAE,CAAC;4BACT,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;wBACtB,CAAC;oBACH,CAAC;oBACD,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBACvD,OAAO,QAAQ,CAAC;gBAClB,CAAC;gBAED,yCAAyC;gBACzC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBACzC,MAAM,IAAI,GAAI,OAAO,CAAC,OAA0B,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACrE,MAAM,OAAO,GAET,EAAE,CAAC;oBACP,IAAI,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;wBAChC,aAAa;wBACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;wBAC5C,IAAI,IAAI,EAAE,CAAC;4BACT,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;wBACtB,CAAC;oBACH,CAAC;oBACD,aAAa;oBACb,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBAC5D,OAAO,QAAQ,CAAC;gBAClB,CAAC;gBAED,yDAAyD;gBACzD,MAAM,IAAI,KAAK,CACb,2DAA2D,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,CACnF,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2018 Google Inc. All rights reserved.
|
|
3
|
+
* Modifications copyright (c) Microsoft Corporation.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Ported from Playwright 1.56 `crAccessibility.ts` and `accessibility.ts`, which
|
|
19
|
+
* Playwright 1.57 removed. Changes: only the `interestingOnly: true` mode, the
|
|
20
|
+
* tree is read through a public CDP session, and the leaf roles include the
|
|
21
|
+
* `image` and `meter` role names that Chromium reports.
|
|
22
|
+
*/
|
|
23
|
+
import type { playwright } from '@web/test-runner-playwright';
|
|
24
|
+
export interface A11ySnapshotNode {
|
|
25
|
+
role: string;
|
|
26
|
+
name: string;
|
|
27
|
+
value?: string | number;
|
|
28
|
+
children?: A11ySnapshotNode[];
|
|
29
|
+
[property: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns an accessibility snapshot in the format of `page.accessibility.snapshot()`
|
|
33
|
+
* in Playwright 1.56, which Playwright 1.57 removed. Works in Chromium only.
|
|
34
|
+
*/
|
|
35
|
+
export declare function chromiumA11ySnapshot(page: playwright.Page, root?: playwright.ElementHandle | null): Promise<A11ySnapshotNode | null>;
|
|
36
|
+
//# sourceMappingURL=chromiumA11ySnapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chromiumA11ySnapshot.d.ts","sourceRoot":"","sources":["../src/chromiumA11ySnapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AA6B9D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7B;AAkLD;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,UAAU,CAAC,IAAI,EACrB,IAAI,CAAC,EAAE,UAAU,CAAC,aAAa,GAAG,IAAI,GACrC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA6ClC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright 2018 Google Inc. All rights reserved.
|
|
4
|
+
* Modifications copyright (c) Microsoft Corporation.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.chromiumA11ySnapshot = chromiumA11ySnapshot;
|
|
20
|
+
const CONTROL_ROLES = new Set([
|
|
21
|
+
'button',
|
|
22
|
+
'checkbox',
|
|
23
|
+
'ColorWell',
|
|
24
|
+
'combobox',
|
|
25
|
+
'DisclosureTriangle',
|
|
26
|
+
'listbox',
|
|
27
|
+
'menu',
|
|
28
|
+
'menubar',
|
|
29
|
+
'menuitem',
|
|
30
|
+
'menuitemcheckbox',
|
|
31
|
+
'menuitemradio',
|
|
32
|
+
'radio',
|
|
33
|
+
'scrollbar',
|
|
34
|
+
'searchbox',
|
|
35
|
+
'slider',
|
|
36
|
+
'spinbutton',
|
|
37
|
+
'switch',
|
|
38
|
+
'tab',
|
|
39
|
+
'textbox',
|
|
40
|
+
'tree',
|
|
41
|
+
]);
|
|
42
|
+
const LEAF_ROLES = new Set([
|
|
43
|
+
'doc-cover',
|
|
44
|
+
'graphics-symbol',
|
|
45
|
+
'img',
|
|
46
|
+
'image',
|
|
47
|
+
'Meter',
|
|
48
|
+
'meter',
|
|
49
|
+
'scrollbar',
|
|
50
|
+
'slider',
|
|
51
|
+
'separator',
|
|
52
|
+
'progressbar',
|
|
53
|
+
]);
|
|
54
|
+
const TEXT_ROLES = new Set(['LineBreak', 'text', 'InlineTextBox', 'StaticText']);
|
|
55
|
+
// The key order follows the snapshot of Playwright 1.56.
|
|
56
|
+
const STRING_PROPERTIES = ['description', 'keyshortcuts', 'roledescription', 'valuetext'];
|
|
57
|
+
const BOOLEAN_PROPERTIES = [
|
|
58
|
+
'disabled',
|
|
59
|
+
'expanded',
|
|
60
|
+
'focused',
|
|
61
|
+
'modal',
|
|
62
|
+
'multiline',
|
|
63
|
+
'multiselectable',
|
|
64
|
+
'readonly',
|
|
65
|
+
'required',
|
|
66
|
+
'selected',
|
|
67
|
+
];
|
|
68
|
+
const NUMBER_PROPERTIES = ['level', 'valuemin', 'valuemax'];
|
|
69
|
+
const TOKEN_PROPERTIES = ['autocomplete', 'haspopup', 'invalid', 'orientation'];
|
|
70
|
+
let rootCounter = 0;
|
|
71
|
+
function createNode(payload) {
|
|
72
|
+
const node = {
|
|
73
|
+
payload,
|
|
74
|
+
role: payload.role ? String(payload.role.value) : 'Unknown',
|
|
75
|
+
name: payload.name ? String(payload.name.value) : '',
|
|
76
|
+
children: [],
|
|
77
|
+
richlyEditable: false,
|
|
78
|
+
editable: false,
|
|
79
|
+
focusable: false,
|
|
80
|
+
hidden: false,
|
|
81
|
+
};
|
|
82
|
+
for (const { name, value } of payload.properties || []) {
|
|
83
|
+
if (name === 'editable') {
|
|
84
|
+
node.richlyEditable = value.value === 'richtext';
|
|
85
|
+
node.editable = true;
|
|
86
|
+
}
|
|
87
|
+
if (name === 'focusable')
|
|
88
|
+
node.focusable = !!value.value;
|
|
89
|
+
if (name === 'hidden')
|
|
90
|
+
node.hidden = !!value.value;
|
|
91
|
+
}
|
|
92
|
+
return node;
|
|
93
|
+
}
|
|
94
|
+
function hasFocusableChild(node) {
|
|
95
|
+
if (node.hasFocusableChild === undefined) {
|
|
96
|
+
node.hasFocusableChild = node.children.some(child => child.focusable || hasFocusableChild(child));
|
|
97
|
+
}
|
|
98
|
+
return node.hasFocusableChild;
|
|
99
|
+
}
|
|
100
|
+
function isPlainTextField(node) {
|
|
101
|
+
if (node.richlyEditable)
|
|
102
|
+
return false;
|
|
103
|
+
if (node.editable)
|
|
104
|
+
return true;
|
|
105
|
+
return node.role === 'textbox' || node.role === 'ComboBox' || node.role === 'searchbox';
|
|
106
|
+
}
|
|
107
|
+
function isLeafNode(node) {
|
|
108
|
+
if (!node.children.length)
|
|
109
|
+
return true;
|
|
110
|
+
if (isPlainTextField(node) || TEXT_ROLES.has(node.role))
|
|
111
|
+
return true;
|
|
112
|
+
if (LEAF_ROLES.has(node.role))
|
|
113
|
+
return true;
|
|
114
|
+
if (hasFocusableChild(node))
|
|
115
|
+
return false;
|
|
116
|
+
if (node.focusable && node.role !== 'WebArea' && node.role !== 'RootWebArea' && node.name) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return node.role === 'heading' && !!node.name;
|
|
120
|
+
}
|
|
121
|
+
function isInteresting(node, insideControl) {
|
|
122
|
+
if (node.role === 'Ignored' || node.hidden)
|
|
123
|
+
return false;
|
|
124
|
+
if (node.focusable || node.richlyEditable)
|
|
125
|
+
return true;
|
|
126
|
+
if (CONTROL_ROLES.has(node.role))
|
|
127
|
+
return true;
|
|
128
|
+
if (insideControl)
|
|
129
|
+
return false;
|
|
130
|
+
return isLeafNode(node) && !!node.name;
|
|
131
|
+
}
|
|
132
|
+
function collectInterestingNodes(collection, node, insideControl) {
|
|
133
|
+
if (isInteresting(node, insideControl))
|
|
134
|
+
collection.add(node);
|
|
135
|
+
if (isLeafNode(node))
|
|
136
|
+
return;
|
|
137
|
+
const childInsideControl = insideControl || CONTROL_ROLES.has(node.role);
|
|
138
|
+
for (const child of node.children) {
|
|
139
|
+
collectInterestingNodes(collection, child, childInsideControl);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function toTristate(value) {
|
|
143
|
+
if (value === 'true')
|
|
144
|
+
return true;
|
|
145
|
+
if (value === 'false')
|
|
146
|
+
return false;
|
|
147
|
+
return 'mixed';
|
|
148
|
+
}
|
|
149
|
+
function serializeNode(node) {
|
|
150
|
+
const properties = new Map();
|
|
151
|
+
for (const { name, value } of node.payload.properties || []) {
|
|
152
|
+
properties.set(name.toLowerCase(), value.value);
|
|
153
|
+
}
|
|
154
|
+
if (node.payload.description) {
|
|
155
|
+
properties.set('description', node.payload.description.value);
|
|
156
|
+
}
|
|
157
|
+
let role = node.role;
|
|
158
|
+
if (role === 'RootWebArea')
|
|
159
|
+
role = 'WebArea';
|
|
160
|
+
if (role === 'StaticText')
|
|
161
|
+
role = 'text';
|
|
162
|
+
const result = {
|
|
163
|
+
role,
|
|
164
|
+
name: node.payload.name ? String(node.payload.name.value || '') : '',
|
|
165
|
+
};
|
|
166
|
+
for (const key of STRING_PROPERTIES) {
|
|
167
|
+
if (properties.has(key))
|
|
168
|
+
result[key] = properties.get(key);
|
|
169
|
+
}
|
|
170
|
+
for (const key of BOOLEAN_PROPERTIES) {
|
|
171
|
+
if (key === 'focused' && (node.role === 'WebArea' || node.role === 'RootWebArea'))
|
|
172
|
+
continue;
|
|
173
|
+
if (properties.get(key))
|
|
174
|
+
result[key] = properties.get(key);
|
|
175
|
+
}
|
|
176
|
+
if (properties.has('checked'))
|
|
177
|
+
result.checked = toTristate(properties.get('checked'));
|
|
178
|
+
if (properties.has('pressed'))
|
|
179
|
+
result.pressed = toTristate(properties.get('pressed'));
|
|
180
|
+
for (const key of NUMBER_PROPERTIES) {
|
|
181
|
+
if (properties.has(key))
|
|
182
|
+
result[key] = properties.get(key);
|
|
183
|
+
}
|
|
184
|
+
for (const key of TOKEN_PROPERTIES) {
|
|
185
|
+
const value = properties.get(key);
|
|
186
|
+
if (value && value !== 'false')
|
|
187
|
+
result[key] = value;
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
function serializeTree(node, interestingNodes) {
|
|
192
|
+
const children = node.children.flatMap(child => serializeTree(child, interestingNodes));
|
|
193
|
+
if (!interestingNodes.has(node))
|
|
194
|
+
return children;
|
|
195
|
+
const result = serializeNode(node);
|
|
196
|
+
if (children.length)
|
|
197
|
+
result.children = children;
|
|
198
|
+
const value = node.payload.value?.value;
|
|
199
|
+
if (typeof value === 'string' || typeof value === 'number')
|
|
200
|
+
result.value = value;
|
|
201
|
+
return [result];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Returns an accessibility snapshot in the format of `page.accessibility.snapshot()`
|
|
205
|
+
* in Playwright 1.56, which Playwright 1.57 removed. Works in Chromium only.
|
|
206
|
+
*/
|
|
207
|
+
async function chromiumA11ySnapshot(page, root) {
|
|
208
|
+
const cdp = await page.context().newCDPSession(page);
|
|
209
|
+
try {
|
|
210
|
+
let rootBackendNodeId;
|
|
211
|
+
if (root) {
|
|
212
|
+
// An element handle belongs to the CDP session of Playwright. Pass the
|
|
213
|
+
// element through a global to get its object ID in this session.
|
|
214
|
+
rootCounter += 1;
|
|
215
|
+
const key = `__wtrA11ySnapshotRoot${rootCounter}`;
|
|
216
|
+
await root.evaluate((element, globalKey) => {
|
|
217
|
+
globalThis[globalKey] = element;
|
|
218
|
+
}, key);
|
|
219
|
+
const { result } = await cdp.send('Runtime.evaluate', {
|
|
220
|
+
expression: `(() => { const element = globalThis.${key}; delete globalThis.${key}; return element; })()`,
|
|
221
|
+
});
|
|
222
|
+
const { node } = await cdp.send('DOM.describeNode', { objectId: result.objectId });
|
|
223
|
+
rootBackendNodeId = node.backendNodeId;
|
|
224
|
+
}
|
|
225
|
+
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
|
|
226
|
+
const nodeById = new Map(nodes.map(payload => [payload.nodeId, createNode(payload)]));
|
|
227
|
+
for (const node of nodeById.values()) {
|
|
228
|
+
for (const childId of node.payload.childIds || []) {
|
|
229
|
+
const child = nodeById.get(childId);
|
|
230
|
+
if (child)
|
|
231
|
+
node.children.push(child);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const tree = nodeById.values().next().value;
|
|
235
|
+
if (!tree)
|
|
236
|
+
return null;
|
|
237
|
+
let needle;
|
|
238
|
+
if (root) {
|
|
239
|
+
needle = [...nodeById.values()].find(node => node.payload.backendDOMNodeId === rootBackendNodeId);
|
|
240
|
+
}
|
|
241
|
+
const interestingNodes = new Set();
|
|
242
|
+
collectInterestingNodes(interestingNodes, tree, false);
|
|
243
|
+
if (root && (!needle || !interestingNodes.has(needle)))
|
|
244
|
+
return null;
|
|
245
|
+
return serializeTree(needle || tree, interestingNodes)[0] ?? null;
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
await cdp.detach();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=chromiumA11ySnapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chromiumA11ySnapshot.js","sourceRoot":"","sources":["../src/chromiumA11ySnapshot.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -2,6 +2,8 @@ import type { ChromeLauncher, puppeteerCore } from '@web/test-runner-chrome';
|
|
|
2
2
|
import { TestRunnerPlugin } from '@web/test-runner-core';
|
|
3
3
|
import type { PlaywrightLauncher, playwright } from '@web/test-runner-playwright';
|
|
4
4
|
|
|
5
|
+
import { chromiumA11ySnapshot } from './chromiumA11ySnapshot.js';
|
|
6
|
+
|
|
5
7
|
export type A11ySnapshotPayload = { selector?: string };
|
|
6
8
|
|
|
7
9
|
export function a11ySnapshotPlugin(): TestRunnerPlugin<A11ySnapshotPayload> {
|
|
@@ -19,9 +21,14 @@ export function a11ySnapshotPlugin(): TestRunnerPlugin<A11ySnapshotPayload> {
|
|
|
19
21
|
};
|
|
20
22
|
};
|
|
21
23
|
if (!accessibility) {
|
|
24
|
+
// Chromium exposes the same tree through the Chrome DevTools Protocol
|
|
25
|
+
if (session.browser.name === 'Chromium') {
|
|
26
|
+
const root = payload && payload.selector ? await page.$(payload.selector) : null;
|
|
27
|
+
return chromiumA11ySnapshot(page, root);
|
|
28
|
+
}
|
|
22
29
|
throw new Error(
|
|
23
|
-
'Accessibility snapshot is not supported for Playwright 1.57 and later. ' +
|
|
24
|
-
'Use Playwright 1.56 or earlier
|
|
30
|
+
'Accessibility snapshot is not supported for Firefox and WebKit in Playwright 1.57 and later. ' +
|
|
31
|
+
'Use Chromium, or Playwright 1.56 or earlier.',
|
|
25
32
|
);
|
|
26
33
|
}
|
|
27
34
|
const options: {
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2018 Google Inc. All rights reserved.
|
|
3
|
+
* Modifications copyright (c) Microsoft Corporation.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Ported from Playwright 1.56 `crAccessibility.ts` and `accessibility.ts`, which
|
|
20
|
+
* Playwright 1.57 removed. Changes: only the `interestingOnly: true` mode, the
|
|
21
|
+
* tree is read through a public CDP session, and the leaf roles include the
|
|
22
|
+
* `image` and `meter` role names that Chromium reports.
|
|
23
|
+
*/
|
|
24
|
+
import type { playwright } from '@web/test-runner-playwright';
|
|
25
|
+
|
|
26
|
+
interface AXValue {
|
|
27
|
+
value?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface AXNode {
|
|
31
|
+
nodeId: string;
|
|
32
|
+
backendDOMNodeId?: number;
|
|
33
|
+
role?: AXValue;
|
|
34
|
+
name?: AXValue;
|
|
35
|
+
description?: AXValue;
|
|
36
|
+
value?: AXValue;
|
|
37
|
+
properties?: { name: string; value: AXValue }[];
|
|
38
|
+
childIds?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface TreeNode {
|
|
42
|
+
payload: AXNode;
|
|
43
|
+
role: string;
|
|
44
|
+
name: string;
|
|
45
|
+
children: TreeNode[];
|
|
46
|
+
richlyEditable: boolean;
|
|
47
|
+
editable: boolean;
|
|
48
|
+
focusable: boolean;
|
|
49
|
+
hidden: boolean;
|
|
50
|
+
hasFocusableChild?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface A11ySnapshotNode {
|
|
54
|
+
role: string;
|
|
55
|
+
name: string;
|
|
56
|
+
value?: string | number;
|
|
57
|
+
children?: A11ySnapshotNode[];
|
|
58
|
+
[property: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const CONTROL_ROLES = new Set([
|
|
62
|
+
'button',
|
|
63
|
+
'checkbox',
|
|
64
|
+
'ColorWell',
|
|
65
|
+
'combobox',
|
|
66
|
+
'DisclosureTriangle',
|
|
67
|
+
'listbox',
|
|
68
|
+
'menu',
|
|
69
|
+
'menubar',
|
|
70
|
+
'menuitem',
|
|
71
|
+
'menuitemcheckbox',
|
|
72
|
+
'menuitemradio',
|
|
73
|
+
'radio',
|
|
74
|
+
'scrollbar',
|
|
75
|
+
'searchbox',
|
|
76
|
+
'slider',
|
|
77
|
+
'spinbutton',
|
|
78
|
+
'switch',
|
|
79
|
+
'tab',
|
|
80
|
+
'textbox',
|
|
81
|
+
'tree',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const LEAF_ROLES = new Set([
|
|
85
|
+
'doc-cover',
|
|
86
|
+
'graphics-symbol',
|
|
87
|
+
'img',
|
|
88
|
+
'image',
|
|
89
|
+
'Meter',
|
|
90
|
+
'meter',
|
|
91
|
+
'scrollbar',
|
|
92
|
+
'slider',
|
|
93
|
+
'separator',
|
|
94
|
+
'progressbar',
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const TEXT_ROLES = new Set(['LineBreak', 'text', 'InlineTextBox', 'StaticText']);
|
|
98
|
+
|
|
99
|
+
// The key order follows the snapshot of Playwright 1.56.
|
|
100
|
+
const STRING_PROPERTIES = ['description', 'keyshortcuts', 'roledescription', 'valuetext'];
|
|
101
|
+
const BOOLEAN_PROPERTIES = [
|
|
102
|
+
'disabled',
|
|
103
|
+
'expanded',
|
|
104
|
+
'focused',
|
|
105
|
+
'modal',
|
|
106
|
+
'multiline',
|
|
107
|
+
'multiselectable',
|
|
108
|
+
'readonly',
|
|
109
|
+
'required',
|
|
110
|
+
'selected',
|
|
111
|
+
];
|
|
112
|
+
const NUMBER_PROPERTIES = ['level', 'valuemin', 'valuemax'];
|
|
113
|
+
const TOKEN_PROPERTIES = ['autocomplete', 'haspopup', 'invalid', 'orientation'];
|
|
114
|
+
|
|
115
|
+
let rootCounter = 0;
|
|
116
|
+
|
|
117
|
+
function createNode(payload: AXNode): TreeNode {
|
|
118
|
+
const node: TreeNode = {
|
|
119
|
+
payload,
|
|
120
|
+
role: payload.role ? String(payload.role.value) : 'Unknown',
|
|
121
|
+
name: payload.name ? String(payload.name.value) : '',
|
|
122
|
+
children: [],
|
|
123
|
+
richlyEditable: false,
|
|
124
|
+
editable: false,
|
|
125
|
+
focusable: false,
|
|
126
|
+
hidden: false,
|
|
127
|
+
};
|
|
128
|
+
for (const { name, value } of payload.properties || []) {
|
|
129
|
+
if (name === 'editable') {
|
|
130
|
+
node.richlyEditable = value.value === 'richtext';
|
|
131
|
+
node.editable = true;
|
|
132
|
+
}
|
|
133
|
+
if (name === 'focusable') node.focusable = !!value.value;
|
|
134
|
+
if (name === 'hidden') node.hidden = !!value.value;
|
|
135
|
+
}
|
|
136
|
+
return node;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hasFocusableChild(node: TreeNode): boolean {
|
|
140
|
+
if (node.hasFocusableChild === undefined) {
|
|
141
|
+
node.hasFocusableChild = node.children.some(
|
|
142
|
+
child => child.focusable || hasFocusableChild(child),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return node.hasFocusableChild;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isPlainTextField(node: TreeNode) {
|
|
149
|
+
if (node.richlyEditable) return false;
|
|
150
|
+
if (node.editable) return true;
|
|
151
|
+
return node.role === 'textbox' || node.role === 'ComboBox' || node.role === 'searchbox';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isLeafNode(node: TreeNode) {
|
|
155
|
+
if (!node.children.length) return true;
|
|
156
|
+
if (isPlainTextField(node) || TEXT_ROLES.has(node.role)) return true;
|
|
157
|
+
if (LEAF_ROLES.has(node.role)) return true;
|
|
158
|
+
if (hasFocusableChild(node)) return false;
|
|
159
|
+
if (node.focusable && node.role !== 'WebArea' && node.role !== 'RootWebArea' && node.name) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return node.role === 'heading' && !!node.name;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isInteresting(node: TreeNode, insideControl: boolean) {
|
|
166
|
+
if (node.role === 'Ignored' || node.hidden) return false;
|
|
167
|
+
if (node.focusable || node.richlyEditable) return true;
|
|
168
|
+
if (CONTROL_ROLES.has(node.role)) return true;
|
|
169
|
+
if (insideControl) return false;
|
|
170
|
+
return isLeafNode(node) && !!node.name;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function collectInterestingNodes(
|
|
174
|
+
collection: Set<TreeNode>,
|
|
175
|
+
node: TreeNode,
|
|
176
|
+
insideControl: boolean,
|
|
177
|
+
) {
|
|
178
|
+
if (isInteresting(node, insideControl)) collection.add(node);
|
|
179
|
+
if (isLeafNode(node)) return;
|
|
180
|
+
const childInsideControl = insideControl || CONTROL_ROLES.has(node.role);
|
|
181
|
+
for (const child of node.children) {
|
|
182
|
+
collectInterestingNodes(collection, child, childInsideControl);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function toTristate(value: unknown) {
|
|
187
|
+
if (value === 'true') return true;
|
|
188
|
+
if (value === 'false') return false;
|
|
189
|
+
return 'mixed';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function serializeNode(node: TreeNode): A11ySnapshotNode {
|
|
193
|
+
const properties = new Map<string, unknown>();
|
|
194
|
+
for (const { name, value } of node.payload.properties || []) {
|
|
195
|
+
properties.set(name.toLowerCase(), value.value);
|
|
196
|
+
}
|
|
197
|
+
if (node.payload.description) {
|
|
198
|
+
properties.set('description', node.payload.description.value);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let role = node.role;
|
|
202
|
+
if (role === 'RootWebArea') role = 'WebArea';
|
|
203
|
+
if (role === 'StaticText') role = 'text';
|
|
204
|
+
const result: A11ySnapshotNode = {
|
|
205
|
+
role,
|
|
206
|
+
name: node.payload.name ? String(node.payload.name.value || '') : '',
|
|
207
|
+
};
|
|
208
|
+
for (const key of STRING_PROPERTIES) {
|
|
209
|
+
if (properties.has(key)) result[key] = properties.get(key);
|
|
210
|
+
}
|
|
211
|
+
for (const key of BOOLEAN_PROPERTIES) {
|
|
212
|
+
if (key === 'focused' && (node.role === 'WebArea' || node.role === 'RootWebArea')) continue;
|
|
213
|
+
if (properties.get(key)) result[key] = properties.get(key);
|
|
214
|
+
}
|
|
215
|
+
if (properties.has('checked')) result.checked = toTristate(properties.get('checked'));
|
|
216
|
+
if (properties.has('pressed')) result.pressed = toTristate(properties.get('pressed'));
|
|
217
|
+
for (const key of NUMBER_PROPERTIES) {
|
|
218
|
+
if (properties.has(key)) result[key] = properties.get(key);
|
|
219
|
+
}
|
|
220
|
+
for (const key of TOKEN_PROPERTIES) {
|
|
221
|
+
const value = properties.get(key);
|
|
222
|
+
if (value && value !== 'false') result[key] = value;
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function serializeTree(node: TreeNode, interestingNodes: Set<TreeNode>): A11ySnapshotNode[] {
|
|
228
|
+
const children = node.children.flatMap(child => serializeTree(child, interestingNodes));
|
|
229
|
+
if (!interestingNodes.has(node)) return children;
|
|
230
|
+
const result = serializeNode(node);
|
|
231
|
+
if (children.length) result.children = children;
|
|
232
|
+
const value = node.payload.value?.value;
|
|
233
|
+
if (typeof value === 'string' || typeof value === 'number') result.value = value;
|
|
234
|
+
return [result];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns an accessibility snapshot in the format of `page.accessibility.snapshot()`
|
|
239
|
+
* in Playwright 1.56, which Playwright 1.57 removed. Works in Chromium only.
|
|
240
|
+
*/
|
|
241
|
+
export async function chromiumA11ySnapshot(
|
|
242
|
+
page: playwright.Page,
|
|
243
|
+
root?: playwright.ElementHandle | null,
|
|
244
|
+
): Promise<A11ySnapshotNode | null> {
|
|
245
|
+
const cdp = await page.context().newCDPSession(page);
|
|
246
|
+
try {
|
|
247
|
+
let rootBackendNodeId: number | undefined;
|
|
248
|
+
if (root) {
|
|
249
|
+
// An element handle belongs to the CDP session of Playwright. Pass the
|
|
250
|
+
// element through a global to get its object ID in this session.
|
|
251
|
+
rootCounter += 1;
|
|
252
|
+
const key = `__wtrA11ySnapshotRoot${rootCounter}`;
|
|
253
|
+
await root.evaluate((element, globalKey) => {
|
|
254
|
+
(globalThis as Record<string, unknown>)[globalKey] = element;
|
|
255
|
+
}, key);
|
|
256
|
+
const { result } = await cdp.send('Runtime.evaluate', {
|
|
257
|
+
expression: `(() => { const element = globalThis.${key}; delete globalThis.${key}; return element; })()`,
|
|
258
|
+
});
|
|
259
|
+
const { node } = await cdp.send('DOM.describeNode', { objectId: result.objectId });
|
|
260
|
+
rootBackendNodeId = node.backendNodeId;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { nodes } = await cdp.send('Accessibility.getFullAXTree');
|
|
264
|
+
const nodeById = new Map<string, TreeNode>(
|
|
265
|
+
(nodes as AXNode[]).map(payload => [payload.nodeId, createNode(payload)]),
|
|
266
|
+
);
|
|
267
|
+
for (const node of nodeById.values()) {
|
|
268
|
+
for (const childId of node.payload.childIds || []) {
|
|
269
|
+
const child = nodeById.get(childId);
|
|
270
|
+
if (child) node.children.push(child);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const tree = nodeById.values().next().value;
|
|
274
|
+
if (!tree) return null;
|
|
275
|
+
|
|
276
|
+
let needle: TreeNode | undefined;
|
|
277
|
+
if (root) {
|
|
278
|
+
needle = [...nodeById.values()].find(
|
|
279
|
+
node => node.payload.backendDOMNodeId === rootBackendNodeId,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const interestingNodes = new Set<TreeNode>();
|
|
283
|
+
collectInterestingNodes(interestingNodes, tree, false);
|
|
284
|
+
if (root && (!needle || !interestingNodes.has(needle))) return null;
|
|
285
|
+
return serializeTree(needle || tree, interestingNodes)[0] ?? null;
|
|
286
|
+
} finally {
|
|
287
|
+
await cdp.detach();
|
|
288
|
+
}
|
|
289
|
+
}
|