Skip to content

fix(driver): scroll off-screen elements into view in fast visibility algorithm#33736

Draft
mschile wants to merge 11 commits into
developfrom
mschile/pensive-rubin-df92b9
Draft

fix(driver): scroll off-screen elements into view in fast visibility algorithm#33736
mschile wants to merge 11 commits into
developfrom
mschile/pensive-rubin-df92b9

Conversation

@mschile
Copy link
Copy Markdown
Collaborator

@mschile mschile commented May 4, 2026

Additional details

When experimentalFastVisibility is enabled, the fast visibility algorithm uses document.elementFromPoint() for point-sampling. That API only detects elements within the browser viewport, so elements scrolled below the fold were incorrectly reported as hidden — even though they were just out of view, not actually hidden by CSS.

The fix preprocesses subjects in fastIsHidden: if the element's bounding rect lies outside the viewport, scroll it into view per the scrollBehavior config (using behavior: 'instant' for synchronous scrolling) and then re-sample. This matches the scroll behavior of action commands like cy.click(), which already scroll before their visibility checks.

Subjects with a clipping ancestor are excluded from the scroll preprocessing. scrollIntoView walks up to the nearest scrollable ancestor — including elements with overflow: hidden, which are programmatically scrollable even though they're not user-scrollable. Scrolling such an ancestor would expose content the test author intentionally clipped (e.g. inputs in a transform-based slider, or out-of-bounds flex items in an overflow: hidden flex container). When any ancestor has overflow-x/overflow-y of hidden or clip, we skip the scroll and let point-sampling identify the element as hidden via the unscrolled rect.

When scrollBehavior is set to false, off-screen elements remain hidden — no scroll is performed.

Elements positioned outside the scrollable document bounds (e.g. position: absolute; top: -100px) still report as hidden, since scrollIntoView cannot bring them into view.


Note

Medium Risk
Changes core experimentalFastVisibility behavior by scrolling elements during visibility checks, which could affect test outcomes/performance in edge cases involving overflow clipping and scroll configuration. Scope is limited to the experimental fast algorithm and is covered by new/updated driver e2e tests.

Overview
Fixes experimentalFastVisibility so elements that are merely below the fold are no longer reported as hidden: fastIsHidden now scrolls off-screen subjects into view (respecting scrollBehavior, and skipping when scrollBehavior: false).

Adds guards to avoid scrolling when an ancestor would clip the element on the off-screen axis (to preserve intentional overflow: hidden/clip behavior), plus shared scrollBehaviorscrollIntoView mapping via new scrollBehaviorOptionsMap util.

Expands the driver visibility e2e suite and fixtures with new scrollable viewport scenarios and explicit tests for scrolling behavior, scrollBehavior: false, and clipping-ancestor edge cases, and documents the updated behavior in the fast-visibility migration guide and CLI changelog.

Reviewed by Cursor Bugbot for commit f9a076b. Bugbot is set up for automated code reviews on this repo. Configure here.

Steps to test

  1. Enable experimentalFastVisibility: true in cypress.config.
  2. Navigate to a page with an element below the fold.
  3. Assert cy.get('.below-fold-element').should('be.visible') — passes on this branch, fails on develop.
  4. Set scrollBehavior: false and re-run — assertion fails (expected, since we no longer scroll).
  5. Run the driver visibility E2E suite: yarn workspace @packages/driver cypress:run -- --spec cypress/e2e/dom/visibility.cy.ts. The new scrollable-viewport-scenarios section and the two explicit off-screen tests cover both the success path and the scrollBehavior: false case across legacy and fast modes.
  6. Run cypress/e2e/e2e/visibility.cy.js to verify the with overflow and transform - slider and overflow-flex-container cases — these exercise the clipping-ancestor exclusion (subjects clipped by overflow: hidden are not scrolled into view).

How has the user experience changed?

Users with experimentalFastVisibility enabled can now assert visibility on elements below the fold without manually scrolling first — the driver scrolls them into view automatically, matching the legacy algorithm's behavior more closely and aligning with how cy.click() already works. Elements clipped by overflow: hidden ancestors continue to be reported as hidden (matching the legacy algorithm and respecting the test author's clipping intent).

pr-33736-demo.mp4

PR Tasks

…ity check (#33045)

When experimentalFastVisibility is enabled, the fast algorithm uses
elementFromPoint() which only detects elements within the viewport.
Elements below the fold were incorrectly reported as hidden.

Before point-sampling, check if the element is outside the viewport and
scroll it into view using scrollIntoView() with the scrollBehavior config
setting. This matches the scroll behavior of action commands like cy.click().

When scrollBehavior is false, off-screen elements remain hidden (no scroll).
Comment thread packages/driver/src/dom/visibility/fastIsHidden.ts Outdated
Extract the scrollBehavior → ScrollLogicalPosition map into
util/scrollBehavior.ts so actionability.ts and fastIsHidden.ts share one
source of truth instead of maintaining duplicate copies.

Also adds the missing changelog entry under the 15.15.0 Bugfixes section.
Comment thread packages/driver/src/dom/visibility/fastIsHidden.ts
…cestor

scrollIntoView walks up to the nearest scrollable ancestor — including
elements with overflow: hidden, which are programmatically scrollable even
though they're not user-scrollable. Scrolling such an ancestor exposed
content the test author intentionally clipped, breaking tests that asserted
clipped elements should NOT be visible (e.g. the overflow-and-transform
slider in driver/e2e/visibility, and the overflow-flex-container fixture
where out-of-bounds flex items are checked alongside in-bounds ones).

Walk up the DOM before scrolling: if any ancestor has overflow-x or
overflow-y of hidden or clip, skip the scroll. Point-sampling on the
unscrolled rect correctly reports the element as hidden because
elementFromPoint returns null for coordinates outside the viewport.
mschile added a commit to cypress-io/cypress-documentation that referenced this pull request May 4, 2026
Elements inside overflow: hidden / clip ancestors are not scrolled into
view, since scrollIntoView would programmatically scroll the clipping
container and expose content the test author intentionally clipped.

Pairs with cypress-io/cypress#33736.
@cypress
Copy link
Copy Markdown

cypress Bot commented May 4, 2026

cypress    Run #70614

Run Properties:  status check passed Passed #70614  •  git commit 3006a8d699: Merge remote-tracking branch 'origin/develop' into mschile/pensive-rubin-df92b9
Project cypress
Branch Review mschile/pensive-rubin-df92b9
Run status status check passed Passed #70614
Run duration 19m 12s
Commit git commit 3006a8d699: Merge remote-tracking branch 'origin/develop' into mschile/pensive-rubin-df92b9
Committer Matthew Schile
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 11
Tests that did not run due to a developer annotating a test with .skip  Pending 1092
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 24798
View all changes introduced in this branch ↗︎

Warning

No Report: Something went wrong and we could not generate a report for the Application Quality products.

Comment thread packages/driver/src/dom/visibility/fastIsHidden.ts
The clipping-ancestor guard was returning true if any ancestor had
overflow-x OR overflow-y of hidden/clip — which incorrectly blocked vertical
scrolling for elements below the fold under the common
body { overflow-x: hidden } pattern. Per CSS spec, setting one axis to a
non-visible value leaves the other axis scrollable.

Pass the bounding rect to determine which axis is off-screen, and only
consider an ancestor clipping if it clips on that same axis. Adds a
regression test covering the body { overflow-x: hidden } case.
@mschile mschile requested a review from cacieprins May 6, 2026 14:46
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 10574c4. Configure here.

Comment thread packages/driver/src/dom/visibility/fastIsHidden.ts
…s bounds

Many UI patterns use overflow: hidden for cosmetic clipping (border-radius
rounding, layout containment) without intent to hide their child content.
The previous check returned true any time an off-screen subject had an
overflow: hidden ancestor on the same axis, which produced false negatives:
in-bounds children of `overflow: hidden` cards below the fold were
reported hidden even though scrolling would correctly bring them into view.

Now we also call getBoundingClientRect on each candidate ancestor and only
short-circuit when the subject is actually outside the ancestor's bounds on
the off-screen axis. Adds a regression test for an in-bounds child of an
overflow: hidden card below the fold.
@mschile mschile marked this pull request as draft May 11, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scrollBehavior directed visibility preprocessing

2 participants