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 supportsfind_element
,find_elements
, andsession
, unlikeWebElement
, which provides a wide range of interaction methods. - Selector Limitation: Only
CSS_SELECTOR
is allowed withinShadowRoot
, no other selectors (like XPath) work.