-
Notifications
You must be signed in to change notification settings - Fork 3.4k
perf: experimental "fast mode" for visibility checks #32801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
cypress
|
||||||||||||||||||||||||||||||||||||||||
| Project |
cypress
|
| Branch Review |
visibility-performance
|
| Run status |
|
| Run duration | 17m 34s |
| Commit |
|
| Committer | Cacie Prins |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
2
|
|
|
10
|
|
|
0
|
|
|
640
|
| View all changes introduced in this branch ↗︎ | |
UI Coverage
0%
|
|
|---|---|
|
|
4
|
|
|
0
|
Accessibility
100%
|
|
|---|---|
|
|
0 critical
0 serious
0 moderate
0 minor
|
|
|
0
|
…resolving circular dependencies
ac255b8 to
f8b1b6f
Compare
| **Performance:** | ||
|
|
||
| - Introduced a new `experimentalFastVisibility` experiment. Enabling this experiment changes how Cypress performs visibility checks and assertions. Read more about [experimental fast visibility](https://on.cypress.io/experiments/#experimental-fast-visibility). Addresses [#33044](https://github.com/cypress-io/cypress/issues/33044). Addressed in [#32801](https://github.com/cypress-io/cypress/pull/32801). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There used to be some guidance on this, that I cannot find atm. But we put Performance at the top, above Features (since they usually impact generally everyone).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/cypress-io/cypress/blob/develop/guides/writing-the-cypress-changelog.md#writing-guidelines (if we remember to do it correctly 😅 )
jennifer-shehane
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cacieprins Some notes to possibly address
| if (isBody(subject) || isHTML(subject)) { | ||
| return false | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems this could be moved out of both functions and ad the top of the isHidden definition. Not a big deal though
| // Chromium consider these elements visible, but Firefox considers them hidden. Should defer to browser | ||
| // behavior, maybe? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the decision here. Comment makes it sound undecided
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the decision should be to defer to the browser behavior based on what we discussed. Based on the logic it looks like that is what is happening and the comment just needs to be cleaned up?
| @@ -0,0 +1,330 @@ | |||
| # Fast Visibility Algorithm Migration Guide | |||
|
|
|||
| _(note: this file or content similar to it will be added to cy docs, when language / details are finalized)_ | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove this note
| - **Significantly faster** visibility calculations for complex DOM structures | ||
| - **Reduced CPU usage** during test execution |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might mention here that one would get benefit even if they are not explicitly doing a lot of 'visible' assertions.
| module.exports = { | ||
| e2e: { | ||
| experimentalFastVisibility: true | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only available in e2e? I think it's a global setting right?
| Run your existing test suite to identify any failures: | ||
|
|
||
| ```bash | ||
| npm run test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| npm run test | |
| npm run cypress run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't this be npx cypress run @jennifer-shehane ?
| This allows you to gradually migrate specs while keeping failing ones working. | ||
|
|
||
| ### Step 5: Fix Tests with Visibility-Related Failures | ||
| For specs that fail due to visibility algorithm differences, update the test expectations to match the correct behavior (see solutions below). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might mention: correct behavior = visually look at it and see if it is visible or not!
| ### Issue 7: Shadow DOM incompatibilities | ||
|
|
||
| **Problem:**: Elements inside shadow DOMs may not be detected properly as visible or hidden. | ||
|
|
||
| **Solution:**: Test shadow dom components in isolation with component testing, and only test if the public interface of the shadow dom component is visible. You wouldn't assert on the visibility of the browser's default video play controls by querying its shadow dom: you would assert on the properties of the video element itself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the result of passing shadow dom in? Will it give false results of visible or hidden? Can we not error in some way so people don't do this? (Determining if something is shadow dom is not exactly the most performant either probably, so I suspect not)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from my testing, I don't think it will work at all, so if you assert something isn't visible it will always be true and if you assert something is visible it will always be false. Is that correct, @cacieprins ?
| e2e: { | ||
| experimentalFastVisibility: false // Disable fast visibility | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again specifies as e2e
| - **Some compatibility differences exist** - when tests fail, the fast algorithm is likely correct and tests should be updated | ||
| - **Performance benefits are significant** - especially for applications with many DOM elements or complex layouts | ||
|
|
||
| By following this migration guide, you can resolve compatibility issues and benefit from faster, more accurate visibility detection while understanding the current limitations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have expected the force: true option to be mentioned in here at some point. I don't like it's use myself, but a lot of people lean back on using it.
jennifer-shehane
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
… take in cfg/options.ts
| **Performance:** | ||
|
|
||
| - Introduced a new `experimentalFastVisibility` experiment. Enabling this experiment changes how Cypress performs visibility checks and assertions. Read more about [experimental fast visibility](https://on.cypress.io/experiments/#experimental-fast-visibility). Addresses [#33044](https://github.com/cypress-io/cypress/issues/33044). Addressed in [#32801](https://github.com/cypress-io/cypress/pull/32801). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/cypress-io/cypress/blob/develop/guides/writing-the-cypress-changelog.md#writing-guidelines (if we remember to do it correctly 😅 )
| // Chromium consider these elements visible, but Firefox considers them hidden. Should defer to browser | ||
| // behavior, maybe? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the decision should be to defer to the browser behavior based on what we discussed. Based on the logic it looks like that is what is happening and the comment just needs to be cleaned up?
| this.attachShadow({ mode: 'open' }) | ||
| this.style.display = 'block' | ||
| } | ||
| // #TODO: support shadow dom in fast visibility algorithm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have a feature issue we can link here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently this doesn't work AT ALL with shadow DOM, right? In other words, asserting any type of visibility on a shadow DOM element will fail?
| import $dom from '../../dom' | ||
| import $ from 'jquery' | ||
|
|
||
| export const selectors = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth freezing these in Object.freeze or is that overkill?
| Run your existing test suite to identify any failures: | ||
|
|
||
| ```bash | ||
| npm run test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't this be npx cypress run @jennifer-shehane ?
| | **[overflow-scroll-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Element outside clip-path polygon | ✅ Yes | ❌ No | ❌ No | Child element of polygon clip-path parent | | ||
| | **[overflow-scroll-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Element outside clip-path inset | ✅ Yes | ❌ No | ❌ No | Child element of `clip-path: inset(25% 25% 25% 25%)` | | ||
| | **[viewport-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Absolutely positioned element outside of the viewport | ✅ Yes | ❌ No | ❌ No | Elements that are outside of the viewport must be scrolled to before the fast algorithm will consider them visible. This is aligned with scroll-container visibility. | | ||
| | **[z-index-coverage](../../../cypress/fixtures/visibility/positioning.html)** | Covered by higher z-index element | ✅ Yes | ❌ No | ❌ No | Element covered by another element with higher z-index | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| | **[z-index-coverage](../../../cypress/fixtures/visibility/positioning.html)** | Covered by higher z-index element | ✅ Yes | ❌ No | ❌ No | Element covered by another element with higher z-index | | |
| | **[z-index-coverage](../../../cypress/fixtures/visibility/positioning.html)** | Covered by higher z-index element | ✅ Yes | ❌ No | ❌ No | Element covered by another element with higher z-index | |
| | **[overflow-scroll-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Element outside clip-path inset | ✅ Yes | ❌ No | ❌ No | Child element of `clip-path: inset(25% 25% 25% 25%)` | | ||
| | **[viewport-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Absolutely positioned element outside of the viewport | ✅ Yes | ❌ No | ❌ No | Elements that are outside of the viewport must be scrolled to before the fast algorithm will consider them visible. This is aligned with scroll-container visibility. | | ||
| | **[z-index-coverage](../../../cypress/fixtures/visibility/positioning.html)** | Covered by higher z-index element | ✅ Yes | ❌ No | ❌ No | Element covered by another element with higher z-index | | ||
| | **[clip-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Element clipped by CSS clip property | ✅ Yes | ❌ No | ❌ No | Element with `clip: rect(0, 0, 0, 0)` or similar clipping | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| | **[clip-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Element clipped by CSS clip property | ✅ Yes | ❌ No | ❌ No | Element with `clip: rect(0, 0, 0, 0)` or similar clipping | | |
| | **[clip-scenarios](../../../cypress/fixtures/visibility/overflow.html)** | Element clipped by CSS clip property | ✅ Yes | ❌ No | ❌ No | Element with `clip: rect(0, 0, 0, 0)` or similar clipping | |
|
|
||
| ### Issue 7: Shadow DOM incompatibilities | ||
|
|
||
| **Problem:**: Elements inside shadow DOMs may not be detected properly as visible or hidden. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| **Problem:**: Elements inside shadow DOMs may not be detected properly as visible or hidden. | |
| **Problem:**: Elements inside shadow DOM may not be detected properly as visible or hidden. |
|
|
||
| **Problem:**: Elements inside shadow DOMs may not be detected properly as visible or hidden. | ||
|
|
||
| **Solution:**: Test shadow dom components in isolation with component testing, and only test if the public interface of the shadow dom component is visible. You wouldn't assert on the visibility of the browser's default video play controls by querying its shadow dom: you would assert on the properties of the video element itself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| **Solution:**: Test shadow dom components in isolation with component testing, and only test if the public interface of the shadow dom component is visible. You wouldn't assert on the visibility of the browser's default video play controls by querying its shadow dom: you would assert on the properties of the video element itself. | |
| **Solution:**: Test shadow DOM components in isolation with component testing, and only test if the public interface of the shadow DOM component is visible. You wouldn't assert on the visibility of the browser's default video play controls by querying its shadow dom: you would assert on the properties of the video element itself. |
| import { unwrap, wrap, isJquery } from '../jquery' | ||
| const { isOption, isOptgroup, isBody, isHTML } = $elements | ||
|
|
||
| const DEBUG = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guessing this is just a local thing to leverage? can we leverage the debug library to set in the browser? I'm fine with either but maybe a comment

Additional details
This is a DRAFT proposal
In order to verify performance improvements, this adds a "stress test" e2e test to the driver that simulates interacting with extremely large virtual scrolling lists. This test should probably not be run with "legacy" visibility, as it immediately causes the browser to eat up all available memory and crash. This fixture works perfectly fine outside of Cypress.
This introduces an
experimentalFastVisibilityoption, that switches our visibility detections to an alternative algorithm. This algorithm has some caveats, but it is much faster than the current visibility algorithm.Cypress uses this visibility algorithm not just for visibility assertions, but also for every interaction that requires "interactability." It's memory intensive and can cause unnecessary layout thrashing due to repeated access of CSS properties that require layout recaulcation.
The "fast" visibility algorithm:
bodyandhtmlare always visible; this is in line with current visibility behaviorcheckVisibilitymethod as first pass, with all options enabled. The following states are considered hidden by this method:content-visibilitytohiddenopacityof0visibilityproperty makes it invisiblecontent-visibilityCSS value is auto, and its derived value prevents the element from being rendered<option>or<optgroup>, it defers to the visibility of the parent<select>element. If there is no parent<select>element (an invalid DOM tree state), the element is considered hidden.The point sampling algorithm:
visibleAtPointcheck, which usesdocument.elementFromPointto determine which element is at the top of the render context at that point. If the element at that point is the subject element or a child of the subject element, the subject element is considered visible at that point.Benefits over the legacy algorithm:
Caveats:
pointer-events:noneeither explicit or inherited will always be considered hidden<option>elements that are not a direct child of<select>or<optgroup>elements are not considered visible<optgroup>elements that are not a direct child of<select>or<optgroup>elements are not considered visiblevisibility.cy.tstests fail because the subject element is either off-screen, or covered by an absolute/fixed element that is not an ancestor.Future performance improvements:
requestAnimationFrameto measure theBoundingClientRectof the subject element; this is difficult due to how the visibility & interactability checks are currently wired through jquery selectors, which prevents this method from being asyncSteps to test
How has the user experience changed?
PR Tasks
cypress-documentation? Experimental fast visibility cypress-documentation#6337type definitions?