Playwright's onceDialog Saved Me From a Handler Leak
Table of Contents
I registered page.onDialog() to accept a delete confirmation in a test. The delete worked fine. Then, three steps later in the same test, the user clicked “Logout” — which also triggers a confirm() dialog. My delete handler was still registered. It silently accepted the logout confirmation, the user got logged out mid-test, and the next assertion failed with a session error. I spent 20 minutes blaming the app before realizing my own handler was the problem.
The Problem: onDialog Is Permanent
page.onDialog() registers a listener that stays active for the entire lifetime of the page. Every dialog that fires — whether you intended to handle it or not — goes through that handler.
// This handler stays registered for ALL future dialogs on this pagepage.onDialog(dialog -> dialog.accept());
// Delete confirmation — handled correctlypage.locator("[data-testid='delete-patient']").click();
// ... 3 steps later, logout also triggers confirm() ...page.locator("[data-testid='logout']").click();// Logout confirm silently accepted — NOT what we wantedThe logout dialog should have been dismissed (cancel the logout, stay in the app), but the permanent handler accepted it. The test continued on a logged-out session and failed on the next page interaction.
The Fix: onceDialog Auto-Removes
page.onceDialog() fires exactly once, then removes itself. The dialog handler is scoped to the next dialog only — after that, it’s gone.
// This handler fires ONCE, then auto-removespage.onceDialog(dialog -> dialog.accept());
// Delete confirmation — handled, handler now gonepage.locator("[data-testid='delete-patient']").click();
// Logout triggers confirm() — no active handler// Falls through to default behavior (or your base class handler)page.locator("[data-testid='logout']").click();Now the delete confirmation gets accepted, the handler removes itself, and the logout dialog falls through to whatever default handler you’ve registered in your base test class.
When to Use Which
The rule of thumb: if you’re writing page.onDialog() inside a test method (not in @BeforeMethod), you almost certainly want page.onceDialog() instead.
Related Posts
I Migrated 3 Teams Off Cypress — Here's When It's Still the Right Choice
An honest take on Cypress vs Playwright migrations from an SDET who's done three — including when migrating is the wrong call.
XPath text() vs Dot — Why Your Text Match Fails
The real difference between XPath text(), dot, contains(), and normalize-space() for test automation — with examples that explain the flaky failures.
Why Your Playwright Tests Are Flaky — The Async Trap Every SDET Falls Into
The 3 async mistakes that cause flaky Playwright tests after a Selenium migration — and how we fixed a 23% intermittent failure rate.
Get weekly QA automation insights
No fluff, just battle-tested strategies from 10+ years in the trenches.
No spam. Unsubscribe anytime.