For current Puppeteer, the normal way to set a form value is a locator and fill():
await page.locator('input[name="email"]').fill('[email protected]');
fill() chooses the appropriate interaction for the element at runtime and supports inputs, textareas, selects, contenteditable elements, and boolean controls such as checkboxes and switches. Use page.type() when the page must receive realistic per-character keyboard events; use evaluate() only for custom DOM work that the high-level APIs cannot express.
Use locator.fill() for ordinary controls
Puppeteer’s Locator API describes fill() as filling the input identified by the locator with the supplied value. It automatically detects the control type, so the same method works across common form elements.
await page.locator('#username').fill('alice');
await page.locator('textarea[name="message"]').fill('Hello');
await page.locator('select[name="country"]').fill('US');
For a checkbox, radio button, or switch, pass a boolean rather than text:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
await page.locator('input[type="checkbox"]').fill(true);
await page.locator('input[type="radio"][value="pro"]').fill(true);
Locator actions wait until the target is in the viewport, visible, enabled, and stable across two animation frames. They retry while those conditions are not met, which makes them safer than immediately querying the DOM and typing.
Give the locator its own timeout when a particular field appears slowly:
await page.locator('#username').setTimeout(15000).fill('alice');
Locator timeouts inherit Puppeteer’s page timeout unless you override them. Keep selectors specific: a broad input selector can match several controls and cause ambiguity or fill the wrong field.
Selecting the right field
Stable CSS selectors
Prefer an id, name, or application-specific attribute that is unlikely to change with layout redesigns:
Recommended Free Tools
await page.locator('#search').fill('Puppeteer');
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('[data-testid="postal-code"]').fill('10001');
Accessible-name selectors
When a control has a useful accessible label, select it by that label instead of relying on generated classes or DOM position:
await page.locator('::-p-aria(Search)').fill('Puppeteer');
Puppeteer supports CSS selectors plus additional selector forms, including ARIA, text, and XPath syntax. Accessible-name selectors generally survive visual markup changes better than selectors tied to nested elements.
Rank #2
Labels and associated controls
If a label is correctly associated with an input through for and id, target the input’s stable id or name. Do not assume that clicking visible label text alone identifies a unique control when a form contains repeated labels.
Complete runnable example
This script launches Chromium, opens a form, fills the email field, submits it, and always closes the browser:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsimport puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com/form');
await page.locator('input[name="email"]').fill('[email protected]');
await page.locator('button[type="submit"]').click();
} finally {
await browser.close();
}
Replace the URL and selectors with those from your form. If the page redirects after submission, wait for the destination or a success locator before asserting the result:
await page.locator('button[type="submit"]').click();
await page.locator('[role="status"]').wait();
fill() versus page.type()
fill() is the high-level choice for setting a final value. page.type(selector, text) simulates keyboard entry and sends keydown, keypress/input, and keyup for each character. Use it when validation, autocomplete, masking, or other application logic depends on keystrokes.
await page.type('#username', 'alice');
await page.type('#username', ' slowly', {delay: 75});
The delay option is the time between key presses and defaults to zero. Typing into an existing value appends text; clear the control first if you need replacement behavior:
await page.locator('#username').fill('');
await page.type('#username', 'alice', {delay: 40});
| Need | Preferred API | Why |
|---|---|---|
| Set a normal input, textarea, select, or contenteditable value | locator.fill(value) |
Detects the control type and waits for actionability. |
| Trigger per-character keyboard behavior | page.type(selector, text) |
Dispatches keyboard and input events for each character. |
| Perform custom DOM logic or read a value | page.evaluate() or page.$eval() |
Runs code in the page context; lower-level and easier to misuse. |
| Use a custom action not covered by locators | waitForSelector() plus an ElementHandle |
Provides direct element access, but requires explicit action and cleanup. |
When direct evaluation is appropriate
page.evaluate() runs a function in the page context and waits for a returned promise. It is useful for custom operations, but assigning element.value alone may not update a framework’s state or notify listeners. Dispatch the events the application expects:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →await page.evaluate(({selector, value}) => {
const element = document.querySelector(selector);
if (!(element instanceof HTMLInputElement)) {
throw new Error('Expected an input element');
}
element.value = value;
element.dispatchEvent(new Event('input', {bubbles: true}));
element.dispatchEvent(new Event('change', {bubbles: true}));
}, {selector: '#username', value: 'alice'});
Frameworks can use custom setters or controlled state, so keyboard-driven entry through fill() or type() is usually more reliable than direct assignment. Treat evaluation as an escape hatch, not the default.
Reading a value with $eval()
$eval() passes the first matching element to your page function and throws if no element matches:
const value = await page.$eval(
'#username',
(element) => (element instanceof HTMLInputElement ? element.value : '')
);
console.log(value);
In TypeScript, annotate the callback parameter as HTMLInputElement when your compiler cannot infer the DOM type.
Lower-level waiting with waitForSelector()
Use waitForSelector() when a locator does not cover a custom interaction:
Free tools Windows power users keep installed
One-click scans. No signup required.
const input = await page.waitForSelector('#username');
if (!input) throw new Error('Input not found');
await input.click();
await input.dispose();
This method waits for DOM availability only. It does not automatically retry a later action if the element is hidden, disabled, or moved. Dispose returned ElementHandles when finished. For ordinary filling, a locator is shorter and includes actionability checks.
Common failures and fixes
“No element found” or a timeout
- Verify the selector in DevTools and ensure it matches the actual frame and page state.
- Wait for navigation or the form’s own locator rather than using an arbitrary sleep.
- Use a per-locator timeout for genuinely slow pages.
- Replace generated classes with an id, name, test id, or accessible-name selector.
The selector matches several inputs
Make it specific with a name, type, surrounding form, or accessible name. A generic input selector is fragile on pages with search, login, and hidden controls.
The field is visible but cannot be filled
Check whether an overlay, disabled attribute, animation, or detached element is blocking interaction. Locators wait for visibility, enabled state, and stable layout; fix the page state or increase the timeout rather than forcing a click on a stale handle.
The value appears in the DOM but the app ignores it
Direct assignment does not necessarily update controlled framework state. Prefer fill() or type(). If custom code is unavoidable, dispatch bubbling input and change events and use the framework’s documented interaction pattern.
Typing produces the wrong behavior
Use fill() for replacement. Use type() only when keyboard events matter, and clear the existing value before typing if necessary.
The field is inside an iframe
Obtain the appropriate frame first, then create the locator from that frame’s context. A page-level selector cannot reach elements isolated in a child frame.
A custom element is not a native input
Inspect whether it exposes a contenteditable region, an internal input, or a component-specific API. Target the actual editable element; do not assume that setting a host element’s value property changes its internal state.
Reliability and performance practices
- Reuse one browser process for a batch of pages, but create a fresh page for isolated workflows.
- Use deterministic selectors and wait for meaningful UI states, not fixed delays.
- Keep keyboard delays at zero unless human-like event timing is required; delays make suites slower.
- Close pages and browsers in
finallyblocks so failed tests do not leak Chromium processes. - After filling, assert the value or the resulting application state to catch silent failures.
await page.locator('#email').fill('[email protected]');
await page.locator('#email').wait();
const actual = await page.$eval('#email', el => el.value);
if (actual !== '[email protected]') {
throw new Error(`Unexpected value: ${actual}`);
}
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is a clean image or PDF of a page rather than interactive form automation, ScreenshotNeo provides a website screenshot API. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify 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.
One GET request returns an image or PDF:
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 documentation for the 63 capture options, including full-page and element shots, device presets, custom CSS and JavaScript, waits, request blocking, cookies, headers, geolocation, PDFs, resizing, caching, signed links, asynchronous webhooks, bulk capture, and the usage API.
Best Value
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account to get started.
Python and Node.js request examples
For scripts that need a screenshot after completing their own Puppeteer workflow, these direct requests use the same API endpoint:
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}`);
Frequently Asked Questions
Does fill() work with a select element?
Yes. Puppeteer’s locator fill operation detects the runtime control type and can fill a native <select> as well as inputs, textareas, and contenteditable elements.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Should I use type() for every form?
No. Use fill() for ordinary replacement. Choose type() when per-character keyboard and input events drive validation, masking, or autocomplete.
Why does element.value = ... not update my React or Vue form?
Direct assignment can bypass controlled state and listeners. Prefer locator interaction, or use the component’s supported events and update path when custom evaluation is unavoidable.
What does waitForSelector() not do?
It waits for DOM availability only. It does not guarantee visibility, enabled state, stable layout, or a successful later action; locators provide those checks automatically.
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.




