Playwright: Handle Alerts, Popups, Dropdowns, iFrames, New Tabs and File Uploads

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.

Advertisement

📖 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

SituationPlaywright API
JavaScript alert, confirm, promptpage.on(‘dialog’) / page.once(‘dialog’)
Link opens a new tab or popup windowpage.waitForEvent(‘popup’)
Any new page in the browser contextcontext.waitForEvent(‘page’)
Native select dropdownlocator.selectOption()
Custom (div-based) dropdownclick, then getByRole(‘option’)
Element inside an iframepage.frameLocator() or locator.contentFrame()
File uploadlocator.setInputFiles() or the filechooser event
Mouse hover, right-click, drag and drophover(), 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

TaskSeleniumPlaywright
Alertdriver.switchTo().alert().accept()page.once(‘dialog’, d => d.accept())
New windowgetWindowHandles() then switchTo().window()page.waitForEvent(‘popup’)
Dropdownnew Select(el).selectByVisibleText()selectOption({ label })
iFrameswitchTo().frame() then defaultContent()page.frameLocator()
Hovernew 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 waitForEvent promises before the click that triggers them.
  • Prefer page.once over page.on for one-off dialogs.
  • Keep a separate Page variable for each tab; close tabs you no longer need.
  • Use frameLocator with a stable selector (title, name or id) for the iframe.
  • Use role-based locators for custom dropdowns, and selectOption only 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

🎬 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”

Leave a Comment