JavaScript execution and CDP

Use execute_script for what WebDriver cannot reach, and the Chrome DevTools Protocol for network and console data.

execute_script

driver.execute_script("return document.title")
driver.execute_script("return window.location.href")

# pass elements as arguments[0]
button = driver.find_element(By.ID, "submit")
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", button)
driver.execute_script("arguments[0].click();", button)

# set a value the normal way will not accept
driver.execute_script("arguments[0].value = arguments[1];", field, "1000")

# read the page as a whole
html = driver.execute_script("return document.documentElement.outerHTML")
  • Arguments are converted to JavaScript automatically; the return value is converted back, but only for JSON-like types.
  • An error thrown inside the script surfaces as JavascriptException with the browser's message.
  • Keep scripts short. A long script is application logic living in a test, which is where test suites go to die.
  • execute_script runs synchronously; use execute_async_script when the result arrives on a callback.
# async: the script must call the callback exactly once
delay = driver.execute_async_script("""
    const done = arguments[arguments.length - 1];
    setTimeout(() => done(performance.now()), 500);
""")
print("resolved after", round(delay))
⚠️
A script that never calls its callback makes execute_async_script hang until the command timeout. Always route every exit path - success, failure and exception - through the same callback, and never await a promise that can reject without a handler.

Shadow DOM

# WebDriver's own locators do not cross a shadow boundary
host = driver.find_element(By.CSS_SELECTOR, "checkout-widget")

inner = driver.execute_script(
    "return arguments[0].shadowRoot.querySelector('button.primary');",
    host,
)

# Selenium 4 supports shadow roots directly on the element
shadow = host.shadow_root
button = shadow.find_element(By.CSS_SELECTOR, "button.primary")
button.click()
  • A closed shadow root cannot be reached from script at all; you need the component to expose a test hook.
  • Locate the host element first, then descend - the host is a normal element and selectable by its tag name or an id.
  • Nested shadow roots mean one hop per component. Write a small helper rather than inlining the script everywhere.
  • shadow_root is the readable option and keeps the rest of your test using ordinary locators.

Chrome DevTools Protocol

from selenium.webdriver.chrome.options import Options

options = Options()
options.set_capability("goog:loggingPrefs", {"browser": "ALL", "performance": "ALL"})

# console messages and page errors
for entry in driver.get_log("browser"):
    print(entry["level"], entry["message"])

# every network request, via CDP
driver.execute_cdp_cmd("Network.enable", {})
driver.find_element(By.ID, "reload").click()
requests = driver.execute_cdp_cmd("Network.getAllCookies", {})
# send custom headers on every request
driver.execute_cdp_cmd("Network.enable", {})
driver.execute_cdp_cmd("Network.setExtraHTTPHeaders", {
    "headers": {"X-Test-Run": "ci-1234"}
})

# block third-party analytics so it cannot slow the suite down
driver.execute_cdp_cmd("Network.setBlockedURLs", {
    "urls": ["*google-analytics.com*", "*segment.io*"]
})
CapabilityDomainTypical use
goog:loggingPrefsBrowser logCollect console errors per test
Network.enableNetworkObserve and shape requests
Network.setExtraHTTPHeadersNetworkTag test traffic
Network.setBlockedURLsNetworkRemove unreliable third parties
Performance.enablePerformanceCapture raw metrics

CDP commands are Chrome-specific. Firefox has no equivalent through Selenium, so anything built on them belongs in an optional path or in a suite that only runs against Chromium.

FAQ

When is execute_script the right tool?
When the interaction is genuinely outside WebDriver's model: reading a computed style, dispatching a native DOM event the test must simulate, walking into a shadow root, or scrolling a container. If plain locators and waits can express the step, use them instead - they fail with clearer errors.
Do I need a proxy library to inspect network traffic?
Not for Chromium. CDP can subscribe to request and response events and read response bodies directly. A proxy is still reasonable when you need the same behaviour across browsers or want to modify responses before the page sees them.

Advanced user actions with ActionChains Cookies, storage and session reuse

Last refreshed 2026-09-18.