ScreenshotNeo

BlogHow-to

How to Wait for a Website to Finish Loading Before a Browserless Screenshot

Choose a Browserless navigation or content wait that matches the page, then capture only after the content you need is ready.

By the ScreenshotNeo team4 October 20268 min read

To wait for a website before taking a Browserless screenshot, choose a readiness condition that matches the page. In a Browserless REST screenshot request, set gotoOptions.waitUntil for navigation, then add a selector or function wait if important content renders afterward. With Puppeteer or Playwright connected to Browserless, await the matching navigation or content condition before calling page.screenshot().

There is no universal “finished loading” signal. A page can fire its load event while a client-side app is still fetching data, or keep network requests open after the content you need is already visible. The reliable approach is to define what ready means for the screenshot and use a condition that checks it.

1. Choose what “ready” means

Browserless REST gotoOptions.waitUntil follows Puppeteer navigation wait conditions. The main choices are:

Condition What it waits for Good starting point
domcontentloaded The initial HTML has been parsed. It does not wait for stylesheets, images, or subframes to finish. Content is in the initial document and you want an earlier capture.
load The page’s load event, after dependent resources have loaded. The screenshot needs resources such as images to finish loading.
networkidle0 No active network connections for at least 500 ms. The page makes a finite set of requests and then goes quiet.
networkidle2 No more than two active network connections for at least 500 ms. The page has some persistent traffic, but waiting for navigation activity to settle is still useful.

For a single known piece of dynamic content, a selector wait is often a better readiness check than waiting for all network activity to stop. For example, wait for the article heading or results container that must appear in the screenshot. Browserless REST also supports waits for a function, event, or fixed duration. Use a fixed duration only when time itself matters, such as allowing an animation to settle; elapsed time does not prove that the correct content loaded.

These controls solve different problems. A navigation condition says when navigation is considered complete. A selector or function says when page-specific content is ready. Screenshot options such as waiting for images or setting a screenshot timeout govern capture behavior and do not replace a readiness condition.

2. Wait in a Browserless REST screenshot request

Send a JSON request body to the Browserless REST screenshot endpoint for your account and deployment. This example waits for navigation to settle, then for a visible main-content element. Use the endpoint and authentication method specified for your Browserless account; do not put a real token in source control.

curl -X POST "https://production-sfo.browserless.io/screenshot?token=YOUR_BROWSERLESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "url": "https://example.com/",
    "gotoOptions": {
      "waitUntil": "networkidle2",
      "timeout": 30000
    },
    "waitForSelector": {
      "selector": "#main-content",
      "timeout": 10000,
      "visible": true
    }
  }' \
  --output screenshot.png

The URL shown is an example endpoint shape; use the host and endpoint assigned to your Browserless account. Browserless documents REST request configuration and precondition waits in its request configuration and timeouts documentation.

Set the selector to an element that proves the content you care about is present, not merely a generic page shell. Keep the navigation and selector timeouts separate: the first bounds navigation, and the second bounds the content wait. Tune both to the site. A visible: true wait is useful when the element must be shown, rather than merely present in the document.

Browserless also documents bestAttempt: true for proceeding with the available page state after some asynchronous operation fails or times out. Use it only when a partial screenshot is acceptable, and make sure downstream code can recognize that the page may not have met the intended condition.

3. Wait with Puppeteer or Playwright connected to Browserless

When you need more control over the page, connect to a Browserless browser session, navigate, wait for the relevant content, capture, and close the session. Browserless’s screenshot example shows the remote-browser workflow.

Puppeteer

import puppeteer from "puppeteer-core";

const browser = await puppeteer.connect({
  browserWSEndpoint: process.env.BROWSERLESS_WS_ENDPOINT,
});

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 1000 });
  await page.goto("https://example.com/", {
    waitUntil: "networkidle2",
    timeout: 30_000,
  });
  await page.waitForSelector("#main-content", {
    visible: true,
    timeout: 10_000,
  });
  await page.screenshot({ path: "screenshot.png", fullPage: true });
} finally {
  await browser.close();
}

Set BROWSERLESS_WS_ENDPOINT to the WebSocket endpoint provided for your Browserless account. Install puppeteer-core in your project. When an element is sufficient, omit fullPage or set it to false; full-page screenshots can take longer and produce larger files.

Playwright

import { chromium } from "playwright";

const browser = await chromium.connectOverCDP(
  process.env.BROWSERLESS_WS_ENDPOINT
);

try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
  await page.goto("https://example.com/", {
    waitUntil: "networkidle",
    timeout: 30_000,
  });
  await page.locator("#main-content").waitFor({
    state: "visible",
    timeout: 10_000,
  });
  await page.screenshot({ path: "screenshot.png", fullPage: true });
} finally {
  await browser.close();
}

Use the connection method and endpoint format supported by your Browserless plan and Playwright setup. Playwright’s networkidle is the corresponding navigation wait shown in Browserless’s example. If the page is known to keep connections open, wait for a page-specific locator instead of making network idleness a requirement.

When a click triggers navigation

Start waiting for navigation before the click so a fast navigation is not missed:

const navigation = page.waitForNavigation({ waitUntil: "domcontentloaded" });
await page.click("a.next-page");
await navigation;
await page.waitForSelector("#next-page-content", { visible: true });

If the action updates the page without navigating, wait for the resulting selector or state instead. A unique destination element can also avoid relying on navigation timing.

4. Use BrowserQL when the capture is a BQL operation

BrowserQL provides a waitForNetworkIdle mutation with idleTime, concurrency, and timeout. Its documented defaults are 500 ms idle time and zero tolerated in-flight requests. If analytics or long-polling traffic prevents strict idleness, increasing concurrency can allow some requests to remain active. Set it based on the page’s behavior, then follow with a page-specific condition when available.

The BQL screenshot mutation has its own capture settings, including fullPage, a CSS selector, timeout, and waitForImages. Its documented screenshot timeout defaults to 30,000 ms, and waitForImages defaults to false. Those capture settings are separate from waiting for navigation or dynamic content to become ready. See Browserless’s documentation for network idle and screenshot.

5. Tune waits for the page

  1. Identify the screenshot’s required content. Choose an element or page state that proves the important content has arrived.
  2. Pick the least restrictive navigation wait that fits. Start with domcontentloaded for initial HTML, load when dependent resources matter, or a network-idle condition for finite client-side requests.
  3. Add a selector or function wait for dynamic content. Use a selector for a known element. Use a function for a condition that depends on multiple values or states.
  4. Give each wait a bounded timeout. Navigation and content waits can fail for different reasons; separate limits make the failure easier to diagnose.
  5. Capture and handle failure deliberately. On timeout, decide whether to fail the job or accept a partial screenshot. Do not label a best-effort capture as fully ready.

For a site with persistent polling, strict networkidle0 may never complete. Try networkidle2, adjust BQL concurrency, or wait for the specific content. For a page where an image must be visible, navigation’s load event or a capture option that waits for images may be relevant; verify that the specific image has loaded if it is essential. A page can satisfy a selector wait while an image below the fold remains lazy-loaded.

6. Troubleshoot common failures

Symptom Likely cause What to change
Navigation wait times out The page has persistent requests, is slow, or failed to load. Use a less strict condition such as networkidle2 or domcontentloaded, then wait for the required content. Raise the timeout only if the page legitimately needs more time.
Selector wait times out The selector is wrong, content never appeared, or the element exists only after an interaction. Inspect the target page and use the selector for the actual ready state. Perform the required click or setup first, and check whether the element is inside a frame.
Screenshot contains a loading shell Navigation completed before the app populated its content. Add a selector or function wait for the loaded content rather than relying only on load.
Screenshot is missing images Images load after the chosen wait, are lazy-loaded, or are outside the viewport. Use a suitable image wait option where supported, scroll or otherwise trigger lazy loading when needed, and verify the required image’s loaded state before capture.
Network idle never happens Polling, analytics, streaming, or other ongoing requests keep the connection count above the threshold. Use networkidle2, adjust BQL concurrency, or use a content selector as the readiness condition.
Screenshot request returns an error after waiting A navigation or precondition timed out, or the page failed before it became ready. Log which wait failed, check the target URL and selector, and decide whether the job should fail or return a clearly marked partial result. Consider bestAttempt only if partial output is acceptable.
Click-triggered navigation is missed The click completed before the navigation listener was registered. Create the navigation wait promise before clicking, then await it after the click.

7. Performance, reliability, and cost

Every wait adds time to the capture, so match it to the screenshot’s purpose. domcontentloaded can return sooner when initial HTML is enough; waiting for all network requests can be slower or never finish on pages with persistent traffic. A selector wait avoids waiting for unrelated activity when the required content has a clear marker. Full-page capture and image loading can add work, so capture only the needed area when possible.

Use explicit timeouts so a stalled page does not occupy a browser session indefinitely. Keep navigation, content, and screenshot timeouts conceptually separate, and record which stage timed out. For reliable jobs, make retries bounded and reserve them for transient failures; repeating a deterministic selector error will not fix it. If a partial result is acceptable, expose that fact to the caller.

Browserless pricing and account limits depend on the plan and are not specified here. Longer waits and retries can consume more browser time, so measure the workflow against your plan rather than assuming every capture has the same cost.

Or skip the browser setup

ScreenshotNeo is a website screenshot API: one GET request returns a PNG, JPEG, WebP, or PDF. Its cookie and consent handling accepts the banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

See the ScreenshotNeo API documentation for request options. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Its MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Does load guarantee that a single-page app is ready?

No. It signals that the load event fired, but client-side code may still be fetching or rendering content. Wait for the specific content the screenshot needs.

Should I always use networkidle0?

No. It is suitable when the page’s requests settle, but persistent traffic can prevent it from completing. Choose a less strict condition or a page-specific wait.

Does a screenshot timeout make the page ready sooner?

No. It limits how long the capture operation can take. Set a readiness condition separately, and handle timeout failures explicitly.

Can I use a fixed delay?

Yes, when a known time-dependent effect needs to settle. For content readiness, a selector or state check gives stronger evidence than waiting an arbitrary number of seconds.

Where can I verify the Browserless option names?

Use Browserless’s official REST configuration, timeout, and screenshot example documentation.