October 11, 2026
Playwright Tutorial | Alerts, Popups, iFrames, File Upload & Download
Preview Subtitle: Playwright Automation Tutorial for QA Engineers | Learn Alerts, JavaScript Dialogs, New Tabs, iFrames, File Uploads…

By Aesha Mangukiya | QA Engineer | QA Map
10 min read
Preview Subtitle: Playwright Automation Tutorial for QA Engineers | Learn Alerts, JavaScript Dialogs, New Tabs, iFrames, File Uploads, Downloads, Screenshots, and Real-World Testing Examples.
Watch the Complete Playwright Tutorial by QA MAP
🎥 Want to learn how to automate real-world browser scenarios with Playwright? In this QA MAP tutorial, you'll see how to handle JavaScript alerts, new tabs, iFrames, file uploads and downloads, screenshots, dynamic content, and browser permissions with practical examples.
Watch here:
Introduction: Beyond Basic Playwright Automation
When learning Playwright, most beginners start with simple actions such as clicking buttons, filling textboxes, and verifying page content.
But real-world web applications involve more complex scenarios.
You may need to handle a JavaScript alert, capture a newly opened browser tab, interact with content inside an iframe, upload a document, or verify that a file has been downloaded successfully.
So, how do you automate these scenarios?
In this guide, we'll explore practical Playwright techniques that help QA engineers handle common browser behaviors and build more reliable automation tests.
The examples follow a simple workflow:
Understand the scenario → Write Playwright code → Run the test → Verify the result
Let's get started.
1. Handling JavaScript Alerts in Playwright
JavaScript dialogs are browser dialogs that can appear when a user performs an action. Common types include alerts, confirmations, and prompts.
Playwright provides the dialog event to handle these dialogs.
JavaScript Alert
An alert displays a message and usually requires the user to click OK.
In Playwright, you can listen for the dialog, read its message, and accept it.
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept();
});
await page.getByRole('button', {
name: 'Click for JS Alert'
}).click();page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept();
});
await page.getByRole('button', {
name: 'Click for JS Alert'
}).click();Here's what the code does:
• page.on('dialog') listens for a dialog event.
• dialog.message() retrieves the dialog message.
• dialog.accept() accepts the dialog and closes it.
This allows Playwright to handle the alert automatically instead of requiring manual interaction.
JavaScript Confirm: Accept and Dismiss
A confirmation dialog typically gives the user two choices: OK or Cancel.
In Playwright, these actions correspond to accept() and dismiss().
To accept the confirmation:
await dialog.accept();await dialog.accept();To dismiss the confirmation:
await dialog.dismiss();await dialog.dismiss();For example, to cancel a confirmation dialog:
page.on('dialog', async dialog => {
await dialog.dismiss();
});
await page.getByRole('button', {
name: 'Click for JS Confirm'
}).click();page.on('dialog', async dialog => {
await dialog.dismiss();
});
await page.getByRole('button', {
name: 'Click for JS Confirm'
}).click();Remember:
• Accept or click OK → dialog.accept()
• Cancel or dismiss → dialog.dismiss()
JavaScript Prompt
A prompt allows the user to enter a value.
Playwright can provide that value when accepting the prompt.
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept('QAMap');
});
await page.getByRole('button', {
name: 'Click for JS Prompt'
}).click();page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept('QAMap');
});
await page.getByRole('button', {
name: 'Click for JS Prompt'
}).click();In this example, QAMap is entered into the prompt and the dialog is accepted.
QA tip: Register the dialog handler before triggering the action that opens the dialog. Also, make sure the handler matches the expected behavior of the test.
2. Handling New Tabs and Popups
In real applications, clicking a link may open a page in the current tab or create a new tab or window.
In automation, you need to capture the new page before interacting with it.
Playwright provides the popup event for this scenario.
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', {
name: 'Click Here'
}).click();
const popup = await popupPromise;
await popup.waitForLoadState();const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', {
name: 'Click Here'
}).click();
const popup = await popupPromise;
await popup.waitForLoadState();Let's understand the sequence:
• page.waitForEvent('popup') starts waiting for a popup associated with the page.
• The link is clicked.
• The new page reference is captured in popup.
• waitForLoadState() waits for the page to reach its load state.
You can then verify the new page's URL:
await expect(popup).toHaveURL(/the-internet/);await expect(popup).toHaveURL(/the-internet/);You can also verify its title or an expected element, depending on the application.
Why Is This Important?
Suppose a user clicks a product link, payment link, or external authentication link that opens a new tab.
If your test continues interacting with the original page, it may fail because the expected content is on the newly opened page.
Capturing the correct page reference helps avoid this problem.
For scenarios involving multiple pages, you can also inspect the pages in the current browser context:
const pages = page.context().pages();
console.log(`Total pages: ${pages.length}`);const pages = page.context().pages();
console.log(`Total pages: ${pages.length}`);Use the popup event when you specifically need to capture a new page created by an action.
3. Handling iFrames in Playwright
An iframe embeds one HTML document inside another. The content inside an iframe is treated as a separate document context.
This means that a locator for the main page may not directly locate an element inside the iframe.
Playwright provides frameLocator() to locate and interact with iframe content.
Example: Verify Text Inside an iFrame
Suppose an iframe contains the following text:
Your content goes here.
You can verify that the expected text is present using:
const frame = page.frameLocator('iframe');
await expect(
frame.locator('body')
).toContainText('Your content goes here.');const frame = page.frameLocator('iframe');
await expect(
frame.locator('body')
).toContainText('Your content goes here.');Here's how it works:
• page.frameLocator('iframe') identifies the iframe.
• frame.locator('body') locates the body element inside the iframe.
• toContainText() verifies that the expected content is present.
This example focuses on verifying content inside the iframe.
If the iframe contains editable fields, buttons, or other interactive elements, you can use locators through the frame locator to perform the required actions.
QA tip: When an element cannot be located on the main page, check whether it belongs to an iframe before changing your locator strategy.
4. File Upload in Playwright
File uploads are common in web applications, including:
• Resume submission portals. • Profile document uploads. • Invoice and report uploads. • Screenshot attachments. • Support ticket attachments.
Playwright supports file uploads through setInputFiles(), so you generally don't need to interact with the operating system's file picker manually.
Upload a Single File
Suppose an application contains a file input with the accessible label Upload file.
await page.getByLabel('Upload file')
.setInputFiles('test-data/sample.pdf');
await page.getByRole('button', {
name: 'Upload'
}).click();await page.getByLabel('Upload file')
.setInputFiles('test-data/sample.pdf');
await page.getByRole('button', {
name: 'Upload'
}).click();In this example:
• getByLabel('Upload file') locates the file input.
• setInputFiles() selects the specified file.
• The Upload button submits the file.
Make sure the file path points to an existing test file in your project.
Verify the Upload Result
Performing the upload action is not enough. QA should also verify whether the application processed the file successfully.
For example:
await expect(
page.getByText('Upload successful')
).toBeVisible();await expect(
page.getByText('Upload successful')
).toBeVisible();The success message and locator should match the actual application.
This assertion helps confirm that the expected upload result appeared.
Upload Multiple Files
Some applications allow users to upload several files at once.
Playwright supports passing an array of file paths to setInputFiles().
await page.getByLabel('Upload files')
.setInputFiles([
'test-data/file1.txt',
'test-data/file2.txt'
]);
await page.getByRole('button', {
name: 'Upload'
}).click();await page.getByLabel('Upload files')
.setInputFiles([
'test-data/file1.txt',
'test-data/file2.txt'
]);
await page.getByRole('button', {
name: 'Upload'
}).click();This example selects two files before submitting the upload.
The application must support multiple-file selection for this scenario to work as intended.
QA tip: Test valid files, unsupported file types, oversized files, empty submissions, and upload failure messages when those cases apply to your application.
5. File Download in Playwright
File downloads are another common automation scenario.
For example, an application may allow users to download invoices, reports, receipts, or generated documents.
Playwright provides the download event to capture a download.
Capture a Download Event
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', {
name: /download/i
}).click();
const download = await downloadPromise;const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', {
name: /download/i
}).click();
const download = await downloadPromise;The workflow is straightforward:
• Start waiting for the download event. • Click the download link. • Capture the resulting download object.
Waiting for the event before clicking is important because the download may begin immediately after the action.
Get the Suggested Filename
You can retrieve the filename suggested by the browser:
const fileName = download.suggestedFilename();
console.log(fileName);const fileName = download.suggestedFilename();
console.log(fileName);This is useful when the application generates filenames dynamically.
Save the Downloaded File
You can save the downloaded file to a specific location:
await download.saveAs(`downloads/${fileName}`);await download.saveAs(`downloads/${fileName}`);Make sure the destination directory exists and that your test environment is configured to allow the file to be saved there.
Verify That the Downloaded File Exists
A download event alone does not prove that the file contains the expected data.
You can use Node.js file-system utilities to perform basic checks.
import fs from 'fs';
expect(fs.existsSync(filePath)).toBeTruthy();
expect(fs.statSync(filePath).size).toBeGreaterThan(0);import fs from 'fs';
expect(fs.existsSync(filePath)).toBeTruthy();
expect(fs.statSync(filePath).size).toBeGreaterThan(0);These assertions check two things:
• The file exists at the expected path. • The file size is greater than zero.
For stronger validation, you can also check the filename, file type, expected contents, or other application-specific requirements.
QA tip: A reliable download test verifies both the download behavior and the resulting file, rather than stopping after the click.
6. Taking Screenshots in Playwright
Screenshots are useful when debugging failed tests, reviewing page layouts, or capturing a specific application state.
Playwright supports screenshots of both complete pages and individual elements.
Capture a Full-Page Screenshot
await page.screenshot({
path: 'screenshots/full-page.png',
fullPage: true
});await page.screenshot({
path: 'screenshots/full-page.png',
fullPage: true
});The fullPage: true option captures the full page rather than only the currently visible viewport.
Capture a Specific Element
If you need a screenshot of a particular component, use the locator's screenshot method.
await page.getByRole('heading', {
name: 'File Uploader'
}).screenshot({
path: 'screenshots/file-uploader.png'
});await page.getByRole('heading', {
name: 'File Uploader'
}).screenshot({
path: 'screenshots/file-uploader.png'
});The distinction is simple:
• Complete page → page.screenshot()
• Specific element → locator.screenshot()
Capture Screenshots on Test Failure
Playwright Test can also be configured to capture screenshots when a test fails.
use: {
screenshot: 'only-on-failure'
}use: {
screenshot: 'only-on-failure'
}This option belongs inside the appropriate Playwright Test configuration.
Failure screenshots can help QA engineers investigate what the browser displayed when the test failed.
7. Handling Dynamic Content Without Fixed Waits
Dynamic content is common in modern web applications.
A page may display a loading indicator after a button is clicked, then show the expected content after an API request completes.
A common beginner mistake is to use a fixed delay:
await page.waitForTimeout(5000);await page.waitForTimeout(5000);This waits for five seconds regardless of whether the content is already available or still loading.
A more reliable approach is to wait for the expected UI condition.
await expect(
page.getByText('Hello World!')
).toBeVisible();await expect(
page.getByText('Hello World!')
).toBeVisible();Playwright's web-first assertion retries until the condition passes or the assertion times out.
This avoids relying on an arbitrary sleep for ordinary UI synchronization.
Fixed waits can still be useful in specific debugging or timing scenarios, but they should not be the default solution for waiting for application content.
QA takeaway: Wait for the result your test needs to verify, not simply for a predetermined amount of time.
8. Handling Dynamic URLs
In real applications, URLs may contain changing identifiers.
For example:
/dashboard/12345
/dashboard/98765
If the identifier is expected to vary, asserting the entire URL may make the test unnecessarily brittle.
Instead, you can verify a stable part of the URL:
await expect(page).toHaveURL(/dashboard/);await expect(page).toHaveURL(/dashboard/);This checks that the URL contains dashboard.
For more specific tests, you can verify a route pattern or the expected identifier when that value is part of the requirement.
QA tip: Assert the parts of the URL that matter to the test. Avoid ignoring important route details when they are essential to the expected behavior.
9. Handling Browser Permissions
Some web applications require browser permissions, such as access to the user's location.
Playwright allows you to configure permissions and geolocation in a browser context.
For example:
const context = await browser.newContext({
permissions: ['geolocation'],
geolocation: {
latitude: 19.0760,
longitude: 72.8777
}
});
const page = await context.newPage();
await page.goto(
'https://the-internet.herokuapp.com/geolocation'
);const context = await browser.newContext({
permissions: ['geolocation'],
geolocation: {
latitude: 19.0760,
longitude: 72.8777
}
});
const page = await context.newPage();
await page.goto(
'https://the-internet.herokuapp.com/geolocation'
);You can then interact with the page:
await page.getByRole('button', {
name: 'Where am I?'
}).click();await page.getByRole('button', {
name: 'Where am I?'
}).click();After the action, add assertions that verify the expected location information.
In this example, the permission and coordinates are configured at the browser-context level.
This approach is useful when testing features that depend on browser permissions, without requiring a tester to respond manually to a permission prompt.
10. Understanding Browser Events in Playwright
Several of the techniques covered in this tutorial use events to handle browser behavior.
Here is a quick reference:
• JavaScript dialog → page.on('dialog', ...)
• New tab or popup → page.waitForEvent('popup')
• File download → page.waitForEvent('download')
The key idea is to identify the browser event associated with the scenario and handle it at the appropriate point in the test.
For example, when a click triggers a download, start waiting for the download event before clicking the link.
When a click opens a popup, start waiting for the popup event before performing the action.
This event-based approach helps make browser automation more predictable.
11. Combining Techniques in a Real-World Workflow
Imagine a requirement in which a user uploads a document, submits it, opens a result page, downloads a generated file, and verifies the downloaded file.
The workflow could look like this:
Upload → Submit → Handle confirmation → Capture new page → Download file → Save file → Verify result
Each step may require a different Playwright technique, depending on the application's actual behavior.
For example, file upload could be handled using:
await page.locator('input[type="file"]')
.setInputFiles('test-data/document.pdf');await page.locator('input[type="file"]')
.setInputFiles('test-data/document.pdf');If the workflow opens a new page, you can capture it with the popup event. If the result page offers a downloadable file, you can capture the download event and save the file.
Finally, use assertions to validate the expected outcome.
The important principle is:
User action → Browser event → Playwright handling → Result verification
Not every application follows the same workflow, so use the techniques that match the actual requirements and browser behavior.
12. Quick Reference: Which Playwright Technique Should You Use?
Browser alert, confirm, or prompt: Use the dialog event with accept() or dismiss().
New tab or popup: Use waitForEvent('popup') to capture the new page.
Element inside an iframe: Use frameLocator() to locate content within the frame.
Single or multiple file upload: Use setInputFiles() with a file path or an array of file paths.
File download: Wait for the download event, capture the download object, and save the file if needed.
Screenshot of the complete page: Use page.screenshot().
Screenshot of a specific element: Use locator.screenshot().
Dynamic content: Prefer a meaningful web-first assertion over an arbitrary fixed delay.
Dynamic URL: Verify a stable route pattern or the relevant expected URL.
Browser permission: Configure the required permission in the browser context.
Once you can identify the scenario correctly, choosing the appropriate Playwright API becomes much easier.
Frequently Asked Questions
1. How do you handle JavaScript alerts in Playwright?
Use the dialog event to listen for a JavaScript dialog. Call dialog.accept() to accept it or dialog.dismiss() to dismiss it.
2. How do you handle a new tab in Playwright?
Use page.waitForEvent('popup') before performing the action that opens the new tab. Then use the captured page reference to interact with or verify the new page.
3. How do you handle iFrames in Playwright?
Use page.frameLocator() to locate an iframe and then find or interact with elements inside it.
4. How do you upload a file in Playwright?
Use locator.setInputFiles() with the file input locator and the path to the file. For multiple files, pass an array of file paths when the application supports multiple uploads.
5. How do you download a file in Playwright?
Wait for the download event, trigger the download action, and capture the resulting download object. You can use suggestedFilename() to obtain the suggested filename and saveAs() to save the file.
6. How do you verify a downloaded file?
Check that the file exists and is not empty. Depending on the requirement, you can also validate its name, type, or contents.
7. Why should you avoid unnecessary waitForTimeout() calls?
Fixed delays can make tests slower and less reliable because they do not check whether the expected condition has actually occurred. Waiting for a meaningful UI condition is generally a better approach.
Final Thoughts
Playwright automation involves much more than clicking buttons and entering text. Real-world QA projects require you to handle browser dialogs, popups, iFrames, file uploads, downloads, screenshots, permissions, and dynamic application behavior.
The most important lessons from this tutorial are:
• Identify the browser scenario before choosing an API.
• Use event-based handling for dialogs, popups, and downloads.
• Use frameLocator() for iframe content.
• Verify upload and download outcomes, not just the actions.
• Prefer meaningful assertions over unnecessary fixed waits.
• Build tests around application requirements and expected behavior.
For QA engineers learning Playwright, these techniques provide a foundation for moving beyond basic scripts toward practical browser automation.
Continue Learning with QA MAP
This tutorial is part of the QA MAP Playwright Automation Series, designed to help beginners and aspiring QA Automation Engineers learn Playwright through practical examples.
🎥 Watch the complete tutorial: https://youtu.be/7M3DtrrR-QM
If this article helped you, share it with someone learning Automation Testing.
Follow QA MAP for more tutorials on Playwright, Selenium, Manual Testing, API Testing, SQL, Software Testing fundamentals, and QA career development.
Happy Testing!