diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts index 38ceeb201a..e913cea8f3 100644 --- a/packages/playwright-core/src/server/injected/roleUtils.ts +++ b/packages/playwright-core/src/server/injected/roleUtils.ts @@ -459,7 +459,9 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN const title = element.getAttribute('title') || ''; if (title.trim()) return title; - return 'Submit Query'; + // SPEC DIFFERENCE. + // Spec says return localized "Submit Query", but browsers and axe-core insist on "Sumbit". + return 'Submit'; } // https://w3c.github.io/html-aam/#input-type-text-input-type-password-input-type-search-input-type-tel-input-type-url-and-textarea-element @@ -538,6 +540,11 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN } } // SPEC DIFFERENCE. + // Spec does not say a word about , but all browsers actually support it. + const summary = element.getAttribute('summary') || ''; + if (summary) + return summary; + // SPEC DIFFERENCE. // Spec says "if the table element has a title attribute, then use that attribute". // We ignore title to pass "name_from_content-manual.html". } diff --git a/tests/assets/axe-core/README.md b/tests/assets/axe-core/README.md index c7cdeccf68..6b075462f8 100644 --- a/tests/assets/axe-core/README.md +++ b/tests/assets/axe-core/README.md @@ -4,4 +4,5 @@ Includes: - `LICENSE` Modifed: -- `implicit-role.js` contains extracted test cases from `/test/commons/aria/implicit-role.js` +- `implicit-role.js` contains test cases extracted from `/test/commons/aria/implicit-role.js` +- `accessible-text.js` contains test cases extracted from `/test/commons/aria/accessible-text.js` diff --git a/tests/assets/axe-core/accessible-text.js b/tests/assets/axe-core/accessible-text.js new file mode 100644 index 0000000000..a951b7bd30 --- /dev/null +++ b/tests/assets/axe-core/accessible-text.js @@ -0,0 +1,1119 @@ +module.exports = [ + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: + '', + target: ['#rule2a', '#rule2c'], + accessibleText: ['File', 'New'], + }, + + { + html: + '
' + + ' Meeting alarms' + + ' ' + + '
' + + '
' + + ' ' + + ' ' + + ' ' + + '
', + target: ['#beep', '#flash'], + accessibleText: ['Beep', 'Flash the screen 3 times'], + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t1', + accessibleText: 'This is a label', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t1', + accessibleText: 'ARIA Label This is a label', + }, + + { + html: + '' + + '' + + '', + target: '#t1', + // accessibleText: 'This is a hidden secret', + // Note: axe-core insists on child nodes being used as visible, although + // spec 2A says "directly referenced by aria-labelledby". + accessibleText: 'This is a', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t1', + accessibleText: 'ARIA Label', + }, + + { + html: + '
This is of everything
' + + 'Alt text goes here' + + '
This is a label
' + + '' + + '', + target: '#target', + accessibleText: 'Alt text goes here', + }, + + { + html: + '
This is of everything
' + + '' + + '
This is a label
' + + '' + + '', + target: '#target', + accessibleText: 'Alt text goes here', + }, + + { + html: + '
This is of everything
' + + '' + + '
This is a label
' + + '' + + '', + target: '#target', + accessibleText: '', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t1', + accessibleText: 'HTML Label', + }, + + { + html: + '
This is of
' + + '
This is a label
' + + '' + + '', + target: '#t2label', + accessibleText: 'This is This is a label of italics', + }, + + { + html: + '
This is of
' + + '
This is a label
' + + '' + + '', + target: '#t2label', + accessibleText: 'This is This is a label of', + }, + + { + html: + '
This is ' + + ' ' + + ' of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2label', + accessibleText: 'This is This is a label of everything', + }, + + { + html: + '
' + + '
', + target: '#target', + accessibleText: 'My form input', + }, + + { + html: + '
' + + '
' + + '', + target: '#target', + accessibleText: 'My form input', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2label', + accessibleText: 'This is This is a label of everything', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2', + accessibleText: 'This is the value of everything', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2', + accessibleText: 'This is of everything', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2', + accessibleText: 'This is the value of everything', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2', + accessibleText: 'This is first third of everything', + }, + + { + html: + '
This is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2', + accessibleText: 'This is the value of everything', + }, + + { + html: + '
This span' + + ' is of everything
' + + '
This is a label
' + + '' + + '', + target: '#t2', + accessibleText: 'This not a span is the value of everything', + }, + + { + html: + '' + + '', + target: '#target', + accessibleText: 'Chosen', + }, + + { + html: + '' + + '' + + '', + target: '#target', + // accessibleText: '', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: 'Chosen', + }, + + { + html: + '', + target: '#target', + accessibleText: '', + }, + + { + html: + '' + + '', + target: '#target', + // accessibleText: '', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: 'Chosen', + }, + + { + html: '', + target: 'a', + accessibleText: 'Hello', + }, + + { + html: 'Hello', + target: 'a', + accessibleText: 'Hello', + }, + + { + html: '', + target: 'button', + accessibleText: 'Hello', + }, + + { + html: 'Hello', + target: 'summary', + // accessibleText: 'Hello', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: '', + }, + + { + html: '', + target: 'a', + accessibleText: 'Hello', + }, + + { + html: 'Hello', + target: 'a', + accessibleText: 'Hello', + }, + + { + html: '', + target: 'button', + accessibleText: 'Hello', + }, + + { + html: 'Hello', + target: 'summary', + // accessibleText: 'Hello', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: '', + }, + + { + html: 'HelloWorld', + target: 'a', + accessibleText: 'HelloWorld', + }, + + { + html: 'Hello
World
', + target: 'a', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'a', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: + '', + target: 'button', + accessibleText: 'Hello World', + }, + + { + html: + '
This is of everything
' + + '
', + target: '#t1', + accessibleText: 'ARIA Label', + }, + + { + html: '
', + target: 'input', + accessibleText: 'I will be king', + }, + + { + html: + '
' + + '
', + target: 'input', + accessibleText: 'you will be queen', + }, + + { + html: '
', + target: 'input', + accessibleText: 'Fallback content heroes', + }, + + { + html: + '
Hello
' + + '
Not part of a11yName
Fail
', + target: 'figure', + accessibleText: 'Hello', + }, + + { + html: + '
Not part of a11yName
Fail
', + target: 'figure', + accessibleText: 'Hello', + }, + + { + html: + '
Not part of a11yName
Hello
', + target: 'figure', + accessibleText: 'Hello', + }, + + { + html: + '
Not part of a11yName
', + target: 'figure', + // accessibleText: 'Hello', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: '', + }, + + { + html: '
Hello
', + target: 'figure', + // accessibleText: 'Hello', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: '', + }, + + { + html: '
', + target: 'figure', + // accessibleText: 'Hello', + // Chrome and axe-core disagree, we follow Chrome and spec. + accessibleText: '', + }, + + { + html: + '
Hello
World
' + + '', + target: 'img', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'img', + accessibleText: 'Hello World', + }, + + { + html: 'Hello World', + target: 'img', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'img', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello', + }, + + { + html: '', + target: 'input', + accessibleText: 'Submit', + }, + + { + html: '', + target: 'input', + accessibleText: 'Reset', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello', + }, + + { + html: '', + target: 'input', + // accessibleText: 'Hello', + // Chrome and axe-core disagree. We follow Chrome and spec. + accessibleText: 'Reset', + }, + + { + html: '', + target: 'input', + // accessibleText: 'Hello', + // Chrome and axe-core disagree. We follow Chrome and spec. + accessibleText: 'Submit', + }, + + { + html: + '
Hello
World
' + + '
', + target: 'table', + accessibleText: 'Hello World', + }, + + { + html: '
', + target: 'table', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello World
Stuff
', + target: 'table', + accessibleText: 'Hello World', + }, + + { + html: '
', + target: 'table', + accessibleText: 'Hello World', + }, + + { + html: '
', + target: 'table', + accessibleText: 'Hello World', + }, + + { + html: '
', + target: 'table', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: '', + target: 'input', + accessibleText: '', + }, + + { + html: + '
Hello
World
' + + '', + target: 'textarea', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'textarea', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'textarea', + accessibleText: 'Hello World', + }, + + { + html: + '' + '', + target: 'textarea', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'textarea', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'textarea', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'textarea', + accessibleText: '', + }, + + { + html: + '
Hello
World
' + + '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'input', + accessibleText: 'Submit', + }, + + { + html: + '
Hello
World
' + + '', + target: 'a', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'a', + accessibleText: 'Hello World', + }, + + { + html: 'Hello World', + target: 'a', + // axe-core does not need href to be present, but spec and Chrome do. + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'a', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'a', + accessibleText: '', + }, + + { + html: + '' + + '' + + '' + + '' + + '' + + '
' + + 'Descriptive Link Text' + + '
' + + '
', + target: 'a', + accessibleText: 'Descriptive Link Text', + }, + + { + html: + '
Hello
World
' + + '', + target: 'button', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'button', + accessibleText: 'Hello World', + }, + + { + html: + '', + target: 'button', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'button', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'button', + accessibleText: '', + }, + + { + html: '
Hello
World
', + target: 'cite', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'cite', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'cite', + accessibleText: 'Hello World', + }, + + { + html: '', + target: 'cite', + accessibleText: '', + }, +]; diff --git a/tests/library/role-utils.spec.ts b/tests/library/role-utils.spec.ts index 4d0045562a..55c4a3d1ad 100644 --- a/tests/library/role-utils.spec.ts +++ b/tests/library/role-utils.spec.ts @@ -80,7 +80,7 @@ test('wpt accname', async ({ page, asset, server, browserName }) => { } }); -test('axe-core implicit role', async ({ page, asset, server }) => { +test('axe-core implicit-role', async ({ page, asset, server }) => { await page.goto(server.EMPTY_PAGE); const testCases = require(asset('axe-core/implicit-role')); for (const testCase of testCases) { @@ -101,3 +101,41 @@ test('axe-core implicit role', async ({ page, asset, server }) => { }); } }); + +test('axe-core accessible-text', async ({ page, asset, server }) => { + await page.goto(server.EMPTY_PAGE); + const testCases = require(asset('axe-core/accessible-text')); + for (const testCase of testCases) { + await test.step(`checking ${JSON.stringify(testCase)}`, async () => { + await page.setContent(` + + ${testCase.html} + + + `); + // Use $eval to force injected script. + const targets = toArray(testCase.target); + const expected = toArray(testCase.accessibleText); + const received = await page.$eval('body', (_, selectors) => { + return selectors.map(selector => { + const injected = (window as any).__injectedScript; + const element = injected.querySelector(injected.parseSelector('css=' + selector), document, false); + if (!element) + throw new Error(`Unable to resolve "${selector}"`); + return injected.getElementAccessibleName(element); + }); + }, targets); + expect(received, `checking ${JSON.stringify(testCase)}`).toEqual(expected); + }); + } +}); + +function toArray(x: any): any[] { + return Array.isArray(x) ? x : [x]; +}