diff --git a/src/webkit/wkAccessibility.ts b/src/webkit/wkAccessibility.ts index 012a549b28..7347b3b526 100644 --- a/src/webkit/wkAccessibility.ts +++ b/src/webkit/wkAccessibility.ts @@ -68,7 +68,7 @@ class WKAXNode implements accessibility.AXNode { return this._children; } - _findNeedle() : WKAXNode { + _findNeedle() : WKAXNode | null { if (this._payload.found) return this; for (const child of this._children) { @@ -108,8 +108,26 @@ class WKAXNode implements accessibility.AXNode { } } + _isTextControl() : boolean { + switch (this._payload.role) { + case 'combobox': + case 'searchfield': + case 'textbox': + case 'TextField': + return true; + } + return false; + } + + _name() : string { + if (this._payload.role === 'text') + return this._payload.value || ''; + return this._payload.name || ''; + } + isInteresting(insideControl: boolean) : boolean { - const {role, focusable, name} = this._payload; + const {role, focusable} = this._payload; + const name = this._name(); if (role === 'ScrollArea') return false; if (role === 'WebArea') @@ -129,14 +147,29 @@ class WKAXNode implements accessibility.AXNode { return this.isLeafNode() && !!name; } + _hasRendundantTextChild() { + if (this._children.length !== 1) + return false; + const child = this._children[0]; + return child._payload.role === 'text' && this._payload.name === child._payload.value; + } + isLeafNode() : boolean { - return !this._children.length; + if (!this._children.length) + return true; + // WebKit on Linux ignores everything inside text controls, normalize this behavior + if (this._isTextControl()) + return true; + // WebKit for mac has text nodes inside heading, li, menuitem, a, and p nodes + if (this._hasRendundantTextChild()) + return true; + return false; } serialize(): accessibility.SerializedAXNode { const node : accessibility.SerializedAXNode = { role: WKRoleToARIARole.get(this._payload.role) || this._payload.role, - name: this._payload.name || '', + name: this._name(), }; if ('description' in this._payload && this._payload.description !== node.name) @@ -148,13 +181,15 @@ class WKAXNode implements accessibility.AXNode { node.roledescription = roledescription; } + if ('value' in this._payload && this._payload.role !== 'text') + node.value = this._payload.value; + type AXPropertyOfType = { [Key in keyof Protocol.Page.AXNode]: Protocol.Page.AXNode[Key] extends Type ? Key : never }[keyof Protocol.Page.AXNode]; const userStringProperties: string[] = [ - 'value', 'keyshortcuts', 'valuetext' ]; diff --git a/test/accessibility.spec.js b/test/accessibility.spec.js index 78eb527d3d..b9bb89356c 100644 --- a/test/accessibility.spec.js +++ b/test/accessibility.spec.js @@ -37,10 +37,6 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) - `); // autofocus happens after a delay in chrome these days await page.waitForFunction(() => document.activeElement.hasAttribute('autofocus')); @@ -58,10 +54,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) {role: 'textbox', name: '', value: 'value only'}, {role: 'textbox', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name {role: 'textbox', name: '', value: 'and a value', description: 'This is a description!'}, // and here - {role: 'combobox', name: '', value: 'First Option', haspopup: true, children: [ - {role: 'combobox option', name: 'First Option', selected: true}, - {role: 'combobox option', name: 'Second Option'}] - }] + ] } : CHROMIUM ? { role: 'WebArea', name: 'Accessibility Test', @@ -75,14 +68,12 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) {role: 'textbox', name: '', value: 'value only'}, {role: 'textbox', name: 'placeholder', value: 'and a value'}, {role: 'textbox', name: 'placeholder', value: 'and a value', description: 'This is a description!'}, - {role: 'combobox', name: '', value: 'First Option', children: [ - {role: 'menuitem', name: 'First Option', selected: true}, - {role: 'menuitem', name: 'Second Option'}] - }] + ] } : { role: 'WebArea', name: 'Accessibility Test', children: [ + {role: 'text', name: 'Hello World'}, {role: 'heading', name: 'Inputs', level: 1}, {role: 'textbox', name: 'Empty input', focused: true}, {role: 'textbox', name: 'readonly input', readonly: true}, @@ -91,10 +82,6 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) {role: 'textbox', name: '', value: 'value only' }, {role: 'textbox', name: 'placeholder',value: 'and a value'}, {role: 'textbox', name: 'This is a description!',value: 'and a value'}, // webkit uses the description over placeholder for the name - {role: 'button', name: '', value: 'First Option', children: [ - { role: 'MenuListOption', name: '', value: 'First Option', selected: true }, - { role: 'MenuListOption', name: '', value: 'Second Option' }] - } ] }; expect(await page.accessibility.snapshot()).toEqual(golden); @@ -338,6 +325,19 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT}) await page.$eval('button', button => button.remove()); expect(await page.accessibility.snapshot({root: button})).toEqual(null); }); + it('should show uninteresting nodes', async({page}) => { + await page.setContent(` +
+
hi
+
+ `); + + const root = await page.$('#root'); + const snapshot = await page.accessibility.snapshot({root, interestingOnly: false}); + expect(snapshot.role).toBe('textbox'); + expect(snapshot.value).toBe('hi'); + expect(!!snapshot.children).toBe(true); + }); }); }); function findFocusedNode(node) {