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 >")

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 >")')

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


.then(() => {
  return $browser.executeScript('return document.querySelector("body > form > div > label > popup-info").shadowRoot.querySelector("span >")').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!


Thanks @daffinito, this is just what I needed to test our heavily web component based app. I do have a follow up question though. As the page is generated iteratively based on data from XHR calls, I need to add proper waits in the mix. If I don’t, the executed script fails with null reference errors.

Here’s the element I need to access:
return document.querySelector("body > booking-container").shadowRoot.querySelector("div > > router-outlet > meeting-type-booking-page").shadowRoot.querySelector("div.calendar-container > available-times").shadowRoot.querySelector(".title")'

Notice how many shadowRoots I need to open up. If I do this without a wait, I’ll get: Error: javascript error: Cannot read property 'shadowRoot' of null and that makes perfect sense.

Adding a simple then(function(){$browser.sleep(5000);}) fixes the issue, but of course I don’t want a static wait in there, I’d like an element based finder. Any suggestions how to do that?

I’m curious what you would do in the case of having to click on an element within the shadworoot? I’m running into a lot of issues where I am getting “Error: javascript error: Cannot read property ‘shadowRoot’ of null” errors.