Selenium tests often fail when a page updates after WebDriver has already looked for an element. An explicit wait keeps the test on the browser state it needs, such as visible text or a clickable button, instead of guessing with a fixed sleep.
In Python, WebDriverWait polls the browser until an expected condition returns a matching value or Selenium raises TimeoutException. The returned value is often the fresh WebElement that matched the condition, so the next action can use the same element without a second lookup.
Match each wait to the next action. Use visibility_of_element_located() before reading text, element_to_be_clickable() before a click, and a text condition when a status message proves completion. A high implicit wait can make explicit-wait polling harder to reason about, so keep implicit waits disabled unless a project standard requires otherwise.
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 selenium-explicit-waits-use.py with a delayed local page and explicit wait conditions.
- selenium-explicit-waits-use.py
from pathlib import Path from tempfile import TemporaryDirectory 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 HTML = """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Selenium explicit wait demo</title> <script> window.addEventListener("DOMContentLoaded", () => { const status = document.querySelector("#status"); const button = document.querySelector("#submit-order"); setTimeout(() => { status.textContent = "Ready to submit"; button.disabled = false; }, 350); button.addEventListener("click", () => { status.textContent = "Submitted"; }); }); </script> </head> <body> <p id="status">Loading form</p> <button id="submit-order" disabled>Submit order</button> </body> </html> """ options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1280,720") 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: page = Path(tmpdir) / "explicit-wait-demo.html" page.write_text(HTML, encoding="utf-8") driver = webdriver.Chrome(service=service, options=options) try: driver.get(page.as_uri()) wait = WebDriverWait(driver, 10, poll_frequency=0.2) wait.until( EC.text_to_be_present_in_element((By.ID, "status"), "Ready to submit"), "status text did not show the form was ready", ) button = wait.until( EC.element_to_be_clickable((By.ID, "submit-order")), "submit button did not become clickable", ) button.click() wait.until( EC.text_to_be_present_in_element((By.ID, "status"), "Submitted"), "status text did not show submission", ) print(f"title: {driver.title}") print(f"button_text: {button.text}") print(f"status: {driver.find_element(By.ID, 'status').text}") finally: driver.quit()
The page starts with disabled controls, changes the status text after JavaScript runs, then enables the button. The waits prove each state before the script moves on.
- Run the script to confirm the waits protect both the button action and the final status check.
$ python3 selenium-explicit-waits-use.py title: Selenium explicit wait demo button_text: Submit order status: Submitted
- Create a wait object near the browser action that needs it.
wait = WebDriverWait(driver, 10, poll_frequency=0.5)
The timeout is the upper bound, not a fixed delay. WebDriverWait continues as soon as the condition returns a matching value.
- Wait for the element state required by the next action.
submit_button = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-testid='submit-order']")), "submit button did not become clickable", ) submit_button.click()
element_to_be_clickable() checks that the element is visible and enabled. Use visibility_of_element_located() when the next action only needs visible text.
- Wait for the page result that proves the action completed.
wait.until( EC.text_to_be_present_in_element( (By.CSS_SELECTOR, "[data-testid='order-status']"), "Submitted", ), "order status did not change to Submitted", )
A successful click() call only proves the browser received the click command. The follow-up condition proves the application reached the expected state.
- Wait for loading overlays to disappear before checking covered controls.
wait.until( EC.invisibility_of_element_located((By.CSS_SELECTOR, "[data-testid='loading']")), "loading overlay did not disappear", )
invisibility_of_element_located() returns true when the element is hidden or no longer present, which fits temporary spinners and overlays.
- Disable implicit waits when explicit waits are the project standard.
driver.implicitly_wait(0)
Implicit waits change element lookup polling for the whole driver session. Keeping them off makes each explicit wait's timeout and locator message easier to interpret.
Related: How to troubleshoot Selenium timeout errors - Remove the demo script after copying the pattern into the real test.
$ rm selenium-explicit-waits-use.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.