Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Playwright can handle alerts, pop-ups, and other web page elements. The reason it often appears not to is that, by default, Playwright auto-dismisses every JavaScript dialog. If no page.on('dialog') listener is attached, alerts, confirms, and prompts are silently dismissed, so your test moves on as if the dialog never appeared. The fix is to register a dialog handler before the action that triggers it, and to use the right event for each element type: filechooser for uploads, waitForEvent('popup') for new tabs, httpCredentials for auth boxes, and frameLocator for iframes.
Playwright handles dialogs differently from Selenium, and that difference trips up most newcomers. There is no switchTo().alert() equivalent that you call after the dialog appears. Instead, Playwright is event-driven, and the defaults are aggressive.
Register a single listener before the triggering action. Use dialog.accept() to click OK and dialog.dismiss() to click Cancel. For a prompt, pass the input text to accept(). You can also inspect dialog.message() and dialog.type() to branch your logic.
// Accept an alert or confirm (click OK)
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', { name: 'Delete' }).click();
// Dismiss a confirm (click Cancel)
page.once('dialog', dialog => dialog.dismiss());
await page.getByRole('button', { name: 'Clear cart' }).click();
// Read the message, then enter text into a prompt
page.on('dialog', async dialog => {
console.log(dialog.type(), dialog.message()); // e.g. "prompt" "Your name?"
await dialog.accept('Ada Lovelace'); // type value + OK
});
await page.getByRole('button', { name: 'Ask name' }).click();The listener must call accept() or dismiss(). If it does nothing, the click that opened the dialog will stall because the modal blocks execution. The same approach handles beforeunload dialogs, which fire when you call page.close({ runBeforeUnload: true }).
The native operating-system file picker cannot be automated by clicking it. Playwright gives you two clean options: catch the filechooser event, or skip the dialog entirely with setInputFiles() on the underlying input.
// Option A: handle the OS file picker via the filechooser event
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.getByRole('button', { name: 'Upload' }).click(),
]);
await fileChooser.setFiles('reports/result.pdf');
// Option B: set files directly on the input, no dialog needed
await page.setInputFiles('input[type=file]', 'reports/result.pdf');Links with target="_blank" or window.open() create a brand-new page object. Listen for the popup event before the click, then drive the returned page just like the first one. Wrapping the click and the wait in Promise.all avoids a race where the pop-up opens before you start listening.
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.getByRole('link', { name: 'Open dashboard' }).click(),
]);
await popup.waitForLoadState();
await popup.getByRole('button', { name: 'Continue' }).click();
console.log(await popup.title());Switching between several windows or tabs is a related but distinct topic, covered in Why Playwright Is Not Able to Handle Multiple Windows or Tabs?
Three more elements regularly get labelled as unhandleable, when in fact each has a one-line answer.
// Basic auth: pass credentials at the context level
const context = await browser.newContext({
httpCredentials: { username: 'admin', password: 's3cret' },
});
const page = await context.newPage();
await page.goto('https://app.example.com/secure');
// iframe: enter the frame before locating
await page.frameLocator('#payment-frame')
.getByRole('button', { name: 'Pay now' })
.click();
// shadow DOM: locators pierce open shadow roots automatically
await page.getByRole('button', { name: 'Add to cart' }).click();| Element | Why it seems unhandled | Correct Playwright approach |
|---|---|---|
| Alert / confirm / prompt | Auto-dismissed by default | page.on('dialog', d => d.accept()) before the action |
| File upload picker | OS dialog, not in the DOM | filechooser event or setInputFiles() |
| New tab / pop-up window | Opens a separate page object | page.waitForEvent('popup') |
| Basic-auth pop-up | Browser chrome, not a DOM node | newContext({ httpCredentials }) |
| iframe element | Plain locator can't enter the frame | page.frameLocator(selector) |
| Shadow DOM element | Assumed to need special API | Standard locators pierce open shadow roots |
Once your dialog and pop-up handling is correct, the next variable is the browser itself, behaviour can differ across Chromium, Firefox, and WebKit. You can run the same Playwright suite across real browsers and operating systems on the TestMu AI Playwright Testing to confirm your dialog, file-chooser, and iframe logic holds up everywhere.
Because Playwright auto-dismisses dialogs by default. With no listener for the dialog event, every alert, confirm, and prompt is automatically dismissed, so the test continues as if nothing happened. Attach a page.on('dialog') handler before the action that triggers the dialog and call accept() or dismiss().
Use page.on('dialog', dialog => dialog.accept()) to click OK, or dialog.dismiss() to click Cancel, set up before the triggering click. For a prompt, pass text to accept: dialog.accept('my value'). The listener must call accept or dismiss, otherwise the action stalls because dialogs are modal and block page execution.
The native OS file picker cannot be clicked. Either listen for the filechooser event and call setFiles() on it, or bypass the dialog completely with page.setInputFiles() against the file input. The second approach is faster and more reliable for most uploads.
Set up page.waitForEvent('popup') before the click that opens the new tab, then work with the returned page object exactly like the original. Wrap the click and the wait in Promise.all so you don't miss the event.
Yes. For iframes, use page.frameLocator() to enter the frame before locating elements, and chain it for nested frames. For shadow DOM, Playwright locators automatically pierce open shadow roots, so getByRole, getByText, and CSS selectors work without any special API. Closed shadow roots are intentionally inaccessible.
The HTTP basic-auth box is browser chrome, not a DOM element, so you cannot click it with a locator. Pass credentials at the context level with browser.newContext({ httpCredentials: { username, password } }) and the prompt is satisfied automatically.
KaneAI - Testing Assistant
World’s first AI-Native E2E testing agent.

TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance