Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Handle a JavaScript dialog in Playwright by registering a page.on('dialog') listener before the action that opens it, then calling dialog.accept() or dialog.dismiss(). If you do nothing, Playwright automatically dismisses dialogs. A new browser tab is different: wait for the opener page’s popup event. A print dialog is tested by intercepting window.print(), not as a normal JavaScript dialog.

The distinction matters because an alert, confirm, prompt, or beforeunload dialog blocks page execution. Once you install a listener, that listener owns the dialog; if it only logs the message and never closes it, the click that triggered it can wait indefinitely.

What counts as a dialog in Playwright?

Playwright exposes the browser’s JavaScript modal APIs through the Dialog object. The dialog’s type(), message(), and defaultValue() methods let a test inspect what appeared before choosing a response.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Browser UI dialog.type() Typical response Important detail
alert() alert accept() There is no user-entered value.
confirm() confirm accept() or dismiss() Accept represents OK; dismiss represents Cancel.
prompt() prompt accept('text') or dismiss() accept() can supply the text returned to the page.
Unload confirmation beforeunload accept() or dismiss() It is only triggered when the page is closed with runBeforeUnload: true.

These are browser JavaScript dialogs, not HTML elements that can be located with CSS or role selectors.

Accept or dismiss an alert, confirm, or prompt

Install the handler first, then perform the click or other action that opens the modal:

page.on('dialog', async dialog => {
  console.log(dialog.type(), dialog.message());
  await dialog.accept();
});

await page.getByRole('button', { name: 'Continue' }).click();

The order is essential. Registering the listener after the click creates a race: the page can already be blocked by the modal before Playwright starts listening.

Choose a response from the dialog contents

Use the type and message to apply different behavior to different dialogs. This example rejects a destructive confirmation, supplies text to a prompt, and accepts anything else:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.on('dialog', async dialog => {
  if (dialog.type() === 'confirm' && dialog.message().includes('delete')) {
    await dialog.dismiss();
  } else if (dialog.type() === 'prompt') {
    await dialog.accept('approved value');
  } else {
    await dialog.accept();
  }
});

await page.getByText('Delete account').click();

For assertions, inspect dialog.type() and dialog.message() before closing it. dialog.defaultValue() is available when the test needs to verify the dialog’s supplied default.

Accepting versus dismissing

  • await dialog.accept(): presses OK. For a prompt, pass the string that the page should receive.
  • await dialog.dismiss(): presses Cancel and makes a confirm or prompt return its canceled result.
  • Always await the call: the modal must be closed before the page can continue.

Why a click hangs when an alert appears

With no page.on('dialog') or browserContext.on('dialog') listener, Playwright auto-dismisses dialogs. That default is useful when the dialog is irrelevant to a test.

Once a listener exists, Playwright stops applying the automatic behavior. The listener must call accept() or dismiss(). A handler that only prints dialog.message() leaves the modal open, so the browser cannot execute more page JavaScript and the triggering action appears to freeze.

If the test already has a global listener, check that every branch closes the dialog. A conditional branch that does not match the message must still end with an explicit accept or dismiss.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test a beforeunload confirmation

Playwright does not run a page’s unload handlers during an ordinary page.close(). Pass runBeforeUnload: true when the purpose of the test is to exercise that confirmation:

page.on('dialog', async dialog => {
  expect(dialog.type()).toBe('beforeunload');
  await dialog.dismiss();
});

await page.close({ runBeforeUnload: true });

Use accept() instead when the scenario is intended to leave the page. The listener must be installed before the close operation.

Handle a real popup window or new tab

A window opened by window.open(), a link with a new target, or similar browser behavior is a new Page, not a JavaScript Dialog. Wait on the opener page’s popup event before clicking:

const popupPromise = page.waitForEvent('popup');
await page.getByText('open the popup').click();

const popup = await popupPromise;
console.log(await popup.evaluate(() => location.href));

The returned page is available once its initial navigation response has started. Use that Page for locators, assertions, screenshots, and further navigation. Do not try to accept or dismiss a tab with a dialog listener.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test a print dialog

window.print() is handled separately from alert, confirm, prompt, and beforeunload. Replace the page’s print function with a promise before triggering the print control:

await page.goto('<url>');
await page.evaluate(() => {
  (window as any).waitForPrintDialog = new Promise<void>(resolve => {
    window.print = resolve;
  });
});

await page.getByText('Print it!').click();
await page.waitForFunction(() => (window as any).waitForPrintDialog);

This verifies that the application called window.print(). It does not attempt to automate the operating-system print window as though it were a Playwright JavaScript dialog.

Apply one dialog policy to every page in a context

When the same rule should cover every page belonging to a browser context, listen on the context rather than each page:

context.on('dialog', async dialog => {
  await dialog.dismiss();
});

The BrowserContext dialog event covers dialogs from every page in that context (documented as available from Playwright v1.34). Choose a page listener when only one tab has the policy; choose the context listener when popups and additional pages should behave identically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A reliable dialog-handling checklist

  1. Identify the UI: decide whether it is a JavaScript dialog, a new Page, or a print invocation.
  2. Install the observer first: put page.on('dialog'), context.on('dialog'), or waitForEvent('popup') before the triggering action.
  3. Inspect when necessary: branch on type() and message(); use defaultValue() for prompt assertions.
  4. Close every dialog: every listener path must await accept() or dismiss().
  5. Keep scope intentional: a page listener is local; a context listener affects all pages in that context.
  6. Use the right assertion: assert a returned popup page for a new tab, or wait on the replacement promise for print.

Troubleshooting common failures

The click or navigation never finishes

Cause: a dialog listener was registered, but one code path only logs or performs an assertion.

Fix: make every branch await dialog.accept() or dialog.dismiss(). If the dialog is irrelevant, remove the listener and allow Playwright’s automatic dismissal.

The test never sees the alert

Cause: the listener was added after the action, or the page has no listener because the dialog was auto-dismissed.

Fix: register the listener before the click, navigation, or script evaluation that opens the dialog. If you only need the page to continue, do not add a listener.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A new tab is not handled

Cause: a popup page was treated as a JavaScript dialog.

Fix: create page.waitForEvent('popup') before the click, await it, and use the resulting Page.

The unload handler does not run

Cause: the page was closed without the opt-in flag.

Fix: call page.close({ runBeforeUnload: true }) and install a listener that handles the resulting beforeunload event.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The print test waits forever

Cause: the test is waiting for a normal dialog event or replaced window.print after clicking.

Fix: install the promise replacement first, trigger the control, then wait for the page promise.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

One complete JavaScript example

The following script shows the ordering for a confirm, a prompt, a new tab, and a print call. Replace the URL and selectors with those from your application:

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();

page.on('dialog', async dialog => {
  console.log(`${dialog.type()}: ${dialog.message()}`);
  if (dialog.type() === 'prompt') {
    await dialog.accept('test value');
  } else {
    await dialog.dismiss();
  }
});

await page.goto('https://example.com/app');
await page.getByRole('button', { name: 'Open confirmation' }).click();

const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open report' }).click();
const popup = await popupPromise;
await popup.waitForLoadState();

await page.evaluate(() => {
  (window as any).printCalled = new Promise<void>(resolve => {
    window.print = resolve;
  });
});
await page.getByRole('button', { name: 'Print' }).click();
await page.waitForFunction(() => (window as any).printCalled);

await browser.close();

If your project is plain JavaScript, remove the TypeScript-only type annotations such as Promise<void> and the as any casts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If the goal is a clean screenshot or PDF rather than testing dialog behavior, ScreenshotNeo provides a single request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

Use the API documentation at https://screenshotneo.com/docs/ for the full option list, including full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, custom viewport and retina scale, PDF paper settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and the OpenAPI specification.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every feature is included on every plan. The current monthly options are:

Plan Price Included shots
Free $0 1,000; no card required
Starter $5 3,000
Growth $15 15,000
Pro $39 60,000
Scale $99 250,000
Business $249 1,000,000

Yearly billing provides two months free. Start with 1,000 free screenshots a month with no card; paid plans start at $5 for 3,000 shots.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does ScreenshotNeo replace Playwright dialog tests?

No. ScreenshotNeo returns screenshots or PDFs through an API and MCP server; Playwright remains the tool for asserting alert, confirm, prompt, beforeunload, popup-page, and print behavior.

Can I use a context listener when a popup page is created later?

Yes. A browserContext.on('dialog') listener applies to dialogs from every page in that context, including pages opened after the listener is registered.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.