logo logo

Shadow DOM Elements and Using TestProject’s OpenSDK {Part 2}

Shadow DOM Part 2

At the end of our 1st Part of this Shadow DOM series, we were left with so many questions in our mind. So let’s catch-up one-by-one and solve that conundrum. I hope you will 💗 it!

Q: Can a QA Automation engineer interact with custom elements WITHOUT appending Shadow DOM?
So let’s try legacy selectors {CSS & XPath} as well as document.querySelector method to find the elements when Shadow DOM is NOT appended.

Custom Element Code Snippet WITHOUT Shadow DOM >

<script>
  	class MyElement extends HTMLElement {
    	connectedCallback() {
      	const button = document.createElement("button");
      	button.onclick = () => alert("Button clicked");
      	button.innerText = "Button in Custom Element";
      	this.append(button);
    		}
  	}
  	customElements.define("my-element", MyElement);
</script>

<div><my-element></my-element></div>

Custom Element Code rendered on Browser >

Custom Element Code rendered on Browser

Let’s try to find Custom Element through CSS, XPath, and document interface >

Custom Element through CSS, XPath, and document interface

Aha! Custom elements are visible on the console after using all the above findElement methods one by one. Even ALL LEGACY SELECTORS ARE WORKING! 🎉

Q: Can a QA Automation engineer interact with custom elements AFTER appending Shadow DOM?
In our first part, we have discussed that shadowRoot interface is the entry point to reach out to Shadow DOM elements i.e. by using shadowRoot.querySelector method. So let’s try legacy selectors {CSS & XPath}, document.querySelector method and also shadowRoot.querySelector method to find the custom elements when Shadow DOM is appended with OPEN mode.

Custom Element Code Snippet WITH appended Shadow DOM {OPEN Mode} >

<script>
   	class MyElement extends HTMLElement {
     	connectedCallback() {
       	const shadow = this.attachShadow({ mode: "open" });
       	const button = document.createElement("button");
       	button.onclick = () => alert("Button clicked");
       	button.innerText = "Shadow DOM Button";
       	shadow.append(button);
     	      }
   	}
   	customElements.define("my-element", MyElement);
</script>
<div><my-element></my-element></div>

Custom Element Code appended with Shadow DOM rendered on Browser >

Custom Element Code appended with Shadow DOM rendered on Browser

Let’s try to find Custom Element appended with Shadow DOM through CSS, XPath,  document interface, and shadowRoot interface >

Custom Element appended with Shadow DOM through CSS, XPath,  document interface, and shadowRoot interface

Wow! EXCEPT FOR THE SHADOWROOT INTERFACE, NOTHING WORKS. That’s why we call the shadowRoot interface as an entry point to the Shadow DOM tree.

Custom Element Code Snippet WITH appended Shadow DOM {CLOSED Mode} >

<script>
   	class MyElement extends HTMLElement {
     	connectedCallback() {
       	const shadow = this.attachShadow({ mode: "open" });
       	const button = document.createElement("button");
       	button.onclick = () => alert("Button clicked");
       	button.innerText = "Shadow DOM Button";
       	shadow.append(button);
     	        }
   	}
   	customElements.define("my-element", MyElement);
</script>
<div><my-element></my-element></div>

Custom Element Code appended with Shadow DOM rendered on Browser >

Custom Element Code appended with Shadow DOM rendered on Browser

Let’s try to find Custom Element appended with Shadow DOM through CSS, XPath,  document interface, and shadowRoot interface >

Custom Element appended with Shadow DOM through CSS, XPath,  document interface, and shadowRoot interface

Oops! NOTHING WORKS even the shadowRoot interface failed.

Q: Do regular selectors {Xpath, CSS} work with Shadow DOM elements?
As we have seen above, legacy selectors including document interface do not work with Shadow DOM elements.

Q: What is the role of the Open/Closed mode of Shadow Root, while finding the elements?
The Shadow Root has two different modes – Open mode allows the shadow root to be accessible from JavaScript outside the shadow root but Closed mode does not. As we have seen above, shadowRoot interface is the only entry point to the Shadow DOM tree when the mode is OPEN. In the case of CLOSED mode, we can not access any Shadow DOM tree elements. In simple terms we can say – we can not automate CLOSED mode Shadow DOM tree elements.

  • If the CLOSED mode Shadow DOM tree is business-critical to automate, then we can ask our developers to keep the mode OPEN on the test environment sandbox, so that we can automate/run it. Developers still can keep CLOSED mode on the production environment.
                                                                                      OR
  • The developers can make OPEN/CLOSED mode as configurable for automation engineers.

Q: How to interact with multi-level Shadow Root elements?
To see the multilevel Shadow DOM tree elements on your machine, get the source code from my GitHub repo, and execute locally. Whenever you run the code locally, it will open up a web application {implemented with Google’s Polymer library} on the browser and then you can see a multi-level Shadow DOM tree through the browser console.  

multi-level Shadow Root elements

Let’s try to find multi-level Custom Elements appended with Shadow DOM through shadowRoot interface >

multi-level Custom Elements appended with Shadow DOM through shadowRoot interface

Here we have used the shadowRoot interface twice – one inside another and finally we succeeded to find multi-level elements.

Q: Does TestProject OpenSDK support Shadow DOM tree elements?
Undoubtedly YES! To interact with Shadow DOM tree elements, you have to set up TestProject’s OpenSDK on your local machine. Then create a Maven Java project and add the following dependency {Here, we are using the Java binding}:

<dependency>
  <groupId>io.testproject</groupId>
  <artifactId>java-sdk</artifactId>
  <version>0.64.0-RELEASE</version>
</dependency>

📍 The OpenSDK also provides bindings with Python and C#.
📍 More on the TestProject’s SDK and OpenSDK can be found here: TestProject SDK vs Open SDK?

After setting up Maven dependency, install & start TestProject Agent (it’s completely free, simply sign up here 😉) on your local machine and also get a token from your TestProject’s account. Now all prerequisites are done and you are ready to run the following automation script on your maven project by using TestProject’s OpenSDK.

Multi-level Shadow DOM tree automation code with TestProject’s OpenSDK >

import io.testproject.sdk.drivers.web.ChromeDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeOptions;

public class ShadowElementTests {
  static ChromeDriver driver;
    public static void main(final String[] args) throws Exception {
      driver = new ChromeDriver("YourTokenNumber", new ChromeOptions());        
      driver.navigate().to("http://localhost:3000/");
          
      WebElement todayShadow = expandRootElement(driver.findElement(By.tagName("todo-app")));
      WebElement todayAddItem = expandRootElement(todayShadow.findElement(By.tagName("add-item")));
      todayAddItem.findElement(By.tagName("textarea")).sendKeys("Add OpenSDK");
      todayAddItem.findElement(By.tagName("textarea")).sendKeys(Keys.ENTER);
      todayAddItem.findElement(By.tagName("textarea")).clear();
      todayAddItem.findElement(By.tagName("textarea")).sendKeys("Add TestProject SDK");
      todayAddItem.findElement(By.tagName("textarea")).sendKeys(Keys.ENTER);

      driver.quit();
    }
    
    public static WebElement expandRootElement(WebElement element) {
      WebElement ele = (WebElement) ((JavascriptExecutor) driver)
          .executeScript("return arguments[0].shadowRoot",element);
      return ele;
    }
}

After execution, we get automatic test reports directly on the TestProject Automation Portal:

Shadow DOM with TestProject - Execution reports

The above code snippet only contains ChromeDriver, but it supports all W3C webdrivers – FirefoxDriver, EdgeDriver, and SafariDriver. Try yourself at your end ✌️

FirefoxDriver >

FirefoxDriver driver = new FirefoxDriver("YourTokenNumber", new FirefoxOptions());

EdgeDriver >

EdgeDriver driver = new EdgeDriver("YourTokenNumber", new EdgeOptions());

SafariDriver >

SafariDriver driver = new EdgeDriver("YourTokenNumber", new SafariOptions());

And don’t forget to add your TestProject Token in your code snippet, otherwise, it will not interact with TestProject Agent and nothing will happen.

📍The application that we have above for automation, can be found at – GitHub repo. This application is implemented by using Google’s Polymer library with NodeJS. So install those first before running this application locally.

Key Takeaways

  • Understood how QA Automation folks can interact with custom elements WITH / WITHOUT appending Shadow DOM.
  • We got familiar with the role of the Open/Closed mode of Shadow Root, while finding the elements.
  • Got to know the limitations of regular selectors {Xpath, CSS} with Shadow DOM.
  • Understood how to interact with multi-level Shadow Root elements.
  • Clear thought on how TestProject’s OpenSDK supports Shadow DOM elements.

Now we are pretty familiar with the Shadow DOM tree and its interaction with TestProject’s OpenSDK. But still, we have a few open questions:

  • Does TestProject record/playback support Shadow DOM elements?
  • Few more interesting aspects of the Shadow DOM tree?

Stay tuned and wait for the last part, very soon it will be on your plate 😉👍😊👍

About the author

Virender Singh

@VirenderSingh has a test automation mindset that contains the Developer’s logic and Tester’s domain expertise. He is always aggressive to share his knowledge through different mediums say Selenium global conference, organizational meetups, writing blogs for any problem statement, and also through organizational internal technical sessions. Frankly, he got into the testing field not really by choice. But once he started, there is no fall-back. Even after 14 years in this field, there is still a lot to learn and contribute to. And this learning curve is expanding day by day, that seems endless…..!

Approachable anytime at – @Linkedin   @Twitter   @Github

Comments

10 2 comments
  • Virender Singh December 26, 2020, 9:09 am

    Few test engineers came across #shadow-root (user-agent) while automating their scenarios, and asked me personally “How to get elements in user-agent shadow root with JavaScript?”. So below, mentioning the solution:

    #shadow-root (user-agent) are browser vendor’s native implementation, so they are not documented and will never be accessible. Only open Shadow DOM elements are accessible through JavaScript.

    • Virender Singh December 26, 2020, 11:25 am

      You cannot access a Shadow DOM created by the browser to display control, which is called a #shadow-root (user-agent) in the Dev Tools. You can only access open custom Shadow DOM (the ones that you create yourself), with the { mode: ‘open’ } option.

      Example: element.attachShadow( { mode: ‘open’ } )

Leave a Reply

FacebookLinkedInTwitterEmail