Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Get the iframe’s element handle, switch to its document with contentFrame(), then wait for and click the button through the returned frame. A page-level selector searches the main document, not the iframe. Always check for None because contentFrame() returns it when the selected element is not an iframe.
The context you must switch to
An iframe has a separate document context. Pyppeteer’s page object represents the outer document, while a Frame object represents the iframe document. The practical distinction is:
| Context | What a selector searches | Typical operation |
|---|---|---|
| Main page | The outer document and its main frame | await page.click("button#submit") |
| Iframe frame | The document loaded by that iframe | await frame.click("button#submit") |
The transition is made from an iframe ElementHandle:
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →- Wait for the intended
<iframe>element on the page. - Call
contentFrame()on that handle. - Verify that a frame object was returned.
- Wait for the button in that frame.
- Call the frame’s
click()method.
Pyppeteer’s API reference documents ElementHandle.contentFrame() and specifies that it returns None when the handle does not reference an iframe. See the Pyppeteer 0.0.25 API reference.
#1 Best Overall
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Minimal runnable Pyppeteer example
The following program opens a page, locates an iframe by ID, enters its document, waits for a button, and clicks it. The selectors are examples; replace them with selectors from your page.
import asyncio
from pyppeteer import launch
async def click_button_in_iframe():
browser = await launch(headless=True)
page = await browser.newPage()
try:
await page.goto(
"https://example.com/checkout",
{"waitUntil": "networkidle2"},
)
iframe_handle = await page.waitForSelector("iframe#payment-frame")
frame = await iframe_handle.contentFrame()
if frame is None:
raise RuntimeError(
"The selected element is not an iframe or has no frame"
)
await frame.waitForSelector("button#submit")
await frame.click("button#submit")
finally:
await browser.close()
asyncio.get_event_loop().run_until_complete(click_button_in_iframe())
Both waits are important. The first waits for the iframe element in the outer page; the second searches inside the iframe after its document is available. Calling page.click() with the button selector would search the wrong context.
The reference page surfaced for Pyppeteer is for version 0.0.25 and is old. The project documentation says Pyppeteer has “almost same API as puppeteer,” but it also describes differences. Confirm the exact method names exposed by the version installed in your environment before copying the example.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11A reliable step-by-step workflow
1. Identify the correct iframe
Pages often contain more than one iframe. Prefer a stable attribute such as an ID, a distinctive class, or another selector that identifies the intended embedded document. Do not assume the first iframe returned by a broad selector is the one containing your button.
Rank #2
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
iframe_handle = await page.waitForSelector("iframe#payment-frame")
If the iframe is inserted by JavaScript after the initial navigation, waiting for the selector allows that insertion to complete instead of attempting an immediate lookup.
2. Convert the element handle into a frame
frame = await iframe_handle.contentFrame()
if frame is None:
raise RuntimeError("Expected an iframe element")
Treat a None result as a real failure. Continuing without a frame only moves the error farther from its cause.
3. Wait inside the frame
await frame.waitForSelector("button#submit")
The button may be rendered after the iframe’s initial document is created. A frame-scoped wait makes the lookup occur in the correct document and lets a selector timeout identify a missing or changed target. Older Pyppeteer references may expose the older waitFor() spelling instead; check your installed package and use the equivalent method it supports.
Free tools Windows power users keep installed
One-click scans. No signup required.
4. Click through the frame object
await frame.click("button#submit")
Frame.click() searches for a matching element in that frame. Keep the selector used here specific to the iframe’s markup, not to a similarly named control in the outer page.
Rank #3
Finding the frame when the iframe selector is unclear
When markup is generated or several embeds look alike, inspect the page’s frame collection and identify the frame associated with the desired content. Current upstream Puppeteer documentation describes frame discovery through the page’s frame objects; Pyppeteer follows the same general model but does not guarantee parity for every newer method.
for candidate in page.frames:
print(candidate)
Use that inspection to determine which frame corresponds to the target content, then run the frame-scoped wait and click against that object. If you can make the outer iframe selector stable, selecting the iframe element and calling contentFrame() remains the clearest approach.
Clicking when the button causes navigation
A click that submits a form or redirects the top-level page must be coordinated with the navigation wait. Starting the click and navigation wait in separate, sequential statements can create a race: the navigation may begin before the wait is registered. The current upstream Puppeteer guidance recommends awaiting both operations together. Adapt the method names and timeout options to the Pyppeteer version you installed.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesimport asyncio
await asyncio.gather(
page.waitForNavigation(),
frame.click("button#submit"),
)
Use this pattern when the click is expected to trigger navigation. If the iframe updates itself without navigating the page, wait for the resulting element or state in the appropriate frame instead.
Rank #4
Nested iframes
An iframe can contain another iframe. Each level requires the same context transition: find the child iframe in the current frame, call contentFrame(), check the result, and then search within the child frame.
outer_handle = await page.waitForSelector("iframe#outer")
outer_frame = await outer_handle.contentFrame()
if outer_frame is None:
raise RuntimeError("Outer element is not an iframe")
inner_handle = await outer_frame.waitForSelector("iframe#inner")
inner_frame = await inner_handle.contentFrame()
if inner_frame is None:
raise RuntimeError("Inner element is not an iframe")
await inner_frame.waitForSelector("button#submit")
await inner_frame.click("button#submit")
A selector on outer_frame cannot jump directly into the nested document; the inner frame must be entered explicitly.
Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| The iframe wait times out | The selector does not match, the iframe is created later, or the page did not load the expected markup. | Inspect the rendered outer page, choose a more stable iframe selector, and wait after the navigation that creates it. |
contentFrame() returns None |
The handle points to a non-iframe element, or the selected element is not the intended embed. | Verify the selector matches an actual <iframe> and fail explicitly rather than calling frame methods on a missing object. |
| The button wait times out | The selector belongs to the outer document, the wrong iframe was selected, or the button is rendered later or under a different selector. | Use a selector from the iframe’s own markup, confirm the frame identity, and wait in that frame. |
| The click runs but the expected page does not change | The action may update only the iframe, or the navigation wait was not coordinated with the click. | For navigation, await waitForNavigation() and frame.click() together. For an in-frame update, wait for the resulting state in that frame. |
| A method shown in an example is missing | Pyppeteer’s version differs from the documentation or from current JavaScript Puppeteer. | Check the installed package’s API. Older references may use waitFor(), while current upstream documentation uses waitForSelector(). |
| Several possible frames are present | A broad iframe selector selected the wrong embed. | Use a distinctive outer selector or inspect the page’s frame collection before choosing the frame. |
Version and documentation compatibility
The primary Pyppeteer reference available for these APIs is labeled version 0.0.25 and was indexed years ago. The Pyppeteer documentation explains its relationship to Puppeteer and notes Pyppeteer-specific differences, including the calling convention for evaluate(). Current upstream references for Puppeteer frames, Puppeteer pages, and Frame.waitForSelector are useful for understanding the maintained upstream concepts, but they do not prove that every current Puppeteer method exists in your Pyppeteer installation.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
When a method name or option differs, prefer the API exposed by your installed package. Keep the underlying sequence unchanged: select the iframe, obtain its frame, wait in that frame, and click through that frame.
Best Value
- JavaScript Jquery
- Introduces core programming concepts in JavaScript and jQuery
- Uses clear descriptions, inspiring examples, and easy-to-follow diagrams
Reliability and performance practices
- Use one precise iframe selector instead of repeatedly scanning every iframe.
- Wait for the specific button you need rather than relying only on the outer page’s load event.
- Keep the
Nonecheck so a markup change produces a clear error. - Coordinate clicks and navigation waits when submission redirects the page.
- For nested embeds, retain a separate variable for each frame so it is obvious which document each selector targets.
- Record the selector and frame-selection failure that caused a timeout; that information distinguishes a changed page from a slow load.
Or skip the browser setup
If your actual goal is to obtain a clean image or PDF of a URL rather than interact with a button, ScreenshotNeo provides a website screenshot API. It is not a replacement for Pyppeteer when you must click an iframe control, but it removes the browser-launch and frame-navigation work for capture jobs. Before taking a shot, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off.
Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response reports the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
One request returns PNG, JPEG, WebP, or PDF output:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for parameters and response details. The same request in Python is:
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}`);
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
Frequently Asked Questions
Does contentFrame() wait for the iframe’s button automatically?
No. It changes the handle into a frame context; you still need a frame-scoped wait such as waitForSelector() (or the equivalent method in your installed Pyppeteer version) before clicking.
Can ScreenshotNeo perform the iframe click shown here?
No. ScreenshotNeo captures a URL as an image or PDF. Use Pyppeteer for an interaction that requires clicking a control, and use ScreenshotNeo when you only need a clean capture.
Recommended Free Tools
Quick Recap
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.

