From dda93b76cdd4c0c08b07ba589a1cc47e252159d7 Mon Sep 17 00:00:00 2001 From: Devin Rousso Date: Tue, 21 Jul 2026 09:41:54 -0600 Subject: [PATCH] fix(role): name `` and `` from an associated label `` and `` are labelable, but the html-aam per-element name step only ran the associated-labels branch for `TEXTAREA`/`SELECT`/`INPUT` (plus `BUTTON`/`OUTPUT`) --- packages/injected/src/roleUtils.ts | 4 ++-- tests/library/role-utils.spec.ts | 15 +++++++++++++++ tests/library/selector-generator.spec.ts | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/injected/src/roleUtils.ts b/packages/injected/src/roleUtils.ts index 6e9f8daa24929..a0e7f43339a2b 100644 --- a/packages/injected/src/roleUtils.ts +++ b/packages/injected/src/roleUtils.ts @@ -848,9 +848,9 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt // For "other form elements", we count select and any other input. // // Note: WebKit does not follow the spec and uses placeholder when aria-labelledby is present. - if (!labelledBy && (tagName === 'TEXTAREA' || tagName === 'SELECT' || tagName === 'INPUT')) { + if (!labelledBy && (tagName === 'TEXTAREA' || tagName === 'SELECT' || tagName === 'INPUT' || tagName === 'METER' || tagName === 'PROGRESS')) { options.visitedElements.add(element); - const labels = (element as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)).labels || []; + const labels = (element as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLMeterElement | HTMLProgressElement)).labels || []; if (labels.length) return getAccessibleNameFromAssociatedLabels(labels, options); diff --git a/tests/library/role-utils.spec.ts b/tests/library/role-utils.spec.ts index 21594e7c06425..81be2a9f163c7 100644 --- a/tests/library/role-utils.spec.ts +++ b/tests/library/role-utils.spec.ts @@ -344,6 +344,21 @@ test('input type=search maps to searchbox unless list points at a datalist', { expect.soft(await getNameAndRole(page, '#search4')).toEqual({ role: 'combobox', name: '' }); }); +test('meter and progress get their name from an associated label', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41891' }, +}, async ({ page }) => { + await page.setContent(` + + + + + `); + expect.soft(await getNameAndRole(page, '#meter1')).toEqual({ role: 'meter', name: 'Battery' }); + expect.soft(await getNameAndRole(page, '#progress1')).toEqual({ role: 'progressbar', name: 'Loading' }); + expect.soft(await getNameAndRole(page, '#meter2')).toEqual({ role: 'meter', name: 'Charge' }); + expect.soft(await getNameAndRole(page, '#meter3')).toEqual({ role: 'meter', name: 'Overridden' }); +}); + test('native controls labelled-by', async ({ page }) => { await page.setContent(` diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index 6d050cacf64bd..a8a233940c9a8 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -593,7 +593,7 @@ it.describe('selector generator', () => { expect.soft(await generate(page, '#target1')).toBe('internal:role=textbox[name=\"Target1\"i]'); expect.soft(await generate(page, '#target2')).toBe('internal:role=button[name=\"Target2\"i]'); expect.soft(await generate(page, '#target3')).toBe('internal:label=\"Target3\"i'); - expect.soft(await generate(page, '#target4')).toBe('internal:label=\"Target4\"i'); + expect.soft(await generate(page, '#target4')).toBe('internal:role=progressbar[name=\"Target4\"i]'); expect.soft(await generate(page, '#target5')).toBe('#target5'); expect.soft(await generate(page, '#target6')).toBe('internal:text="text"i');