skip to content

Pre-Action Readiness

Before every action a fixed list of checks is re-run until it passes or the timeout ends, and force is the escape hatch that turns eight of those checks off at once.

on this pageshow

explore

questions

5

In Cypress, what does `.click()` check about an element before it fires the click?

level: juniorimportance: must knowfreq 76%

answer

  1. The action waits for the element
  2. A fixed list, re-run until it passes
  3. Scroll, hidden, disabled, detached, readonly
  4. Bounded by defaultCommandTimeout, not a sleep
  5. The error names the check that failed

basics

~20 s

Cypress re-runs a fixed readiness list before every action: it scrolls the element into view, then requires it to be attached, visible, enabled, not readonly, not animating and not covered at the point the event will land.

solid answer

~40 s

`.click()` does not fire until the element passes Cypress's actionability checks, and Cypress re-runs that whole list — re-querying the subject each time — until it passes or `defaultCommandTimeout` (`4000` ms) runs out. Cypress scrolls the element into view using `scrollBehavior`, then checks it is not hidden, not `disabled`, not detached from the document, not readonly (only `.type()` and `.clear()` ask for that), not animating, and not covered at the point the event will land — nudging the page when a `position: fixed` or `sticky` element is in the way. Only then does it compute the coordinates, the element's centre unless you pass `position`, and fire the events. A failure names the check that never passed, so the error itself tells you what to fix.

code

javascript · 7 lines
javascript
cy.visit('/catalogue?q=dune')

cy.contains('[data-cy=book-row]', 'Dune')
  .find('[data-cy=borrow]')
  .click({ timeout: 10000, scrollBehavior: 'center' })

cy.get('[data-cy=loan-count]').should('have.text', '1')

go deeper

for a junior

Be ready to name the list without prompting: scroll into view, then not hidden, disabled, detached, readonly, animating or covered, then fire at the coordinates.

for a middle

Explain that the whole list is re-run on every retry against a freshly queried subject, and that the budget is defaultCommandTimeout rather than a fixed pause.

for a senior

Show that you read the specific failure text rather than adding force, and that you know which check produced which message.

for a principal

An interviewer at this level wants the policy: which of these checks a suite is allowed to switch off, where that decision is recorded, and how it is reviewed.

## Why an action command waits at all Cypress runs your spec inside the browser, in the same event loop as the application, and an **action command** simulates the events a real user's interaction would produce. Firing those events at an element a user could not reach would prove nothing, so before `.click()`, `.dblclick()`, `.rightclick()`, `.type()`, `.clear()`, `.check()`, `.uncheck()`, `.select()`, `.trigger()` or `.selectFile()` does anything, Cypress asks whether the element is in a state that would accept the interaction. That question is answered by a **fixed list of checks and actions** that Cypress calls **actionability** — always the same list, in the same order, for every action command. ## The list Cypress runs before the event | Check or action | What it means | Typical cause of a failure | |---|---|---| | Scroll the element into view | Uses `scrollBehavior`, `'top'` by default | not a gate — Cypress always scrolls | | Not hidden | The element is visible by the configured `visibilityStrategy` | a results panel collapsed with `display: none` | | Not disabled | The `disabled` property is not `true` on a form control | the Borrow button still `disabled` while availability loads | | Not detached | The element is still inside the application's `document` | the book row re-rendered under the command | | Not readonly | Only `.type()` and `.clear()` ask for this | the search box locked while a request is in flight | | Not animating | Movement between position samples stays under `animationDistanceThreshold` | a drawer still sliding open | | Not covered | Nothing else sits at the point the event will land on | a cookie banner over the button's centre | | Scroll again if still covered | Nudges the page past a `fixed` or `sticky` coverer | a sticky catalogue header pinned to the top | | Fire at the coordinates | The centre of the element unless you pass `position` | — | Two entries on that list are worth calling out. **Cypress scrolls every time**, even when the element was already on screen, so that a command behaves identically on every run. And **coverage is judged at a single point**, not across the whole box, so a button that is half under an overlay still fails when its centre is the covered part. ## It re-runs the list; it does not sleep The list is not evaluated once. Cypress evaluates it, and if any check fails it evaluates the whole thing again from the top: 1. The subject is re-queried from the chain that produced it, so a row the app has just re-rendered can be picked up on a later attempt. 2. Every check runs again in order — a check that passed a moment ago is not trusted. 3. The moment all of them pass, the events fire **synchronously**, so nothing can change between the last check and the event. The budget for that loop is `defaultCommandTimeout`, `4000` ms by default, or the `{ timeout }` you pass to the individual command. When it runs out, the error you see is the last check that failed, which is why the message is usually specific rather than a bare timeout. ## Reading the failure Each check has its own wording, and the wording is the diagnosis: - `failed because this element is not visible` — with a reason line explaining which rule hid it. - `failed because this element is disabled` — the `disabled` property, not a class or `aria` attribute. - `failed because this element is readonly` — only ever from `.type()` or `.clear()`. - `could not be issued because this element is currently animating` — with the three options that relax it. - `failed because this element ... is being covered by another element` — and it prints the coverer. - `failed because this element ... has CSS pointer-events: none` — a different rule from coverage. ## What the list deliberately leaves out - **Queries never scroll.** `cy.get()`, `.find()`, `.first()` and friends locate elements without moving the page; only action commands scroll. - **`opacity: 0` is actionable.** An element at zero opacity fails a `should('be.visible')` assertion, but an action command will still interact with it. - **`disabled` only counts on form controls.** A `disabled` attribute on a `<div>` does not stop a real user, so it does not stop Cypress either. - **Readonly is not checked for clicks.** A readonly input is perfectly clickable; only typing commands request that check. ## What changed in Cypress 16 As of Cypress 16 the visibility check delegates to the browser's own `Element.checkVisibility()` API under `visibilityStrategy: 'modern'`, which is the default, and `scrollBehavior` additionally accepts a per-axis `{ block, inline }` object as well as the whole-alignment values `'start'`, `'end'`, `'center'` and `'nearest'`. The list itself is unchanged: the same checks run, in the same place, before every action.

  • Which Cypress commands run this readiness list?
    The action commands: `.click()`, `.dblclick()`, `.rightclick()`, `.type()`, `.clear()`, `.check()`, `.uncheck()`, `.select()`, `.trigger()` and `.selectFile()`. Queries such as `cy.get()` and `.find()` do not — they locate elements without touching the page, which is why they never scroll anything into view.
  • Does the readiness list check that an element is `opacity: 0`?
    No, not for actions. An element at zero opacity is treated as actionable and receives the events, even though a `should('be.visible')` assertion on it fails. The two rules deliberately differ: an assertion is asking what the user can see, an action is asking what the user could reach.

saying these in an interview costs you the question

  • Cypress just sleeps before each click
  • The checks run once, not on every retry
  • cy.get() also scrolls the element into view
  • A disabled attribute on a div blocks the click
  • An opacity: 0 element can never be clicked
open as a page

In Cypress, what does `{ force: true }` on `.click()` actually turn off?

level: middleimportance: must knowfreq 68%

basics

~10 s

It skips eight things at once: scrolling into view and the not-hidden, not-disabled, not-detached, not-readonly, not-animating and not-covered checks, plus firing at a descendant. The event goes straight to the element you targeted.

open as a page

A Cypress `.click()` fails saying the Borrow button is covered by another element. How do you diagnose it?

level: seniorimportance: should knowfreq 54%

basics

~20 s

Read the second element in the message: that is what sat at the point the click would have landed. Cypress tests only that one point, and it has already tried nudge-scrolling if the coverer was fixed or sticky.

open as a page

How far should a Cypress 16 suite lean on `visibilityStrategy: 'legacy'`?

level: principalimportance: should knowfreq 34%

basics

~20 s

Treat it as a short migration crutch, not a setting. It is deprecated and scheduled for removal, so use it to keep named specs green through one upgrade while you rewrite the assertions that depended on ancestor clipping.

open as a page

In Cypress, how does an action command decide an element is still animating?

level: middleimportance: nice to knowfreq 33%

basics

~20 s

It measures rather than listens. On each retry Cypress records the element's position and compares the last two samples; if they are more than animationDistanceThreshold apart, five pixels by default, the element counts as animating.

open as a page