Shadow DOM Support & reusable component objects
Shadow DOM is one of the key browser features that make up web components. Web components are a really great way to build reusable elements, and are able to scale all the way up to complete web applications. Style encapsulation, the feature that gives shadow DOM it's power, has been a bit of a pain when it comes to E2E or UI testing. Things just got a little easier though, as WebdriverIO v5.5.0 introduced built-in support for shadow DOM via two new commands, shadow$
and shadow$$
. Let's dig into what they're all about.
History
With v0 of the shadow DOM spec, came the /deep/
selector. This special selector made it possible to query inside an element's shadowRoot
. Here we're querying for a button that is inside the my-element
custom element's shadowRoot
:
$('body my-element /deep/ button');
The /deep/ selector was short lived, and is rumored to be replaced some day.
With /deep/ being deprecated and subsequently removed, developers found other ways to get at their shadow elements. The typical approach was to use custom commands in WebdriverIO. These commands used the execute
command to string together querySelector and shadowRoot.querySelector calls in order to find elements. This generally worked such that, instead of a basic string query, queries were put into arrays. Each string in the array represented a shadow boundary. Using these commands looked something like this:
const myButton = browser.shadowDomElement(['body my-element', 'button']);
The downside of both the /deep/
selector and the javascript approach was that in order to find an element, the query always needed to start at the document level. This made tests a little unwieldy and hard to maintain. Code like this was not uncommon:
it('submits the form', ()=> {
const myInput = browser.shadowDomElement(BASE_SELECTOR.concat(['my-deeply-nested-element', 'input']));
const myButton = browser.shadowDomElement(BASE_SELECTOR.concat(['my-deeply-nested-element', 'button']));
myInput.setValue('test');
myButton.click();
});