QA How-To
How to Handle alerts in Playwright (2026)
Learn playwright how to handle alerts using dialog listeners, accept and dismiss actions, prompt input, message assertions, popup handling, and stable patterns.
22 min read | 3,428 words
TL;DR
Arm a page.once('dialog') handler before the triggering action, assert the dialog, then accept or dismiss it. Finish by asserting the application's post-dialog state, and scope persistent listeners carefully.
Key Takeaways
- Register the dialog handler before the action that can open the dialog.
- Use page.once for one expected dialog and page.on only for deliberate multi-dialog workflows.
- Every installed dialog listener must accept or dismiss every dialog it receives.
- Assert the dialog type and message, then verify the resulting application state.
- Test both accept and dismiss branches for confirms and prompts.
- Attach handlers to the page that owns the dialog, especially in popup workflows.
- Treat native browser dialogs and DOM-based modals as different automation problems.
playwright how to handle alerts is a practical Playwright workflow that depends on choosing the right event, locator, assertion, and diagnostic evidence for the scenario.
How to handle alerts in Playwright examples should prove three things: the application opened the expected dialog, the test chose the correct response, and the UI reached the correct state afterward. In Playwright Test, register a dialog listener before the action that opens an alert, confirm, prompt, or beforeunload dialog, then accept or dismiss it and assert the result.
That ordering rule matters because browser dialogs are modal. Once one appears, page JavaScript and user interaction are blocked until the dialog is handled. Playwright automatically dismisses dialogs only when no page or browser-context dialog listener is installed. The moment a listener exists, that listener must resolve every dialog it receives.
This guide uses TypeScript and the current Playwright Test APIs. The examples favor accessible locators, web-first assertions, one-shot listeners for isolated dialogs, and explicit cleanup for reusable listeners.
TL;DR
| Scenario | Recommended API | Critical detail |
|---|---|---|
| One alert | page.once('dialog', handler) | Register before clicking |
| Confirm | dialog.accept() or dialog.dismiss() | Assert type and message first |
| Prompt | dialog.accept('text') | Pass input only when accepting |
| Repeated dialogs | page.on('dialog', handler) | Remove or scope the listener |
| New tab dialog | popup.once('dialog', handler) | Attach to the page that owns it |
| beforeunload | page.close({ runBeforeUnload: true }) | The close call does not wait for closure |
A compact alert test looks like this:
import { test, expect } from '@playwright/test';
test('accepts a deletion alert', async ({ page }) => {
await page.goto('https://example.test/items');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.message()).toBe('Item deleted');
await dialog.accept();
});
await page.getByRole('button', { name: 'Delete item' }).click();
await expect(page.getByText('No items found')).toBeVisible();
});
1. playwright how to handle alerts: Understand the Core Contract
import { test, expect } from '@playwright/test';
test('accepts a confirmation dialog', async ({ page }) => {
await page.goto('/account');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('confirm');
expect(dialog.message()).toBe('Delete this account?');
await dialog.accept();
});
await page.getByRole('button', { name: 'Delete account' }).click();
await expect(page.getByText('Account deleted')).toBeVisible();
});
A Playwright Dialog object represents a JavaScript alert, confirm, prompt, or beforeunload confirmation. It is emitted through the dialog event on Page or BrowserContext. Its core methods are dialog.type(), dialog.message(), dialog.defaultValue(), dialog.accept(), and dialog.dismiss(). These methods map cleanly to what a user can observe or choose.
The event is emitted when the dialog opens, not when it closes. Your handler should inspect the dialog and resolve it quickly. Do not put slow network calls, long calculations, or unrelated assertions inside the handler. A blocked page cannot continue while the handler is doing that work.
The most important behavioral distinction is whether a listener exists:
| Listener state | Playwright behavior |
|---|---|
| No page or context listener | The dialog is dismissed automatically |
| Listener installed | Your handler must accept or dismiss |
| Listener only logs the message | The triggering action stalls |
| One-shot listener with page.once | It handles one dialog and removes itself |
| Persistent listener with page.on | It handles every later dialog until removed |
Automatic dismissal is convenient for tests where the dialog is irrelevant, but it is not proof that the application showed the right text. If dialog behavior belongs to the acceptance criteria, install a handler and assert it.
A common deadlock looks harmless:
page.on('dialog', dialog => {
console.log(dialog.message());
});
await page.getByRole('button', { name: 'Archive' }).click();
The click never completes because the listener observes the dialog without resolving it. Always end a successful handler path with await dialog.accept() or await dialog.dismiss().
For locator fundamentals used throughout this guide, see Playwright getByRole locators.
2. playwright how to handle alerts: Build the First Reliable Test
Install Playwright Test in a Node.js project and let the installer download the selected browsers:
npm init playwright@latest
npx playwright install
A small configuration gives examples a predictable base URL, a trace on first retry, and artifacts that help when dialog timing fails:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
Keep dialog tests isolated. A listener attached in one test must not influence another test or a later step in the same test. The built-in page fixture creates a fresh browser context and page for each test, which is a strong default boundary. Within one test, prefer page.once when exactly one dialog is expected.
The examples assume an application exposes semantic buttons and visible result messages. Replace URLs and labels with your product's values, but preserve the ordering: create the listener, trigger the dialog, then assert the post-dialog state.
You usually do not need a hard wait around a dialog. Locator actions auto-wait for actionability, the dialog event delivers the synchronization signal, and expect assertions retry until their condition is satisfied. Adding page.waitForTimeout makes the test slower without repairing incorrect event ordering. If your suite has general timeout trouble, use the diagnostic workflow in fixing Playwright timeout errors.
3. Handle Alerts With a One-Shot Listener
An alert has only one meaningful user response, OK. Use page.once because the test expects exactly one event. Assert dialog.type() and dialog.message() before accepting so the test verifies the user-facing contract, not merely the final page state.
import { test, expect } from '@playwright/test';
test('acknowledges the save alert', async ({ page }) => {
await page.goto('/profile');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.message()).toBe('Profile saved successfully');
await dialog.accept();
});
await page.getByRole('button', { name: 'Save profile' }).click();
await expect(page.getByRole('status')).toHaveText('Saved');
});
The click is awaited normally. Playwright dispatches the dialog event while the action is in progress, your handler accepts it, and the action can finish. There is no reason to wrap the click and handler in Promise.all because page.once registers synchronously. This approach is readable and prevents a race.
If the message includes a generated identifier, assert the stable business portion:
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('alert');
expect(dialog.message()).toMatch(/^Order ORD-[A-Z0-9]+ was created$/);
await dialog.accept();
});
Use a regular expression only for genuinely variable content. Broad patterns such as /created/ can allow broken wording, wrong entities, or missing identifiers to pass. A better test encodes the format that users and support teams depend on.
After the alert closes, assert an observable outcome such as a status message, row change, route, or API-backed state. Dialog text alone proves presentation, not that the operation succeeded. Conversely, the final state alone does not prove the alert appeared. Strong How to handle alerts in Playwright examples cover both sides of the interaction.
4. Test Confirm Dialogs for Both Accept and Dismiss Paths
A confirm dialog represents a branch. Clicking OK returns true to application code, while Cancel returns false. Both branches deserve coverage when they produce different business outcomes.
A parameterized test keeps shared setup in one place without hiding the expected behavior:
import { test, expect } from '@playwright/test';
const cases = [
{ choice: 'accept', remaining: 0, notice: 'Project removed' },
{ choice: 'dismiss', remaining: 1, notice: 'Removal canceled' },
] as const;
for (const testCase of cases) {
test('delete confirmation: ' + testCase.choice, async ({ page }) => {
await page.goto('/projects');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('confirm');
expect(dialog.message()).toBe('Delete project Alpha?');
if (testCase.choice === 'accept') {
await dialog.accept();
} else {
await dialog.dismiss();
}
});
await page
.getByRole('row', { name: /Alpha/ })
.getByRole('button', { name: 'Delete' })
.click();
await expect(page.getByTestId('project-count'))
.toHaveText(String(testCase.remaining));
await expect(page.getByRole('status')).toHaveText(testCase.notice);
});
}
This design makes a useful distinction between action and oracle. The handler supplies the user decision. Assertions outside the handler prove what that decision caused. If deletion is asynchronous, the web-first count and status assertions wait for the UI to settle.
Avoid a global rule that accepts every confirm dialog. It can turn a surprising account deletion, navigation warning, or consent prompt into an unnoticed side effect. Scope the choice to the test and assert exact dialog identity before accepting it.
For teams building typed fixture helpers around patterns like this, typing Playwright fixtures for QA explains how to preserve useful TypeScript inference.
5. Enter and Validate Prompt Dialog Values
A prompt includes a message, an optional default value, and a text input. Call dialog.accept(value) to submit text. Call dialog.dismiss() to return null to the application. dialog.defaultValue() lets you verify the input's initial content.
import { test, expect } from '@playwright/test';
test('renames a workspace through a prompt', async ({ page }) => {
await page.goto('/workspaces/quality');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('prompt');
expect(dialog.message()).toBe('Enter a new workspace name');
expect(dialog.defaultValue()).toBe('Quality');
await dialog.accept('Release Quality');
});
await page.getByRole('button', { name: 'Rename workspace' }).click();
await expect(page.getByRole('heading', { level: 1 }))
.toHaveText('Release Quality');
});
Test validation boundaries based on the application's requirements. Useful cases include an empty string, leading and trailing spaces, Unicode text, a maximum-length value, and cancellation. Do not assume the browser trims input. Trimming, normalization, and validation belong to application logic.
The cancellation test should prove that the existing value remains unchanged:
test('keeps the name when rename is canceled', async ({ page }) => {
await page.goto('/workspaces/quality');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('prompt');
await dialog.dismiss();
});
await page.getByRole('button', { name: 'Rename workspace' }).click();
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Quality');
});
Passing text to accept is appropriate for prompts. Alerts and confirms ignore prompt text because they have no input field. Keep data meaningful and explicit. A value such as Release Quality communicates intent better than test123 and makes failure screenshots easier to interpret.
6. Choose page.once, page.on, or browserContext.on Deliberately
The event scope should match the behavior under test. page.once is safest for a single known dialog. page.on is appropriate when one page deliberately opens several dialogs. browserContext.on can cover dialogs from multiple pages, including pages created later, but its broader scope demands careful filtering and cleanup.
| API | Scope | Lifetime | Best fit |
|---|---|---|---|
| page.once('dialog') | One page | One event | Isolated alert, confirm, or prompt |
| page.on('dialog') | One page | Until removed or page closes | Known sequence on one page |
| context.once('dialog') | All pages in context | One event | First dialog may belong to a popup |
| context.on('dialog') | All pages in context | Until removed or context closes | Central policy in a dedicated context |
For a known two-dialog workflow, a persistent handler can track order:
test('handles warning then completion dialogs', async ({ page }) => {
await page.goto('/imports');
const seen: string[] = [];
const handler = async (dialog: import('@playwright/test').Dialog) => {
seen.push(dialog.message());
if (dialog.message() === 'Existing records will be replaced') {
await dialog.accept();
return;
}
if (dialog.message() === 'Import complete') {
await dialog.accept();
return;
}
await dialog.dismiss();
throw new Error('Unexpected dialog: ' + dialog.message());
};
page.on('dialog', handler);
try {
await page.getByRole('button', { name: 'Replace and import' }).click();
await expect(page.getByRole('status')).toHaveText('100 records imported');
expect(seen).toEqual([
'Existing records will be replaced',
'Import complete',
]);
} finally {
page.off('dialog', handler);
}
});
The fallback dismisses an unknown dialog before throwing, so the page does not remain blocked. The finally block prevents listener leakage if an assertion fails. A listener is test state, and test state should have an explicit lifecycle.
7. Handle Popup and Multi-Page Dialogs Correctly
Dialogs belong to the page that opened them. If a click opens a popup and that popup later shows a confirm, attach the listener to the popup, not the original page.
test('accepts a confirmation in a popup', async ({ page }) => {
await page.goto('/reports');
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open report editor' }).click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');
popup.once('dialog', async dialog => {
expect(dialog.type()).toBe('confirm');
expect(dialog.message()).toBe('Publish this report?');
await dialog.accept();
});
await popup.getByRole('button', { name: 'Publish' }).click();
await expect(popup.getByRole('status')).toHaveText('Published');
});
The popup wait is armed before the click, just like any event triggered by an action. After receiving the Page object, the test installs the dialog listener before the popup action.
A context listener is useful when the product can open one of several pages dynamically. In that case, inspect dialog.page() to identify the owner:
const context = page.context();
context.once('dialog', async dialog => {
const owner = dialog.page();
expect(owner).not.toBeNull();
expect(owner?.url()).toContain('/report-editor');
await dialog.accept();
});
dialog.page() can be null for a beforeunload dialog if the page is already closed. Code that handles that dialog type should not rely unconditionally on a page reference.
When multiple workers run in parallel, each Playwright Test page fixture remains in its own context by default. Problems usually arise from broad listeners inside shared custom contexts, not from Playwright's normal per-test isolation.
8. Test beforeunload Without Assuming the Page Closed
A beforeunload confirmation is different from an alert. It is triggered while a page is trying to close or navigate away with unsaved work. Playwright exposes it when page.close() is called with runBeforeUnload: true.
The close call does not wait for the page to finish closing in this mode. The application can remain open if the dialog is dismissed. Test the chosen behavior explicitly:
test('keeps an editor open when unload is dismissed', async ({ page }) => {
await page.goto('/editor');
await page.getByLabel('Document body').fill('Unsaved changes');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('beforeunload');
await dialog.dismiss();
});
await page.close({ runBeforeUnload: true });
await expect(page.getByLabel('Document body'))
.toHaveValue('Unsaved changes');
});
For an accept path, avoid immediately using a page that may be closing. Wait for the close event:
test('allows an editor with changes to close', async ({ page }) => {
await page.goto('/editor');
await page.getByLabel('Document body').fill('Unsaved changes');
page.once('dialog', async dialog => {
expect(dialog.type()).toBe('beforeunload');
await dialog.accept();
});
const closed = page.waitForEvent('close');
await page.close({ runBeforeUnload: true });
await closed;
expect(page.isClosed()).toBe(true);
});
Browser wording for beforeunload is controlled largely by the browser, not the application, so exact message assertions are usually brittle. Assert type and behavior. Your product can control when the warning is registered, but modern browsers restrict custom text.
9. Build Reusable Dialog Helpers Without Hiding Assertions
A small helper reduces repetition, but it should still make expected type, message, and response visible at the call site. A helper that silently accepts everything is dangerous.
// tests/support/dialog.ts
import { expect, type Page } from '@playwright/test';
type DialogChoice =
| { action: 'accept'; promptText?: string }
| { action: 'dismiss' };
export function expectNextDialog(
page: Page,
expected: {
type: 'alert' | 'confirm' | 'prompt' | 'beforeunload';
message: string | RegExp;
},
choice: DialogChoice,
): void {
page.once('dialog', async dialog => {
expect(dialog.type()).toBe(expected.type);
if (typeof expected.message === 'string') {
expect(dialog.message()).toBe(expected.message);
} else {
expect(dialog.message()).toMatch(expected.message);
}
if (choice.action === 'accept') {
await dialog.accept(choice.promptText);
} else {
await dialog.dismiss();
}
});
}
A test remains readable:
test('approves a refund', async ({ page }) => {
await page.goto('/orders/42');
expectNextDialog(
page,
{ type: 'confirm', message: 'Refund $25.00 to the customer?' },
{ action: 'accept' },
);
await page.getByRole('button', { name: 'Issue refund' }).click();
await expect(page.getByRole('status')).toHaveText('Refund issued');
});
This helper registers synchronously and returns void, which discourages callers from awaiting registration. Its handler is asynchronous because accept and dismiss return promises. If your code style flags unobserved asynchronous event handlers, keep the handler narrow and rely on post-action assertions to expose failure. For complex flows, an explicit promise-based helper can capture errors, but it must still be armed before the trigger.
Reusable helpers should improve signal, not conceal workflow. Keep one-off or unusual behavior directly in the test.
10. Debug Playwright Dialog Handling Examples and CI Failures
When a dialog test hangs on click, inspect listeners first. A listener that only logs, a conditional branch that never accepts or dismisses, or a handler attached to the wrong page are the most common causes. Trace Viewer can show the triggering action, but native dialogs are transient, so handler logs remain useful.
Capture structured diagnostics without weakening assertions:
page.once('dialog', async dialog => {
console.info({
type: dialog.type(),
message: dialog.message(),
defaultValue: dialog.defaultValue(),
pageUrl: dialog.page()?.url() ?? null,
});
expect(dialog.type()).toBe('confirm');
expect(dialog.message()).toBe('Deploy version 2.4?');
await dialog.accept();
});
If a test passes locally but times out in CI, check whether CI renders a different route, feature flag, locale, permission state, or validation message. Increasing the timeout will not make a missing dialog appear. Confirm the precondition just before the trigger with a web-first assertion, then verify the dialog text expected for that environment.
Run a focused test with tracing:
npx playwright test tests/dialogs.spec.ts --project=chromium --trace=on
npx playwright show-trace test-results/path-to-trace/trace.zip
Also inspect whether a shared page.on listener remains installed after an earlier step. Use page.off with the same function reference. Anonymous functions cannot be conveniently removed later, which is another reason to name persistent handlers.
Do not replace dialog handling with DOM locators. Native JavaScript dialogs are not elements in the page DOM, so getByRole('dialog') cannot locate them. That locator is correct for custom HTML modals, which are a separate UI pattern. Determine which kind your application uses before choosing an API.
Interview Questions and Answers
Q: What happens when a Playwright test does not register a dialog listener?
Playwright automatically dismisses JavaScript dialogs when neither the page nor its browser context has a dialog listener. That prevents an irrelevant dialog from blocking a test. It does not verify the dialog, so acceptance criteria about its type or message require an explicit listener.
Q: Why should the listener be registered before the click?
The dialog can appear synchronously during the click. Registering afterward creates a race in which the event is already missed and the browser is blocked. Arm the event handler first, then perform the action that triggers it.
Q: When should you use page.once instead of page.on?
Use page.once when exactly one dialog is expected. It automatically removes itself and minimizes cross-step leakage. Use page.on only for a deliberate series of dialogs, and remove the handler when the series is finished.
Q: How do you test both branches of a confirm dialog?
Create separate or parameterized cases. In one, assert the confirm and call dialog.accept(); in the other, call dialog.dismiss(). Then assert distinct business outcomes outside the handler, such as deletion versus unchanged data.
Q: How do you submit text to a prompt?
Assert that dialog.type() is prompt, optionally verify dialog.defaultValue(), and call dialog.accept('expected text'). To test cancellation, call dialog.dismiss() and verify the application keeps or restores the prior state.
Q: Why can a dialog listener cause a timeout?
Once any listener receives a dialog, Playwright expects it to accept or dismiss that dialog. Logging the message without resolving it leaves the modal open and blocks the triggering action. Every handler branch, including unexpected-dialog branches, should resolve the dialog.
Q: Can getByRole('dialog') handle a JavaScript alert?
No. Native alerts, confirms, and prompts are browser UI surfaced through the Playwright dialog event. getByRole('dialog') targets accessible elements in the page DOM, such as an application-built modal.
Q: How would you handle a dialog opened in a popup?
Wait for the popup event before the action that opens it, obtain the popup Page, and register the dialog handler on that page before triggering the dialog. A browser-context listener is an alternative when the owner page is not known, but it should inspect dialog.page() and remain tightly scoped.
Common Mistakes
- Registering page.once('dialog') after clicking. The event may already have fired, so install the handler first.
- Logging a dialog without accepting or dismissing it. Any installed listener owns resolution and can freeze the page.
- Accepting every dialog globally. This can approve an unrelated destructive action and hide a product defect.
- Asserting only the message. Also verify type and the resulting application state.
- Using page.on for a single event without cleanup. Prefer page.once for a one-dialog expectation.
- Calling page.off with a new anonymous function. Removal requires the same handler reference that was registered.
- Attaching the handler to the opener instead of the popup that owns the dialog.
- Treating a native dialog like an HTML modal. Use dialog events for browser dialogs and locators for DOM modals.
- Adding page.waitForTimeout to fix event ordering. A hard sleep cannot reliably repair a listener registered too late.
- Matching volatile beforeunload text. Browser-controlled wording can vary, so assert type and behavior.
- Throwing before resolving an unexpected dialog. Dismiss it first, then report the unexpected state.
- Omitting the cancel path. Confirm and prompt cancellation often carry important data-loss or safety behavior.
Conclusion
Reliable How to handle alerts in Playwright examples are built around event ownership and observable outcomes. Register the listener before the trigger, assert the Dialog object's stable properties, accept or dismiss it, and verify what the application did next. Use page.once for isolated interactions and reserve broader listeners for workflows that truly need them.
Add one alert test, both confirm branches, prompt submission and cancellation, and any critical beforeunload behavior to your suite. Keep handlers short, scoped, and explicit, then use traces and structured logs when environment-specific failures appear.
Interview Questions and Answers
What is Playwright's default behavior for JavaScript dialogs?
Playwright dismisses dialogs automatically when there is no dialog listener on the page or browser context. If a listener is present, the listener becomes responsible for accepting or dismissing every received dialog. Otherwise the modal blocks page execution and the triggering action can time out.
Why must a dialog handler be registered before its triggering action?
The browser can open the dialog synchronously while the action is executing. Registering afterward can miss the event and leaves no safe synchronization point. Installing the handler first removes that race.
When is page.once preferable to page.on for dialog testing?
page.once is preferable when exactly one dialog is expected because it automatically removes the listener after the first event. page.on is useful for multiple intentional dialogs, but it needs filtering and explicit cleanup. The narrower listener usually produces safer tests.
How would you verify a confirm dialog comprehensively?
I would assert its type and stable message, run separate accept and dismiss cases, and verify the distinct business outcome of each choice. The listener models the user decision, while web-first assertions outside the listener verify the state transition.
How do you handle a prompt dialog in Playwright?
I register a one-shot dialog listener before the trigger, assert type, message, and defaultValue when relevant, then call dialog.accept with the intended text. I also cover dismissal and important validation boundaries such as empty or maximum-length input.
What causes dialog-related Playwright tests to hang?
The usual cause is a listener that logs or asserts but never calls accept or dismiss. Other causes include a handler branch that does not resolve an unexpected dialog and a listener attached to the wrong page. I resolve the dialog on every path and keep the handler scoped.
How are native dialogs different from HTML modal dialogs in tests?
Native alerts, confirms, and prompts are browser UI represented by Playwright Dialog objects. HTML modals are elements in the document and should be tested with locators, accessibility roles, and normal actions. Confusing the two leads to locators that can never find a native alert.
How would you handle dialogs from multiple pages?
When I know the owner, I attach the listener directly to that Page, such as a popup. If the owner is dynamic, I can listen on BrowserContext and inspect dialog.page(), while tightly scoping and removing the listener. I avoid context-wide automatic acceptance policies.
Frequently Asked Questions
Does Playwright automatically handle alert dialogs?
Yes. Playwright automatically dismisses JavaScript dialogs when no page or browser-context dialog listener exists. Once you install a listener, that listener must accept or dismiss each dialog it receives.
How do I accept a confirm dialog in Playwright?
Register page.once('dialog') before the triggering action, assert that dialog.type() is confirm, and await dialog.accept(). Then assert the UI or data change caused by accepting.
How do I cancel a Playwright dialog?
Call await dialog.dismiss() inside a listener registered before the trigger. For confirm and prompt dialogs, also verify that the canceled operation did not change application state.
How do I enter text in a Playwright prompt?
Call await dialog.accept('your text') from the prompt handler. You can inspect dialog.defaultValue() before accepting to verify the prompt's initial value.
Why does my Playwright click hang when a dialog appears?
A registered listener probably observed the dialog without resolving it, or one conditional branch skipped accept and dismiss. Native dialogs block the page until the handler completes one of those actions.
Should I use page.once or page.on for dialogs?
Use page.once for a single expected dialog because it removes itself after one event. Use page.on for a known sequence, keep the handler reference, and remove it with page.off afterward.
Can Playwright locators find browser alert text?
No. Native JavaScript dialogs are outside the page DOM and are handled through the dialog event. Locators such as getByRole('dialog') apply to HTML modal components rendered by the application.
How do I test a beforeunload dialog?
Install a dialog handler, call page.close({ runBeforeUnload: true }), and accept or dismiss the beforeunload dialog. Do not assume the close call waits for the page to close, and assert the resulting page state.