ScreenshotNeo

BlogHow-to

How to Wait for a Page to Load in Selenium WebDriver

Selenium waits for a document readiness threshold during navigation, but dynamic pages need condition-based waits for the state your test depends on.

By the ScreenshotNeo team4 October 202610 min read

Selenium WebDriver navigation normally waits until the document reaches readyState complete. That does not mean a JavaScript application has finished rendering the content your test needs. After navigation, use an explicit wait for the specific element or application state required by the next step.

For example, in Python:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
submit_button = wait.until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, "button[type='submit']"))
)
submit_button.click()

The 10-second timeout is an example. Choose a limit suitable for your application and test environment. Selenium’s navigation readiness threshold and an explicit wait solve different problems: one controls how long navigation blocks; the other waits for the condition your test actually needs. See the Selenium Project’s waiting strategies and expected conditions.

1. What “page loaded” means in Selenium

There are two useful meanings of “loaded” in an automated test:

  • Document readiness: the browser has reached the readiness threshold configured for WebDriver navigation.
  • Application readiness: the page has reached a meaningful state, such as a result list appearing or a button becoming enabled.

A call such as driver.get(url) waits according to the page-load strategy. With the default normal strategy, navigation waits for document.readyState to become complete. The document can be complete while JavaScript continues fetching data, rendering a single-page application, or updating the DOM. Wait for the application condition before acting.

A fixed sleep only waits for a predetermined duration. If it is too short, the test can still fail; if it is too long, every run pays the delay. Condition-based waits poll for the state you need and stop when it is met or the timeout expires.

2. Python: wait for the condition your test needs

Install Selenium with python -m pip install selenium and make sure the browser and driver setup supported by your Selenium version is available. This complete example opens a page and waits for a visible element:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

url = "https://example.com"
selector = "h1"
timeout_seconds = 10

driver = webdriver.Chrome()
try:
    driver.get(url)  # Uses the configured page-load strategy.
    heading = WebDriverWait(driver, timeout_seconds).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, selector))
    )
    print("Page is ready for this step:", heading.text)
except TimeoutException:
    print(f"The element {selector!r} was not visible within {timeout_seconds}s")
    print("Current URL:", driver.current_url)
    print("Document readyState:", driver.execute_script("return document.readyState"))
    raise
finally:
    driver.quit()

Use the condition that matches the next operation. For a click, wait for clickability; for reading content, wait for visibility or presence; for a changing value, wait until the expected value appears. Selenium’s wait repeatedly evaluates the condition until it succeeds or times out.

Common Python expected conditions

Need Condition What it confirms
Element exists in the DOM presence_of_element_located(locator) It can be found, but may be hidden.
Element is displayed visibility_of_element_located(locator) It exists and is visible with nonzero dimensions.
Element can be interacted with element_to_be_clickable(locator) It is visible and enabled; overlays can still interfere.
Element disappears invisibility_of_element_located(locator) It is hidden or absent, useful for a loading indicator.
Text appears text_to_be_present_in_element(locator, text) The expected text is present in the located element.
URL changes url_contains(fragment) The browser URL includes the expected fragment.

For a custom state, use a lambda that returns a truthy value when ready:

wait = WebDriverWait(driver, 10)
wait.until(lambda d: d.execute_script("return window.appReady === true"))

Prefer a visible user-facing result or a stable application condition where possible. A private JavaScript flag can couple the test to implementation details.

3. Other Selenium language bindings

The same approach applies in other bindings: navigate, then wait for a condition tied to the next step. Match imports and APIs to the Selenium version installed in your project.

Java

import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

WebDriver driver = new ChromeDriver();
try {
    driver.get("https://example.com");
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    WebElement heading = wait.until(
        ExpectedConditions.visibilityOfElementLocated(By.cssSelector("h1"))
    );
    System.out.println(heading.getText());
} finally {
    driver.quit();
}

JavaScript

const { Builder, By, until } = require('selenium-webdriver');

(async function main() {
  const driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.get('https://example.com');
    const heading = await driver.wait(
      until.elementIsVisible(await driver.findElement(By.css('h1'))),
      10000,
      'h1 did not become visible'
    );
    console.log(await heading.getText());
  } finally {
    await driver.quit();
  }
})();

In JavaScript, locating the element before passing it to elementIsVisible can itself fail if it does not exist yet. For elements that appear later, poll the lookup as part of the wait:

const heading = await driver.wait(async () => {
  const matches = await driver.findElements(By.css('h1'));
  if (matches.length === 0) return false;
  return (await matches[0].isDisplayed()) ? matches[0] : false;
}, 10000, 'h1 did not appear and become visible');

C#

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;

IWebDriver driver = new ChromeDriver();
try
{
    driver.Navigate().GoToUrl("https://example.com");
    var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
    var heading = wait.Until(d =>
    {
        var element = d.FindElement(By.CssSelector("h1"));
        return element.Displayed ? element : null;
    });
    Console.WriteLine(heading.Text);
}
finally
{
    driver.Quit();
}

4. Choose a page-load strategy

Page-load strategy controls when a navigation command returns. It does not wait for a particular application element.

Strategy Navigation threshold When it can help What to do next
normal readyState is complete (default) Tests that need the normal document load threshold. Still wait for dynamic content if needed.
eager readyState is interactive Tests that can proceed while some resources are still loading. Wait explicitly for the required state before interaction.
none WebDriver does not block on a ready-state threshold Tests that manage navigation readiness themselves. Immediately wait for a reliable condition; handle incomplete documents.

Python configuration example:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.page_load_strategy = "eager"  # "normal", "eager", or "none"
driver = webdriver.Chrome(options=options)

These strategies can let tests proceed sooner when they do not need all resources. They do not make a single-page application ready by themselves. Read Selenium’s browser options documentation for the binding and browser you use.

5. Page-load, implicit, explicit, and script timeouts

Timeout or wait Scope Typical use
Page-load timeout Navigation commands Bound how long navigation may block.
Implicit wait Element location calls throughout the session Global delay for unsuccessful element lookups.
Explicit wait One chosen condition Wait for a particular element or state before proceeding.
Script timeout Asynchronous JavaScript execution Limit time for async scripts executed through WebDriver.

The Selenium Browser Options documentation lists defaults for a new session of 300,000 ms for page-load timeout, 0 ms for implicit wait, and 30,000 ms for script timeout. Defaults can change across Selenium or WebDriver revisions; check the documentation for your installed version.

# Python examples
# Bound navigation waiting to 20 seconds.
driver.set_page_load_timeout(20)

# Implicit wait applies globally to element location calls.
# Selenium recommends avoiding a mix with explicit waits.
driver.implicitly_wait(0)

# Bound asynchronous script execution to 15 seconds.
driver.set_script_timeout(15)

Selenium warns: “Do not mix implicit and explicit waits.” An implicit wait can lengthen element lookups performed inside an explicit wait, making the overall duration difficult to predict. Prefer an implicit timeout of zero and explicit waits for the conditions that matter.

6. Navigation edge cases

  • Single-page applications: a URL transition or completed document load may happen before the new view renders. Wait for a distinctive element, text, or state from that view.
  • Loading indicators: wait for the relevant content to appear, and optionally wait for a spinner to become invisible. A spinner disappearing alone does not prove the desired content loaded.
  • Delayed or lazy content: content may load only after scrolling or interaction. Perform the triggering action, then wait for the content condition.
  • Frames: switch into the correct frame before locating or waiting for elements inside it; switch back to the default content when finished.
  • Alerts and new windows: wait for the alert or window condition, then switch to it. A page-load completion signal does not perform that switch.
  • Navigation interrupted by a timeout: a page-load timeout can occur while the browser is still on the destination page. Catch the timeout only when the test can safely inspect the resulting state, then use an explicit wait for the required condition.
  • Stale elements: a re-render can invalidate a previously located element. Wait using a locator and reacquire the element after the update instead of holding an old element reference.

7. Troubleshooting

Symptom Likely cause Fix
NoSuchElementException immediately after navigation The element is added after document readiness. Use an explicit wait for presence or visibility with a locator.
TimeoutException from an explicit wait The condition never became true, the locator is wrong, or the timeout is too short for the environment. Check the locator and current page, inspect the DOM and logs, then adjust the timeout based on observed application behavior.
The element exists but interaction fails It is hidden, disabled, covered by an overlay, or still moving. Wait for visibility or clickability; handle the overlay or animation according to the test scenario.
Wait duration is unexpectedly long Implicit and explicit waits are mixed, or each poll performs a slow lookup. Set implicit wait to zero and use explicit waits; keep the polled condition focused.
Navigation hangs or throws a page-load timeout A resource never finishes, the page is slow, or the configured threshold is unsuitable. Set a deliberate page-load timeout; consider eager if the test does not need every resource, then explicitly wait for the required state.
document.readyState is complete, but content is missing Document readiness is not application readiness. Wait for the specific content, state, or user-visible result.
Element becomes stale during a wait The page replaced the node during a render. Wait by locator and reacquire it on each poll; avoid reusing stale references.

When diagnosing a failure, capture the URL, readyState, relevant console or driver logs, and the locator being waited on. This helps distinguish a navigation problem from a late application render or an incorrect condition.

8. Performance, reliability, and cost

Condition-based waits improve test efficiency because they can return as soon as the condition succeeds. Keep the condition specific and avoid polling work that triggers side effects. A very short timeout can create flaky failures on slower CI machines; an excessively long timeout hides real failures and delays feedback. Set timeouts according to the application’s expected behavior and the test environment.

Page-load strategy can reduce navigation blocking when the test does not depend on all page resources. Pair eager or none with explicit conditions so faster navigation does not become a race. For reliability, wait on stable product behavior rather than arbitrary delays, and make failure output useful enough to diagnose the condition that did not occur.

WebDriver waits have no separate per-wait service charge; their practical cost is test runtime and the browser or CI resources used during that time. Longer waits can increase suite duration, while waits that are too short can cause reruns and unreliable results.

9. Capture a loaded page without setting up a browser

If your goal is a screenshot or PDF rather than browser interaction, ScreenshotNeo provides a website screenshot API and MCP server. A single request captures a URL; the API supports PNG, JPEG, WebP, and PDF, with options including wait-for-selector, delay, or network idle. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.

10. FAQ

Does Selenium wait after driver.get()?

Yes. Navigation waits according to the configured page-load strategy. By default, that threshold is document readiness at complete; dynamic application work may continue afterward.

Should I use time.sleep()?

Use it only when a fixed delay is specifically part of the behavior under test. For page readiness, an explicit condition is usually more precise and can proceed as soon as the condition is met.

What should I wait for in a single-page application?

Wait for an element, text, URL, or application state that proves the view needed by the next test step is ready.

Can I make Selenium stop waiting for the whole page?

Set the page-load strategy to eager or none where appropriate, then explicitly wait for the condition your test needs. These strategies change navigation blocking; they do not guarantee application readiness.

What is the safest default approach?

Keep implicit wait at zero, use the normal navigation threshold unless there is a reason to change it, and add explicit waits for dynamic or interactive states.