# dismiss-dialog ## Purpose `dismissDialog` attaches a one-shot handler to the next `page.on("dialog")` event, with the caller choosing between `accept` and `dismiss`. It's the small piece of glue that turns Playwright's event-driven dialog model into an imperative one-liner, which matches how most tests actually think about modal dialogs. ## Features - Installs a single `page.on("dialog", …)` listener; auto-removes after it fires - Choice of `accept` (OK/Allow) or `dismiss` (Cancel/Deny) - Safe to register before the action that triggers the dialog - Returns an unsubscribe function so the test can detach manually if the dialog never appears ## Usage: Basic Example ```ts import { dismissDialog } from "@suaveplan/testing-e2e/helpers"; const off = dismissDialog(page, "accept"); await page.click("text=Delete"); // If a confirm() pops, it is accepted. off(); // optional — the handler auto-removes after firing ``` ## Usage: Advanced Example ```ts import { dismissDialog } from "@suaveplan/testing-e2e/helpers"; import { expect } from "@playwright/test"; test("destructive action requires confirm", async ({ page }) => { await page.goto("/account"); dismissDialog(page, "dismiss"); // cancel the confirm await page.click("text=Delete account"); await expect(page.getByRole("alert")).toHaveText(/cancelled/i); dismissDialog(page, "accept"); // now accept it await page.click("text=Delete account"); await expect(page).toHaveURL("/goodbye"); }); ``` ## API Reference ### `dismissDialog(page, action): () => void` - `page: Page` — Playwright page - `action: "accept" | "dismiss"` — what to do when the dialog appears - Returns an unsubscribe function; safe to call after the handler has already auto-removed ## Implementation Notes Per the project-wide guidance around browser dialogs, tests should avoid triggering dialogs when possible — they block the entire Playwright event stream until dismissed. `dismissDialog` is for the cases where dismissal is unavoidable (e.g. testing a `beforeunload` prompt). The handler uses `{ once: true }` semantics so a stale handler from a prior test never intercepts the current one. ### Race Protection `dismissDialog(page)` registers a one-shot `page.once("dialog", d => d.dismiss())` listener and immediately returns a disposer that removes the listener if the dialog never fires. Doing it as one-shot avoids leaking listeners across test boundaries — a repeated bug in the previous iteration. The helper also logs the dialog's `type` and `message` through the structured logger so trace viewers show exactly what was dismissed.