Playwright Selecting elements in Shadow DOM

Profile picture for user arilio666

Playwright CSS and Text Engines pierce the Shadow DOM by default. Which makes it simple to locate shadow elements, and you can work with them like other DOM elements. However, you need to consider a few rules while working with Shadow DOM elements which will discuss in this article.

Table of Contents

  1. Shadow DOM Terminology
  2. How Shadow DOM elements are Located
  3. Demo Website
  4. Locate Element with Unique Selector
  5. Locate Element with Same Selector
  6. Using the CSS :light extension and the text:light selector
  7. Video Tutorial

1. Shadow DOM Terminology

Playwright Selecting elements in Shadow DOM

Here are the basic shadow dom terminology:

  • Shadow host: The regular DOM node to which the shadow DOM is attached.
  • Shadow tree: The DOM tree inside the shadow DOM.
  • Shadow boundary: The place where the shadow DOM ends and the regular DOM begins.
  • Shadow root: The root node of the shadow tree.

How Shadow DOM elements are Located

  • First, Playwright engines search for the elements in the light DOM in the iteration order then CSS or text engines search recursively inside open Shadow DOM roots in the iteration order.
  • In the CSS engine, any Descendant combinator (e.g., ul.menu li) or Child combinator (e.g., ul.menu li) pierces an arbitrary number of open shadow roots, including the implicit descendant combinator at the start of the selector. 
  • CSS or Text engines do not search inside closed shadow roots or iframes. For iFrame, you can use the Frame locator.
  • You can opt-out of default behavior and differentiate light DOM elements from Shadow DOM elements using the :light CSS extension or text:light selector engine. If you use this technique, the engines do not pierce shadow roots.

Demo Website

Link: http://autopract.com/selenium/shadowdom1/

handle shadow dom in playwirght

Here you can see we have two shadow trees. The div with class target1 exists in Light DOM and both Shadow DOMs. The div with target2 class exists in the first shadow dom, and div with target3 exists in the second shadow dom.

Locate Element with Unique Selector

In case you are able to find a unique selector, you can directly locate the element using that selector, no matter if it is in shadow dom or light dom. Here div.target2 and div.target3 selector is unique, and there is no conflict.

// Element in Shadow DOM 1
console.log(await page.locator('div.target2').textContent())
// Element in Shadow DOM 2
console.log(await page.locator('div.target3').textContent())

The output will be Shadow Element 1-2 and Shadow Element 2-2.

Locate Element with Same Selector

When you have same selector you will get strict mode violation error.

// Strict Mode Violation - Same Element in Light, Shadow DOM 1 and Shadow DOM 2
console.log(await page.locator('div.target1').textContent())

You can use in-build locator techniques or methods of the playwright to avoid strict mode violation.

 // Element in Light DOM
console.log(await page.locator('div.target1').first().textContent())
 // Element in Shadow DOM 1
console.log(await page.locator('div.target1').nth(1).textContent())
 // Element in Shadow DOM 2
console.log(await page.locator('div.target1').last().textContent())

You will see Light Element, Shadow Element 1-1, and Shadow Element 2-1 text printed in the output.

You can also make a unique selector by referring to the parent or shadow root element.

 // Element in Shadow DOM 1
 console.log(await page.locator('div.firstshadow div.target1').textContent())
 // Element in Shadow DOM 2
 console.log(await page.locator('div.secondshadow div.target1').textContent())

The above code will output Shadow Element 1-1 and Shadow Element 2-1.

Using the CSS :light extension and the text:light selector

You can differentiate parent elements with shadow dom elements using the CSS:light extension or text:light selector.

// Light DOM Element using CSS Engine
console.log(await page.locator(':light(div.target1)').textContent())
// Light DOM Element using Text Engine
console.log(await page.locator('text:light=Element').textContent())

This will output Light Element text twice.

Video Tutorial: Playwright Shadow DOM