1
votes

I would like to test my "waiting for server reply" UI behavior. How do I do that reliably without pausing my test for a hardcoded delay?

Say, I have a button that triggers an http request and has to show certain animation / actions until the response arrives.

A dumb working method is:

  cy.route({
     delay: 1000,
     response: "blah blah",
  })

  // triggger submission  
  cy.get('#my-submit-button').click()

  // will disappear upon receiving response
  cy.contains('Waiting for response...')

I am pretty much sure that the "waiting" text will appear within a second while the response paused, but then I commit the sin of pausing the test for a whole second.

If I start to shorten or remove the delay then I am running a risk of creating flaky tests since there's a chance the response would be processed before I check for the existence of the "Waiting..." text, which would've been removed by that moment.

Is there a way to ensure the response is produced only after the check for the "Waiting..." text without a hard delay?

I naïvely tried to do a cypress assertion from the route's onResponse, but cypress wasn't happy about that:

  cy.route({
     onResponse: xfr => {
        cy.contains('Waiting for response...')
        return xfr
     },
     response: "blah blah",
  })

  cy.get('#my-submit-button').click()

producing the https://on.cypress.io/returning-promise-and-commands-in-another-command error:

Error: Uncaught CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

The command that returned the promise was:

  > cy.click()

The cy command you invoked inside the promise was:

  > cy.contains()

Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.

Cypress will resolve your command with whatever the final Cypress command yields.

The reason this is an error instead of a warning is because Cypress internally queues commands serially whereas Promises execute as soon as they are invoked. Attempting to reconcile this would prevent Cypress from ever resolving.
3

3 Answers

0
votes

Could this work?

cy.route({
     delay: 1000,
     response: "blah blah",
  }).as('getResponse')

cy.wait('getResponse')

https://docs.cypress.io/guides/guides/network-requests.html#Waiting

0
votes

You could record the time these things happen using cy.moment(), then compare it afterwards using .isBefore() or one of the other moment functions.

I'm using onResponse here to record the time the response comes in.

  let timeDisappeared;
  let timeResponded;

  cy.route({
     delay: 1000,
     response: "blah blah",
     onResponse: () => {
       timeResponded = Cypress.moment()
     }
  })

  // triggger submission  
  cy.get('#my-submit-button').click()

  // waits for the text to appear
  cy.contains('Waiting for response...').should('exist')

  // waits for the text to disappear
  cy.contains('Waiting for response...').should('not.exist').then(() => {
    timeDisappeared = Cypress.moment()
  })

  expect(timeResponded.isBefore(timeDisappeared))
0
votes

After saw your comment and check the other answers

I think Bredan's answer is the best solution to do this. You can

  1. stub a response with delay: x seconds
  2. check the text to appear. Record the time: timeAppear
  3. check the text to disappear. Record the time: timeDisappear
  4. Check if timeDisappear - timeAppear > x seconds
    • or Check timeDisappear - timeAppear - x seconds > certain seconds(You can define the tolerance) This proves the text shows in the given response time.

You can extend the response Delay to longer value.

I modified Bredan's answer a bit to reflect the steps above,

 let timeAppear;
 let timeDisappear;

 //delay the response to come back after 20 seconds 
  cy.route({
     delay: 20000,
     response: "blah blah"
  })

  // triggger submission  
  cy.get('#my-submit-button').click()

  // waits for the text to appear and record the time starts. 

  cy.contains('Waiting for response...').should('exist').then(() => {
    timeAppear = Cypress.moment()
  })

  // waits for the text to disappear and record the time ends
  // add timeouts option here to make sure it is longer than delay
  cy.contains('Waiting for response...',{timeout:30000}).should('not.exist').then(() => {
    timeDisappear = Cypress.moment();
    //check the time is above the delay time 2000 ms
    expect(timeDisappear - timeAppear).to.be.above(20000);
    //or check the tolerance value e.g.5000 ms
    expect(timeDisappear - timeAppear - 20000).to.be.below(5000);
  })