Playwright UI Handling
Dialogs, new tabs, context menus and keyboard shortcuts are where Selenium testers usually reach for special APIs — switchTo().alert(), window handles, the Actions class. Playwright handles each more directly. This guide shows the code for every case (type-checked against Playwright 1.63), the traps to avoid, and the Selenium equivalent for comparison.
Alerts, Confirms and Prompts
JavaScript dialogs (alert(), confirm(), prompt(), and "leave this page?" beforeunload dialogs) aren't part of the page's HTML, so locators can't find them. Playwright raises a dialog event instead.
The key rule: Playwright automatically dismisses dialogs when no listener is registered — so a confirm is answered "Cancel" unless you say otherwise. Register the handler before the action that opens the dialog.
test('confirm dialog: read message and accept', async ({ page }) => {
await page.setContent(`
<button onclick="document.getElementById('r').textContent =
confirm('Delete customer 1043?') ? 'Deleted' : 'Kept'">Delete</button>
<p id="r"></p>`);
page.once('dialog', async dialog => { // register BEFORE the click
expect(dialog.type()).toBe('confirm');
expect(dialog.message()).toBe('Delete customer 1043?');
await dialog.accept(); // OK
});
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.locator('#r')).toHaveText('Deleted');
});
| Dialog method | Does |
|---|---|
dialog.type() |
'alert', 'confirm', 'prompt' or 'beforeunload' |
dialog.message() |
The dialog's text — assert on it |
dialog.accept() |
OK |
dialog.accept('text') |
Type into a prompt and press OK |
dialog.dismiss() |
Cancel |
page.once('dialog', dialog => dialog.accept('Playwright User')); // prompt
await page.getByRole('button', { name: 'Rename' }).click();
Use page.once for a single expected dialog; page.on handles every dialog on the page (useful for random alerts). Test both answers of a confirm: accepting deletes the record, dismissing keeps it. Custom "modal" popups built from HTML are not dialogs — use normal locators such as getByRole('dialog').
New Tabs and Popups
test('new tab', async ({ page }) => {
const popupPromise = page.waitForEvent('popup'); // start waiting before the click
await page.getByRole('link', { name: 'Terms' }).click();
const terms = await popupPromise;
await terms.waitForLoadState();
await expect(terms).toHaveTitle(/Terms/);
await terms.close(); // back to working with the original page
await expect(page.getByRole('link', { name: 'Terms' })).toBeVisible();
});
page.waitForEvent('popup')— a tab or window opened by this page (target="_blank",window.open).context.waitForEvent('page')— any new page in the browser context, from any source.- Create the promise first, then click, then
awaitit — the same effect as the olderPromise.all([waitForEvent, click])pattern, and easier to read. - There's no "switching": each tab is its own
pageobject, and you simply call methods on the one you want.context.pages()lists all open pages.
Right-Click, Double-Click and Hover
await page.getByTestId('file-row').click({ button: 'right' }); // open the custom context menu
await page.getByRole('menuitem', { name: 'Rename' }).click();
await page.getByText('Double me').dblclick();
await page.getByRole('button', { name: 'Account' }).hover(); // hover menus and tooltips
await page.getByRole('menuitem', { name: 'Logout' }).click();
Like every action, these auto-wait for the element to be visible, stable and not covered. The browser's native context menu (Back, Reload, Inspect) can't be automated — only menus the application draws in HTML.
Keyboard: fill(), press() and keyboard
await page.getByLabel('Search').fill('laptop'); // replace the field's value in one step
await page.getByLabel('Search').press('Enter'); // a key on a specific element
await page.getByLabel('Notes').press('Shift+Tab'); // key combinations
await page.keyboard.press('ControlOrMeta+A'); // Ctrl on Windows/Linux, Cmd on macOS
await page.keyboard.type('new text'); // type into whatever has focus
| Method | Use |
|---|---|
locator.fill(text) |
Set an input's value — the default for text fields |
locator.pressSequentially(text) |
Type key by key, for fields that react to each keystroke (autocomplete) |
locator.press('Enter') |
Press a key or combination on an element |
page.keyboard.press() / down() / up() |
Page-level keys and holding modifiers |
ControlOrMeta picks the right modifier for the operating system — useful when the same suite runs on Windows CI agents and developers' Macs.
Checkboxes and Radio Buttons
const newsletter = page.getByRole('checkbox', { name: 'Newsletter' });
await expect(newsletter).not.toBeChecked(); // verify the default state first
await newsletter.check();
await expect(newsletter).toBeChecked();
await newsletter.setChecked(false); // set a state from data (true/false)
check() and uncheck() are idempotent — they set the state rather than toggling it, so an already-checked box stays checked (unlike a plain click). Select checkboxes by their label, not by nth(index), so the test survives reordering.
Coming From Selenium
| Task | Selenium | Playwright |
|---|---|---|
| Accept an alert | driver.switchTo().alert().accept() |
page.once('dialog', d => d.accept()) before the action |
| Alert text | alert.getText() |
dialog.message() |
| New tab | getWindowHandles() + switchTo().window() |
page.waitForEvent('popup') → a new page object |
| Right-click | new Actions(driver).contextClick(el).perform() |
click({ button: 'right' }) |
| Hover | actions.moveToElement(el).perform() |
hover() |
| Ctrl+A | keyDown(CONTROL).sendKeys("a").keyUp(CONTROL) |
keyboard.press('ControlOrMeta+A') |
| Select a checkbox | if (!el.isSelected()) el.click() |
check() |
Convert more of your Selenium code in the Selenium ⇄ Playwright ⇄ Cypress Translator. Selenium versions in detail: Alerts, Frames & Windows and Selenium Actions Class.
From Real Projects
I've built my automation skills on Selenium with Java and TestNG, and that background maps well onto Playwright: page classes become page objects, TestNG groups and parallel runs become projects and workers, and explicit waits become auto-waiting and web-first assertions. If you're coming from Selenium too, focus on those differences first. Testing Testsigma — a platform where users write automated tests in plain English — meant thinking like its users, who are testers themselves. Register dialog handlers before the action that triggers them. Related: JavaScript for Playwright Automation.
📚 Official documentation: Playwright documentation · Selenium documentation
FAQs
How do you handle alerts in Playwright?
Register page.once('dialog', …) before the action that triggers it, check dialog.message(), then accept() or dismiss().
What happens if you don't handle a dialog?
Playwright dismisses it automatically — a confirm behaves as if the user clicked Cancel.
How do you type into a prompt?
dialog.accept('your text').
How do you handle a new tab?
Create page.waitForEvent('popup') before the click, await it after, and work with the returned page object directly.
How do you press Enter or keyboard shortcuts?
locator.press('Enter') for an element, page.keyboard.press('ControlOrMeta+A') for page-level shortcuts.
fill() or pressSequentially()?
fill() for normal fields; pressSequentially() only when the page must see each keystroke.
Related
- Playwright Locators & Actions
- The Complete Playwright Tutorial
- Playwright Cheat Sheet
- Top 20 Playwright Interview Questions