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

There is no single Puppeteer wait that proves all JavaScript on a page has finished. Wait for the specific completion signal your task needs: the Promise you control, an application-ready predicate, a rendered element, a navigation, or—only when appropriate—a quiet network. For a screenshot, tie the wait to the state that makes the page visually ready rather than adding an arbitrary delay.

Choose the wait that matches the work

“JavaScript finished” is usually too broad to be useful. A page may run timers, analytics, polling, or other scripts indefinitely. Instead, define what must be true before the next step: a Promise has resolved, the application has set a ready flag, a result element is visible, navigation has completed, or relevant network activity has stopped.

Wait What it observes Best use Important limitation
page.evaluate() with an async function Completion of the returned Promise Waiting for an async function or page operation you control It waits only for the Promise returned by the evaluated function, not every script on the page.
page.waitForFunction() A page-context predicate becoming truthy Application readiness flags or a specific data/DOM condition The predicate must eventually become truthy or the wait times out.
page.waitForSelector() A selector appearing, optionally visible A particular rendered element signals readiness It does not automatically retry a later action that fails.
Locator Element presence and readiness for an action Clicking or interacting with an element while Puppeteer checks its state It is action-oriented; choose a separate signal if you need to wait for application data.
page.waitForNetworkIdle() Network activity quiet for the configured idle period Pages where network quiescence genuinely indicates readiness Quiet network activity does not prove JavaScript or rendering is complete.
page.waitForNavigation() A navigation or reload Actions expected to navigate Start the wait before triggering the action so the event is not missed.

The Puppeteer API documents that page.evaluate() waits when its page function returns a Promise, and that waitForFunction() resolves when its function returns a truthy value. See the Page.evaluate API and Frame.waitForFunction API.

Wait for an async operation you control

If the page exposes an async function that represents the work you need, return or await that Promise inside page.evaluate(). Puppeteer waits for the returned Promise to settle before continuing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const result = await page.evaluate(async () => {
  await window.loadUserData();
  return window.userData;
});

console.log(result);

This is the most direct choice when your code owns the operation. It does not wait for unrelated analytics, animations, timers, or other scripts that happen to remain active.

Make errors and timeouts visible

A Promise that rejects will cause the evaluation to fail rather than silently count as completion. If the page operation can hang, make sure the operation itself has an appropriate timeout or cancellation strategy; an unbounded wait leaves the automation job stuck. Avoid replacing a meaningful completion signal with a fixed sleep: a delay may be too short on a slow run and waste time on a fast one.

Wait for an application-defined readiness condition

When the site exposes a flag or a condition you can identify, use page.waitForFunction(). The function runs in the page context and resolves when it returns a truthy value. Its options include polling, timeout, signal, and arguments.

await page.waitForFunction(
  () => window.appReady === true,
  { timeout: 15_000, polling: 'mutation' }
);

Replace window.appReady with a condition that actually corresponds to the result you need—for example, a populated object or a DOM state. A predicate that never becomes truthy will time out. Use a finite timeout so failures identify a missing readiness condition instead of hanging indefinitely. The API reference documents the available wait options: Frame.waitForFunction.

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

Wait for a rendered element or use a locator

If a particular element is the useful signal, wait for it directly. waitForSelector() returns immediately when the selector already exists; otherwise it waits for it to appear or for the timeout to be reached.

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

// Use the element, then release the handle when finished.
const text = await results.evaluate(element => element.textContent);
await results.dispose();
console.log(text);

The explicit disposal matters when using the low-level element handle returned by waitForSelector(). If you only need to perform an action, a locator is often more convenient because locators automatically wait for an element to be present and in the right state for that action.

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

A selector wait and a locator solve related but different problems: the selector wait gives you a lower-level readiness point or handle; a locator wraps an action with readiness checks. A selector wait alone does not make a later click retry automatically if the element becomes unusable. See the Puppeteer page interactions guide and Page.waitForSelector API.

Use network idle only when network quiet means ready

page.waitForNetworkIdle() is useful when the site’s network behavior matches the condition you need. It resolves after network activity has been idle for the configured period and always waits at least the configured idleTime.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForNetworkIdle({ idleTime: 500, timeout: 15_000 });

This is not a universal “JavaScript is done” signal. A page can finish its requests before client-side rendering is complete, or keep making requests after the content you need is already ready. Use an application predicate or element signal when those more closely represent readiness. The behavior is described in the Page.waitForNetworkIdle API.

Coordinate actions that trigger navigation

For a click or other action expected to navigate, create the navigation wait before starting the action. Waiting afterward risks missing a fast navigation.

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

waitForNavigation() waits for navigation or reload; it is not a substitute for waiting for client-rendered content after the new document loads. If the next page populates asynchronously, follow navigation with the relevant selector, predicate, or other readiness signal. See the Page.waitForNavigation API.

For screenshots, wait for the visual state you need

A screenshot taken before content appears is usually a synchronization problem: the capture ran after document loading but before the application reached the visual state you wanted. First identify a stable signal—such as a visible results container or a page readiness flag—and await that signal before capturing. If the page’s behavior makes network idle a reliable proxy, it can be part of the wait, but do not treat it as proof that all JavaScript finished.

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

Use a finite timeout and make the failed condition observable. A timeout waiting for [data-testid="results"] points toward a missing element or a changed selector; a timeout waiting for window.appReady points toward the application condition not being met. This is more useful than increasing an arbitrary sleep without knowing what is late.

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

Or skip the browser setup

For a screenshot without running Puppeteer yourself, ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. Its clean-shot flow accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. See ScreenshotNeo and its API documentation.

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

Replace the URL with the page you want and provide your API key. Sign up for 1,000 free screenshots a month, with no card required.

Troubleshoot waits that hang or finish too early

  • The wait times out: Check that the predicate can become truthy or that the selector still matches the page. Keep a finite timeout and report which condition was missing.
  • The screenshot is blank or incomplete: A navigation event or document load may have finished before client rendering. Wait for the page’s actual content element or readiness predicate before capture.
  • Network idle never arrives: Ongoing requests may mean network quiet is not an appropriate completion signal for that page. Wait on a specific application condition instead.
  • A selector wait succeeds but the click fails: The element may not be in the right state at action time. Use a locator for the action so Puppeteer can perform its readiness checks.
  • A navigation wait is missed: Start waitForNavigation() and the click together with Promise.all(), with the wait created before the action begins.
  • An element handle accumulates: Dispose of a handle returned by waitForSelector() when done; use a locator when handle-level access is unnecessary.

Set practical timeout and reliability boundaries

Do not wait for every possible script to stop. Define the smallest observable condition that guarantees the next operation can succeed, then use the matching API. Prefer a Promise for work you own, a predicate for application state, an element for visible output, and navigation waits for document transitions. Treat network idle as a proxy only when the site’s request pattern makes it meaningful.

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

Timeout values are configuration choices, not performance guarantees. The examples use 15 seconds as an explicit ceiling; tune that ceiling to the page and job’s requirements, and investigate repeated timeouts rather than masking them with longer waits. The cited API references document wait behavior and options; verify any defaults against the Puppeteer version installed in your project.

Frequently Asked Questions

Does `page.goto()` mean page JavaScript has finished?

No. A navigation or document-load milestone does not establish that application-specific asynchronous rendering is complete. Wait for the relevant app predicate or rendered element.

Can Puppeteer wait until every JavaScript task on a page stops?

There may be ongoing timers, polling, analytics, or other work with no meaningful final state. Define readiness by the outcome your next step needs instead.

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.

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