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.

Steps to use explicit waits in Selenium:

  1. 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.

  2. 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
  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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

  8. Remove the demo script after copying the pattern into the real test.
    $ rm selenium-explicit-waits-use.py