A Cypress visibility failure can mean three different things: the element is not rendered, it is outside a scroll container, or it is rendered but covered at the point a person would click. Start by identifying whether an action such as .click() failed or a visibility assertion such as should('be.visible') failed. Then check your Cypress major version and choose an assertion or scroll setting that matches the behavior you actually need to prove.
First identify the failed check
When an action command fails
Commands such as .click(), .type() and .check() perform actionability checks. Cypress waits and retries, scrolls the subject into view, and verifies that the action can be performed. A fixed or sticky header may cover the target after Cypress scrolls it to the top of the viewport. That is an action-positioning problem, not necessarily a visibility problem.
cy.get('[data-cy=save]').click({ scrollBehavior: 'center' })
The scrollBehavior option controls where Cypress places the subject before acting. center is often safer than the default top when a header occupies the top of the viewport. You can set a project default in configuration, or override it only on commands whose layout requires a different alignment.
When a visibility assertion fails
cy.get(selector).should('be.visible') asks Cypress to evaluate visibility according to its configured visibility strategy. It does not mean “a human could click the element at its current viewport coordinate.” A separate coverage or geometry check may be required.
Check the Cypress version and visibility strategy
Cypress 16 changed the default strategy to the browser-native Element.checkVisibility() API. The modern strategy recognizes states such as display: none, visibility: hidden and relevant content-visibility conditions, after a zero-dimension guard. Its behavior is intentionally different from the older ancestor-walking algorithm.
#1 Best Overall
Confirm the installed version before changing tests:
npx cypress version
# or inspect the cypress entry in package.json
npm ls cypress
The configuration reference defines modern as the default visibilityStrategy and top as the default scrollBehavior. A project upgraded to Cypress 16 can therefore produce a different result without any application CSS changing.
What changed for overflow
The legacy algorithm treated clipping by an overflow: hidden ancestor, and content scrolled outside an overflow: auto or overflow: scroll ancestor, as hidden. The modern algorithm does not automatically make a nonzero, rendered element hidden merely because it lies outside that ancestor’s scrollport. An element can therefore satisfy be.visible while it is currently below, above, or beside the visible portion of a scroll container.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Fix an action blocked by a fixed or sticky ancestor
Use alignment instead of forcing the click
Try an explicit scroll alignment first:
cy.get('[data-cy=save]')
.click({ scrollBehavior: 'center' })
If every test in a suite has the same header geometry, configure the default rather than repeating the option:
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
scrollBehavior: 'center'
}
})
Choose the alignment that leaves the target clear of your overlay. center is not universally correct: a bottom toolbar may require top, while a very tall target may need a custom layout or a smaller test viewport.
Rank #2
Reserve space for the overlay in the application
If a fixed header is meant to remain on screen, the durable application fix is to prevent content from being hidden beneath it. For example, add a top offset to the page’s scroll container or use scroll-margin-top on anchored targets. This fixes real-user behavior and makes Cypress’s automatic scrolling less fragile.
/* application CSS example */
:root { --header-height: 64px; }
main { padding-top: var(--header-height); }
[data-cy=save] { scroll-margin-top: var(--header-height); }
Use force: true only deliberately
force: true skips Cypress’s actionability waiting and checks:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →cy.get('[data-cy=save]').click({ force: true })
This is appropriate only when bypassing actionability is itself part of the test (for example, a low-level event test). It can conceal a broken layout, an inaccessible control, or an overlay that a real user cannot dismiss. It also does not prove that the element is currently usable.
Test overflowed ancestors according to the intended behavior
Prove that an element is outside a particular scrollport
If the requirement is “this button is below the visible area of this container,” assert geometry directly. Adapt the direction to your layout; the example below checks the element below the container’s bottom edge:
cy.get('#scroll-container button').should(($el) => {
const container = $el[0].closest('#scroll-container')
expect($el[0].getBoundingClientRect().top)
.to.be.greaterThan(container.getBoundingClientRect().bottom)
})
For content above the scrollport, compare the element’s bottom with the container’s top. For horizontal scrolling, compare left and right edges. Reading both rectangles in the same callback avoids mixing stale measurements.
Rank #3
Do not use visibility for a collapsed-state contract
A panel with overflow: hidden and max-height: 0 can leave a child with nonzero dimensions. Under the modern strategy, that child may still be reported visible. If the product’s contract is “the panel is closed,” assert the state your component exposes:
Free tools Windows power users keep installed
One-click scans. No signup required.
cy.get('[data-cy=details-panel]')
.should('have.attr', 'aria-hidden', 'true')
cy.get('[data-cy=details-toggle]')
.should('have.attr', 'aria-expanded', 'false')
Use the attribute your application actually maintains; do not add an accessibility attribute solely to satisfy a test.
Detect coverage by fixed or sticky overlays
Modern visibility does not perform the legacy fixed/sticky coverage check. If your requirement is “nothing covers the point where this onscreen control is displayed,” perform a viewport hit test. A reusable Cypress query can compare the element’s center with document.elementFromPoint():
Cypress.Commands.add('isCovered', (selector) => {
cy.get(selector).should(($el) => {
const el = $el[0]
const rect = el.getBoundingClientRect()
const x = rect.left + rect.width / 2
const y = rect.top + rect.height / 2
const topElement = document.elementFromPoint(x, y)
expect(topElement, 'top element at target center')
.to.satisfy((candidate) => {
return candidate === el || !!candidate && el.contains(candidate)
})
})
})
Call it only for a target that should currently be onscreen:
cy.isCovered('[data-cy=save]')
elementFromPoint uses viewport coordinates. If the target is outside the viewport, its center may not represent a usable onscreen location; scroll first, then run the coverage check. A null result is a failure because no element occupies that point.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Choose the assertion that matches the user requirement
| Requirement | Best check | Why |
|---|---|---|
| The control can be interacted with after Cypress scrolls | Action command with suitable scrollBehavior |
Actionability includes scrolling, retries and interaction checks. |
| The element is rendered and not CSS-hidden | should('be.visible') |
Uses the configured modern or legacy visibility strategy. |
| The target lies outside a specific scrollport | getBoundingClientRect() comparison |
Expresses the required above/below/left/right relationship. |
| A fixed or sticky layer does not cover an onscreen point | elementFromPoint() hit test |
Tests current viewport coverage rather than generic rendering. |
| A disclosure is closed | aria-expanded, aria-hidden or component state |
Tests the semantic state users and assistive technology receive. |
Use legacy visibility only as a migration bridge
Cypress provides visibilityStrategy: 'legacy' globally or for a suite/test while you migrate. Both the option and the legacy value are deprecated and scheduled for removal in a future major release. Treat this as temporary compatibility, not a permanent fix.
Rank #4
// cypress.config.js (temporary migration setting)
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
visibilityStrategy: 'legacy'
}
})
Prefer updating each test to assert geometry, coverage, or application state. That keeps the test meaningful when Cypress changes its internal visibility implementation again.
Common failures and targeted fixes
“Element is not visible because it has an ancestor with overflow hidden”
- Determine whether the message came from an action or an assertion.
- On Cypress 16 or newer, do not assume overflow clipping alone means hidden; inspect the actual rectangles and intended behavior.
- If a collapsed wrapper represents closed state, assert its state attribute.
- If the control should be clickable, fix the layout or choose an alignment that clears the overlay.
“Click element is covered by a fixed header”
- Try
click({ scrollBehavior: 'center' })or another alignment. - Add application scroll offset or
scroll-margin-topwhen the header is part of the real design. - Use a hit test when coverage itself is the behavior under test.
- Do not use
force: truemerely to make the test green.
“Should be visible” passes, but the user cannot click
Visibility and coverage are different. Run the hit-test command at the current viewport location, inspect z-index and pointer-event rules, and verify that the target is not behind a modal, cookie banner or sticky control. The modern algorithm intentionally does not detect all overlay cases.
Tests differ after a Cypress upgrade
Record the installed Cypress version, browser, viewport and relevant CSS. Compare the result under the modern strategy, then rewrite the assertion around the intended behavior. Use the legacy strategy only long enough to migrate affected tests.
Recommended Free Tools
Or skip the browser setup
When the goal is a clean reference image rather than an interaction test, ScreenshotNeo provides a single screenshot API call. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives Claude, Cursor and other MCP clients take_screenshot, get_page_info and capture_pdf tools.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the complete parameter list and options in the ScreenshotNeo documentation. The service supports full-page and element captures, device and viewport settings, dark mode, retina scale, PDF output, custom CSS/JavaScript, clicks, waits, request blocking, cookies, headers, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous webhooks, bulk capture and a usage API.
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account to try it.
FAQ
Does should('be.visible') guarantee a click will work?
No. It evaluates visibility, while an action also depends on scrolling, hit area, enabled state and whether another element covers the target.
Should I always set scrollBehavior to center?
No. Center is useful for top overlays, but choose the alignment that fits your fixed elements and target size.
Can overflow clipping still be tested?
Yes. Use bounding-rectangle comparisons for the exact scrollport relationship rather than relying on a generic visibility assertion.
Is the legacy strategy a permanent compatibility setting?
No. Cypress documents it as deprecated and scheduled for removal; migrate to behavior-specific assertions.
Frequently Asked Questions
Does should('be.visible') guarantee a click will work?
No. Visibility does not prove that the target is uncovered, enabled, or positioned where an action can reach it.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallShould every test use scrollBehavior: 'center'?
No. Select the alignment that clears the fixed or sticky elements in your own layout.
How can I test that content is outside a scroll container?
Compare the target and container bounding rectangles in the direction that represents your layout.
Can I keep Cypress’s legacy visibility strategy indefinitely?
No. It is deprecated and intended only as a temporary migration bridge.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

