Relic Solution: Interacting with elements in a Shadow DOM

Do you have a website that uses the Shadow DOM and you’d like to monitor it using a Synthetics Scripted Browser monitor? Have you tried writing a script already and ran into errors like the following, when trying to locate the elements in the Shadow DOM:

TaskTimedOut: Task timed-out waiting for element to be located using: By(xpath, /html/body/form/div/label/popup-info//span/span[2])
Error: no such element: Unable to locate element: {"method":"xpath","selector":"/html/body/form/div/label/popup-info//span/span[2]"}

If so, you’re in the right place! I’ve put together an example below that demonstrates how to interact with a Shadow DOM element, using Javascript and $browser.executeScript. I’ve used Mozilla’s Shadow DOM example, found here, for this example.

Let’s say we want to confirm the text in the popup has the word “validation” in it. If we look at the HTML in the Elements panel of Chrome DevTools, we can see that the element is in the Shadow DOM by the #shadow-root (open) node:

Using $browser.executeScript, we can access the shadow root. An easy way to generate the Javascript code needed is to right click on the element in the DevTools and select Copy > Copy JS path:

This returns the following:

document.querySelector("body > form > div > label > popup-info").shadowRoot.querySelector("span > span.info")

All we need to do is add a return to the beginning of that and we can use it with $browser.executeScript to get a reference to the Shadow DOM element:

$browser.executeScript('return document.querySelector("body > form > div > label > popup-info").shadowRoot.querySelector("span > span.info")')

This will return the element as a promise, which we can access with .then((el) => { … }). Here is a complete example:

var assert = require('assert'),
	By = $driver.By

$browser.get('https://mdn.github.io/web-components-examples/popup-info-box-external-stylesheet')

.then(() => {
  return $browser.executeScript('return document.querySelector("body > form > div > label > popup-info").shadowRoot.querySelector("span > span.info")').then((el) => {
	return el.getText().then((text) => {
  	assert.ok(text.indexOf('validation') !== -1)
	})
  })
})

Hopefully this helps you interact with your Shadow DOM elements!

Questions? Feedback? Have an alternative solution? Let us know below!

3 Likes