ScreenshotNeo

BlogGuides

Puppeteer waitForFunction Options Explained

Learn how Puppeteer waitForFunction works, how to choose polling and timeout options, pass arguments, cancel waits, and handle common failures.

By the ScreenshotNeo team4 October 20268 min read

page.waitForFunction(pageFunction, options?, ...args) repeatedly evaluates a function in the browser page context until it returns a truthy value, then resolves with a handle for that result. Use it when page readiness depends on a condition—such as a global value, rendered text, or computed style—rather than just the presence of an element.

The options object comes before arguments passed to the page function. The documented polling choices are 'raf' (the default), 'mutation', or a number of milliseconds. The documented default timeout is 30 seconds; set it to 0 to disable that limit, or use an AbortSignal to cancel a pending wait. See the official Page.waitForFunction API and FrameWaitForFunctionOptions for the version installed in your project.

1. Basic usage and argument order

The first argument is the predicate evaluated in the page. The optional second argument is the options object; values after it are serialized as arguments to the predicate. If there are no options to set but you do need to pass a predicate argument, pass an empty object as the second argument.

const selector = '.foo';
await page.waitForFunction(
  selector => Boolean(document.querySelector(selector)),
  {},
  selector,
);

The function runs in the page context, so it can inspect browser globals and the DOM. It should return a truthy value when the desired condition is met. The resolved value is a handle, which you can read with jsonValue() when needed:

const resultHandle = await page.waitForFunction(
  () => document.querySelector('[data-ready="true"]')?.textContent || false,
  { timeout: 10_000 },
);
const value = await resultHandle.jsonValue();
console.log(value);
await resultHandle.dispose();

2. The polling option

polling controls when Puppeteer reevaluates the predicate. Choose based on what changes the condition you are watching; the documentation does not establish a universally best mode or provide comparative performance benchmarks.

Value Evaluation trigger Use when
'raf' (default) Animation-frame callbacks The condition may change with rendering or styling, or you want the documented default behavior.
'mutation' DOM mutations The condition is tied to changes in the document tree.
Number in milliseconds Fixed interval A regular polling cadence describes the condition better than frame or mutation triggers.
// Check on animation frames (the default).
await page.waitForFunction(() => window.appReady === true);

// Recheck when the DOM changes.
await page.waitForFunction(
  () => document.querySelectorAll('.result').length >= 5,
  { polling: 'mutation' },
);

// Check every 250 milliseconds.
await page.waitForFunction(
  () => window.jobStatus === 'complete',
  { polling: 250, timeout: 20_000 },
);

Polling is not a substitute for defining the right condition. If the predicate only checks that an element exists, it can resolve before that element has useful content. Check the state your next step actually needs, such as non-empty text or a specific application state.

3. Timeout and cancellation

The documented default timeout is 30000 milliseconds. You can set a per-wait limit or configure the page’s default timeout using Page.setDefaultTimeout(). A timeout of 0 disables the wait’s timeout; only do that when the surrounding operation has another reliable way to stop.

// Per-wait timeout.
await page.waitForFunction(
  () => window.reportReady === true,
  { timeout: 8_000 },
);

// Disable this wait's timeout. Ensure the caller has a cancellation path.
await page.waitForFunction(
  () => window.longRunningTaskDone === true,
  { timeout: 0 },
);

An AbortSignal can cancel a pending wait. This is useful when a task is abandoned or its caller is shutting down. The following example uses standard Node.js abort-controller APIs and passes the signal in the options object:

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5_000);

try {
  await page.waitForFunction(
    () => window.operationDone === true,
    { timeout: 10_000, signal: controller.signal },
  );
} finally {
  clearTimeout(timer);
}

Keep the external cancellation timer shorter than the wait timeout if you want cancellation to win first. Handle the resulting rejection at the level that owns the task lifecycle.

4. Async predicates and values

The predicate may be asynchronous. Puppeteer waits for its promise to settle and checks the returned value for truthiness. Async predicates can be useful when the condition itself requires asynchronous page-context work, but each evaluation should finish promptly and avoid starting duplicate side effects.

await page.waitForFunction(async () => {
  const response = await fetch('/status');
  if (!response.ok) return false;
  const status = await response.json();
  return status.ready === true;
}, { polling: 500, timeout: 15_000 });

This example makes repeated requests while polling. In a real page, prefer observing an existing state or make the check safe to repeat; otherwise the wait can add load or trigger unintended work. Puppeteer’s documentation demonstrates an async page function, but does not claim that this pattern is a performance recommendation.

5. Complete Node.js example

This runnable example launches Chromium, opens a page, waits for a DOM condition, reads the returned value, and closes the browser even if the wait fails. Install Puppeteer in the project first with npm install puppeteer.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const handle = await page.waitForFunction(
      () => {
        const heading = document.querySelector('h1');
        return heading && heading.textContent.trim();
      },
      { polling: 'mutation', timeout: 10_000 },
    );

    console.log('Heading:', await handle.jsonValue());
    await handle.dispose();
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error('Page condition did not complete:', error);
  process.exitCode = 1;
});

waitForFunction resolves only when its own predicate is truthy. Navigation readiness options and this page condition represent different milestones: choose the condition that tells your workflow it can proceed.

6. Choosing options in practice

  • Use the default 'raf' when you want the documented default or are observing a value that can change during rendering.
  • Use 'mutation' when changes to the DOM are the event that can make the predicate true. It may not be a useful trigger for a condition that changes without DOM mutation.
  • Use a numeric interval when you want a fixed cadence. The interval is expressed in milliseconds; choose it to suit the condition and acceptable wait behavior, not based on an undocumented speed claim.
  • Set a bounded timeout for normal automation so a missing condition does not wait forever. Increase it only when the page legitimately needs more time.
  • Use a signal when the caller may cancel the task before its timeout.
  • Pass arguments explicitly after the options object instead of relying on values from the Node.js closure; the predicate executes in the page context.

7. Common errors and fixes

Symptom Likely cause Fix
The wait times out although the page looks ready. The predicate checks a different state than the one visible to you, or the state never becomes truthy. Log or inspect the exact page-side condition, and check the needed text/value rather than a loosely related marker.
The predicate receives undefined or the wrong value. The arguments shifted because the options position was omitted or misunderstood. Use page.waitForFunction(fn, {}, value) when passing arguments without other options.
The wait resolves too early. The predicate returns a truthy value before the page is actually usable, such as an element object whose content is still empty. Wait for the required content or state, for example non-empty text or a specific status value.
A mutation-polled wait never notices the change. The condition changes without a DOM mutation. Use 'raf' or a numeric interval if that trigger matches the condition.
The wait continues after the caller no longer needs it. No cancellation signal is supplied and the timeout has not expired. Pass an AbortSignal and abort it when the task is cancelled.
The wait runs indefinitely. The timeout was set to 0 or disabled globally, and the condition never becomes true. Restore a finite timeout or ensure an external cancellation path always runs.

8. Performance, reliability, and cost

waitForFunction performs page-context evaluations until success, cancellation, or timeout. Pick a polling trigger that corresponds to the state change and keep the predicate small and safe to repeat. Async predicates that perform network requests or other work can repeat that work across evaluations. The official option reference explains the triggers and default timeout but supplies no comparative benchmark, so do not assume one polling mode is universally faster.

For reliability, wait for the exact condition needed by the next step, keep the timeout finite, and connect cancellation to the caller’s lifecycle. On failure, report the condition and timeout in your automation logs; a timeout means the predicate did not become truthy within the configured limit, not necessarily that the browser failed to load.

Puppeteer’s cited API documentation does not specify a price for this method. Operational cost depends on the browser and infrastructure running the automation; this dossier provides no figures to quantify it.

9. FAQ

Does waitForFunction return a Boolean?

It returns a promise for a handle corresponding to the awaited value returned by the predicate. If you need the value in Node.js, read the handle with jsonValue().

Can I use a string instead of a function?

The documented signature accepts a function or string. A function is usually easier to maintain when the condition is more than a trivial expression.

Can I use waitForFunction to check a CSS selector?

Yes. A page function can query the document, or you can use a selector-specific wait when the condition is simply that a selector appears. Use a function when you need a richer condition than selector presence.

Which polling mode is fastest?

The cited documentation gives polling triggers, not a measured ranking. Choose based on how the condition changes.

10. Or skip the browser setup

If the goal is a screenshot rather than custom browser-side automation, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF; its options cover waits such as a selector, delay, or network idle. Puppeteer remains useful when you need a custom predicate like waitForFunction.

Use the ScreenshotNeo API documentation for the full request options. Here is the one-call cURL example:

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

Equivalent 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)

Equivalent 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}`);
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, and failed loads are never billed, and response headers identify the page verdict and billing outcome. Cache hits also cost nothing.
  • An MCP server lets AI agents, including Claude and Cursor, take screenshots with ScreenshotNeo tools.
  • 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.

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