skip to content

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

level: seniorimportance: should knowfreq 54%

answer

  1. The message names the culprit
  2. One point is tested, not the box
  3. A descendant covering is fine
  4. Fixed and sticky coverers get nudged
  5. scrollBehavior center moves the landing point

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.

solid answer

~40 s

The error prints two nodes: the element you targeted and the element Cypress found at the coordinates the event would use — the centre unless the command passed `position`. Only that single point is tested, so a half-covered button still fails when its centre is the covered part. A descendant of the target does not count as covering it; Cypress simply fires at the descendant. Before failing, Cypress re-checked on every retry and, when the coverer is `position: fixed` or `sticky`, scrolled the page to nudge the target out from under it — so a failure means the coverer was neither, or nudging never cleared it. Usual causes are a modal backdrop, a cookie banner or a toast that has not dismissed. `pointer-events: none` produces its own separate message.

code

javascript · 6 lines
javascript
// wait for the coverer to go, then act
cy.get('[data-cy=availability-toast]').should('not.exist')

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

go deeper

for a junior

Know that this failure means something is on top of the element, and that the error message itself names the element that is in the way.

for a middle

Explain that only the event coordinates are tested, that a descendant does not count, and that Cypress nudges the page past fixed or sticky coverers before giving up.

for a senior

Show the triage: identify the coverer, decide whether it is transient or a real state problem, and pick between waiting on its removal, scrollBehavior, and treating it as a product defect.

for a principal

An interviewer at this level expects a view on repeat offenders — chat widgets, banners, debug overlays — and whether the environment should suppress them rather than every spec working around them.

## What the message is actually telling you The coverage failure prints two nodes, and the second one is the whole diagnosis: ``` cy.click() failed because this element: `<button data-cy="borrow">Borrow</button>` is being covered by another element: `<div class="cookie-banner">...</div>` Fix this problem, or use {force: true} to disable error checking. ``` Cypress computed the coordinates the event would use, asked the document what element sits at that point, and found something that is **not your target and not a descendant of it**. The second node is that element. There is a sibling message, `failed because the center of this element is hidden from view`, which is the same check when the point resolves to nothing at all — the target's centre is outside the visible area. ## Four properties of the check that shape the diagnosis - **One point, not the whole box.** Only the coordinates the event will use are tested — the element's centre unless the command passed `position` or explicit `x`/`y`. A Borrow button whose left half is clear still fails if the banner covers its centre. - **Descendants do not count.** If the element at those coordinates is inside your target, that is not coverage; Cypress fires the event there instead. - **Cypress already tried to fix it.** When the covering element is `position: fixed` or `sticky`, Cypress scrolls the page to nudge the target out from underneath it and re-checks, walking up through every scrollable container. A failure therefore means the coverer was not fixed or sticky, or nudging never cleared it. If the command ran with `scrollBehavior: false`, the nudge is not attempted at all. - **`pointer-events: none` is a separate rule.** It produces its own message naming the element and the ancestor the property was inherited from, so a click blocked that way never says "covered". ## Working through it 1. **Read the coverer's markup in the error.** A class name usually identifies it immediately: a modal backdrop, a cookie banner, a loading veil, a toast. 2. **Decide whether it is permanent or transient.** A toast that dismisses itself is a timing problem; a backdrop that is still up is a state problem — the previous step did not finish. 3. **For a transient coverer, wait on the thing that removes it**, not on the button. Asserting the toast is gone before acting makes the failure legible next time. 4. **For a fixed header the nudge did not clear**, change where the element is scrolled to: `{ scrollBehavior: 'center' }` on the command, or globally in the configuration, is the targeted fix. 5. **For a modal that should not be there**, treat the failure as a real defect in the flow the test is walking, not as an actionability problem to work around. ## Mapping the messages | Message | What it means | First move | |---|---|---| | `is being covered by another element` | Something else owns the event point | Identify the printed coverer | | `the center of this element is hidden from view` | Nothing resolves at the point | Check clipping and scroll position | | `has CSS pointer-events: none` | The target itself cannot receive mouse input | Look at the app's CSS, not the layout | | `this element is not visible` | The visibility check failed before coverage was reached | Read the reason line in the error | ## The trap of reaching for force first `{ force: true }` makes the message disappear, and it is the wrong first move here more often than anywhere else, because a coverage failure is the one check that most reliably reports a genuine user-facing problem: something really is on top of the control. A forced click fires at the button underneath the modal and the test goes green while a reader with the same modal open cannot borrow anything. Force is defensible once you have identified the coverer and decided the coverage is an artefact of the test environment — a third-party widget, a chat bubble, a debug overlay — and not of the product. One more note specific to Cypress 16: under the default `visibilityStrategy: 'modern'` the visibility check no longer treats coverage of a fixed element as hidden, so coverage is now reported by the coverage check and its two-node message rather than by a visibility failure.

  • Why does the coverage check use only one point?
    Because that point is where the event will actually be dispatched — the element's centre by default, or whatever `position` or explicit `x`/`y` selected. Testing the whole box would answer a different question from the one that decides where the click lands.
  • The message says `has CSS pointer-events: none` instead. What changed?
    Nothing is on top of the element; the element itself is declared unable to receive mouse input, possibly through inheritance — the error names the ancestor the property came from. That is an application CSS problem, not a layout or timing one, so scrolling and waiting will never clear it.

saying these in an interview costs you the question

  • Add force: true and move on
  • Cypress checks the whole element for coverage
  • A child element covering the target fails the click
  • Cypress never scrolls again once the element is in view
  • Coverage failures are always test flakiness