How to Wait for a Selector in a Puppeteer Frame
Wait for elements inside an iframe with Puppeteer’s Frame.waitForSelector(), configure visibility and timeouts, and handle missing frames and navigation.
Call waitForSelector() on the Frame that contains the element. For example, if the target is inside an iframe, first find that frame, then await its selector:
const button = await frame.waitForSelector('button.submit', { visible: true });
Frame.waitForSelector() resolves to an element handle when it finds a match. A normal wait that reaches its timeout without finding the selector throws. The method is documented to work across navigations. See Puppeteer’s Frame.waitForSelector reference and Page.frames reference.
Find the frame, then wait
A selector evaluated on the top-level page does not search inside a child frame. Inspect the page’s frame tree and call the wait on the matching frame. This runnable CommonJS example launches Chromium, opens a page, finds a frame by URL, waits for a visible submit button, clicks it, and closes the browser:
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 frame = page.frames().find(frame =>
frame.url().includes('/embedded-form')
);
if (!frame) {
throw new Error('Embedded form frame not found');
}
const submit = await frame.waitForSelector('button[type="submit"]', {
visible: true,
timeout: 10_000,
});
if (!submit) {
throw new Error('Submit button was not found');
}
try {
await submit.click();
} finally {
await submit.dispose();
}
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
Replace the example URL and frame URL fragment with values from your application. Frame URLs can change after redirects or navigation, so choose a stable identifying part when possible. For nested frames, inspect page.frames() or recursively inspect each frame’s childFrames(), then wait on the frame whose document contains the target.
Configure the wait condition
The method accepts a selector and optional wait settings. Choose the condition that reflects what the next step requires:
| Option | Meaning | Use it when |
|---|---|---|
visible: true |
Wait for the element to exist and be visible. | You need to interact with or inspect a visible element. |
hidden: true |
Wait until the element is absent or hidden. | You need to know that a loading indicator or overlay has gone away. |
timeout |
Maximum wait in milliseconds. The documented default is 30,000 ms; 0 disables the timeout. |
The application has a known load window or your job has an overall deadline. |
signal |
An abort signal that cancels the wait. | The containing operation can be cancelled or superseded. |
Use the documented selector syntax supported by Puppeteer, including CSS selectors. Keep selectors scoped to the intended frame; a matching selector in the parent document does not satisfy a wait on a child frame, and vice versa. See the Frame API options.
Handle missing elements and navigation
A regular wait that fails to find a matching element before the timeout rejects with an error. Catch that error if absence is an expected outcome, and distinguish it from browser or navigation failures. With hidden: true, the wait can resolve to null when the selector is absent. Check for that result where your code needs to handle it explicitly.
The frame-level method is documented to work across navigations. This is useful when the frame’s document changes while waiting, but it does not remove the need to identify the correct frame or to handle a timeout. If the iframe itself is removed and replaced, reacquire the current frame from the page before waiting again.
When a wait returns an element handle, dispose of it after use so it does not remain alive unnecessarily. A try/finally block is a simple way to ensure disposal even if the interaction fails.
Choose between frame waits, locators, and element waits
| API | Scope and purpose |
|---|---|
frame.waitForSelector() |
Waits for a selector in a particular frame and returns an element handle. It works across navigations, according to the Frame API. |
frame.locator() |
Use when the next step is an interaction such as clicking or filling. Puppeteer’s guide recommends locators for selection and interaction because they wait for element presence and relevant action preconditions. |
elementHandle.waitForSelector() |
Waits relative to an element handle. Its documentation says it does not work across navigations or after the element is detached. |
waitForSelector() is a lower-level wait. It gives you a handle, but it does not automatically retry a later action if that action fails. For interaction-oriented code, consider a locator; for a frame-specific wait where you need the handle, use the frame method. References: Puppeteer page interactions guide and ElementHandle.waitForSelector reference.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Wait times out although the element is visible in the browser. | The wait ran on the page or a different frame, or the selector does not match the frame’s document. | Inspect page.frames(), confirm the selected frame URL, and validate the selector against that frame. |
| “Frame not found” in your own guard. | The iframe has not appeared yet, its URL differs from the assumed fragment, or it navigated. | Wait for the iframe element on the page, then reacquire and inspect the frame tree. Avoid relying on an overly specific URL. |
| The selector wait times out intermittently. | The element appears later than the configured timeout or is conditionally rendered. | Set a timeout appropriate for the application and wait for the precise selector and state needed. Do not disable the timeout unless another reliable deadline exists. |
| The element handle becomes detached or an action fails after the wait. | The document changed or the application replaced the element between the wait and action. | Use a locator for the interaction, or reacquire the frame and wait for a fresh handle; dispose of the old handle. |
A hidden wait returns null. |
The selector was already absent, which satisfies the hidden condition. | Treat null as the expected “already hidden” result rather than as a found element. |
Performance and reliability notes
- Wait for the smallest condition that proves the next operation can proceed. A selector wait is often more targeted than waiting an arbitrary fixed delay.
- Set a finite timeout consistent with the surrounding job deadline. A timeout of zero removes this wait’s own limit, so use it only when cancellation or another deadline is guaranteed.
- When a frame is replaced or navigates, reacquire it if the original frame object is no longer the one you intend to use. The documented cross-navigation behavior applies to the frame-level wait; element handles have their own detachment constraints.
- Dispose of returned handles promptly. For repeated interactions, prefer locators where their automatic action waits fit the task.
Or skip the browser setup
If your goal is a screenshot of the page rather than an interaction with a frame element, ScreenshotNeo can capture it through one GET request. See the API documentation for configuration options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots. Create a free account.
FAQ
Does frame.waitForSelector() work after navigation?
Yes. Puppeteer’s Frame API says this method works across navigations.
What is the default timeout?
The documented default is 30 seconds. Pass timeout: 0 to disable that timeout.
Should I use visible: true for every wait?
No. Use it when visibility is part of the condition you need. If presence is enough, omit it; if waiting for something to disappear, use hidden: true.
Can a page selector find an element inside an iframe?
Use the frame containing that element and call the frame’s method. The top-level page and child frame have separate document scopes.


