Use Puppeteer locators as the default way to find and operate form controls. They can fill text fields, textareas, contenteditable regions, native selects, checkboxes, radio buttons, and switches while waiting for the element to be visible, enabled, in the viewport, and stable. Use page.select() when you need explicit native-select values, and use keyboard typing when per-character events or Enter-key behavior matters.
This tutorial uses the current Puppeteer documentation syntax (shown as version 25.12.0 in the reviewed pages; the ElementHandle.type() page showed 25.9.0). APIs can change, so verify the version you install against the official interaction guide.
Set up Puppeteer and open a page
Install Puppeteer in a Node.js project, then import it, launch a browser, create a page, and navigate to the form. The example selectors and URL are illustrative: inspect your own HTML, labels, accessible names, and field attributes before choosing selectors.
npm install puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/signup', {waitUntil: 'networkidle2'});
// Form interactions go here.
await browser.close();
The getting-started guide also documents importing puppeteer-core when you manage the browser executable yourself. Keep the browser open until all result checks and downloads are complete, and close it in a finally block in production code so failures do not leave processes behind.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
Choose a reliable locator
Puppeteer’s documentation says, “Locators is the recommended way to select an element and interact with it.” A locator is both a selection strategy and an action: it resolves the element when the action runs, checks readiness, and retries when the target is not yet actionable.
For click and fill operations, the documented checks include being in the viewport, visible, enabled, and stable across consecutive animation frames. Prefer a unique, meaningful selector rather than a generated class name.
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('button[type="submit"]').click();
CSS selectors work directly. Puppeteer also documents selector syntax for text, accessibility attributes, XPath, and open shadow DOM. A field name such as email is only an example; inspect the target page and use its actual stable attribute or accessible label.
Fill text inputs, textareas, and editable regions
locator.fill(value) chooses an appropriate method for the element’s runtime type. The documented supported types include input, textarea, select, and contenteditable.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
await page.locator('input[name="firstName"]').fill('Avery');
await page.locator('input[type="email"]').fill('[email protected]');
await page.locator('textarea[name="message"]').fill('Hello there');
await page.locator('[contenteditable="true"]').fill('Editable text');
Filling replaces the existing value. If the site uses a custom editor that is not a supported editable element, use the page’s documented interaction or a keyboard-based approach instead of assuming a normal input will work.
Set checkboxes, radio buttons, and switches
For checkbox, radio-button, and switch controls, pass a boolean to fill(). This expresses the desired state instead of relying on a click that may toggle the control the wrong way.
await page.locator('input[name="terms"]').fill(true);
await page.locator('input[value="monthly"]').fill(true);
await page.locator('input[name="marketing"]').fill(false);
Target the actual control, not merely a decorative element that looks like one. Radio buttons in a group remain mutually exclusive according to the page’s HTML and behavior. The boolean support documented here applies to these control types; a custom widget may require its own button or keyboard interaction.
Select options in a native select
Use page.select(selector, ...values) when you know the option values in a native <select>. Puppeteer selects the matching options, triggers input and change, and returns the selected values.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Rank #3
const country = await page.select('select[name="country"]', 'CA');
console.log(country); // selected value array
const topics = await page.select(
'select[name="topics"]',
'news',
'events'
);
console.log(topics);
- Use the option’s
value, not necessarily the text visible to a user. - For a
multipleselect, pass each desired value. - For a single select, only the first supplied value is considered.
- If no matching
<select>exists, Puppeteer throws; treat that as a selector or page-state error rather than silently continuing.
For a select that is implemented as a custom popover or listbox rather than a native element, locate and click its controls as the page requires; page.select() is specifically for native selects.
Decide between fill, select, and keyboard typing
| Method | Best fit | Event and readiness behavior |
|---|---|---|
locator.fill() |
Inputs, textareas, contenteditable, selects, and boolean controls | Locator readiness checks and retries; value is set in one operation |
page.select() |
Native select values, especially when values are known explicitly | Triggers input and change; returns selected values |
ElementHandle.type() |
Per-character keyboard/input events, typing delays, or Enter-key flows | Focuses the element and sends key events for each character |
Keyboard typing is a separate interaction pattern. The API focuses the element and emits keydown, keypress/input, and keyup events for each character; an optional delay can be inserted between keystrokes.
const email = await page.$('input[name="email"]');
if (!email) throw new Error('Email input not found');
await email.type('[email protected]', {delay: 30});
await email.press('Enter');
await email.dispose();
Use Enter only when that form’s behavior makes Enter a valid submission or next-step action. Otherwise, click the intended submit control.
Submit and wait for a meaningful result
A click on a specific submit button is usually the clearest choice when a form has multiple actions.
Rank #4
- 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
await page.locator('button[type="submit"]').click();
await page.locator('[role="status"]').wait();
The final wait must represent the page’s real outcome: a confirmation element, an error message, a URL change, or another state your application defines. Do not replace that decision with an arbitrary sleep. If navigation is expected, combine the click with a navigation wait appropriate to the site’s behavior, then verify the resulting content.
Locator APIs remain the normal path. The interaction guide describes page.waitForSelector() and ElementHandle as lower-level alternatives when a locator does not provide the needed functionality. waitForSelector() waits for a selector but does not automatically retry the later action, and handles returned by lower-level APIs should be disposed.
A complete form example
This script combines the documented patterns. Replace every selector, option value, URL, and expected confirmation with those from your page.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com/signup', {waitUntil: 'networkidle2'});
await page.locator('input[name="firstName"]').fill('Avery');
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('textarea[name="message"]').fill('Please contact me.');
await page.locator('input[name="terms"]').fill(true);
await page.select('select[name="country"]', 'CA');
await page.locator('button[type="submit"]').click();
await page.locator('[data-testid="confirmation"]').wait();
console.log('Form completed');
} finally {
await browser.close();
}
Troubleshoot common failures
“Element not found” or a timeout
- Cause: The selector does not match the page, the form is inside an iframe, or the page has not rendered the control.
- Fix: Inspect the live DOM, confirm the frame context, and use a stable name, label, role, or attribute. Locators can wait for readiness, but they cannot find an element that is not in their search context.
The action is blocked or never becomes ready
- Cause: The control is hidden, disabled, outside the viewport, moving, or covered by another element.
- Fix: Wait for the page’s real state, remove the condition in the application under test, or choose the visible control. Do not force a click merely to hide a timing problem.
page.select() throws
- Cause: The selector does not identify a native
<select>, or the option value does not exist. - Fix: Confirm the element type and exact option
value. For a custom listbox, interact with its button and options instead.
The server does not react to a filled value
- Cause: The application listens for keyboard events, validation events, or a framework-specific interaction.
- Fix: Use
ElementHandle.type()when character-by-character events matter, or follow the control’s normal click and blur sequence. Verify the resulting validation state.
Enter submits the wrong action
- Cause: The form has multiple submit buttons or maps Enter to a different default.
- Fix: Click the intended submit button by a unique selector instead of pressing Enter.
A lower-level handle becomes stale
- Cause: A framework rerender replaced the DOM node after you obtained the handle.
- Fix: Prefer a locator, which can resolve and retry the action, or reacquire and dispose the handle around the operation.
Or skip the browser setup
If your goal is a clean image or PDF of a resulting form page rather than browser automation, ScreenshotNeo provides a single request. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
See the ScreenshotNeo documentation for all options, including full-page and element captures, device presets, retina scale, dark mode, custom CSS and JavaScript, clicks, waits, blocking rules, headers, cookies, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous webhooks, bulk capture, usage, and PDF controls. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Best Value
FAQ
Should I use a CSS selector or an accessibility locator?
Use the most stable, unique strategy available on the page. Meaningful names, labels, roles, and attributes usually survive presentation changes better than generated class names; Puppeteer supports CSS plus documented text, accessibility, XPath, and open-shadow-DOM selector syntax.
Can locator.fill() choose a select option?
The documented fill method supports select elements, but use page.select() when you need explicit option values, returned selections, or the documented input and change events.
Why does the documentation show different Puppeteer versions?
The reviewed pages identify version 25.12.0 generally, while the ElementHandle.type() result identifies 25.9.0. Check the API reference matching the version installed in your project.
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.

