October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Wait for a Page to Load in Puppeteer Before a Screenshot

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Wait for a navigation boundary, then wait for the page state your image actually needs, and only then call page.screenshot(). A reliable baseline is page.goto(url, { waitUntil: 'networkidle2' }), followed by waitForSelector(), waitForFunction(), or a short waitForNetworkIdle() when content arrives after navigation.

The reliable Puppeteer sequence

This complete example waits for the main navigation to settle, verifies a page-specific element, captures a full-page PNG, and closes the browser even when an error occurs.

import puppeteer from 'puppeteer';

const url = 'https://example.com';
const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();
  await page.goto(url, {
    waitUntil: 'networkidle2',
    timeout: 30_000,
  });

  // Replace this selector with an element that proves your page is ready.
  await page.waitForSelector('body', {
    visible: true,
    timeout: 15_000,
  });

  await page.screenshot({
    path: 'screenshot.png',
    fullPage: true,
  });
} finally {
  await browser.close();
}

page.goto() resolves at the navigation boundary you choose. It does not promise that every image, font, animation, or client-side component is visually complete. The second wait should therefore represent the exact state that must appear in the screenshot. Always await page.screenshot(); otherwise your process can finish before the file is written.

Choosing waitUntil for navigation

The waitUntil value tells goto() which navigation event to observe. Pick the least expensive boundary that is sufficient for the page you are capturing.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Condition What it observes Use it when Important limitation
domcontentloaded The document has been parsed. Your screenshot only needs the initial DOM and does not depend on images, fonts, or later resources. Images, web fonts, and client-rendered data may still be missing.
load The browser’s load event. The page treats the load event as its readiness boundary. JavaScript can continue fetching and rendering after the event.
networkidle2 Network activity has reduced to no more than a small number of active connections. Most ordinary pages need their requests to settle. Puppeteer’s screenshot guide uses this condition. Analytics, polling, streaming, or other persistent connections can keep the page changing.
networkidle0 Zero in-flight network connections. Only when the target genuinely becomes completely quiet. Pages with long-lived connections may never reach it and will time out.

For many sites, networkidle2 is a practical first attempt. If the page has a known readiness marker, combine a faster navigation boundary with a selector or predicate instead of waiting indefinitely for global network silence.

Waiting for requests that start after navigation

Single-page applications often begin data requests after goto() has resolved. Add the dedicated network-idle wait after navigation:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com/dashboard', {
    waitUntil: 'domcontentloaded',
    timeout: 30_000,
  });

  await page.waitForNetworkIdle({
    idleTime: 500,
    timeout: 10_000,
  });

  await page.screenshot({ path: 'dashboard.png' });
} finally {
  await browser.close();
}

page.waitForNetworkIdle() resolves after the configured idle period and always waits at least that long. It is a traffic condition, not proof that the visual state you want exists. A page can be network-idle while a component still shows a skeleton, and a page with a WebSocket can remain non-idle forever.

Wait for the element that proves readiness

Use waitForSelector() for a visible component

If the screenshot must contain a report, chart, or confirmation panel, wait for that element directly:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
await page.goto('https://example.com/report', {
  waitUntil: 'domcontentloaded',
  timeout: 30_000,
});

await page.waitForSelector('[data-testid="report-ready"]', {
  visible: true,
  timeout: 15_000,
});

await page.screenshot({ path: 'report.png', fullPage: true });

Choose a selector that is emitted only when the required state is present. A generic body selector merely proves that a document exists; it does not prove that data, images, or a chart has rendered.

Use waitForFunction() for an application predicate

When readiness depends on text, a class, or a JavaScript value, wait for that predicate rather than guessing with a delay:

await page.goto('https://example.com/app', {
  waitUntil: 'domcontentloaded',
  timeout: 30_000,
});

await page.waitForFunction(
  () => document.querySelector('[data-testid="status"]')?.textContent === 'Ready',
  { timeout: 15_000 },
);

await page.screenshot({ path: 'ready.png' });

This approach tracks the state that matters to the image. Keep the predicate deterministic and give it a timeout so a broken application produces an error instead of an endless wait.

Waiting for navigation caused by a click or form

Start the navigation wait before the action. Doing so prevents a fast navigation from occurring before Puppeteer begins listening for it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto('https://example.com/list', {
  waitUntil: 'domcontentloaded',
  timeout: 30_000,
});

await Promise.all([
  page.waitForNavigation({
    waitUntil: 'networkidle2',
    timeout: 30_000,
  }),
  page.click('a.next'),
]);

await page.waitForSelector('[data-testid="results"]', {
  visible: true,
  timeout: 15_000,
});

await page.screenshot({ path: 'next-page.png', fullPage: true });

The same pattern applies to a form submission. If the action updates the current document without a navigation, do not use waitForNavigation(); wait for the resulting selector or predicate instead.

Make the screenshot reflect the loaded page

Full page versus viewport

page.screenshot({ fullPage: true }) captures the document’s full scrollable height. Omit fullPage when you need only the current viewport. Set a viewport before navigation if layout breakpoints matter:

await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'desktop.png', fullPage: true });

Lazy-loaded images

Network idle does not guarantee that content below the fold has been requested. A full-page capture can therefore contain unloaded lazy images. Wait for a page-specific “all images ready” condition when the site provides one, or trigger the site’s normal loading behavior before taking the screenshot. Do not assume a fixed sleep covers every image or font; rendering time varies with the page and environment.

Animations and changing content

A successful wait can still produce different pixels on each run if an animation, rotating banner, clock, or live feed is active. When reproducibility matters, use the application’s stable state or a predicate that identifies it. A timeout controls how long Puppeteer waits; it does not freeze the page after the wait succeeds.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Timeouts and failure handling

Set explicit timeouts for navigation and readiness waits. Catch failures and retain enough context to diagnose whether navigation, the selector, or the screenshot failed.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

try {
  await page.goto('https://example.com/report', {
    waitUntil: 'networkidle2',
    timeout: 30_000,
  });
  await page.waitForSelector('[data-testid="report-ready"]', {
    visible: true,
    timeout: 15_000,
  });
  await page.screenshot({ path: 'report.png', fullPage: true });
} catch (error) {
  console.error('Capture failed:', error);
  // Save a diagnostic viewport when possible.
  try {
    await page.screenshot({ path: 'capture-error.png' });
  } catch (diagnosticError) {
    console.error('Diagnostic screenshot failed:', diagnosticError);
  }
  throw error;
} finally {
  await browser.close();
}

Common symptoms and fixes

  • goto() times out with networkidle0: switch to networkidle2 or domcontentloaded, then wait for a specific selector. Polling and WebSockets can prevent zero in-flight requests.
  • The screenshot shows a loading skeleton: wait for the component’s ready selector or a predicate on its status, not merely load.
  • Images are missing: the page may lazy-load below the fold or fetch images after navigation. Add an application-specific readiness check and allow enough time for those requests.
  • A selector wait expires: verify the selector in the same viewport and state, check whether the element is inside a frame, and confirm that the application really emits that marker on success.
  • A click wait hangs: the click may update the page without navigation. Replace waitForNavigation() with waitForSelector() or waitForFunction().
  • The output file is incomplete or absent: ensure the screenshot call is awaited and that the browser remains open until it resolves.
  • Captures differ between runs: live content or animation is changing. Capture a stable application state and use consistent viewport settings.

Performance and reliability decisions

Every extra wait increases latency, so make each one serve a distinct purpose. A useful sequence is: navigate with domcontentloaded when early work is sufficient, wait for the exact ready selector, and use waitForNetworkIdle() only when late requests themselves are relevant. Conversely, a mostly static marketing page may need only networkidle2.

Keep timeouts bounded and log which boundary failed. A navigation timeout indicates that the main resource or chosen network condition did not complete. A selector timeout indicates that the expected visual state was never observed. These are different operational failures and should be retried or investigated differently. No wait mode guarantees that every resource has finished or that a page is visually complete; the correct boundary depends on the target application.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo returns a screenshot or PDF from one GET request, so you do not have to operate Puppeteer, Chromium, navigation waits, or cleanup code. It can wait for a selector, a delay, or network idle; load lazy images for full-page captures; click an element; run custom JavaScript; and set a cache TTL. Before capture it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets, with each cleanup step configurable. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The API also supports element captures by CSS selector, dark mode, device presets or custom viewports, retina scale, PDF paper and page-range settings, custom CSS, hidden selectors, blocked ads or resource types, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, signed links, asynchronous jobs with signed webhooks, and bulk capture of up to 100 URLs per call. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

cURL

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

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for request parameters and response headers. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account to get started.

FAQ

Is networkidle2 always better than networkidle0?

No. networkidle2 tolerates a small amount of ongoing traffic and is practical for many pages. Use networkidle0 only when zero active connections is realistic for that site.

Can I replace a readiness wait with setTimeout?

You can add a delay, but a selector or application predicate is usually more precise because it ends when the required state exists rather than after an arbitrary number of milliseconds.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does a successful navigation still produce an incomplete image?

Navigation completion is not visual completion. Late API calls, lazy resources, client rendering, and animations can continue after goto(). Add a wait tied to the element or state the screenshot must show.

What happens when the page never reaches the chosen condition?

The operation rejects when its timeout expires. Treat that as a diagnosable failure, record which wait failed, and choose a condition compatible with the page instead of removing timeouts.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.