Search code examples
vuejs3cypresse2e-testingpinia

Cypress using actions from Pinia Vue3


I was learning some cypress from this video: https://www.youtube.com/watch?v=03kG2rdJYtc I'm interested with he's saying at 29:33: "programatic login" But he's using vue2 and Vuex.

My project is created with Vite and the state management is Pinia. So how can I do a programatic login using the pinia action?

For example the welcome logged in user should see dashboard:

describe('Welcome', () => {
  it('logged in user should visit dashboard', () => {
    // login
    cy.visit('/')
    cy.url().should('contain', '/dashboard')
  })
})

And my userStore:

export const useUserStore = defineStore({
  id: 'user',
  state: () => ({
    username: ref(useLocalStorage('username', null)),
  }),
  getters: {
    isLoggedIn: (state) => state.username !== null,
  },
  actions: {
    login(username, password) {
      return useAuthLoginService(username, password)
        .then((response) => {
          this.username = response.username
        })
        .catch((error) => {
          return Promise.reject(new Error(error))
        })
    },
  },
})

How can I call the login action on the cypress test? For now as a workaround I'm writing on a localstorage like:

localStorage.setItem('username', 'user')

And it works fine, because userStore catch this item from localstorage and passes like it's logged in... But I don't like this solution, seems fragile, and I'd like to use the action which is made for login users.

Another thing I tried is adding the app variable inside window but it doesn't work for me... don't understand why...

on main.js

The video shows that code:

const vue = new Vue({...})
if(window.Cypress){
  window.app = app
}

In my case it's:

const app = createApp(App)
if(window.Cypress){
  window.app = app
}

But in cypress tests the window.app it's undefined... I don't know how I would access to userStore using this... like it was vuex.


Solution

  • Using the Pinia demo app as an example:

    The store is initialized in App.vue. Add a reference to the newly created store(s) for Cypress to use

    export default defineComponent({
      components: { Layout, PiniaLogo },
    
      setup() {
        const user = useUserStore()
        const cart = useCartStore()
     
        if (window.Cypress) {
          window.store = {user, cart)   // test can see window.store
        }
        ...
    

    In the test

    let store;  
    
    describe('Pinia demo with counters', () => {
    
      beforeEach(() => {
        cy.viewport(1000, 1000)
        cy.visit(`http://localhost:${PORT}`)
          .then(win => store = win.store)       // get app's store object 
      })
    
      it('works', () => {
        cy.wait(500) // wait for the JS to load
          .then(() => store.cart.addItem('Cypress test item'))  // invoke action
          .then(() => {
            const item1 = store.cart.items[0]                   // invoke getter
            cy.wrap(item1)
              .should('have.property', 'name', 'Cypress test item')  // passes
          })
    

    The login action is asynchronous, so return the promise to allow Cypress to wait.

    // user.js
    
    async login(user, password) {
      const userData = await apiLogin(user, password)
    
      this.$patch({
        name: user,
        ...userData,
      })
      return userData        // this returns a promise which can awaited
    },
    
    // main.spec.js
    
    describe('Pinia demo with counters', () => {
      beforeEach(() => {
        cy.viewport(1000, 1000)
        cy.visit(`http://localhost:${PORT}`).then(win => {
          store = win.store
    
          // default name in store before login
          cy.wrap(store.user.name).should('eq', 'Eduardo')
    
          // logging in
          store.user.login('ed', 'ed').then(() => {        // wait for API call
            cy.wrap(store.user.name).should('eq', 'ed')
          })
        })
      })
    

    Alternatively, wait for the name to change on the page

    // main.spec.js
    
    cy.visit(`http://localhost:${PORT}`).then(win => {
      store = win.store
    
      // default name in store
      cy.wrap(store.user.name).should('eq', 'Eduardo')
    
      // logging on
      store.user.login('ed', 'ed')  
      cy.contains('Hello ed')              // waits for name on page to change
        .then(() => {          
          cy.wrap(store.user.name).should('eq', 'ed')
        })
    })