Skip to content
Overview

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.

1 · Open & Verify

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.

TypeScript
// 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();
2 · Close via Button

Use a scoped locator to find the close button inside the dialog. This prevents false matches with other buttons on the page.

TypeScript
// Playwright — close dialog via × button
await page.getByTestId('open-info-dialog').click();

const dialog = page.getByTestId('info-alert-dialog');
await expect(dialog).toBeVisible();

await dialog.getByTestId('info-dialog-close-btn').click();
await expect(dialog).not.toBeVisible();
3 · Confirm Action

Scope into the dialog before targeting the confirm or cancel button. Unscoped button clicks are fragile when similar buttons exist elsewhere on the page.

TypeScript
// 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();
4 · Aria-Label Targeting

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.

TypeScript
// 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();
5 · Backdrop 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.

TypeScript
// 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();
6 · Escape Key

Keyboard dismissal is common in accessible UIs. Always verify the focus state before pressing Escape — some frameworks require focus on the dialog element.

TypeScript
// 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();
7 · Scoped Locators

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.

TypeScript
// 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();
Method Summary

Quick reference across all three frameworks.

ActionSeleniumPlaywright JSPlaywright PYCypress
Open dialogelement.click()locator.click()locator.click().click()
Assert visibleisDisplayed()expect(dialog).toBeVisible()expect(dialog).to_be_visible()should('be.visible')
Scope to dialogfindElement(By.css('[role="dialog"]'))getByRole('dialog')get_by_role('dialog')get('[role="dialog"]')
Escape keysendKeys(Keys.ESCAPE)keyboard.press('Escape')keyboard.press('Escape')type('{esc}')
Backdrop clickActions.moveToElement(el, x, y).click()click({ position: {x,y} })click(position={'x':5,'y':5}).click('topLeft')
Aria-label buttonfindElement(By.css("[aria-label='...']"))getByRole('button', { name: '...' })get_by_role('button', name='...')find("[aria-label='...']")
FAQ