How to capture the 'latest' instance of a request if it is called multiple times during a Cypress test?

1.4k Views Asked by At

In my Cypress Cucumber test, here is my current feature file scenario (this scenario fails on the final step):

Given I log in
Then I am on the dashboard
And the dashboard displays 0 peers (@Peers is called here, & 0 is the correct value here)
When I go to User 1 page
And I click the Add Peer button (@AddPeer request is successful here)
And I go to the dashboard
Then the dashboard displays 1 peers (@Peers is called here. The actual value is 0, but I am expecting 1. It looks like the code is using the response body from the 1st `Peers` intercept)

Here are my step definitions:

Given('I log in', () => {
    cy.intercept('GET', `**/user/peers`).as('Peers')
    cy.intercept('POST', `**/Peers/add`).as('AddPeer')
});

Then('I am on the dashboard', () => {
    cy.url().should('include', `dashboard`)
})

Then('the dashboard displays {int} peers', expectedPeersCount => {
    cy.wait('@Peers').then(xhr => {
        const peers = xhr.response.body
        expect(peers.length).to.eq(expectedPeersCount)
    });
});

When('I click the Add Peer button', () => {
    dashboard.btnAddPeer().click()
    cy.wait('@AddPeer').then(xhr => {
        expect(xhr.response.statusCode).to.eq(200)
    })
})

When('I go to the dashboard', () => {
    cy.visit('/dashboard');
});

In the backend, @AddPeers() adds a peer to a list, while @Peers() returns a list of my peers.

When I go to the dashboard, @Peers() returns the latest list of peers.

But for some reason, the above code is still using the 'old' response that has an empty response body.

Can someone please point out how I can get the 'latest' @Peers response?

Here is the 1st Peers response that is empty:

first empty response

And here is the 2nd Peers response that contains 1 array item:

2nd response that contains one array item

Attempted fix:

Given('I log in', () => {
    cy.intercept('GET', `**/user/peers`).as('Peers0')
    cy.intercept('GET', `**/user/peers`).as('Peers1')
});

Then('the dashboard displays {int} peers', expectedPeersCount => {
    cy.wait(`@Peers${expectedPeersCount }`).then(xhr => {
        const peers = xhr.response.body
        expect(peers.length).to.eq(expectedPeersCount)
    });
});

Cypress logs:

enter image description here

enter image description here

enter image description here

Peers1 looks like it's empty below, but it shouldnt' be:

enter image description here

And then it looks below like Peers0 has the populated array. Note the Matched cy.intercepts()

enter image description here

2

There are 2 best solutions below

4
On

A bit of misunderstanding here. cy.wait() for an alias intercept will only wait for the first requesting matching the given params.

cy.intercept('request').as('call')
// some actions to trigger request
cy.wait('@call')
// later in the test trigger same request

// this will refer to the first request made
// by the app earlier in the test
cy.wait('@call')

You can either make another intercept with a unique alias and then use .wait() on the new unique alias.

cy.intercept('request').as('call')
// some actions to trigger request
cy.wait('@call')
// later in the test trigger same request

cy.intercept('request').as('newCall')
// some actions to trigger same request
cy.wait('@newCall')

or you can use your same approach and use cy.get() to get the list of matching requests, however, this may be a good choice as cy.get() will not wait for your new request to complete.

cy.intercept('request').as('call')
// some actions to trigger request
cy.wait('@call')
// later in the test trigger same request

// have to ensure the request is completed by this point
// to get the list of all matching and hopefully
// the last request will be the one you seek
cy.get('@call.all')
  // get last matching request
  .invoke('at', -1)
0
On

I can't see why the original code doesn't work, it looks perfectly good (without a running system to play with).

But the "fix" variation is backwards - the last intercept that is set up is the first to match.

From the docs: enter image description here

This diagram shows route5 being the first (non-middleware) route checked, followed by route3, then route1.


Also, since each intercept is intended to catch only one call, add { times: 1 }, to make it so.

Given('I log in', () => {
  cy.intercept('GET', `**/user/peers`, {times: 1})
    .as('Peers1')                                     // catches 2nd call
  cy.intercept('GET', `**/user/peers`, {times: 1})
    .as('Peers0')                                     // catches 1st call
})

You can see it in this screenshot

enter image description here

where Peers1 is matched first then Peers0, but the following block is expecting the reverse order

Then('the dashboard displays {int} peers', expectedPeersCount => {
  cy.wait(`@Peers${expectedPeersCount }`).then(xhr => {
      const peers = xhr.response.body
      expect(peers.length).to.eq(expectedPeersCount)
  });
})

Called with 0 by And the dashboard displays 0 peers and then called with 1 by Then the dashboard displays 1 peers


Dynamic aliases

You might be able to set the alias dynamically depending on how many peers are in the response.

Given('I log in', () => {
  cy.intercept('GET', `**/user/peers`, (req) => {
    req.continue().then(res => {
      if (re.body.length === 0) {
        req.alias = 'Peers0'
      }
      if (re.body.length === 1) {
        req.alias = 'Peers1'
      }
    })
  })
})

If this works, then you don't need to worry about the order of setup.