Resource library

QA How-To

Cypress retry-ability: Examples

Use each cypress retry-ability example to test delayed DOM updates, network responses, rerenders, derived values, navigation, and loading states reliably.

26 min read | 2,771 words

TL;DR

A reliable Cypress retry-ability example places a query before a meaningful `.should()` assertion and keeps side effects outside the retry loop. For network-driven screens, wait on a narrowly aliased request and then let a fresh DOM query retry until the UI reflects the response.

Key Takeaways

  • Attach the expected condition to a query so Cypress can repeat the complete lookup against current DOM state.
  • Use should callbacks for repeatable calculations and then callbacks for one-time control flow.
  • Register intercepts before triggering requests, then assert both the response and eventual UI state.
  • Requery after clicks, selections, and submissions that can cause a framework rerender.
  • Anchor negative assertions to a known event so they do not pass before the workflow starts.
  • Freeze time and stub data when changing content would otherwise prevent a stable assertion.
  • Keep timeout overrides local and make every example fail for a meaningful unmet condition.

A useful Cypress retry-ability example must show more than cy.get('button').should('be.visible'). It should demonstrate which part Cypress repeats, where a side effect creates a retry boundary, and how the test proves the final state without sleeping.

The examples in this guide cover delayed rendering, React-style rerenders, computed values, network responses, navigation, disappearance, and debounced search. Every snippet uses public Cypress APIs available in 2026. Selectors and routes are explicit application contracts that you can replace with those from your product.

The examples are intentionally independent. You can copy one into a TypeScript spec, adapt the URLs and data-cy selectors, and preserve the synchronization pattern.

TL;DR

cy.intercept('POST', '/api/orders').as('createOrder')

cy.get('[data-cy=submit-order]').should('be.enabled').click()
cy.wait('@createOrder').its('response.statusCode').should('eq', 201)

cy.get('[data-cy=confirmation]', { timeout: 10000 })
  .should('be.visible')
  .and('contain.text', 'Order confirmed')
Scenario Retry target Avoid
Element appears later Query plus visible or content assertion Fixed delay
DOM rerenders after action Fresh query after the action Reusing captured element
Several derived values settle Read-only .should(callback) Assertions in .then()
API drives the page Aliased wait, then UI assertion Broad route alias
Loader disappears Positive start or request, then not.exist Immediate negative check
Debounced search Clock or request alias, then results assertion Real-time sleep

1. Set Up a Reproducible Example Project

Install Cypress in the application repository and let its setup wizard create the test folders. Keep the dependency locked so local and CI runs use the same release.

npm install --save-dev cypress
npx cypress open

A minimal TypeScript configuration can look like this:

import { defineConfig } from 'cypress'

export default defineConfig({
  defaultCommandTimeout: 6000,
  viewportWidth: 1280,
  viewportHeight: 720,
  e2e: {
    baseUrl: 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.ts',
  },
})

The examples assume that the application is available at the configured baseUrl. They also assume the product exposes data-cy attributes such as data-cy=orders. These attributes are not Cypress methods. They are an agreed selector contract in the application markup. Use roles and accessible names when they uniquely express user intent, or use dedicated attributes when localization and layout changes should not affect selection.

Do not begin by setting a very large defaultCommandTimeout. Six seconds above is illustrative, not a performance standard. A failing example should reveal a missing state promptly. A known long operation can receive a local override.

Run a spec in headless mode with:

npx cypress run --spec cypress/e2e/retry-examples.cy.ts

No retry plugin is needed. Query retry-ability and assertions are part of Cypress itself. Whole-test retries are separately configured and are not required for any pattern in this article.

2. Cypress Retry-ability Example for a Delayed Element

Suppose /notifications adds a success notification after an asynchronous job finishes. The correct test describes the state that must eventually become visible.

describe('notifications', () => {
  it('shows the completed import notification', () => {
    cy.visit('/notifications')
    cy.get('[data-cy=start-import]').click()

    cy.get('[data-cy=notification]', { timeout: 15000 })
      .should('be.visible')
      .and('contain.text', 'Import completed')
  })
})

While the notification is absent, cy.get() keeps querying. If an empty placeholder first matches but its text has not updated, the linked assertion keeps the query chain active. The test proceeds as soon as the visible matching notification contains the expected message.

Contrast that with a fixed wait:

// Weak synchronization
cy.get('[data-cy=start-import]').click()
cy.wait(5000)
cy.get('[data-cy=notification]').should('contain.text', 'Import completed')

Five seconds may waste time or may not be long enough. It also provides no evidence that import completion, rather than elapsed time, is the required boundary.

Use the local 15-second timeout only if the accepted import behavior can legitimately take that long in the test environment. If the product promise is five seconds, allowing 15 seconds could hide a performance defect. Functional and performance expectations should agree.

If several notifications can exist, narrow the query by content or scope it to the import region. A selector that accidentally passes on an old notification is a data isolation defect, not a retry problem. Clear or uniquely identify test-owned data before the action.

3. Cypress Retry-ability Example Across a Rerender

Modern UI frameworks often replace nodes after state changes. Cypress handles this well when the test ends an action chain and queries the new state again.

it('updates shipping methods when the country changes', () => {
  cy.visit('/checkout')

  cy.get('[data-cy=country]').select('Canada')

  cy.get('[data-cy=shipping-methods]')
    .find('input[type=radio]')
    .should('have.length', 2)

  cy.get('[data-cy=shipping-methods]')
    .contains('label', 'Express')
    .should('be.visible')
})

The country selection can cause the whole shipping component to rerender. The next get, find, and should form a fresh linked query chain. If the component initially contains zero radio inputs, Cypress repeats from get until there are two.

Avoid capturing the old container and carrying it through the action:

// Fragile when country selection replaces the checkout subtree
cy.get('[data-cy=checkout]').then(($checkout) => {
  cy.wrap($checkout).find('[data-cy=country]').select('Canada')
  cy.wrap($checkout).find('[data-cy=shipping-methods]').should('be.visible')
})

The .then() callback runs once, and $checkout refers to the resolved jQuery element. Rewrapping does not magically reacquire a replacement node. Fresh application-level selectors are clearer and give Cypress current DOM state.

Keep action chains short even when the component does not currently rerender. The product can change implementation later while preserving user behavior. A test organized around user actions and resulting states survives that refactor.

4. Use a should Callback for Values That Settle Together

A callback assertion is ideal when a single component shows several values that may update over multiple renders. The callback is retried, so every operation inside it must be synchronous and safe to repeat.

it('calculates an invoice total', () => {
  cy.visit('/invoice/preview')

  cy.get('[data-cy=invoice]').should(($invoice) => {
    const money = (selector: string) => {
      const text = $invoice.find(selector).text().trim()
      return Number(text.replace(/[$,]/g, ''))
    }

    const subtotal = money('[data-cy=subtotal]')
    const discount = money('[data-cy=discount]')
    const tax = money('[data-cy=tax]')
    const total = money('[data-cy=total]')

    expect(subtotal, 'subtotal').to.be.greaterThan(0)
    expect(discount, 'discount').to.be.at.least(0)
    expect(tax, 'tax').to.be.at.least(0)
    expect(total, 'subtotal minus discount plus tax')
      .to.eq(subtotal - discount + tax)
  })
})

If the subtotal appears first and tax appears later, one of the expectations throws. Cypress runs the get and callback again. The local money helper reads only from the current yielded invoice and does not retain state across invocations.

Do not add cy.log(), cy.get(), cy.request(), or any Cypress command inside the callback. Current Cypress versions reject commands in a .should() callback. Do not increment a persistent counter or write to a database either, since the callback may run an unknown number of times.

If parsing itself deserves reuse, move the pure parser to a tested utility. Keep selection and assertion in the spec so the retry boundary remains visible.

5. Compare should and then With the Same Scenario

The difference becomes obvious when an attribute is added after the element first renders. Imagine a user card appears immediately, then receives data-user-id after hydration.

This one-time callback can fail too early:

// The card can exist before data-user-id is populated
cy.get('[data-cy=user-card]').then(($card) => {
  expect($card.attr('data-user-id')).to.match(/^usr_[a-z0-9]+$/)
})

The get query can finish as soon as the card exists. .then() executes once, so the JavaScript expectation does not trigger another DOM lookup.

Use a retryable assertion instead:

cy.get('[data-cy=user-card]')
  .should('have.attr', 'data-user-id')
  .and('match', /^usr_[a-z0-9]+$/)

The have.attr chainer without an expected value changes the yielded subject to the attribute value. The following match assertion therefore receives a string. Cypress repeats the chain until the attribute exists and matches, or the timeout ends.

.then() is still valuable after the retryable precondition:

cy.get('[data-cy=user-card]')
  .should('have.attr', 'data-user-id')
  .then((userId) => {
    cy.request(`/api/users/${userId}`).its('status').should('eq', 200)
  })

Here, the attribute has already satisfied its retryable condition. The callback runs once to queue the request. The Cypress cy.then examples explain yielded subjects and return values in more detail.

6. Cypress Intercept and Retry Example

For an API-driven list, stub a deterministic response, wait for the matched request, and assert the rendered result. This tests the UI contract without relying on shared backend data.

it('renders active projects from the API', () => {
  cy.intercept(
    { method: 'GET', pathname: '/api/projects', query: { status: 'active' } },
    {
      statusCode: 200,
      delay: 300,
      body: [
        { id: 'prj_1', name: 'Payments', status: 'active' },
        { id: 'prj_2', name: 'Search', status: 'active' },
      ],
    },
  ).as('getActiveProjects')

  cy.visit('/projects?status=active')

  cy.wait('@getActiveProjects')
    .its('response.statusCode')
    .should('eq', 200)

  cy.get('[data-cy=project-row]').should('have.length', 2)
  cy.get('[data-cy=project-row]').eq(0).should('contain.text', 'Payments')
  cy.get('[data-cy=project-row]').eq(1).should('contain.text', 'Search')
})

The route matcher identifies both path and query. Registering it before visit prevents missing an early page-load request. The 300-millisecond delay is optional and exists only to exercise the loading transition in this controlled example. It is not a wait in the test.

After cy.wait, the page may still need another render. The row query and assertions retry independently until the UI catches up. The alias proves the known request occurred, while the row assertions prove the browser handled its response.

For an integration test against a real service, omit the static response but keep the alias and seed known data through an approved setup boundary. Read Cypress cy.intercept route examples for request mutation, fixtures, and error cases.

7. Actionability Example for a Covered Button

Cypress checks whether an action target can receive the action. Suppose a loading overlay covers the Save button until profile data finishes loading. The button exists from the first render, but it is not actionable yet.

it('saves an edited profile after loading completes', () => {
  cy.intercept('GET', '/api/profile').as('getProfile')
  cy.intercept('PUT', '/api/profile').as('updateProfile')

  cy.visit('/profile')
  cy.wait('@getProfile')

  cy.get('[data-cy=display-name]').clear().type('Asha Rao')
  cy.get('[data-cy=save-profile]').should('be.enabled').click()

  cy.wait('@updateProfile')
    .its('response.statusCode')
    .should('eq', 200)

  cy.get('[data-cy=save-status]').should('have.text', 'Saved')
})

Before clicking, Cypress resolves the button and applies actionability checks. The explicit be.enabled assertion documents one requirement. If an overlay still covers the enabled button, .click() also waits for coverage to clear within the command timeout. The action fires once.

Do not add { force: true } simply because the normal click reports coverage. A real user cannot click a covered control. Forced action can hide a z-index, loading, or modal defect and can trigger an impossible user flow. Use force only when the test intentionally interacts with behavior that is not user-actionability, and document why.

If the overlay never clears, inspect the profile request and application error state. Extending the timeout will not correct a failed initialization path.

8. Retry Navigation and URL State

Client-side navigation can update the URL after guards, data loading, and state transitions. Cypress location queries give the resulting URL parts a retryable contract.

it('redirects a signed-in user to the dashboard', () => {
  cy.visit('/sign-in')

  cy.get('[data-cy=email]').type('qa@example.test')
  cy.get('[data-cy=password]').type('correct-test-password', { log: false })
  cy.get('[data-cy=sign-in]').click()

  cy.location('pathname', { timeout: 10000 }).should('eq', '/dashboard')
  cy.location('search').should('eq', '')
  cy.get('h1').should('have.text', 'Dashboard')
})

cy.location('pathname') queries the current location property and retries its assertion. It is clearer than reading window.location once in .then() when navigation is still in progress. The heading assertion verifies page content separately, since the correct path alone does not prove successful rendering.

In a production suite, prefer authentication through an approved test API plus cy.session() for most specs, and retain a smaller number of true login UI tests. Never commit real passwords. This example uses a reserved test-domain address and a placeholder credential solely to show command flow.

For redirects that intentionally preserve query parameters, assert only the stable contract or use cy.location('search').should(...). Do not compare a whole URL if an irrelevant parameter order or environment host can vary.

9. Make Negative Assertions Wait for the Right Event

A negative assertion can pass before the workflow has actually begun. For example, a spinner does not exist before a request starts, so checking only not.exist says nothing about completion.

When the loading transition is part of the requirement, assert both phases:

it('shows and removes the report loader', () => {
  cy.intercept('POST', '/api/reports').as('createReport')
  cy.visit('/reports')

  cy.get('[data-cy=generate-report]').click()
  cy.get('[data-cy=report-loader]').should('be.visible')

  cy.wait('@createReport').its('response.statusCode').should('eq', 201)
  cy.get('[data-cy=report-loader]').should('not.exist')
  cy.get('[data-cy=report-link]').should('be.visible')
})

If the loader can legitimately render too quickly to observe, its appearance should not be a required assertion. Anchor completion to the request and positive report link instead:

cy.get('[data-cy=generate-report]').click()
cy.wait('@createReport')
cy.get('[data-cy=report-link]').should('have.attr', 'href').and('include', '/reports/')

Choose the version based on product behavior, not on a desire to exercise a selector. A test should encode a requirement.

Also distinguish not.exist from not.be.visible. The first requires no matched DOM node. The second permits a hidden node. Use the one that reflects the accessibility and interaction contract. A hidden dialog left focusable may be a defect even though a visibility assertion passes.

10. Test Debounced Search Without Sleeping

Debounced inputs are a common source of fixed waits. Control the browser clock and the network response instead. The following example assumes the application schedules search with a 500-millisecond setTimeout.

it('searches after the debounce interval', () => {
  cy.clock()
  cy.intercept(
    { method: 'GET', pathname: '/api/search', query: { q: 'cypress' } },
    { body: [{ id: 'doc_1', title: 'Cypress guide' }] },
  ).as('search')

  cy.visit('/search')
  cy.get('[data-cy=search-input]').type('cypress')

  cy.tick(499)
  cy.get('@search.all').should('have.length', 0)

  cy.tick(1)
  cy.wait('@search')
  cy.get('[data-cy=search-result]')
    .should('have.length', 1)
    .and('contain.text', 'Cypress guide')
})

cy.clock() controls timers in the application window, and cy.tick() advances them without real elapsed waiting. The alias collection proves no matching request happened at 499 milliseconds, then cy.wait('@search') observes the request after the final millisecond. The result assertion retries until rendering completes.

Confirm which timing APIs the product uses. Fake timers do not automatically control a server, an external iframe, or every browser scheduling primitive. If the debounce implementation is not under clock control, wait on the narrowly matched request rather than adding a fixed sleep.

Keep the debounce duration in one product constant when possible. If the value changes intentionally, both implementation tests and end-to-end expectations should be updated as a reviewed behavior change.

11. Use Fixtures and Stable Time for Repeatable State

Retry-ability handles eventual state, but it cannot make changing inputs stable. A screen that displays the current date, random records, or a live balance can reach a different valid state on every run. Control those inputs before asserting.

it('renders a deterministic activity summary', () => {
  const now = Date.UTC(2026, 6, 13, 9, 0, 0)
  cy.clock(now)

  cy.intercept('GET', '/api/activity', {
    fixture: 'activity.json',
  }).as('getActivity')

  cy.visit('/activity')
  cy.wait('@getActivity')

  cy.get('[data-cy=report-date]').should('have.text', 'July 13, 2026')
  cy.get('[data-cy=activity-row]').should('have.length', 3)
})

Place a valid JSON array in cypress/fixtures/activity.json that matches the application's response schema. The fixture controls records, and the clock controls browser-side date formatting. The test can now retry toward one known state.

A fixture does not validate the live API. Pair UI tests like this with schema, contract, or service tests. The Cypress fixture examples show loading and typing fixture data.

Prefer one stable response per scenario over a giant universal fixture. Small scenario-specific data makes the relationship between input and assertion obvious. Keep personally identifiable or production-derived data out of the repository.

12. Build a Complete Checkout Retry Example

The following spec combines the patterns into one realistic flow. It stubs deterministic catalog and order responses, performs single user actions, waits on operation-specific aliases, and requeries each resulting state.

describe('checkout retry boundaries', () => {
  beforeEach(() => {
    cy.intercept('GET', '/api/cart', {
      statusCode: 200,
      body: {
        items: [{ sku: 'BK-101', name: 'Testing Handbook', price: 40 }],
        subtotal: 40,
      },
    }).as('getCart')

    cy.intercept('POST', '/api/orders', {
      statusCode: 201,
      body: { id: 'ord_2026_0713', status: 'confirmed' },
    }).as('createOrder')
  })

  it('places an order and shows confirmation', () => {
    cy.visit('/checkout')
    cy.wait('@getCart')

    cy.get('[data-cy=cart-item]').should('have.length', 1)
    cy.get('[data-cy=cart-item]').should('contain.text', 'Testing Handbook')
    cy.get('[data-cy=subtotal]').should('have.text', '$40.00')

    cy.get('[data-cy=email]').type('buyer@example.test')
    cy.get('[data-cy=terms]').check()
    cy.get('[data-cy=place-order]').should('be.enabled').click()

    cy.wait('@createOrder').then(({ request, response }) => {
      expect(request.body).to.include({ email: 'buyer@example.test' })
      expect(response?.statusCode).to.eq(201)
      expect(response?.body).to.deep.include({
        id: 'ord_2026_0713',
        status: 'confirmed',
      })
    })

    cy.location('pathname').should('eq', '/orders/ord_2026_0713')
    cy.get('[data-cy=order-status]')
      .should('be.visible')
      .and('have.text', 'Confirmed')
  })
})

The .then() inspection is appropriate because the interception has resolved and request evidence is fixed. UI evolution remains in retryable query chains. This separation makes a failure precise: request shape, response, navigation, and rendered status have independent evidence.

13. Review and Debug Each Retry Example

When adapting an example, mark each boundary in a review:

  • Starting state: how are identity, data, feature flags, and route established?
  • Trigger: which command changes application state, and should it happen exactly once?
  • Async boundary: is the relevant signal DOM state, an intercepted request, navigation, or a controlled timer?
  • Retry chain: which queries need to rerun together?
  • Assertion: does it describe a business outcome rather than implementation trivia?
  • Timeout: does any local override reflect an accepted latency?
  • Evidence: will CI preserve enough detail to explain failure?

Use the Cypress Command Log to see the last yielded subject and assertion. In CI, retain screenshots and video according to team policy, and include server correlation IDs when possible. Do not debug by inserting sleeps between every command. That changes timing while obscuring the missing signal.

If a copied example fails, verify its assumptions. The selector, route, fixture schema, response status, and debounce duration belong to the sample application contract. Cypress cannot infer your product's names. Adapt those explicit pieces while preserving the query, action, and assertion boundaries.

Interview Questions and Answers

Q: Show a Cypress retry-ability example for delayed text.

Use cy.get('[data-cy=status]').should('have.text', 'Complete'). The query and assertion repeat until the text becomes exactly Complete or the timeout expires. No fixed wait is needed.

Q: Why start a new query after selecting a country?

The selection can rerender dependent controls and detach the old subtree. A fresh query obtains current DOM state and gives Cypress a clean retry chain. It also makes the action and result phases readable.

Q: When would you use .should(callback)?

I use it for multiple synchronous expectations or derived values from one evolving subject. Every operation inside must be repeatable and side-effect free because the callback can run many times. Cypress commands do not belong inside it.

Q: Why wait for an alias and then assert the UI?

The alias proves the expected network operation completed, but the framework can render later. The subsequent UI query proves the user-visible outcome. Together they localize failures to transport or presentation.

Q: How do you test disappearance without an early pass?

I anchor it to a positive event, such as observing the loader or waiting for the associated request. Then I assert not.exist or not.be.visible according to the requirement. I also assert the positive completed state.

Q: How do you test a debounce without cy.wait(500)?

If the debounce uses controllable timers, I call cy.clock(), type, and advance time with cy.tick(). I register a narrow intercept first and wait for that request. This verifies timing without consuming real time.

Q: Is an assertion inside .then() ever correct?

Yes, when the preceding value is already resolved and is not expected to change, such as a completed intercepted request. It is wrong when the assertion itself must cause the preceding DOM query to repeat.

Q: What would you inspect when an example times out only in CI?

I compare browser, viewport, data, server latency, feature flags, and application logs. I inspect the action and network evidence before the assertion. I change the timeout only if the condition is correct and its longer CI latency is accepted.

Common Mistakes

  • Copying selectors and routes without adapting them to the application's actual contracts.
  • Calling Cypress commands or changing external state inside .should(callback).
  • Expecting a JavaScript assertion in .then() to rerun the prior query.
  • Holding a jQuery element through a state-changing action and later rerender.
  • Registering cy.intercept() after visit or after the triggering click.
  • Asserting only the response and never checking that the UI rendered it.
  • Checking not.exist before the relevant operation has started.
  • Using force: true to bypass a real coverage or disabled-state defect.
  • Advancing a fake clock without confirming the product uses controlled timers.
  • Raising all timeouts because one environment or workflow is slow.
  • Using shared live data that allows an old row or notification to satisfy the assertion.

Conclusion

Each Cypress retry-ability example follows the same durable design: establish controlled state, perform a side effect once, identify the asynchronous boundary, and attach a meaningful assertion to a fresh query. Cypress can then repeat reads safely while the application settles.

Choose one flaky workflow and label its query, action, network, and assertion phases. Replace elapsed-time waits with an observable condition, requery after rerenders, and keep callback assertions idempotent. The resulting test will be faster when the app is fast and more informative when the expected state never arrives.

Interview Questions and Answers

Write a retryable assertion for an asynchronously populated table.

I would write `cy.get('[data-cy=table]').find('[data-cy=row]').should('have.length', expectedCount)`. If the length is wrong, Cypress repeats the linked `get`, `find`, and assertion. The selector should be scoped to test-owned data.

How do you handle a rerender after an action?

I end the chain at the action and start a fresh selector for the resulting state. This avoids using a detached subject and allows Cypress to query the replacement DOM. It also exposes the workflow boundary in the test.

Give a correct use of should callback.

A total component can be queried once, then its subtotal, tax, and total text can be parsed synchronously inside `.should(callback)`. I assert the arithmetic there. The callback performs no commands or mutations, so it is safe to retry.

Give a correct use of then after retrying.

I can first assert that an element has a user ID attribute, then use `.then(userId => cy.request(...))`. The attribute precondition is retryable, while the request is intentionally queued once after resolution.

How do you combine intercept with DOM retry-ability?

I register the intercept before the trigger, give it an operation-specific alias, and wait for the matching response. I then query the DOM and assert its eventual state. I do not assume network completion and rendering are simultaneous.

How would you test an element that disappears?

I first prove the relevant workflow began through a visible loading state or intercepted request. After completion, I assert the element does not exist or is not visible, depending on the contract. I also assert a positive final state to avoid a vacuous pass.

Why is force click usually the wrong retry fix?

It bypasses user actionability checks and can hide a real overlay, disabled control, or layout defect. I investigate why the target is not actionable and wait on the legitimate readiness condition. Force is reserved for intentionally non-user behavior.

What makes an example runnable rather than pseudocode?

It uses documented APIs, valid syntax, explicit application assumptions, and complete setup for routes, selectors, and data. The sample does not invent commands. A reader only needs to map those declared application contracts to the product.

Frequently Asked Questions

What is a simple Cypress retry-ability example?

Use `cy.get('[data-cy=status]').should('have.text', 'Complete')` for status text that appears asynchronously. Cypress reruns the query and assertion until it passes or reaches the timeout.

How do I retry a Cypress assertion until a value changes?

Attach `.should()` to a query that obtains the changing value. For derived conditions, use a side-effect-free `.should(callback)` and place synchronous Chai expectations inside it.

Can I put cy.wait inside a should callback?

No. Cypress commands are not allowed inside a `.should()` callback, and the callback may run repeatedly. Put waits and other commands before or after it.

How do I wait for an API response and UI update in Cypress?

Register `cy.intercept()` before the trigger, wait for its alias, assert the response when useful, then start a fresh DOM query with a UI assertion. The UI assertion handles rendering that occurs after the response.

How do I retry a URL assertion in Cypress?

Use a location query such as `cy.location('pathname').should('eq', '/dashboard')`. Cypress retries the location query and assertion while client-side navigation completes.

How can I test debounced search in Cypress without sleeping?

Use `cy.clock()` and `cy.tick()` when the debounce uses browser timers, and observe the search through a narrow intercept alias. Then assert the rendered results with a retryable query.

Why can should not exist pass too early?

The element can be absent before the operation starts, so the negative condition is already true. Anchor the assertion to a known request or positive loading event and also verify the completed state.

Related Guides