mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(role): accessibleName computation should walk the flat dom tree (#19301)
- When visiting `<slot>` element, descend into assigned nodes. - When node has `assignedSlot`, skip it during regular traversal. Fixes #18989.
This commit is contained in:
parent
8660288518
commit
4784dae10f
@ -579,7 +579,9 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
|
||||
if (allowsNameFromContent || options.embeddedInLabelledBy !== 'none' || options.embeddedInLabel !== 'none' || options.embeddedInTextAlternativeElement || options.embeddedInTargetElement === 'descendant') {
|
||||
options.visitedElements.add(element);
|
||||
const tokens: string[] = [];
|
||||
const visit = (node: Node) => {
|
||||
const visit = (node: Node, skipSlotted: boolean) => {
|
||||
if (skipSlotted && (node as Element | Text).assignedSlot)
|
||||
return;
|
||||
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
|
||||
const display = getComputedStyle(node as Element)?.getPropertyValue('display') || 'inline';
|
||||
let token = getElementAccessibleNameInternal(node as Element, childOptions);
|
||||
@ -596,14 +598,20 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
|
||||
}
|
||||
};
|
||||
tokens.push(getPseudoContent(getComputedStyle(element, '::before')));
|
||||
for (let child = element.firstChild; child; child = child.nextSibling)
|
||||
visit(child);
|
||||
if (element.shadowRoot) {
|
||||
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
|
||||
visit(child);
|
||||
const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
|
||||
if (assignedNodes.length) {
|
||||
for (const child of assignedNodes)
|
||||
visit(child, false);
|
||||
} else {
|
||||
for (let child = element.firstChild; child; child = child.nextSibling)
|
||||
visit(child, true);
|
||||
if (element.shadowRoot) {
|
||||
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
|
||||
visit(child, true);
|
||||
}
|
||||
for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
|
||||
visit(owned, true);
|
||||
}
|
||||
for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
|
||||
visit(owned);
|
||||
tokens.push(getPseudoContent(getComputedStyle(element, '::after')));
|
||||
const accessibleName = tokens.join('');
|
||||
if (accessibleName.trim())
|
||||
|
||||
@ -423,3 +423,63 @@ test('errors', async ({ page }) => {
|
||||
const e8 = await page.$('role=treeitem[expanded="none"]').catch(e => e);
|
||||
expect(e8.message).toContain(`"expanded" must be one of true, false`);
|
||||
});
|
||||
|
||||
test('should detect accessible name with slots', async ({ page }) => {
|
||||
// Text "foo" is assigned to the slot, should not be used twice.
|
||||
await page.setContent(`
|
||||
<button><div>foo</div></button>
|
||||
<script>
|
||||
(() => {
|
||||
const container = document.querySelector('div');
|
||||
const shadow = container.attachShadow({ mode: 'open' });
|
||||
const slot = document.createElement('slot');
|
||||
shadow.appendChild(slot);
|
||||
})();
|
||||
</script>
|
||||
`);
|
||||
expect(await page.locator(`role=button[name="foo"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
|
||||
`<button><div>foo</div></button>`,
|
||||
]);
|
||||
|
||||
// Text "foo" is assigned to the slot, should be used instead of slot content.
|
||||
await page.setContent(`
|
||||
<div>foo</div>
|
||||
<script>
|
||||
(() => {
|
||||
const container = document.querySelector('div');
|
||||
const shadow = container.attachShadow({ mode: 'open' });
|
||||
const button = document.createElement('button');
|
||||
shadow.appendChild(button);
|
||||
const slot = document.createElement('slot');
|
||||
button.appendChild(slot);
|
||||
const span = document.createElement('span');
|
||||
span.textContent = 'pre';
|
||||
slot.appendChild(span);
|
||||
})();
|
||||
</script>
|
||||
`);
|
||||
expect(await page.locator(`role=button[name="foo"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
|
||||
`<button><slot><span>pre</span></slot></button>`,
|
||||
]);
|
||||
|
||||
// Nothing is assigned to the slot, should use slot content.
|
||||
await page.setContent(`
|
||||
<div></div>
|
||||
<script>
|
||||
(() => {
|
||||
const container = document.querySelector('div');
|
||||
const shadow = container.attachShadow({ mode: 'open' });
|
||||
const button = document.createElement('button');
|
||||
shadow.appendChild(button);
|
||||
const slot = document.createElement('slot');
|
||||
button.appendChild(slot);
|
||||
const span = document.createElement('span');
|
||||
span.textContent = 'pre';
|
||||
slot.appendChild(span);
|
||||
})();
|
||||
</script>
|
||||
`);
|
||||
expect(await page.locator(`role=button[name="pre"]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
|
||||
`<button><slot><span>pre</span></slot></button>`,
|
||||
]);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user