Alerts, popups, dropdowns, iframes and new tabs are where most beginners get stuck when moving to Playwright. This guide shows the right Playwright API for each situation with runnable TypeScript examples you can try on a free practice site.
📖 What You’ll Learn: JavaScript alerts, confirms and prompts, new tabs and popup windows, native and custom dropdowns, iframes and nested frames, file uploads, hover, right-click, drag and drop, and the Selenium equivalents.
🗂️ Quick Reference
| Situation | Playwright API |
|---|---|
| JavaScript alert, confirm, prompt | page.on(‘dialog’) / page.once(‘dialog’) |
| Link opens a new tab or popup window | page.waitForEvent(‘popup’) |
| Any new page in the browser context | context.waitForEvent(‘page’) |
| Native select dropdown | locator.selectOption() |
| Custom (div-based) dropdown | click, then getByRole(‘option’) |
| Element inside an iframe | page.frameLocator() or locator.contentFrame() |
| File upload | locator.setInputFiles() or the filechooser event |
| Mouse hover, right-click, drag and drop | hover(), click({ button: ‘right’ }), dragTo() |
All examples below use the free practice site the-internet.herokuapp.com, so you can copy them and run them yourself. You can also practise on the SQA Tools automation practice pages.
🔔 Handle Alerts, Confirms and Prompts (Dialogs)
JavaScript dialogs (alert(), confirm(), prompt() and beforeunload) block the page. By default Playwright dismisses them automatically, so a confirm returns “Cancel”. To accept a dialog, read its message or type into a prompt, register a handler before the action that opens it.
Accept an alert and read its message
import { test, expect } from '@playwright/test';
test('accept a JS alert', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/javascript_alerts');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.message()).toBe('I am a JS Alert');
await dialog.accept();
});
await page.getByRole('button', { name: 'Click for JS Alert' }).click();
await expect(page.locator('#result')).toHaveText('You successfully clicked an alert');
});
Accept or dismiss a confirm
test('dismiss a confirm', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/javascript_alerts');
page.once('dialog', dialog => dialog.dismiss()); // click "Cancel"
await page.getByRole('button', { name: 'Click for JS Confirm' }).click();
await expect(page.locator('#result')).toHaveText('You clicked: Cancel');
});
Type into a prompt
test('answer a prompt', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/javascript_alerts');
page.once('dialog', dialog => dialog.accept('Playwright'));
await page.getByRole('button', { name: 'Click for JS Prompt' }).click();
await expect(page.locator('#result')).toHaveText('You entered: Playwright');
});
⚠️ Common mistake: Use page.once for a single dialog. With page.on the handler stays active for the rest of the test, and a handler that neither accepts nor dismisses the dialog will make the next action hang.
📝 Note: Website modals built with HTML (cookie banners, “subscribe” overlays) are not dialogs. Handle them like normal elements: await page.getByRole('button', { name: 'Accept' }).click().
🪟 Handle a New Tab or Popup Window
When a link opens a new tab (target="_blank") or window.open() creates a popup, wait for the popup event. Start waiting before the click, then work with the new page object.
test('work with a new tab', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/windows');
const popupPromise = page.waitForEvent('popup'); // 1. start listening
await page.getByRole('link', { name: 'Click Here' }).click(); // 2. open it
const newTab = await popupPromise; // 3. get the new page
await newTab.waitForLoadState();
await expect(newTab.locator('h3')).toHaveText('New Window');
// Switch back: just use the original page object again
await expect(page.locator('h3')).toHaveText('Opening a new window');
await newTab.close();
});
There is no “switch to window” step like in Selenium: each tab is a separate Page object, and you simply call methods on the one you need.
Track any new page in the context
const pagePromise = page.context().waitForEvent('page');
await page.getByRole('link', { name: 'Open report' }).click();
const reportPage = await pagePromise;
// List every open tab
for (const p of page.context().pages()) {
console.log(await p.title());
}
🔽 Select from a Dropdown
Native select elements
For a real <select> element, use selectOption(). You can select by value, label or index, and select several options in a multi-select.
test('select from a dropdown', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/dropdown');
const dropdown = page.locator('#dropdown');
await dropdown.selectOption('1'); // by value
await expect(dropdown).toHaveValue('1');
await dropdown.selectOption({ label: 'Option 2' }); // by visible text
await expect(dropdown).toHaveValue('2');
await dropdown.selectOption({ index: 1 }); // by position
});
// Multi-select: pass an array
// await page.locator('#colors').selectOption(['red', 'blue']);
Read all options to verify the list:
const options = await page.locator('#dropdown option').allTextContents();
expect(options).toEqual(['Please select an option', 'Option 1', 'Option 2']);
Custom dropdowns (div or React components)
Many modern apps draw dropdowns with div and li elements. selectOption() does not work on them. Click to open, then click the option, preferably by role.
await page.getByRole('combobox', { name: 'Country' }).click();
await page.getByRole('option', { name: 'India' }).click();
await expect(page.getByRole('combobox', { name: 'Country' })).toHaveText(/India/);
🖼️ Handle iFrames
Elements inside an <iframe> belong to a different document, so normal page locators cannot see them. Use frameLocator() to reach inside. It auto-waits like any locator, and there is no “switch back” step.
// Single iframe, e.g. an embedded payment form
const payment = page.frameLocator('iframe[title="Secure payment"]');
await payment.getByLabel('Card number').fill('4242 4242 4242 4242');
await payment.getByRole('button', { name: 'Pay' }).click();
Nested frames
test('read text from nested frames', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/nested_frames');
const middle = page
.frameLocator('frame[name="frame-top"]')
.frameLocator('frame[name="frame-middle"]');
await expect(middle.locator('#content')).toHaveText('MIDDLE');
});
From a locator to its frame
In recent Playwright versions you can also go from an iframe element to its content with contentFrame():
const frame = page.locator('#editor-frame').contentFrame();
await frame.locator('body').fill('Hello from Playwright');
💡 Tip: Need the full Frame object (for example to run evaluate)? Use page.frame({ name: 'frame-name' }) or page.frame({ url: /checkout/ }).
📤 Upload Files
For a normal <input type="file">, call setInputFiles(). It works even when the input is hidden.
import path from 'path';
test('upload a file', async ({ page }) => {
await page.goto('https://the-internet.herokuapp.com/upload');
await page.locator('#file-upload').setInputFiles(path.join(__dirname, 'data', 'sample.txt'));
await page.locator('#file-submit').click();
await expect(page.locator('#uploaded-files')).toHaveText('sample.txt');
});
// Multiple files: setInputFiles(['a.png', 'b.png'])
// Clear the input: setInputFiles([])
If the page opens the file picker from a custom button, wait for the filechooser event:
const chooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Upload resume' }).click();
const chooser = await chooserPromise;
await chooser.setFiles('data/resume.pdf');
For downloads, see how to perform file downloads in Playwright.
🖱️ Mouse Hover, Right-Click, Double-Click and Drag and Drop
// Hover to reveal a hidden menu or caption
await page.goto('https://the-internet.herokuapp.com/hovers');
await page.locator('.figure').first().hover();
await expect(page.getByText('name: user1')).toBeVisible();
// Right-click and double-click
await page.locator('#hot-spot').click({ button: 'right' });
await page.getByText('Edit').dblclick();
// Drag and drop
await page.goto('https://the-internet.herokuapp.com/drag_and_drop');
await page.locator('#column-a').dragTo(page.locator('#column-b'));
await expect(page.locator('#column-a header')).toHaveText('B');
// Keyboard
await page.getByLabel('Search').press('Enter');
await page.keyboard.press('Control+A');
🔄 Selenium vs Playwright for These Scenarios
| Task | Selenium | Playwright |
|---|---|---|
| Alert | driver.switchTo().alert().accept() | page.once(‘dialog’, d => d.accept()) |
| New window | getWindowHandles() then switchTo().window() | page.waitForEvent(‘popup’) |
| Dropdown | new Select(el).selectByVisibleText() | selectOption({ label }) |
| iFrame | switchTo().frame() then defaultContent() | page.frameLocator() |
| Hover | new Actions(driver).moveToElement(el) | locator.hover() |
Compare with our Python Selenium guides for alerts, dropdowns and iframes, or read the full Playwright vs Selenium vs Cypress comparison.
✅ Best Practices
- Register dialog handlers and start
waitForEventpromises before the click that triggers them. - Prefer
page.onceoverpage.onfor one-off dialogs. - Keep a separate
Pagevariable for each tab; close tabs you no longer need. - Use
frameLocatorwith a stable selector (title, name or id) for the iframe. - Use role-based locators for custom dropdowns, and
selectOptiononly for native selects.
❓ FAQ
🤔 How do I handle a popup in Playwright?
For a new tab or window, use const popup = await page.waitForEvent('popup') (start it before the click). For a JavaScript alert, use page.once('dialog', d => d.accept()). For an HTML modal, click its buttons like any other element.
🤔 Why does Playwright close my alert automatically?
Playwright dismisses dialogs by default so tests never hang. Add a dialog handler to accept it, read the message or enter prompt text.
🤔 How do I select a value from a dropdown in Playwright?
Use await page.locator('select#id').selectOption({ label: 'Option text' }) for native selects. For custom dropdowns, click the control and then page.getByRole('option', { name: '...' }).click().
🤔 How do I switch to an iframe in Playwright?
You do not switch. Create a frame locator, page.frameLocator('iframe#id'), and use locators on it. The main page stays available through page.
🤔 How do I switch between tabs in Playwright?
Every tab is its own Page object. Keep references (for example page and newTab) and call methods on the one you need; context.pages() lists all open tabs.
🎯 Next Steps
- 🎭 Playwright Waits Explained
- 🎭 Playwright Locators: Beginner to Advanced
- 🎭 Reading Data from Web Tables
- 🎭 Playwright with TypeScript Training Notes
🎬 Watch the full Playwright tutorial series on YouTube!
Subscribe to SqaToolsByDY • Practice on sqatools.in
4 thoughts on “Playwright: Handle Alerts, Popups, Dropdowns, iFrames, New Tabs and File Uploads”