How to Type Text into an Iframe with Puppeteer
Find the iframe’s Puppeteer Frame, then fill or type into an element inside it. This guide covers frame selection, runnable examples, timing, and common errors.
To type text into an iframe with Puppeteer, first get the iframe’s Frame, then locate the field inside that frame. For ordinary form entry, use frame.locator(selector).fill(text). Use frame.type(selector, text, {delay}) when the page needs character-by-character keyboard and input events.
Complete runnable example
This example opens a page, finds the iframe by part of its URL, fills a textarea inside it, and checks the resulting value. Replace the page URL, iframe URL fragment, and selector with those used by 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/form-page', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
const frame = page.frames().find((candidate) =>
candidate.url().includes('/embedded-form')
);
if (!frame) {
throw new Error('Target iframe was not found');
}
const message = 'Hello from Puppeteer';
await frame.locator('textarea[name="message"]').fill(message);
const value = await frame.locator('textarea[name="message"]').inputValue();
console.log(value);
} finally {
await browser.close();
}
})();
Install Puppeteer in your project with npm install puppeteer, save the code as a JavaScript file, and run it with Node.js. The iframe must actually load on the page, and the URL fragment and form selector must match the target.
Find the correct iframe
The iframe element is in the parent page, but its loaded document is represented by a separate Puppeteer Frame. A locator created from the main page does not automatically search inside a child frame. Get the frame first, then query its document.
Match by URL
const frame = page.frames().find((candidate) =>
candidate.url().includes('/embedded-form')
);
if (!frame) throw new Error('Target iframe was not found');
Match a distinctive part of the iframe URL rather than a generic domain when the page contains multiple frames from the same host.
Inspect frame names and URLs
for (const frame of page.frames()) {
console.log({ url: frame.url(), name: await frame.name() });
}
This is useful when the iframe has a stable name or when you need to discover what URL actually loaded. For nested frames, inspect childFrames() on the relevant parent frame.
Select through the iframe element
If the iframe element itself has a stable selector, inspect that element and obtain its content frame:
const iframeElement = await page.waitForSelector('iframe#checkout');
if (!iframeElement) throw new Error('Iframe element was not found');
const frame = await iframeElement.contentFrame();
if (!frame) throw new Error('Iframe document is not available');
await frame.locator('input[name="email"]').fill('person@example.com');
Use this when an iframe’s URL is dynamic but its parent-page selector is stable. The frame can be unavailable briefly while it attaches or navigates, so make selection after the iframe is present and loaded.
Choose between fill and type
| Need | Use | Behavior |
|---|---|---|
| Set a normal input, textarea, or supported form field | frame.locator(selector).fill(text) |
Recommended locator interaction; waits for the element and suitable state, then populates it. |
| Trigger per-character keyboard/input events or add a typing delay | frame.type(selector, text, {delay}) |
Emits keyboard and input events for each character; delay is in milliseconds and defaults to zero. |
| Press a special key such as Enter or ArrowDown | frame.locator(selector).press(key) or the page keyboard API |
Use a key press for special keys; they are not ordinary text characters. |
Fill a standard field
await frame.locator('input[name="email"]').fill('person@example.com');
Locator interactions wait for the target to be present and in a suitable state, including visibility, enabled state, and stable bounds. A timeout usually means one of those conditions was not met or the selector did not match.
Type character by character
await frame.type('input[name="email"]', 'person@example.com', { delay: 40 });
Frame.type() sends keydown, keypress/input, and keyup events for each character. Its optional delay is measured in milliseconds. Choose this when the application depends on per-key events, such as updating suggestions as the user types. It is slower than filling and usually unnecessary for setting a field’s final value.
Type, then press a key
await frame.type('input[name="search"]', 'puppeteer', { delay: 25 });
await frame.locator('input[name="search"]').press('Enter');
Use a key press for keys such as Enter, Control, or ArrowDown. For key combinations, use Puppeteer’s keyboard API rather than putting key names into the text string.
Wait for the iframe and field
Frames may attach or navigate after the parent page loads. If the frame does not exist immediately, wait for the iframe element or poll the frame list with a bounded timeout. Avoid arbitrary long sleeps where a selector or lifecycle condition can express readiness.
await page.waitForSelector('iframe#checkout', { timeout: 10000 });
const deadline = Date.now() + 10000;
let frame;
while (Date.now() < deadline) {
frame = page.frames().find((candidate) =>
candidate.url().includes('/checkout')
);
if (frame) break;
await new Promise((resolve) => setTimeout(resolve, 100));
}
if (!frame) throw new Error('Checkout frame did not load');
await frame.locator('input[name="email"]').fill('person@example.com');
When a frame navigates, the old frame may be replaced or detached. If interaction reports that a frame or execution context is gone, reacquire the frame after navigation and retry only when the operation is safe to repeat.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Frame not found | The iframe has not attached or navigated yet, or the URL/name match is wrong. | Log each frame’s URL and name, confirm the match, and wait for the iframe to appear or load before selecting it. |
| Selector not found | The selector is being used against the main page, or it does not match the iframe document. | Run the selector from the target Frame; inspect the iframe’s actual markup and use a specific selector. |
| More than one field matches | The selector is ambiguous. Frame.type() acts on the first matching element. |
Make the selector unique, for example by using a field name, id, or a more specific CSS path. |
| Locator times out | The field is absent, hidden, disabled, moving, or otherwise not ready for interaction. | Check that the frame finished loading, verify the selector and field state, and wait for the application’s real readiness condition. |
| Application does not react to fill | The page depends on per-character keyboard events rather than the final field value. | Try frame.type() and use a short delay only if the application needs it. |
| Special key appears as text or has no effect | A key name was passed as ordinary text. | Call press('Enter') or the keyboard API for special keys. |
| Frame detached during interaction | The iframe navigated or was replaced while the script was using it. | Wait for the new frame, reacquire it, and then locate the field again. |
Cross-origin behavior and third-party embed restrictions depend on the page and browser context. This guide does not assume a particular site-specific cause: inspect the actual frame URL, lifecycle, and browser error before diagnosing an embed failure.
Performance, reliability, and cost
- Prefer fill for ordinary entry. It avoids the per-character work of keyboard simulation. Use typing delays only when page behavior requires them.
- Use specific selectors. A unique frame and field selector reduce accidental interaction with the wrong embedded form.
- Wait on conditions, not guessed delays. Waiting for the iframe and a locator’s interaction readiness is generally more reliable than sleeping for a fixed long interval.
- Bound navigation and interaction waits. Set timeouts appropriate to your own application and report which frame or selector timed out.
- Reacquire after navigation. Frame objects can become stale when their document is replaced; locate the current frame again after navigation.
- Browser cost is operational. A Puppeteer run needs a browser process and time to load the page. Parallel runs consume more memory and CPU; keep concurrency appropriate for the machine and close the browser in a
finallyblock.
Or skip the browser setup
If you need a screenshot of a page after automating it, ScreenshotNeo is a website screenshot API and MCP server. Its API takes one GET request, so you can avoid setting up a browser just to capture a page. See the ScreenshotNeo API documentation for 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Can I use a page locator to find an input inside an iframe?
No. Get the iframe’s Frame and create the locator from that frame so the selector runs in the embedded document.
Should I use fill or type?
Use fill() to set a normal form value. Use type() if the page needs keyboard and input events for each character.
Does type() press Enter after the text?
No. Call a key press separately when the workflow requires Enter or another special key.
What if the page has several iframes?
Inspect their names and URLs, then select using a distinctive property and verify that the expected frame was found before entering text.
References
- Puppeteer Page.frames() API, frame collection and frame-tree APIs.
- Puppeteer page interactions guide, locator usage and fill behavior.
- Puppeteer Frame.type() API, keyboard event behavior and delay option.


