26

Is it possible to use cy.intercept to intercept the same API call multiple times in the same test? I tried the following:

cy.intercept({ pathname: "/url", method: "POST" }).as("call1")
// ... some logic
cy.wait("@call1")

// ... some logic

cy.intercept({ pathname: "/url", method: "POST" }).as("call2")
// ... some logic
cy.wait("@call2")

I would expect that cy.wait("@call2") would wait for the 2nd time the API gets called. However, the second cy.wait will continue immediately because the first API call is identical to the second one.

Liran H
  • 9,143
  • 7
  • 39
  • 52
Bob van 't Padje
  • 685
  • 1
  • 6
  • 17

4 Answers4

28

Updated for Cypress v7.0.0 Released 04/05/2021

The change-log shows from this release the intercepts are now called in reverse order

Response handlers (supplied via event handlers or via req.continue(cb)) supplied to cy.intercept() will be called in reverse order until res.send is called or until there are no more response handlers.

Also illustrated in this diagram from the documentation

enter image description here


When you set up identical intercepts, the first one will grab all the calls. But you can wait on the first alias multiple times.

Here's a (reasonably) simple illustration

spec

Cypress.config('defaultCommandTimeout', 10); // low timeout
                                             // makes the gets depend on the waits 
                                             // for success

it('never fires @call2',()=>{

  cy.intercept({ pathname: "/posts", method: "POST" }).as("call1")
  cy.intercept({ pathname: "/posts", method: "POST" }).as("call2")

  cy.visit('../app/intercept-identical.html')
  
  cy.wait('@call1')                               // call1 fires 
  cy.get('div#1').should('have.text', '201')

  cy.wait('@call2')                               // call2 never fires
  cy.wait('@call1')                               // call1 fires a second time
  cy.get('div#2').should('have.text', '201')

})

app

<body>
  <div id="1"></div>
  <div id="2"></div>
  <script>

    setTimeout(() => {
      fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        body: JSON.stringify({ title: 'foo', body: 'bar', userId: 1 }),
        headers: { 'Content-type': 'application/json; charset=UTF-8' },
      }).then(response => {
        document.getElementById('1').innerText = response.status;
      })
    }, 500)
  
    setTimeout(() => {
      fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'POST',
        body: JSON.stringify({ title: 'foo', body: 'bar', userId: 2 }),
        headers: { 'Content-type': 'application/json; charset=UTF-8' },
      }).then(response => {
        document.getElementById('2').innerText = response.status;
      })
    }, 1000)

  </script>
</body>

You can see it in the Cypress log,

command call# occurrence (orange tag)
wait @call1 1
wait @call2
wait @call1 2
Paolo
  • 3,530
  • 7
  • 21
  • This does seem to work. However, I feel like `cy.wait` is not actually waiting until the request is finished, but will proceed immediately. This also seems to happen when just using one `cy.wait`. – Bob van 't Padje Mar 24 '21 at 13:08
  • 1
    It's easy to disprove your theory - just comment out `cy.wait` and see it fail. If it were not actually waiting, it would still pass. This is why I added `Cypress.config('defaultCommandTimeout', 10)`, when building a test always make it fail first (red-green-refactor). –  Mar 24 '21 at 19:49
  • That's actually a really good tip. Setting the timeout to such a low number will enforce waiting for api calls. Thanks :) – Bob van 't Padje Mar 25 '21 at 12:02
  • 1
    How do I get call2 to fire? I need to modify the response of the same request to simulate data changing from the endpoint. – Kelsey Hannan Apr 14 '22 at 20:49
5
// wait for 2 calls to complete
cy.wait('@queryGridInput').wait('@queryGridInput')
// get
cy.get("@queryGridInput.all").then((xhrs)=>{});

@alias.all will wait for all the instances to be completed. It will return an array of xhrs containing all the matching @alias.

https://www.cypress.io/blog/2019/12/23/asserting-network-calls-from-cypress-tests/

Cypress: I am trying to intercept the 10 calls which originate from 1 button click but the cy.wait().should is only tapping the last call

user3847870
  • 368
  • 1
  • 6
  • 21
3

You have many ways to do this:

  1. Create a unique alias for the same endpoint intercepted

    cy.intercept({ pathname: "/posts", method: "POST" }).as("call")
    
    //First Action
    cy.get("button").click()
    cy.wait("@call").its("request.url").should("contain", "somevalue")
    
    //Second Action
    cy.get("button2").click()
    cy.wait("@call").its("request.url").should("contain", "othervalue")
    
  2. Create a specific en endpoint, you can use a glob pattern to generate a dynamic endpoint

    //notice path key instead of pathname
    cy.intercept({path: "/post*parameter1=true", method: "POST"}).as("call1")
    
    //First Action
    cy.get("button").click()
    cy.wait("@call1").its("request.url").should("contain", "somevalue")
    
    cy.intercept({path: "/post*parameter2=false", method: "POST"}).as("call2")
    
    //Second Action
    cy.get("button2").click()
    cy.wait("@call2").its("request.url").should("contain", "othervalue")
    
  3. Validate all endpoints called at the finish

     cy.intercept({ pathname: "/posts", method: "POST" }).as("call")
    
     //First Action
     cy.get("button").click()
     cy.wait("@call")
    
     //Second Action
     cy.get("button2").click()
     cy.wait("@call")
    
     //you can add the number of request at the finish of alias
     cy.get("@call.1").its("request.url").should("contain", "somevalue")
     cy.get("@call.2").its("request.url").should("contain", "othervalue")
    
     //another option instead of add the number of request maybe use the position in letters, but I think that it only works for first and last.
     cy.get("@call.first").its("request.url").should("contain", "somevalue")
     cy.get("@call.last").its("request.url").should("contain", "othervalue")
    
user199960
  • 35
  • 3
  • In #2 you don't need a fake parameter. Cypress now allows overrides, last intercept defined is the first matched. – Fody Oct 07 '21 at 00:04
  • Is not a fake parameter, is only to be more specific the match with the endpoint if you have query parameters, is not necessary to use, but it is very useful in many cases. – user199960 Oct 07 '21 at 03:05
  • 1
    The point is, you don't need it - the same URL can be assigned a second time. – Fody Oct 07 '21 at 07:59
  • 1
    can you provide some docs/links to some of these? for example [at]call.1 etc ? for me adding .1 or .2 doesn't seem to work. cy.wait() could not find a registered alias for: [at]call.1 when it was registered as "call". Same for .first etc, where did you get this from? – trainoasis Feb 09 '23 at 08:24
2

I would have used aliasing on individual requests if your request differ in some way. In my case I made multiple post request to the same route but with a slightly different body. So I ended up doing something like this:

cy.intercept("POST", '/your_route', (req) => {
    if (req.body.hasOwnProperty('your_prop')) {
        req.alias = 'your_alias';
        req.reply(your_response);
    }
});

This will intercept and stub a post request that has a desired property in the body.