An iframe is a web page embedded inside another web page. Payment forms, embedded videos, CAPTCHA widgets, and third-party integrations frequently use iframes to isolate their content from the host page. From Selenium’s perspective, an iframe is a completely separate document — elements inside it are invisible to find_element until you explicitly switch into the iframe’s context. Shadow DOM creates a similar isolation boundary, encapsulating component internals in a way that standard selectors cannot penetrate. Both require context-switching techniques.
Navigating iFrames and Shadow DOM Boundaries
Think of iframes as rooms in a building. Selenium starts in the lobby (main document). To interact with elements in Room 3 (an iframe), you must walk into that room first. When you are done, you walk back to the lobby.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# driver = webdriver.Chrome()
# ── IFRAMES — Three ways to switch ──
# Method 1: Switch by iframe element
def switch_by_element(driver):
iframe = driver.find_element(By.CSS_SELECTOR, "iframe.payment-frame")
driver.switch_to.frame(iframe)
# Now we can interact with elements INSIDE the iframe
card_input = driver.find_element(By.ID, "card-number")
card_input.send_keys("4242424242424242")
# Switch back to the main document
driver.switch_to.default_content()
# Method 2: Switch by name or ID attribute
def switch_by_name(driver):
driver.switch_to.frame("payment-iframe") # name or id attribute
# ... interact with iframe content ...
driver.switch_to.default_content()
# Method 3: Switch by index (0-based)
def switch_by_index(driver):
driver.switch_to.frame(0) # First iframe on the page
# ... interact with iframe content ...
driver.switch_to.default_content()
# ── Nested iframes ──
def handle_nested_iframes(driver):
# Main page → outer iframe → inner iframe
driver.switch_to.frame("outer-frame") # Enter outer
driver.switch_to.frame("inner-frame") # Enter inner
# Interact with content in the innermost iframe
element = driver.find_element(By.ID, "deep-element")
element.click()
# Go back one level (to outer iframe)
driver.switch_to.parent_frame()
# Go back to main document (from any depth)
driver.switch_to.default_content()
# ── Wait for iframe to load before switching ──
def safe_switch_to_iframe(driver, locator, timeout=10):
WebDriverWait(driver, timeout).until(
EC.frame_to_be_available_and_switch_to_it(locator)
)
# Now inside the iframe — find elements normally
# ── SHADOW DOM ──
def access_shadow_dom(driver):
# Find the shadow host element (the custom element with a shadow root)
shadow_host = driver.find_element(By.CSS_SELECTOR, "my-custom-component")
# Access the shadow root using Selenium 4's shadow_root property
shadow_root = shadow_host.shadow_root
# Find elements INSIDE the shadow DOM
inner_button = shadow_root.find_element(By.CSS_SELECTOR, "button.inner-btn")
inner_button.click()
# Note: shadow_root.find_element only supports By.CSS_SELECTOR
# XPath does NOT work inside shadow DOM
# ── Context switching reference ──
CONTEXT_COMMANDS = [
("driver.switch_to.frame(element)", "Switch into an iframe by WebElement"),
("driver.switch_to.frame('name_or_id')", "Switch into an iframe by name/id"),
("driver.switch_to.frame(0)", "Switch into an iframe by index"),
("driver.switch_to.parent_frame()", "Go back one iframe level"),
("driver.switch_to.default_content()", "Return to the main document (top level)"),
("host.shadow_root", "Access shadow DOM (Selenium 4+)"),
]
print("Context Switching Reference")
print("=" * 65)
for cmd, desc in CONTEXT_COMMANDS:
print(f" {cmd}")
print(f" → {desc}")
find_element call fails with NoSuchElementException, the first thing to check is whether the input is inside an iframe. Inspect the page in DevTools and look for <iframe> elements wrapping the payment section.EC.frame_to_be_available_and_switch_to_it(locator) instead of raw switch_to.frame(). The expected condition waits for the iframe to load and be available before switching, preventing NoSuchFrameException on slow-loading iframes. This is especially important for third-party iframes (payment, CAPTCHA, ads) that load asynchronously after the main page.find_element calls operate within that iframe’s document. If you try to find a main-page element while inside an iframe, it will throw NoSuchElementException even though the element exists on the main page. Always call driver.switch_to.default_content() before interacting with main-page elements again. Forgetting to switch back is one of the most common and confusing iframe-related bugs.Common Mistakes
Mistake 1 — Forgetting to switch back to the main document after iframe interaction
❌ Wrong: Switching into the payment iframe, filling the card number, then trying to click the “Submit Order” button on the main page — fails because Selenium is still inside the iframe context.
✅ Correct: After finishing iframe interactions, call driver.switch_to.default_content() before interacting with any element on the main page.
Mistake 2 — Using XPath inside Shadow DOM
❌ Wrong: shadow_root.find_element(By.XPATH, "//button[@class='inner']") — XPath is not supported inside shadow roots.
✅ Correct: shadow_root.find_element(By.CSS_SELECTOR, "button.inner") — Shadow DOM only supports CSS selectors in Selenium 4.