Search code examples
reactjscypressjotai

Set State Management (Jotai) Before Execution of Cypress E2E Tests


My React application contains no routing and works in a very step-oriented fashion. In order to get to step 2, the user has to fill out step 1. And in order to get to step 6, the user has to have gone through steps 1 - 5. So with each step, the execution time becomes longer and longer in Cypress. I have to have the user go through all the steps before it on each step's tests.

State management of the app is controlled by Jotai. And that is what determines which page is currently visible. I could theoretically populate the Jotai state management before each test to avoid having to navigate through the whole user flow and all the steps. I could set the current page to "step-6" and populate all other necessary parts of the state management.

But I can’t figure out how to do this in Cypress E2E and I’m not sure if it’s possible. I’m also unsure if this is even something that should be done in E2E tests or if it would be better described as component testing. Any advice is appreciated.


Solution

  • To invoke internal state setters from a test you need to expose it on the window object.

    Take the Joita Hacker News example app

    It starts by displaying post id 9001, and increments the id using the button at bottom-right.

    enter image description here

    This is the code of the button click handler, it uses the Jotai hook to create the setter.

    function Next() {
      // const [, set] = useAtom(postId)
      const setPostId = useSetAtom(postId)
      return (
        <button onClick={() => setPostId((id) => id + 1)}>
          <div>→</div>
        </button>
      )
    }
    

    I could test that in Cypress by repeating the button click 100 times, which takes about 15 seconds to run the test

    cy.visit('http://localhost:3000/');
    cy.get('h1').should('contain', '9001');   // confirm the initial header 
    
    Cypress._.times(100, () => {
      cy.get('button').click()
    })
    
    cy.get('h1').should('contain', '9101');   // confirm the final header 
    

    Exposing the setPostId() function

    Or I can set the state directly if I expose the setter on the window object

    function Next() {
      // const [, set] = useAtom(postId)
      const setPostId = useSetAtom(postId)
    
      // state setter is exposed - but only during Cypress testing
      if (window.Cypress) {
        window.setPostId = setPostId
      }
    
      return (
        <button onClick={() => setPostId((id) => id + 1)}>
          <div>→</div>
        </button>
      )
    }
    

    Then the test can jump directly to the state required, which takes about 900ms to run the test.

    cy.visit('http://localhost:3000/');
    cy.get('h1').should('contain', '9001')
    
    cy.window().then(win => win.setPostId(9101))
    cy.get('h1').should('contain', '9101')
    

    enter image description here