summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/test/src/accessibility.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/test/src/accessibility.spec.ts')
-rw-r--r--remote/test/puppeteer/test/src/accessibility.spec.ts582
1 files changed, 582 insertions, 0 deletions
diff --git a/remote/test/puppeteer/test/src/accessibility.spec.ts b/remote/test/puppeteer/test/src/accessibility.spec.ts
new file mode 100644
index 0000000000..3ab518e703
--- /dev/null
+++ b/remote/test/puppeteer/test/src/accessibility.spec.ts
@@ -0,0 +1,582 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import assert from 'assert';
+
+import expect from 'expect';
+import {SerializedAXNode} from 'puppeteer-core/internal/common/Accessibility.js';
+
+import {
+ getTestState,
+ setupTestBrowserHooks,
+ setupTestPageAndContextHooks,
+} from './mocha-utils.js';
+
+describe('Accessibility', function () {
+ setupTestBrowserHooks();
+ setupTestPageAndContextHooks();
+
+ it('should work', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <head>
+ <title>Accessibility Test</title>
+ </head>
+ <body>
+ <div>Hello World</div>
+ <h1>Inputs</h1>
+ <input placeholder="Empty input" autofocus />
+ <input placeholder="readonly input" readonly />
+ <input placeholder="disabled input" disabled />
+ <input aria-label="Input with whitespace" value=" " />
+ <input value="value only" />
+ <input aria-placeholder="placeholder" value="and a value" />
+ <div aria-hidden="true" id="desc">This is a description!</div>
+ <input aria-placeholder="placeholder" value="and a value" aria-describedby="desc" />
+ <select>
+ <option>First Option</option>
+ <option>Second Option</option>
+ </select>
+ </body>`);
+
+ await page.focus('[placeholder="Empty input"]');
+ const golden = isFirefox
+ ? {
+ role: 'document',
+ name: 'Accessibility Test',
+ children: [
+ {role: 'text leaf', name: 'Hello World'},
+ {role: 'heading', name: 'Inputs', level: 1},
+ {role: 'entry', name: 'Empty input', focused: true},
+ {role: 'entry', name: 'readonly input', readonly: true},
+ {role: 'entry', name: 'disabled input', disabled: true},
+ {role: 'entry', name: 'Input with whitespace', value: ' '},
+ {role: 'entry', name: '', value: 'value only'},
+ {role: 'entry', name: '', value: 'and a value'}, // firefox doesn't use aria-placeholder for the name
+ {
+ role: 'entry',
+ 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'},
+ ],
+ },
+ ],
+ }
+ : {
+ role: 'RootWebArea',
+ name: 'Accessibility Test',
+ children: [
+ {role: 'StaticText', name: 'Hello World'},
+ {role: 'heading', name: 'Inputs', level: 1},
+ {role: 'textbox', name: 'Empty input', focused: true},
+ {role: 'textbox', name: 'readonly input', readonly: true},
+ {role: 'textbox', name: 'disabled input', disabled: true},
+ {role: 'textbox', name: 'Input with whitespace', value: ' '},
+ {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',
+ haspopup: 'menu',
+ children: [
+ {role: 'menuitem', name: 'First Option', selected: true},
+ {role: 'menuitem', name: 'Second Option'},
+ ],
+ },
+ ],
+ };
+ expect(await page.accessibility.snapshot()).toEqual(golden);
+ });
+ it('should report uninteresting nodes', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`<textarea>hi</textarea>`);
+ await page.focus('textarea');
+ const golden = isFirefox
+ ? {
+ role: 'entry',
+ name: '',
+ value: 'hi',
+ focused: true,
+ multiline: true,
+ children: [
+ {
+ role: 'text leaf',
+ name: 'hi',
+ },
+ ],
+ }
+ : {
+ role: 'textbox',
+ name: '',
+ value: 'hi',
+ focused: true,
+ multiline: true,
+ children: [
+ {
+ role: 'generic',
+ name: '',
+ children: [
+ {
+ role: 'StaticText',
+ name: 'hi',
+ },
+ ],
+ },
+ ],
+ };
+ expect(
+ findFocusedNode(
+ await page.accessibility.snapshot({interestingOnly: false})
+ )
+ ).toEqual(golden);
+ });
+ it('get snapshots while the tree is re-calculated', async () => {
+ // see https://github.com/puppeteer/puppeteer/issues/9404
+ const {page} = getTestState();
+
+ await page.setContent(
+ `<!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Accessible name + aria-expanded puppeteer bug</title>
+ <style>
+ [aria-expanded="false"] + * {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <button hidden>Show</button>
+ <p>Some content</p>
+ <script>
+ const button = document.querySelector('button');
+ button.removeAttribute('hidden')
+ button.setAttribute('aria-expanded', 'false');
+ button.addEventListener('click', function() {
+ button.setAttribute('aria-expanded', button.getAttribute('aria-expanded') !== 'true')
+ if (button.getAttribute('aria-expanded') == 'true') {
+ button.textContent = 'Hide'
+ } else {
+ button.textContent = 'Show'
+ }
+ })
+ </script>
+ </body>
+ </html>`
+ );
+ async function getAccessibleName(page: any, element: any) {
+ return (await page.accessibility.snapshot({root: element})).name;
+ }
+ const button = await page.$('button');
+ expect(await getAccessibleName(page, button)).toEqual('Show');
+ await button?.click();
+ await page.waitForSelector('aria/Hide');
+ });
+ it('roledescription', async () => {
+ const {page} = getTestState();
+
+ await page.setContent(
+ '<div tabIndex=-1 aria-roledescription="foo">Hi</div>'
+ );
+ const snapshot = await page.accessibility.snapshot();
+ // See https://chromium-review.googlesource.com/c/chromium/src/+/3088862
+ assert(snapshot);
+ assert(snapshot.children);
+ assert(snapshot.children[0]);
+ expect(snapshot.children[0]!.roledescription).toBeUndefined();
+ });
+ it('orientation', async () => {
+ const {page} = getTestState();
+
+ await page.setContent(
+ '<a href="" role="slider" aria-orientation="vertical">11</a>'
+ );
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ assert(snapshot.children[0]);
+ expect(snapshot.children[0]!.orientation).toEqual('vertical');
+ });
+ it('autocomplete', async () => {
+ const {page} = getTestState();
+
+ await page.setContent('<input type="number" aria-autocomplete="list" />');
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ assert(snapshot.children[0]);
+ expect(snapshot.children[0]!.autocomplete).toEqual('list');
+ });
+ it('multiselectable', async () => {
+ const {page} = getTestState();
+
+ await page.setContent(
+ '<div role="grid" tabIndex=-1 aria-multiselectable=true>hey</div>'
+ );
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ assert(snapshot.children[0]);
+ expect(snapshot.children[0]!.multiselectable).toEqual(true);
+ });
+ it('keyshortcuts', async () => {
+ const {page} = getTestState();
+
+ await page.setContent(
+ '<div role="grid" tabIndex=-1 aria-keyshortcuts="foo">hey</div>'
+ );
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ assert(snapshot.children[0]);
+ expect(snapshot.children[0]!.keyshortcuts).toEqual('foo');
+ });
+ describe('filtering children of leaf nodes', function () {
+ it('should not report text nodes inside controls', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <div role="tablist">
+ <div role="tab" aria-selected="true"><b>Tab1</b></div>
+ <div role="tab">Tab2</div>
+ </div>`);
+ const golden = isFirefox
+ ? {
+ role: 'document',
+ name: '',
+ children: [
+ {
+ role: 'pagetab',
+ name: 'Tab1',
+ selected: true,
+ },
+ {
+ role: 'pagetab',
+ name: 'Tab2',
+ },
+ ],
+ }
+ : {
+ role: 'RootWebArea',
+ name: '',
+ children: [
+ {
+ role: 'tab',
+ name: 'Tab1',
+ selected: true,
+ },
+ {
+ role: 'tab',
+ name: 'Tab2',
+ },
+ ],
+ };
+ expect(await page.accessibility.snapshot()).toEqual(golden);
+ });
+ it('rich text editable fields should have children', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <div contenteditable="true">
+ Edit this image: <img src="fakeimage.png" alt="my fake image">
+ </div>`);
+ const golden = isFirefox
+ ? {
+ role: 'section',
+ name: '',
+ children: [
+ {
+ role: 'text leaf',
+ name: 'Edit this image:',
+ },
+ {
+ role: 'StaticText',
+ name: 'my fake image',
+ },
+ ],
+ }
+ : {
+ role: 'generic',
+ name: '',
+ value: 'Edit this image: ',
+ children: [
+ {
+ role: 'StaticText',
+ name: 'Edit this image: ',
+ },
+ {
+ role: 'img',
+ name: 'my fake image',
+ },
+ ],
+ };
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ expect(snapshot.children[0]).toEqual(golden);
+ });
+ it('rich text editable fields with role should have children', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <div contenteditable="true" role='textbox'>
+ Edit this image: <img src="fakeimage.png" alt="my fake image">
+ </div>`);
+ // Image node should not be exposed in contenteditable elements. See https://crbug.com/1324392.
+ const golden = isFirefox
+ ? {
+ role: 'entry',
+ name: '',
+ value: 'Edit this image: my fake image',
+ children: [
+ {
+ role: 'StaticText',
+ name: 'my fake image',
+ },
+ ],
+ }
+ : {
+ role: 'textbox',
+ name: '',
+ value: 'Edit this image: ',
+ multiline: true,
+ children: [
+ {
+ role: 'StaticText',
+ name: 'Edit this image: ',
+ },
+ ],
+ };
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ expect(snapshot.children[0]).toEqual(golden);
+ });
+
+ // Firefox does not support contenteditable="plaintext-only".
+ describe('plaintext contenteditable', function () {
+ it('plain text field with role should not have children', async () => {
+ const {page} = getTestState();
+
+ await page.setContent(`
+ <div contenteditable="plaintext-only" role='textbox'>Edit this image:<img src="fakeimage.png" alt="my fake image"></div>`);
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ expect(snapshot.children[0]).toEqual({
+ role: 'textbox',
+ name: '',
+ value: 'Edit this image:',
+ multiline: true,
+ });
+ });
+ });
+ it('non editable textbox with role and tabIndex and label should not have children', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <div role="textbox" tabIndex=0 aria-checked="true" aria-label="my favorite textbox">
+ this is the inner content
+ <img alt="yo" src="fakeimg.png">
+ </div>`);
+ const golden = isFirefox
+ ? {
+ role: 'entry',
+ name: 'my favorite textbox',
+ value: 'this is the inner content yo',
+ }
+ : {
+ role: 'textbox',
+ name: 'my favorite textbox',
+ value: 'this is the inner content ',
+ };
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ expect(snapshot.children[0]).toEqual(golden);
+ });
+ it('checkbox with and tabIndex and label should not have children', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <div role="checkbox" tabIndex=0 aria-checked="true" aria-label="my favorite checkbox">
+ this is the inner content
+ <img alt="yo" src="fakeimg.png">
+ </div>`);
+ const golden = isFirefox
+ ? {
+ role: 'checkbutton',
+ name: 'my favorite checkbox',
+ checked: true,
+ }
+ : {
+ role: 'checkbox',
+ name: 'my favorite checkbox',
+ checked: true,
+ };
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ expect(snapshot.children[0]).toEqual(golden);
+ });
+ it('checkbox without label should not have children', async () => {
+ const {page, isFirefox} = getTestState();
+
+ await page.setContent(`
+ <div role="checkbox" aria-checked="true">
+ this is the inner content
+ <img alt="yo" src="fakeimg.png">
+ </div>`);
+ const golden = isFirefox
+ ? {
+ role: 'checkbutton',
+ name: 'this is the inner content yo',
+ checked: true,
+ }
+ : {
+ role: 'checkbox',
+ name: 'this is the inner content yo',
+ checked: true,
+ };
+ const snapshot = await page.accessibility.snapshot();
+ assert(snapshot);
+ assert(snapshot.children);
+ expect(snapshot.children[0]).toEqual(golden);
+ });
+
+ describe('root option', function () {
+ it('should work a button', async () => {
+ const {page} = getTestState();
+
+ 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 () => {
+ const {page} = getTestState();
+
+ 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 () => {
+ const {page} = getTestState();
+
+ 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'},
+ ],
+ orientation: 'vertical',
+ });
+ });
+ it('should return null when the element is no longer in DOM', async () => {
+ const {page} = getTestState();
+
+ await page.setContent(`<button>My Button</button>`);
+ const button = (await page.$('button'))!;
+ await page.$eval('button', button => {
+ return button.remove();
+ });
+ expect(await page.accessibility.snapshot({root: button})).toEqual(null);
+ });
+ it('should support the interestingOnly option', async () => {
+ const {page} = getTestState();
+
+ 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: 'generic',
+ name: '',
+ children: [
+ {
+ role: 'button',
+ name: 'My Button',
+ children: [{role: 'StaticText', name: 'My Button'}],
+ },
+ ],
+ });
+ });
+ });
+ });
+
+ function findFocusedNode(
+ node: SerializedAXNode | null
+ ): SerializedAXNode | null {
+ if (node?.focused) {
+ return node;
+ }
+ for (const child of node?.children || []) {
+ const focusedChild = findFocusedNode(child);
+ if (focusedChild) {
+ return focusedChild;
+ }
+ }
+ return null;
+ }
+});