Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use a conditional in Cypress only after the value you are branching on is stable. For a state you expect, prefer a retryable .should() assertion. Use a one-time .then() DOM inspection only when the application cannot change during the decision; otherwise, control the state through test setup, a server response, cookies, local storage, or an explicit application signal.
The rule that makes Cypress conditionals reliable
A conditional test chooses one action when a condition is true and another when it is false. The JavaScript syntax is easy; deciding when to evaluate the condition is the difficult part.
A DOM-based branch is trustworthy only when the observed state has settled and cannot change while Cypress is making its decision. Client-rendered applications often continue updating after the page-load event. If your test checks the body too early, it can take the “element is absent” path and the application can render that element immediately afterward.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteMake the state knowable before branching. The most reliable sources are:
#1 Best Overall
- Test-controlled server state: seed the account, feature flag, or record before visiting the page.
- A stable response: wait for the request that determines what the page should render.
- Cookies or local storage: set the same state the application reads at startup.
- An application signal: expose a data attribute or status element that is written only after initialization is complete.
- A settled DOM: use a synchronous inspection only when you can guarantee that no asynchronous render can alter the decision.
This distinction matters more than the if statement itself. A retry or a longer timeout cannot turn an inherently changing decision into a deterministic one.
Use .should() when the expected state is known
If the test knows what should happen, assert that state instead of writing a conditional. Cypress retries a .should() assertion until it passes or reaches its timeout.
cy.get('[data-cy=welcome-modal]').should('be.visible')
The linked Cypress query and assertion are retried, so this handles a modal that appears after client-side rendering. It also gives a useful failure when the expected state never arrives.
Free tools Windows power users keep installed
One-click scans. No signup required.
Use a callback when several assertions describe the same expected state:
cy.get('[data-cy=account-panel]').should(($panel) => {
expect($panel).to.contain('Signed in')
expect($panel.find('[data-cy=logout]').length).to.eq(1)
})
Cypress may invoke that callback more than once. Keep it safe to repeat: make assertions only, and do not put Cypress commands or one-time side effects inside it. A callback that clicks, submits, writes a file, or mutates application state can run repeatedly and produce misleading results.
Branch on the DOM only when it is guaranteed to be stable
When either branch is legitimate and you truly cannot know the state in advance, inspect the body synchronously inside a one-shot .then() callback:
cy.get('body').then(($body) => {
if ($body.find('[data-cy=welcome-modal]').length) {
cy.get('[data-cy=welcome-modal]').should('be.visible')
} else {
cy.get('[data-cy=main-content]').should('be.visible')
}
})
$body.find() is a synchronous jQuery lookup against the subject Cypress has already yielded. It does not wait for a future render. The callback runs once, and Cypress commands queued inside the selected branch run afterward.
Rank #2
Use this pattern only if all of the following are true:
- The page has finished the render that can add or remove the element.
- No timer, request, animation, feature-flag evaluation, or subscription can change the result during the callback.
- Both branches are valid outcomes for this test.
- You have no more deterministic state source available.
If an element might appear later, this code can choose the wrong branch. Replacing .then() with .should() does not fix that: a retryable assertion is for proving an expected state, not for performing a one-time control-flow decision.
Prefer a deterministic state source for optional UI
Seed the server state
If a welcome modal depends on whether a user has completed onboarding, create a user in the desired state before visiting the page. Each test then has one expected path rather than discovering a random path from the rendered DOM.
beforeEach(() => {
cy.request('POST', '/test-support/users', {
email: '[email protected]',
onboardingComplete: true
})
cy.visit('/dashboard')
})
it('shows the dashboard for an onboarded user', () => {
cy.get('[data-cy=main-content]').should('be.visible')
})
The endpoint and payload in this example are application-specific. The important design is that the test creates the state it intends to verify.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Wait for the request that determines rendering
When the server response is the source of truth, synchronize with that response before asserting the resulting branch. Waiting for a request is more meaningful than waiting an arbitrary number of milliseconds.
cy.intercept('GET', '/api/feature-flags').as('flags')
cy.visit('/settings')
cy.wait('@flags')
cy.get('[data-cy=beta-settings]').should('be.visible')
Keep the final assertion retryable. The response can have arrived while the framework is still committing the corresponding DOM update.
Set cookies or local storage before the visit
For a preference or experiment assignment read during startup, establish it before loading the application. This avoids observing an intermediate default and then branching on it.
Rank #3
cy.setCookie('consent', 'accepted')
cy.visit('/')
cy.window().then((win) => {
win.localStorage.setItem('theme', 'dark')
})
cy.reload()
cy.get('body').should('have.class', 'theme-dark')
Use the mechanism your application actually reads. If initialization occurs only during the first page load, set local storage before cy.visit() by using a small setup page or a documented application hook rather than relying on a reload as a hidden dependency.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Expose an explicit readiness signal
A stable marker such as data-app-ready="true" gives the test a clear synchronization point. The application should set it only after the asynchronous work relevant to the test has completed.
cy.get('[data-cy=app-root]')
.should('have.attr', 'data-app-ready', 'true')
cy.get('[data-cy=welcome-modal]').then(($modal) => {
if ($modal.length) {
cy.get('[data-cy=welcome-modal]').should('be.visible')
} else {
cy.get('[data-cy=main-content]').should('be.visible')
}
})
The readiness assertion creates a meaningful boundary. The subsequent branch is still appropriate only if the application promises that the relevant elements will not change after readiness.
Re-query after a render replaces an element
Cypress retries linked queries from the beginning of their chain. An assertion in the middle of a chain can establish a retry boundary, however: later commands may operate on the subject that passed the earlier assertion. If a framework re-renders and replaces that element, the saved subject can become detached.
Split the chain and query the document again when a later render can replace the node:
cy.get('[data-cy=save-status]').should('contain', 'Saving')
// The save completes and the component is replaced.
cy.get('[data-cy=save-status]').should('contain', 'Saved')
cy.get('[data-cy=save-button]').should('not.be.disabled')
This is safer than holding a reference from the first query and asking it to represent a newly rendered element. Re-querying also makes the intended synchronization point visible to the next reader.
Handling common conditional-testing questions
“How do I do something different when an element may or may not exist?”
First ask why it may not exist. If the test controls the reason, create two explicit tests with separate setup. If the absence is a legitimate runtime outcome and the DOM is stable, use the one-shot body inspection shown above. Do not use a failing cy.get() as an existence probe: an ordinary cy.get() fails when its subject is not found before your JavaScript if can run.
Rank #4
“How do I test A/B experiments?”
Assign the variant in setup, then assert only that variant. A test that accepts whichever experiment it happens to receive can pass while testing neither experience completely.
cy.setCookie('experiment_checkout', 'new')
cy.visit('/checkout')
cy.get('[data-cy=new-checkout]').should('be.visible')
cy.get('[data-cy=classic-checkout]').should('not.exist')
Run a separate test with the assignment set to classic. If an external experimentation service chooses the assignment, wait for its stable response and assert the assignment before checking the UI.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
“Can I recover from a failed cy.get()?”
Not with a normal JavaScript try/catch. Cypress commands are queued and their failures are handled by Cypress. Use a stable state signal, a request you can wait for, or a one-shot body inspection after the page is known to be settled. If the element is required, let cy.get(...).should(...) fail with its diagnostic instead of hiding the failure behind conditional code.
“How do I branch on text?”
Prefer a semantic state value such as a status attribute, API response, or feature flag. If text is the only stable signal, inspect it once after readiness:
cy.get('[data-cy=payment-status]').should('have.attr', 'data-ready', 'true')
cy.get('[data-cy=payment-status]').then(($status) => {
const text = $status.text().trim()
if (text === 'Paid') {
cy.get('[data-cy=receipt]').should('be.visible')
} else if (text === 'Pending') {
cy.get('[data-cy=pending-message]').should('be.visible')
} else {
throw new Error(`Unexpected payment status: ${text}`)
}
})
The explicit unexpected-state failure prevents a new label from silently taking the wrong branch.
Test retries do not make an unstable branch deterministic
Cypress test retries are disabled by default unless you configure them. When enabled, a failed test can run again, which is useful for detecting or containing intermittent failures in a broader workflow. A retry does not repair a race in your conditional: each run can still observe the DOM at a different moment.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchimport { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
retries: {
runMode: 2,
openMode: 0
}
}
})
Use retries as an operational policy after fixing synchronization, not as a substitute for controlling state. Keep the number low enough that a real regression remains visible and investigate tests that pass only on a later attempt.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.A practical decision checklist
- Is the expected state known? Write a direct
.should()assertion. - Can setup determine the state? Seed the server, set a cookie, configure local storage, or assign the experiment before visiting.
- Does a request determine the state? Wait for that request, then assert the rendered result.
- Can the app expose readiness? Add or use a stable readiness marker and wait for it.
- Are both outcomes genuinely valid and the DOM guaranteed stable? Use a one-shot
.then()inspection. - Could a component be replaced? Split the chain and query again after the render boundary.
- Would a new state be silently accepted? Add an explicit unexpected-state failure.
Troubleshooting conditional Cypress tests
| Symptom | Likely cause | Fix |
|---|---|---|
| The false branch runs, but the element appears moments later. | The DOM was inspected before client rendering settled. | Wait for a request or readiness marker, or control the state in setup. Use the body-inspection pattern only after stability is guaranteed. |
A .should(callback) click happens more than once. |
The callback is retried and contains a side effect. | Keep the callback assertion-only; perform the one-time action in a separate command after the assertion. |
| A later command reports a detached subject. | A re-render replaced the element after an earlier assertion. | End the first chain and query the element again from the document. |
cy.get() fails before the conditional executes. |
A missing element is being used as an existence test. | Use deterministic setup, a stable readiness signal, or a settled body inspection instead. |
| The test passes locally but changes result in CI. | Timing differs, exposing an unstable branch or uncontrolled experiment. | Control the server state, wait on the determining request, and assert the assigned variant. |
| Increasing the timeout only makes the suite slower. | The test is waiting for a state it never proves, or the branch itself is racy. | Replace arbitrary delays with a specific request, attribute, or expected assertion; then use a timeout appropriate to that operation. |
| Retries hide a real failure. | The test succeeds only on a later run. | Keep retries modest, record the retry behavior, and remove the underlying race before treating the test as reliable. |
Or skip the browser setup
If your goal is to capture a stable result rather than drive a conditional browser flow, ScreenshotNeo can return a screenshot or PDF with one HTTP request. It accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
Use the API documentation at https://screenshotneo.com/docs/ for the full parameter list. A direct call looks like this:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
And in Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
})
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`)
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`)
const image = Buffer.from(await res.arrayBuffer())
ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Its free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Every plan includes the available features, including full-page and element capture, custom waits, device and viewport settings, cookies and headers, blocking rules, caching, signed links, asynchronous jobs, bulk capture, and PDF options.
Sign up for the free ScreenshotNeo plan to try the 1,000 monthly screenshots without adding a card.
Frequently Asked Questions
Should a conditional Cypress test have one or two test cases?
If both outcomes are important, create separate tests with explicit setup whenever possible. A single branch is best reserved for a legitimate runtime choice that cannot be predetermined.
What is the safest way to verify that a branch was selected?
Assert a distinctive result in each branch and fail explicitly for any unexpected state, rather than allowing an unmatched value to fall through.
When should I remove a conditional entirely?
Remove it when the test owns the state. Deterministic setup plus a direct assertion usually gives clearer failures and less timing sensitivity than accepting either DOM outcome.
Quick Recap
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.

