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.
Steps to switch into an iframe with Selenium:
- Create selenium-iframe-switch.py with a local parent page and iframe page.
- selenium-iframe-switch.py
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 - Run the script and confirm Selenium reads both the iframe result and the parent document result.
$ 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 for the iframe and switch into it before looking for elements inside it.
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.
- Interact with elements from inside the active iframe.
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.
- Verify the iframe state before leaving it.
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
- Switch back to the parent document before reading parent-page elements.
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.
- Use a located iframe element when the test already stores the frame reference.
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.
- Remove the demo script after moving the iframe pattern into the real test.
$ rm selenium-iframe-switch.py
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.