QA How-To
How to Handle alerts in Cypress (2026)
Learn cypress how to handle alerts, confirms, prompts, beforeunload, and print dialogs with current events, stubs, assertions, and reliable, safe examples.
23 min read | 2,728 words
TL;DR
For `alert()`, register `cy.on('window:alert', message => expect(message).to.eq(...))`; Cypress accepts it automatically. For `confirm()`, return `false` from `window:confirm` to test Cancel, or omit the return to accept. For `prompt()`, stub `win.prompt` in `cy.visit({ onBeforeLoad(win) { ... } })` and control its return value.
Key Takeaways
- Cypress automatically accepts native `alert()` dialogs, so listen to `window:alert` to assert their text.
- Cypress accepts `confirm()` by default, and returning `false` from `window:confirm` simulates Cancel.
- Stub `window.prompt` in `cy.visit()` `onBeforeLoad` so application code receives a controlled value or `null`.
- Native browser dialogs are not DOM elements, while application modals should be tested with normal Cypress queries.
- Register listeners or stubs before the action that opens the dialog and assert the resulting application state.
- Use test-scoped `cy.on()` by default and reserve global `Cypress.on()` listeners for deliberate suite-wide behavior.
- A dialog assertion is incomplete unless the test also proves the accept, cancel, or submitted-value branch.
The short answer to cypress how to handle alerts is to use Cypress window events for native alert() and confirm(), and stub window.prompt before application code loads. Cypress automatically accepts alerts and confirms, but a window:confirm listener can return false to simulate Cancel. These browser dialogs are not HTML elements, so cy.get() cannot locate them.
The word alert is overloaded. A native JavaScript dialog blocks the browser through window.alert, window.confirm, or window.prompt. A product alert, toast, modal, or ARIA alert is ordinary DOM. Correct classification determines the entire test. This guide covers both, with emphasis on native behavior, listener scope, branch assertions, and failure diagnosis.
TL;DR
| UI behavior | Cypress mechanism | Control available |
|---|---|---|
window.alert(message) |
cy.on('window:alert', handler) |
Assert message, Cypress accepts |
window.confirm(message) accept |
Default behavior or listener | Assert message, return true or nothing |
window.confirm(message) cancel |
cy.on('window:confirm', () => false) |
Simulate Cancel |
window.prompt(message) value |
Stub win.prompt before load |
Return a string |
window.prompt(message) cancel |
Stub returning null |
Simulate Cancel |
beforeunload |
window:before:unload event |
Assert handler behavior |
window.print() |
Stub win.print before load |
Prevent print UI and assert call |
| HTML modal or toast | Normal Cypress queries | Click controls and assert DOM |
Always register the event handler or stub before the user action. Then assert both the dialog contract and the application state produced by the selected branch.
1. Cypress How to Handle Alerts: Identify Native Dialogs Versus DOM UI
Open DevTools and inspect the behavior. A native browser dialog is created by a window method and is rendered outside the page DOM. It does not have CSS selectors, roles you can query through the application DOM, or a button that cy.contains('OK') can click. Cypress's automation layer handles it and emits events.
A DOM modal, toast, banner, or alert is application markup. It may use role='dialog', role='alertdialog', role='alert', or no accessible role at all. Test it like any other component: query the visible container, verify its content, click its Cancel or Confirm button, and assert focus and resulting state.
Classification prevents two common dead ends. First, cy.get('.alert') cannot find a native window.alert. Second, a window:alert listener will never fire for a React modal that only looks like a browser dialog.
Also distinguish browser permission prompts, HTTP basic authentication challenges, file pickers, and download prompts. They are not the JavaScript dialog events described here. Cypress may handle them through browser permissions, visit authentication, file selection APIs, or browser launch configuration. Do not call every piece of browser chrome an alert.
For each scenario, write down the branch under test: acknowledgement, acceptance, cancellation, text submission, navigation warning, or print initiation. The final application assertion should prove that branch.
2. Assert a Native window.alert() Message
Cypress automatically accepts native alerts. Register a window:alert handler before the action and assert the exact message or stable parts of it.
it('shows an alert after copying an invite link', () => {
cy.visit('/team/invitations')
cy.on('window:alert', (message) => {
expect(message).to.eq('Invite link copied')
})
cy.get('[data-cy=copy-invite-link]').click()
cy.get('[data-cy=copy-status]').should('have.text', 'Copied')
})
The event callback is synchronous. If the assertion throws, the test fails. Cypress accepts the alert and continues, so the status assertion proves the application reached the post-alert state.
Do not try to click the native OK button. There is no Cypress DOM selector for it. Do not return false from window:alert expecting dismissal control. Cypress always accepts alerts, and the event is for observation.
Exact equality is appropriate for a stable product message. If the alert includes a generated ID or count, assert structured stable parts and verify the dynamic value from controlled test data. Avoid vague include('success') checks when the same word appears in several messages.
Registering after the click is too late because alert() executes synchronously during the application's click handler. Cypress may have already accepted it before the listener exists. Listener first, trigger second is the reliable ordering for all dialog examples in this article.
3. Accept a Native window.confirm() Dialog
Cypress automatically accepts confirmations by default. Add a listener when the text itself is part of the contract, then assert the accepted outcome.
it('deletes a draft after confirmation', () => {
cy.intercept('DELETE', '/api/drafts/dr_42').as('deleteDraft')
cy.visit('/drafts/dr_42')
cy.on('window:confirm', (message) => {
expect(message).to.eq('Delete this draft permanently?')
return true
})
cy.get('[data-cy=delete-draft]').click()
cy.wait('@deleteDraft')
.its('response.statusCode')
.should('eq', 204)
cy.location('pathname').should('eq', '/drafts')
})
Returning true is explicit but optional for the accept case. If the handler returns nothing, Cypress still accepts. The intercept proves the destructive request happened, and the route assertion proves the user left the deleted resource.
Do not assert only that the confirm appeared. A frontend could show the correct text and then execute the wrong branch. For a destructive action, verify the operation method and resource ID, response, visible removal, or subsequent 404 according to the system contract.
Keep the listener close to the trigger. A broad listener in support code can accept confirmations throughout the suite and hide an unexpected dialog. Test-specific handlers make surprise dialogs visible and keep branch intent readable.
For network assertion patterns, review Cypress cy.intercept examples. A confirm controls whether the operation should start, while the intercept proves what actually crossed the network.
4. Cancel a Native window.confirm() Dialog
Return false from the event handler to simulate the user selecting Cancel. The strongest negative test proves no destructive request occurred and that application state stayed intact.
it('keeps a draft when deletion is canceled', () => {
cy.intercept('DELETE', '/api/drafts/dr_42').as('deleteDraft')
cy.visit('/drafts/dr_42')
cy.on('window:confirm', (message) => {
expect(message).to.eq('Delete this draft permanently?')
return false
})
cy.get('[data-cy=delete-draft]').click()
cy.get('@deleteDraft.all').should('have.length', 0)
cy.location('pathname').should('eq', '/drafts/dr_42')
cy.get('[data-cy=draft-title]').should('have.text', 'Release notes')
})
The alias collection gives a focused assertion that no matching request occurred. The route and title prove the page remains usable. If the application writes optimistically before opening the confirm, this test catches that defect.
Be careful with negative assertions that can pass too early. Here window.confirm() and the application's branch decision are synchronous inside the completed click, so checking the alias collection after the click is meaningful. If cancellation triggers asynchronous cleanup, also assert a positive stable state.
A Sinon stub is useful when call count matters:
const confirmStub = cy.stub().as('confirmDialog').returns(false)
cy.on('window:confirm', confirmStub)
cy.get('[data-cy=delete-draft]').click()
cy.get('@confirmDialog').should('have.been.calledOnceWith',
'Delete this draft permanently?',
)
Use a handler for simple branch control and a stub when arguments, call count, or ordering are important.
5. Handle window.prompt() With onBeforeLoad
A prompt returns user-entered text or null for Cancel. Stub the window method before application scripts can capture or call it. For end-to-end tests, cy.visit() provides onBeforeLoad.
it('renames a workspace from a prompt', () => {
cy.intercept('PATCH', '/api/workspaces/ws_42').as('renameWorkspace')
cy.visit('/workspaces/ws_42', {
onBeforeLoad(win) {
cy.stub(win, 'prompt')
.as('renamePrompt')
.returns('Payments QA')
},
})
cy.get('[data-cy=rename-workspace]').click()
cy.get('@renamePrompt').should(
'have.been.calledOnceWith',
'Enter a new workspace name:',
'Checkout QA',
)
cy.wait('@renameWorkspace').then(({ request, response }) => {
expect(request.body).to.deep.eq({ name: 'Payments QA' })
expect(response?.statusCode).to.eq(200)
})
cy.get('h1').should('have.text', 'Payments QA')
})
This controls the return value, asserts the prompt message and default input, verifies the request, and confirms the new heading. It does not attempt to type into browser chrome.
If the application obtains window.prompt only at click time, stubbing after visit can work, but onBeforeLoad is safer because it replaces the method before application code runs. It is essential when the app caches a reference during initialization.
For a component test, stub the relevant window before mounting the component. Stubs created with cy.stub() are automatically restored between tests, which prevents one prompt behavior from leaking into the next scenario. The Cypress stub and spy examples cover aliases and Sinon assertions in more depth.
6. Simulate Prompt Cancel, Empty Text, and Validation
Cancel and empty input are different. Native prompt Cancel returns null; submitting an empty field returns ''. The application may ignore both, or it may treat them differently. Write separate tests when the behavior matters.
it('does not rename when the prompt is canceled', () => {
cy.visit('/workspaces/ws_42', {
onBeforeLoad(win) {
cy.stub(win, 'prompt').as('renamePrompt').returns(null)
},
})
cy.get('[data-cy=rename-workspace]').click()
cy.get('@renamePrompt').should('have.been.calledOnce')
cy.get('h1').should('have.text', 'Checkout QA')
cy.get('[data-cy=rename-error]').should('not.exist')
})
For empty input:
cy.visit('/workspaces/ws_42', {
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('')
},
})
cy.get('[data-cy=rename-workspace]').click()
cy.get('[data-cy=rename-error]')
.should('have.text', 'Workspace name is required')
If the product trims whitespace, add one focused case for ' '. If it imposes length or character rules, validate those mainly at unit, component, and API layers, then keep one browser proof. Prompt behavior itself should not require dozens of end-to-end permutations.
Never return undefined unless the application contract specifically handles it. Real prompt outcomes are a string or null; an unrealistic stub can create a branch no user reaches. Use the same principle for default arguments: assert them only when they are meaningful product behavior.
7. Test beforeunload and Navigation Warnings Carefully
Applications sometimes register a beforeunload handler for unsaved changes. Modern browsers control the visible warning text, so the application cannot depend on a custom string appearing. Cypress exposes window:before:unload, which yields the event object.
it('marks navigation as guarded when edits are unsaved', () => {
cy.visit('/profile/edit')
cy.get('[data-cy=display-name]').clear().type('Changed name')
cy.on('window:before:unload', (event) => {
expect(event.returnValue).to.eq('')
})
cy.get('[data-cy=leave-editor]').click()
})
Adapt the navigation trigger to your application. A client-side router may show a custom DOM confirmation instead of using beforeunload, especially for same-page application routes. In that case, test the DOM modal. The native event is most relevant for full page navigation, reload, or closing behavior.
Do not assert custom browser warning text. Current browsers intentionally show generic language for security and consistency. Assert that the application marks the event according to its implementation and separately test any internal route blocker.
Also cover the clean state. After saving or reverting changes, navigation should not invoke the guard. A listener or spy can verify the absence or the application can expose a stable navigation outcome. Avoid global suppression of unload behavior, since it can make unsaved-change defects invisible.
8. Stub Print and Other Window Methods That Open Browser UI
window.print() opens browser-controlled UI that is unsuitable for normal automation. Stub the method before page load, click the print control, and assert the call plus any printable state.
it('prepares and prints an invoice', () => {
cy.visit('/invoices/inv_42', {
onBeforeLoad(win) {
cy.stub(win, 'print').as('printDialog')
},
})
cy.get('[data-cy=print-invoice]').click()
cy.get('[data-cy=invoice-print-view]').should('be.visible')
cy.get('@printDialog').should('have.been.calledOnce')
})
The stub prevents browser print UI and records the application contract. It does not prove printer drivers, page margins, or the final paper output. Use print CSS tests, visual review, or PDF generation tests for those risks.
This same boundary thinking applies to wrappers around external browser APIs. If the app calls a well-defined window method that would interrupt automation, stub the smallest method and assert arguments and surrounding state. Do not replace the entire window object or silence every browser call.
Permission prompts and file pickers require their own supported mechanisms. File upload uses selectFile, not a dialog event. HTTP basic authentication uses auth on cy.visit() or cy.request(). A precise test maps one browser feature to one Cypress boundary.
9. Scope Event Listeners and Prevent Cross-Test Leakage
Use cy.on() for event behavior tied to the current test or Cypress command chain context. Cypress cleans up listeners attached to cy between tests. Cypress.on() attaches globally and can affect every test in the spec or support bundle, so reserve it for deliberate suite-wide observation.
A global confirm handler that always returns false can silently cancel unrelated workflows. A global alert assertion can fail a test that legitimately shows a different message. Keep dialog handlers near the triggering command and express the expected branch in that test.
When a test can produce several alerts, a stub gives explicit call history:
it('shows both import warnings in order', () => {
const alertStub = cy.stub().as('alertDialog')
cy.on('window:alert', alertStub)
cy.visit('/imports')
cy.get('[data-cy=validate-import]').click()
cy.get('@alertDialog').should((stub) => {
expect(stub).to.have.callCount(2)
expect(stub.getCall(0).args[0]).to.eq('Two rows were skipped')
expect(stub.getCall(1).args[0]).to.eq('Validation complete')
})
})
The .should() callback is retryable, and all work inside it is synchronous and repeat-safe. It observes the stub without triggering more dialogs. If alert order is not a product requirement, assert the set of messages instead of accidental order.
Avoid mixing two handlers that make conflicting return decisions for the same confirm. Centralize the decision within the scenario so future maintainers can see why Accept or Cancel occurs.
10. Test Application Modals, Toasts, and ARIA Alerts as DOM
Most modern products prefer custom modals because they can style content, support richer actions, and integrate with routing. Test these controls through their user-facing DOM.
it('cancels deletion from an application modal', () => {
cy.visit('/projects/prj_42')
cy.get('[data-cy=delete-project]').click()
cy.get(`[role=alertdialog][aria-label='Delete project']`)
.should('be.visible')
.within(() => {
cy.contains('This action cannot be undone').should('be.visible')
cy.get('button').contains('Cancel').click()
})
cy.get('[role=alertdialog]').should('not.exist')
cy.get('h1').should('have.text', 'Apollo')
})
Unlike a native confirm, Cypress does not auto-accept this modal. You choose a real button, can assert focus, and should verify Escape, background interaction, accessible naming, and focus return according to the component contract. The Cypress data-cy selector guide helps balance accessible queries with durable test hooks.
For a toast using role='alert', assert its message and the underlying business outcome. Do not make a transient toast the only proof that a save succeeded. Alias the save request or reopen the record. If the toast disappears, use Cypress retryability around its intended visible phase and avoid arbitrary waits.
A product may use the word alert in selector names, logs, and requirements. The implementation decides the Cypress API. Native method call means event or stub. DOM markup means query and interaction.
11. Cypress How to Handle Alerts: Verify Both Dialog and Outcome
A complete dialog test has four parts: prepare deterministic state, register the handler, trigger the action, and verify the branch outcome. The handler must exist before the synchronous call. Its return value must model a real user decision. The final assertion must cover a state the dialog controls.
| Dialog branch | Dialog evidence | Outcome evidence |
|---|---|---|
| Alert acknowledged | Correct message emitted | Status or next step appears |
| Confirm accepted | Correct message, accepted | Destructive request and removal occur |
| Confirm canceled | Correct message, false |
No request, item remains |
| Prompt submitted | Correct message and default | Returned value is saved |
| Prompt canceled | Stub returns null |
No save and original value remains |
| Print initiated | print called once |
Printable state was prepared |
When a dialog test is flaky, inspect listener timing, window replacement after navigation, multiple calls, and whether the UI is native or DOM. Increasing a timeout rarely fixes a synchronous dialog contract. If the application navigates to a new window or origin before prompting, register the relevant behavior in the correct window lifecycle.
Keep detailed input validation below the end-to-end layer and a few representative branches in Cypress. For broader browser context changes, see Cypress cy.origin cross-domain examples, but do not use cy.origin() for a same-window native alert.
Review Checklist for Dialog Coverage
Before approving a dialog test, review event timing, listener scope, real browser return value, exact message ownership, and the post-decision state. Confirm that an unexpected second dialog would fail visibly. For destructive actions, prove the protected request is absent on Cancel and uniquely matched on Accept. For prompts, cover null separately from an empty string and validate the returned value at the API boundary. For DOM modals, add focus return and keyboard coverage in component tests. Finally, remove diagnostic global listeners and run the full spec so no handler leaks into another scenario. This checklist turns a passing dialog example into a maintainable regression guard.
Interview Questions and Answers
Q: How does Cypress handle JavaScript alerts by default?
Cypress automatically accepts window.alert() and emits the window:alert event. I register a handler before the trigger to assert the message, then verify the application state after acknowledgement. I do not try to locate the native OK button.
Q: How do you cancel a confirm dialog in Cypress?
I register cy.on('window:confirm', handler) before the action and return false. Then I assert the destructive request did not occur and the original item or page remains.
Q: How do you enter text into a prompt?
I stub win.prompt in cy.visit() onBeforeLoad and make the stub return the desired string. I assert its message and default argument, then verify the returned string reached the request and UI.
Q: What does prompt Cancel return?
It returns null, which is different from the empty string returned after submitting an empty prompt. I test them separately when application validation treats them differently.
Q: Why will cy.get() not find a native alert?
The browser renders native dialog UI outside the application's DOM. Cypress exposes window events and stubbing mechanisms for it. A custom React or HTML modal is DOM and should use normal queries instead.
Q: When would you use a stub instead of an event callback?
I use a stub when call count, arguments, order, or later assertions matter. A simple callback is enough to assert one message and return one confirm decision.
Q: What is risky about a global confirm handler?
It can accept or cancel confirmations in unrelated tests and hide unexpected dialogs. I prefer test-scoped cy.on() handlers near the trigger, with global listeners only for deliberate suite policy.
Common Mistakes
- Querying a native browser alert with
cy.get()orcy.contains(). - Expecting to click the native OK button.
- Registering the event listener after the action already called the dialog.
- Returning
falsefromwindow:alertand expecting to control acceptance. - Forgetting that confirm is accepted by default.
- Using the same test for prompt Cancel and empty-string submission.
- Stubbing
prompttoo late after application code cached the original method. - Asserting only the dialog message and not the resulting application branch.
- Applying
Cypress.on()globally for a one-test decision. - Suppressing an unexpected dialog instead of finding why it appeared.
- Treating a DOM modal or toast as a native JavaScript dialog.
- Claiming a stubbed print call proves printed layout or printer behavior.
Conclusion
For cypress how to handle alerts, match the Cypress mechanism to the browser contract. Observe alerts, control confirms through the listener return value, and stub prompts before application code loads. For HTML modals and toasts, use ordinary DOM queries and real controls.
Choose one current dialog workflow and write the two meaningful branches. Assert the message, the user decision, the network effect or lack of one, and the final visible state. That structure keeps native browser automation simple and catches the business defect that the dialog was designed to prevent.
Interview Questions and Answers
How do you handle a native alert in Cypress?
I register a `window:alert` listener before the triggering action and assert the message. Cypress accepts the alert automatically. I then assert the business state after acknowledgement.
How do you test both branches of a confirm dialog?
For Accept, I let Cypress accept or explicitly return true, then verify the operation occurred. For Cancel, I return false and verify no operation occurred and the original state remains. Both tests assert the confirm message.
Why must a prompt stub be installed before page load?
Application initialization can capture a reference to `window.prompt`, so replacing it later may not affect the call. `onBeforeLoad` gives the test the earliest supported hook. It also makes the return value deterministic before user interaction.
How do you distinguish a native dialog from an application modal?
A native dialog comes from a window method and is outside the DOM, so I use Cypress events or stubs. An application modal is HTML, often with dialog semantics, so I query it, use its buttons, and test focus and accessibility behavior.
When is `cy.stub()` useful for dialog testing?
It is useful when I need call count, exact arguments, return behavior, or call order. I alias the stub and use Sinon assertions after the action. Cypress restores its sandboxed stubs between tests.
How would you test an unsaved-changes warning?
I create a dirty form state and observe `window:before:unload` for true browser navigation, asserting the event contract. For same-page router navigation that uses a custom modal, I test that DOM component and both leave and stay branches.
What makes a dialog test complete?
It installs the handler before the trigger, asserts the dialog message and realistic decision, and proves the corresponding state change or non-change. A message-only assertion is incomplete because the application can execute the wrong branch afterward.
Frequently Asked Questions
Does Cypress automatically accept alerts?
Yes. Cypress automatically accepts native `window.alert()` dialogs. Use the `window:alert` event to assert the message and then verify the application's post-alert state.
How do I click OK on an alert in Cypress?
You do not click the native button. Cypress accepts it automatically because the dialog is outside the page DOM. Register an event handler if you need to assert its message.
How do I cancel a confirm popup in Cypress?
Register `cy.on('window:confirm', () => false)` before the action. Then assert that the canceled branch occurred, such as no delete request and the item remaining visible.
How do I handle a prompt in Cypress?
Stub `win.prompt` in the `onBeforeLoad` option of `cy.visit()` and return the desired string. Return `null` to simulate Cancel.
Can Cypress find a browser alert with a CSS selector?
No. Native JavaScript dialogs are browser UI outside the document. Custom HTML modals and toasts are DOM elements and can be queried normally.
What is the difference between `cy.on()` and `Cypress.on()` for alerts?
Use `cy.on()` for test-scoped behavior that Cypress cleans up between tests. `Cypress.on()` is global and can affect unrelated scenarios, so use it only for intentional suite-wide behavior.
How do I test a print dialog in Cypress?
Stub `win.print` before page load, trigger the print control, and assert the stub was called after printable state was prepared. This does not validate printer UI or final paper layout.