diff --git a/packages/playwright-core/src/server/injected/domUtils.ts b/packages/playwright-core/src/server/injected/domUtils.ts index f22f8e5e4c..5e6f98bd23 100644 --- a/packages/playwright-core/src/server/injected/domUtils.ts +++ b/packages/playwright-core/src/server/injected/domUtils.ts @@ -103,7 +103,7 @@ export function isElementVisible(element: Element): boolean { return rect.width > 0 && rect.height > 0; } -function isVisibleTextNode(node: Text) { +export function isVisibleTextNode(node: Text) { // https://stackoverflow.com/questions/1461059/is-there-an-equivalent-to-getboundingclientrect-for-text-nodes const range = node.ownerDocument.createRange(); range.selectNode(node); diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts index 18ae2f33c9..ad38aeab20 100644 --- a/packages/playwright-core/src/server/injected/roleUtils.ts +++ b/packages/playwright-core/src/server/injected/roleUtils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { closestCrossShadow, enclosingShadowRootOrDocument, getElementComputedStyle, isElementStyleVisibilityVisible, parentElementOrShadowHost } from './domUtils'; +import { closestCrossShadow, enclosingShadowRootOrDocument, getElementComputedStyle, isElementStyleVisibilityVisible, isVisibleTextNode, parentElementOrShadowHost } from './domUtils'; function hasExplicitAccessibleName(e: Element) { return e.hasAttribute('aria-label') || e.hasAttribute('aria-labelledby'); @@ -238,11 +238,22 @@ function getAriaBoolean(attr: string | null) { export function isElementHiddenForAria(element: Element, cache: Map): boolean { if (['STYLE', 'SCRIPT', 'NOSCRIPT', 'TEMPLATE'].includes(element.tagName)) return true; + const style = getElementComputedStyle(element); + const isSlot = element.nodeName === 'SLOT'; + if (style?.display === 'contents' && !isSlot) { + // display:contents is not rendered itself, but its child nodes are. + for (let child = element.firstChild; child; child = child.nextSibling) { + if (child.nodeType === 1 /* Node.ELEMENT_NODE */ && !isElementHiddenForAria(child as Element, cache)) + return false; + if (child.nodeType === 3 /* Node.TEXT_NODE */ && isVisibleTextNode(child as Text)) + return false; + } + return true; + } // Note: