Actions and user interactions
Click, fill, type, press, check, select, upload and drag - and which options you reach for when a real user would not click the centre of a button.
The everyday actions
import { test, expect } from "@playwright/test";
test("a user can update their profile", async ({ page }) => {
await page.goto("/settings");
const name = page.getByLabel("Display name");
await name.fill("Ada Lovelace"); // clears, then types
await page.getByLabel("Bio").pressSequentially("Mathematician", { delay: 20 });
await page.getByLabel("Email me about updates").check();
await page.getByLabel("Weekly").check();
await page.getByLabel("Language").selectOption("en-GB");
await page.getByLabel("Language").selectOption({ label: "English (UK)" });
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("status")).toHaveText("Profile saved");
});| Action | Does | Use instead when |
|---|---|---|
fill | Clears and sets the value in one go | Always, for text inputs |
pressSequentially | Types character by character | A handler needs per-keystroke events |
press | Sends one key or chord | Enter, Escape, Tab, Control+A |
check / uncheck | Sets checkbox state idempotently | Always, for checkboxes |
selectOption | Chooses by value, label or index | For native selects only |
setInputFiles | Attaches files | File inputs and drag-drop zones |
💡
type() is the old name for pressSequentially() and is deprecated. Prefer fill() for ordinary input - it is faster and, unlike typing, it cannot be racing the page's own formatting logic.Clicks that are not a plain click
const row = page.getByRole("row", { name: "Invoice 1042" });
await row.click(); // left, centre, once
await row.dblclick();
await row.click({ button: "right" }); // context menu
await row.click({ modifiers: ["Shift"] }); // range select
await row.click({ position: { x: 10, y: 10 } }); // a specific spot
await row.hover();
await row.click({ trial: true }); // check actionability, do nothing// select a range the way a user does
await page.getByRole("row", { name: "Invoice 1042" }).click();
await page.getByRole("row", { name: "Invoice 1047" }).click({ modifiers: ["Shift"] });
// hover to reveal a menu, then click the item
await page.getByRole("button", { name: "Actions" }).hover();
await page.getByRole("menuitem", { name: "Duplicate" }).click();
// drag and drop
await page.getByTestId("card-1").dragTo(page.getByTestId("column-done"));click({ trial: true })performs the actionability checks without clicking - the fastest way to find out why a click is failing.force: trueskips the checks and clicks anyway. It makes a test pass that a user could not perform, so treat it as a temporary diagnostic, not a fix.dragTodoes the move-hold-move-release sequence for you; drop in a slow, animated target can still need a manualhover()first.- Playwright scrolls the element into view automatically before acting, so an explicit scroll is rarely needed.
Files, keyboard and scrolling
// a single file, or several
await page.getByLabel("Contract").setInputFiles("fixtures/contract.pdf");
await page.getByLabel("Photos").setInputFiles([
"fixtures/one.jpg",
"fixtures/two.jpg",
]);
// an in-memory file, no fixture on disk
await page.getByLabel("Import").setInputFiles({
name: "data.csv",
mimeType: "text/csv",
buffer: Buffer.from("id,name\n1,ada\n"),
});
// clear the selection
await page.getByLabel("Photos").setInputFiles([]);// keyboard focus and chords
await page.getByRole("textbox").focus();
await page.keyboard.press("Control+A");
await page.keyboard.press("Backspace");
await page.keyboard.type("replaced");
// global shortcut
await page.keyboard.press("Control+Shift+P");
// programmatic scrolling, when you need a specific element in view
await page.getByText("Terms and conditions").scrollIntoViewIfNeeded();| Need | Method |
|---|---|
| Set a text input | locator.fill() |
| Send keys to the focused element | page.keyboard.press() |
| Attach a file | locator.setInputFiles() |
| Bring an element into view | locator.scrollIntoViewIfNeeded() |
| Move the mouse somewhere | page.mouse.move(x, y) |
| Select text range | locator.selectText() |
Everything on the page object - keyboard, mouse, touchscreen - acts on the page as a whole and bypasses locator checks. That makes them powerful and easy to misuse: prefer a locator action whenever one exists, and reach for the page-level API only when the interaction genuinely is not element-based.
FAQ
Why does my click time out even though the element is visible?
Visibility is only one of Playwright's actionability checks. A click also requires the element to be stable (not animating), to receive events (nothing overlapping it) and to be enabled. Use
click({ trial: true }) or the trace to see which check failed.When is force: true acceptable?
Almost never in a committed test. It exists for situations where you deliberately test an element a user cannot reach, such as a hidden control exercised through keyboard navigation. If you find yourself adding it to get past a real overlay, fix the overlay interaction instead.
Related
Auto-waiting, timeouts and flakiness Locators and assertions
Last refreshed 2026-09-18.