Dialogs and popups

Answer JavaScript dialogs and interact with the pages your app opens in a new tab or window.

Use this guide when a flow asks you to confirm an action or opens a new tab or window. Wrap the action that opens the dialog or popup so Retest is waiting before it appears.

page.dialog() handles JavaScript alert, confirm, prompt and beforeunload dialogs, which hold the page until answered. For a modal built from page elements, including an HTML dialog element, use ordinary Locators and actions.

Answer a dialog Link to Answer a dialog

The task app's Delete button opens a confirmation. Wait for that dialog, check its message if it matters to the flow, then accept it and check the page's result.

tests/dialogs.retest.ts
import { expect, test } from '@rehearsal-labs/retest'test('confirms deleting a task', async ({ page }) => {  await page.goto('/dialogs')  const dialog = await page.dialog(() =>    page.getByRole('button', { name: 'Delete' }).click(),  )  expect(dialog.type).toBe('confirm')  expect(dialog.message).toBe('Delete this task?')  await dialog.accept()  await expect(page.getByTestId('status')).toHaveText('Deleted')})

page.dialog() returns when the dialog opens, while the click is still waiting. Await accept() or dismiss() next. The answer finishes both the dialog and its opening action before you send another page command.

To test cancellation, use await dialog.dismiss() and check that the app kept the task. Answer each dialog once; a second answer fails with usage, and another command while it is open fails with not_actionable.

Answer a prompt Link to Answer a prompt

A prompt accepts a text answer. Passing no text accepts its default value, while dismissing it returns null to the app.

TypeScript
await page.goto('/dialogs')const rename = await page.dialog(() =>  page.getByRole('button', { name: 'Rename' }).click(),)expect(rename.defaultValue).toBe('Release checklist')await rename.accept('Release notes')await expect(page.getByTestId('status')).toHaveText('Renamed to Release notes')

Only a prompt accepts text; supplying text for an alert or confirmation fails with usage. If answering a dialog immediately opens another, wrap the first answer in a second page.dialog() wait.

TypeScript
const first = await page.dialog(() =>  page.getByRole('button', { name: 'Two dialogs' }).click(),)const second = await page.dialog(() => first.accept())await second.accept()await expect(page.getByTestId('status')).toHaveText('Second accepted')

The second wait starts before you answer the first dialog, so it catches the confirmation that follows. Once both are answered, check the page to confirm that the sequence finished.

Unexpected dialogs Link to Unexpected dialogs

If no dialog opens within the action budget, the wait fails with timeout; an earlier failure in the action is reported instead. If the test returns with a dialog still open, it fails with not_awaited, naming the wait's source line.

On Chromium, Retest dismisses a dialog that no test waited for and fails the action that opened it with not_actionable. A dialog opened later by a timer also fails the test, so automatic dismissal does not make an unexpected dialog pass.

For a beforeunload confirmation, wrap the navigation or popup close that asks to leave. Accepting lets the page leave; dismissing keeps it where it was and fails that command with not_actionable.

Leaving a page

Chromium shows beforeunload only after the page has received a click. Retest accepts an unhandled beforeunload so the requested navigation can finish, but still reports the unexpected dialog as a failure.

Interact with a popup Link to Interact with a popup

Use page.popup() for a link with a target, window.open() or a form that opens a new page. It returns a page with its own locators, actions and checks, after its first document arrives and the triggering action finishes.

tests/popups.retest.ts
import { expect, test } from '@rehearsal-labs/retest'test('saves an email in the terms popup', async ({ page }) => {  await page.goto('/popups')  const terms = await page.popup(() =>    page.getByRole('link', { name: 'Terms' }).click(),  )  await terms.getByLabel('Email').fill('alice@tasks.example')  await terms.getByRole('button', { name: 'Save' }).click()  await expect(terms.getByTestId('saved')).toHaveText('Saved alice@tasks.example')  await terms.close()  await expect(page).toHaveURL('/popups')})

Calls on terms act on the popup, and calls on page still act on the opener. A popup can also have dialogs and open more popups; for a sign-in popup, the secret's allowed origins still apply.

Close a popup when you have finished with it, or let Retest close it at the end of the test. If it closes itself first, a later command fails with not_actionable and sends nothing; commands after your own close() fail with usage.

A popup wait takes only a newly opened page, so reusing an existing named window does not satisfy it. If no new page opens within the action budget, the wait fails with timeout.

Reports and platforms Link to Reports and platforms

The report timeline shows each dialog and its answer. Popup steps have their own app names, such as web/popup-1, so you can follow both pages. On failure, each popup still open gets its own screenshot when capture is allowed.

Popup evidence

Popup recordings, console output and network diagnostics are not captured. A recording run that opens a popup reports partial evidence, even though the popup can have a failure screenshot.

Dialogs and popups work on Chromium-family browsers. Electron supports JavaScript dialogs in its first window, but popup() is unsupported because Retest reaches only that window.

Firefox and WebKit refuse dialog() and popup() with unsupported. If a JavaScript dialog opens there, the command that opened it and every later command, including screenshots and close, fail with unsupported because the dialog blocks the page. Native apps use page.alert for their alerts and do not have browser popups.

Next steps Link to Next steps