Search code examples
javascriptpythonhtmlselenium-webdriverfrontend

How can I locate element inside several shadow-roots


I need help locating the Xpath or CSS Selector of the download JSON button on this page.

https://data.ntsb.gov/carol-main-public/query-builder?month=12&year=1962

I copied the selector via Chrome browser but it is not locating the button when I control find.

Xpath -> //*[@id="exportResultsButton"]

CSS -> #exportResultsButton


Solution

  • Your button is placed inside several shadow-roots, to get internal shadow root structure, you should get it's host first and then get shadowRoot property.

    In your case you can write function that drills inside several shadow-roots.

    1. 1st level has tag app-container
    2. 2nd level has id query_builder
    3. 3rd level has id results

    get_shadow_root function gets shadow root from host, using JS executor.

    drill_into_shadow_roots goes inside every provided selector from array, makes it host and gets shadowRoot property, so next iteration would start from new host.

    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    from selenium import webdriver
    
    timeout = 10
    
    def get_shadow_root(element):
        return driver.execute_script('return arguments[0].shadowRoot', element)
    
    def drill_into_shadow_roots(selectors):
        host = None
        _wait = WebDriverWait(driver, timeout)
        for by, selector in selectors:
            host = _wait.until(EC.presence_of_element_located((by, selector)))
            host = get_shadow_root(host)
            _wait = WebDriverWait(host, timeout)
        return host
    
    
    driver.get("https://data.ntsb.gov/carol-main-public/query-builder?month=12&year=1962")
    
    selectors = [(By.TAG_NAME, 'app-container'), (By.ID, 'query_builder'), (By.ID, 'results')]
    results = drill_into_shadow_roots(selectors)
    WebDriverWait(results, timeout).until(EC.visibility_of_element_located((By.ID, 'exportResultsButton'))).click()