# Dialogs and popups

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

Source: https://rehearsal.dev/retest/docs/guides/dialogs-and-popups

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](https://rehearsal.dev/retest/docs/essentials/locators.md) and actions.

## 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.

```ts filename="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

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

```ts
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.

```ts
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

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`.

> [!NOTE]
> **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

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.

```ts filename="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](https://rehearsal.dev/retest/docs/guides/signing-in.md) 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

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.

> [!NOTE]
> **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

- [Signing in and secrets](https://rehearsal.dev/retest/docs/guides/signing-in.md): Allow the destination used by a sign-in popup.
- [Uploads and downloads](https://rehearsal.dev/retest/docs/guides/files.md): Wait for a file picker or export opened by an action.
- [Test API](https://rehearsal.dev/retest/docs/reference/test-api.md): Look up dialog answers and popup methods.
