basic root accessibility stuff

This commit is contained in:
Joel Einbinder 2020-01-10 13:15:03 -08:00 committed by Joel Einbinder
parent 92bd854d8f
commit f2d3ac68ae
9 changed files with 56 additions and 33 deletions

View file

@ -55,13 +55,12 @@ export interface AXNode {
isLeafNode(): boolean; isLeafNode(): boolean;
isControl(): boolean; isControl(): boolean;
serialize(): SerializedAXNode; serialize(): SerializedAXNode;
findElement(element: dom.ElementHandle): Promise<AXNode|null>;
children(): Iterable<AXNode>; children(): Iterable<AXNode>;
} }
export class Accessibility { export class Accessibility {
private _getAXTree: () => Promise<AXNode>; private _getAXTree: (needle?: dom.ElementHandle) => Promise<{tree: AXNode, needle?: AXNode}>;
constructor(getAXTree: () => Promise<AXNode>) { constructor(getAXTree: (needle?: dom.ElementHandle) => Promise<{tree: AXNode, needle?: AXNode}>) {
this._getAXTree = getAXTree; this._getAXTree = getAXTree;
} }
@ -73,18 +72,12 @@ export class Accessibility {
interestingOnly = true, interestingOnly = true,
root = null, root = null,
} = options; } = options;
const defaultRoot = await this._getAXTree(); const {tree, needle} = await this._getAXTree(root);
let needle: AXNode | null = defaultRoot;
if (root) {
needle = await defaultRoot.findElement(root);
if (!needle)
return null;
}
if (!interestingOnly) if (!interestingOnly)
return serializeTree(needle)[0]; return serializeTree(needle)[0];
const interestingNodes: Set<AXNode> = new Set(); const interestingNodes: Set<AXNode> = new Set();
collectInterestingNodes(interestingNodes, defaultRoot, false); collectInterestingNodes(interestingNodes, tree, false);
if (root && !interestingNodes.has(needle)) if (root && !interestingNodes.has(needle))
return null; return null;
return serializeTree(needle, interestingNodes)[0]; return serializeTree(needle, interestingNodes)[0];

View file

@ -20,9 +20,13 @@ import { Protocol } from './protocol';
import * as dom from '../dom'; import * as dom from '../dom';
import * as accessibility from '../accessibility'; import * as accessibility from '../accessibility';
export async function getAccessibilityTree(client: CRSession) : Promise<accessibility.AXNode> { export async function getAccessibilityTree(client: CRSession, needle?: dom.ElementHandle) : Promise<{tree: accessibility.AXNode, needle?: accessibility.AXNode}> {
const {nodes} = await client.send('Accessibility.getFullAXTree'); const {nodes} = await client.send('Accessibility.getFullAXTree');
return CRAXNode.createTree(client, nodes); const tree = CRAXNode.createTree(client, nodes);
return {
tree,
needle: needle && await tree._findElement(needle)
};
} }
class CRAXNode implements accessibility.AXNode { class CRAXNode implements accessibility.AXNode {
@ -90,7 +94,7 @@ class CRAXNode implements accessibility.AXNode {
return this._children; return this._children;
} }
async findElement(element: dom.ElementHandle): Promise<CRAXNode | null> { async _findElement(element: dom.ElementHandle): Promise<CRAXNode | null> {
const remoteObject = element._remoteObject as Protocol.Runtime.RemoteObject; const remoteObject = element._remoteObject as Protocol.Runtime.RemoteObject;
const {node: {backendNodeId}} = await this._client.send('DOM.describeNode', {objectId: remoteObject.objectId}); const {node: {backendNodeId}} = await this._client.send('DOM.describeNode', {objectId: remoteObject.objectId});
const needle = this.find(node => node._payload.backendDOMNodeId === backendNodeId); const needle = this.find(node => node._payload.backendDOMNodeId === backendNodeId);

View file

@ -480,8 +480,8 @@ export class CRPage implements PageDelegate {
return to._createHandle(result.object).asElement()!; return to._createHandle(result.object).asElement()!;
} }
async getAccessibilityTree(): Promise<accessibility.AXNode> { async getAccessibilityTree(needle?: dom.ElementHandle) {
return getAccessibilityTree(this._client); return getAccessibilityTree(this._client, needle);
} }
async pdf(options?: types.PDFOptions): Promise<platform.BufferType> { async pdf(options?: types.PDFOptions): Promise<platform.BufferType> {

View file

@ -18,15 +18,21 @@
import * as accessibility from '../accessibility'; import * as accessibility from '../accessibility';
import { FFSession } from './ffConnection'; import { FFSession } from './ffConnection';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as dom from '../dom';
export async function getAccessibilityTree(session: FFSession) : Promise<accessibility.AXNode> { export async function getAccessibilityTree(session: FFSession, needle: dom.ElementHandle) : Promise<{tree: accessibility.AXNode, needle?: accessibility.AXNode}> {
const { tree } = await session.send('Accessibility.getFullAXTree'); const objectId = needle ? needle._remoteObject.objectId : undefined;
return new FFAXNode(tree); const { tree } = await session.send('Accessibility.getFullAXTree', { objectId });
const axNode = new FFAXNode(tree);
return {
tree: axNode,
needle: needle && axNode._findNeedle()
};
} }
class FFAXNode implements accessibility.AXNode { class FFAXNode implements accessibility.AXNode {
_children: FFAXNode[]; _children: FFAXNode[];
private _payload: any; private _payload: Protocol.AXTree;
private _editable: boolean; private _editable: boolean;
private _richlyEditable: boolean; private _richlyEditable: boolean;
private _focusable: boolean; private _focusable: boolean;
@ -77,8 +83,15 @@ class FFAXNode implements accessibility.AXNode {
return this._children; return this._children;
} }
async findElement(): Promise<FFAXNode | null> { _findNeedle(): FFAXNode | null {
throw new Error('Not implimented'); if (this._payload.foundObject)
return this;
for (const child of this._children) {
const found = child._findNeedle();
if (found)
return found;
}
return null;
} }
isLeafNode(): boolean { isLeafNode(): boolean {

View file

@ -355,8 +355,8 @@ export class FFPage implements PageDelegate {
return handle; return handle;
} }
async getAccessibilityTree() : Promise<accessibility.AXNode> { async getAccessibilityTree(needle?: dom.ElementHandle) {
return getAccessibilityTree(this._session); return getAccessibilityTree(this._session, needle);
} }
coverage(): Coverage | undefined { coverage(): Coverage | undefined {

View file

@ -68,7 +68,7 @@ export interface PageDelegate {
setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>; setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>;
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>; getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
getAccessibilityTree(): Promise<accessibility.AXNode>; getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle?: accessibility.AXNode}>;
pdf?: (options?: types.PDFOptions) => Promise<platform.BufferType>; pdf?: (options?: types.PDFOptions) => Promise<platform.BufferType>;
coverage(): Coverage | undefined; coverage(): Coverage | undefined;
} }

View file

@ -16,10 +16,16 @@
import * as accessibility from '../accessibility'; import * as accessibility from '../accessibility';
import { WKSession } from './wkConnection'; import { WKSession } from './wkConnection';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as dom from '../dom';
export async function getAccessibilityTree(session: WKSession) { export async function getAccessibilityTree(session: WKSession, needle?: dom.ElementHandle) {
const {axNode} = await session.send('Page.accessibilitySnapshot'); const objectId = needle ? needle._remoteObject.objectId : undefined;
return new WKAXNode(axNode); const {axNode} = await session.send('Page.accessibilitySnapshot', { objectId });
const tree = new WKAXNode(axNode);
return {
tree,
needle: needle && tree._findNeedle()
};
} }
class WKAXNode implements accessibility.AXNode { class WKAXNode implements accessibility.AXNode {
@ -38,7 +44,14 @@ class WKAXNode implements accessibility.AXNode {
return this._children; return this._children;
} }
async findElement() { _findNeedle() : WKAXNode {
if (this._payload.found)
return this;
for (const child of this._children) {
const found = child._findNeedle()
if (found)
return found;
}
return null; return null;
} }

View file

@ -497,8 +497,8 @@ export class WKPage implements PageDelegate {
return to._createHandle(result.object) as dom.ElementHandle<T>; return to._createHandle(result.object) as dom.ElementHandle<T>;
} }
async getAccessibilityTree() : Promise<accessibility.AXNode> { async getAccessibilityTree(needle?: dom.ElementHandle) : Promise<{tree: accessibility.AXNode, needle?: accessibility.AXNode}> {
return getAccessibilityTree(this._session); return getAccessibilityTree(this._session, needle);
} }
coverage(): Coverage | undefined { coverage(): Coverage | undefined {

View file

@ -363,8 +363,8 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(snapshot.children[0]).toEqual(golden); expect(snapshot.children[0]).toEqual(golden);
}); });
describe.skip(FFOX || WEBKIT)('root option', function() { describe('root option', function() {
it('should work a button', async({page}) => { fit('should work a button', async({page}) => {
await page.setContent(`<button>My Button</button>`); await page.setContent(`<button>My Button</button>`);
const button = await page.$('button'); const button = await page.$('button');