ScreenshotNeo

BlogGuides

How to Choose a Browser Wait Condition for Website Captures

Choose a browser wait condition based on what the screenshot must show. Compare lifecycle events, explicit content checks, and network idle with runnable examples.

By the ScreenshotNeo team29 September 202610 min read

How to Choose a Browser Wait Condition for Website Captures

A browser wait condition tells your capture code when to take the screenshot. Choose the earliest signal that guarantees the content you need is visible: use DOMContentLoaded for parsed markup, load when dependent resources matter, and a specific visible-content check when an application fills in data asynchronously. Use networkidle cautiously; a quiet network does not prove that the right content is on screen.

There is no universal “page loaded” point. Playwright’s documentation puts it plainly: “There is no way to tell that there is a ‘loaded’ page, it depends on the page, framework, etc.” Playwright: Navigations.

1. What a browser wait condition actually waits for

Navigation and loading are separate phases. A navigation can commit once response headers arrive and the session history is updated; document parsing and lifecycle events follow. Different wait conditions describe different milestones, not a guarantee that every visual change on a modern site has finished.

  • commit: the response has started and the document begins loading. Useful when you intend to perform your own readiness check immediately afterward.
  • domcontentloaded: the HTML has been parsed. It does not mean all images, styles, scripts, or app data are ready.
  • load: the document and dependent resources such as stylesheets, scripts, iframes, and images have reached the load milestone. Lazy-loaded content or later app requests may still be pending.
  • networkidle: in Playwright, no network connections for at least 500 ms. This describes network activity, not visual completeness.
  • An explicit content condition: a known element is visible, text has appeared, or an application-specific condition is true. This is usually the most direct signal when the screenshot must include a particular result.

For example, an online dashboard can reach load before its data request finishes. Conversely, analytics, polling, or a persistent connection can keep the network active after the useful content is already visible. Match the condition to the capture’s purpose.

2. Which browser wait condition should I use for a screenshot?

What the capture needs Start with What to add or watch for
The response and document shell as early as possible Playwright commit Follow it with a locator or app-state check for the actual target.
Parsed HTML and DOM structure Playwright domcontentloaded; Selenium eager Dependent resources and client-rendered data can still be incomplete.
Images and other dependent resources at their load milestone Playwright load; Selenium normal Later lazy loads and asynchronous UI updates can still happen.
A particular result, chart, or component Visible locator or an assertion about its content Choose a stable selector or meaningful text that represents readiness.
Only a brief quiet period in network activity networkidle, when appropriate Do not treat it as proof that a particular visual state is ready.

Selenium uses a different set of page-load strategies: normal waits for the document’s complete ready state, eager for interactive, and none does not block on a ready state. These are session-level WebDriver settings, not names interchangeable with Playwright’s navigation options. Selenium also cautions that a complete document state does not necessarily mean a single-page application has finished dynamic loading. See Selenium driver options.

Navigation milestones happen before some applications finish rendering the content a screenshot needs.
Navigation milestones happen before some applications finish rendering the content a screenshot needs.

3. A practical decision process

  1. Write down what must be in the image. Is it a document shell, a hero image, a search result list, or a populated dashboard? “Page loaded” is too vague to guide a reliable wait.
  2. Pick the earliest sufficient milestone. For parsed markup, use DOM content loaded. If initial dependent assets must finish, wait for load. This avoids waiting for unrelated work when a narrower milestone is enough.
  3. Add a target-specific condition for dynamic content. If JavaScript fetches or hydrates the content later, wait for the target component or expected text rather than assuming a lifecycle milestone settles the app.
  4. Set a timeout as a failure bound. A timeout is useful for detecting a page that never reaches the expected state. It is not evidence that an arbitrary sleep duration makes the capture ready.
  5. Inspect the captured result when changing the condition. If output is incomplete, determine whether the target selector is wrong, the app is slow, or the page enters a different state. Avoid increasing every wait indiscriminately.

4. Playwright: runnable screenshot examples

Install Playwright and its Chromium browser with npm install -D playwright and npx playwright install chromium. Save the following as capture.mjs, then run node capture.mjs. The example waits for a known content target, which is preferable when the image must contain that target.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
try {
  await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded',
    timeout: 30_000,
  });
  // Replace this with a stable selector for the content your shot needs.
  await page.locator('h1').waitFor({ state: 'visible', timeout: 15_000 });
  await page.screenshot({ path: 'capture.png', fullPage: true });
} finally {
  await browser.close();
}

To use a lifecycle milestone by itself, change waitUntil to 'load', 'commit', or 'networkidle'. Playwright’s page.goto() defaults to load. A later call to page.waitForLoadState() is often unnecessary: it resolves immediately if the state has already been reached, and actions auto-wait for their own actionability conditions. For content readiness, use a locator or web-first assertion, as in the official Page API and assertion guidance.

If the application’s target has no suitable visible element, wait for an app-specific signal you control, such as a known global state or a response that represents the data. Avoid coupling the screenshot to incidental requests when a visible outcome can be checked.

Playwright with an assertion

In a Playwright Test project, an assertion can make the expected screenshot state explicit:

import { test, expect } from '@playwright/test';

test('captures the populated results', async ({ page }) => {
  await page.goto('https://example.com/search?q=shoes', {
    waitUntil: 'domcontentloaded',
  });
  const results = page.locator('[data-testid="search-results"]');
  await expect(results).toBeVisible({ timeout: 15_000 });
  await expect(results).toContainText('Results');
  await page.screenshot({ path: 'results.png', fullPage: true });
});

Use selectors the site actually provides. If you own the page, stable test identifiers are less brittle than long CSS paths or styling classes. When the capture must include an image, wait for that image to be loaded as well as visible; visibility alone can occur before its pixels are available.

5. Selenium: choose a strategy and wait for the target

Install Selenium’s Python package with pip install selenium and configure a browser driver for your environment. The following example uses eager to avoid waiting for every dependent resource, then explicitly waits for the capture target. Selenium’s pageLoadStrategy applies to the WebDriver session, so every navigation in that session follows the selected strategy.

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

options = webdriver.ChromeOptions()
options.page_load_strategy = "eager"  # normal, eager, or none

driver = webdriver.Chrome(options=options)
try:
    driver.set_page_load_timeout(30)
    driver.get("https://example.com")
    target = WebDriverWait(driver, 15).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, "h1"))
    )
    driver.save_screenshot("capture.png")
finally:
    driver.quit()

Use normal when the complete document load milestone is necessary; choose eager when parsed/interactively available DOM plus explicit waits are enough. none returns without waiting for a ready state, so add a sufficient explicit wait before capturing. Selenium’s options documentation describes these strategies.

6. Why network idle is not a universal answer

Playwright defines network idle as a 500 ms interval without network connections, but explicitly discourages using it as a general testing readiness signal. Some pages continue polling, stream data, or keep connections open. Others become network-quiet before a delayed timer or rendering step displays the content.

A visible target condition ties the wait to what must appear in the capture.
A visible target condition ties the wait to what must appear in the capture.

Prefer a known visible component or assertion when the screenshot has a clear requirement. Network idle can still be a useful additional condition for a page whose meaningful requests are finite and whose desired state follows those requests, but verify that behavior for the site and workflow. It should not replace a content check simply because it sounds comprehensive.

7. Timeouts, speed, and reliable captures

A broad wait can spend time on assets irrelevant to the image; an early wait can capture content before it appears. A target-specific wait balances these risks: it returns when the needed state exists, while still failing within a defined bound if that state never arrives. There is no universally correct timeout; set it according to your application and capture environment, then record failures so you can distinguish slow responses from missing targets.

  • Use the narrowest readiness condition that is correct. Avoid waiting for full load if only a text result is needed and the site’s resource loading is slow.
  • Bound each stage. Navigation and content waits can have separate timeouts, making it clearer which step failed.
  • Keep the browser lifecycle tidy. Close pages and browsers in cleanup paths, particularly in batch jobs, to avoid resource leaks.
  • Control variability where it matters. Fix the viewport and use a stable URL and selector. If content varies by account, locale, or time, make that state explicit before capture.
  • Do not turn a sleep into a readiness test. A fixed delay wastes time on fast runs and can still be too short on slow ones. If a delay is required for a known animation, keep it separate from the content readiness check.

Back/forward cache restoration is a navigation edge case: the browser can restore a page without the usual sequence of commit, DOMContentLoaded, and load events. If your capture workflow tests history navigation, wait for the restored page’s actual state rather than assuming those events will fire again. See Playwright navigation guidance.

8. Troubleshooting

Symptom Likely cause Fix
Screenshot has a shell but no data The lifecycle event completed before client-side fetching or hydration finished. Wait for a visible data container or expected result text; keep navigation and content waits separate.
networkidle never resolves Polling, analytics, streaming, or persistent requests keep activity going. Use a specific content condition, or a lifecycle milestone plus that condition.
Capture occasionally misses an element The selector is unstable, the content is conditional, or the wait checks presence instead of visibility. Use a stable selector and wait for the visible state or meaningful text; inspect whether the application shows an error state.
Navigation times out even though a page appears The selected milestone includes slow resources or the application never completes that milestone. Consider an earlier milestone and an explicit target wait. Keep an appropriate navigation timeout as a bound.
Selenium returns before the page is ready eager or none was selected without enough explicit waits. Wait for the page element that must appear, or use normal if full document load is required.
Image is visible but blank in the shot The image element appeared before the image resource finished loading. Wait for the relevant image’s loaded state, not visibility alone.
Wait resolves immediately but capture is still incomplete The lifecycle state had already occurred; it says nothing about later app work. Add a condition tied to the content, rather than repeating the same lifecycle wait.

9. Or skip the browser setup

If your task is simply to fetch a clean website screenshot, ScreenshotNeo is a website screenshot API and MCP server. A single GET request takes a URL and returns PNG, JPEG, WebP, or PDF. It handles capture setup for you, and offers wait options such as waiting for a selector, a delay, or network idle. See the ScreenshotNeo API documentation for parameters and response details.

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 Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

ScreenshotNeo accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. One thousand screenshots a month are free with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card.

10. FAQ

Should I use load or DOMContentLoaded for screenshots?

Use DOMContentLoaded when parsed markup is enough. Use load when dependent resources at the document load milestone must be complete. If the image needs app data that appears later, add a target-specific condition in either case.

Is networkidle more reliable than waiting for an element?

No. Network quietness is not proof that the element you need is visible. A specific visible-state assertion directly checks the screenshot requirement and avoids pages whose background requests never stop.

Does a longer timeout make a capture more reliable?

It gives the condition more time to succeed, but it does not make the condition meaningful. Choose the right condition first, then use a timeout as a bounded failure limit.

Can Selenium and Playwright use the same wait option?

No. Their configuration names and semantics differ. Playwright navigation uses lifecycle states such as domcontentloaded and networkidle; Selenium page-load strategy uses normal, eager, and none.