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:
+ '' +
+ ' ' +
+ ' - ' +
+ ' File' +
+ '
' +
+ ' ' +
+ ' - New
' +
+ ' - Open…
' +
+ ' …' +
+ '
' +
+ ' ' +
+ '
',
+ target: ['#rule2a', '#rule2c'],
+ accessibleText: ['File', 'New'],
+ },
+
+ {
+ html:
+ '',
+ 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:
+ 'This is a ' +
+ 'hidden ' +
+ 'secret
' +
+ '' +
+ '',
+ 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
' +
+ '
' +
+ '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: 'HelloWorld
',
+ 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: 'Not part of a11yName
',
+ 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: '
',
+ 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:
+ '',
+ 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];
+}