An iframe puts a second document inside the page that Selenium is currently driving. A test that looks for a button or input before changing frame context can fail with NoSuchElementException or hit a similarly named element in the parent page, so the frame switch needs to happen immediately before the frame-only action.
In Python, driver.switch_to.frame() changes WebDriver element searches to the selected <iframe> document. A stable locator for the iframe is safer than frame order, and frame_to_be_available_and_switch_to_it() can wait for JavaScript to insert the frame before switching into it.
After working inside the iframe, switch back to default_content() before reading parent-page status, clicking a parent control, or switching into another frame. Keep element lookups scoped to the active context, and repeat the frame switch after navigation or a page redraw that replaces the iframe node.
from pathlib import Path from tempfile import TemporaryDirectory import os import shutil from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait FRAME_HTML = """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Checkout frame</title> <script> window.addEventListener("DOMContentLoaded", () => { const email = document.querySelector("#customer-email"); const button = document.querySelector("#confirm-payment"); const status = document.querySelector("#frame-status"); button.addEventListener("click", () => { status.textContent = `Ready for ${email.value}`; }); }); </script> </head> <body> <label for="customer-email">Customer email</label> <input id="customer-email" name="customer-email" type="email"> <button id="confirm-payment" type="button">Confirm payment</button> <p id="frame-status">Waiting for iframe action</p> </body> </html> """ PARENT_HTML = """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Selenium iframe switch demo</title> </head> <body> <h1>Checkout wrapper</h1> <iframe id="checkout-frame" name="checkout-frame" title="Checkout form" src="{frame_uri}"> </iframe> <p id="parent-status">Parent document restored</p> </body> </html> """ options = Options() options.add_argument("--headless=new") options.add_argument("--disable-dev-shm-usage") options.add_argument("--window-size=1280,720") if hasattr(os, "geteuid") and os.geteuid() == 0: options.add_argument("--no-sandbox") for browser_name in ("google-chrome", "chromium", "chromium-browser"): browser_path = shutil.which(browser_name) if browser_path: options.binary_location = browser_path break driver_path = shutil.which("chromedriver") service = Service(driver_path) if driver_path else Service() with TemporaryDirectory() as tmpdir: frame_page = Path(tmpdir) / "checkout-frame.html" parent_page = Path(tmpdir) / "checkout.html" frame_page.write_text(FRAME_HTML, encoding="utf-8") parent_page.write_text( PARENT_HTML.format(frame_uri=frame_page.as_uri()), encoding="utf-8", ) driver = webdriver.Chrome(service=service, options=options) try: driver.get(parent_page.as_uri()) wait = WebDriverWait(driver, 10) wait.until( EC.frame_to_be_available_and_switch_to_it( (By.CSS_SELECTOR, "iframe#checkout-frame") ) ) wait.until(EC.visibility_of_element_located((By.ID, "customer-email"))).send_keys( "qa@example.net" ) driver.find_element(By.ID, "confirm-payment").click() frame_status = wait.until( EC.text_to_be_present_in_element( (By.ID, "frame-status"), "Ready for qa@example.net", ) ) status_text = driver.find_element(By.ID, "frame-status").text driver.switch_to.default_content() parent_status = driver.find_element(By.ID, "parent-status").text print(f"title: {driver.title}") print(f"frame_switched: {frame_status}") print(f"frame_status: {status_text}") print(f"parent_status: {parent_status}") finally: driver.quit()
The script expects Python Selenium, Chrome or Chromium, and a matching chromedriver.
Related: How to install Selenium WebDriver for Python
Related: How to configure ChromeDriver for Selenium
$ python3 selenium-iframe-switch.py title: Selenium iframe switch demo frame_switched: True frame_status: Ready for qa@example.net parent_status: Parent document restored
wait.until( EC.frame_to_be_available_and_switch_to_it( (By.CSS_SELECTOR, "iframe#checkout-frame") ) )
The expected condition returns after WebDriver has switched context. Element searches after this line run inside checkout-frame.
wait.until(EC.visibility_of_element_located((By.ID, "customer-email"))).send_keys( "qa@example.net" ) driver.find_element(By.ID, "confirm-payment").click()
Use selectors that belong to the iframe document after switching. Selectors from the parent document are no longer visible until the context is restored.
wait.until( EC.text_to_be_present_in_element( (By.ID, "frame-status"), "Ready for qa@example.net", ) ) status_text = driver.find_element(By.ID, "frame-status").text
driver.switch_to.default_content() parent_status = driver.find_element(By.ID, "parent-status").text
Forgetting default_content() keeps later locators inside the iframe. Parent buttons, banners, and status elements can look missing even though they are present in the top-level document.
iframe = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, "iframe#checkout-frame")) ) driver.switch_to.frame(iframe)
Switching by WebElement avoids relying on iframe index order. Repeat the lookup after navigation or a redraw that replaces the <iframe> node.
$ rm selenium-iframe-switch.py