Clicking, Typing & Interacting
Learn all the ways to interact with page elements
Clicking, Typing & Interacting
In the previous tutorial, we learned how to find elements on the page. Now let's do stuff with them — click buttons, fill forms, drag things around. Playwright makes this surprisingly simple, and handles most of the timing headaches for you.
The Magic of Auto-Waiting
"Do I need to add waits before clicking?"
Nope! Before we dive into actions, you need to understand Playwright's killer feature: auto-waiting.
When you do this:
await page.getByRole('button', { name: 'Submit' }).click();
Playwright automatically:
- Waits for the element to appear in the DOM
- Waits for it to be visible
- Waits for it to be stable (not animating)
- Waits for it to receive pointer events (not covered by other elements)
- Waits for it to be enabled
Only then does it click. No sleep(), no waitForElement(). It just works. How cool is that?
Clicking
Basic Click
// Click a button
await page.getByRole('button', { name: 'Submit' }).click();
// Click a link
await page.getByRole('link', { name: 'Learn more' }).click();
// Click anything
await page.locator('.card').click();
Double Click
// Select a word in text
await page.getByText('Click to edit').dblclick();
// Open a file in a file manager
await page.locator('.file-item').dblclick();
Right Click (Context Menu)
// Open context menu
await page.getByText('Document.pdf').click({ button: 'right' });
// Then interact with the menu
await page.getByRole('menuitem', { name: 'Delete' }).click();
Click Position
Sometimes you need to click a specific spot on an element:
// Click the top-left corner
await page.locator('.slider').click({ position: { x: 0, y: 0 } });
// Click the center (default)
await page.locator('.canvas').click({ position: { x: 100, y: 50 } });
Click with Modifiers
Hold keys while clicking:
// Ctrl+Click (or Cmd+Click on Mac) - often opens in new tab
await page.getByRole('link', { name: 'Article' }).click({ modifiers: ['Control'] });
// Shift+Click - often selects a range
await page.getByRole('row').last().click({ modifiers: ['Shift'] });
// Multiple modifiers
await page.locator('.item').click({ modifiers: ['Control', 'Shift'] });
Force Click
"Playwright won't let me click this element!"
When Playwright refuses to click (element covered, disabled, etc.) and you know what you're doing:
// Skip actionability checks
await page.locator('.hidden-trigger').click({ force: true });
Warning: Use force sparingly. If Playwright won't click something, there's usually a reason — often the same reason a real user couldn't click it.
Typing Text
fill() — The Fast Way
Use fill() for most text input. It clears existing content and types instantly:
// Fill an input
await page.getByLabel('Email').fill('test@example.com');
// Fill a textarea
await page.getByLabel('Message').fill('Hello!\n\nThis is a multi-line message.');
// Fill clears existing text first
await page.getByLabel('Search').fill('new search'); // Replaces any existing text
type() — The Slow Way
Use type() when you need to simulate real keystrokes (triggers keyboard events):
// Type slowly, like a human
await page.getByLabel('Search').type('playwright', { delay: 100 }); // 100ms between keys
// Useful for autocomplete testing
await page.getByRole('combobox').type('new y');
// Now wait for suggestions to appear
await page.getByRole('option', { name: 'New York' }).click();
pressSequentially() — Character by Character
Similar to type() but clearer about intent:
await page.getByLabel('Code').pressSequentially('ABC123', { delay: 50 });
Clearing Input
// Method 1: fill with empty string
await page.getByLabel('Search').fill('');
// Method 2: clear() method
await page.getByLabel('Search').clear();
// Method 3: select all and delete
await page.getByLabel('Search').press('Control+a');
await page.getByLabel('Search').press('Backspace');
Keyboard Actions
Single Keys
// Press Enter
await page.getByLabel('Search').press('Enter');
// Press Escape
await page.keyboard.press('Escape');
// Press Tab
await page.keyboard.press('Tab');
// Arrow keys
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');
Key Combinations
// Ctrl+A (select all)
await page.keyboard.press('Control+a');
// Ctrl+C (copy)
await page.keyboard.press('Control+c');
// Ctrl+V (paste)
await page.keyboard.press('Control+v');
// Ctrl+Shift+K (e.g., delete line in editors)
await page.keyboard.press('Control+Shift+k');
// On Mac? Playwright handles it - Control maps to Meta automatically
// Or be explicit:
await page.keyboard.press('Meta+c'); // Cmd+C on Mac
Hold Keys
// Hold Shift while doing something
await page.keyboard.down('Shift');
await page.getByText('First item').click();
await page.getByText('Last item').click();
await page.keyboard.up('Shift');
Working with Forms
Checkboxes
// Check a checkbox
await page.getByLabel('Subscribe to newsletter').check();
// Uncheck
await page.getByLabel('Subscribe to newsletter').uncheck();
// Toggle (use setChecked for explicit state)
await page.getByLabel('Enable notifications').setChecked(true);
await page.getByLabel('Enable notifications').setChecked(false);
// Check if it's checked
const isChecked = await page.getByLabel('Remember me').isChecked();
Radio Buttons
// Select a radio option
await page.getByLabel('Express shipping').check();
// Or by role
await page.getByRole('radio', { name: 'Credit card' }).check();
Dropdowns (Select)
For native <select> elements:
// By visible text
await page.getByLabel('Country').selectOption('United States');
// By value attribute
await page.getByLabel('Country').selectOption({ value: 'us' });
// By label text
await page.getByLabel('Country').selectOption({ label: 'United States' });
// Multiple selections (for multi-select)
await page.getByLabel('Toppings').selectOption(['cheese', 'pepperoni', 'mushrooms']);
For custom dropdowns (divs styled as dropdowns):
// Click to open
await page.getByRole('combobox', { name: 'Country' }).click();
// Then click the option
await page.getByRole('option', { name: 'United States' }).click();
File Uploads
// Single file
await page.getByLabel('Upload document').setInputFiles('path/to/file.pdf');
// Multiple files
await page.getByLabel('Upload photos').setInputFiles([
'photo1.jpg',
'photo2.jpg',
'photo3.jpg'
]);
// Clear selected files
await page.getByLabel('Upload').setInputFiles([]);
// For drag-and-drop style uploaders
await page.locator('.dropzone').setInputFiles('document.pdf');
For file chooser dialogs:
// Handle the file chooser event
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Upload' }).click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('path/to/file.pdf');
Hover and Focus
Hover
// Hover to reveal a dropdown menu
await page.getByRole('button', { name: 'Account' }).hover();
await page.getByRole('menuitem', { name: 'Settings' }).click();
// Hover over an element to see tooltip
await page.getByText('What is this?').hover();
await expect(page.getByRole('tooltip')).toBeVisible();
Focus
// Focus an input without typing
await page.getByLabel('Search').focus();
// Check if element is focused
await expect(page.getByLabel('Search')).toBeFocused();
// Blur (unfocus) the active element
await page.getByLabel('Search').blur();
Drag and Drop
"Can Playwright handle drag and drop?"
Absolutely.
Simple Drag and Drop
// Drag element to target
await page.getByText('Drag me').dragTo(page.getByText('Drop here'));
// Or with locators
await page.locator('.draggable').dragTo(page.locator('.droppable'));
Manual Drag (for complex cases)
// More control over the drag operation
const source = page.locator('.card');
const target = page.locator('.column-2');
await source.hover();
await page.mouse.down();
await target.hover();
await page.mouse.up();
Drag with Offset
// Drag to specific position
await page.locator('.slider-handle').dragTo(page.locator('.slider-track'), {
targetPosition: { x: 100, y: 0 }
});
Scrolling
Scroll into View
Most of the time, Playwright auto-scrolls. But sometimes you need control:
// Scroll element into view
await page.getByText('Footer').scrollIntoViewIfNeeded();
// Scroll to specific position
await page.mouse.wheel(0, 500); // Scroll down 500px
// Scroll within a container
await page.locator('.scroll-container').evaluate(el => {
el.scrollTop = 1000;
});
Actions on Frames
For iframes:
// Get the frame
const frame = page.frameLocator('iframe[name="editor"]');
// Now use it like page
await frame.getByRole('textbox').fill('Hello from iframe');
await frame.getByRole('button', { name: 'Save' }).click();
Waiting for Actions to Complete
Usually auto-waiting handles everything. But sometimes you need to wait for side effects:
// Wait for navigation after click
await Promise.all([
page.waitForURL('**/dashboard'),
page.getByRole('button', { name: 'Login' }).click(),
]);
// Wait for network request after action
await Promise.all([
page.waitForResponse(resp => resp.url().includes('/api/save')),
page.getByRole('button', { name: 'Save' }).click(),
]);
// Wait for element to disappear
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByText('Item deleted')).toBeVisible();
await expect(page.getByText('Item deleted')).toBeHidden();
Common Patterns
Login Flow
async function login(page, username, password) {
await page.getByLabel('Username').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('**/dashboard');
}
Form Submission
// Fill and submit a form
await page.getByLabel('Name').fill('John Doe');
await page.getByLabel('Email').fill('john@example.com');
await page.getByLabel('Message').fill('Hello there!');
await page.getByRole('button', { name: 'Submit' }).click();
// Wait for success message
await expect(page.getByText('Message sent!')).toBeVisible();
Handling Dialogs
// Accept an alert
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', { name: 'Delete' }).click();
// Dismiss a confirm dialog
page.on('dialog', dialog => dialog.dismiss());
// Handle prompt with input
page.on('dialog', async dialog => {
await dialog.accept('My input');
});
Debugging Failed Actions
When an action fails:
-
Check the locator — Is it finding the right element?
console.log(await page.getByRole('button', { name: 'Submit' }).count()); -
Take a screenshot — See what the page looks like
await page.screenshot({ path: 'debug.png' }); -
Use pause() — Interactive debugging
await page.pause(); -
Check if element is covered — Common issue
await page.getByRole('button', { name: 'Submit' }).click({ trial: true }); // This checks if click would work without actually clicking
What's Next?
You're now a Playwright action hero! You learned:
click(),dblclick(), and right-click with{ button: 'right' }fill()for fast input,type()for realistic typingcheck(),uncheck(),selectOption()for form controlssetInputFiles()for uploadshover(),focus(),dragTo()for interactions- Playwright auto-waits, so you rarely need explicit waits
But how do you verify things actually worked? Let's learn about assertions. Let's go!