How to Check Whether an Element Exists in Cypress
Use Cypress’s retrying DOM queries to check that an element exists, disappears, or is visible—and avoid flaky conditional tests.
To assert that an element exists, query it with cy.get(selector). Cypress retries the query until it finds a match or reaches its timeout, so an extra .should('exist') is usually unnecessary. To wait for an element to disappear, use cy.get(selector).should('not.exist').
// The query itself asserts that a matching element exists
cy.get('[data-cy=notice]')
// Retry until the matching element is absent
cy.get('[data-cy=loading-spinner]').should('not.exist')
Choose the assertion that matches the requirement
| What the test needs to establish | Pattern | Meaning |
|---|---|---|
| Element exists | cy.get(selector) |
A matching element is present in the DOM. |
| Element is absent | cy.get(selector).should('not.exist') |
The matching element is absent; Cypress retries the assertion. |
| Element is visible | cy.get(selector).should('be.visible') |
The element satisfies Cypress’s visibility assertion. |
Existence and visibility are different. An element can be in the DOM but hidden. If the requirement is that a user can see it, assert visibility explicitly.
Write a reliable existence check
- Choose a stable selector. Prefer a dedicated test attribute such as
data-cywhen your application supports one. Cypress recommends these because they do not depend on styling or visible text that may change. - Query the element. Use
cy.get()for a CSS selector. The query retries until it finds a match or times out. - Assert the intended state. For presence, the query’s implicit existence assertion is enough. Add
.should('be.visible')only when visibility matters. - For disappearance, use a negative assertion. Chain
.should('not.exist')so Cypress retries while waiting for the element to leave the DOM.
describe('notice', () => {
it('shows the notice', () => {
cy.visit('/account')
cy.get('[data-cy=notice]').should('be.visible')
})
it('removes the loading spinner', () => {
cy.visit('/account')
cy.get('[data-cy=loading-spinner]').should('not.exist')
})
})
The example assumes the application has those routes and test attributes; replace them with the route and selectors from your app.
Configure waiting and query scope
cy.get() uses Cypress’s defaultCommandTimeout setting unless you pass a per-query timeout option. Increase the timeout only when the application legitimately needs more time; a long timeout can make failures slower without fixing an unstable test.
// Wait up to 10 seconds for this specific element
cy.get('[data-cy=report-ready]', { timeout: 10000 })
// Queries inside within() are scoped to the selected container
cy.get('[data-cy=dialog]').within(() => {
cy.get('[data-cy=confirm]').should('be.visible')
})
Outside a .within() scope, cy.get() searches the application document. It does not automatically search inside iframe documents; iframe content needs separate supported handling.
Wait for an element to appear and then disappear
A negative assertion can pass immediately when the element has not appeared yet. If the behavior under test is “show a saving message, then remove it,” first assert that it appears, then assert that it disappears.
cy.get('[data-cy=save-button]').click()
cy.get('[data-cy=saving-message]').should('be.visible')
cy.get('[data-cy=saving-message]').should('not.exist')
This sequence proves both states in order. Use the actual action and selectors for your application.
Understand retries: should() versus then()
Cypress retries queries and chained .should() assertions while they are waiting for the expected state. A .then() callback runs once after the preceding query yields; it is a one-time inspection, not a retrying assertion for a changing page.
// Retry until the element exists
cy.get('[data-cy=results]').should('exist')
// Runs once after the query yields; avoid using this to wait for a changing DOM
cy.get('[data-cy=results]').then(($results) => {
expect($results).to.have.length.greaterThan(0)
})
The explicit .should('exist') above is valid, though usually redundant after cy.get(). Prefer a retrying assertion for the condition the test actually needs.
Conditional checks and flaky control flow
A one-time check of the DOM is not a safe basis for branching if the page may still change asynchronously. Cypress warns that conditional DOM testing is reliable only when the state has settled and cannot change. Prefer making the application deterministic or branching on a stable source of truth, such as known test data or application state.
A failed cy.get() is not a normal false result that you can catch and use like a synchronous if. If the element is expected, let the query retry and fail clearly if it never appears. If either state is legitimately possible, establish that the page has settled before inspecting the DOM; otherwise the test can take the wrong branch depending on timing.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
cy.get() times out |
The selector is wrong, the element never renders, or it takes longer than the configured timeout. | Check the selector and application state first. Use a stable data-cy attribute, and adjust the command timeout only if the expected behavior really takes longer. |
| The test passes before a transient element appears | should('not.exist') succeeds while the element is still absent initially. |
Assert the intermediate appearance first, then assert not.exist. |
| The element exists but the visibility check fails | DOM presence does not imply visibility. | Check whether the application should reveal it; keep be.visible only when visibility is part of the requirement. |
| The query cannot find content inside an iframe | cy.get() does not descend into iframe documents. |
Use Cypress-supported iframe handling for the application and target the iframe document explicitly. |
| A conditional test behaves inconsistently | The DOM snapshot changes after the branch decision. | Wait for a guaranteed settled state or make the test scenario deterministic instead of branching on a transient DOM snapshot. |
A then() check fails while the page is loading |
The callback runs once and does not retry its assertion. | Use a retrying .should() assertion for asynchronous DOM state. |
Performance and reliability notes
- Use the narrowest stable selector that expresses the requirement. Stable test attributes avoid coupling the test to layout, styling, or copy.
- Rely on Cypress’s retry behavior instead of adding arbitrary fixed delays when the condition can be expressed as an assertion.
- Keep timeouts close to expected application behavior. A larger timeout is a waiting limit, not a fix for a selector or state problem.
- Assert the user-visible state only when that is the behavior under test; DOM existence is a different condition.
- Make conditional flows deterministic. A timing-dependent branch can make a test flaky and harder to diagnose.
Or skip the browser setup
If your goal is to inspect how a page renders, ScreenshotNeo can return a screenshot or PDF from one API request, without setting up a browser capture flow. Its API can remove cookie and consent banners, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response includes page-verdict and billing headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents.
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 request options. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card.
FAQ
Do I need to write .should('exist') after every cy.get()?
No. The query already has an implicit existence assertion. Add an assertion when it expresses another requirement, such as visibility or absence.
Does not.exist mean hidden?
No. It means there is no matching element in the DOM. A hidden element can still exist.
Can I use cy.get() to check content inside an iframe?
Not directly. Cypress documents that the query does not descend into iframe documents.
Can I branch on whether a selector matches?
Only when you know the page state has settled and cannot change. Otherwise, use deterministic application state or a test setup that guarantees the condition.


