ScreenshotNeo

BlogHow-to

How to Tap an Element Inside an Iframe with Puppeteer

Find the right Puppeteer frame, tap an element in its context, and handle nested frames, navigation, timing, and common failures.

By the ScreenshotNeo team4 October 20266 min read

To tap an element inside an iframe with Puppeteer, find the iframe’s Frame object and call frame.tap(selector). The main page and each iframe have separate document contexts, so a selector run against the page does not automatically reach into a child frame. Puppeteer also supports frame.locator(selector).click() when the intended action is a pointer click; locators wait for the target to be ready before interacting.

The examples below use current Puppeteer APIs. Check the documentation for the version installed in your project if an API behaves differently.

1. Install Puppeteer and launch a page

Install Puppeteer in a Node.js project:

npm install puppeteer

Save this example as tap-iframe.js. Replace the example page URL, frame URL fragment, and target selector with values from the site you are automating:

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',
    });

    // Inspect the current frames when you are unsure which one to use.
    for (const frame of page.frames()) {
      console.log({ name: frame.name(), url: frame.url() });
    }

    // Use a condition that identifies the intended frame on this site.
    const frame = page.frames().find(frame =>
      frame.url().includes('/embedded-content')
    );
    if (!frame) throw new Error('Target iframe not found');

    await frame.tap('button.submit');
    console.log('Tapped the button in the iframe');
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

page.frames() gives the current frame tree. The URL test is only an example: use a URL condition, frame name, or another property that uniquely identifies the intended frame on the site you are working with. Puppeteer documents frame attributes and the frame tree, but there is no universal frame selector that fits every page.

2. Choose the right frame and action

Inspect frame names and URLs

Log frames after the page or relevant iframe has loaded. If the target frame is nested inside another iframe, inspect its parent’s childFrames() as well:

function printFrames(frame, depth = 0) {
  console.log(`${'  '.repeat(depth)}name=${frame.name()} url=${frame.url()}`);
  for (const child of frame.childFrames()) printFrames(child, depth + 1);
}

printFrames(page.mainFrame());

Frame selection is specific to the page and its current lifecycle. A frame can attach, navigate, or detach while automation runs. If the expected frame is missing, confirm that it has attached, that your identifying condition still matches its current URL or name, and that the target is not nested deeper.

Tap for touch input, use a locator for click input

frame.tap(selector) taps the first matching element in that frame. Use it when the interaction should be a touch action. For a pointer click, Puppeteer recommends locators; Frame.locator() scopes the locator to the frame:

await frame.locator('button.submit').click();

Locators check that a target is in the viewport, visible, enabled, and has a stable bounding box before clicking; they retry when the element is not ready. See the official Puppeteer page interaction guide, Frame API, and Frame.locator() reference.

Direct methods such as frame.click(selector) remain available. Prefer a locator for ordinary element interaction when its readiness behavior is useful. Choose a selector that identifies one intended element; a broad selector can match a different control than expected.

3. Handle navigation caused by the tap

If tapping submits a form or otherwise navigates the iframe, start waiting for navigation before the tap, in the same Promise.all. This avoids missing a fast navigation:

const [response] = await Promise.all([
  frame.waitForNavigation(),
  frame.locator('button.submit').click(),
]);

console.log('Iframe navigation completed', response?.url());

Use this only when the action is expected to navigate that frame. For a single-page application action that updates content without navigation, wait for a meaningful result instead, such as a confirmation selector. Navigation expectations depend on the site; the Frame API documents frame navigation, and Puppeteer’s Page.click() reference shows the concurrent wait-and-click pattern.

4. Wait for a frame that attaches later

A frame may not exist yet when the parent page’s initial navigation finishes. In that case, wait for the site’s frame creation or readiness condition before selecting it. Keep the wait specific to the page’s behavior; do not assume that a fixed delay guarantees the iframe is ready. Once it is attached, obtain the current frame and then locate or tap the target.

If the iframe itself navigates after it is attached, inspect page.frames() again and verify that the selected frame still represents the intended content before interacting. Detached or replaced frames are stale contexts; select the current frame after the lifecycle change.

5. Troubleshooting

Symptom Likely cause Fix
Frame not found The iframe has not attached, the URL/name condition does not match, or the target is nested. Print page.frames(), inspect frame names and URLs, wait for attachment, and traverse childFrames().
Selector not found or tap times out The selector is wrong for the child document, content has not rendered, or the selector is ambiguous. Check the selector against the iframe document, wait for the relevant frame content, and choose a unique selector.
Element is not interactable The target may be hidden, disabled, outside the viewport, or moving. Use frame.locator(selector).click() for its readiness checks, and verify the site has made the control available.
Navigation wait hangs or is missed The action did not navigate, the wrong frame was watched, or the wait began after the action. Pair the expected frame’s waitForNavigation() and click in Promise.all; for in-page updates, wait for a result selector instead.
Frame execution context was destroyed The iframe navigated or was replaced during the operation. Wait for the lifecycle transition, reacquire the current frame from the page, then retry against the intended state.

6. Reliability and performance notes

  • Use stable frame identity. A URL fragment or frame name should distinguish the intended frame on that page. Recheck the current frame tree when a site changes its embeds.
  • Scope selectors correctly. Query the target through its owning frame. Main-document selectors do not cross into child documents.
  • Wait on conditions, not arbitrary delays. Locator readiness and targeted frame or result waits are more reliable than sleeping for a guessed interval.
  • Coordinate navigation. Register the wait and action concurrently when navigation is expected, and wait on the frame that should navigate.
  • Keep the browser lifecycle bounded. Close the browser in a finally block so errors do not leave a process running. Reuse a browser for multiple operations in a controlled automation job when appropriate, while keeping page and frame state fresh for each target.

Frame inspection and selection are generally inexpensive compared with loading the page and its embedded content. The primary reliability cost is waiting for the correct frame and state; a fixed sleep can waste time and still race a slow or replaced iframe.

7. Or skip the browser setup

If the task is to capture the page rather than interact with an iframe control, ScreenshotNeo returns a screenshot or PDF with one API request. Its API documentation covers the available capture 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 removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.

Sign up for 1,000 free screenshots a month, with no card required.

8. FAQ

Can I access an iframe on another domain?

Puppeteer interacts with the browser’s frame contexts through its Frame API. Select the frame attached to the page and use that frame’s context; a main-page query still does not target the child document.

Should I use tap() or click()?

Use tap() when you need touch input. For pointer interaction, use frame.locator(selector).click() and its locator readiness checks.

Can a selector target an element in a nested iframe?

First find the child frame that owns the element, including through childFrames() if needed, then use a locator or frame method in that child frame.