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.

Advertisement

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 await it — the same effect as the older Promise.all([waitForEvent, click]) pattern, and easier to read.
  • There's no "switching": each tab is its own page object, 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.