Skip to main content

A robust Shadow DOM traverser for Selenium

Project description

Lumos ShadowDOM 🪄

Automate Shadow DOM elements in Selenium with zero headache.

Shadow DOMs are tricky. They hide elements from standard Selenium commands. lumos-shadowdom solves this by letting you traverse nested shadow roots automatically using simple paths or even just text.


🚀 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.

  1. Open your website in Chrome.
  2. Open DevTools (Press F12 or Right Click -> Inspect).
  3. Go to the Console tab.
  4. Paste the script below and hit Enter.
  5. Hold Alt and Click on any element in the page.
  6. 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);

📚 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, find input.name.
  • 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 > button returns all buttons in the nav.
  • 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:

  1. Ensure the element is visible on the screen.
  2. If the path is very long, try using the "Short Path" provided by the tool.
  3. Check if the element is inside an iframe. Lumos currently supports Shadow DOM, but iframe support is separate (you must switch to the iframe first).

⚠️ 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 License: MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lumos_shadowdom-0.2.0.tar.gz (12.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lumos_shadowdom-0.2.0-py3-none-any.whl (8.5 kB view details)

Uploaded Python 3

File details

Details for the file lumos_shadowdom-0.2.0.tar.gz.

File metadata

  • Download URL: lumos_shadowdom-0.2.0.tar.gz
  • Upload date:
  • Size: 12.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for lumos_shadowdom-0.2.0.tar.gz
Algorithm Hash digest
SHA256 6eb424032019133c21b5315c8652c3b8437a025abca04128a06e553557552187
MD5 4eaf8cfd3cc2c03bf87e4186cba044d4
BLAKE2b-256 1fee34e2229b865290547ecc16bc013044cd66686a1bfd366e6ae9859bc6186a

See more details on using hashes here.

File details

Details for the file lumos_shadowdom-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for lumos_shadowdom-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 000f1328f1c0432236f617ba0edfb61e4ebf7784b545251027a20d19f78d5956
MD5 92936ae781f821ba9468a94d9ffe5b66
BLAKE2b-256 42ab4f6baacf85cd715fbc70cf41f1a9059f5c1ac53259ff747ca3ac65f5635e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page