How to Configure Puppeteer waitForSelector Options
Configure Puppeteer waitForSelector for presence, visibility, disappearance, timeouts, and cancellation, with runnable examples and fixes for common failures.
page.waitForSelector(selector, options) waits for a matching element and returns an ElementHandle. Configure visible: true to wait for an element that exists and is visible, hidden: true to wait until it is absent or hidden, timeout to set the maximum wait in milliseconds, and signal to cancel the wait. If the selector already matches when the call starts, Puppeteer returns immediately. The documented default timeout is 30 seconds.
1. Install Puppeteer and run a complete example
This example launches Chromium, navigates to a page, waits for an image to exist and be visible, and cleans up both the handle and browser. Install Puppeteer with npm install puppeteer; its package includes a compatible browser download.
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 image = await page.waitForSelector('img', {
visible: true,
timeout: 10_000,
});
if (!image) {
throw new Error('Image was not found');
}
console.log('A visible image is present');
await image.dispose();
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The hidden: true case may resolve to null when the element is absent, so code that waits for disappearance should not assume it receives an element handle.
2. Understand the options
The options object is optional. The selector may be a CSS selector or Puppeteer selector syntax. See the official Page.waitForSelector reference and WaitForSelectorOptions reference.
| Option | Meaning | Default and guidance |
|---|---|---|
visible |
Require the matching element to exist and be visible. Puppeteer documents this visibility check in terms of display not being none and visibility not being hidden. |
false. Set true when the next step requires the element to be visible. |
hidden |
Wait for the selector to be absent from the DOM or for the matching element to be hidden under the documented CSS checks. | false. On success because the element is absent, the result is null. |
timeout |
Maximum wait in milliseconds. | 30,000 ms (30 seconds). Use 0 to disable the timeout; use that only when an unbounded wait is intended. |
signal |
An AbortSignal that cancels the wait. |
Unset unless cancellation is needed. |
Do not combine visible: true and hidden: true as if they were two stages of one wait. Choose the condition the current step needs: presence, visible presence, or disappearance/hidden state.
3. Wait for presence, visibility, or disappearance
Wait for a matching element to exist
With the default options, Puppeteer waits for a match; it does not require the element to be visible. If it already exists when called, the promise resolves immediately.
const button = await page.waitForSelector('button.submit', { timeout: 8_000 });
try {
// Use button here.
} finally {
await button?.dispose();
}
Wait for an element to be visible
const dialog = await page.waitForSelector('[role="dialog"]', {
visible: true,
timeout: 12_000,
});
try {
// The element exists and passes Puppeteer's documented CSS visibility checks.
} finally {
await dialog?.dispose();
}
This check is not a general guarantee that an element is unobstructed, inside the viewport, or ready for every interaction. If you need to perform an action, consider Puppeteer locators, which provide higher-level action precondition checks.
Wait for a loading indicator to disappear
const result = await page.waitForSelector('.loading-spinner', {
hidden: true,
timeout: 20_000,
});
if (result === null) {
console.log('The spinner was absent when the wait completed');
} else {
console.log('The spinner exists but is hidden');
await result.dispose();
}
Both absence and hidden state satisfy this wait. This makes hidden: true useful for loaders that may be removed or hidden when work finishes.
4. Set per-call and page-wide timeouts
Set timeout on a call when a particular selector needs a different limit. To change the default for waits on a page, call page.setDefaultTimeout(milliseconds). A per-call timeout makes the local expectation visible next to the wait.
page.setDefaultTimeout(15_000);
// Uses the page default.
const main = await page.waitForSelector('main');
await main?.dispose();
// Overrides the default for this wait.
const report = await page.waitForSelector('.report-ready', {
visible: true,
timeout: 45_000,
});
await report?.dispose();
The API reference documents 30,000 milliseconds as the default and 0 as disabling the timeout. An unlimited wait can leave a job hanging when a selector never appears, so prefer a finite timeout for automation that must finish predictably.
5. Cancel a wait with AbortSignal
Pass an AbortSignal to stop waiting when the surrounding task is cancelled or no longer needs the result. The signal is part of the documented options; handle rejection as part of normal cancellation flow.
const controller = new AbortController();
const wait = page.waitForSelector('.results', {
visible: true,
timeout: 30_000,
signal: controller.signal,
});
// For example, cancel when another part of your task makes the wait unnecessary:
setTimeout(() => controller.abort(), 5_000);
try {
const results = await wait;
await results?.dispose();
} catch (error) {
if (controller.signal.aborted) {
console.log('Selector wait cancelled');
} else {
throw error;
}
}
In production code, connect cancellation to the real task lifecycle and clear any timer you create for demonstration or timeout coordination.
6. Manage handles and choose between waitForSelector and locators
A successful wait returns an ElementHandle. When you use the handle directly, dispose it after use to release the associated remote object. Puppeteer’s page interactions guide demonstrates this cleanup pattern.
const element = await page.waitForSelector('div.card');
try {
if (element) {
console.log(await element.evaluate((node) => node.textContent));
}
} finally {
await element?.dispose();
}
waitForSelector is a lower-level way to wait for selector availability or a specified visibility condition. For actions such as clicking, locators offer a higher-level workflow and wait for relevant preconditions such as visibility and enabled state. Locator timeouts inherit the page timeout by default. The two approaches are related, but select based on the condition and action you need rather than assuming they are interchangeable. The guide also notes that some page-level APIs, including page.click(selector), use waitForSelector for backwards compatibility.
7. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Timeout waiting for selector | The selector never matched, the page did not reach the expected state, or the timeout was too short. | Check the selector against the rendered page, confirm navigation completed to the intended page, and increase the per-call timeout only if the operation legitimately takes longer. |
| The wait resolves, but the next step cannot interact with the element | The default wait only requires a match; the element may not be visible. | Use visible: true when visibility is required, or use a locator for an interaction workflow. |
| A disappearance wait seems to pass immediately | hidden: true succeeds when the selector is absent, including when the element was never inserted. |
If the workflow requires the element to appear first and then disappear, perform a separate appearance wait before the hidden wait. |
Code expects a handle but gets null |
A wait with hidden: true can resolve with null when the selector is absent. |
Branch on the result and only call handle methods when it is non-null. |
| Automation waits forever | timeout: 0 disables the wait timeout, or the page-wide default was changed. |
Use a finite timeout and review page.setDefaultTimeout(). |
| Wait is cancelled or rejects during shutdown | An abort signal was triggered or the page/browser was closed while work remained pending. | Handle cancellation separately from selector failures, and stop or await pending work as part of browser cleanup. |
| Selector matches the wrong element | The selector is too broad or the page contains repeated components. | Scope it to a stable container or use a more specific CSS or Puppeteer selector. |
8. Performance, reliability, and cost
- Wait for the state you need. A broad page-load condition can take longer than waiting for the specific element your next step depends on. Conversely, a selector wait does not prove that unrelated page work has finished.
- Keep selectors stable and specific. Prefer selectors tied to stable markup or accessible roles where suitable; broad selectors can match early or match the wrong component.
- Bound the wait. Finite timeouts make failures visible and keep automation from holding browser resources indefinitely. Choose the duration based on the operation, not by making every wait arbitrarily long.
- Clean up resources. Dispose handles you retain, and close pages or browsers in cleanup paths so failed waits do not leave browser processes running.
- Budget browser work around total time. A selector timeout is only one part of a job’s duration; navigation, rendering, and later actions also consume time. Puppeteer’s cited references provide API behavior, not published performance benchmarks or pricing.
9. Or skip the browser setup
If your goal is to capture a page as an image or PDF rather than interact with its DOM, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call API captures a URL without requiring you to install or manage Puppeteer in your application. See the ScreenshotNeo API documentation for configuration details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents screenshot, page-info, and PDF capture tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month with no card.
10. FAQ
Does waitForSelector wait for a selector that already exists?
Yes. The method returns immediately when a matching selector exists at the time of the call.
Does visible mean the element is clickable?
No. The documented visibility condition checks that the element is not styled with display: none or visibility: hidden. Use an interaction workflow with appropriate action checks when you need to click.
Can I wait until an element is removed from the DOM?
Yes. Use hidden: true; absence from the DOM satisfies the wait, as does the element becoming hidden.
What is the default timeout?
The method reference documents 30 seconds. Set a per-call timeout or change the page default with page.setDefaultTimeout().
When should I use a locator instead?
Use a locator when the task is an interaction and you want its higher-level action preconditions, such as visibility and enabled state, handled as part of that workflow.


