Modern web applications can display different types of browser interactions. In automation, these interactions must be handled correctly before the test can continue.
A JavaScript dialog and a browser popup are not the same thing. Understanding this distinction is very important in Playwright.
JavaScript
alert(), confirm() and
prompt() are handled through Playwright's
dialog object. A new tab or browser window is
represented as another Playwright Page.
Suppose an application contains:
function deleteCustomer() {
alert("Customer deleted successfully");
}
Playwright can listen for the dialog and accept it.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.test/customers")
def handle_dialog(dialog):
print(dialog.type)
print(dialog.message)
dialog.accept()
page.on("dialog", handle_dialog)
page.get_by_role("button", name="Delete Customer").click()
browser.close()
dialog event.
Imagine an admin application where deleting a customer displays a JavaScript alert:
from playwright.sync_api import expect
def test_delete_customer(page):
page.goto("https://example.test/customers")
def handle_dialog(dialog):
assert dialog.type == "alert"
print("Alert message:", dialog.message)
dialog.accept()
page.on("dialog", handle_dialog)
page.get_by_role(
"button",
name="Delete Customer"
).click()
expect(
page.get_by_text("Customer deleted successfully")
).to_be_visible()
A JavaScript confirmation dialog normally contains
OK and Cancel.
function cancelOrder() {
const result = confirm(
"Are you sure you want to cancel this order?"
);
if (result) {
console.log("Order cancelled");
}
}
Accept the confirmation
def handle_dialog(dialog):
print(dialog.type)
print(dialog.message)
dialog.accept()
page.on("dialog", handle_dialog)
page.get_by_role(
"button",
name="Cancel Order"
).click()
Dismiss the confirmation
def handle_dialog(dialog):
print(dialog.message)
dialog.dismiss()
page.on("dialog", handle_dialog)
page.get_by_role(
"button",
name="Cancel Order"
).click()
A common e-commerce workflow is cancelling an order.
from playwright.sync_api import expect
def test_cancel_order(page):
page.goto(
"https://example.test/orders/ORD-1001"
)
def handle_dialog(dialog):
assert dialog.type == "confirm"
assert (
"cancel this order"
in dialog.message.lower()
)
dialog.accept()
page.on("dialog", handle_dialog)
page.get_by_role(
"button",
name="Cancel Order"
).click()
expect(
page.get_by_text("Cancelled")
).to_be_visible()
A JavaScript prompt asks the user for text.
const name = prompt(
"Enter customer name"
);
In Playwright, use dialog.accept("text") to provide
a value.
def handle_dialog(dialog):
print(dialog.type)
print(dialog.message)
dialog.accept("Rahul")
page.on("dialog", handle_dialog)
page.get_by_role(
"button",
name="Enter Customer Name"
).click()
prompt() → Playwright receives a
dialog → dialog.accept("value").
Consider an order-management application that asks the support engineer for a reference number.
from playwright.sync_api import expect
def test_enter_reference_number(page):
page.goto(
"https://example.test/support"
)
def handle_dialog(dialog):
assert dialog.type == "prompt"
assert (
"reference"
in dialog.message.lower()
)
dialog.accept("REF-2026-1001")
page.on("dialog", handle_dialog)
page.get_by_role(
"button",
name="Enter Reference"
).click()
expect(
page.get_by_text("REF-2026-1001")
).to_be_visible()
Playwright provides useful information through the dialog object.
| Property / Method | Purpose |
|---|---|
dialog.type |
Returns alert, confirm, prompt or beforeunload. |
dialog.message |
Returns the dialog text. |
dialog.default_value |
Returns the default value for a prompt. |
dialog.accept() |
Accepts the dialog. |
dialog.accept("value") |
Accepts a prompt and provides text. |
dialog.dismiss() |
Dismisses the dialog. |
Sometimes you do not want a permanent dialog listener. You can explicitly wait for the dialog while performing the action.
with page.expect_dialog() as dialog_info:
page.get_by_role(
"button",
name="Delete"
).click()
dialog = dialog_info.value
print(dialog.type)
print(dialog.message)
dialog.accept()
expect_dialog() clearly connects the expected dialog
with the specific action that triggers it.
A popup or new tab is different from a JavaScript dialog.
Playwright represents a new browser tab as another
Page.
with page.expect_popup() as popup_info:
page.get_by_role(
"link",
name="Open Report"
).click()
report_page = popup_info.value
report_page.wait_for_load_state()
print(report_page.url)
assert "report" in report_page.url
Payment applications frequently open a separate page or tab for an external payment provider.
from playwright.sync_api import expect
def test_payment_popup(page):
page.goto(
"https://example.test/checkout"
)
page.get_by_role(
"button",
name="Pay Now"
).click()
with page.expect_popup() as popup_info:
page.get_by_role(
"link",
name="Open Payment Gateway"
).click()
payment_page = popup_info.value
expect(
payment_page
.get_by_role("heading", name="Payment")
).to_be_visible()
print(
"Payment URL:",
payment_page.url
)
A common pattern is a button using JavaScript such as
window.open().
<button onclick="window.open('/invoice')">
Open Invoice
</button>
Playwright can capture the new page before the click completes.
with page.expect_popup() as popup_info:
page.get_by_role(
"button",
name="Open Invoice"
).click()
invoice_page = popup_info.value
expect(
invoice_page.get_by_text("Invoice")
).to_be_visible()
When an application opens multiple tabs, each tab is represented
by a Playwright Page.
with page.expect_popup() as popup_info:
page.get_by_role(
"link",
name="Customer Portal"
).click()
customer_page = popup_info.value
print("Main page:", page.url)
print("Customer page:", customer_page.url)
customer_page.get_by_role(
"heading",
name="Customer Portal"
)
Browser → Context → Page → Dialog / Popup.
❌ Avoid
page.get_by_role("button", name="Delete").click()
time.sleep(3)
# Hope the alert has appeared by now
✅ Preferred
with page.expect_dialog() as dialog_info:
page.get_by_role(
"button",
name="Delete"
).click()
dialog = dialog_info.value
assert dialog.type == "alert"
dialog.accept()
This example combines a confirmation dialog with application validation.
from playwright.sync_api import expect
def test_customer_cancels_order(page):
# Arrange
page.goto(
"https://example.test/orders/ORD-1001"
)
# Dialog handler
def handle_dialog(dialog):
assert dialog.type == "confirm"
assert (
"cancel"
in dialog.message.lower()
)
dialog.accept()
page.on("dialog", handle_dialog)
# Act
page.get_by_role(
"button",
name="Cancel Order"
).click()
# Assert
expect(
page.get_by_test_id("order-status")
).to_have_text("Cancelled")
The test follows:
Selenium handles JavaScript alerts through the browser's alert interface.
from selenium import webdriver
from selenium.webdriver.common.alert import Alert
driver = webdriver.Chrome()
driver.get(
"https://example.test/customers"
)
driver.find_element(
"id",
"deleteCustomer"
).click()
alert = Alert(driver)
print(alert.text)
alert.accept()
driver.quit()
from selenium.webdriver.common.alert import Alert
driver.find_element(
"id",
"cancelOrder"
).click()
alert = Alert(driver)
print(alert.text)
alert.accept()
Dismiss the confirmation
alert = Alert(driver)
alert.dismiss()
from selenium.webdriver.common.alert import Alert
driver.find_element(
"id",
"enterReference"
).click()
alert = Alert(driver)
print(alert.text)
alert.send_keys(
"REF-2026-1001"
)
alert.accept()
Selenium commonly works with window handles when a new browser window or tab is opened.
main_window = driver.current_window_handle
driver.find_element(
"id",
"openReport"
).click()
windows = driver.window_handles
for window in windows:
if window != main_window:
driver.switch_to.window(window)
print(
driver.current_url
)
break
| Scenario | Playwright | Selenium |
|---|---|---|
| JavaScript Alert |
page.on("dialog")
|
Alert(driver)
|
| Accept Alert |
dialog.accept()
|
alert.accept()
|
| Dismiss Alert |
dialog.dismiss()
|
alert.dismiss()
|
| Prompt Text |
dialog.accept("value")
|
alert.send_keys("value")
|
| Wait for Dialog |
page.expect_dialog()
|
Usually interact with Alert(driver)
after the alert appears.
|
| New Tab / Popup |
page.expect_popup()
|
Window handles |
| Switch Tab |
Work directly with the returned Page.
|
driver.switch_to.window()
|
| Popup URL |
popup.url
|
driver.current_url
after switching.
|
Scenario
User clicks Delete Customer, confirms the JavaScript dialog, and verifies that the customer is removed.
Playwright
with page.expect_dialog() as dialog_info:
page.get_by_role(
"button",
name="Delete Customer"
).click()
dialog = dialog_info.value
assert dialog.type == "confirm"
dialog.accept()
expect(
page.get_by_text(
"Customer deleted"
)
).to_be_visible()
Selenium
driver.find_element(
"id",
"deleteCustomer"
).click()
alert = Alert(driver)
assert alert.text == (
"Delete this customer?"
)
alert.accept()
assert driver.find_element(
"id",
"successMessage"
).is_displayed()
Playwright
with page.expect_popup() as popup_info:
page.get_by_role(
"link",
name="Open Invoice"
).click()
invoice = popup_info.value
expect(
invoice.get_by_text(
"Invoice"
)
).to_be_visible()
Selenium
main_window = (
driver.current_window_handle
)
driver.find_element(
"id",
"openInvoice"
).click()
for window in driver.window_handles:
if window != main_window:
driver.switch_to.window(window)
break
assert "Invoice" in (
driver.title
)
Both Playwright and Selenium can automate alerts, dialogs and multiple browser windows. Their APIs and synchronization models differ.
| Area | Playwright Approach | Selenium Approach |
|---|---|---|
| Dialog event |
Event listener or expect_dialog()
|
Alert interface |
| Popup |
expect_popup()
|
Window handles |
| New page |
Returned Page object
|
Switch driver context |
| Synchronization | Designed around browser events and auto-waiting behavior | Explicit waits are commonly used for many conditions |
| Test readability | Popup handling can be expressed close to the action that creates it. | Window-handle management can add additional steps. |
In a larger test suite, you may want to validate the dialog type and choose the correct behavior.
def handle_dialog(dialog):
print("Dialog type:", dialog.type)
print("Message:", dialog.message)
if dialog.type == "alert":
dialog.accept()
elif dialog.type == "confirm":
dialog.accept()
elif dialog.type == "prompt":
dialog.accept("Test User")
else:
dialog.dismiss()
page.on("dialog", handle_dialog)
A test should not simply accept every dialog. If a dialog is unexpected, the test should make that visible.
with page.expect_dialog() as dialog_info:
page.get_by_role(
"button",
name="Delete Customer"
).click()
dialog = dialog_info.value
assert dialog.type == "confirm"
assert (
dialog.message
== "Are you sure you want to delete this customer?"
)
dialog.accept()
This pattern verifies both the dialog type and the dialog message before accepting it.
| Mistake | Better Approach |
|---|---|
Using time.sleep() |
Wait for the actual dialog or popup event. |
| Registering the dialog handler too late | Register it before triggering the dialog. |
| Accepting every dialog blindly | Validate type and message when relevant. |
| Treating a popup as a JavaScript alert |
Use expect_popup() for a new page/tab.
|
| Using window-handle logic in Playwright |
Work with the returned Playwright
Page.
|
| Ignoring popup state | Assert the popup URL, title or important content. |
The following example demonstrates a realistic order-management workflow containing both a confirmation dialog and a popup.
from playwright.sync_api import expect
def test_order_management(page):
# --------------------------------
# 1. Open application
# --------------------------------
page.goto(
"https://example.test/orders"
)
# --------------------------------
# 2. Cancel order
# --------------------------------
with page.expect_dialog() as dialog_info:
page.get_by_role(
"button",
name="Cancel Order"
).click()
dialog = dialog_info.value
assert dialog.type == "confirm"
assert (
"cancel"
in dialog.message.lower()
)
dialog.accept()
# --------------------------------
# 3. Validate order status
# --------------------------------
expect(
page.get_by_test_id("order-status")
).to_have_text("Cancelled")
# --------------------------------
# 4. Open invoice in new tab
# --------------------------------
with page.expect_popup() as popup_info:
page.get_by_role(
"link",
name="Open Invoice"
).click()
invoice_page = popup_info.value
# --------------------------------
# 5. Validate popup
# --------------------------------
expect(
invoice_page.get_by_role(
"heading",
name="Invoice"
)
).to_be_visible()
expect(
invoice_page
).to_have_title(
"Order Invoice"
)
print(
"Invoice URL:",
invoice_page.url
)
| Requirement | Playwright Python |
|---|---|
| Listen for any dialog |
page.on("dialog", handler)
|
| Wait for one dialog |
page.expect_dialog()
|
| Read dialog type |
dialog.type
|
| Read dialog message |
dialog.message
|
| Accept |
dialog.accept()
|
| Accept prompt with value |
dialog.accept("value")
|
| Cancel / dismiss |
dialog.dismiss()
|
| Wait for popup |
page.expect_popup()
|
| Access popup |
popup_info.value
|
| Popup URL |
popup.url
|
- Register dialog handling before triggering the dialog.
-
Use
expect_dialog()when a specific action is expected to produce one dialog. - Validate the dialog type when the behavior matters.
- Validate the dialog message for important business workflows.
-
Use
dialog.accept()for OK/confirm behavior. -
Use
dialog.dismiss()for Cancel behavior. -
Use
dialog.accept("value")for prompt input. -
Use
expect_popup()for a newly opened page/tab. -
Treat the popup as a separate Playwright
Page. - Validate popup URL, title or business content.
-
Avoid arbitrary hard waits such as
time.sleep(). - Do not blindly accept every dialog because that can hide unexpected application behavior.
dialog.accept()
accept / dismiss
accept("value")
expect_popup()
JavaScript dialog →
dialog
New browser tab / popup →
Page
Playwright pattern →
wait for the browser event + perform the action + validate the result
Alert →
expect_dialog()
Confirm →
accept() / dismiss()
Prompt →
accept("value")
New Tab →
expect_popup()
Popup validation →
expect()