Hero Background

Power Your Software Testing with AI Agents and Cloud

The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. Test Intelligently and Ship Faster.

AutomationSelenium WebDriverTutorial

Selenium Page Load Strategy: Normal, Eager, and None

Selenium page load strategy sets when get() returns. Set normal, eager, or none in Java or Python page_load_strategy, tested in Chrome, Edge, Firefox, Safari.

Last Updated on:

Selenium's page load strategy decides when a navigation command such as get() hands control back to your test, and the answer is not the same in every browser. In a September 2026 run on the TestMu AI grid, get() under the none strategy left Chrome and Edge loading the requested page, while Firefox and Safari were still on the blank start page, which reports readyState complete.

This guide covers what normal, eager, and none wait for in Selenium WebDriver, how to set each one in Java, Python, and JavaScript, and what each returned into across four browsers, along with the navigation gaps in Firefox and Safari and the waits that close them.

Overview

Selenium's page load strategy is a session capability that decides when navigation commands such as get() hand control back to your test. It takes three values, normal (the default), eager, and none, and you set it once per session on the browser options, for example options.page_load_strategy = "eager" in Python.

  • normal (default): The normal page load strategy returns after the load event, when readyState is complete. Use it when the next step reads fully loaded content right after navigating. Waits for images and iframes: yes.
  • eager: The eager page load strategy returns once the document is interactive and DOMContentLoaded has fired, while images and iframes keep downloading. It saves at most the gap between DOMContentLoaded and load. Waits for images and iframes: no.
  • none: The none page load strategy returns without waiting for readiness. In TestMu AI grid runs, Firefox and Safari were still showing the previous page when get() returned, so pair none with a staleness check and an element wait. Waits for DOM readiness: no.

What Is Page Load Strategy in Selenium?

Page load strategy is the pageLoadStrategy session capability, which tells WebDriver when a navigation command such as get() or navigate().to() is finished. The W3C WebDriver specification defines three values: normal, the default, waits for document.readyState to reach complete; eager waits for interactive; and none returns without waiting.

The value is sent once, when the session starts, so it applies to every navigation in that session. It is also older than Selenium 4: the Selenium 3.141.59 Java bindings already ship the PageLoadStrategy enum, and their ChromeOptions, FirefoxOptions, and EdgeOptions classes have a setPageLoadStrategy() method.

Selenium 4 moved that setter into AbstractDriverOptions, the base class every browser's options object extends, so SafariOptions gained it too, and Python's options objects gained a page_load_strategy property.

A shorter wait does not stop the page from loading. When a user reported that CSS and JavaScript files kept downloading under NONE and EAGER, Selenium maintainer diemol answered in SeleniumHQ/selenium issue 9315: "Having the command return earlier does not mean that the page will stop loading."

How document.readyState Maps to Each Strategy

MDN's document.readyState reference defines the three states the strategies wait for. One detail shapes eager: a document becomes interactive before its deferred and module scripts run, and DOMContentLoaded fires only after those scripts have executed.

StrategyNavigation returns whenreadyState at returnCan still be loading after return
normal (default)The load event has firedcompleteOnly what the page fetches later, such as AJAX data
eagerThe document is interactive; in every grid run that measured it, DOMContentLoaded had also firedinteractive, or complete if load finished firstImages, stylesheets, iframes, and async scripts
noneRight after the navigation startsAny, including the previous document'sEverything

How to Set Page Load Strategy in Java, Python, and JavaScript

Set the Selenium page load strategy on the browser options object before the session starts. The client sends it as the pageLoadStrategy capability, so a local driver and a remote grid read it the same way, and a session that never sets it runs as normal.

The C# and Ruby forms in this table come from the Selenium documentation's pageLoadStrategy section; the Java, Python, and JavaScript forms ran in the code in this guide.

LanguageSetterValues
Javaoptions.setPageLoadStrategy(PageLoadStrategy.EAGER)PageLoadStrategy.NORMAL, EAGER, NONE
Pythonoptions.page_load_strategy = "eager""normal", "eager", "none"
JavaScriptoptions.setPageLoadStrategy('eager')'normal', 'eager', 'none'
C#options.PageLoadStrategy = PageLoadStrategy.EagerPageLoadStrategy.Normal, Eager, None
Rubyoptions.page_load_strategy = :eager:normal, :eager, :none
Any W3C client or grid"pageLoadStrategy": "eager" in the capabilities"normal", "eager", "none"

In Java, the setter sits on every options class, so this is the whole change for a local Chrome session:

ChromeOptions options = new ChromeOptions();
options.setPageLoadStrategy(PageLoadStrategy.EAGER); // NORMAL applies when you skip this line
WebDriver driver = new ChromeDriver(options);

To confirm what the session accepted, read the capability back with driver.getCapabilities().getCapability("pageLoadStrategy") in Java or driver.capabilities["pageLoadStrategy"] in Python.

In JavaScript, chrome.Options inherits setPageLoadStrategy() from the Capabilities class. This script ran on the TestMu AI grid with Selenium 4.49.0:

const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');

(async () => {
  const options = new chrome.Options()
    .setPageLoadStrategy('eager') // 'normal' (default), 'eager' or 'none'
    .setBrowserVersion('latest')
    .setPlatform('Windows 11');
  options.set('LT:Options', {
    username: process.env.LT_USERNAME,
    accessKey: process.env.LT_ACCESS_KEY,
    build: 'Selenium Page Load Strategy',
    name: 'JavaScript eager',
  });

  const driver = await new Builder()
    .usingServer('https://hub.lambdatest.com/wd/hub')
    .forBrowser('chrome')
    .setChromeOptions(options)
    .build();
  try {
    console.log('pageLoadStrategy:', (await driver.getCapabilities()).get('pageLoadStrategy'));
    await driver.get('https://ecommerce-playground.lambdatest.io/');
    console.log('readyState when get() returned:', await driver.executeScript('return document.readyState'));
    await driver.executeScript('lambda-status=passed');
  } finally {
    await driver.quit();
  }
})();

Output from two runs of the same script:

pageLoadStrategy: eager
readyState when get() returned: interactive

pageLoadStrategy: eager
readyState when get() returned: complete

The second run returned into complete because that page's load event had already fired by the time the script read readyState. Eager guarantees at least interactive; when load finishes first, you get complete.

Set page_load_strategy in Selenium Python

Python exposes the strategy as a string property on the options object: "normal", "eager", or "none". The script below sets eager on Chrome, runs on the TestMu AI grid, and includes a navigate() helper for eager and none navigations that the waiting section explains.

import os

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

HOME = "https://ecommerce-playground.lambdatest.io/"
SPECIAL = HOME + "index.php?route=product/special"


def start(strategy):
    options = webdriver.ChromeOptions()
    options.page_load_strategy = strategy  # "normal" (default), "eager" or "none"
    options.browser_version = "latest"
    options.platform_name = "Windows 11"
    options.set_capability("LT:Options", {
        "username": os.environ["LT_USERNAME"],
        "accessKey": os.environ["LT_ACCESS_KEY"],
        "build": "Selenium Page Load Strategy",
        "name": f"Python {strategy}",
    })
    return webdriver.Remote(command_executor="https://hub.lambdatest.com/wd/hub", options=options)


def navigate(driver, url, ready_locator, timeout=30):
    """get() that is safe under eager and none: the old page is gone, then the element you need."""
    old_html = driver.find_element(By.TAG_NAME, "html")
    driver.get(url)
    wait = WebDriverWait(driver, timeout)
    wait.until(EC.staleness_of(old_html))
    return wait.until(EC.visibility_of_element_located(ready_locator))


driver = start(os.environ.get("STRATEGY", "eager"))
try:
    driver.get(HOME)
    print("page_load_strategy:", driver.capabilities["pageLoadStrategy"])
    print("readyState when get() returned:", driver.execute_script("return document.readyState"))
    heading = navigate(driver, SPECIAL, (By.CSS_SELECTOR, "body.product-special h1"))
    print("next page ready:", heading.text)
    driver.execute_script("lambda-status=passed")
finally:
    driver.quit()

Run it with LT_USERNAME and LT_ACCESS_KEY set in the environment. Output from the grid run:

page_load_strategy: eager
readyState when get() returned: interactive
next page ready: Special Offers

With STRATEGY=none, the same script printed loading after the first get(), and navigate() still returned the Special Offers heading.

Page Load Strategy Measured in Four Browsers

The strategy table states intent. To see what a test gets back under each Selenium page load strategy, the Java project below opened one session per browser and strategy on TestMu AI's Automation Cloud, loaded the TestMu AI ecommerce playground home page, and read the live document from inside the browser the moment get() returned.

TestMu AI's Automation Cloud runs existing Selenium scripts across 3,000+ browser/OS combinations without local drivers, so Chrome and Edge on Windows 11 and Safari on macOS Sequoia run from the same test class by switching the options object and platform name.

Project Setup

You need JDK 17 or newer and Maven. Create a project with the standard src/test/java layout and this pom.xml, which pins Selenium 4.49.0, TestNG 7.12.0, and Surefire 3.5.6 and caps parallel data provider threads at four:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>demo</groupId>
  <artifactId>page-load-strategy</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>4.49.0</version>
    </dependency>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>7.12.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.6</version>
        <configuration>
          <properties>
            <property>
              <name>dataproviderthreadcount</name>
              <value>4</value>
            </property>
          </properties>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Export your username and access key as LT_USERNAME and LT_ACCESS_KEY. The code reads both from the environment and sends them inside LT:Options, which keeps them out of the hub URL; the TestNG guide for the TestMu AI Selenium grid covers the account side.

GridDriver.java starts sessions and reads page state:

package demo;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.PageLoadStrategy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.remote.AbstractDriverOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.safari.SafariOptions;

public final class GridDriver {

    private static final String HUB = "https://hub.lambdatest.com/wd/hub";

    private GridDriver() {}

    public static RemoteWebDriver start(String browser, PageLoadStrategy strategy, String testName) throws Exception {
        AbstractDriverOptions<?> options = switch (browser) {
            case "Chrome" -> new ChromeOptions();
            case "Edge" -> new EdgeOptions();
            case "Firefox" -> new FirefoxOptions();
            case "Safari" -> new SafariOptions();
            default -> throw new IllegalArgumentException("Unknown browser: " + browser);
        };
        options.setPageLoadStrategy(strategy); // NORMAL applies when this line is omitted
        options.setBrowserVersion("latest");
        options.setPlatformName(browser.equals("Safari") ? "macOS Sequoia" : "Windows 11");

        Map<String, Object> ltOptions = new HashMap<>();
        ltOptions.put("username", System.getenv("LT_USERNAME"));
        ltOptions.put("accessKey", System.getenv("LT_ACCESS_KEY"));
        ltOptions.put("build", "Selenium Page Load Strategy");
        ltOptions.put("name", testName);
        options.setCapability("LT:Options", ltOptions);

        return new RemoteWebDriver(URI.create(HUB).toURL(), options);
    }

    /** Reads the document the browser is really showing, from inside the page. */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> pageState(WebDriver driver) {
        Object state = ((JavascriptExecutor) driver).executeScript(
            "const nav = performance.getEntriesByType('navigation')[0];"
            + "return { url: document.URL, readyState: document.readyState,"
            + " domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd) : 0,"
            + " load: nav ? Math.round(nav.loadEventEnd) : 0 };");
        // A script that runs while one document replaces another can come back empty
        return state != null ? (Map<String, Object>) state
            : Map.of("url", "", "readyState", "unknown", "domContentLoaded", 0L, "load", 0L);
    }
}
  • start() - picks ChromeOptions, EdgeOptions, FirefoxOptions, or SafariOptions, sets the strategy through the shared AbstractDriverOptions setter, and opens a RemoteWebDriver session. Other LT:Options keys, such as video or network logs, come from the TestMu AI Capabilities Generator.
  • pageState() - runs a script inside the page and returns document.URL, document.readyState, and the Navigation Timing values domContentLoadedEventEnd and loadEventEnd. It reads the URL from the document itself because WebDriver's view of the page can disagree with the live document, as the results show.

The Measurement Test

PageLoadStrategyTest.java crosses four browsers with three strategies in a parallel TestNG data provider, times get(), and snapshots the page the moment the call returns:

package demo;

import java.time.Duration;
import java.util.Map;
import org.openqa.selenium.PageLoadStrategy;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class PageLoadStrategyTest {

    static final String HOME = "https://ecommerce-playground.lambdatest.io/";

    @DataProvider(parallel = true)
    public Object[][] browsersAndStrategies() {
        String[] browsers = {"Chrome", "Edge", "Firefox", "Safari"};
        PageLoadStrategy[] strategies = {PageLoadStrategy.NORMAL, PageLoadStrategy.EAGER, PageLoadStrategy.NONE};
        Object[][] rows = new Object[browsers.length * strategies.length][];
        int i = 0;
        for (String browser : browsers) {
            for (PageLoadStrategy strategy : strategies) {
                rows[i++] = new Object[] {browser, strategy};
            }
        }
        return rows;
    }

    @Test(dataProvider = "browsersAndStrategies")
    public void whatGetReturnsInto(String browser, PageLoadStrategy strategy) throws Exception {
        RemoteWebDriver driver = GridDriver.start(browser, strategy, browser + " " + strategy);
        String status = "failed";
        try {
            long start = System.nanoTime();
            driver.get(HOME);
            long getMs = (System.nanoTime() - start) / 1_000_000;
            Map<String, Object> atReturn = GridDriver.pageState(driver);

            // Wait on the live document: the right URL, readyState complete, load event finished
            new WebDriverWait(driver, Duration.ofSeconds(60)).until(d -> {
                Map<String, Object> page = GridDriver.pageState(d);
                return page.get("url").toString().startsWith(HOME)
                    && "complete".equals(page.get("readyState"))
                    && ((Number) page.get("load")).longValue() > 0;
            });
            Map<String, Object> loaded = GridDriver.pageState(driver);

            String version = driver.getCapabilities().getBrowserVersion().split("[.]")[0];
            boolean onTarget = atReturn.get("url").toString().startsWith(HOME);
            boolean dclFired = onTarget && ((Number) atReturn.get("domContentLoaded")).longValue() > 0;
            System.out.printf("%-7s %-3s %-6s | get() %5d ms | returned into %-11s on %-11s | DOMContentLoaded fired: %-3s"
                    + " | page finished: DOMContentLoaded %5s ms, load %5s ms%n",
                browser, version, strategy, getMs, atReturn.get("readyState"),
                onTarget ? "target page" : atReturn.get("url"), dclFired ? "yes" : "no",
                loaded.get("domContentLoaded"), loaded.get("load"));
            status = "passed";
        } finally {
            driver.executeScript("lambda-status=" + status);
            driver.quit();
        }
    }
}
  • browsersAndStrategies() - returns the 12 browser and strategy pairs; parallel = true runs them concurrently, up to the four threads the pom allows.
  • whatGetReturnsInto() - times get() with System.nanoTime(), captures the page state at once, then waits until the target document is complete and its load event has finished before printing both states.
  • lambda-status - marks each session passed or failed on the TestMu AI dashboard, so a failing combination is easy to open with its video and logs.

Run the class with Maven:

mvn test -Dtest=PageLoadStrategyTest

Results on the TestMu AI Grid

Console output from the run on September 30, 2026, sorted by browser (one session per line; timings change from run to run):

Chrome  154 normal | get()  3299 ms | returned into complete    on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  2920 ms, load  3211 ms
Chrome  154 eager  | get()  2957 ms | returned into interactive on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  2681 ms, load  3013 ms
Chrome  154 none   | get()    59 ms | returned into loading     on target page | DOMContentLoaded fired: no  | page finished: DOMContentLoaded  2575 ms, load  3080 ms
Edge    154 normal | get()  6122 ms | returned into complete    on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  2738 ms, load  6027 ms
Edge    154 eager  | get()  3237 ms | returned into complete    on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  3007 ms, load  3012 ms
Edge    154 none   | get()    71 ms | returned into loading     on target page | DOMContentLoaded fired: no  | page finished: DOMContentLoaded  2779 ms, load  2877 ms
Firefox 156 normal | get()  5713 ms | returned into complete    on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  3678 ms, load  5255 ms
Firefox 156 eager  | get()  4211 ms | returned into interactive on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  3569 ms, load  5148 ms
Firefox 156 none   | get()   600 ms | returned into complete    on about:blank | DOMContentLoaded fired: no  | page finished: DOMContentLoaded  3455 ms, load  4789 ms
Safari  18  normal | get()  5308 ms | returned into complete    on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  2236 ms, load  3284 ms
Safari  18  eager  | get()  2962 ms | returned into interactive on target page | DOMContentLoaded fired: yes | page finished: DOMContentLoaded  2175 ms, load  3235 ms
Safari  18  none   | get()    51 ms | returned into complete    on about:blank | DOMContentLoaded fired: no  | page finished: DOMContentLoaded  2225 ms, load  3299 ms
Browsernormal returned intoeager returned intonone returned into
Chrome 154complete on target page (3299 ms)interactive on target page (2957 ms)loading on target page (59 ms)
Edge 154complete on target page (6122 ms)complete on target page (3237 ms)loading on target page (71 ms)
Firefox 156complete on target page (5713 ms)interactive on target page (4211 ms)complete on about:blank (600 ms)
Safari 18complete on target page (5308 ms)interactive on target page (2962 ms)complete on about:blank (51 ms)
  • normal - returned into complete in all four browsers, after 3.3 s in Chrome, 6.1 s in Edge, 5.7 s in Firefox, and 5.3 s in Safari.
  • eager - returned with DOMContentLoaded already fired in all four browsers: interactive in Chrome, Firefox, and Safari, and complete in Edge, where that page load fired its load event 5 ms after DOMContentLoaded.
  • none - Chrome (59 ms) and Edge (71 ms) returned already on the target URL in the loading state; Safari (51 ms) and Firefox (600 ms) returned while about:blank was still the live document and reported readyState complete.

Single-run get() times are not a benchmark, because network conditions differ from session to session. The in-page gap between DOMContentLoaded and load says more: in the Edge normal session, DOMContentLoaded fired at 2.7 s and load at 6.0 s, so get() waited 6.1 s, while in the Edge eager session the two events were 5 ms apart and eager saved nothing.

Note

Note: Run the same matrix against your own pages on TestMu AI. The networkThrottling capability's presets run from GPRS and Regular 2G up to Regular 4G, so you can see how much of a slow load eager would skip. See the network throttling presets or start testing for free.

Page Load Strategy vs Page Load Timeout

Page load strategy picks which readiness state a navigation waits for; the page load timeout caps how long that wait may last.

The W3C WebDriver Recommendation gives every session a page load timeout of 300,000 milliseconds, or five minutes, unless the session sets another value.

A navigation that runs past that limit fails with a timeout error, which Selenium raises as TimeoutException. The limit bounds only the strategies that wait: under none, the specification returns success before any readiness check, so a navigation cannot time out.

The oneSecondPageLoadTimeout() test sets a one-second limit on Chrome and loads the same home page with each strategy:

Chrome  normal pageLoadTimeout 1 s: TimeoutException after 1224 ms
Chrome  eager  pageLoadTimeout 1 s: TimeoutException after 1186 ms
Chrome  none   pageLoadTimeout 1 s: get() returned after 73 ms

Normal and eager both threw TimeoutException just after the one-second limit, because DOMContentLoaded on this page took longer than a second; none returned in 73 ms. When slow pages trip the timeout under normal, raise the limit, and treat eager as a fix only if DOMContentLoaded itself fits inside it.

// Java: raise or lower the cap for every navigation in the session
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
# Python: the value is in seconds
driver.set_page_load_timeout(30)

For handling patterns around TimeoutException and its relatives, see these common Selenium exceptions.

How to Wait After an eager or none Navigation

Under eager and none the strategy no longer promises a ready page, so the test has to define ready. Two waits covered every browser in the grid runs: first prove the previous document is gone, then wait for the element the next step uses.

WebElement oldHtml = driver.findElement(By.tagName("html"));
driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=product/special");

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
wait.until(ExpectedConditions.stalenessOf(oldHtml));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("body.product-special h1")));

The navigate() helper in the Python script is the same pair of waits. Avoid these shortcuts after an eager or none navigation:

  • A readyState check - under none, Firefox and Safari reported complete for the blank start page or the previous page.
  • A getCurrentUrl() check - Safari reported the destination URL while the previous page was still the live document.
  • Thread.sleep() - waits the full duration when the page is fast and still fails when the page is slower than the sleep.

On TestMu AI, the SmartWait capability runs actionability checks before each action, holding it until the element is visible, enabled, and stable, within a window you set between 5 and 120 seconds. It removes per-element waits after eager, but it checks the element an action targets, not which document that element belongs to, so keep the staleness check wherever the previous page can still be live.

For content that keeps arriving after readyState reaches complete, such as AJAX panels, the Selenium wait for page to load guide covers element, staleness, and AJAX waits in Python, and waits in Selenium compares implicit, explicit, and fluent waits.

Youtube thumbnail

Which Page Load Strategy Should You Use?

Keep normal unless you have measured a real gap between DOMContentLoaded and load on the pages your tests visit; then move that suite to eager and add element waits. Reserve none for tests that decide readiness themselves, like the measurement test above.

StrategyChoose it whenPair it with
normalTests read page content right after navigating, or pages finish loading soon after DOMContentLoadedElement waits for content fetched after the load event
eagerImages, iframes, ads, or async scripts keep loading well after DOMContentLoadedA wait for the element the next step uses
noneThe test measures loading or waits on its own signalsA staleness check on the old html element, then an element wait

The gap is easy to measure: pageState() already returns domContentLoaded and load from the Navigation Timing API, and load minus domContentLoaded is the most eager can save on that load. Across the 12 loads above it ranged from 5 ms to 3.3 s on the same page, so sample several loads, ideally under the network conditions your users have, before switching. For more ways to time a page from a test, see how to measure page load time with Selenium.

Per-Navigation Waits With WebDriver BiDi

The strategy is fixed for the session, but WebDriver BiDi's navigate command takes a readiness value on each call. Selenium's Java bindings expose it as BrowsingContext.navigate(url, ReadinessState), and TestMu AI enables BiDi on Chrome, Firefox, and Edge sessions that request webSocketUrl: true, as its WebDriver BiDi docs describe.

These lines come from BiDiNavigateTest.java, which ran on Chrome and Firefox with the same GridDriver setup plus the BiDi capability:

// ChromeOptions or FirefoxOptions, with browserVersion, platformName, and LT:Options set as in GridDriver
options.setCapability("webSocketUrl", true); // turns on WebDriver BiDi

RemoteWebDriver remote = new RemoteWebDriver(URI.create("https://hub.lambdatest.com/wd/hub").toURL(), options);
WebDriver driver = new Augmenter().augment(remote);

BrowsingContext tab = new BrowsingContext(driver, driver.getWindowHandle());
tab.navigate(HOME, ReadinessState.INTERACTIVE);
tab.navigate(SPECIAL, ReadinessState.COMPLETE);
Chrome  BiDi navigate(INTERACTIVE)  2872 ms | {domContentLoaded=2789, load=3399, readyState=complete, url=https://ecommerce-playground.lambdatest.io/}
Chrome  BiDi navigate(COMPLETE)     2237 ms | {domContentLoaded=1978, load=2191, readyState=complete, url=https://ecommerce-playground.lambdatest.io/index.php?route=product/special}
Firefox BiDi navigate(INTERACTIVE)  3452 ms | {domContentLoaded=3345, load=0, readyState=interactive, url=https://ecommerce-playground.lambdatest.io/}
Firefox BiDi navigate(COMPLETE)     1536 ms | {domContentLoaded=1305, load=1438, readyState=complete, url=https://ecommerce-playground.lambdatest.io/index.php?route=product/special}

Firefox returned from the INTERACTIVE call with readyState interactive and its load event still pending, then waited for complete on the next page, all in one session. On Chrome the load event had already fired by the time the page state was read, the same race the JavaScript eager run showed.

Selenium Skill for AI Coding Agents

When an AI coding assistant writes the test, it needs the same wait rules. TestMu AI's open-source Selenium Skill gives assistants such as Claude Code, Copilot, and Cursor the patterns this guide's code follows: explicit WebDriverWait instead of sleeps, credentials from LT_USERNAME and LT_ACCESS_KEY, driver.quit() in teardown, and lambda-status reporting.

npx agentskillsforall add https://github.com/LambdaTest/agent-skills.git --skill selenium-skill

The same repository holds skills for Playwright, Appium, and other frameworks; the agent skills overview explains how skills work and how to install them.

Install the Selenium Skill so Claude Code, Copilot, and Cursor pair every eager or none navigation with explicit waits.

Selenium

Conclusion

Start with the slowest page in your suite: log pageState() after get() for a few loads and compare load with domContentLoaded before you change the Selenium page load strategy. Then run the suite with the strategy you picked across Chrome, Edge, Firefox, and Safari using Selenium automation on TestMu AI, with the Selenium 4 capabilities reference open for the options format.

Author

...

Vipul Gupta

Blogs: 24

  • Twitter
  • Linkedin

Vipul Gupta is a Sr. Lead SDET at Zupee with over 9 years of experience in functional and automation testing. He has built 10+ automation projects from scratch covering web, API, and mobile applications. Vipul is skilled in Selenium, Appium, Rest Assured, Playwright, Java, Python, Pytest, BDD, TDD, Maven, Jenkins, TestNG, and JUnit. He has successfully led end-to-end QA efforts, including setting up teams from scratch and managing a 15-member QA team to ensure manual and automation testing run in parallel from day one. Vipul graduated in B.Tech CSE from CGC College of Engineering and is followed by 3,000+ QA and SDET professionals on LinkedIn, reflecting his strong influence in the testing community.

Reviewer

...

Navin Chandra

Reviewer

  • Linkedin

Navin Chandra is a Member of Technical Staff at TestMu AI (formerly LambdaTest), building the open-source automation that powers its Selenium and Appium cloud grid. A committer to both Selenium and Appium, he implemented WebDriver BiDi support in Selenium for real-time browser events and bidirectional control and is developing Apple's iOS RemoteXPC protocol in Appium to enable low-level wireless communication with iOS system services. He contributes to Selenium across multiple language bindings as a member of the Selenium GitHub organization. He has served as a Google Summer of Code mentee and mentor at openSUSE and an LFX mentee at CNCF's KubeArmor, and is a SUSE Certified Deployment Specialist. Navin holds a B.Tech in Computer Science.

Add to Google preferred sources

Summarise with AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

Page Load Strategy FAQs

Did you find this page helpful?

More Related Blogs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests