How to Click Elements Inside an Iframe with Puppeteer
Use Puppeteer’s iframe Frame to find and click elements in the frame, handle nested frames, and wait safely for navigation.
To click an element inside an iframe with Puppeteer, get the iframe’s Frame and interact with that frame’s document. For ordinary clicks, use a frame locator:
const iframeHandle = await page.$('iframe');
if (!iframeHandle) throw new Error('iframe not found');
const frame = await iframeHandle.contentFrame();
if (!frame) throw new Error('iframe content frame not available');
await frame.locator('button').click();
A selector run on page searches the main document. A selector run on frame searches inside that iframe. The same approach applies to links, inputs, and other elements. [Puppeteer Frame API] [Puppeteer interaction guide]
1. Get the iframe’s Frame and click
- Select the intended iframe element.
- Call
contentFrame()to get its associated PuppeteerFrame. - Use a locator on that frame to find and interact with the target.
const iframeHandle = await page.$('iframe[title="Checkout"]');
if (!iframeHandle) throw new Error('Checkout iframe not found');
const frame = await iframeHandle.contentFrame();
if (!frame) throw new Error('Checkout iframe has no available content frame');
await frame.locator('button[type="submit"]').click();
contentFrame() resolves the frame associated with an iframe element; it can return null when a frame is unavailable. Puppeteer describes a Frame as the DOM frame associated with an iframe. [ElementHandle.contentFrame()] [Frame API]
2. Runnable example with setup
This CommonJS example opens a page, finds an iframe by its title, clicks a button inside it, and closes the browser. Replace the page URL, iframe selector, and button selector with values from your page.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/page-with-iframe', {
waitUntil: 'domcontentloaded',
});
const iframeHandle = await page.$('iframe[title="Checkout"]');
if (!iframeHandle) throw new Error('Checkout iframe not found');
const frame = await iframeHandle.contentFrame();
if (!frame) throw new Error('Checkout iframe is not attached');
await frame.locator('button[type="submit"]').click();
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
The example uses Puppeteer’s documented frame and locator APIs. It assumes the page and iframe are accessible to the running browser and that the target selector matches the page’s actual markup.
3. Choose the right iframe
When a page has more than one iframe, do not assume the first one is the target. Prefer a stable attribute such as title or name where available. You can also inspect the frame tree and match a frame by URL:
const frames = page.frames();
for (const candidate of frames) {
console.log(candidate.url());
}
const frame = frames.find(candidate =>
candidate.url().includes('/embedded-form')
);
if (!frame) throw new Error('Target frame not found');
await frame.locator('button.submit').click();
URL matching is specific to the site: redirects or frame navigation may change the URL. Use a stable matching rule appropriate for your page. Puppeteer exposes the current frames through page.frames(), and each frame can expose its child frames. [Frame API]
4. Handle nested iframes
If the target element is inside an iframe nested within another iframe, select the child frame too. A frame’s childFrames() method exposes its children:
const outerHandle = await page.$('iframe[title="Payment"]');
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[title="Card entry"]');
if (!innerHandle) throw new Error('Nested iframe not found');
const innerFrame = await innerHandle.contentFrame();
if (!innerFrame) throw new Error('Nested frame unavailable');
await innerFrame.locator('input[name="cardnumber"]').click();
Alternatively, inspect outerFrame.childFrames() and match the desired child by URL or another suitable property. The key is to interact with the particular frame that contains the element, rather than querying the top-level page. [Frame API]
5. Wait for readiness and navigation
Locators are the recommended interaction style in Puppeteer. They wait for the target and check conditions such as visibility, enabled state, viewport position, and a stable bounding box before clicking. [Page interactions]
await frame.locator('button.continue').click();
If you need to wait explicitly for a selector before using a lower-level call, use the frame’s selector API:
await frame.waitForSelector('button.continue', { visible: true });
await frame.click('button.continue');
If the click triggers navigation in that frame, begin waiting for navigation and clicking together. Waiting only after the click can miss a fast navigation:
const [response] = await Promise.all([
frame.waitForNavigation(),
frame.locator('a.continue').click(),
]);
console.log('Frame navigation response:', response?.status());
Use this pattern only when the action is expected to navigate that frame. Puppeteer’s Frame API documents the paired wait-and-click pattern to avoid the race. [Frame API]
6. Locator click or direct Frame click?
| Approach | Use it when | Behavior |
|---|---|---|
frame.locator(selector).click() |
Most interactions | Recommended locator route; waits for common action preconditions. |
frame.click(selector) |
You specifically want the lower-level frame method | Directly clicks the matching frame element. |
For routine automation, the locator form is easier to read and handles readiness checks. The direct method remains available in the Frame API. [Frame API] [Page interactions]
7. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Selector finds nothing | The selector is running against page, which queries the main document, or the wrong frame was selected. |
Get the iframe’s Frame and query with frame.locator(). Inspect page.frames() and their URLs if there are multiple frames. |
iframeHandle is missing |
The iframe selector does not match, or the iframe has not appeared yet. | Check the selector and page state; wait for the iframe selector if it loads dynamically. |
contentFrame() returns null |
The iframe is unavailable, detached, or has been replaced. | Reacquire the iframe element after the page updates, then call contentFrame() again. |
| Click times out or cannot proceed | The target is missing, hidden, disabled, unstable, or not the intended element. | Verify the selector in the frame, wait for the UI state, and use a locator so Puppeteer can check common click preconditions. |
| Navigation wait times out or is missed | The click and navigation wait were sequenced separately, or the click did not navigate that frame. | Use Promise.all with frame.waitForNavigation() and the click, and confirm navigation is expected. |
| Nested target remains unfound | The element is inside a child iframe. | Find the nested iframe from its parent frame, get its content frame, then query there. |
8. Performance, reliability, and operating cost
- Keep frame selection specific. A stable iframe attribute avoids accidentally acting on an unrelated frame when the page contains several.
- Wait for the state you need. A locator click handles common readiness checks. Avoid adding arbitrary delays unless the page requires a known delay; waiting for a selector or the relevant navigation is more targeted.
- Expect frame changes. Pages can detach or replace frames during navigation and UI updates. Reacquire the iframe handle and its frame after such a change.
- Handle navigation as a pair. Start the navigation wait before or together with the click to avoid missing a fast response.
- Account for browser runtime. Puppeteer automation requires launching and managing a browser process. Keep browser lifetime and concurrency appropriate to your workload; the code example closes the browser in a
finallyblock.
The cited Puppeteer material documents the interaction APIs and their behavior but does not provide a universal runtime or cost benchmark. Actual runtime and infrastructure cost depend on the page, browser environment, and workload.
9. Or skip the browser setup
If your goal is to capture a page rather than automate an interaction inside its iframe, ScreenshotNeo returns a screenshot or PDF from one GET request. See the ScreenshotNeo API documentation.
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, failed loads, timeouts, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. These captures are for screenshots and PDFs, not a replacement for Puppeteer when you need to click inside an iframe.
Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card.
10. FAQ
Can I use page.click() for an element inside an iframe?
Use a frame-scoped method instead. Get the iframe’s Frame, then click through its locator or the Frame API.
Does the iframe have to be same-origin?
Puppeteer’s frame API operates on the browser’s frame tree. The documented workflow is to obtain the corresponding Frame and query it; confirm that the target frame exists and contains the element you need.
How do I know which frame contains the button?
Inspect page.frames() and the frame URLs, or identify the iframe element with a stable attribute and call contentFrame().
Can Puppeteer click in nested iframes?
Yes. Resolve each parent and child iframe to its own Frame, then query the frame containing the target.


