Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use locator.fill() for normal form entry. Use locator.pressSequentially() only when the page must receive keyboard events for every character, such as a custom mask, autocomplete widget, or other keyboard-sensitive control. Playwright marks locator.type() and page.type() deprecated, so new tests should choose between these locator-based methods according to the page behavior they need.

The short decision

Situation Use What the page receives Status
Ordinary input, textarea, or contenteditable field locator.fill(value) The value is set and an input event is triggered Recommended default
UI logic depends on each character being typed locator.pressSequentially(text) Keyboard and input events for each character Current locator-level solution
Existing code uses locator.type() Migrate to fill() or pressSequentially() Choose based on the event behavior required Deprecated
Existing code uses page.type() Migrate to a locator method Use a targeted locator instead of a page-level selector action Deprecated

The choice is not about making automation look more human. It is about whether assigning the final value is sufficient or the application has code that reacts to keyboard activity one character at a time.

What locator.fill() does

fill() is Playwright’s normal field-entry operation. It waits for the locator, performs the required actionability checks, focuses the element, sets the value, and triggers an input event. It accepts an input, textarea, or [contenteditable] element and can clear an existing value by receiving an empty string.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prefer a semantic locator when the page exposes one. For example, getByLabel() usually remains readable when the visual layout or CSS classes change:

import { test, expect } from '@playwright/test';

test('submits a sign-in form', async ({ page }) => {
  await page.goto('https://example.com/login');

  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('correct horse battery staple');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

This is generally faster and less fragile than reproducing a series of key presses. It also avoids tying the test to timing assumptions about how quickly a person might type.

When fill is the right fit

  • A standard text, email, password, search, or multiline field only needs a value.
  • Your application updates state from the normal input event.
  • You want to replace whatever is already in the field, including clearing it.
  • The test should express user intent (“the email field contains this value”) rather than a particular keyboard sequence.

What locator.pressSequentially() does

pressSequentially(text) focuses the locator and sends the characters one by one. For each character, Playwright produces the keyboard activity associated with typing—keydown, keypress/input, and keyup. That makes it the appropriate locator-level operation when application behavior depends on those events rather than merely on the final value.

import { test, expect } from '@playwright/test';

test('drives a keyboard-sensitive search box', async ({ page }) => {
  await page.goto('https://example.com');

  const search = page.getByRole('combobox', { name: 'Search' });
  await search.pressSequentially('playwright');

  await expect(page.getByRole('option', { name: /playwright/i })).toBeVisible();
});

Use this deliberately for controls such as a formatter that reacts on every key, an autocomplete that opens only after keyboard events, or code that listens to keydown or keyup. If the same behavior works after a direct fill, prefer fill() and keep the test simpler.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Character-by-character typing is not automatically more realistic

Sequential input is slower and introduces more event activity, but that is not a reason to use it everywhere. A test should reproduce the contract the application depends on. If that contract is “the field has this value,” fill it. If it is “each key invokes keyboard logic,” send sequential keys.

Why locator.type() and page.type() should not be used in new code

Playwright’s current guidance deprecates locator.type(). The documented replacement is fill() in most cases and pressSequentially() when special keyboard handling requires one-by-one events. The page-level page.type(selector, text) API is deprecated as well; migrate to a locator and then select the appropriate operation.

// Old locator-level code
await page.locator('#coupon').type('SAVE20');

// Normal replacement
await page.locator('#coupon').fill('SAVE20');

// Replacement when key events are required
await page.locator('#coupon').pressSequentially('SAVE20');

Do not migrate every old type() call mechanically to pressSequentially(). First determine whether the old test actually depended on per-character events. Most ordinary fields should become fill().

Event behavior: fill, sequential typing, and the Keyboard API

API Targeting Event behavior Typical use
locator.fill() Targets a specific locator Sets the value and triggers input Normal form entry
locator.pressSequentially() Targets a specific locator Sends keyboard and input events for each character Keyboard-sensitive widgets
keyboard.type() Uses the currently focused page element Key and input events per character Lower-level keyboard control
keyboard.insertText() Uses the currently focused page element Only an input event; no keydown, keyup, or keypress Inserting text when key events are explicitly unnecessary

keyboard.type() and keyboard.insertText() are not direct substitutes for a locator-targeted fill. They operate through keyboard focus, so a preceding focus action and the currently focused element matter. For field entry, Playwright’s own API guidance is to use locator.fill() in most cases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A practical choice workflow

  1. Identify the control. Use a stable, user-facing locator such as getByLabel(), getByRole(), or a deliberately scoped locator.
  2. Try fill() first. Confirm that the field value changes and the application updates as expected.
  3. Check the failure behavior. If an autocomplete, mask, validator, or shortcut does not run because it listens for keyboard events, switch that field to pressSequentially().
  4. Keep the smallest operation that passes. Do not make every field sequential merely because one control needs it.
  5. Replace deprecated calls during maintenance. Decide the event requirement for each old type() call instead of applying a blind search-and-replace.

Common cases and edge conditions

React, Vue, and other controlled fields

For a controlled text field whose state responds to the normal input event, fill() is normally the clearest operation. If the component has additional logic bound specifically to keyboard events, use pressSequentially() for that component and leave unrelated fields on fill().

Autocomplete and masks

A suggestion list or formatting mask may update after each key. If filling the final string does not open suggestions or apply the mask, use sequential pressing and assert the resulting UI, not just the text value.

Clearing a field

Call await locator.fill('') to clear a supported field. This keeps clearing and entering consistent and avoids a separate keyboard shortcut sequence.

Contenteditable regions

fill() supports [contenteditable]. If the editor implements keyboard-only behavior, use a locator and pressSequentially() instead. Verify the editor’s resulting state with an assertion that reflects what the user can see.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fields that are not text-entry controls

fill() is for inputs, textareas, and contenteditable elements. Do not use it as a universal replacement for selecting an option, clicking a button, or interacting with a non-text widget; choose the operation that matches that control.

Troubleshooting

Symptom Likely cause Fix
The value is present, but suggestions never appear The widget listens for per-character keyboard events Use pressSequentially() and wait for/assert the suggestion state
A field cannot be filled The locator resolves to an unsupported element or the element is not actionable Check that it is an input, textarea, or contenteditable element; use a more precise locator and let Playwright’s actionability checks identify blocking UI
A migrated test behaves differently from type() The old test depended on keyboard events Use pressSequentially() for that field; otherwise use fill()
A keyboard API writes into the wrong element Focus is on a different element Prefer a locator method, or explicitly focus the intended element before a lower-level Keyboard API call
Assertions pass intermittently after entry The test checks too early or relies on an asynchronous UI update Assert the user-visible result with Playwright’s web-first assertions instead of adding arbitrary delays

Reliability, speed, and maintainability

  • Reliability: fill() has a smaller event surface, so fewer application handlers can interfere with the operation.
  • Speed: assigning a value is generally less work than dispatching a complete keyboard sequence. Use sequential input only where its events are part of the behavior under test.
  • Maintainability: locator-based calls state both the target and the intent. They are easier to review than page-level selector typing or a low-level sequence whose purpose is unclear.
  • Debugging: when a field fails, first determine whether the failure is targeting/actionability, value handling, or keyboard-event handling. That diagnosis points directly to the appropriate API.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is to obtain a clean screenshot of a page after testing—or to capture pages without maintaining a browser setup—ScreenshotNeo provides a website screenshot API and MCP server. One request returns a PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

See the ScreenshotNeo API documentation for the complete option set. A basic cURL call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in 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 buffer = Buffer.from(await res.arrayBuffer());

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes the features; the Free plan provides 1,000 screenshots each month without a card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to try it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

Is locator.type() removed?

It is deprecated, not presented as the API to choose for new tests. Existing tests may continue to run according to the Playwright release you use, but migration to fill() or pressSequentially() avoids building new code on a deprecated method.

Does fill() trigger keyboard events?

The documented behavior is that it focuses and fills the control and triggers an input event. If your application requires a keydown/keypress/keyup sequence for each character, use pressSequentially() instead.

When should I use keyboard.insertText()?

Use it only when you intentionally need to insert text into the currently focused element without keyboard events. It dispatches an input event but no keydown, keyup, or keypress events, so it is not a replacement for sequential typing.

Frequently Asked Questions

Can I use fill() to replace an existing value?

Yes. Calling fill() with the new string replaces the current value; calling it with an empty string clears a supported field.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which method should a new test start with?

Start with a semantic locator and locator.fill(). Change only the specific field that demonstrably requires per-character keyboard handling to locator.pressSequentially().

Why prefer a locator over page.type()?

Locator methods keep the target and action together, while page.type() is deprecated and relies on a page-level selector pattern.

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.