Browser automation can finish navigation before a JavaScript-driven interface exposes the next usable control. Selenium explicit waits synchronize each test action with the application state that action requires, avoiding fixed sleeps that are either too short or longer than necessary.
Python's WebDriverWait polls a condition until it returns a value that is not false or raises TimeoutException at the deadline. Locator-based expected conditions such as element_to_be_clickable() return the matching WebElement, so the next action can use the element found by the successful poll.
The local page begins with a disabled button and changes its status only after JavaScript enables the control. A second status change after the click separates button readiness from application success; keep implicit waits at their default zero unless a project requires them because Selenium warns that mixing implicit and explicit waits can produce unpredictable timeout lengths.
Related: How to click a button with Selenium
Related: How to run a headless browser in Selenium
Steps to use explicit waits in Selenium:
- Create explicit-wait-demo.html with a button that becomes enabled after a short delay.
- explicit-wait-demo.html
<!doctype html> <html lang="en"> <meta charset="utf-8"> <title>Selenium explicit wait demo</title> <button id="submit-order" disabled>Submit order</button> <p id="status">Loading order</p> <script> const button = document.querySelector("#submit-order"); const status = document.querySelector("#status"); setTimeout(() => { button.disabled = false; status.textContent = "Ready to submit"; }, 700); button.addEventListener("click", () => { status.textContent = "Submitted"; }); </script> </html>
- Start selenium-explicit-waits-use.py with the wait imports and local page URL.
- selenium-explicit-waits-use.py
from pathlib import Path 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 page_url = (Path(__file__).parent / "explicit-wait-demo.html").resolve().as_uri()
- Extend selenium-explicit-waits-use.py after page_url with the headless Chrome session.
options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1280,720") browser_path = shutil.which("google-chrome") or shutil.which("chromium") if browser_path: options.binary_location = browser_path driver_path = shutil.which("chromedriver") service = Service(driver_path) if driver_path else Service() driver = webdriver.Chrome(service=service, options=options)
An installed ChromeDriver is used when it is on PATH. Otherwise, the empty Service() lets Selenium Manager resolve a compatible driver on supported systems.
Related: How to configure ChromeDriver for Selenium - Append the ready-state waits and button click after driver creation.
try: driver.get(page_url) wait = WebDriverWait(driver, 5) wait.until( EC.text_to_be_present_in_element((By.ID, "status"), "Ready to submit"), "order status did not become ready", ) button = wait.until( EC.element_to_be_clickable((By.ID, "submit-order")), "submit button did not become clickable", ) ready_status = driver.find_element(By.ID, "status").text button_text = button.text button.click()
Custom messages make a timeout identify the state that never appeared instead of reporting an empty wait failure.
Related: How to troubleshoot Selenium timeout errors - Complete selenium-explicit-waits-use.py with the result wait and browser cleanup.
- selenium-explicit-waits-use.py
from pathlib import Path 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 page_url = (Path(__file__).parent / "explicit-wait-demo.html").resolve().as_uri() options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1280,720") browser_path = shutil.which("google-chrome") or shutil.which("chromium") if browser_path: options.binary_location = browser_path driver_path = shutil.which("chromedriver") service = Service(driver_path) if driver_path else Service() driver = webdriver.Chrome(service=service, options=options) try: driver.get(page_url) wait = WebDriverWait(driver, 5) wait.until( EC.text_to_be_present_in_element((By.ID, "status"), "Ready to submit"), "order status did not become ready", ) button = wait.until( EC.element_to_be_clickable((By.ID, "submit-order")), "submit button did not become clickable", ) ready_status = driver.find_element(By.ID, "status").text button_text = button.text button.click() wait.until( EC.text_to_be_present_in_element((By.ID, "status"), "Submitted"), "order status did not change to Submitted", ) result_status = driver.find_element(By.ID, "status").text print(f"ready: {ready_status}") print(f"button: {button_text}") print(f"result: {result_status}") finally: driver.quit()
The final wait observes the application response rather than treating a returned click() call as proof that submission finished.
- Run selenium-explicit-waits-use.py to confirm both the ready state and submitted result.
$ python3 selenium-explicit-waits-use.py ready: Ready to submit button: Submit order result: Submitted
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.