skip to content

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

level: middleimportance: nice to knowfreq 33%

answer

  1. Measured, not reported by the page
  2. Two position samples, one distance
  3. Five pixels is the default tolerance
  4. animationDistanceThreshold raises the tolerance
  5. waitForAnimations turns the check off

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.

solid answer

~40 s

Cypress has no reliable event that means "the animation finished", so it infers motion geometrically. On every pass of the readiness loop it records the element's coordinates and measures the straight-line distance between the two most recent samples. If that distance is greater than `animationDistanceThreshold` — `5` pixels by default — the element counts as animating and the command retries instead of firing. Sticky elements are sampled relative to the viewport and everything else relative to the document, so scrolling the page does not read as an animation. Two options tune it: `waitForAnimations: false` drops the animation check alone and leaves the rest of the list in place, and a larger `animationDistanceThreshold` tolerates faster movement. The failure text, `this element is currently animating`, names both.

go deeper

for a junior

Know that Cypress waits for a moving element to settle before acting, and that the wait is automatic rather than something you write.

for a middle

Be ready to describe the two-sample distance measurement, the five-pixel default, and the difference between raising the threshold and turning the check off.

for a senior

Talk about elements that never settle — looping carousels, long easing tails — and how you decide between pausing animation in the test build and relaxing the check.

for a principal

An interviewer at this level wants the standard: whether animations are disabled environment-wide for tests, and what that costs in fidelity.

## Cypress infers motion; it is not told about it There is no browser event that means "this element has finished animating" for every animation technique, so Cypress does not ask the page. It measures. On each pass of the actionability loop it records the element's position and compares the two most recent samples: 1. Get the element's coordinates for this attempt — relative to the viewport when the element (or an ancestor) is `position: sticky`, and relative to the document otherwise. 2. Push that point onto the history for this command. 3. If fewer than two samples exist, retry to collect another one. 4. Measure the straight-line distance between the last two samples. 5. If that distance is **greater than `animationDistanceThreshold`**, treat the element as animating and retry the whole readiness list. The default threshold is `5` pixels. The sampling choice in step 1 matters: because a scrolling page moves every document-relative coordinate, sticky elements are measured against the viewport so that scrolling the page does not read as an animation. ## The two options that tune it | Option | Default | What it changes | |---|---|---| | `animationDistanceThreshold` | `5` | How far the element may move between samples before it counts as animating | | `waitForAnimations` | `true` | Whether the animation check runs at all | Both are configuration keys and both can be passed per command: - Raising the threshold — `{ animationDistanceThreshold: 20 }` — keeps the check but tolerates a faster transition, which suits a page whose cards settle quickly. - `{ waitForAnimations: false }` drops the animation check **only**. Visibility, disabled, readonly and coverage still run, which is what makes it a much narrower instrument than `{ force: true }`. - Setting either in the Cypress configuration applies to every action command in the run; passing it in the command's options object applies to that call alone. ## What the failure looks like When the loop runs out of time while the element is still moving, the error reads `could not be issued because this element is currently animating`, prints the node, and lists the three ways to proceed: `{ force: true }`, `{ waitForAnimations: false }` and a larger `{ animationDistanceThreshold: 20 }`. Seeing that message is a signal that the element genuinely kept moving for the whole timeout — a long CSS transition, an easing curve with a slow tail, an animation that loops, or a container that keeps re-laying out. There is a second, rarer message from the same code: `Not enough coord points provided to calculate distance`. That means the command timed out before two position samples could be taken at all, which normally only happens on a very slow machine or with a very short timeout. ## Where this shows up in a real suite - A results list that animates each book card in on render: a `.click()` on the first Borrow button fires only after that card's motion falls under the threshold. - A toast that slides in over the catalogue: it is the **coverage** check, not the animation check, that fails there — the toast is moving, but the button under it is not. - A carousel that never stops moving: the animation check will never pass, and the honest fix is either to pause the animation in the test environment or to accept `{ waitForAnimations: false }` for that one command. - A suite that sets `waitForAnimations: false` globally: every action fires against whatever position the element occupies at that instant. On a fast transition that is usually harmless; on a long one it means clicks land at the element's *old* coordinates. The judgement to hold onto is that the animation check is a **proxy for stability**, measured in pixels between two samples, not a statement about CSS. Anything that moves the element — a transition, a layout shift, a parent resizing — reads the same way to it.

  • Why are sticky elements sampled differently?
    A `position: sticky` element keeps its place in the viewport while the document scrolls, so document-relative coordinates would change on every scroll and read as motion. Cypress samples those elements against the viewport instead, so only real movement registers as an animation.
  • A toast slides in over the Borrow button. Which check fails?
    The coverage check, not the animation check. The animation check only looks at the target element's own position, and the Borrow button is not moving — something else has arrived on top of the point the click would land on, so the failure names the covering element.

It is the check a photographer makes before pressing the shutter: not "has the animation ended" but "are two consecutive frames close enough to call this still".

saying these in an interview costs you the question

  • Cypress listens for CSS animation end events
  • The animation check inspects the element's transition property
  • waitForAnimations: false disables all actionability checks
  • Scrolling the page counts as an animation
  • The threshold is measured in milliseconds