PRACTICAL GUIDE / playwright mouse and keyboard actions
Mouse, Keyboard, and Drag-and-Drop Workflows in Playwright
Model reliable Playwright mouse, keyboard, and drag-and-drop workflows with semantic actions, deliberate event sequences, and stable coordinates.
In this guide9 sections
- Choose the Interaction Contract First
- Model Keyboard Workflows Around Focus
- Test Shortcuts Across Operating Systems
- Use Mouse Coordinates in a Stable Reference Frame
- Start Drag-and-Drop With dragTo
- Account for Touch and Mobile Input
- Diagnose Event Sequence Failures
- Operational Checklist for Complex Input
- Action Plan
What you will learn
- Choose the Interaction Contract First
- Model Keyboard Workflows Around Focus
- Test Shortcuts Across Operating Systems
- Use Mouse Coordinates in a Stable Reference Frame
Input automation becomes fragile when a test reproduces hand movement without stating user intent. Ten mouse coordinates and four key events may happen to complete a workflow, yet nobody reviewing the test can tell which control should own focus, which drag threshold matters, or what state proves success.
Playwright offers two useful levels. Locator actions express interaction with a named control and include readiness checks. Device APIs such as page.keyboard and page.mouse expose event sequences and coordinates. Choose the highest level that still exercises the behavior under test, then assert the application state produced by the sequence.
Choose the Interaction Contract First
For text fields, buttons, checkboxes, and native select controls, begin with fill, click, check, and other locator actions. They communicate the target and benefit from Playwright's actionability model. Drop to keyboard or pointer primitives when the event sequence itself matters: an editor shortcut, a press-and-hold command, a drawing canvas, a range selection, or a custom drag surface.
The official Playwright input guide distinguishes normal text entry, character-by-character typing, key presses, mouse actions, and drag operations. That separation should appear in test design too. A search test normally cares that a query value is entered, while an autocomplete test may care that each input event updates suggestions.
Animated field map
From Interaction Intent to Verified State
Select a target and input level based on the event contract, then assert what the application changed.
01 / interaction intent
Interaction intent
Name the user behavior and the events that actually matter.
02 / target locator
Target locator
Resolve the semantic control or stable interaction surface.
03 / device sequence
Input device sequence
Send a locator action or deliberate keyboard and pointer steps.
04 / event handlers
Application handlers
Let focus, pointer, keyboard, and drag handlers process input.
05 / state assertion
State assertion
Verify the durable UI or data result of the interaction.
A low-level test is not automatically more realistic. It can bypass locator checks, hard-code geometry, and expose browser details that the product does not promise. Use low-level control deliberately and keep the observable business result at the center of the test.
Model Keyboard Workflows Around Focus
Keyboard events go to the currently focused element. Before pressing a shortcut, prove where focus is or focus a semantic locator through a user-visible step. A test that calls page.keyboard.press('Enter') without establishing focus depends on whatever previous action happened to leave active.
import { expect, test } from '@playwright/test'
test('keyboard user creates a command palette item', async ({ page }) => {
await page.goto('/workspace')
await page.getByRole('button', { name: 'Open command palette' }).focus()
await page.keyboard.press('Enter')
const palette = page.getByRole('dialog', { name: 'Command palette' })
await expect(palette).toBeVisible()
const search = palette.getByRole('combobox', { name: 'Commands' })
await expect(search).toBeFocused()
await search.fill('Create issue')
await search.press('ArrowDown')
await search.press('Enter')
await expect(page.getByRole('heading', { name: 'New issue' })).toBeVisible()
})Use fill() for setting an input value efficiently. Use pressSequentially() only when the application behavior depends on individual key events, such as debounce logic or an input mask. For a shortcut, locator.press() is often clearer because it couples the keystroke to its intended recipient.
Press-and-hold workflows require balanced down and up calls. Wrap release in finally so a failed assertion does not leave the modifier pressed for later steps.
await page.keyboard.down('Shift')
try {
await page.getByRole('option', { name: 'Report Q2' }).click()
await page.getByRole('option', { name: 'Report Q4' }).click()
} finally {
await page.keyboard.up('Shift')
}Test Shortcuts Across Operating Systems
Product shortcuts may map to Meta on macOS and Control elsewhere. Keep the mapping in one helper or project option rather than scattering platform branches through tests. More importantly, verify the resulting command and ensure the shortcut does not conflict with a browser-reserved action in the environments you support.
function primaryModifier(): 'Meta' | 'Control' {
return process.platform === 'darwin' ? 'Meta' : 'Control'
}
const editor = page.getByRole('textbox', { name: 'Issue description' })
await editor.fill('Release blocker')
await editor.press(`${primaryModifier()}+a`)
await editor.press(`${primaryModifier()}+b`)
await expect(page.getByRole('button', { name: 'Bold' })).toHaveAttribute(
'aria-pressed',
'true',
)The assertion checks editor state instead of merely recording that keyboard events occurred. For accessibility coverage, also test Tab and Shift+Tab order, Escape behavior, focus restoration after dialogs, and visible focus styling. Shortcuts supplement navigable controls; they should not be the only way to perform an essential command.
Use Mouse Coordinates in a Stable Reference Frame
page.mouse operates in main-frame CSS pixels relative to the viewport. Hard-coded page coordinates are therefore vulnerable to responsive layout and scrolling. Calculate points from the current bounding box of a stable surface, then express movement relative to that box.
const canvas = page.getByTestId('floor-plan-canvas')
await expect(canvas).toBeVisible()
const box = await canvas.boundingBox()
if (!box) throw new Error('Canvas has no visible bounding box')
const start = { x: box.x + box.width * 0.25, y: box.y + box.height * 0.5 }
const end = { x: box.x + box.width * 0.75, y: box.y + box.height * 0.5 }
await page.mouse.move(start.x, start.y)
await page.mouse.down()
await page.mouse.move(end.x, end.y, { steps: 12 })
await page.mouse.up()
await expect(page.getByTestId('selection-width')).toHaveText('50%')Fix the project viewport for coordinate-sensitive scenarios and avoid relying on the machine's desktop size. If browser zoom or transforms are part of the product, test them explicitly because the relationship between visual geometry and application coordinates becomes part of the contract.
Start Drag-and-Drop With dragTo
For a normal draggable element and drop target, locator.dragTo() provides the clearest intent. Scope source and destination semantically, perform the drag, and assert an order or state change that persists after the gesture.
const source = page
.getByRole('listitem')
.filter({ has: page.getByText('Investigate login failure', { exact: true }) })
const destination = page.getByRole('region', { name: 'In progress' })
await source.dragTo(destination)
await expect(
destination.getByText('Investigate login failure', { exact: true }),
).toBeVisible()
await expect(page.getByRole('status')).toHaveText('Issue moved')Custom components sometimes require movement beyond a threshold, an intermediate hover over a lane, or a particular handle. Then use source and target bounding boxes with page.mouse, but retain semantic locators to discover those boxes. Avoid copying coordinates from a recording because they describe one layout, not the interaction rule.
Some applications implement HTML drag events with a data payload, while others use pointer events. Do not immediately inject synthetic dragstart and drop events. That may bypass the pointer path and actionability the browser scenario is meant to cover. Establish which event contract the component implements before selecting the fallback.
Hover-driven menus deserve the same state-machine treatment. Hover the named trigger, assert that the menu is visible, move to a menu item, and verify the selected result. If the product intentionally uses an open delay or close corridor, assert the visible state instead of sleeping for the animation duration. Keep the pointer inside the documented trigger and menu geometry; moving directly by coordinates can accidentally cross another element and close the menu. Retain keyboard coverage for the same command because a feature available only through hover is inaccessible to touch and keyboard users.
Double-click behavior should be isolated from single-click behavior. locator.dblclick() sends the expected sequence, but the test must prove the product did not execute an unintended single-click action twice before handling the double click. Seed a state where the two outcomes differ, then assert the final command and any counter or selection state that would expose duplicate processing.
Account for Touch and Mobile Input
A mobile viewport does not automatically turn mouse input into touch input. A project must use a touch-capable context when the scenario relies on touchscreen behavior, and page.touchscreen.tap() represents a tap rather than a full multi-touch gesture system. Keep swipe, pinch, and platform gesture expectations aligned with what the browser automation surface can actually produce.
For responsive controls that respond identically to pointer input, test the business workflow through locator actions in device projects. Add a smaller set of touch-specific cases only where handlers or interaction rules differ. This avoids duplicating every scenario while still covering the risk introduced by touch behavior.
Diagnose Event Sequence Failures
When an interaction fails, determine whether targeting, focus, geometry, event order, or application state is responsible.
| Symptom | Likely issue | Evidence to collect |
|---|---|---|
| Key press affects the page, not the field | Focus is elsewhere | Active element, focus trace, and focused assertion |
| Shortcut works locally only | Platform modifier differs | Worker OS, project name, and emitted key combination |
| Drag starts but never drops | Threshold or target path is wrong | Source/target boxes and pointer event log |
| Pointer lands outside the surface | Scroll or responsive layout changed geometry | Screenshot, viewport, scroll position, and bounding box |
| UI changes but data reverts | Optimistic update failed | Network response and persisted state assertion |
| Click works, keyboard fails | Control lacks keyboard semantics | Role, focusability, and key handler behavior |
Temporary in-page event logging can reveal order, but remove broad listeners after diagnosis because they add noise and can affect timing. The trace should remain the default artifact because it connects actions, DOM snapshots, and application output.
Operational Checklist for Complex Input
- Start with a locator action and move lower only when event detail is relevant.
- Establish and assert focus before sending keyboard input.
- Balance every modifier or mouse
downwith anup, even on failure. - Map platform shortcuts centrally and verify the command result.
- Derive coordinates from current bounding boxes inside a fixed viewport.
- Prefer
dragTo()before implementing a manual pointer path. - Assert persisted state, not merely animation or a transient toast.
- Cover keyboard reachability and focus restoration for essential actions.
- Capture geometry, focus, project, and trace data for CI-only failures.
Action Plan
Choose one complex workflow and label every step as target discovery, input sequence, or result assertion. Replace coordinates with semantic locator actions wherever event geometry is irrelevant. For the remaining low-level steps, calculate positions from current boxes, make focus and modifier state explicit, and add an assertion against durable application state. That structure produces tests that still read like user behavior while retaining the precision needed for editors, canvases, shortcuts, and drag surfaces.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Playwright documentation
Microsoft
Canonical API, locator, fixture, browser, and test-runner behavior.
- 02Playwright best practices
Microsoft
Official guidance for resilient tests, isolation, and user-facing locators.
- 03
FAQ / QUICK ANSWERS
Questions testers ask
Should Playwright tests use locator actions or page.mouse?
Prefer locator actions for ordinary controls because they include targeting and actionability checks. Use page.mouse when coordinates, paths, or low-level pointer sequencing are the behavior being tested, such as a canvas editor.
What is the difference between press() and pressSequentially()?
press() sends a key or shortcut such as Enter or Control+A, while pressSequentially() emits key events for each character in order. Use fill() for normal text entry and reserve sequential typing for applications that react to each keystroke.
How do I drag an element with Playwright?
Start with locator.dragTo() and assert the resulting state. If the application depends on custom pointer thresholds or intermediate moves, use bounding boxes with page.mouse and make the sequence explicit.
Why does drag-and-drop pass locally but fail in CI?
Responsive layout, zoom, scrolling, animation, and different target geometry can invalidate hard-coded coordinates. Calculate points from current bounding boxes and use a fixed project viewport for coordinate-sensitive coverage.
Can Playwright test keyboard accessibility with shortcuts?
Yes, but test focus movement and resulting state, not only whether a key event fired. A keyboard workflow should prove that controls are reachable, focus remains visible, and the shortcut does not trap or misdirect the user.
RELATED GUIDES
Continue the learning route
GUIDE 01
Keyboard Navigation Testing
Learn keyboard navigation testing for web apps: tab order, focus indicators, skip links, modal focus traps, and practical QA test cases you can run today.
GUIDE 02
Playwright Locators Guide: Find Elements Reliably
Playwright locators guide for stable UI tests with role selectors, filters, assertions, strict mode, debugging, and flaky selector fixes in CI.
GUIDE 03
Playwright Tutorial: End-to-End Testing from Scratch
Playwright tutorial for beginners: install, write your first test, TypeScript setup, codegen, fixtures, API testing, debugging, and CI tips in one guide.
GUIDE 04
Flaky Tests: Causes and How to Fix Them
Learn how to fix flaky tests with root cause analysis, stable waits, quarantine strategy, CI retries policy, and practical Playwright examples.