cypresscypress-conditional-testing

Cypress returning Synchronous value within Async command?


So I think this is probably me mixing up sync/async code (Mainly because Cypress has told me so) but I have a function within a page object within Cypress that is searching for customer data. I need to use this data later on in my test case to confirm the values.

Here is my function:

searchCustomer(searchText: string) {
  this.customerInput.type(searchText)
  this.searchButton.click()
  cy.wait('@{AliasedCustomerRequest}').then(intercept => {
    const data = intercept.response.body.data
    console.log('Response Data: \n')
    console.log(data)
    if (data.length > 0) {
      {Click some drop downdowns }
      return data < ----I think here is the problem
    } else {
      {Do other stuff }
    }
  })
}

and in my test case itself:

let customerData = searchAndSelectCustomerIfExist('Joe Schmoe')
//Do some stuff with customerData (Probably fill in some form fields and confirm values)

So You can see what I am trying to do, if we search and find a customer I need to store that data for my test case (so I can then run some cy.validate commands and check if the values exist/etc....)

Cypress basically told me I was wrong via the error message:

cy.then() failed because you are mixing up async and sync code.

In your callback function you invoked 1 or more cy commands but then returned a synchronous value.

Cypress commands are asynchronous and it doesn't make sense to queue cy commands and yet return a synchronous value.

You likely forgot to properly chain the cy commands using another cy.then().

So obviously I am mixing up async/sync code. But since the return was within the .then() I was thinking this would work. But I assume in my test case that doesn't work since the commands run synchronously I assume?


Solution

  • Since you have Cypress commands inside the function, you need to return the chain and use .then() on the returned value.

    Also you need to return something from the else branch that's not going to break the code that uses the method, e.g an empty array.

    searchCustomer(searchText: string): Chainable<any[]> {
    
      this.customerInput.type(searchText)
      this.searchButton.click()
    
      return cy.wait('@{AliasedCustomerRequest}').then(intercept => {
    
        const data = intercept.response.body.data
        console.log('Response Data: \n')
        console.log(data)
        if (data.length) {
          {Click some drop downdowns }
          return data                              
        } else {
          {Do other stuff }
          return []
        }
    
      })
    }
    
    // using 
    searchCustomer('my-customer').then((data: any[]) => {
      if (data.length) {
    
      }
    })
    

    Finally "Click some drop downdowns" is asynchronous code, and you may get headaches calling that inside the search.

    It would be better to do those actions after the result is passed back. That also makes your code cleaner (easier to understand) since searchCustomer() does only that, has no side effects.