feat: root
option in page.accessibility.snapshot() (#4318)
Going from `AXNode` -> `ElementHandle` is turning out to be controversial. This patch instead adds a way to go from `ElementHandle` -> `AXNode`. If the API looks good, I'll add it into Firefox as well. References #3641
This commit is contained in:
parent
b3027a6e16
commit
a3cb16308c
@ -2098,6 +2098,7 @@ Most of the accessibility tree gets filtered out when converting from Blink AX T
|
||||
#### accessibility.snapshot([options])
|
||||
- `options` <[Object]>
|
||||
- `interestingOnly` <[boolean]> Prune uninteresting nodes from the tree. Defaults to `true`.
|
||||
- `root` <[ElementHandle]> The root DOM element for the snapshot. Defaults to the whole page.
|
||||
- returns: <[Promise]<[Object]>> An [AXNode] object with the following properties:
|
||||
- `role` <[string]> The [role](https://www.w3.org/TR/wai-aria/#usage_intro).
|
||||
- `name` <[string]> A human readable name for the node.
|
||||
|
@ -60,20 +60,36 @@ class Accessibility {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{interestingOnly?: boolean}=} options
|
||||
* @param {{interestingOnly?: boolean, root?: ?Puppeteer.ElementHandle}=} options
|
||||
* @return {!Promise<!SerializedAXNode>}
|
||||
*/
|
||||
async snapshot(options = {}) {
|
||||
const {interestingOnly = true} = options;
|
||||
const {
|
||||
interestingOnly = true,
|
||||
root = null,
|
||||
} = options;
|
||||
const {nodes} = await this._client.send('Accessibility.getFullAXTree');
|
||||
const root = AXNode.createTree(nodes);
|
||||
let backendNodeId = null;
|
||||
if (root) {
|
||||
const {node} = await this._client.send('DOM.describeNode', {objectId: root._remoteObject.objectId});
|
||||
backendNodeId = node.backendNodeId;
|
||||
}
|
||||
const defaultRoot = AXNode.createTree(nodes);
|
||||
let needle = defaultRoot;
|
||||
if (backendNodeId) {
|
||||
needle = defaultRoot.find(node => node._payload.backendDOMNodeId === backendNodeId);
|
||||
if (!needle)
|
||||
return null;
|
||||
}
|
||||
if (!interestingOnly)
|
||||
return serializeTree(root)[0];
|
||||
return serializeTree(needle)[0];
|
||||
|
||||
/** @type {!Set<!AXNode>} */
|
||||
const interestingNodes = new Set();
|
||||
collectInterestingNodes(interestingNodes, root, false);
|
||||
return serializeTree(root, interestingNodes)[0];
|
||||
collectInterestingNodes(interestingNodes, defaultRoot, false);
|
||||
if (!interestingNodes.has(needle))
|
||||
return null;
|
||||
return serializeTree(needle, interestingNodes)[0];
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +195,21 @@ class AXNode {
|
||||
return this._cachedHasFocusableChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function(AXNode):boolean} predicate
|
||||
* @return {?AXNode}
|
||||
*/
|
||||
find(predicate) {
|
||||
if (predicate(this))
|
||||
return this;
|
||||
for (const child of this._children) {
|
||||
const result = child.find(predicate);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
@ -307,6 +307,63 @@ module.exports.addTests = function({testRunner, expect, FFOX}) {
|
||||
const snapshot = await page.accessibility.snapshot();
|
||||
expect(snapshot.children[0]).toEqual(golden);
|
||||
});
|
||||
|
||||
describe_fails_ffox('root option', function() {
|
||||
it('should work a button', async({page}) => {
|
||||
await page.setContent(`<button>My Button</button>`);
|
||||
|
||||
const button = await page.$('button');
|
||||
expect(await page.accessibility.snapshot({root: button})).toEqual({
|
||||
role: 'button',
|
||||
name: 'My Button'
|
||||
});
|
||||
});
|
||||
it('should work an input', async({page}) => {
|
||||
await page.setContent(`<input title="My Input" value="My Value">`);
|
||||
|
||||
const input = await page.$('input');
|
||||
expect(await page.accessibility.snapshot({root: input})).toEqual({
|
||||
role: 'textbox',
|
||||
name: 'My Input',
|
||||
value: 'My Value'
|
||||
});
|
||||
});
|
||||
it('should work a menu', async({page}) => {
|
||||
await page.setContent(`
|
||||
<div role="menu" title="My Menu">
|
||||
<div role="menuitem">First Item</div>
|
||||
<div role="menuitem">Second Item</div>
|
||||
<div role="menuitem">Third Item</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const menu = await page.$('div[role="menu"]');
|
||||
expect(await page.accessibility.snapshot({root: menu})).toEqual({
|
||||
role: 'menu',
|
||||
name: 'My Menu',
|
||||
children:
|
||||
[ { role: 'menuitem', name: 'First Item' },
|
||||
{ role: 'menuitem', name: 'Second Item' },
|
||||
{ role: 'menuitem', name: 'Third Item' } ]
|
||||
});
|
||||
});
|
||||
it('should return null when the element is no longer in DOM', async({page}) => {
|
||||
await page.setContent(`<button>My Button</button>`);
|
||||
const button = await page.$('button');
|
||||
await page.$eval('button', button => button.remove());
|
||||
expect(await page.accessibility.snapshot({root: button})).toEqual(null);
|
||||
});
|
||||
it('should support the interestingOnly option', async({page}) => {
|
||||
await page.setContent(`<div><button>My Button</button></div>`);
|
||||
const div = await page.$('div');
|
||||
expect(await page.accessibility.snapshot({root: div})).toEqual(null);
|
||||
expect(await page.accessibility.snapshot({root: div, interestingOnly: false})).toEqual({
|
||||
role: 'GenericContainer',
|
||||
name: '',
|
||||
children: [ { role: 'button', name: 'My Button' } ] }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
function findFocusedNode(node) {
|
||||
if (node.focused)
|
||||
|
Loading…
Reference in New Issue
Block a user