How to Fill Out Forms With Puppeteer Locators
Use Puppeteer’s locator API to fill text fields, selects, editable regions, and checkboxes, then submit reliably with runnable examples and troubleshooting tips.
Use page.locator(selector).fill(value) to fill a form field with Puppeteer. It supports input, textarea, select, and contenteditable elements; pass true or false for checkboxes, radio buttons, and switches. Locators wait for the target to be ready and retry actions subject to their timeout. The examples below use Puppeteer’s current locator API; check the linked reference if you need to confirm behavior for a specific installed version.
1. Install Puppeteer and open a page
Install Puppeteer in a Node.js project, then launch a browser and navigate to the page containing the form. This runnable example uses a public example domain only as a placeholder; replace it and the selectors with the target page’s actual URL and markup.
npm install puppeteer
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Fill fields here.
} finally {
await browser.close();
}
})();
For pages that build their forms after initial navigation, wait for a form-specific locator by acting on it directly or use a targeted wait before filling. Avoid relying on a fixed delay unless the page genuinely requires time for a known asynchronous operation.
2. Fill common form controls with locators
Use selectors grounded in the page’s real markup. Stable names, labels, and accessible names are usually more durable than positional selectors such as input:nth-child(3).
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/contact', { waitUntil: 'domcontentloaded' });
await page.locator('input[name="email"]').fill('reader@example.com');
await page.locator('textarea[name="message"]').fill('Hello');
await page.locator('input[name="terms"]').fill(true);
await page.locator('button[type="submit"]').click();
// Add an assertion or inspect the resulting page here.
} finally {
await browser.close();
}
})();
The selectors and URL above are examples, not a guarantee about any site. Identify the actual field attributes with the page’s DOM or browser developer tools. Puppeteer also supports selector syntax for text, accessibility attributes, XPath, and open Shadow DOM. For example, the official getting-started guide uses an accessible name:
await page.locator('::-p-aria(Search)').fill('automate beyond recorder');
See the Puppeteer page interactions guide and Locator.fill() API reference for supported locator behavior and selector syntax.
3. Handle selects, checkboxes, and editable regions
Select elements
locator.fill(value) supports a select element. You can also use page.select(selector, ...values) when you want the explicit option-value interface:
await page.locator('select[name="country"]').fill('CA');
// Alternative:
await page.select('select[name="country"]', 'CA');
page.select() selects option values and triggers input and change events. For a multiple select, pass all desired option values; for a single select, only the first supplied value is used:
await page.select('select[name="topics"]', 'billing', 'support');
Use the option’s value, not necessarily its visible label. Inspect the page when a value appears correct but no option is selected.
Checkboxes, radio buttons, and switches
Pass a boolean to state the desired checked value, rather than treating these controls as text fields:
await page.locator('input[name="terms"]').fill(true);
await page.locator('input[name="newsletter"]').fill(false);
Use a selector specific to the intended radio option. Filling one radio control with true selects it; a form may require a particular option rather than the group generally.
Contenteditable elements
For an editable region, target the element and fill its text:
await page.locator('[contenteditable="true"]').fill('Message entered into an editable region');
If the application uses a rich text editor, verify the saved or submitted value in the application. Editor frameworks may maintain state beyond the editable DOM node.
4. Choose selectors that survive page changes
A locator is a strategy for finding a page element and performing an action. Puppeteer describes locators as the recommended way to select an element and interact with it. Prefer, in order where available:
- A stable accessible name or label that identifies the field to a user.
- A stable attribute such as
name, a meaningfulid, or a dedicated test attribute. - A scoped CSS selector rooted in the relevant form or section.
- Text, XPath, or Puppeteer-specific selector syntax when it matches the page’s structure better.
For example, if a page contains two email fields, scope the locator to the correct form instead of assuming the first matching input is the right one. Check that the locator resolves to the intended control when pages contain repeated labels or hidden responsive forms.
5. Understand waiting, retries, and timeouts
Locator actions automatically wait for the target to be present and in a state where the action can proceed. Depending on the action, readiness checks include visibility, enabled state, viewport placement, and stable geometry. If those conditions are not met, locator actions retry; they can still time out when the target is absent or never becomes actionable.
Use a targeted locator action as the wait when possible. A lower-level waitForSelector() can wait for an element to appear, but it does not automatically retry a later action if the page changes between the wait and that action. Locator actions handle readiness and retries as part of the interaction flow. Consult the Locator API reference for the installed Puppeteer version’s timeout controls.
When adjusting timeouts, keep them aligned with the page’s expected behavior. A longer timeout can accommodate a slow application, but it can also make a broken selector take longer to diagnose. Prefer waiting for a specific form state over waiting for every network request to finish; analytics and long-lived connections can keep network activity open.
6. Submit and confirm the result
A click completing does not by itself prove that the form was accepted. Wait for a visible success message, a confirmation URL, or another page-specific signal. The following example shows the structure; replace the success selector with one that exists on the target site:
await page.locator('input[name="email"]').fill('reader@example.com');
await page.locator('button[type="submit"]').click();
await page.locator('[role="status"]').wait();
For applications that navigate after submission, wait for a known confirmation element or inspect the resulting URL. For validation failures, read the field-level messages and correct the input rather than assuming the click failed.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Locator action times out | The selector does not match, the form has not appeared, or the element never reaches an actionable state. | Inspect the live DOM, verify the selector, and wait for the specific form state. Increase the timeout only when the page’s normal load time justifies it. |
| The wrong field receives text | A broad selector matches multiple controls or a hidden duplicate. | Scope the locator to the correct form and use a stable name, label, or accessible name. Check for duplicate responsive markup. |
| Select remains unchanged | The supplied value is not an option value, or the wrong select was targeted. | Inspect the option values and target the correct select. For multiple selections, pass each intended value to page.select(). |
| Checkbox state is wrong | A string or click toggle was used where a desired state was needed. | Use fill(true) to check and fill(false) to uncheck the specific control. |
| Submit click happens, but no success | Validation rejected the values, submission is asynchronous, or the success condition was not checked. | Inspect validation messages and wait for a page-specific confirmation signal after clicking. |
| Editable text appears but is not saved | The rich text editor may track application state separately from the DOM. | Verify the application’s resulting value and use a selector/action that matches the editor’s supported interaction model. |
| Action works locally but is flaky in automation | The page shifts, fields load dynamically, or the selector depends on changing layout. | Use stable attributes, let locator readiness checks run, and avoid fixed sleeps and positional selectors. |
8. Performance, reliability, and cost
Form filling itself is usually a small part of an automation run. Reliability depends more on selector quality, page readiness, application validation, and the confirmation condition than on adding more waits. Keep browser cleanup in a finally block, avoid launching a new browser for every field, and reuse a browser when running multiple independent page tasks if your application’s isolation requirements allow it.
For production automation, log the page URL, the step that failed, and a useful error message. Do not log passwords, payment data, session cookies, or other secrets. Use test accounts and test environments for workflows that submit data or trigger external side effects. Browser execution has compute and maintenance costs; timeouts, retries, and browser lifetime should be bounded so one inaccessible page does not stall a batch.
9. Or skip the browser setup
If the result you need is a screenshot of a page or form, ScreenshotNeo is a website screenshot API and MCP server. Its one-request API returns an image or PDF. It does not fill or submit forms; use Puppeteer above when you need to interact with form controls.
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}`);
See the ScreenshotNeo API documentation for request options and response details. 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 the free plan.
10. FAQ
Does locator fill work with file upload inputs?
The documented supported types for fill() are contenteditable, select, textarea, and input, with booleans for checkbox-like controls. For file upload workflows, consult Puppeteer’s dedicated file chooser or upload API rather than assuming text filling will set a file.
Can I fill fields inside an iframe?
Locate the relevant frame and perform the locator interaction in that frame’s context. A page-level locator generally searches the page’s own document, not the contents of a separate frame.
Does filling a form submit it?
No. Fill the controls, then explicitly click the submit control or use the form’s intended submission behavior, and verify the result.
Where can I confirm exact API behavior?
Use the official page interactions guide, fill API reference, and page.select() reference. Documentation behavior can evolve with Puppeteer releases.


