19

Cypress's visible matcher treats an element as visible based on a variety of factors, however it doesn't take the viewport into account, so an element that is scrolled off-screen is still treated as visible.

I need to test that a link to an on-page anchor is functioning correctly. Once the link is clicked, the page scrolls to the element with the id as defined in the href of the link (example/#some-id).

How can verify that the element is within the viewport?

Undistraction
  • 42,754
  • 56
  • 195
  • 331

3 Answers3

15

I've cobbled together the following commands which appear to work so far, but amazed there isn't on out-of-box solution:

Cypress.Commands.add('topIsWithinViewport', { prevSubject: true }, subject => {
  const windowInnerWidth = Cypress.config(`viewportWidth`);

  const bounding = subject[0].getBoundingClientRect();

  const rightBoundOfWindow = windowInnerWidth;

  expect(bounding.top).to.be.at.least(0);
  expect(bounding.left).to.be.at.least(0);
  expect(bounding.right).to.be.lessThan(rightBoundOfWindow);

  return subject;
})

Cypress.Commands.add('isWithinViewport', { prevSubject: true }, subject => {
  const windowInnerWidth = Cypress.config(`viewportWidth`);
  const windowInnerHeight = Cypress.config(`viewportHeight`);

  const bounding = subject[0].getBoundingClientRect();

  const rightBoundOfWindow = windowInnerWidth;
  const bottomBoundOfWindow = windowInnerHeight;

  expect(bounding.top).to.be.at.least(0);
  expect(bounding.left).to.be.at.least(0);
  expect(bounding.right).to.be.lessThan(rightBoundOfWindow);
  expect(bounding.bottom).to.be.lessThan(bottomBoundOfWindow);

  return subject;
})
Valera Tumash
  • 628
  • 7
  • 16
Undistraction
  • 42,754
  • 56
  • 195
  • 331
2

I did a little refactoring on Undistracted's approach if anyone is interested:

Cypress.Commands.add('isWithinViewport', { prevSubject: true }, (subject) => {
  const rect = subject[0].getBoundingClientRect();

  expect(rect.top).to.be.within(0, window.innerHeight);
  expect(rect.right).to.be.within(0, window.innerWidth);
  expect(rect.bottom).to.be.within(0, window.innerHeight);
  expect(rect.left).to.be.within(0, window.innerWidth);

  return subject;
});

Cypress.Commands.add('isOutsideViewport', { prevSubject: true }, (subject) => {
  const rect = subject[0].getBoundingClientRect();

  expect(rect.top).not.to.be.within(0, window.innerHeight);
  expect(rect.right).not.to.be.within(0, window.innerWidth);
  expect(rect.bottom).not.to.be.within(0, window.innerHeight);
  expect(rect.left).not.to.be.within(0, window.innerWidth);

  return subject;
});

Uses window.innerWidth and window.innerHeight in case you have used cy.viewport before calling. Also uses .within to facilitate the outside addition.

Andrew Leedham
  • 700
  • 7
  • 16
0

This code worked for me

cy.get(element).eq(index).then(($el) => {
        const bottom = Cypress.$(cy.state('window')).height()
        console.log("element", $el[0])
        const rect = $el[0].getBoundingClientRect()
        const isInViewport = (
            rect.top >= -2 &&
            rect.left >= 0 &&
            rect.bottom <= bottom &&
            rect.right <= Cypress.$(cy.state('window')).width()
        )
        expect(isInViewport).to.be.true
    })