diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts
index b7c8a48207..d724bda841 100644
--- a/packages/playwright-core/src/server/injected/roleUtils.ts
+++ b/packages/playwright-core/src/server/injected/roleUtils.ts
@@ -463,7 +463,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
// step 2e.
if (!['presentation', 'none'].includes(role)) {
- // https://w3c.github.io/html-aam/#input-type-button-input-type-submit-and-input-type-reset
+ // https://w3c.github.io/html-aam/#input-type-button-input-type-submit-and-input-type-reset-accessible-name-computation
if (element.tagName === 'INPUT' && ['button', 'submit', 'reset'].includes((element as HTMLInputElement).type)) {
options.visitedElements.add(element);
const value = (element as HTMLInputElement).value || '';
@@ -477,7 +477,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
return title;
}
- // https://w3c.github.io/html-aam/#input-type-image
+ // https://w3c.github.io/html-aam/#input-type-image-accessible-name-computation
if (element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'image') {
options.visitedElements.add(element);
const alt = element.getAttribute('alt') || '';
@@ -504,8 +504,24 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
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
- // https://w3c.github.io/html-aam/#other-form-elements
+ // https://w3c.github.io/html-aam/#button-element-accessible-name-computation
+ if (element.tagName === 'BUTTON') {
+ options.visitedElements.add(element);
+ const labels = (element as HTMLButtonElement).labels || [];
+ if (labels.length) {
+ return [...labels].map(label => getElementAccessibleNameInternal(label, {
+ ...options,
+ embeddedInLabel: 'self',
+ embeddedInTextAlternativeElement: false,
+ embeddedInLabelledBy: 'none',
+ embeddedInTargetElement: 'none',
+ })).filter(accessibleName => !!accessibleName).join(' ');
+ }
+ // From here, fallthrough to step 2f.
+ }
+
+ // https://w3c.github.io/html-aam/#input-type-text-input-type-password-input-type-number-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-element-accessible-name-computation
+ // https://w3c.github.io/html-aam/#other-form-elements-accessible-name-computation
// For "other form elements", we count select and any other input.
if (element.tagName === 'TEXTAREA' || element.tagName === 'SELECT' || element.tagName === 'INPUT') {
options.visitedElements.add(element);
diff --git a/tests/library/role-utils.spec.ts b/tests/library/role-utils.spec.ts
index 869d701cba..f0186a9c7b 100644
--- a/tests/library/role-utils.spec.ts
+++ b/tests/library/role-utils.spec.ts
@@ -240,6 +240,32 @@ test('svg title', async ({ page }) => {
expect.soft(await getNameAndRole(page, 'a')).toEqual({ role: 'link', name: 'a link' });
});
+test('native controls', async ({ page }) => {
+ await page.setContent(`
+
+
+
+
+
+
+
+
+
+
+
+ `);
+
+ expect.soft(await getNameAndRole(page, '#text1')).toEqual({ role: 'textbox', name: 'TEXT1' });
+ expect.soft(await getNameAndRole(page, '#text2')).toEqual({ role: 'textbox', name: 'TEXT2' });
+ expect.soft(await getNameAndRole(page, '#text3')).toEqual({ role: 'textbox', name: 'TEXT3' });
+ expect.soft(await getNameAndRole(page, '#image1')).toEqual({ role: 'button', name: 'IMAGE1' });
+ expect.soft(await getNameAndRole(page, '#image2')).toEqual({ role: 'button', name: 'IMAGE2' });
+ expect.soft(await getNameAndRole(page, '#button1')).toEqual({ role: 'combobox', name: 'BUTTON1' });
+ expect.soft(await getNameAndRole(page, '#button2')).toEqual({ role: 'combobox', name: '' });
+ expect.soft(await getNameAndRole(page, '#button3')).toEqual({ role: 'button', name: 'BUTTON3' });
+ expect.soft(await getNameAndRole(page, '#button4')).toEqual({ role: 'button', name: 'BUTTON4' });
+});
+
function toArray(x: any): any[] {
return Array.isArray(x) ? x : [x];
}