Waituntil Option in Playwright

Learn how Playwright's waitUntil option controls navigation timing and page load strategies. Test your waitUntil strategies across real devices with BrowserStack.

Get Started free
Waituntil Option in Playwright
Home Guide Waituntil Option in Playwright

Waituntil Option in Playwright

Ever watched your Playwright test fail with a timeout error, even though the page clearly loaded in front of you?

I had the same frustration. My tests worked locally but failed in CI pipelines constantly. The culprit?

I was using the default waitUntil setting, which didn’t match how my application actually loaded.

The waitUntil option tells Playwright when to consider navigation complete. But “complete” means different things depending on your page’s behavior.

A single-page app that fetches data asynchronously needs a different strategy than a static HTML page. Playwright gives you four options, each designed for different loading behaviors.

Overview

Common waitUntil Options in Playwright

  • load: Waits for the page’s load event, ensuring all resources, including images and scripts, are fully loaded. Use this when you need the entire page, visual and functional elements included, to be ready.
  • domcontentloaded: Waits until the HTML is fully parsed and the DOM is ready, without waiting for images or stylesheets. Ideal for quickly interacting with elements present in the initial HTML.
  • networkidle: Waits until there are no ongoing network requests for a short period, typically 500ms. This is useful for pages that fetch content dynamically, ensuring asynchronous data has loaded.
  • commit: Waits for navigation to be committed, meaning the response headers are received and session history updated. This is the earliest point at which Playwright considers navigation started, before rendering or resource loading.

In this article, I’ll show you how each value works, when to use which one, and how to fix timing issues in your tests.

What Does the waitUntil Option in Playwright Navigation Do?

The waitUntil option is a parameter you pass to Playwright’s navigation methods like page.goto(), page.reload(), and page.goBack(). It tells Playwright what condition to wait for before considering the navigation successful and moving to the next line of code.

Without this option, Playwright uses a default waiting strategy that might not match your page’s actual loading behavior. This mismatch is what causes those frustrating timeout errors or premature interactions with incomplete pages.

Here’s the basic syntax:

javascriptawait page.goto(‘https://example.com’, { waitUntil: ‘load’ });

When you set waitUntil, you’re essentially answering the question: “When is this page ready for me to interact with it?” The answer depends entirely on what your page does during load.

What Happens During Navigation

To understand why waitUntil matters, you need to know what happens when a browser loads a page:

  1. Navigation commits: The browser receives response headers and updates the session history
  2. HTML parsing begins: The browser starts building the DOM tree from the HTML
  3. DOM ready: The HTML is fully parsed (this triggers DOMContentLoaded event)
  4. Resources load: Images, stylesheets, fonts, and scripts download and execute
  5. Page load complete: All resources finish loading (this triggers the load event)
  6. Async activity: JavaScript may continue making API calls and updating the page

Traditional page loads follow this sequence linearly. But modern web apps often render initial content quickly, then fetch data asynchronously. This is where choosing the right waitUntil value becomes critical.

If you wait for load on a React app that fetches data after rendering, you might interact with the page before the data appears. If you wait for networkidle on a page with constant analytics pings, you’ll wait forever.

The waitUntil option gives you control over which point in this lifecycle Playwright should consider “done.”

The waitUntil option gives you control over which point in this lifecycle Playwright should consider “done.” But choosing the right strategy requires testing under realistic conditions. BrowserStack’s cloud infrastructure lets you run Playwright tests with network throttling and performance monitoring to validate your waitUntil choices before they impact production.

Run Playwright Tests

How Do the Different waitUntil Values Behave Under the Hood?

Each waitUntil value corresponds to a specific browser event or network state. Understanding what triggers each one helps you pick the right strategy for your use case.

1. load

The load option waits for the browser’s load event to fire. This happens when the browser has finished downloading and processing all resources referenced in the initial HTML: images, stylesheets, scripts, iframes, and fonts.

await page.goto(‘https://example.com’, { waitUntil: ‘load’ });

Under the hood: Playwright listens for the window.load event. The browser fires this event only after every resource in the page (including those in iframes) has finished loading.

When it completes:

  • All tags have downloaded their images
  • All
    stylesheets are parsed and applied
  • All
  • All iframes have fully loaded

What it doesn’t wait for: Asynchronous JavaScript requests made after the initial page load. If your page makes a fetch() call in a useEffect hook or after a script executes, load won’t wait for that.

2. domcontentloaded

The domcontentloaded option waits for the browser’s DOMContentLoaded event. This fires as soon as the HTML document is fully parsed and the DOM tree is built, without waiting for stylesheets, images, or subframes.

await page.goto(‘https://example.com’, { waitUntil: ‘domcontentloaded’ });

Under the hood: Playwright listens for the DOMContentLoaded event, which fires when the HTML parser has finished building the DOM structure.

When it completes:

  • The HTML is fully parsed
  • All DOM elements are accessible via JavaScript
  • Synchronous scripts in the HTML have executed

What it doesn’t wait for: Images, stylesheets, fonts, or async scripts. The page might look unstyled or have missing images when this resolves.

Speed advantage: This is typically 30-50% faster than load on resource-heavy pages because it doesn’t wait for images or external assets.

3. networkidle

The networkidle option waits until there are no more than 2 network connections active for at least 500ms. This catches most asynchronous data fetching that happens after the initial page load.

await page.goto(‘https://example.com’, { waitUntil: ‘networkidle’ });

Under the hood: Playwright monitors all network requests (XHR, fetch, resource loading) and starts a 500ms timer every time the number of active connections drops to 2 or fewer. If no new requests start within that window, navigation is considered complete.

When it completes:

  • Initial page resources have loaded
  • API calls triggered by JavaScript have finished
  • Dynamic content fetched via AJAX is likely rendered

The 2-connection allowance: Some browsers maintain persistent connections for things like Server-Sent Events or long-polling. The 2-connection threshold prevents these from blocking indefinitely.

Warning: Pages with continuous polling, live chat widgets, or real-time updates may never reach network idle. You’ll hit the navigation timeout in these cases.

4. commit

The commit option waits only for the navigation to commit-meaning the browser has received the response headers and updated the session history. This is the earliest possible point where navigation is considered started.

await page.goto(‘https://example.com’, { waitUntil: ‘commit’ });

Under the hood: Playwright resolves as soon as the browser receives HTTP response headers and commits to navigating to the new URL. At this point, page.url() will return the new URL, but nothing has been rendered yet.

When it completes:

  • Response headers received
  • HTTP status code is available
  • Session history is updated
  • The old page is gone from the viewport

What it doesn’t wait for: Literally everything else. No HTML parsing, no DOM building, no resource loading. The page is essentially blank.

Use case: This is rarely used in test automation. It’s mainly useful when you only care about navigation happening (like checking redirects) or when you plan to wait for specific elements manually afterward.

Why Choosing the Correct waitUntil Matters in Test Automation and Scraping

Picking the wrong waitUntil value doesn’t just slow down your tests-it creates flaky failures that waste hours of debugging time. The right choice directly impacts test reliability, execution speed, and data accuracy.

Here’s what happens when you get it wrong:

  • Test Reliability: Using domcontentloaded on API-driven pages causes “Element not found” errors because content hasn’t loaded yet. Using networkidle on pages with analytics triggers 30-second timeouts on every navigation.
  • Execution Speed: Wrong choices add 2-3 seconds per page load. Across 100 tests, that’s 3+ minutes wasted per run. Switching from load to domcontentloaded in one project cut CI time from 12 to 8 minutes-a 33% improvement.
  • Data Accuracy in Web Scraping: Using domcontentloaded on JavaScript-rendered content gives you empty fields. Pages that switch from server-side to client-side rendering break your scraper silently.
  • Resource Consumption: Using load when you only need DOM downloads unnecessary images and videos. Extra wait time means higher cloud testing bills and wasted bandwidth on slow networks or CI environments.

Getting these timing decisions right the first time saves countless hours of debugging and re-running failed tests. BrowserStack’s Playwright integration gives you detailed test logs, screenshots, and video recordings to pinpoint exactly where your waitUntil strategy breaks down.

Inline Banner Guide

How To Apply waitUntil in Playwright Code (With Examples)

Now that you understand what each waitUntil value does, let’s look at how to use them in real scenarios. The key is matching the strategy to your page’s loading behavior.

1. Basic Navigation with waitUntil

The most common place to use waitUntil is with page.goto():

await page.goto(‘https://example.com’, { waitUntil: ‘load’ });

You can also use it with other navigation methods:

await page.reload({ waitUntil: ‘networkidle’ });await page.goBack({ waitUntil: ‘domcontentloaded’ });
await page.goForward({ waitUntil: ‘commit’ });

2. Multiple waitUntil Values

Playwright lets you pass an array of events if you need to wait for multiple conditions:

await page.goto(‘https://example.com’, { waitUntil: [‘load’, ‘networkidle’] });

This waits for both the load event AND network idle. Navigation only completes when both conditions are met. This is useful when you want the security of load but also need to catch async requests.

Let’s consider a few scenarios to better understand how you can apply waitUntil in Playwright.

Scenario 1: Static Content Website

For a blog or documentation site where all content is in the initial HTML:

await page.goto(‘https://blog.example.com/article’, { waitUntil: ‘domcontentloaded’ });await page.locator(‘h1’).textContent();

You don’t need images or stylesheets to read the article title, so domcontentloaded is fastest. The DOM is ready, and you can immediately interact with text elements.

Scenario 2: Single-Page Application (SPA)

For a React, Vue, or Angular app that fetches data after rendering:

await page.goto(‘https://app.example.com/dashboard’, { waitUntil: ‘networkidle’ });await page.locator(‘.user-stats’).isVisible();

The initial HTML is just a shell. The real content loads via API calls after JavaScript executes. networkidle ensures those fetch requests complete before you try to interact with the data.

Scenario 3: Image-Heavy Landing Page

For a marketing page where visual elements matter:

await page.goto(‘https://example.com/product’, { waitUntil: ‘load’ });await page.screenshot({ path: ‘product-page.png’ });

If you’re taking screenshots for visual testing, you need all images loaded. load ensures the page looks exactly as users see it.

Scenario 4: Checking Redirects

When you only care about where navigation ends up:

await page.goto(‘https://example.com/old-url’, { waitUntil: ‘commit’ });const finalUrl = page.url();
console.log(finalUrl); // https://example.com/new-url

You don’t need to wait for the page to render-just confirm the redirect happened. commit is the fastest way to check this.

Scenario 5: Form Submission with Dynamic Feedback

For pages that show loading states or success messages after form submission:

await page.locator(‘button[type=”submit”]’).click();await page.waitForURL(‘/success’, { waitUntil: ‘networkidle’ });**

After form submission triggers navigation, networkidle ensures any success message loaded via API is ready before you assert on it.

Common Pitfalls When Using waitUntil and How To Avoid Them

Even experienced Playwright users run into issues with waitUntil. These mistakes create hard-to-debug failures that seem random but follow predictable patterns.

Here are the most common pitfalls and how to avoid them:

  • Using networkidle on pages with continuous background activity: Pages with analytics or live chat never reach network idle. Your test times out after 30 seconds. Solution: Use load, then wait for specific elements.
  • Assuming load means “ready to interact”: The load event fires before JavaScript finishes initializing. Clicking buttons too early does nothing. Solution: Wait for elements to be actionable, not just visible.
  • Using domcontentloaded for JavaScript-rendered content: SPAs render empty HTML shells first. domcontentloaded fires before content appears. Solution: Use networkidle for SPAs, or add explicit element waits after domcontentloaded.
  • Not accounting for third-party scripts: Ads and tracking scripts load slowly. Using load makes tests hang waiting for external resources. Solution: Use domcontentloaded or block third-party scripts with route handlers.
  • Ignoring different behaviors across environments: Pages reach networkidle quickly locally but timeout in CI with slow networks. Solution: Increase timeouts for CI or test with realistic network conditions.
  • Using commit without additional waits: commit resolves when the page is still blank. Any interaction fails immediately. Solution: Only use commit for checking redirects, then add waitForLoadState(‘domcontentloaded’).
  • Not combining multiple strategies: One waitUntil value for all pages creates timeouts or premature interactions. Solution: Create page-specific helper functions with the right strategy for each page type.

When waitUntil Alone Is Not Enough and What to Use Instead

The waitUntil option handles basic page loading, but real applications load content in stages, show loading spinners, or fetch data after the page renders. In these cases, you need more precise waits beyond just navigation events.

Sometimes waitUntil isn’t enough because:

  • Specific elements load after navigation completes: Your API call finishes after load fires, so the element you need isn’t visible yet
  • Loading states hide actual content: The page shows a spinner while networkidle resolves, then updates with data
  • Elements exist but aren’t interactive: Buttons are in the DOM but disabled or missing event listeners
  • Third-party widgets load separately: Chat widgets or maps initialize after the main page finishes loading
  • Content updates dynamically: Counters, timers, or text fields change values after appearing in the DOM

What to use instead:

  • waitForSelector(): Wait for specific elements to appear, be visible, or be hidden before interacting with them
  • waitForResponse(): Wait for specific API calls to complete instead of all network activity
  • waitForFunction(): Wait for custom conditions like text content reaching a specific value or elements meeting certain criteria
  • waitForLoadState(): Chain multiple load states like waitForLoadState(‘networkidle’) after initial navigation
  • Locator auto-waiting: Use Playwright locators that automatically wait for elements to be actionable before clicking or typing
  • Frame locators: Wait for iframe content to load when dealing with embedded widgets

The key is combining waitUntil with these targeted waits to match your application’s actual loading behavior.

How Device and Network Conditions Affect waitUntil

The same waitUntil strategy behaves completely differently depending on network speed and device performance. On your fast WiFi with a powerful laptop, pages reach load and networkidle in milliseconds. But on a 3G mobile connection or a low-end Android device, that same page might take 10x longer or never reach networkidle at all.

Device performance also impacts JavaScript execution timing. High-end devices parse and execute React or Vue quickly, so domcontentloaded and full interactivity happen almost simultaneously. On slower devices or overloaded CI runners, there’s a 2-3 second gap where the DOM is ready but event listeners aren’t attached yet.

This is where testing on actual devices and network conditions becomes critical. BrowserStack lets you validate your waitUntil strategy across real-world scenarios before users encounter problems.

What BrowserStack provides:

  • Real device cloud: Test on actual iPhones, Android phones, and tablets to see how waitUntil performs on real hardware
  • Network throttling: Simulate 3G, 4G, and slow WiFi conditions to verify your strategy works under poor connectivity
  • Geographic distribution: Test from different regions with varying latency to catch location-specific timing issues
  • Parallel testing: Run tests with different waitUntil values across multiple devices simultaneously to find the optimal strategy
  • Reporting and Analytics: Track load times and identify which waitUntil value works best for each device-network combination

Talk to an Expert

Conclusion

Choosing the right waitUntil strategy is crucial for reliable test automation and accurate web scraping. Use load for image-heavy pages, domcontentloaded for static content, networkidle for API-driven SPAs, and commit only for redirect checks. The wrong choice leads to flaky tests, wasted time, and incomplete data.

However, network conditions and device performance dramatically affect when navigation events fire. What works on your laptop might fail on mobile 3G or in CI environments. Use BrowserStack to test your waitUntil strategies across real devices, browsers, and network conditions. This ensures your automation works reliably for actual users.

Try BrowserStack for Free

Useful Resources for Playwright

Tool Comparisons:

Tags
Automation framework Automation Testing Real Device Cloud

Get answers on our Discord Community

Join our Discord community to connect with others! Get your questions answered and stay informed.

Join Discord Community
Discord