diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts
index 5ed6fd7b85..a7b3cd1d4b 100644
--- a/packages/playwright-core/src/server/injected/roleUtils.ts
+++ b/packages/playwright-core/src/server/injected/roleUtils.ts
@@ -354,27 +354,34 @@ export function getPseudoContent(element: Element, pseudo: '::before' | '::after
if (cache?.has(element))
return cache?.get(element) || '';
const pseudoStyle = getElementComputedStyle(element, pseudo);
- const content = getPseudoContentImpl(pseudoStyle);
+ const content = getPseudoContentImpl(element, pseudoStyle);
if (cache)
cache.set(element, content);
return content;
}
-function getPseudoContentImpl(pseudoStyle: CSSStyleDeclaration | undefined) {
+function getPseudoContentImpl(element: Element, pseudoStyle: CSSStyleDeclaration | undefined) {
// Note: all browsers ignore display:none and visibility:hidden pseudos.
if (!pseudoStyle || pseudoStyle.display === 'none' || pseudoStyle.visibility === 'hidden')
return '';
const content = pseudoStyle.content;
+ let resolvedContent: string | undefined;
if ((content[0] === '\'' && content[content.length - 1] === '\'') ||
(content[0] === '"' && content[content.length - 1] === '"')) {
- const unquoted = content.substring(1, content.length - 1);
+ resolvedContent = content.substring(1, content.length - 1);
+ } else if (content.startsWith('attr(') && content.endsWith(')')) {
+ // Firefox does not resolve attribute accessors in content.
+ const attrName = content.substring('attr('.length, content.length - 1).trim();
+ resolvedContent = element.getAttribute(attrName) || '';
+ }
+ if (resolvedContent !== undefined) {
// SPEC DIFFERENCE.
// Spec says "CSS textual content, without a space", but we account for display
// to pass "name_file-label-inline-block-styles-manual.html"
const display = pseudoStyle.display || 'inline';
if (display !== 'inline')
- return ' ' + unquoted + ' ';
- return unquoted;
+ return ' ' + resolvedContent + ' ';
+ return resolvedContent;
}
return '';
}
diff --git a/tests/library/role-utils.spec.ts b/tests/library/role-utils.spec.ts
index 2b5792d0f1..1b625a106a 100644
--- a/tests/library/role-utils.spec.ts
+++ b/tests/library/role-utils.spec.ts
@@ -495,6 +495,21 @@ test('should not include hidden pseudo into accessible name', async ({ page }) =
expect.soft(await getNameAndRole(page, 'a')).toEqual({ role: 'link', name: 'hello hello' });
});
+test('should resolve pseudo content from attr', async ({ page }) => {
+ await page.setContent(`
+
+
+ world
+
+ `);
+ expect(await getNameAndRole(page, 'a')).toEqual({ role: 'link', name: 'hello world' });
+});
+
test('should ignore invalid aria-labelledby', async ({ page }) => {
await page.setContent(`