Skip to content

How to wait for page to load in Selenium? An Expert‘s Guide

Let me guess – you‘ve started scraping a site with Selenium and suddenly you‘re facing dreaded timeout errors, stale element exceptions, and flaky locators. Sound familiar?

Many of us have been there! In today‘s dynamic web, waiting properly for pages to fully load before interacting is critical for reliable automation.

In this comprehensive 3200+ word guide, I‘ll leverage my 5+ years as a professional web scraping expert to explore the various methods and best practices for graceful waiting in Selenium.

Whether you‘re just starting out or a seasoned pro, robust wait logic is a must-have tool for stability. Let‘s dive in!

Why You Can‘t Just Rush In

In the early days of the web, pages were mostly simple HTML rendered sequentially. Scrapers could start extracting immediately on page load.

But today‘s web is highly dynamic. According to Google research, the median time to first paint is 1.7s, but the median time to fully interactive is a whopping 15 seconds. That‘s a lot of time for content to load.

As a scraper, if you rush in too quickly, here are some common problems you‘ll face:

  • Button click errors because the element hasn‘t rendered yet
  • Trying to read data from a table that hasn‘t loaded server content
  • Sending text to an input that isn‘t visible on the screen
  • Scraping empty elements that will be populated after page load

These types of exceptions are symptoms that you need to wait longer for the page to be ready before interacting.

By The Numbers: Page Load Times

To understand how long we may need to wait, let‘s look at some real-world metrics on page load performance from the 2020 State of the Web Report by Akamai:

  • Median Time To Interactive: 15s
  • Average Page Weight: 2744KB
  • Average Number of Requests: 105
  • Average Images per Page: 53
  • JavaScript Bytes per Page: 453KB

Pages are bigger and more complex today, with much more work happening after the initial response. It‘s critical for scrapers to wait for interactivity, not just the first paint.

Common Exceptions Caused by No Waiting

Here are some specific exceptions that can occur when elements aren‘t ready yet:

  • StaleElementReferenceException – Element removed from DOM after fetch
  • ElementNotInteractableException – Trying to click unseen element
  • NoSuchElementException – Lookup timed out because element doesn‘t exist yet

Each of these indicates more waiting is required by the scraper.

Explicit Waits Are Your Friend

To avoid these errors, we need to wait for the page to fully render before interacting. There are two main approaches in Selenium:

Implicit Waits – Set a global wait time on the driver

Explicit Waits – Wait for specific conditions to occur

An explicit wait is much preferred over an implicit wait in most cases. Let‘s understand why.

Implicit Waits: The Sledgehammer Approach

Implicit waits set a timeout on the driver to poll the DOM when finding elements. This means any time you call:


The driver will retry up to the implicit wait duration to locate that element before throwing a NoSuchElementException.

You might use it like:

driver = webdriver.Chrome()

Now all lookups will retry for up to 10 seconds to find elements if not immediately present.

The downside is it waits for every locator, even ones that aren‘t needed to determine page readiness. This can really slow down your scraper.

Think of implicit waits like adding a 5 second sleep to every element fetch. It adds up!

The Precision of Explicit Waits

Explicit waits allow us to precisely wait for specific conditions indicating readiness before proceeding.

The keys ideas are:

  • Only wait when needed – Avoid unnecessary waits unrelated to page readiness
  • Precise conditions – Wait for exact elements or states, not just blanket time
  • Flexibility – Customize wait logic per page with different conditions
  • Readable – Easy to understand intent when revisiting old code

Here is a typical example waiting for an element to appear:

from import By
from import WebDriverWait 

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "myDynamicElement"))

This pauses execution until the element with ID "myDynamicElement" loads, or 10 seconds passes.

Other useful expected conditions provided by Selenium include:

  • title_contains() – Wait for page title to update
  • staleness_of() – Wait for element to no longer be attached to DOM
  • element_to_be_clickable() – Wait for element to be visible and enabled

Explicit Beats Implicit: A Real World Example

Let‘s compare the two waits with a real example.

Say I‘m scraping a site that has a navigation bar, left side panel, and main content.

The key element I need to wait for is an ID "#main-content" where my data is rendered.

With implicit wait:

  • 10 seconds added to every element lookup, even if not needed
  • Still prone to stale element errors if too quick

With explicit wait:

  • Only wait when needed for #main-content selector
  • Avoid unnecessary waits for nav and side panel
  • Wait specifically until data loads before continuing

By selectively waiting for a single readiness condition like an element, I avoid unnecessary delays.

Patterns for Effective Explicit Waits

Now that you‘re convinced explicit waits are the way to go, let‘s explore some best practices for using them effectively.

Page Load Waits

Waiting for the document ready state is a common technique to determine when loading is complete:

WebDriverWait(driver, 10).until(
   lambda d: d.execute_script(‘return document.readyState‘) == ‘complete‘

This polls the browser until the ready state is "complete", indicating all assets are loaded.

A more lightweight pattern is watching for specific high-level elements:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "main-content"))

This succeeds when the main content section loads, without waiting on everything else.

Per-Action Waits

You can also wait right before taking an action, like clicking an element:

menu = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "top-menu"))

submenu = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "submenu"))

This ensures both the top menu and submenu are ready before clicking.

Parallel Waits

Waiting for multiple conditions can confirm the page is ready:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "header")),
    EC.presence_of_element_located((By.ID, "footer")), 
    EC.presence_of_element_located((By.ID, "main"))

Requiring the header, footer, and main content to load reduces false positives.

Chained & Nested Waits

For advanced scenarios, you can also nest waits:

element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "dropdown"))

menu = WebDriverWait(element, 10).until(
    EC.presence_of_element_located((By.ID, "menu"))  

This waits first for a parent element, then a child element within that.

AJAX Polling Waits

Some sites load via continuous AJAX requests. You can loop waiting for changes:

while True:

    current_count = driver.find_element_by_id(‘result-count‘).text

    # If count changed since last check, page is still loading
    if current_count != previous_count:
        previous_count = current_count

    break # Page loaded!

This polls an element looking for changes to detect loading.

Asynchronous Waits

In async frameworks like asyncio, you can await promises:

await page.waitForSelector(‘#content‘)

The syntax is a bit different but provides asynchronous waiting.

Implicit + Explicit Combination

You can even combine both implicit and explicit waits:


my_element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "my-element"))

This way you have a global wait as well as a specific one. Just ensure they use reasonable durations.

Choosing Locators That Signal Readiness

When selecting locators to wait for, you want elements that match these criteria:

  • Appear late in the load process
  • Have unique IDs or classes that won‘t change
  • Located above the fold for fast checks
  • Are unlikely to get relocated by site changes
  • Don‘t get removed from the DOM and go stale

Some common examples are:

  • Main header or nav loaded after assets
  • Primary content containers or widgets
  • Page footer
  • Small dynamic UI elements like buttons

Load indicators like spinners are also great wait triggers when they disappear.

Tuning Timeouts For Optimal Waiting

Setting timeouts too long can really slow your scraper – but too short may cause flaky failures.

Here are some best practices on tuning durations:

  • Set page load timeouts longer, around 10-20 seconds.
  • Use shorter timeouts like 3-5 seconds for individual elements.
  • Consider browser performance, mobile vs. desktop.
  • Factor in network latency, broadband vs. 3G.
  • Monitor for timeout errors and adjust higher if needed.
  • Analyze page load waterfall for typical load times.
  • Budget 1-2 seconds extra as a buffer.
  • Standardize similar waits across your codebase.

As you scrape more pages, you‘ll get better intuition on optimal waits for reliability.

Handling Wait and Timeout Failures

Even with robust waits, you may still encounter occasional timeouts. Here are some ways to handle them:

  • Log debugging details – Adding prints helps diagnose where waits fail.
  • Retry on timeout – Retry short explicit waits up to 3 times on failure.
  • Increase timeout – If many timeouts occur, incrementally increase waits.
  • Use try/except – Catch specific exceptions like StaleElementReference.
  • Disable on fail – You can skip waits after repeated failures to let tests continue.

With some resilience built-in, these sporadic issues won‘t break your scraper.

Waiting in Other Languages

So far the examples have been in Python, but explicit waits are available across languages:

  • JavaWebDriverWait and ExpectedConditions
  • C#WebDriverWait and ExpectedConditions
  • RubyWebDriver::Wait and ExpectedConditions
  • JavaScriptbrowser.wait() and utility methods

The concepts are very similar – just the syntax differs slightly.

Beyond Selenium: More Waiting Tools

There are also some other helpful waiting libraries beyond Selenium:

  • Timetime.sleep() is simple but pauses all execution.
  • Retry – The Retry package makes retries and waits easy.
  • Aiohttpawait response.text() awaits network calls to complete.
  • Beautiful SoupBeautifulSoup(page.content, features="lxml") will wait for full parse.
  • Scrapyyield scrapy.Request(url, callback=self.parse) is asynchronous.

Mixing these with Selenium provides robust waits across your code.

In Summary: Wait Well and Scrap Reliably

In closing, here are five key takeaways:

  1. Use explicit waits – They avoid unnecessary timeouts and target specific conditions.

  2. Wait for multiple signals – Combine waits for header, body, footer, etc. to confirm page readiness.

  3. Tune timeouts wisely – Set values based on real-world page load data to optimize delays.

  4. Standardize waits – Reuse consistent patterns across your codebase.

  5. Add resilience – Implement retries and failure handling to account for dynamic pages.

Waiting may seem tedious at first. But investing in robust wait logic will reward you with reliable, resilient scrapers prepared for the modern web.

Hopefully these patterns and tips distilled from my years as a professional web scraping specialist will help you wait successfully. Scrap on!

Join the conversation

Your email address will not be published. Required fields are marked *