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');