Mastering Playwright: How to Wait for Page to Load Effectively
TutorialsEfficient page loading is important for both web scraping projects and various other needs. Master Playwright and make the pages load as fast as possible!

Vilius Dumcius
Waiting for a web page to finish loading is crucial for reliable browser automation. It doesn’t really matter if you’re running web testing scripts or performing web scraping, your Playwright code must often pause until the page is fully loaded before interacting with it.
If not, you risk flakiness, as in, a script might click an element that hasn’t appeared yet, causing intermittent failures. Playwright defines a page “load” as the point when a new document is loaded into the browser context after a navigation (such as a URL change or redirect).
In this post, we’ll explore how to wait for a page to load in Playwright effectively. We’ll discuss Playwright’s different page load states, its built-in auto-waiting, and various explicit waiting methods (with both JavaScript and Python examples). By the end, you’ll know how to ensure your scripts only proceed when the page is truly ready.
Understanding Page Load States in Playwright
Playwright provides distinct page load states that represent milestones in the loading process. The main states are DOMContentLoaded, load, and network idle (called “networkidle” in code). Each denotes a different level of the page being fully loaded:
- DOMContentLoaded: Page has loaded and parsed the initial HTML document and built the DOM tree, but without waiting for styles, images, or other subresources. In other words, the core HTML is ready, though other resources may still be loading.
- Load: The browser’s load event has fired, meaning all resources on the page (HTML, CSS, images, scripts, iframes, etc.) have finished loading. This indicates the entire page is fully loaded, including all static files.
- Networkidle (Network Idle): No pending network connections for at least 500 ms. In this state, all network requests have finished (e.g. no XHR/fetch in progress), and the network is idle. This is especially useful for pages that load additional async data (like single-page applications), as it waits for a quiet period after initial load.
Playwright’s page.waitForLoadState() method lets you wait for any of these states explicitly. For example, you can wait for a specific state after navigation or user actions:
In JavaScript:
await page.goto('https://example.com');
// Wait for DOM content to load (HTML parsed):
await page.waitForLoadState('domcontentloaded');
// Wait for all network requests to finish (network idle state):
await page.waitForLoadState('networkidle');
// Wait for full load event (all resources loaded):
await page.waitForLoadState('load');
In Python:
page.goto("https://example.com")
# Wait for DOMContentLoaded (HTML parsed):
page.wait_for_load_state("domcontentloaded")
# Wait for network idle (no pending requests):
page.wait_for_load_state("networkidle")
# Wait for full load event (all resources loaded):
page.wait_for_load_state("load")
In the above code, we navigate to a page and then explicitly wait for different load states. The page object in Playwright offers this fine-grained control to ensure the page (or specific parts of it) is ready. For instance, waiting for "domcontentloaded" ensures the basic DOM is ready to manipulate, while waiting for "networkidle" guarantees that any dynamic content loaded via AJAX is done. Waiting for "load" is the most conservative, ensuring the page is completely loaded, including images and other assets.
Playwright’s Auto-Waiting Mechanism
One of Playwright’s strengths is its built-in auto-waiting. Playwright automatically waits for elements to be in an actionable state before interacting with them. In practice, this means that if you call await page.click('selector') or await page.fill('selector', 'text'), Playwright will pause until the target element appears and is ready (visible, enabled, and not moving) before performing the action.
This await page actionability check often eliminates the need to manually wait for the page to load fully in simple scenarios. For example:
// This click will auto-wait for the button to be visible and enabled
await page.getByText('Login').click();
In the above snippet, Playwright waits behind the scenes for the “Login” button to be present and visible before clicking, so you don’t have to insert an explicit wait. This auto-wait mechanism makes scripts more resilient to minor timing issues and is a big reason why Playwright tests can be less flaky by default.
However, explicit waits are still necessary in certain cases. Playwright’s auto-waiting ensures the element you act on is ready, but it doesn’t guarantee that other parts of the page or background processes have finished.
For example, a page might display a placeholder element that is visible (so Playwright thinks it’s clickable) but the actual content or JavaScript dynamic content (such as event listeners or data) isn’t loaded yet.
A common scenario is client-side hydration: the element exists in the DOM and is enabled, but the script that makes it interactive hasn’t run yet. In such cases, a click might do nothing because the page isn’t fully functional yet.
Similarly, after triggering a navigation or an action that loads new content, you may need to wait for specific elements or network calls to complete.
The key is to leverage Playwright’s auto-wait for most interactions and use explicit waits only when needed. Overusing manual waits can slow down tests unnecessarily. As a rule of thumb, let Playwright handle waits for element interactions, and insert your own waits at key points. For example, after a navigation, or when waiting for a specific element or data to load This approach keeps your tests efficient and reliable.
Methods to Wait for Page Load in Playwright
Playwright offers several methods to wait for a page to load or for certain conditions to be met. Below, we discuss the primary techniques and when to use each.
Using page.goto() with waitUntil Options
The simplest way to load a page is using await page.goto(url). By default, page.goto() will wait for the load event to fire, meaning it waits for a full page load (all resources) before resolving. However, Playwright lets you customize this waiting behavior through the waitUntil option:
- waitUntil: 'load': Waits for the full load event (page completely loaded). This is the default. It ensures everything is loaded but can be slower.
- waitUntil: 'domcontentloaded': Waits only for the DOMContentLoaded event. This means the HTML is parsed and DOM constructed, but it does not wait for images or other resources. This option is faster and useful if your script doesn’t need those resources (e.g., if you only need the text content or DOM structure).
- waitUntil: 'networkidle': Waits until there have been no network requests for 500 ms. This is handy for pages that load additional data asynchronously (for example, single-page apps that fetch data after initial render). It effectively waits for the page to reach an idle network state after the initial load.
In JavaScript, it would look like this:
// Wait for only the DOM content to be loaded (faster, no images/styles):
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Wait for all resources to load (slower, but page is fully loaded):
await page.goto('https://example.com', { waitUntil: 'load' });
// Wait for network to be idle after navigation (good for SPAs/dynamic content):
await page.goto('https://example.com', { waitUntil: 'networkidle' });
In Python , it would look like this:
# Navigate and wait only for DOM content (HTML) to load:
page.goto("https://example.com", wait_until="domcontentloaded")
# Navigate and wait for full page load (all resources):
page.goto("https://example.com", wait_until="load")
# Navigate and wait for network idle (no pending requests):
page.goto("https://example.com", wait_until="networkidle")
Each option has its pros and cons. Waiting for DOMContentLoaded is often sufficient if you just need the primary content and can significantly speed up your script since it doesn’t wait for images or ads to load. Using load guarantees everything is loaded, which can be important for tasks like taking screenshots or when you need every resource ready (e.g., styles for visual testing), at the cost of extra wait time.
The networkidle option is great for modern sites that load data via AJAX as it waits for those background network requests to finish, ensuring the page is truly fully loaded from the user’s perspective. However, be cautious: if the page continuously makes network calls (like analytics pings or long-polling connections), networkidle may hang or only resolve after a timeout.
In fact, Playwright’s documentation notes that using networkidle can sometimes be discouraged because it might wait too long and slow down tests. Use it when you know the page will reach an idle state (or combine it with a reasonable timeout) and it’s necessary for your scenario.
Using page.waitForLoadState()
Whereas page.goto()’s waiting happens during navigation, page.waitForLoadState() lets you wait for a page load state at any time. This can be useful if you need to wait for additional loading after some interaction.
For instance, if you click a button that triggers content load via JavaScript, you might call await page.waitForLoadState('networkidle') right after the click to pause until all network activity has finished:
// Assume clicking this button loads additional content via AJAX:
await page.click('#loadMore');
// Wait for network to go idle, meaning AJAX calls finished:
await page.waitForLoadState('networkidle');
This method uses the same states discussed earlier ('load', 'domcontentloaded', 'networkidle'). If no state is specified, it defaults to waiting for the load event. You can also specify a timeout here (e.g. page.waitForLoadState('load', { timeout: 10000 }) to wait up to 10 seconds) to override the default.
It’s worth noting that historically, scripts often used page.waitForNavigation() after actions that trigger navigations. For example, in Puppeteer or early Playwright you might see code like:
await Promise.all([
page.click('a.some-link'),
page.waitForNavigation({ waitUntil: 'load' })
]);
Playwright still supports page.waitForNavigation(), but it must be used in combination with an action that causes a navigation (as shown above).
In modern Playwright code, you often don’t need to call this directly. Instead, you can rely on page.goto() or page.waitForLoadState() and page.waitForURL() (covered next) for clearer intent.
In general, waitForLoadState is more flexible as it doesn’t require a navigation trigger and can be used to wait for page stability at any point.
If you have a navigation-triggering action, a more explicit approach is to use page.waitForURL(), described below.
Waiting for Elements and Network Conditions
Often, the most reliable way to wait for a page to load (especially for dynamic content) is to wait for a specific condition, such as an element appearing or an API call finishing.
Playwright provides methods like page.waitForSelector(), page.waitForResponse(), and page.waitForRequest() for these finer-grained waits.
- Waiting for specific elements: page.waitForSelector(selector[, options]) pauses until an element matching the given CSS selector appears (and optionally until it’s visible). This is extremely useful for SPAs or content that loads in stages. Instead of waiting for a global page load, you wait for a specific element that indicates the content you need is present.
- Waiting for network responses/requests: If your page makes XHR/fetch calls that you know of, you can wait for those as a signal that content is ready. Playwright’s page.waitForResponse() waits for a network response that matches a given URL pattern or predicate.
Using these targeted waits often yields a more reliable and faster script than always waiting for a full page load. You’re effectively synchronizing with exactly what you care about: the presence of specific elements or the completion of specific background tasks.
As an added benefit, this approach avoids waiting for unrelated network requests (such as ads or analytics) that aren’t important for your automation. As a best practice, “pick and wait only for necessary details” rather than every possible activity on the page.
Finally, note that Playwright’s locator objects also have a .waitFor() method (and in Playwright Test, there are web assertions like expect(element).toBeVisible() which include auto-waiting). These can be used to wait for element states more fluently, but under the hood they serve a similar purpose, that is, ensuring the UI is in the expected state before moving on.
Handling Navigation With waitForURL()
When clicking a link or button causes the page’s URL to change (a navigation or route change), Playwright’s page.waitForURL() is an explicit way to wait for that transition. This method pauses execution until the page’s URL matches a certain pattern. It’s very handy for scenarios like redirect handling or single-page application route changes, where you want to ensure you’ve arrived at the correct page before continuing.
For example, suppose your test clicks a “Login” button that navigates to a dashboard page. You can do the following:
In JavaScript:
await page.getByText('Login').click();
// Wait for URL to contain "/dashboard"
await page.waitForURL('**/dashboard');
In Python:
page.get_by_text("Login").click()
# Wait until URL changes to include "/dashboard"
page.wait_for_url("**/dashboard")
The waitForURL method supports wildcard patterns (**) and even regular expressions, so you can flexibly match dynamic URLs (like user IDs or query params). By default, waitForURL will also wait for the page to load (it uses the same load state logic as navigation), but you can specify a waitUntil option here as well if needed.
In practice, page.waitForURL() is often used in place of the older waitForNavigation pattern because it clearly ties the wait to a specific URL change. It’s especially useful in SPAs where a navigation might not involve a full page reload, that is, the URL change (via the History API) is the primary indicator of navigation.
Playwright will consider the wait fulfilled once the URL matches and the navigation is complete (committed and, by default, loaded).
One thing to remember: waitForURL only works when the URL truly changes. If your action doesn’t actually change the page URL (for example, clicking a tab that loads new content via AJAX without changing the URL), then you should rely on the earlier methods (like waiting for an element or network request).
In summary, use page.waitForURL() for traditional navigations or client-side routing where the address changes, to make your script pause until the new page is ready at the expected URL.
Choosing the Right Load State for Your Scenario
Now that we’ve covered the tools, how do you decide which page load waiting strategy to use? The choice should be driven by your page’s behavior and your testing goals:
- Use the minimal necessary wait: If your test or script only needs the basic DOM content, don’t wait for everything. For example, if you’re verifying text on the page or scraping data that’s present in the initial HTML, waiting for the DOMContentLoaded state (or using waitForSelector for a specific element) is sufficient and faster.
- Wait for full load when required: If you need all aspects of the page to be present (such as verifying an image is displayed, or performing visual testing, or simply to avoid any missing pieces), use the load state. This ensures the page is fully loaded with all static assets. It’s a safer route for pages that don’t do heavy dynamic loading after, and for scenarios where completeness is more important than speed.
- Consider networkidle for dynamic pages: For applications that load significant dynamic content after the initial HTML (common in modern web apps), waiting for network idle can ensure you don’t proceed until that async content is done. For instance, in a data dashboard that fetches charts via API calls on load, networkidle will wait for those network requests to finish so that the charts are rendered.
- Adjust for real conditions: Always consider varying network conditions and performance. A script might work on fast local network but time out on a slow connection. If you run tests on cloud browsers or real devices, the page might take longer. Be mindful of Playwright’s timeouts (the default timeout is 30s for most waits) and increase them or use retries when dealing with especially slow pages.
Conclusion
Mastering page load waits in Playwright is key to writing stable, efficient tests and scrapers that use proxies . We began by understanding Playwright’s loading states, that is, from DOMContentLoaded to full load and network idle. We saw that Playwright often handles waiting for us with its auto-waiting mechanism, which is usually enough for basic interactions.
But for complex scenarios, we have an arsenal of explicit waits: we can wait for specific elements to appear, for network responses to arrive, or for the URL to change, depending on what “fully loaded” means for our context. By mixing these techniques, you can ensure your script only proceeds when the page (or the part of the page you care about) is ready.