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]; }