What are Playwright Actions?
Playwright actions are the methods you call on a Locator or Page to simulate real user interactions with a web application — clicking a button, typing into an input, uploading a file, dragging a card across a Kanban board, or pressing a keyboard shortcut. They are the verbs of your test suite, and Playwright ships one of the richest action APIs in the industry.
Every action automatically waits until the target element passes actionability checks — attached to the DOM, visible, stable, enabled, and able to receive events. That single design decision is why Playwright suites tend to be dramatically less flaky than legacy Selenium suites, where every action needed a manual wait wrapper to be reliable.
Why Actions Matter
Choosing the right action for the job is the difference between a readable, self-documenting test and a brittle wall of coordinates. Prefer semantic actions on semantic locators — page.getByRole('button', { name: 'Submit' }).click() — over generic mouse coordinates. The test reads like a user story, and any accessibility regression that breaks the button's role will surface as a clear failure instead of a mystery.
Prerequisites
- Node.js 18+ and a Playwright project (npm init playwright@latest).
- Familiarity with Playwright locators — especially getByRole, getByLabel, and getByTestId.
- Comfort with async/await syntax; every action is awaited.
- A sample application to drive — the docs use https://demo.playwright.dev/todomvc.
click()
Click is by far the most commonly used action. It fires a full mousedown/mouseup pair at the element's centre, respects Playwright's auto-waiting, and can be tuned with options like button, modifiers, and clickCount.
await page.getByRole('button', { name: 'Login' }).click();Right-click for context menus:
await page.locator('.menu').click({ button: 'right' });Modifier click (Ctrl+click to open in new tab, Shift+click for multi-select):
await page.getByRole('link', { name: 'Docs' }).click({ modifiers: ['Control'] });dblclick()
Performs two rapid clicks. Use it for desktop-style behaviour such as renaming a file, opening a workspace item, or entering an inline editor.
await page.locator('.file').dblclick();check() & uncheck()
Prefer check() over click() for checkboxes and radios — it is idempotent (calling check() on an already-checked box is a no-op) and it reads more clearly.
await page.getByRole('checkbox', { name: 'I agree' }).check();
await page.getByRole('checkbox', { name: 'Newsletter' }).uncheck();fill()
fill() clears the input and sets its value in a single call. It is faster and more reliable than press-by-press typing for standard form fields.
await page.getByLabel('Email').fill('admin@test.com');Use pressSequentially() only when the application listens to individual keystrokes (e.g. autocomplete widgets that filter on every keydown).
clear()
Removes the current value without setting a new one — useful when you want to test 'required field' validation.
await page.locator('#username').clear();press()
Sends a single key event to the focused element. Great for submitting a form with Enter or navigating a listbox with arrow keys.
await page.getByLabel('Search').press('Enter');Common key names:
- Enter
- Escape
- Tab
- ArrowDown
- ArrowUp
- Backspace
- Control+A
- Meta+K
selectOption()
Native select-element helper. Accepts a value, a label, or an index — pass an array to select multiple in a multi-select.
await page.locator('#country').selectOption('USA');
await page.locator('#country').selectOption({ label: 'United States' });
await page.locator('#tags').selectOption(['bug', 'urgent']);hover()
Moves the mouse over the element. Essential for testing dropdown menus, tooltips, and hover-only affordances.
await page.getByRole('button', { name: 'Products' }).hover();dragAndDrop()
One-liner for HTML5 drag-and-drop. Ideal for Kanban boards, dashboard reordering, and file organisers.
await page.dragAndDrop('#source', '#target');For libraries that use non-standard drag protocols (react-dnd, Sortable.js), fall back to the mouse API — move, down, move, up.
File Upload
setInputFiles works directly against the file input, bypassing the OS file dialog entirely — no fragile OS-level automation needed.
await page.locator('input[type=file]').setInputFiles('resume.pdf');Upload multiple files at once:
await page.locator('input[type=file]').setInputFiles(['resume.pdf', 'image.png']);Clear an existing selection by passing an empty array.
Keyboard Actions
page.keyboard exposes low-level typing and shortcut helpers scoped to the whole page rather than a specific element.
await page.keyboard.type('Playwright');
await page.keyboard.press('Control+A');
await page.keyboard.press('Delete');Mouse Actions
For canvas apps, drawing tools, and any UI where clicks alone will not do — page.mouse gives you precise movement, button state, and wheel control.
await page.mouse.move(300, 200);
await page.mouse.down();
await page.mouse.move(400, 300);
await page.mouse.up();Scroll
Scroll the viewport with the wheel API, or scroll a specific element into view before interacting with it.
await page.mouse.wheel(0, 500);
await page.locator('.footer').scrollIntoViewIfNeeded();Common Pitfalls
- Reaching for click({ force: true }) to bypass a failing actionability check — you are hiding a real UI bug (element covered, disabled, or off-screen). Fix the UI or the locator instead.
- Using page.mouse.click(x, y) with coordinates — brittle to any layout change. Always click a locator.
- Wrapping a fill() in try/catch to survive missing inputs — the correct move is to assert the input is visible first, or fix the flow.
- Calling type() instead of fill() on a large form — dramatically slower without any behavioural benefit for standard inputs.
- Using page.keyboard.press for a shortcut that should be scoped to a specific input — focus the input first, then call locator.press().
Debugging Tips
- Run with --headed --debug (or PWDEBUG=1) to step through actions in the Playwright Inspector.
- Enable trace: 'on-first-retry' in playwright.config.ts — the trace records every action and lets you scrub through the DOM at each step.
- When a click seems to miss, screenshot the element with await locator.screenshot({ path: 'button.png' }) to confirm it is where you think it is.
- For flaky drag-and-drop, log the source and target bounding boxes and verify they do not overlap unexpectedly.
When to Use — and When Not To
Use semantic actions (click, fill, check, selectOption) for every real user interaction — they are readable, resilient, and get you the auto-waiting guarantee for free.
Only drop down to page.mouse and page.keyboard for genuinely low-level scenarios: canvas drawing, custom drag protocols, native context menus, or unusual keyboard-driven UIs. If you find yourself using coordinates in a business flow, step back and add a better locator or ask the product team for a stable data-testid.
Enterprise Best Practices
- Prefer semantic locators (getByRole, getByLabel) as the target of every action.
- Never use raw coordinates for a business flow — reserve mouse.move for canvas-style UIs only.
- Avoid { force: true } — it silences the actionability check that keeps your suite honest.
- Trust Playwright's auto-waiting; do not chain waitForTimeout before or after actions.
- Wrap repeated multi-step actions (login, add-to-cart) inside Page Object methods with descriptive names.
- Keep keyboard shortcuts consistent with the product's documented shortcuts so tests double as living documentation.
FAQ
Difference between click() and dblclick()?
click() fires a single click. dblclick() fires two rapid clicks and the browser generates a dblclick event only if the application listens for one.
Why use fill() instead of type()?
fill() clears the field and sets the value in a single fast operation. type() (or pressSequentially()) simulates every keystroke — use it only when the application reacts to individual keydown events, such as autocomplete widgets.
When should I use dragAndDrop()?
Whenever the application uses standard HTML5 drag-and-drop — Kanban boards, dashboards, file organisers, workflow builders. For libraries that use custom pointer protocols, fall back to the mouse API.
How do I upload a file without triggering the OS dialog?
Call setInputFiles() directly on the <input type=file> element — Playwright bypasses the OS dialog entirely. If the app hides the input behind a styled button, use page.on('filechooser') and pass the file path to the chooser event.
Can I simulate real hardware keyboard events?
Yes — page.keyboard.down('Shift'), page.keyboard.press('KeyA'), page.keyboard.up('Shift') gives you full control over modifier state and individual key events.
Summary
Playwright offers one of the richest, most user-realistic action APIs in modern test automation. Combining semantic locators with auto-waiting actions produces suites that read like specifications, survive redesigns, and stay under a one-percent flake rate at scale.
Master the semantic actions first — click, fill, check, selectOption, hover, dragAndDrop, setInputFiles — and reach for page.mouse or page.keyboard only when a legitimate low-level scenario requires it. Do that consistently and your tests become the closest thing the industry has to executable product documentation.
Get Playwright tutorials in your inbox
Weekly tips, real-world examples, and framework patterns – no spam, unsubscribe anytime.
Playwright Framework Series
View all →- 1Getting Started with Playwright: Installation, Setup, and Your First Test
- 2Playwright Locators: The Complete Guide with Real-World Examples
- 3Playwright Actions: Complete Guide to Click, Fill, Hover, Keyboard, Mouse & File Upload
- 4Playwright Assertions: The Complete Guide with Real-World Examples
- 5Playwright Auto Waiting: The Complete Guide with Real-World Examples
- 6Playwright Fixtures: The Complete Guide with Real-World Examples
- 7Playwright Browser Context & Multiple Tabs: The Complete Guide with Real-World Examples
- 8Playwright Authentication & Session Management: Complete Guide with Enterprise Examples
- 9Playwright Network Interception & API Mocking: Complete Guide with Real-World Examples
- 10Playwright Page Object Model (POM): The Complete Guide with Enterprise Examples
- 11Page Object Model with Playwright: A Practical Guide
- 12How to Build a Robust Professional Playwright Framework from Scratch (Step-by-Step)
- 13Building an Enterprise Playwright Framework from Scratch
API Testing Series
View all →- 1Playwright API Testing: The Complete Guide with Real-World Examples
- 2Part 1 : Playwright API Testing Tutorial: Build Enterprise-Level API Automation Framework
- 3Part 2: Playwright API Testing Tutorial: Setting Up Project & Sending Your First API Request
- 4Part 3: Mastering CRUD Operations in Playwright API Testing
- 5Part 4: Authentication in Playwright API Testing (Bearer Token, JWT, API Key & Reusable Fixtures)
- 6Part 5: Building an Enterprise-Level Playwright API Automation Framework
- 7Part 6: API Models, Schema Validation & Test Data Management in Playwright
- 8Part 7: Custom Fixtures, Hooks, Logging & Parallel Execution in Playwright API Testing
- 9Part 8: Building a Production-Ready Playwright API Framework (Environment Management, CI/CD, Reporting & Best Practices)
- 10Part 9: Advanced Playwright API Testing Techniques for Enterprise Automation
- 11Part 10: Building a Complete Enterprise Playwright API Automation Framework (Final Part)
- 12API Testing with Playwright: Request Context, Auth, and Assertions