Lumos ShadowDOM 🪄
Automate Shadow DOM elements in Selenium with zero headache.
Getting NoSuchElementException for an element you can clearly see in DevTools? It is probably inside a shadow root. Shadow DOMs are tricky. They hide elements from standard Selenium commands, so driver.find_element() raises NoSuchElementException for elements inside a shadow root (web components, Lit, Salesforce LWC, design systems). lumos-shadowdom solves this by letting you traverse nested open shadow roots automatically using one simple path or even just visible text.
Learn more: Conquering Shadow DOM with Lumos · Project page
🚀 Quick Start
1. Install
pip install lumos-shadowdom
2. Use it in your code
Lumos adds new superpowers to your Selenium driver automatically!
from selenium import webdriver
import lumos # <--- Import this to activate magic!
driver = webdriver.Chrome()
driver.get("https://example.com")
# 🟢 Scenario 1: You know the path (or found it using our tool below)
# Syntax: "host-element > nested-host > target-element"
driver.find_shadow("my-app > settings-panel > button#save").click()
# 🟢 Scenario 2: Find ALL matching elements in shadow DOM
buttons = driver.find_all_shadow("nav-menu > button.action")
for btn in buttons:
print(btn.text)
# 🟢 Scenario 3: You just want to find a button with specific text
# This scans the ENTIRE page (including all shadow roots) for you.
driver.find_shadow_text("Confirm Purchase").click()
🕵️ How to Find the Path? (The Magic Tool)
Don't waste time inspecting elements manually. Use our Discovery Tool to generate the exact path instantly.
- Open your website in Chrome.
- Open DevTools (Press
F12or Right Click -> Inspect). - Go to the Console tab.
- Paste the script below and hit Enter.
- Hold
Altand Click on any element in the page. - The Console will show you the Short Path and Long Path. Copy the "Short Path" and use it in
driver.find_shadow().
Copy this Script:
// Lumos Discovery Tool v2.0
// Paste this into Chrome Console, then Alt + Click any element.
document.addEventListener('click', function(e) {
if (!e.altKey) return;
e.preventDefault();
e.stopImmediatePropagation();
// 1. Get the real target (piercing Shadow DOM)
let composed = e.composedPath();
let target = composed[0];
let fullPath = [];
let shortPath = [];
let currentScope = [];
// 2. Traverse up the tree
for (let i = 0; i < composed.length; i++) {
let node = composed[i];
// Stop at document root
if (node.nodeType !== Node.ELEMENT_NODE && !(node instanceof ShadowRoot)) {
if (currentScope.length > 0) {
fullPath.unshift(currentScope.join(' '));
shortPath.unshift(getShortSelector(currentScope));
}
break;
}
// Handle Shadow Root Boundary
if (node instanceof ShadowRoot) {
fullPath.unshift(currentScope.join(' '));
shortPath.unshift(getShortSelector(currentScope));
currentScope = [];
continue;
}
// Build Selector for current node
let selector = node.tagName.toLowerCase();
if (node.id) {
selector += '#' + node.id;
} else if (node.className && typeof node.className === 'string') {
selector += '.' + node.className.trim().split(/\s+/).join('.');
}
currentScope.unshift(selector);
}
function getShortSelector(scope) {
let short = [];
for (let j = scope.length - 1; j >= 0; j--) {
short.unshift(scope[j]);
if (scope[j].includes('#')) break; // Stop at ID for brevity
}
return short.join(' ');
}
console.log(`%c 🪄 Lumos Path Discovered!`, 'background: #222; color: #bada55; font-size: 14px; padding: 4px;');
console.log(`%c Short Path:`, 'font-weight: bold; color: #007bff;', shortPath.join(' > '));
console.log(`%c Long Path: `, 'color: #666;', fullPath.join(' > '));
}, true);
🤔 Why not Selenium 4's shadow_root?
Selenium 4 can open a shadow root natively, but you need one hop per shadow host:
from selenium.webdriver.common.by import By
save = (
driver.find_element(By.CSS_SELECTOR, "my-app").shadow_root
.find_element(By.CSS_SELECTOR, "settings-panel").shadow_root
.find_element(By.CSS_SELECTOR, "button#save")
)
Selenium 4 element.shadow_root |
driver.execute_script(...) by hand |
Lumos | |
|---|---|---|---|
| Nested roots | One find_element + .shadow_root per host |
Hand-written JS per page | One path: "my-app > settings-panel > button#save" |
| Waiting | None built in; wrap each hop in WebDriverWait yourself |
None built in | Waits up to timeout (default 10 s) for the whole path |
| Find by visible text | No | Write it yourself | driver.find_shadow_text("Confirm Purchase") searches every open shadow root |
| Selenium 3 | Not available | Yes | Yes |
What Lumos does not do: it can't reach closed shadow roots (mode='closed'), and it doesn't cross <iframe> boundaries, so switch with driver.switch_to.frame(...) first. See Limitations & Edge Cases below.
📚 API Reference
driver.find_shadow(path: str, timeout: int = 10) -> WebElement
Finds a single element inside nested shadow DOMs.
- path: A string describing the path to the element. Use
>to separate shadow boundaries.- Example:
user-profile > #settings > input.name - Means: Find
user-profile, enter its shadow root, find#settings, enter its shadow root, findinput.name.
- Example:
- timeout: Seconds to wait for element (default: 10).
- Returns: The matching WebElement.
driver.find_all_shadow(path: str, timeout: int = 10) -> List[WebElement]
Finds ALL elements matching the path inside nested shadow DOMs.
- path: Same syntax as
find_shadow. The last selector matches multiple elements.- Example:
app-root > nav-menu > buttonreturns all buttons in the nav.
- Example:
- Returns: List of matching WebElements (may be empty).
driver.find_shadow_text(text: str, timeout: int = 10) -> WebElement
Recursively searches every shadow root in the DOM for an element containing the specified text.
- text: The text to look for (case-sensitive).
- Returns: The first matching WebElement found.
❓ FAQ
Q: Why do I need this?
A: Standard driver.find_element() cannot "see" inside a Shadow DOM. You usually have to write complex code to get the shadow root, then find the element, then repeat for nested roots. Lumos does this in one line.
Q: Does it work with Selenium 3? A: Yes! It is fully compatible with Selenium 3.141.0 and Selenium 4+.
Q: The Discovery Tool shows a path, but Lumos can't find it. A:
- Ensure the element is visible on the screen.
- If the path is very long, try using the "Short Path" provided by the tool.
- Check if the element is inside an
iframe. Lumos currently supports Shadow DOM, butiframesupport is separate (you must switch to the iframe first).
Q: Which exception do I get when nothing is found?
A: find_shadow() and find_shadow_text() raise lumos.ElementNotFoundError (a LumosException) after the timeout, with the path or text in the message. find_all_shadow() returns an empty list after the timeout instead of raising.
Q: Can I use the CSS child combinator (>) inside one shadow root?
A: In a string path, > always means "enter the next shadow root". Use a descendant selector (a space) inside a root, or use the Lumos class with a list of selectors, where each item can be any CSS selector: Lumos(driver).find_element(["my-app", "div.panel > settings-panel", "button#save"]).
Q: find_shadow_text() returns a different element than I expected.
A: It returns the first element with no child elements whose innerText contains your text (case-sensitive), walking the page in document order and descending into each open shadow root as it meets it. Use a longer, more specific text, or use a path with find_shadow().
⚠️ Limitations & Edge Cases
1. Shadow DOM inside <iframe>
Lumos cannot automatically cross <iframe> boundaries because they are separate documents.
Solution: Switch to the iframe first using Selenium, then use Lumos.
# 1. Switch to the iframe
iframe = driver.find_element(By.ID, "my-frame")
driver.switch_to.frame(iframe)
# 2. Now use Lumos inside the iframe
driver.find_shadow("host-inside-frame > button").click()
2. Closed Shadow Roots
Lumos cannot access Shadow DOMs created with mode='closed'. This is a browser security limitation that no tool can bypass standardly.
Author: Dhiraj Das · Follow @dkkkkd on X License: MIT (see LICENSE)
Metadata
Release files for lumos-shadowdom 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lumos_shadowdom-0.2.1.tar.gz | 14.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lumos_shadowdom-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.5 kB
Release files / lumos_shadowdom-0.2.1.tar.gz
| Download URL | lumos_shadowdom-0.2.1.tar.gz |
|---|---|
| Size | 14.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0780ffdfece15384fad81e26660d73cb4de8459327fe8e8e7bd390208638351e
|
|
BLAKE2b-256 checksum How to use checksums |
c76a18d644ab3b16ac2685338befd08a37b2ff696a24e666e895f7cba5fe9b9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / lumos_shadowdom-0.2.1-py3-none-any.whl
| Download URL | lumos_shadowdom-0.2.1-py3-none-any.whl |
|---|---|
| Size | 9.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cb6e7cea09f27b508b99bb4e42d86da32e3cc63408ab4ed75b7dac908a0fd557
|
|
BLAKE2b-256 checksum How to use checksums |
3bdecdd0fd894c878a1b5c3b188551e4bed00260b2118854f475911822859649
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Security for lumos-shadowdom 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.