7

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?

0

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Im a bit confused on your last statement. Do you have an example? IE: Are you saying I should do the dropdown clicking inside my test case (and outside of the function). I think im not used to returning a function either (That is a closure right?). Im still rusty on JS
Basically, yes. I was thinking of the Single Responsibility Principle. What exactly is a Single Responsibility is open to interpretation, you may decide clicking dropdowns is actually part of searchCustomer. On a practical side, if you separate the querying part (getting the data) from the action part (clicking the dropdown) it's easier to test your test code because searchCustomer has no side effects, it just searches and returns the data.
The part about returning a function - it's asynchronous code so as an analogy it's like connecting up pipes. The data isn't flowing yet when you return from searchCustomer, so you return the end of the "pipe" and in the test connect it to the code inside the .then().
Would I still need the return data within? And the returning a function thing works because it's a "promise" like function right? (Within Cypress I mean)
Yes you always need to return something, either return data or return [] in the else part if data is undefined. And yes, returning the cy.wait() command is promise-like - so you are returning an object that "promises" to give you a value in the future.
|
1

you just need to add return before the cy.wait

here's a bare-bones example

it("test", () => {
  function searchCustomer() {
    return cy.wait(100).then(intercept => {
      const data = {text: "my data"}
      return data
    })
  }

  const myCustomer = searchCustomer()
  myCustomer.should("have.key", "text")
  myCustomer.its("text").should("eq", "my data")
});

8 Comments

This is actually what I tried originally but I still received an undefined value when trying to assign the return value of the function.
I didn't notice that the wait didn't have a return. The cy.wrap is not needed, just the return. Tested with the example and I think it does what you're looking for.
So do we need the return within the cy.wait as well? I guess im confused since we have a return on the function and within the cy.wait as wel
Also do you mind explaining why/what the return before cy.wait does/is? Im not used to seeing it.
you have two (nested) functions and both need to return something for you to be able to use a the returned value. cy.wait is asynchronous and will allow you to chain with then or should etc... Then you need to return the actual data, which the other return handles. To understand better why the return cy.wait is needed you can look into how promises and async/await works which is analogous. cypress uses a Chainer which is like a Promise
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.