Cypress Alert Handling: From Zero to Hero! Complete Guide
Cypress Alert Handling: From Zero to Hero! Complete Guide
One of the most common mistakes in articles about Cypress is putting any popup, modal, or dialog into the same bucket and saying that "everything is handled the same way". That is not true. In Cypress there is an important difference between native browser dialogs—like alert, confirm, prompt or beforeunload—and the modals your application renders using HTML, CSS or React/Vue.
Cypress handles these cases in a diametrically opposed way, and understanding that difference is what separates an unstable (flaky) test from a robust and reliable one.
The official documentation makes it clear by separating events like window:alert, window:confirm, and detailing how to stub window.prompt.
The good news is that Cypress has an excellent design for these scenarios. You can listen to native events, dynamically control whether you accept or cancel a cypress confirm dialog, replace a prompt before the application loads, and visualize all your stubs in the Cypress console itself.
What types of dialogs exist in Cypress
When you look for information about "cypress alert handling", you must be clear about which of these 4 scenarios you are facing:
- Simple warnings:
window.alert() - Confirmations (OK/Cancel):
window.confirm() - Text inputs:
window.prompt() - Navigation warnings:
beforeunloadevent
Next, we will see how to quickly install Cypress and how to handle each of these cases with clean and structured code.
How to install Cypress and prepare the project
If you are starting a project from scratch, installing Cypress is as simple as running:
npm install cypress --save-dev
npx cypress open
A solid base structure in TypeScript (cypress.config.ts) would look like this:
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: 'cypress/support/e2e.ts',
},
})
How alert works in Cypress
With window.alert(), Cypress has a very clear default behavior: it automatically accepts the alert. You don't have to interact with it, nor look for a button in the DOM, nor click on "Ok".
However, in QA Automation we almost always want to validate (assert) the text that this alert contains to make sure the application shows the correct message.
Basic example with window:alert
To validate the text, we subscribe to the window:alert event:
describe('Handling Alert in Cypress', () => {
it('validates the text of a native alert', () => {
// 1. We configure the listener BEFORE triggering the action
cy.on('window:alert', (text) => {
expect(text).to.contains('Successfully saved')
})
// 2. We execute the action
cy.visit('/profile')
cy.get('[data-cy="btn-save"]').click()
})
})
How to handle confirm in Cypress
The case of window.confirm() (where the user can choose "OK" or "Cancel") is slightly different.
By default, Cypress also accepts it automatically. However, it gives you the flexibility to return false from the event to simulate a "Cancel".
Case 1: Accept the confirm (default behavior)
describe('Handling Confirm', () => {
it('accepts the confirmation by default', () => {
cy.on('window:confirm', (text) => {
expect(text).to.equal('Are you sure you want to delete this record?')
})
cy.visit('/records')
cy.get('[data-cy="btn-delete"]').click()
})
})
Case 2: Cancel the confirm in Cypress
If you want to simulate that the user rejects the action (clicks Cancel), you simply need to return false inside the callback.
describe('Cancel Confirm', () => {
it('cancels the confirm when returning false', () => {
cy.on('window:confirm', (text) => {
expect(text).to.equal('Are you sure you want to delete this record?')
// By returning false, we tell Cypress to cancel
return false
})
cy.visit('/records')
cy.get('[data-cy="btn-delete"]').click()
// We validate that the record still exists in the DOM
cy.get('[data-cy="records-list"]').should('contain', 'Record 1')
})
})
How to handle prompt in Cypress
Unlike alert and confirm, Cypress does not automate window.prompt() magically. If the application throws a prompt and you do nothing, the test will hang.
The official and correct way to handle this is to stub (replace the original call with a controlled spy) the window object using cy.stub().
Important! You must inject the stub before the application loads using the onBeforeLoad option of cy.visit().
Example of how to simulate a prompt
describe('Handling Prompt', () => {
it('injects a custom value into the prompt', () => {
cy.visit('/profile', {
onBeforeLoad(win) {
// We replace window.prompt and simulate the user typing 'Nestor'
cy.stub(win, 'prompt').returns('Nestor')
},
})
cy.get('[data-cy="btn-edit-name"]').click()
// (Optional) We can validate that the stub was called
cy.window().its('prompt').should('be.called')
})
})
How to validate navigation with beforeunload
Sometimes, web applications throw a native dialog if you try to leave the page without saving changes (using the beforeunload event).
Cypress can intercept this perfectly with the window:before:unload event.
describe('Unsaved changes warning', () => {
it('validates that the beforeunload protection is triggered', () => {
// 1. We add the listener to validate the unload event
cy.on('window:before:unload', (e) => {
expect(e.returnValue).to.exist
})
// 2. We fill a form but do NOT save
cy.visit('/article-editor')
cy.get('[data-cy="input-title"]').type('Unsaved draft')
// 3. We try to navigate to another page
cy.get('a[href="/dashboard"]').click()
})
})
Vital difference: cy.on() vs Cypress.on()
A very common mistake when looking for tutorials is confusing cy.on() with Cypress.on(). Although they look similar, they act on completely different levels:
cy.on('window:confirm', handler): Assigns the listener only for the currently running test (it). When the test finishes, the listeners are cleaned up automatically. This is a good practice because it maintains State Isolation.Cypress.on('window:confirm', handler): Assigns the listener globally. It will persist through all tests in that spec file, which can generate bugs that are extremely hard to track (flakiness).
Common mistakes when automating dialogs in Cypress
- Registering the listener TOO LATE: If you click() and in the next line you register the
cy.on('window:alert', ...), the test will fail because the event has already passed. Always register the event first. - Trying to find the "Ok" button in the DOM: Native browser popups do not belong to the DOM (HTML) structure of the web page. You will never be able to select them with
cy.get('.btn-ok'). - Confusing native dialogs with Bootstrap/Tailwind modals: If you see a modal appear on your screen but inspecting the source code shows it is a
<div>with a high z-index, it is not a native alert! It is simply HTML and you must test it like any other element on the page using cy.get() and an assertion on its visibility.
Conclusion
Mastering Cypress Alert Handling and understanding the difference between window:alert, window:confirm and how to do a good prompt stub is fundamental to up leveling as a QA Automation Engineer.
Ignoring the officially documented Cypress conventions and trying to force clicks where they do not belong usually results in unreliable test suites. If you apply these patterns in your day to day, you will have deterministic tests that are much faster to debug.