Locators find elements; actions make things happen. In this lesson you will learn every Playwright action a tester needs, how auto-waiting protects each one, and which method to choose, with runnable examples on free practice pages.
📖 What You’ll Learn: actionability checks, click options, dblclick and right-click, fill vs pressSequentially, clear, press and the keyboard API, check/uncheck/setChecked, hover, focus, drag and drop, scrolling, and reading values from the page.
🤖 How Playwright Actions Work
An action is anything a user does: click, type, tick a checkbox, press a key, hover, drag. In Playwright you call actions on a locator, and Playwright handles the timing for you.
Before each action, Playwright runs actionability checks and waits until they pass (up to the timeout):
| Check | click | fill | check | hover |
|---|---|---|---|---|
| Attached to the DOM | ✅ | ✅ | ✅ | ✅ |
| Visible | ✅ | ✅ | ✅ | ✅ |
| Stable (not animating) | ✅ | ✅ | ✅ | |
| Receives events (not covered) | ✅ | ✅ | ✅ | |
| Enabled | ✅ | ✅ | ✅ | |
| Editable | ✅ |
This is why you do not need sleeps before clicks. If a check never passes, the error tells you exactly which one failed, for example “element is not visible” or “another element would receive the click”. Learn more in Playwright waits.
🖱️ click() and Its Options
const login = page.getByRole('button', { name: 'Login' });
await login.click(); // left click
await login.click({ button: 'right' }); // right click
await login.click({ clickCount: 2 }); // same as dblclick()
await login.dblclick(); // double click
await login.click({ modifiers: ['Shift'] }); // Shift + click ('Control', 'Alt', 'Meta')
await login.click({ position: { x: 5, y: 5 } }); // click a specific point
await login.click({ trial: true }); // run checks only, do not click
Example on a practice page that adds a button every time you click:
import { test, expect } from '@playwright/test';
test('add three elements', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/add_remove_elements/');
const add = page.getByRole('button', { name: 'Add Element' });
for (let i = 0; i < 3; i++) {
await add.click();
}
await expect(page.locator('.added-manually')).toHaveCount(3);
await page.locator('.added-manually').first().click(); // delete one
await expect(page.locator('.added-manually')).toHaveCount(2);
});
⚠️ Common mistake: Avoid click({ force: true }). It skips the actionability checks and clicks even when the element is hidden or covered, so your test can pass while a real user would be blocked. Fix the cause (wait for the overlay to disappear) instead.
⌨️ Typing Text: fill(), clear() and pressSequentially()
const username = page.getByPlaceholder('Username');
await username.fill('standard_user'); // clears the field, then sets the value in one step
await username.clear(); // empties the field
await username.pressSequentially('standard_user', { delay: 100 }); // types key by key
| Method | Use it when |
|---|---|
fill() | Almost always: fastest and most reliable for inputs and textareas |
pressSequentially() | The page reacts to each keystroke (autocomplete, live search, input masks) |
clear() | You need an empty field |
📝 Note: locator.type() is deprecated. Use fill(), or pressSequentially() when you really need individual key events.
Verify what was typed with toHaveValue:
test('number input', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/inputs');
const number = page.locator('input[type="number"]');
await number.fill('42');
await expect(number).toHaveValue('42');
await number.press('ArrowUp'); // increments the number input
await expect(number).toHaveValue('43');
});
🔑 Pressing Keys: press() and page.keyboard
await page.getByLabel('Search').press('Enter');
await page.getByLabel('Search').press('Control+A'); // key combination
await page.getByLabel('Search').press('Backspace');
// Page-level keyboard (acts on whatever has focus)
await page.keyboard.press('Tab');
await page.keyboard.type('Hello');
await page.keyboard.down('Shift');
await page.keyboard.press('ArrowLeft');
await page.keyboard.up('Shift');
Common key names: Enter, Tab, Escape, Backspace, Delete, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, PageDown, F1 to F12, and modifiers Shift, Control, Alt, Meta. Use ControlOrMeta for shortcuts that are Ctrl on Windows/Linux and Cmd on macOS.
test('key press is detected', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/key_presses');
await page.locator('#target').press('Escape');
await expect(page.locator('#result')).toContainText('You entered:');
});
☑️ Checkboxes and Radio Buttons: check(), uncheck(), setChecked()
test('toggle checkboxes', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/checkboxes');
const boxes = page.locator('#checkboxes input[type="checkbox"]');
await boxes.nth(0).check(); // ticks it (no-op if already ticked)
await expect(boxes.nth(0)).toBeChecked();
await boxes.nth(1).uncheck();
await expect(boxes.nth(1)).not.toBeChecked();
await boxes.nth(1).setChecked(true); // set to a specific state
await expect(boxes.nth(1)).toBeChecked();
});
Prefer check() over click() for checkboxes: check() is idempotent and verifies the final state, while click() simply toggles. Radio buttons work the same way: page.getByLabel('Express delivery').check().
🔽 Dropdowns, File Uploads and Dialogs
These have their own detailed lesson, Alerts, Popups, Dropdowns, iFrames and Tabs. Quick reference:
await page.locator('#dropdown').selectOption({ label: 'Option 2' }); // native select
await page.locator('#file-upload').setInputFiles('data/sample.txt'); // upload
page.once('dialog', d => d.accept()); // JS alert
🖐️ Hover, Focus, Drag and Scroll
test('hover reveals the caption', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/hovers');
await page.locator('.figure').nth(1).hover();
await expect(page.getByText('name: user2')).toBeVisible();
});
test('right-click opens a context-menu alert', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/context_menu');
page.once('dialog', async dialog => {
expect(dialog.message()).toBe('You selected a context menu');
await dialog.accept();
});
await page.locator('#hot-spot').click({ button: 'right' });
});
await page.getByLabel('Email').focus(); // focus without clicking
await page.getByLabel('Email').blur(); // trigger validation on blur
await page.locator('#source').dragTo(page.locator('#target')); // drag and drop
await page.getByText('Footer links').scrollIntoViewIfNeeded(); // scroll to an element
await page.mouse.wheel(0, 800); // scroll the page down
Playwright scrolls elements into view automatically before acting, so you rarely need to scroll yourself. Explicit scrolling is useful for infinite-scroll pages and lazy-loaded content.
📖 Reading Values from the Page
Actions often go together with reading data:
| Method | Returns |
|---|---|
textContent() | All text, including hidden text (string or null) |
innerText() | Visible, rendered text |
inputValue() | Current value of an input, textarea or select |
getAttribute('href') | An attribute value |
allTextContents() | Text of every matching element (string array) |
count() | Number of matching elements |
isVisible(), isChecked(), isEnabled() | Current state, checked once without waiting |
💡 Tip: To verify something, use web-first assertions such as toHaveText, toHaveValue and toBeChecked. They wait and retry. Use the read methods above only when you need the value for later steps.
📝 Assignment
- On https://the-internet.herokuapp.com/add_remove_elements/ add five elements, delete two, and assert three remain.
- On https://the-internet.herokuapp.com/checkboxes make both boxes checked using
check(), then both unchecked usingsetChecked(false), with assertions after each step. - On https://the-internet.herokuapp.com/inputs type
100, pressArrowDownthree times and assert the value is97. - On https://the-internet.herokuapp.com/hovers hover each of the three figures in a loop and assert the matching caption (
name: user1toname: user3) is visible. - On https://www.saucedemo.com log in using
pressSequentiallyfor the username andpress('Enter')in the password field instead of clicking the button. - Bonus: read all three hover captions with
allTextContents()and assert the array length is 3.
❓ FAQ
🤔 What is the difference between fill and type in Playwright?
fill() sets the whole value at once and is the recommended method. The old type() is deprecated; its replacement pressSequentially() types key by key for pages that react to each keystroke.
🤔 How do I double-click or right-click in Playwright?
Use locator.dblclick() for a double click and locator.click({ button: 'right' }) for a right click.
🤔 How do I press Enter in Playwright?
await locator.press('Enter') on an element, or await page.keyboard.press('Enter') for whatever has focus.
🤔 Why does Playwright say “element is not visible” or “intercepts pointer events”?
The element is hidden or covered by another element, such as a loader or cookie banner. Wait for the overlay to disappear or close it first, instead of using force: true.
🤔 How do I check a checkbox in Playwright?
await page.getByLabel('Remember me').check(), then verify with await expect(...).toBeChecked().
🎯 Next Steps
- 🎭 Record Tests with Playwright Codegen
- 🎭 Alerts, Popups, Dropdowns, iFrames and Tabs
- 🎭 Playwright Assertions: Complete Guide
- 🎭 Playwright Tutorial with TypeScript
🎬 Watch the full Playwright tutorial series on YouTube!
Subscribe to SqaToolsByDY • Practice on sqatools.in
2 thoughts on “Playwright Actions: Click, Fill, Type, Check, Press, Hover and More”