How to Click an Element Inside an Iframe with Puppeteer
Get the iframe’s Puppeteer Frame, then click through a locator in that frame. This guide covers frame discovery, nested iframes, navigation, errors, and runnable code.
To click an element inside an iframe with Puppeteer, get the iframe’s Frame and create a locator from that frame. A locator on page searches the main document, so it cannot find elements inside a child frame.
const iframeHandle = await page.$('iframe#payment');
if (!iframeHandle) {
throw new Error('Payment iframe element not found');
}
const frame = await iframeHandle.contentFrame();
if (!frame) {
throw new Error('Payment iframe frame not available');
}
await frame.locator('button.submit').click();
ElementHandle.contentFrame() gets the frame associated with an iframe element. Puppeteer recommends locators for selecting and interacting with elements; they wait for the target to be present and ready for the action. See the contentFrame API, Frame API, and page interaction guide.
1. Get the iframe frame and click the target
Use an iframe selector that identifies the intended frame. Check both the iframe element and the returned frame before using them. Then query the target from the frame:
const iframeHandle = await page.$('iframe#checkout');
if (!iframeHandle) {
throw new Error('Checkout iframe element not found');
}
const frame = await iframeHandle.contentFrame();
if (!frame) {
throw new Error('Checkout iframe frame not available');
}
await frame.locator('button[type="submit"]').click();
The selector passed to frame.locator() is evaluated in the iframe’s document. It does not need to include the iframe element itself.
Complete runnable example
Install Puppeteer, save this as click-iframe.js, and run it with Node.js. Replace the example URL, iframe selector, and button selector with values from the page you control or are authorized to automate.
npm install puppeteer
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/checkout', {
waitUntil: 'domcontentloaded',
});
const iframeHandle = await page.$('iframe#checkout');
if (!iframeHandle) {
throw new Error('Checkout iframe element not found');
}
const frame = await iframeHandle.contentFrame();
if (!frame) {
throw new Error('Checkout iframe frame not available');
}
await frame.locator('button[type="submit"]').click();
console.log('Clicked submit button inside the checkout iframe');
} finally {
await browser.close();
}
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
If your project uses ECMAScript modules, change the import to import puppeteer from 'puppeteer'; and retain the rest of the function.
2. Choose a reliable way to find the frame
Use a known iframe selector
This is usually the simplest option when the page has a stable ID, name, or other distinguishing attribute. Query the iframe element, call contentFrame(), and interact with the returned frame.
const iframeHandle = await page.$('iframe[name="payment-frame"]');
if (!iframeHandle) throw new Error('Payment iframe not found');
const frame = await iframeHandle.contentFrame();
if (!frame) throw new Error('Payment frame unavailable');
await frame.locator('[data-action="confirm"]').click();
Find a frame by its name
When the name is more dependable than the iframe’s position or markup selector, inspect the page’s frames and read each frame element’s name attribute:
let targetFrame;
for (const candidate of page.frames()) {
const element = await candidate.frameElement();
const name = await element.evaluate(el => el.getAttribute('name'));
if (name === 'myframe') {
targetFrame = candidate;
break;
}
}
if (!targetFrame) {
throw new Error('Named frame not found');
}
await targetFrame.locator('.selector').click();
page.frames() returns the frames currently associated with the page. The main frame and child frames can also be inspected with page.mainFrame() and frame.childFrames(). See the Frame API for these methods.
Wait for a dynamically added iframe
If the page creates the iframe after its initial HTML loads, wait for the iframe element before asking for its frame. A locator wait is preferable to inserting a guessed sleep:
const iframeLocator = page.locator('iframe#checkout');
await iframeLocator.wait();
const iframeHandle = await page.$('iframe#checkout');
if (!iframeHandle) throw new Error('Checkout iframe not found after waiting');
const frame = await iframeHandle.contentFrame();
if (!frame) throw new Error('Checkout frame unavailable');
await frame.locator('button.submit').click();
If the iframe exists but its document is still loading, wait for a target condition in the frame by using a locator for the target. Locator actions retry while action preconditions are not met and will time out if the target never becomes ready.
3. Handle nested iframes
If the target lives in an iframe nested inside another iframe, query and enter each level. Use the innermost frame for the final target:
const outerHandle = await page.$('iframe#outer');
if (!outerHandle) throw new Error('Outer iframe not found');
const outerFrame = await outerHandle.contentFrame();
if (!outerFrame) throw new Error('Outer frame unavailable');
const innerHandle = await outerFrame.$('iframe#inner');
if (!innerHandle) throw new Error('Inner iframe not found');
const innerFrame = await innerHandle.contentFrame();
if (!innerFrame) throw new Error('Inner frame unavailable');
await innerFrame.locator('button.confirm').click();
A selector issued to a frame only searches that frame’s document. It does not automatically cross into child frames. When you do not know where the target is, inspect the frame tree with childFrames() or enumerate page.frames() and identify the correct frame from its element, name, or other known information.
4. Synchronize a click that navigates
If clicking the target is expected to navigate the iframe, start waiting for that navigation at the same time as the click. Starting the wait afterward can miss a fast navigation:
const [response] = await Promise.all([
frame.waitForNavigation(),
frame.locator('button.submit').click(),
]);
console.log('Iframe navigation response:', response?.status());
Use this only when the action is expected to navigate the frame. A click that updates content without navigation does not need waitForNavigation(). Puppeteer documents this concurrent wait-and-click pattern in the Frame API.
5. Pick a locator or direct frame click
| Method | Example | Use it when |
|---|---|---|
| Locator | frame.locator('button.submit').click() |
You want Puppeteer to wait for the element and its click readiness conditions. This is the recommended interaction approach. |
| Direct frame click | frame.click('button.submit') |
You need the lower-level selector method and are prepared to handle a missing match and any explicit waiting yourself. |
Frame.click(selector) clicks the first matching element and rejects if no element matches. Use a specific selector if multiple elements could match. Puppeteer supports CSS selectors as well as its documented text and accessibility selector syntax; see the interaction guide.
6. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Locator times out or reports no matching element | The locator was created from page, so it searches the main frame, or the selector is wrong. |
Get the iframe’s frame and create the locator from that frame. Confirm the selector against the iframe document. |
iframeHandle is missing |
The iframe selector does not match, or the iframe has not been added yet. | Check the selector and wait for the iframe element to appear before looking it up. |
contentFrame() result is unavailable |
The selected element is not the expected iframe or the page changed while it was being located. | Confirm the element is an iframe and reacquire it from the current page state. |
| The click targets the wrong element | A broad selector matched a different element; direct Frame.click() uses the first match. |
Use an ID, attribute, or other selector unique to the target; verify the frame as well as the selector. |
| Click times out although the selector exists | The target may be hidden, disabled, moving, or not yet in a click-ready state. | Check the page state and the element’s readiness. Locators retry while waiting for action preconditions; increase the timeout only when a slower, valid state transition is expected. |
| Navigation wait hangs or misses the navigation | The action does not navigate, or the wait was started after the click. | Only wait when navigation is expected, and use Promise.all to arm the wait concurrently with the click. |
| A previously found frame stops working | The page detached or replaced the frame during a rerender or navigation. | Reacquire the current iframe and frame after the page changes. Puppeteer exposes frame attach, navigate, and detach lifecycle events. |
| Target is still not found after entering a frame | The target is in a nested child iframe. | Inspect childFrames(), enter the next iframe, and locate the target in the innermost frame. |
A timeout is evidence that one of the expected conditions was not met. First distinguish a wrong frame or selector from an element that is genuinely taking longer to become ready; adding an arbitrary delay can hide the cause without fixing it.
7. Reliability, runtime, and cost considerations
- Prefer condition-based waits. Wait for the iframe or target locator rather than sleeping for a fixed duration. This avoids needless delay when the page is fast and gives a meaningful timeout when it is not ready.
- Keep frame handles short-lived. A frame can navigate or detach as a page changes. Reacquire it after a rerender or navigation instead of assuming an old reference remains the target.
- Make selectors specific. A narrower iframe selector and unique target selector reduce ambiguity and prevent clicks on the wrong control.
- Set deliberate timeouts. Use the page or locator timeout settings appropriate to the application’s expected load behavior. Longer limits can accommodate slow transitions but also make real failures take longer to report.
- Account for browser resources. Puppeteer controls a browser process, so close pages and browsers when finished, and avoid launching a new browser for every individual action when a longer-lived process fits your application.
- Estimate cost from your own runtime. The cited Puppeteer documentation does not provide a universal cost or performance benchmark for iframe clicks. Runtime and infrastructure costs depend on how you launch and operate the browser.
8. Or skip the browser setup
If your goal is a screenshot rather than an interaction that must change page state, ScreenshotNeo can capture a page with one API request. It is a website screenshot API and MCP server from Yorker Media. It does not perform this iframe click; use Puppeteer when the click itself is required.
See the ScreenshotNeo API documentation for request options.
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}`);
ScreenshotNeo accepts cookie and consent banners like 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, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.
Sign up free for 1,000 screenshots a month, no card required.
9. FAQ
Can Puppeteer click inside a cross-origin iframe?
Yes. Puppeteer’s frame APIs let you locate and interact with the iframe’s document through its Frame; use the frame context for the locator.
Does page.locator() search all iframes?
No. A page locator searches the main frame. Create the locator from the frame that contains the target.
Should I use page.frames() or contentFrame()?
Use contentFrame() when you have a reliable iframe element selector. Enumerate page.frames() when a frame name or other frame metadata is the better way to identify it.
Do I need to wait for navigation after every iframe click?
No. Wait only when the click is expected to navigate the frame. For that case, start the wait and click together with Promise.all.


