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

Use the wait that represents the condition your next operation needs. For a click that changes the current URL, register page.waitForNavigation() at the same time as the click. For content, wait for a selector or an in-page predicate. For a request, response, or quiet network, wait for that event explicitly. For a new tab, wait for a browser target. Use a fixed timer only when elapsed time itself is the requirement.

Every Puppeteer wait consumes part of the Firebase function’s execution budget. Configure the function timeout for the trigger you use, and keep each individual wait finite so a failed page cannot hold a function until the outer limit.

What “wait” means in Puppeteer

“Wait for the page” is ambiguous. Puppeteer can wait for several different events, and they do not prove the same thing.

Need Use What it proves Typical failure
The current document navigated page.waitForNavigation() A navigation event completed according to the selected lifecycle condition. Timeout when the click did not navigate, navigation was blocked, or the page is a single-page app.
An element exists or is visible page.waitForSelector() The selector matched; visibility options can require that it is visible. Timeout because the selector is wrong, content is inside an iframe, or rendering failed.
An application state is true page.waitForFunction() A page-context function returned a truthy value. Timeout because the condition never becomes true or references unavailable page variables.
A request or response occurred Request/response waiters The specified network event happened, not that every UI update finished. Wrong URL predicate, method, status, or request fired before the waiter was installed.
Network activity became quiet page.waitForNetworkIdle() Network stayed below the configured concurrency for at least the configured idle period. Analytics, polling, streaming, or ads prevent idleness; an idle network still may not mean the UI is ready.
A popup or new tab opened browserContext().waitForTarget() A new browser target matching your predicate exists. The click opened no target, the predicate is too broad, or the target is not yet associated with a page.
A real elapsed interval new Promise(resolve => setTimeout(resolve, ms)) Only that the timer elapsed. It wakes before the page is ready or wastes the function budget.

The current Puppeteer documentation marks Page.waitForTimeout obsolete and recommends a native timer only when a fixed delay is genuinely required. A state-based wait is normally both faster and more reliable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
AMD RYZEN 7 9800X3D 8-Core, 16-Thread Desktop Processor
  • The world’s fastest gaming processor, built on AMD ‘Zen5’ technology and Next Gen 3D V-Cache.
  • 8 cores and 16 threads, delivering +~16% IPC uplift and great power efficiency
  • 96MB L3 cache with better thermal performance vs. previous gen and allowing higher clock speeds, up to 5.2GHz
  • Drop-in ready for proven Socket AM5 infrastructure
  • Cooler not included

Waiting after a click that navigates

Start the navigation wait and the action together. Waiting for the click first can lose a fast navigation before the separate waiter is registered.

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

if (!response) {
  throw new Error('The action did not produce a navigation response');
}

domcontentloaded means the document was parsed; it does not guarantee that images, client-side data, or a loading spinner are finished. If the next operation needs rendered application content, follow navigation with a condition for that content:

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

Some links open a new target rather than navigating the current page. Use the popup pattern below instead of waiting on the original page.

Waiting for a selector or visible content

Use waitForSelector(selector, options) when the DOM element itself is the contract between steps. The documented default timeout for selector waits is 30 seconds; set a shorter value when a function should fail quickly or a longer value when the page is known to be slow.

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 submit = await page.waitForSelector('button[type="submit"]', {
  visible: true,
  timeout: 15_000,
});
await submit.click();

A successful selector wait does not mean the element is enabled, stable, or backed by loaded data. Check application-specific attributes when needed:

Rank #2
AMD Ryzen 9 9950X3D 16-Core Processor
  • AMD Ryzen 9 9950X3D Gaming and Content Creation Processor
  • Max. Boost Clock : Up to 5.7 GHz; Base Clock: 4.3 GHz
  • Form Factor: Desktops , Boxed Processor
  • Architecture: Zen 5; Former Codename: Granite Ridge AM5
await page.waitForSelector('[aria-busy="false"]', { timeout: 10_000 });
await page.waitForFunction(
  () => document.querySelector('#results')?.children.length > 0,
  { timeout: 10_000 }
);

If the element is inside an iframe, obtain the frame and wait there. If it is created by a shadow DOM component, use a page predicate that traverses the relevant shadow root.

Waiting for an in-page condition

page.waitForFunction() is appropriate when no single selector describes readiness: a JavaScript variable changes, a counter reaches a value, or a framework sets a state flag.

await page.waitForFunction(
  () => window.appState?.report?.status === 'complete',
  { timeout: 20_000, polling: 'mutation' }
);

The function runs in the page context, so it can access window and the document but not ordinary variables in your Firebase process unless you pass them as arguments. Keep the predicate cheap and deterministic; a predicate that throws or never becomes true ends in a timeout.

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

Waiting for requests and network idle

Wait for the exact request or response when that is the meaningful event. Install the waiter before the action that triggers it.

const responsePromise = page.waitForResponse(
  response => response.url().endsWith('/api/report') &&
    response.request().method() === 'GET' &&
    response.status() === 200,
  { timeout: 20_000 }
);

await page.click('#load-report');
const response = await responsePromise;

Use waitForNetworkIdle when you specifically need a quiet period, for example before taking a screenshot of a page whose resources load in bursts:

Rank #3
Sale
AMD Ryzen 5 5500 6-Core, 12-Thread Unlocked Desktop Processor with Wraith Stealth Cooler
  • Can deliver fast 100 plus FPS performance in the world's most popular games, discrete graphics card required
  • 6 Cores and 12 processing threads, bundled with the AMD Wraith Stealth cooler
  • 4.2 GHz Max Boost, unlocked for overclocking, 19 MB cache, DDR4-3200 support
  • For the advanced Socket AM4 platform
await page.waitForNetworkIdle({
  idleTime: 500,
  concurrency: 0,
  timeout: 20_000,
});

Network idle is not proof that a single-page application has finished updating. Polling, WebSockets, analytics, and long-lived requests can prevent idle; conversely, the network can be quiet while a client-side render is still pending. Combine a network wait with a selector or page predicate when both conditions matter.

Waiting for a popup or new tab

A call to window.open creates a new browser target. Waiting for navigation on the original page will not give you the popup. Match the expected URL (and, if useful, the opener) with BrowserContext.waitForTarget:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const context = page.browserContext();
const targetPromise = context.waitForTarget(
  target => target.url().startsWith('https://example.com/result'),
  { timeout: 20_000 }
);

await page.click('button.open-result');
const target = await targetPromise;
const popup = await target.page();
if (!popup) throw new Error('The target has no page');

await popup.waitForSelector('#result', { timeout: 10_000 });

Use a precise predicate. A predicate that only checks the target type can resolve the first unrelated tab, especially in a reused browser context.

Using a fixed delay safely

Sometimes time itself is required: allowing an animation to finish, spacing requests to an external service, or reproducing a scheduled interaction. In current Puppeteer documentation, Page.waitForTimeout is obsolete. Use a native timer and keep the delay bounded:

await new Promise(resolve => setTimeout(resolve, 1_000));

Do not replace a missing readiness condition with “sleep five seconds.” A slow response can exceed the guess, while a fast response leaves unnecessary latency. Prefer a selector, predicate, request, or network condition and reserve timers for behavior that is intentionally time-based.

Rank #4
Sale
AMD Ryzen™ 5 9600X 6-Core, 12-Thread Unlocked Desktop Processor
  • Pure gaming performance with smooth 100+ FPS in the world's most popular games
  • 6 Cores and 12 processing threads, based on AMD "Zen 5" architecture
  • 5.4 GHz Max Boost, unlocked for overclocking, 38 MB cache, DDR5-5600 support
  • For the state-of-the-art Socket AM5 platform, can support PCIe 5.0 on select motherboards
  • Cooler not included

A Firebase Functions example with explicit waits

The following HTTP function shows launch, navigation, content readiness, and cleanup. Adapt browser installation and launch options to the exact Puppeteer package and Firebase runtime you deploy. Puppeteer guarantees compatibility with its bundled browser; using an unrelated executable path is at your own risk.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const { onRequest } = require('firebase-functions/v2/https');
const puppeteer = require('puppeteer');

exports.capture = onRequest(
  { timeoutSeconds: 120, memory: '1GiB' },
  async (req, res) => {
    let browser;
    try {
      const url = String(req.query.url || 'https://example.com');
      browser = await puppeteer.launch({ headless: true });
      const page = await browser.newPage();
      page.setDefaultTimeout(15_000);
      page.setDefaultNavigationTimeout(30_000);

      await page.goto(url, {
        waitUntil: 'domcontentloaded',
        timeout: 30_000,
      });
      await page.waitForSelector('main', {
        visible: true,
        timeout: 15_000,
      });

      const title = await page.title();
      res.status(200).json({ title, url: page.url() });
    } catch (error) {
      console.error(error);
      res.status(504).json({ error: 'Page was not ready before the wait timed out' });
    } finally {
      if (browser) await browser.close();
    }
  }
);

Set the function’s timeout above the longest legitimate browser workflow, but do not use the maximum merely because it is available. A browser that hangs should fail at its own navigation or condition timeout, leaving time for error handling and response delivery.

How long can a Puppeteer wait run in Firebase Functions?

The function timeout is the outer ceiling for all work: startup, browser launch, navigation, waits, processing, and cleanup. Firebase currently documents different maximum durations by trigger:

Firebase trigger Documented maximum
HTTP and callable 3,600 seconds
Scheduled and task queue 1,800 seconds
Other event-driven functions 540 seconds

These are platform limits, not recommended wait values. Configure timeoutSeconds in runtime options for the function type you actually use. Firebase treats runtime options in source as the source of truth by default; console or CLI changes can be overridden unless you deliberately use the documented external-change preservation behavior. Verify the current limits for your region, generation, and trigger when deploying.

A practical budget includes a navigation timeout, a content timeout, and a small margin for logging and browser shutdown. For example, a 120-second HTTP function might use a 30-second navigation timeout and 15-second selector timeout, with an explicit overall policy to reject unusually slow pages.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Sale
AMD Ryzen 7 7800X3D 8-Core, 16-Thread Desktop Processor
  • Processor provides dependable and fast execution of tasks with maximum efficiency.Graphics Frequency : 2200 MHZ.Number of CPU Cores : 8. Maximum Operating Temperature (Tjmax) : 89°C.
  • Ryzen 7 product line processor for better usability and increased efficiency
  • 5 nm process technology for reliable performance with maximum productivity
  • Octa-core (8 Core) processor core allows multitasking with great reliability and fast processing speed
  • 8 MB L2 plus 96 MB L3 cache memory provides excellent hit rate in short access time enabling improved system performance
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common timeout and deployment failures

“Navigation timeout exceeded”

  • Cause: the URL is slow, blocked, redirecting indefinitely, or the selected lifecycle event never occurs.
  • Fix: verify the URL from the deployed region, set a finite navigation timeout, choose the lifecycle event that matches your need, and add a selector or predicate for application readiness.

The click happened but the wait timed out

  • Cause: the click did not navigate, or the waiter was registered after the click.
  • Fix: use Promise.all with waitForNavigation and click. If it is a single-page update, wait for the resulting selector, request, or page state instead.

The selector never appears

  • Cause: wrong selector, iframe or shadow DOM, authentication failure, or a page that rendered an error state.
  • Fix: capture the final URL and HTML, inspect frames, check response status, and wait for an error selector as a separate branch.

Network idle never arrives

  • Cause: polling, WebSockets, analytics, downloads, or advertisements keep requests active.
  • Fix: wait for the specific response and visible content instead; if idle is still required, tune its concurrency and idle time and retain a hard timeout.

The popup wait resolves incorrectly

  • Cause: a broad target predicate matched an existing tab or an unrelated popup.
  • Fix: match the expected URL prefix, target type, and opener where possible, and create the promise before the click.

The function is killed before Puppeteer finishes

  • Cause: the configured Firebase timeout is shorter than the browser workflow, or the trigger has a lower platform maximum.
  • Fix: set an appropriate timeoutSeconds, shorten individual waits, close the browser in finally, and account for cold-start and cleanup time.

Browser launch fails after deployment

  • Cause: the deployed package cannot find a compatible browser or an executable path was copied from another environment.
  • Fix: use the browser bundled and supported by your installed Puppeteer setup, follow the runtime’s packaging requirements, and avoid assuming one Chromium recipe works for every Firebase generation.

Performance and reliability checklist

  • Register event waits before the action that causes them.
  • Use the narrowest condition that proves the next step is safe.
  • Set explicit navigation, selector, request, and predicate timeouts.
  • Keep a separate outer function budget with margin for cleanup.
  • Log the URL, wait type, elapsed time, and timeout cause without logging secrets.
  • Close pages and browsers in finally, including error paths.
  • Reuse a browser only when your isolation and concurrency design permits it; otherwise launch per invocation and accept the cold-start cost.
  • Treat retries carefully: a retry can duplicate a click or external request unless the operation is idempotent.

Or skip the browser setup

If your goal is a reliable website image or PDF rather than browser automation, ScreenshotNeo provides a GET endpoint and an MCP server. It accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be disabled individually. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

One call returns PNG, JPEG, WebP, or PDF. The service supports full-page lazy-image loading, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and page options, custom CSS and JavaScript, pre-capture clicks, selector or network-idle waits, blocking rules, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification.

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 API documentation for parameters. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. 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 included on every plan. Create a free ScreenshotNeo account.

Frequently Asked Questions

Should I use waitUntil: 'networkidle0' in goto?

Only when network quiet is the condition you need. Many sites keep analytics, polling, or sockets open, so a specific response plus a readiness selector is often more dependable.

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

What timeout should I choose for waitForSelector?

The documented default is 30 seconds. Choose a value based on your page’s normal latency and the Firebase function budget, then keep a separate outer function timeout.

Can a navigation wait detect a single-page-app route change?

Not necessarily. If the URL changes without a document navigation, wait for the route’s distinctive selector, application state, or API response.

Quick Recap

SaleBestseller No. 1
AMD RYZEN 7 9800X3D 8-Core, 16-Thread Desktop Processor
AMD RYZEN 7 9800X3D 8-Core, 16-Thread Desktop Processor
8 cores and 16 threads, delivering +~16% IPC uplift and great power efficiency; Drop-in ready for proven Socket AM5 infrastructure
$443.00
Bestseller No. 2
AMD Ryzen 9 9950X3D 16-Core Processor
AMD Ryzen 9 9950X3D 16-Core Processor
AMD Ryzen 9 9950X3D Gaming and Content Creation Processor; Max. Boost Clock : Up to 5.7 GHz; Base Clock: 4.3 GHz
$669.99
SaleBestseller No. 3
AMD Ryzen 5 5500 6-Core, 12-Thread Unlocked Desktop Processor with Wraith Stealth Cooler
AMD Ryzen 5 5500 6-Core, 12-Thread Unlocked Desktop Processor with Wraith Stealth Cooler
6 Cores and 12 processing threads, bundled with the AMD Wraith Stealth cooler; 4.2 GHz Max Boost, unlocked for overclocking, 19 MB cache, DDR4-3200 support
$87.95
SaleBestseller No. 4
AMD Ryzen™ 5 9600X 6-Core, 12-Thread Unlocked Desktop Processor
AMD Ryzen™ 5 9600X 6-Core, 12-Thread Unlocked Desktop Processor
Pure gaming performance with smooth 100+ FPS in the world's most popular games; 6 Cores and 12 processing threads, based on AMD "Zen 5" architecture
$174.95
SaleBestseller No. 5
AMD Ryzen 7 7800X3D 8-Core, 16-Thread Desktop Processor
AMD Ryzen 7 7800X3D 8-Core, 16-Thread Desktop Processor
Ryzen 7 product line processor for better usability and increased efficiency; 5 nm process technology for reliable performance with maximum productivity
$348.00

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.