October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober 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 Fully in Puppeteer

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

Use a condition that matches what your script needs. For ordinary navigation, page.goto(url) waits for Puppeteer’s default load lifecycle event. If your page renders results after that event, wait for the result element or application state explicitly. Network-idle waits are useful when network quiet is itself part of the requirement, but they do not prove that animations, timers, or future application work have ended.

The reliable pattern is therefore: navigate, wait for a task-specific readiness signal, then interact with or capture the page. The examples below target Puppeteer 25.12.0 API behavior documented on 2026-09-29; timeout and lifecycle details can change between releases.

What “fully loaded” means in Puppeteer

There is no single browser milestone that guarantees every page is finished. A lifecycle event describes navigation progress, while an application condition describes whether the content your code needs is ready.

Requirement Recommended wait What it observes
Ordinary navigation page.goto(url) or waitUntil: 'load' The documented default load lifecycle event
DOM parsed before all subresources finish waitUntil: 'domcontentloaded' Document parsing, not application readiness
Network quiet is part of your requirement waitUntil: 'networkidle2' or page.waitForNetworkIdle() A configured period of network idleness
A result, button, or view must exist page.waitForSelector() or a Locator The actual DOM element or interaction state your task needs
A click causes navigation Promise.all([page.waitForNavigation(), page.click(...)]) Navigation registered before the click can trigger it

waitUntil accepts one lifecycle event or an array. When you pass an array, every named event must fire. Puppeteer’s documented default navigation timeout is 30,000 milliseconds; set a task-specific timeout when a slower page is expected.

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

Start with the default navigation wait

For a static page, the shortest correct solution is:

const puppeteer = require('puppeteer');

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

  await page.goto('https://example.com');
  console.log(await page.title());

  await browser.close();
})();

Because no waitUntil option is supplied, Puppeteer uses 'load'. Make the choice explicit when it matters:

await page.goto(url, {
  waitUntil: 'domcontentloaded',
  timeout: 30000
});

Use 'domcontentloaded' when your code only needs the parsed document and can tolerate images or other subresources still loading. Use 'load' when the navigation should include the page’s normal load lifecycle. Neither choice tells you that a client-side framework has finished fetching and rendering its data.

Wait for the content your script actually uses

Wait for a required selector

If a results panel is inserted after navigation, wait for that panel rather than adding an arbitrary sleep:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto('https://example.com/search?q=puppeteer');
await page.waitForSelector('[data-testid="results"]', {
  visible: true,
  timeout: 30000
});
const text = await page.$eval('[data-testid="results"]', el => el.textContent);
console.log(text);

page.waitForSelector() resolves when a matching element is added to the DOM. visible: true additionally requires it to be visible. Its documented default timeout is 30 seconds, and a selector that does not appear before the timeout causes the wait to throw. A hidden wait can resolve with null when the selector is absent, which is useful for optional elements.

Use a Locator for interactions

Puppeteer recommends Locators for selecting and interacting with elements. A Locator waits for the element and the relevant interaction state, so it is generally a better fit for actions such as clicking a button than manually querying and clicking immediately. Use waitForSelector when you need a lower-level presence or visibility check.

const submit = page.locator('button[type="submit"]');
await submit.click();

Wait for an application condition

Sometimes the element exists immediately but changes from “Loading…” to usable content later. In that case, wait for the state your script can verify:

await page.waitForFunction(() => {
  const status = document.querySelector('[data-testid="status"]');
  return status && status.textContent.trim() === 'Ready';
}, { timeout: 30000 });

Keep the predicate specific. A broad condition such as “the body is non-empty” usually becomes true before the application has rendered useful data.

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

Use network-idle waits deliberately

Navigation with networkidle2

Puppeteer’s screenshot guide demonstrates networkidle2 before taking a screenshot:

await page.goto(url, { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'page.png', fullPage: true });

This is appropriate when your task depends on a period of network quiet. It can be a poor universal “fully loaded” switch: analytics, polling, advertisements, sockets, or other long-lived activity may prevent the condition from matching, while a quiet network does not guarantee that an animation or delayed state transition has completed.

Wait for idleness after navigation

You can separate navigation from the network-idle check:

await page.goto(url);
await page.waitForNetworkIdle();

The method resolves after the network is idle for at least the configured idle time. The current options reference documents a default idle time of 500 milliseconds and a default concurrency of zero. Configure those values only when they represent your application’s definition of quiet:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.waitForNetworkIdle({
  idleTime: 1000,
  concurrency: 0,
  timeout: 30000
});

Prefer a selector or application predicate when you know exactly what “ready” means; use network idleness as one part of a synchronization contract, not as proof that all future work has stopped.

Avoid click-and-navigation races

Register the navigation wait before performing a click that can navigate. Waiting afterward can miss a fast navigation and leave your script hanging:

const [response] = await Promise.all([
  page.waitForNavigation({ waitUntil: 'load' }),
  page.click('a.next-page')
]);

if (response) {
  console.log('HTTP status:', response.status());
}
await page.waitForSelector('[data-testid="results"]', { visible: true });

The same pattern applies to form submissions and other interactions that replace the document. If the click updates the current page without navigation, wait for the resulting selector or application state instead.

Combine waits without waiting for irrelevant work

A robust scraper or screenshot script often has two stages: a navigation milestone followed by the exact content check.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const response = await page.goto(url, {
  waitUntil: 'domcontentloaded',
  timeout: 45000
});

await page.waitForSelector('.product-grid article', {
  visible: true,
  timeout: 30000
});

if (response && response.status() >= 400) {
  throw new Error(`Navigation returned HTTP ${response.status()}`);
}

This avoids forcing every page to satisfy a global network-idle rule. For a page where network quiet is independently important, perform waitForNetworkIdle() as an additional, explicit step.

Timeouts, failures, and recovery

Navigation timeout

WaitForOptions documents a 30,000-millisecond default timeout. Increase it for a known slow route, or pass timeout: 0 to disable the timeout when you have an external cancellation strategy. Disabling timeouts without another deadline can leave a worker stuck indefinitely.

await page.goto(url, {
  waitUntil: ['domcontentloaded', 'load'],
  timeout: 60000
});

Selector timeout

A selector timeout usually means the selector is wrong, the page is still on a different route, the element is inside a frame, or the application never reached the expected state. Capture the current URL and a diagnostic screenshot before retrying:

try {
  await page.waitForSelector('[data-testid="results"]', {
    visible: true,
    timeout: 15000
  });
} catch (error) {
  console.error('URL at failure:', page.url());
  await page.screenshot({ path: 'wait-failure.png', fullPage: true });
  throw error;
}

HTTP errors and headless shell behavior

Do not assume that a resolved navigation means the server returned a successful status. Puppeteer’s Page documentation notes that headless shell mode may return without throwing for HTTP errors such as 404 or 500. Inspect the response status when it affects your result, as shown in the combined-wait example.

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

Network-idle never resolves

  • Replace a global network-idle wait with a known result selector if the page polls or keeps a connection open.
  • Use a finite timeout and report the URL, status, and last observed application state.
  • If the page has optional third-party requests, do not make those requests part of your readiness contract.

The selector appears too early

Change the condition from presence to visibility, or wait for a text or attribute value that identifies the completed state. An element can be present while its data, disabled state, or child nodes are still being populated.

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

Performance and reliability practices

  • Use the narrowest condition. A specific selector normally finishes sooner and fails more clearly than waiting for unrelated requests.
  • Keep navigation and content timeouts separate. A slow document load and a missing results element are different failures and should produce different diagnostics.
  • Reuse a browser process carefully. Reusing one launched browser while creating isolated pages reduces startup work, but close pages and browsers on all success and failure paths.
  • Record the observed status. Log the URL, response status, selected wait condition, and elapsed time so a timeout can be reproduced.
  • Do not use fixed delays as a readiness test. A delay may be too short on a slow run and wasteful on a fast one; an observed selector or application state expresses the actual requirement.

Complete example: load, wait, verify, and capture

This script uses a lifecycle wait for navigation, a visible selector for application readiness, and explicit status checking before capture:

const puppeteer = require('puppeteer');

async function capture(url) {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    const response = await page.goto(url, {
      waitUntil: 'load',
      timeout: 30000
    });

    if (response && response.status() >= 400) {
      throw new Error(`Navigation failed with HTTP ${response.status()}`);
    }

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

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

capture('https://example.com').catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Replace [data-testid="page-ready"] with a selector that your page sets only when the content needed by the capture or extraction task is usable. If there is no such marker, use a stable result element or an application predicate instead.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server when you do not need to manage Chromium, waits, and cleanup yourself. Before the capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and 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.

One-call cURL request

See the parameter reference in the ScreenshotNeo documentation.

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}`);

ScreenshotNeo also offers full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets and custom viewports, retina scale, PDF output, custom CSS and JavaScript, click-before-capture, selector or delay waits, network-idle waits, request and resource blocking, headers, cookies, user agents, Authorization, timezone and geolocation controls, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.

Frequently Asked Questions

Is a fixed sleep ever equivalent to waiting for readiness?

No. A fixed delay observes elapsed time, not the page’s state. It can finish before a slow response arrives or delay every fast run. Prefer a selector, an application predicate, or a deliberately configured network-idle condition.

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

Can I require both a lifecycle event and a content marker?

Yes. Pass the lifecycle condition to page.goto(), then call page.waitForSelector() or page.waitForFunction() for the marker. This gives navigation and application readiness separate, diagnosable 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.