Dialogs appear in real applications for confirmations, warnings, and notifications. Automating them well means knowing how to open, assert, interact, and close them — including edge cases like backdrop clicks and keyboard dismissal.
Always assert that the dialog is visible before interacting with it. Race conditions can cause tests to fail if you click before the dialog renders.
// Playwright — open dialog and assert it is visible
await page.getByTestId('open-info-dialog').click();
const dialog = page.getByRole('dialog', { name: 'Session Notice' });
await expect(dialog).toBeVisible();Scope into the dialog before targeting the confirm or cancel button. Unscoped button clicks are fragile when similar buttons exist elsewhere on the page.
// Playwright — confirm action inside dialog
await page.getByTestId('open-confirm-dialog').click();
const dialog = page.getByTestId('confirm-action-dialog');
await expect(dialog.getByRole('heading')).toHaveText('Confirm Submission');
await dialog.getByTestId('confirm-ok-btn').click();
await expect(dialog).not.toBeVisible();When a button has no data-testid, check for an aria-label. It is the most stable fallback when test ids are missing — more reliable than text alone.
// Playwright — locate button by aria-label (no data-testid)
await page.getByTestId('open-delete-dialog').click();
const dialog = page.getByRole('dialog', { name: 'Delete Account' });
// The Delete button has aria-label but no data-testid
await dialog.getByRole('button', {
name: 'Confirm account deletion'
}).click();Clicking the backdrop requires targeting a position outside the dialog box. A plain click() lands on the dialog box center, which stops propagation and does not close the dialog.
// Playwright — click backdrop (outside dialog box)
await page.getByTestId('open-backdrop-dialog').click();
const backdrop = page.getByTestId('backdrop-dismiss-dialog');
await expect(backdrop).toBeVisible();
// Click at top-left corner — outside the centered dialog box
await backdrop.click({ position: { x: 5, y: 5 } });
await expect(backdrop).not.toBeVisible();Keyboard dismissal is common in accessible UIs. Always verify the focus state before pressing Escape — some frameworks require focus on the dialog element.
// Playwright — press Escape to dismiss dialog
await page.getByTestId('open-escape-dialog').click();
const dialog = page.getByTestId('escape-dismiss-dialog');
await expect(dialog).toBeVisible();
await page.keyboard.press('Escape');
await expect(dialog).not.toBeVisible();Repeated elements with the same data-testid require scoping to a unique parent. The data-notif-id attribute is the stable anchor for identifying the right item in the list.
// Playwright — scope to the right notification before dismissing
// All Dismiss buttons share data-testid — scope to the unique parent
const targetNotif = page.locator(
'[data-testid="notif-item"][data-notif-id="notif-2"]'
);
await targetNotif.getByTestId('notif-dismiss-btn').click();
// Confirm in the dialog scoped by data-notif-id
const dialog = page.locator(
'[data-testid="dismiss-confirm-dialog"][data-notif-id="notif-2"]'
);
await dialog.getByRole('button', { name: /Confirm dismiss/i }).click();Quick reference across all three frameworks.
| Action | Selenium | Playwright JS | Playwright PY | Cypress |
|---|---|---|---|---|
| Open dialog | element.click() | locator.click() | locator.click() | .click() |
| Assert visible | isDisplayed() | expect(dialog).toBeVisible() | expect(dialog).to_be_visible() | should('be.visible') |
| Scope to dialog | findElement(By.css('[role="dialog"]')) | getByRole('dialog') | get_by_role('dialog') | get('[role="dialog"]') |
| Escape key | sendKeys(Keys.ESCAPE) | keyboard.press('Escape') | keyboard.press('Escape') | type('{esc}') |
| Backdrop click | Actions.moveToElement(el, x, y).click() | click({ position: {x,y} }) | click(position={'x':5,'y':5}) | .click('topLeft') |
| Aria-label button | findElement(By.css("[aria-label='...']")) | getByRole('button', { name: '...' }) | get_by_role('button', name='...') | find("[aria-label='...']") |