Working on a legacy monolithic software platform, the Shadow DOM hasn’t been much of an issue for me. However, now that we are working on bringing it into the modern world, that is changing. The first of these changes are currently being rolled out to our customers. Up until now, the testing and CI for new micro services and micro front end components have been solely the responsibility of the team building them.
Unfortunately we had an issue with one of these new components recently. This was caused by a change to the underlying monolith, so wasn’t detected by the stand alone testing. Lesson learnt! It is now time to update our end to end testing of the monolith to support the new micro frontend.
This first micro front end is replacing the navigation and, as mentioned, it sits within a Shadow DOM. Our end to end test framework is a C# Selenium SpecFlow setup. I am guessing as you already know as you are here and reading this, there is no out of the box support for using elements located in a Shadow DOM in this type of framework. As you can imagine, that is a bit of a problem when its your main navigation menu!
After some research, I found a number of proposed solutions. Lots of them were using deprecated solutions or plain didn’t work. Eventually I went to the dark side and found a solution I could implement. So to add to the noise in the search results, here is how I added an IWebDriver extension method for the Shadow DOM!
How I fixed our tests to work with the Shadow DOM
First, I created a static class in my “Helpers” folder. If you are wondering what my Helpers folder is then check out my blog post The best C# helper classes all tests deserve
return – much like in C#, this will return the output of what follows.
document.querySelector(‘nameOfShadowRoot’).shadowRoot – This locates the start of the ShadowDom. I’ll show an example of how to find this in DevTools later.
Now you understand how the extension method works, lets look at how to use it!
This is an example of one of my Page Objects files. You will see that at the top of the class I declare a string called
shadowRoot. This is just so I don’t have to re-type the name of the root each time I need to use it. Instead I can call the string, this makes it far easier to refactor if it changes.
Then on the next line you can see how the whole thing is used. We declare a new
IWebElement variable to hold our element. Then call our
FindShadowRootElement is now available as an option, much like
FindElement would be. Into this we pass in our
shadowRoot string followed by the
CssSelector for our element. We now have an element in the Shadow DOM that we can interact with.
Something to note. The CssSelector should not contain any quotes, single or double. So where the above example would normally be written as
[data-pendo=”home”] it should be written as
More tips and answers to questions about the Shadow DOM
How do we find the name of the ShadowRoot?
When you inspect the page and see #shadow-root (Open) then the element immediately before it contains the name.
So in this example, the shadowRoot name is
What if #shadow-root has (closed) rather than (open)?
Those elements are inaccessible by design and so cannot be reached.
What if I have a shadowRoot inside another shadowRoot?
This does happen, but isn’t a problem I have had to tackle in our application. It’s simple enough to do should you need to. You would just need to extend my solution to add a second (third, fourth etc)
or a suitable extension method like I created above. You could even have variations of the extension for each layer of your Shadow DOM.
I was demo’ing this piece of work to a team recently. I explained that I had to write an extension method for IWebDriver in order to interact with elements in the Shadow DOM. The product owner on the team thought I was talking about “something from Marvel”🤣 So just for you Claire, here’s a link to a blog article that explains the Shadow DOM far better than I could have – What the heck is the Shadow DOM?
Want to read more from me? Why not check out my tips and tricks blog