# Dialogs JavaScript dialogs (`alert`, `confirm`, `prompt`, `beforeunload`) appear as native browser modals — they block the page until the user or the automation handles them. > **craftdriver's stance: dialogs block until handled.** > If a dialog opens and nothing handles it, the action that triggered > it will time out. This is intentional. Playwright silently > auto-dismisses unhandled dialogs; craftdriver promotes them to loud > failures so unexpected dialogs surface as broken tests rather than > passing tests against a stuck browser. You handle a dialog one of three ways, depending on the shape of the test: | API | When to use | | ------------------------- | -------------------------------------------------------- | | `browser.waitForDialog()` | One-shot — you know exactly which action triggers it. | | `browser.onDialog(...)` | Persistent — multiple dialogs in a workflow, or "accept all". | | `browser.acceptDialog()` / `browser.dismissDialog()` | You already raced past the dialog and want to handle whatever is open. | ## `waitForDialog(opts?)` — one-shot wait Returns a `Promise` that resolves the next time a dialog opens. Pair it with the triggering action in `Promise.all` so both run concurrently: ```typescript // confirm() const [, dialog] = await Promise.all([ browser.click('#confirm-btn'), browser.waitForDialog(), ]); console.log(dialog.type()); // 'confirm' console.log(dialog.message()); // 'Are you sure?' await dialog.accept(); // continue // prompt() — pass text to fill the input first const [, prompt] = await Promise.all([ browser.click('#prompt-btn'), browser.waitForDialog(), ]); console.log(prompt.defaultValue()); // pre-filled default await prompt.accept('my answer'); // alert() const [, alert] = await Promise.all([ browser.click('#alert-btn'), browser.waitForDialog(), ]); await alert.dismiss(); ``` `waitForDialog` rejects with a timeout error if no dialog appears within `opts.timeout` (default: the browser's default timeout). ## `onDialog(handler)` — persistent handler Register a handler that fires for **every** dialog until you call the returned `off()` function. Use it when a workflow triggers multiple dialogs, or when you want a blanket "accept everything" during a noisy sequence: ```typescript const off = browser.onDialog(async (dialog) => { await dialog.accept(); }); await runNoisyWorkflow(); off(); // stop listening — subsequent dialogs are loud again ``` `onDialog` does not retroactively handle dialogs that opened before registration; pair it with `acceptDialog` / `dismissDialog` for those. ## Imperative `acceptDialog` / `dismissDialog` When you cannot wrap the triggering action in `Promise.all` (e.g. multiple actions race the dialog, or you handle a dialog inside a catch block), use the imperative API to act on whichever dialog is currently open: ```typescript await browser.acceptDialog(); // click OK await browser.acceptDialog('value'); // fill a prompt, then accept await browser.dismissDialog(); // click Cancel / close const text = await browser.getDialogMessage(); ``` These map directly onto W3C Classic alert endpoints (`/alert/accept`, `/alert/dismiss`, `/alert/text`) and throw if no dialog is currently open. ## `Dialog` interface | Member | Description | | ---------------------------- | -------------------------------------------------------------------------- | | `dialog.type()` | `'alert'` \| `'confirm'` \| `'prompt'` \| `'beforeunload'` | | `dialog.message()` | Text shown in the dialog. | | `dialog.defaultValue()` | Pre-filled value for prompts; empty string for other types. | | `await dialog.accept(text?)` | Click OK. For prompts, optionally provide the input text first. | | `await dialog.dismiss()` | Click Cancel / close. | ## Recipes ### Accept everything during a flow ```typescript const off = browser.onDialog((d) => d.accept()); try { await runFlow(); } finally { off(); } ``` ### Assert on a dialog and dismiss ```typescript const [, dialog] = await Promise.all([ browser.click('#delete'), browser.waitForDialog(), ]); expect(dialog.type()).toBe('confirm'); expect(dialog.message()).toContain('cannot be undone'); await dialog.dismiss(); ``` ### `beforeunload` confirmation `beforeunload` arrives like any other dialog. Accept it to allow the navigation, dismiss it to keep the page: ```typescript const [, dialog] = await Promise.all([ browser.navigateTo('/somewhere-else'), browser.waitForDialog(), ]); expect(dialog.type()).toBe('beforeunload'); await dialog.accept(); ```