Skip to content

Instantly share code, notes, and snippets.

@stevekinney
Created December 8, 2021 12:49
Show Gist options
  • Save stevekinney/9fc5c5f69006722739546751df026ceb to your computer and use it in GitHub Desktop.
Save stevekinney/9fc5c5f69006722739546751df026ceb to your computer and use it in GitHub Desktop.

SSR solves a lot of problems for our users, but can be tricky for our tests. Cypress waits until the window fires its load event, but when we're using SSR, our JavaScript might show up after our page officially loads.

For example, this test will fail:

describe.only('Ratings Filter with SSR', () => {
  beforeEach(() => {
    cy.visit('/secret-menu');
    cy.get('#minimum-rating-visibility').as('rating-filter');
  });

  it('should only show items with a popularity rating of 7 or higher', () => {
      cy.get('@rating-filter').invoke('val', 7).trigger('change');
      cy.get('td[headers="popularity-column"]').each(($el) => {
        expect(+$el.text()).to.be.gte(7);
      });
    });
  });
});

Here is an incomplete list of things you shouldn't do:

  • Abandon SSR just for the sake of your tests.
  • Abandon testing your application.
  • Have your Cypress wait an arbitrary amount of time before running your tests just so you can be sure that it's hydrated.

The answer is that we need to wait for whatever event our framework of choice fires before moving forward with our test. In my case, this application happens to be using SvelteKit, but it doesn't really matter.

describe.only('Ratings Filter with SSR', () => {
  beforeEach(() => {
    cy.visit('/secret-menu');
    cy.get('#minimum-rating-visibility').as('rating-filter');
  });

  it('should only show items with a popularity rating of 7 or higher', () => {
    cy.wrap(
      new Promise((resolve) => {
        cy.window().then((win) => {
          win.addEventListener('sveltekit:start', resolve);
        });
      })
    ).then(() => {
      cy.get('@rating-filter').invoke('val', 7).trigger('change');
      cy.get('td[headers="popularity-column"]').each(($el) => {
        expect(+$el.text()).to.be.gte(7);
      });
    });
  });
});

This is a little verbose, can we pull it out into a command?

Cypress.Commands.add('waitForApp', () => {
  return cy.wrap(
    new Promise((resolve) => {
      cy.window().then((win) => {
        win.addEventListener('sveltekit:start', resolve);
      });
    })
  );
});

Then we can do something like this:

it('should only show items with a popularity rating of 7 or higher', () => {
  cy.waitForApp().then(() => {
    cy.get('@rating-filter').invoke('val', 7).trigger('change');
  });
});

Even better, I can wait for the app to hydrate before each test.

describe.only('Ratings Filter with SSR', () => {
  beforeEach(() => {
    cy.visit('/secret-menu');
    cy.get('#minimum-rating-visibility').as('rating-filter');
    return cy.waitForApp();
  });

  it('should only show items with a popularity rating of 7 or higher', () => {
    cy.get('@rating-filter').invoke('val', 7).trigger('change');
    cy.get('td[headers="popularity-column"]').each(($el) => {
      expect(+$el.text()).to.be.gte(7);
    });
  });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment