-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
.should('not.be.visible') fails when elem out of viewport #877
Comments
I don't believe this is a bug. We wrote the visibility calculations to take into account elements outside of the viewport. What I mean is - an element is considered visible if the user in could in any way interact with it - even if they needed to scroll to it. The reason this rule has to be in place is because scrolling is a mutation. If Cypress first attempted to scroll elements on every single Visibility is simply - is the element capable of being seen by the user? Yes? Visible. |
This is the visibility logic in our code, in case you want to investigate before we are able to: https://github.com/cypress-io/cypress/blob/code-of-conduct/packages/driver/src/dom/visibility.coffee#L17 |
Yes, what @brian-mann explains above is actually true. The example in the kitchen sink works because we take into account elements being clipped by a parent container, but we don't take into account the viewport size when calculating visibility. It's complicated logic. What you are doing above is essentially what you should continue doing, writing the code to manually check if the element is visible within the viewport. |
There are a few exceptions to the rules I listed above. If the element could be clipped by a parent in any capacity, then we check to see if this is currently the case. In those situations you may need to scroll an element into view first before asserting on its visibility. |
Right. Imagine a scenario where this wasn't the case. You would virtually always need to first The vast majority of the time, probably in the 99% percentile range - all you care about is whether or not the element could in fact be seen by the user in some natural capacity. |
Yea, that's what I've thought and been relying on (current behavior).. I just misunderstood @jennifer-shehane in the chat and thought it wasn't a feature to begin with. What's puzzling, is why the test code works at all, considering that |
Correct. We diverged it completely and use our own algorithm. Then we stiched together the assertion to be our definition of visibility. https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/config/jquery.coffee#L8 |
Yea, maybe assertions doc update is in order (maybe you've already mentioned it's planned, don't remember). |
For readers in the future, you can add custom commands in Cypress.Commands.add('isNotInViewport', element => {
cy.get(element).then($el => {
const bottom = Cypress.$(cy.state('window')).height()
const rect = $el[0].getBoundingClientRect()
expect(rect.top).to.be.greaterThan(bottom)
expect(rect.bottom).to.be.greaterThan(bottom)
expect(rect.top).to.be.greaterThan(bottom)
expect(rect.bottom).to.be.greaterThan(bottom)
})
})
Cypress.Commands.add('isInViewport', element => {
cy.get(element).then($el => {
const bottom = Cypress.$(cy.state('window')).height()
const rect = $el[0].getBoundingClientRect()
expect(rect.top).not.to.be.greaterThan(bottom)
expect(rect.bottom).not.to.be.greaterThan(bottom)
expect(rect.top).not.to.be.greaterThan(bottom)
expect(rect.bottom).not.to.be.greaterThan(bottom)
})
}) and then in your tests use it like so: cy.isNotInViewport('[data-cy=some-invisible-element]')
cy.isInViewport('[data-cy=some-visible-element]') |
In my particular use case, the assertion fails since the element comes into view after a smooth scroll. As a solution, I replace |
@shreyansqt Thanks for your code Updated it to be chain-able
|
Inspired by previous very helpful commands I wrote a command which you can use to check position of element in all directions. My case was testing a "caroussel" on mobile where you can slide through options left or right. Needed to verify it was not placing the options in a vertical list. However, the elements would be visible for a few pixels on the edges. That's why my command checks the position of the element's center in relation to viewport instead of the edges. // Positions: inside, above, below, left, right
cy.get('center').positionToViewport('inside').click()
cy.get('left').positionToViewport('left')
cy.get('below').positionToViewport('below')
// Command
Cypress.Commands.add('positionToViewport', { prevSubject: true }, (element, position) => {
cy.get(element).should($el => {
const height = Cypress.$(cy.state('window')).height()
const width = Cypress.$(cy.state('window')).width()
const rect = $el[0].getBoundingClientRect()
if(position == 'inside'){
expect((rect.top + (rect.height/2)), 'element center not above viewport').to.be.greaterThan(0)
expect((rect.top + (rect.height/2)), 'element center not below viewport').to.be.lessThan(height)
expect((rect.left + (rect.width/2)), 'element center not left of viewport').to.be.greaterThan(0)
expect((rect.left, + (rect.width/2)), 'element center not right of viewport').to.be.lessThan(width)
}else if(position == 'above'){
expect((rect.top + (rect.height/2)), 'element center above viewport').to.be.lessThan(0)
}else if(position == 'below'){
expect((rect.top + (rect.height/2)), 'element center below viewport').to.be.greaterThan(height)
}else if(position == 'left'){
expect((rect.left + (rect.width/2)), 'element center left of viewport').to.be.lessThan(0)
}else if(position == 'right'){
expect((rect.left + (rect.width/2)), 'element center right of viewport').to.be.greaterThan(width)
}
})
}) Any comments or improvements are welcome ofcourse. |
Inspired by @Whassup, I rewrote the command as an assertion. Simply paste the following into a const isInViewport = (_chai, utils) => {
function assertIsInViewport(options) {
const subject = this._obj;
const bottom = Cypress.$(cy.state('window')).height();
const rect = subject[0].getBoundingClientRect();
this.assert(
rect.top < bottom && rect.bottom < bottom,
"expected #{this} to be in viewport",
"expected #{this} to not be in viewport",
this._obj
)
}
_chai.Assertion.addMethod('inViewport', assertIsInViewport)
};
chai.use(isInViewport); Usage: cy.get("button").should("be.inViewport"); |
Thanks @thomaseizinger For my use case I changed the behavior so that also the current scroll-position is taken into account and the element is seen as in viewport, as long as it could partly be seen. Either the const isInViewport = (_chai, utils) => {
function assertIsInViewport(options) {
const subject = this._obj
const windowHeight = Cypress.$(cy.state('window')).height()
const bottomOfCurrentViewport = windowHeight
const rect = subject[0].getBoundingClientRect()
this.assert(
(rect.top > 0 && rect.top < bottomOfCurrentViewport) ||
(rect.bottom > 0 && rect.bottom < bottomOfCurrentViewport),
'expected #{this} to be in viewport',
'expected #{this} to not be in viewport',
subject,
)
}
_chai.Assertion.addMethod('inViewport', assertIsInViewport)
}
chai.use(isInViewport) |
For anyone that needs a command to check that something is not in the viewport with horizontal and vertical checks, I use this, /**
* A custom command to check whether the element is not visible within the viewport.
*/
Cypress.Commands.add('isNotInViewport', (element) => {
cy.get(element).should(($el) => {
const bottom = Cypress.$(cy.state('window')).height();
const right = Cypress.$(cy.state('window')).width();
const rect = $el[0].getBoundingClientRect();
expect(rect).to.satisfy((rect) => rect.top < 0 || rect.top > bottom || rect.left < 0 || rect.left > right);
}); |
…isse testene for nå. Har ikke hatt noen caser hvor slike tester hadde ordnet noe uansett, så tenker det ikke er verdt å bruke mere tid på det nå. prøvde å å sette opp custom comands eller assertions som beskrevet her: cypress-io/cypress#877
I've combined a few answers given here:
It's chainable, uses
instead of
|
I'm relatively new to Cypress and have tried to implement some of the suggestions above with regards to creating a new command in commands.ts However, I am having problems with cy.state, I get the below error message and I am having difficultly trying to find a fix for it, can anyone help? 'Property 'state' does not exist on type 'cy & EventEmitter''. All of the solutions seem to use cy.state so I am trying to figure out to fix this, any help much appreciated! e.g. expect(rect.top).not.to.be.greaterThan(bottom); return subject; |
I was struggling with getting the above to work when the viewport had been scrolled - to test if an element had been scrolled to or not. I got this to give me my desired behaviour accounting for scrolling /cypress/support.index.js Cypress.Commands.add("isScrolledTo", { prevSubject: true }, (element) => {
cy.get(element).should(($el) => {
const bottom = Cypress.$(cy.state("window")).height();
const rect = $el[0].getBoundingClientRect();
expect(rect.top).not.to.be.greaterThan(bottom, `Expected element not to be below the visible scrolled area`);
expect(rect.top).to.be.greaterThan(0 - rect.height, `Expected element not to be above the visible scrolled area`)
});
}); In tests: cy.get('#payment-calculator').isScrolledTo() |
A potential solution for Cypress 8+ function isInViewport(el) {
cy.get(el)
.then($el => {
cy.window().then(window => {
const { documentElement } = window.document;
const bottom = documentElement.clientHeight;
const right = documentElement.clientWidth;
const rect = $el[0].getBoundingClientRect();
expect(rect.top).to.be.lessThan(bottom);
expect(rect.bottom).to.be.greaterThan(0);
expect(rect.right).to.be.greaterThan(0);
expect(rect.left).to.be.lessThan(right);
});
});
} |
I needed to be able to set timeout and retries, like any other cypress command so this is my custom command: Cypress.Commands.add('waitUntilIsNotInViewport', { prevSubject: true },
(subject, timeout = Cypress.config('defaultCommandTimeout')) => {
const height = Cypress.$(cy.state('window')).height();
const width = Cypress.$(cy.state('window')).width();
return cy.wrap(subject, { timeout }).should($el => {
const rect = $el[0].getBoundingClientRect();
const notVisibleVertically = rect.bottom < 0 || rect.top > height;
const notVisibleHorizontally = rect.right < 0 || rect.left > width;
expect(notVisibleVertically || notVisibleHorizontally,
'the element should be outside the viewport').to.be.true;
});
}
); |
Current behavior:
fails even when DOM element is not in viewport.
It seems I'm not the only one reporting this behavior.
Desired behavior:
Acc to doc, only actionable commands should autoscroll DOM to viewport.
How to reproduce:
The text was updated successfully, but these errors were encountered: