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");
});
ActionDoesUse instead when
fillClears and sets the value in one goAlways, for text inputs
pressSequentiallyTypes character by characterA handler needs per-keystroke events
pressSends one key or chordEnter, Escape, Tab, Control+A
check / uncheckSets checkbox state idempotentlyAlways, for checkboxes
selectOptionChooses by value, label or indexFor native selects only
setInputFilesAttaches filesFile 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: true skips 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.
  • dragTo does the move-hold-move-release sequence for you; drop in a slow, animated target can still need a manual hover() 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();
NeedMethod
Set a text inputlocator.fill()
Send keys to the focused elementpage.keyboard.press()
Attach a filelocator.setInputFiles()
Bring an element into viewlocator.scrollIntoViewIfNeeded()
Move the mouse somewherepage.mouse.move(x, y)
Select text rangelocator.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.

Auto-waiting, timeouts and flakiness Locators and assertions

Last refreshed 2026-09-18.