Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

For an application-rendered modal, use Cypress’s ordinary DOM queries: trigger the UI, find the dialog by a stable selector or accessible name, assert it is visible, interact with its controls, then assert the resulting state. Browser-native alert(), confirm(), and prompt() dialogs use different handling: Cypress automatically accepts alerts and confirmations by default, while prompts are best handled by stubbing window.prompt before the application loads.

First identify which kind of dialog you are testing

“Modal” can refer to two different things in a Cypress test. An application-rendered modal is part of the page’s DOM—often a dialog element, a panel, or a component layered over the page. Query and interact with it as you would with any other page content. A browser-native JavaScript dialog is created by alert(), confirm(), or prompt(); it is not a normal DOM element that you can find with cy.get().

This distinction determines the test strategy. For a rendered modal, assert its visible state and test its controls and effects. For a native dialog, register the relevant browser event handler or stub the browser method before the app invokes it.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test an application-rendered modal with normal Cypress commands

Trigger the action that opens the modal, query it using a stable selector or accessible name, and assert that it is visible before interacting with it. After clicking a button, assert the application outcome—not just that the click command ran.

it('opens and closes the settings dialog', () => {
  cy.get('[data-cy="open-settings"]').click()

  cy.get('[role="dialog"]')
    .should('be.visible')
    .within(() => {
      cy.contains('h2', 'Settings').should('be.visible')
      cy.get('[data-cy="close-dialog"]').click()
    })

  cy.get('[role="dialog"]').should('not.exist')
})

This example assumes the application exposes the selectors and dialog markup shown. Use selectors that reflect your own app. A data-* test selector is usually less brittle than a selector coupled to layout or styling; an accessible dialog name can also make the test clearer when the markup provides one.

Assert the state that matters

A modal can remain in the DOM while hidden, or it can be removed when closed. Match the final assertion to the implementation: use should('not.exist') if it is removed, or assert that it is no longer visible if it remains hidden. For confirmation flows, assert the result of confirming or cancelling—such as a changed status, a closed dialog, or a record remaining present—rather than relying only on a click.

Cypress retries queries and assertions while waiting for the expected state. Prefer an assertion tied to meaningful UI readiness over a fixed delay. This makes the test wait for the condition it needs rather than assuming a particular rendering time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle native JavaScript alerts and confirmations

Inspect an alert message

Cypress automatically accepts browser alert() dialogs. You cannot change that automatic acceptance behavior, but you can observe the message with the window:alert event. Register the listener before the action that opens the alert.

it('shows the expected alert', () => {
  cy.on('window:alert', (message) => {
    expect(message).to.eq('Changes saved')
  })

  cy.get('[data-cy="save"]').click()
  cy.get('[data-cy="saved-state"]').should('be.visible')
})

The event handler runs when the app calls alert(); Cypress accepts the dialog automatically. The final DOM assertion checks the application outcome after that event.

Accept or dismiss a confirmation

Cypress automatically accepts confirm() unless a window:confirm handler returns false. Return false to exercise the dismissed branch. The following example checks the message synchronously, dismisses the confirmation, and verifies that the deletion did not take place.

it('dismisses a confirm dialog', () => {
  cy.on('window:confirm', (message) => {
    expect(message).to.eq('Are you sure?')
    return false
  })

  cy.get('[data-cy="delete"]').click()
  cy.get('[data-cy="deleted-state"]').should('not.exist')
})

To test acceptance, register a handler that inspects the message and does not return false, then assert the confirmed application state. In both branches, register the handler before clicking the control or performing another action that invokes confirm().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep event handlers outside the Cypress command queue

Cypress event callbacks such as cy.on('window:confirm', ...) run outside the normal Cypress command queue. Do not put cy.* commands, queued Cypress assertions, or cy.task() calls inside the callback. Use synchronous JavaScript assertions there, or record information with a stub and assert it after the triggering Cypress command finishes.

This boundary matters because Cypress commands are scheduled and run through its command queue, while the event callback is invoked as a browser event occurs. Keeping commands out of the callback avoids mixing those execution models. Put follow-up UI checks after the click, as in the examples above.

Stub prompt() before the application loads

A native prompt() is a browser method, not an input field in the page DOM. Stub window.prompt in the onBeforeLoad callback passed to cy.visit(), so the stub exists before application code can call it.

it('uses the supplied prompt value', () => {
  cy.visit('/', {
    onBeforeLoad(win) {
      cy.stub(win, 'prompt').returns('Ada Lovelace')
    },
  })

  cy.get('[data-cy="ask-name"]').click()
  cy.get('[data-cy="greeting"]').should('contain', 'Ada Lovelace')
})

Change the route, control selector, and expected UI to match the application. Installing the stub on load is the important part: stubbing after the click is too late if the app has already called prompt().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reach a modal inside an iframe

Same-origin iframe

Cypress can interact with content in a same-origin iframe. Access the frame’s document body, wait until it is non-empty, then wrap that body as a Cypress subject before continuing to query inside it.

cy.get('iframe#checkout')
  .its('0.contentDocument.body')
  .should('not.be.empty')
  .then(cy.wrap)
  .find('[role="dialog"]')
  .should('be.visible')
  .contains('button', 'Close')
  .click()

The non-empty assertion lets Cypress retry while the iframe content is still rendering. cy.wrap() is needed to continue with Cypress queries and assertions against the body obtained from the frame.

Cross-origin embedded iframe

A cross-origin embedded frame is restricted by the browser’s same-origin policy. cy.origin() supports working with top-level cross-origin navigation; it does not enter a cross-origin iframe embedded in another page. Do not treat it as a general iframe workaround.

Cypress documents chromeWebSecurity: false as a possible workaround in Chromium-family browsers, but it is environment-specific and has Firefox and WebKit limitations. That setting is not a portable solution for testing an embedded cross-origin modal. If the test depends on the workaround, account for the browser and security behavior in the environments where the suite runs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Understand “covered” and “not visible” failures

An element can exist in the DOM and still be unavailable for interaction. Cypress’s visibility and actionability checks account for whether another element covers the target. A modal overlay may cover a page button, for example; Cypress can reject a click on that covered button because a real user could not reach it through the overlay.

  • If the test intends to use the modal, query the modal’s own control and assert that the dialog is visible before clicking it.
  • If the test intends to use a control behind the modal, first follow the real user path to dismiss or otherwise resolve the overlay.
  • If a target unexpectedly appears covered, inspect the overlay and stacking state rather than assuming that DOM presence means the target is interactable.

A covered-element error is therefore useful evidence about the rendered interface, not merely a selector problem. Fix the test’s interaction path or the application’s overlay behavior; do not make the test click through an obstruction that a user would encounter.

Use cy.prompt() only when its limits fit

The current Cypress cy.prompt() reference includes natural-language steps such as “dismiss the modal.” It can be a convenient layer for supported tests, but its documented limits are material: it is for E2E tests, supports Chromium-based browsers, does not support iframes, and has other unsupported command areas.

For browser-native dialogs, explicit event handlers and a prompt stub make the expected behavior and branch control visible in the test itself. For a DOM modal, ordinary selectors and assertions show exactly which element the test opens and what result it expects. Prefer cy.prompt() only when the test type, browser, and lack of iframe interaction fit its supported scope.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

A reliable modal-test checklist

  • Classify the target as a DOM-rendered modal or a native JavaScript dialog before choosing commands.
  • Register window:alert or window:confirm handlers before triggering the action.
  • Return false from window:confirm only when testing dismissal.
  • Stub window.prompt in onBeforeLoad, before the app can call it.
  • Use stable selectors or accessible dialog names, and assert that the modal is visible before interacting.
  • Keep Cypress commands out of event callbacks; put post-action checks in the normal command chain.
  • For a same-origin iframe, wait for a non-empty body and wrap it before querying.
  • For a cross-origin iframe, do not assume that cy.origin() can access embedded content.
  • Use state assertions to synchronize with rendering instead of adding a fixed sleep.
  • Treat a covered target as a potential overlay or stacking issue and follow a user-reachable path.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server, not a replacement for Cypress assertions or native-dialog handling. It is useful when you need a screenshot or PDF of a page without setting up a browser capture pipeline. One GET request returns an image or PDF; here is the cURL example from its API usage:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for the request options. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with response headers indicating the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. For Cypress modal behavior, keep using Cypress: a screenshot service does not open, accept, dismiss, or assert a dialog.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

Frequently Asked Questions

Can Cypress find a native alert with cy.get()?

No. Native browser alerts are not DOM elements. Cypress accepts them automatically and exposes their messages through the window:alert event.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does cy.origin() let Cypress query a cross-origin iframe?

No. It addresses top-level cross-origin navigation, not a cross-origin iframe embedded in a page.

Can ScreenshotNeo test whether a Cypress modal opens?

No. ScreenshotNeo captures websites; it does not drive Cypress tests or operate native JavaScript dialogs.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.