How to Type Text into a Puppeteer Frame
Use Puppeteer’s Frame API to find an iframe, wait for its input, and type text. See frame discovery, nested frames, keyboard options, and fixes for common errors.
To type into an iframe with Puppeteer, find the matching Frame and call await frame.type(selector, text). The selector is evaluated inside that frame, not against the parent page. For example:
const frame = page.frames().find(frame => frame.url().includes('widget'));
if (!frame) throw new Error('Target frame not found');
await frame.waitForSelector('input[name="message"]');
await frame.type('input[name="message"]', 'Hello from Puppeteer');
Replace widget and the selector with values that match your page. The official Frame.type() reference documents the method and its options.
1. Install Puppeteer and launch a page
This runnable example starts Chromium, navigates to a page, locates a frame by a URL fragment, waits for an input, types into it, and closes the browser. Set TARGET_URL to a page you are authorized to automate, and adjust the frame fragment and input selector to match it.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(process.env.TARGET_URL, { waitUntil: 'domcontentloaded' });
const frame = page.frames().find(f => f.url().includes('widget'));
if (!frame) throw new Error('Target frame not found');
await frame.waitForSelector('input[name="message"]', { timeout: 10000 });
await frame.type('input[name="message"]', 'Hello from Puppeteer');
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
Save as type-in-frame.js, install Puppeteer with npm install puppeteer, then run TARGET_URL=https://your-authorized-test-page.example node type-in-frame.js. This is a CommonJS example; use import puppeteer from 'puppeteer' in an ES module project.
2. Find the right frame
A page can contain multiple frames, and the first child frame is not necessarily the one you need. Inspect the frame tree and choose a stable identifier, usually a distinctive URL or the iframe element’s name.
for (const frame of page.frames()) {
console.log({ name: frame.name(), url: frame.url() });
}
To select by URL, use a distinctive part of the iframe URL:
const frame = page.frames().find(f => f.url().includes('/embedded-form/'));
if (!frame) throw new Error('Embedded form frame not found');
For a known frame name, inspect the page’s iframe elements and their name attributes, then choose the corresponding frame. Frame discovery is separate from querying the field: once you have the correct Frame, its selector methods operate in that frame’s document. Puppeteer’s page.frames() and Frame references describe these APIs.
3. Wait for the field, then type
Frames and their contents may appear after navigation or after the page’s own scripts run. Wait for the target selector before typing. Frame.type() targets the first matching element in that frame and returns a promise, so await it before submitting or checking the result.
await frame.waitForSelector('input[name="message"]', { timeout: 10000 });
await frame.type('input[name="message"]', 'Hello from Puppeteer');
Use a selector that uniquely identifies the intended field when possible. If multiple elements match, the method types into the first match; a broader selector can therefore reach the wrong field.
4. Choose the typing API that fits
| Need | API | Targeting behavior |
|---|---|---|
| Type ordinary text into a selector in a known frame | frame.type(selector, text, options) |
Finds the first selector match within that frame. |
| Type into the currently focused field | page.keyboard.type(text) |
Uses current focus; it does not take a selector. |
| Send a special key such as Control or ArrowDown | page.keyboard.press(key) |
Sends a key press to the current focus. |
Frame.type() dispatches keyboard and input events for each character. Its optional delay is the interval between key presses in milliseconds and defaults to 0. For example:
await frame.type('input[name="message"]', 'Hello', { delay: 50 });
Use the keyboard API for special keys. To focus a frame-local field first, you can click its selector and then send a key:
await frame.click('input[name="message"]');
await page.keyboard.press('ArrowDown');
Keyboard typing acts on whichever element is focused, so confirm focus is inside the intended frame and field before using it. See the official Keyboard.type() and Keyboard.press() references.
5. Handle nested frames and changing pages
Frames can be nested. Find the parent frame first, then inspect its child frames and select the intended nested frame using the same checks:
const parentFrame = page.frames().find(f => f.url().includes('/outer-widget/'));
if (!parentFrame) throw new Error('Parent frame not found');
const childFrame = parentFrame.childFrames().find(f => f.url().includes('/inner-form/'));
if (!childFrame) throw new Error('Nested form frame not found');
await childFrame.waitForSelector('input[name="message"]');
await childFrame.type('input[name="message"]', 'Hello from Puppeteer');
If a site replaces or navigates an iframe, the old frame context may no longer refer to the current document. Re-enumerate page.frames() after the navigation or replacement and locate the frame again. Avoid retaining a frame reference across a page transition unless you have confirmed it remains current.
6. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
Target frame not found |
The URL/name condition does not match, or the iframe has not appeared yet. | Log each frame’s name() and url(); wait for the iframe to load and use a distinctive, current identifier. |
| Selector timeout or no matching element | The selector is wrong, the field has not rendered, or it belongs to a nested frame. | Check the selector against the frame’s document, wait for the field, and inspect child frames if needed. |
| Text goes to the wrong field or nowhere | The selector is too broad, or keyboard typing is being used without the intended focus. | Use a specific frame-local selector with frame.type(), or explicitly focus the correct element before page.keyboard.type(). |
| Typing stops after an iframe navigation | The frame context changed when the iframe navigated or was replaced. | Find the current frame again after navigation, then wait for its field before typing. |
| Site does not accept the resulting value | Application validation or site-specific input handling may require additional interaction. | Inspect the site’s expected form flow and validation behavior. The documented typing events alone do not guarantee that every application will accept the value. |
7. Reliability, speed, and version notes
For reliable automation, identify the frame with a condition tied to the actual embed, wait for the frame-local field, and await each operation in order. A zero typing delay is the fastest default; add a delay only when the workflow benefits from character-by-character pacing or the site’s behavior calls for it. Since typing emits events per character, large text and nonzero delays take proportionally longer.
Puppeteer API pages may show different version labels. Check the types and documentation matching the Puppeteer package installed in your project when version-specific behavior matters. A selector match and successful typing do not establish that a remote site submitted or persisted the value; verify the page’s resulting state using the workflow appropriate to that site.
8. Or skip the browser setup
If your goal is to capture the resulting page rather than automate text entry, ScreenshotNeo returns a screenshot or PDF from one API 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 removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its 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 free and get 1,000 screenshots a month with no card.
9. FAQ
Can I call page.type() with a selector inside an iframe?
Use the target frame’s frame.type() method for a selector inside an iframe. First obtain the correct frame from the page’s frame tree.
Does frame.type() type into every matching element?
No. It targets the first element matching the selector in that frame.
When should I use a typing delay?
Use the optional delay when paced, character-by-character input is useful. It is measured in milliseconds between key presses and defaults to zero.


