How to wait for page load in Selenium

Selenium tests can reach a page while the browser document has loaded but the application has not finished drawing the state the next step needs. A page-load wait separates the browser lifecycle from the application-ready marker, so a navigation does not get treated as finished only because the URL changed.

With the default normal page load strategy, WebDriver waits for a URL navigation to reach document.readyState value complete before driver.get() returns. That boundary covers the document load event and resources declared by the page, but JavaScript can still add controls, rows, route content, or status text after the browser reports complete.

Use a page-load timeout to bound navigation, then use WebDriverWait for both the browser load state and a page-specific marker such as a visible heading, enabled button, loaded table row, or route status. When a project uses eager or none page load strategy, or when navigation is triggered by a click or form submit, the explicit readiness check becomes the part that protects the next action.

Steps to wait for page load in Selenium:

  1. Create selenium-page-load-wait.py with a local page and explicit load-state checks.
    selenium-page-load-wait.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 page load wait demo</title>
        <script>
          window.addEventListener("load", () => {
            const status = document.querySelector("#status");
            status.textContent = "Browser load event fired";
     
            setTimeout(() => {
              const marker = document.createElement("p");
              marker.id = "app-ready";
              marker.textContent = "Dashboard ready";
              document.body.appendChild(marker);
            }, 350);
          });
        </script>
      </head>
      <body>
        <h1>Orders</h1>
        <p id="status">Loading assets</p>
      </body>
    </html>
    """
     
     
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--window-size=1280,720")
    options.page_load_strategy = "eager"
     
    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) / "page-load-wait.html"
        page.write_text(HTML, encoding="utf-8")
     
        driver = webdriver.Chrome(service=service, options=options)
        try:
            driver.set_page_load_timeout(10)
            driver.get(page.as_uri())
     
            wait = WebDriverWait(driver, 10, poll_frequency=0.2)
            wait.until(
                lambda browser: browser.execute_script("return document.readyState")
                == "complete",
                "document.readyState did not reach complete",
            )
            marker = wait.until(
                EC.visibility_of_element_located((By.ID, "app-ready")),
                "application ready marker did not appear",
            )
     
            print(f"title: {driver.title}")
            print(f"ready_state: {driver.execute_script('return document.readyState')}")
            print(f"status: {driver.find_element(By.ID, 'status').text}")
            print(f"marker: {marker.text}")
        finally:
            driver.quit()

    The demo uses eager page load strategy so navigation can return before every resource is complete. The waits cover both browser load completion and the delayed application marker.

  2. Run the script to confirm the wait reaches the browser load event and the delayed marker.
    $ python3 selenium-page-load-wait.py
    title: Selenium page load wait demo
    ready_state: complete
    status: Browser load event fired
    marker: Dashboard ready
  3. Keep the page load strategy explicit when the project changes the default.
    options.page_load_strategy = "normal"

    normal waits for complete and is the default. Use eager only when late-loading assets are not needed, and use none only when every required readiness check is handled by explicit waits.

  4. Disable implicit waits if explicit waits own page readiness in the test suite.
    driver.implicitly_wait(0)

    Implicit waits apply to element lookup across the session and can make explicit wait timing harder to diagnose.
    Related: How to troubleshoot Selenium timeout errors

  5. Set a page-load timeout before opening the real URL.
    driver.set_page_load_timeout(20)

    The pageLoad timeout limits how long navigation may block. It does not replace an explicit wait for application content that appears after the browser load state.

  6. Open the target page.
    driver.get("https://app.example.com/orders")
  7. Wait until the browser reports a complete document state.
    wait = WebDriverWait(driver, 10, poll_frequency=0.2)
    wait.until(
        lambda browser: browser.execute_script("return document.readyState") == "complete",
        "document.readyState did not reach complete",
    )

    With normal page load strategy, driver.get() already waits for complete on URL navigation. Keep this check when the session uses eager or none, or after an action that changes pages without a direct driver.get() call.

  8. Wait for the application state needed by the next test action.
    ready_marker = wait.until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='orders-ready']")),
        "orders page ready marker did not appear",
    )

    Use a marker that belongs to the target page state, such as a route heading, enabled submit button, loaded row, or status message. Waiting only for complete can still miss single-page application updates.
    Related: How to use explicit waits in Selenium

  9. Remove the demo script after copying the pattern into the real test.
    $ rm selenium-page-load-wait.py