ScreenshotNeo

BlogHow-to

How to Wait for Elements to Be Clickable in Cypress

Cypress waits for click actionability automatically. Learn when to add assertions or timeouts, how to wait for network requests, and how to diagnose failed clicks.

By the ScreenshotNeo team4 October 20267 min read

In most Cypress tests, you do not need a separate wait before clicking. Query the element and call .click(); Cypress retries the query while it checks whether the element is actionable, then clicks once when it is ready:

cy.get('[data-cy="submit"]').click()

If the element never becomes actionable before the command timeout, Cypress fails the test. Add a meaningful assertion when the test needs to verify a state such as enabled, or give a genuinely slow element a local timeout. Avoid fixed sleeps such as cy.wait(3000) as a guess about when the page will be ready. Cypress retry-ability · click command.

1. What “clickable” means in Cypress

“Clickable” is shorthand for passing Cypress’s actionability checks; it is not a separate Cypress assertion. Before a normal click, Cypress checks conditions that include whether the element is visible, enabled, attached to the DOM, not readonly, not animating, and not covered by another element. Cypress also scrolls it into view when needed. A visible button can still be covered by a loading overlay, for example, and therefore not yet actionable. See Interacting with elements.

Cypress retries the linked query and checks while waiting. Once the target passes actionability checks, Cypress attempts the click once. It does not repeat the click action if the click itself changes the page or fails. If the target does not become actionable before timeout, the command fails.

2. The standard pattern: query and click

Use a stable selector and finish the chain with the action:

cy.get('[data-cy="submit"]').click()

This is generally all that is needed to wait for an element to become clickable. Prefer stable test attributes such as data-cy where the application provides them. Cypress retries the query chain as it waits, so this pattern handles an element that appears after rendering or becomes actionable after a transient animation or overlay.

Keep the click at the end of the chain. If clicking can rerender or remove the target, start a fresh query for the post-click check:

cy.get('[data-cy="open-modal"]').click()
cy.get('[data-cy="modal"]').should('be.visible')

Further commands that rely on the original subject after a click can be unsafe if the application replaces that element. A fresh query obtains the current DOM subject.

3. Add assertions for conditions the test should verify

Add an assertion when it expresses a requirement of the scenario, rather than solely to make Cypress wait. For example, if the submit control is expected to become enabled before submission:

cy.get('[data-cy="submit"]')
  .should('be.enabled')
  .click()

Assertions retry until they pass or time out. A standalone .should('be.visible') is often redundant as a pre-click wait: the click has its own actionability checks. Visibility alone also does not prove the element is uncovered. Use the action command’s checks for actionability and assertions for behavior the test actually intends to verify. See cy.should().

4. Give a genuinely slow element a local timeout

Cypress documents a default defaultCommandTimeout of 4 seconds for retrying commands. When a particular element legitimately takes longer, pass a timeout to that query:

cy.get('[data-cy="submit"]', { timeout: 10000 }).click()

The click command also has a timeout for resolving, including the wait for actionability. A local query timeout is often the clearest way to extend the retry window for a slow target. Choose a value based on expected application behavior; increasing timeouts cannot fix an element that remains disabled, covered, detached, or absent. Cypress recommends local overrides for exceptional slow steps rather than raising the timeout globally for every command. See Retry-ability and cy.click().

5. Wait for a network request when that is the condition

Waiting for an element to be actionable and waiting for a request to finish are different synchronization needs. If clicking Save should send a request and the next step depends on its response, register an intercept before the click and wait for its alias:

cy.intercept('POST', '/api/todos').as('createTodo')
cy.get('[data-cy="save"]').click()
cy.wait('@createTodo')
  .its('response.statusCode')
  .should('eq', 201)

Registering the intercept first ensures Cypress can observe the request. The alias wait synchronizes on request completion; it does not wait for a DOM element to become clickable. If the next condition is rendered UI, assert that condition too:

cy.get('[data-cy="todo-list"]').should('contain', 'New todo')

Cypress notes that an assertion chained directly to cy.wait('@alias') runs once against the yielded interception. Use the appropriate retryable query chain for DOM state. See cy.wait().

6. Why fixed waits and forced clicks are poor readiness fixes

Avoid guessed pauses

cy.wait(3000) observes neither element readiness nor a particular request. It wastes time when the page is ready sooner and may still be too short when the page is slower. Prefer an actionable query, an assertion for the state under test, or a request alias when request completion is what matters. Cypress covers this in Optimizing test performance.

Do not use force to make a wait

cy.get('[data-cy="submit"]').click({ force: true })

force: true bypasses normal actionability checks and their waiting behavior. It is not a wait-until-clickable option. Use it only when bypassing those checks is deliberate and the test is specifically meant to exercise that behavior. Otherwise, fix the actual blocker, such as an overlay or disabled state. See the click options.

7. Avoid stale subjects and non-retrying callbacks

Queries and assertions can retry, but .then() callbacks do not. Avoid capturing a DOM element in .then() and assuming that the captured node will remain current while the app rerenders. Prefer linked queries and retryable assertions for changing conditions:

// Prefer a query and assertion that can retry.
cy.get('[data-cy="submit"]').should('be.enabled').click()

// After an action that may rerender, query the new state.
cy.get('[data-cy="save"]').click()
cy.get('[data-cy="saved-message"]').should('be.visible')

Keep actions at the end of their chain and begin a new query after actions that can change the DOM. See Retry-ability.

8. Troubleshooting failed clicks

Symptom Likely cause What to do
Timed out finding the element The selector does not match, the element is not rendered, or it appears after the timeout. Check the selector and application state. If the element is legitimately slow, use a local timeout on cy.get().
Element is disabled The application has not enabled the control, or it is intentionally disabled. Assert the expected enabled state and investigate why it does not occur. Extend the timeout only if the transition is expected to take longer.
Element is covered An overlay, modal, sticky element, or another page element is intercepting the click. Wait for the actual overlay to disappear or for the intended UI state. Do not use force: true to hide an unintended obstruction.
Element is detached from the DOM A framework rerender replaced the node between finding it and acting. Use a fresh query chain and avoid retaining the old node in a .then() callback.
Element is animating or moving A transition or layout shift is still in progress. Let the normal actionability wait finish, or assert the meaningful completed state. Check whether animations or layout shifts are unexpected in the app.
Click passes but expected UI is missing The click succeeded but application work or rendering has not completed, or the click did not trigger the expected path. Wait for the relevant request alias if network completion matters, then assert the resulting DOM state.
Timeout increases do not help The element never meets the required condition, or the test is synchronizing on the wrong event. Inspect the failing actionability reason and test the condition that should enable or reveal the control.

9. Performance and reliability checklist

  • Use Cypress’s built-in actionability wait for the click instead of adding a fixed delay.
  • Wait on a specific request alias only when the test needs that request to complete.
  • Assert the resulting user-visible state after actions that trigger asynchronous work.
  • Use a per-command timeout for an exceptional slow element; avoid globally slowing every retrying command to accommodate one step.
  • Keep selectors stable and re-query after actions that can rerender the page.
  • Do not use forced clicks unless bypassing actionability is part of the behavior being tested.

These choices make tests respond to the actual state under test and avoid time spent sleeping after the page is already ready. A timeout is a maximum wait, not a reason to wait that entire duration when the condition passes sooner.

10. Or skip the browser setup

If your task is to capture a page screenshot while investigating a UI state, you can use ScreenshotNeo, a website screenshot API and MCP server for developers. This does not replace Cypress interaction tests; it is useful when you need a rendered page image without setting up browser capture code. See the ScreenshotNeo API documentation.

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

The API also accepts options for full-page or element capture, viewport and device presets, wait conditions, custom CSS or JavaScript, headers and cookies, output format, and caching. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.

11. FAQ

Does Cypress have a “wait until clickable” command?

The usual pattern is to query the element and call .click(). The action waits for Cypress’s actionability checks; no separate clickable command is needed.

Does .click() keep clicking until it works?

No. Cypress waits for actionability, then attempts the click once. It retries the linked query and checks, not the side-effecting click itself.

Is .should('be.visible') enough to prove a click will work?

No. Visibility is only one part of actionability; for example, another element may still cover the target. The click performs its own checks.

When should I use cy.wait('@alias')?

Use it when the test needs a specific intercepted request to finish. Use a query, click, or DOM assertion for element readiness and rendered UI state.

What is the default command timeout?

Cypress documents a 4-second default defaultCommandTimeout for retrying commands. A local override can extend an exceptional slow step.