Part 2: ServiceNow Selenium – Shadow DOMs

In our last post, we used execute_script() to navigate Shadow DOMs in ServiceNow, which required writing JavaScript to traverse into shadow roots. This method worked but was complex and hard to maintain.

A Better Way: Directly Accessing Shadow DOMs with shadow_root

Selenium’s Python shadow_root function now provides a simpler alternative. Here’s how to approach this in a step-by-step guide.

Step 1: Old Approach with JavaScript

Let’s first recall the old approach. We needed to execute JavaScript to access the shadow DOM, as shown:

 
## To click a shadow element
driver.execute_script("return <js_path>.click()")

This method allowed interaction but added complexity due to manually executing JavaScript to achieve the interactions we want.

Step 2: The New Approach with shadow_root

With the recent improvements in Selenium, we can now directly access shadow DOMs without executing JavaScript. Here’s how it works:

 
# 1. Locate the Shadow Host
# First, identify the shadow host element using its CSS selector:
shadow_host = driver.find_element(By.CSS_SELECTOR, "shadow-host-selector") 

# 2. Access the Shadow Root
shadow_root = shadow_host.shadow_root 

# 3. Find the Element within the Shadow DOM
# With the shadow root exposed,
# you can now interact with elements inside it using regular CSS selectors:
element_in_shadow = shadow_root.find_element(By.CSS_SELECTOR, "inner-element-selector") 

No need for complex JavaScript paths—just clean, readable code.

Note: When accessing elements within the shadow DOM using shadow_root, only CSS_SELECTOR can be used to find elements. Other selectors like XPath are not supported.

Example: Interacting with ServiceNow All Tab

 
import time

from selenium.webdriver.common.by import By

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

from servicenow_selenium.servicenow_selenium import ServiceNowSelenium

# Setup your ServiceNow instance credentials

url = "https://seamlessmigrationllcdemo2.service-now.com/"

username = "midserver_selenium"

password = "testPassword2112$"

# Initialize ServiceNow Selenium

snTest = ServiceNowSelenium(url, username, password)

driver = snTest.driver

# Log in

try:

    snTest.login()

    time.sleep(5)  # Let the login process complete

except Exception as e:

    print(f"Login failed with error: {e}")

    driver.quit()

    exit()

# Traverse shadow hosts & shadow roots until "All" is reached

try:

    # Wait for the element to be present in the DOM

    shadow_host1 = WebDriverWait(driver, 10).until(

        EC.presence_of_element_located((By.XPATH, "//*[starts-with(name(), 'macroponent')]"))

    )

    shadow_root1 = shadow_host1.shadow_root

    # Continue accessing the subsequent shadow hosts and roots

    shadow_host2 = shadow_root1.find_element(By.CSS_SELECTOR, 'sn-polaris-layout')

    shadow_root2 = shadow_host2.shadow_root

    shadow_host3 = shadow_root2.find_element(By.CSS_SELECTOR, 'sn-polaris-header')

    shadow_root3 = shadow_host3.shadow_root

    # Find the 'All' menu button and click it

    all_menu_button = shadow_root3.find_element(By.CSS_SELECTOR, 'div[aria-label="All"]')

    all_menu_button.click()

    time.sleep(15) ## To view the all menu clicked

except Exception as e:

    print(f"An error occurred: {e}")

# Logout and quit

snTest.logout_endpoint()

driver.quit()

In this example, we started from the first shadow host, “macroponent”, and traversed our way down the Shadow DOM until we reached the desired element. From there, then end result is us able to click the “All” tab in the Servicenow navbar menu.

Difference Between ShadowRoot and WebElement

When accessing a shadow DOM with shadow_root, it’s important to note that the object returned is a ShadowRoot, which is distinct from the traditional WebElement object. Within a ShadowRoot, you can only use the following methods:

  • find_element
  • find_elements
  • session

Only CSS_SELECTOR is allowed as the selector inside a ShadowRoot, and no other types of selectors (like XPath) are supported.

The elements retrieved from within the ShadowRoot are regular WebElement objects, allowing you to interact with them using familiar methods such as click(), get_attribute(), and more.

Key Takeaways

  • No More JavaScript Execution: Selenium’s shadow_root simplifies interaction with Shadow DOM elements by allowing direct access through CSS selectors.
  • Cleaner Code: This approach results in more readable, maintainable test scripts.
  • More Efficient Testing: By removing the need for JavaScript execution, test scripts run faster and are easier to debug.
  • Limited Functions in ShadowRoot: ShadowRoot only supports find_element, find_elements, and session, unlike WebElement, which provides a wide range of interaction methods.
  • Selector Limitation: Only CSS_SELECTOR is allowed within ShadowRoot, no other selectors (like XPath) work.