/ #cypress #testing #javascript 

Cypress Scroll Position Assertions

This post goes through approaches to asserting on the scroll position.

First we’ll see how to assert that we’re at the top of the page.

Next we’ll look at 2 approaches to assert that we’ve scrolled to an element.

The examples for this post are available at github.com/HugoDF/cypress-scroll-position-assertions/.

Table of Contents

Cypress read and assert window scroll position/offset

Cypress exposes window using cy.window() which is a chainable Cypress wrapper. We can then use cy.window().its('scrollY') to unpack the value we want to assert against. Finally we chain the assertion .should() to assert against the window’s scrollY. For example to check that we’ve scrolled, we can write: cy.window().its('scrollY').should('not.equal', 0).

A setup for a test could look like the following, we us cy.visit('/') to load “/”, cy.scrollTo('bottom') to trigger a scroll to the bottom of the page, and cy.window().its('scrollY').should('not.equal', 0) to check that we’ve started scrolling.

beforeEach(() => {
  cy.visit('/')
    .scrollTo('bottom')
    .window()
    .its('scrollY')
    .should('not.equal', 0);
});

Cypress assert scroll to top of the page

Given the following application code which listens to clicks on the “Scroll to Top” button and smooth-scrolls to the top of the page (top: 0).

<div class="demos">
  <div class="spacer">Spacer 1</div>
  <div class="spacer">Spacer 2</div>
  <div class="spacer">Spacer 3</div>
  <div data-testid="scroll-target" data-js="scroll-target">Scroll Target</div>
  <div class="spacer">Spacer 4</div>
  <div class="spacer">Spacer 5</div>
  <div class="controls">
    <button data-testid="scroll-to-top" data-js="scroll-to-top">
      Scroll To Top
    </button>
    <button data-testid="scroll-to-element" data-js="scroll-to-element">
      Scroll To Element
    </button>
  </div>
</div>
<script>
  document.querySelector('[data-js=scroll-to-top]').addEventListener('click', () => {
    window.scroll({ top: 0, behavior: 'smooth' });
  });
</script>

To check that clicking “Scroll to Top” does what’s expected, we can use cy.click('[data-testid=js-scroll-to-top]') to trigger the click. We can follow that up with an assertion against cy.window().its('scrollY'). For that assertion to check that the scrollY is 0, in other words, the scroll position is at the top of the page, we use .should('equal', 0).

We can check that on click of the “Scroll to Top” yield the correct output as follows.

beforeEach(() => {
  cy.visit('/');
  cy.scrollTo('bottom').window().its('scrollY').should('not.equal', 0);
});

it('scrolls to top', () => {
  cy.get('[data-testid=scroll-to-top]').click();

  cy.window().its('scrollY').should('equal', 0);
});

Running the above test in the Cypress UI yields the following result.

Cypress UI with “Scroll to Top” test passing with the scrollY assertion

Cypress assert scrolled to an element

Given the following application code which listens to clicks on the “Scroll to Target” button and smooth-scrolls to “Scroll Target” element.

<div class="demos">
  <div class="spacer">Spacer 1</div>
  <div class="spacer">Spacer 2</div>
  <div class="spacer">Spacer 3</div>
  <div data-testid="scroll-target" data-js="scroll-target">Scroll Target</div>
  <div class="spacer">Spacer 4</div>
  <div class="spacer">Spacer 5</div>
  <div class="controls">
    <button data-testid="scroll-to-top" data-js="scroll-to-top">
      Scroll To Top
    </button>
    <button data-testid="scroll-to-element" data-js="scroll-to-element">
      Scroll To Element
    </button>
  </div>
</div>
<script>
  // handler for the "scroll-to-top"
  document.querySelector('[data-js=scroll-to-element]').addEventListener('click', () => {
    document.querySelector('[data-js=scroll-target]').scrollIntoView({
      behavior: 'smooth'
    });
  });
</script>

In order to test that scrolling to an element works, we need to assert that cy.window().its('scrollY') is equal to the offset of the scroll target.

There are 2 approaches to do this.

The first is to leverage cy.$$() to get a jQuery wrapper. Once we’ve got the wrapper, we can use jQuery’s offset() function and read the top property of it. The whole expression looks like cy.$$().offset().top, in the case of this test cy.$$('[data-testid=scroll-target]').offset().top.

The full assertion then reads cy.window().its('scrollY').should('equal', cy.$$('[data-testid=scroll-target]').offset().top). In other words, the window scroll position equals the offset top of the “Scroll Target” element.

beforeEach(() => {
  cy.visit('/');
  cy.scrollTo('bottom').window().its('scrollY').should('not.equal', 0);
});

// tests from previous section

it('scrolls to target - cy.$$ + jQuery.offset()', () => {
  cy.get('[data-testid=scroll-to-element]').click();

  // Using cy.$$ (jQuery) + offset().top
  cy.window()
    .its('scrollY')
    .should('equal', cy.$$('[data-testid=scroll-target]').offset().top);
});

The altertive is to use cy.get() (in our case cy.get('[data-testid=scroll-target]')), which returns a Cypress wrapper. Now we can’t access jQuery’s offset or Element’s offsetTop on the Cypress wrapper. We need to .then() the selection to access the element.

In our .then, per cy.get().then(element => {}) we can unwrap the jQuery wrapper using element.get(0) or element[0].

The full .then is therefore cy.get('[data-testid=scroll-target]').then((element) => element[0].offsetTop).

We can chain another .then to this initial one, which takes the offset from the previous then.

Finally we can use cy.window().its('scrollY') and assert that the scrollY equals the offset from the element in the previous step.

In full:

beforeEach(() => {
  cy.visit('/');
  cy.scrollTo('bottom').window().its('scrollY').should('not.equal', 0);
});

// tests from previous section

it('scrolls to target - cy.get + unwrap jQuery reference', () => {
  cy.get('[data-testid=scroll-to-element]').click();

  // Using cy.get() + unwrapping the jQuery reference with `el[0]` or `el.get(0)`
  cy.get('[data-testid=scroll-target]')
    .then((element) => element[0].offsetTop)
    .then((offset) => cy.window().its('scrollY').should('equal', offset));
});

Running the above tests in the Cypress UI gives the following output:

Cypress UI with “Scroll to Target” tests passing with both approaches to the scrollY assertion

We’ve now seen how to read the window’s scroll position and assert against it using .should()

We’ve then seen how to check that the window is scrolled to the top and 2 approaches to check that we’ve scrolled to an element.

All the code examples for this post are available at github.com/HugoDF/cypress-scroll-position-assertions/.

Photo by Mike Lewis HeadSmart Media on Unsplash.

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Get The Jest Handbook (100 pages)

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.