Search code examples
javascriptautomated-testscypresse2e-testing

Handling multiple API calls of a single API call


With my team we are trying to implement a command for a really common operation for the business logic but I'm having issues handling its implementation. Basically:

  1. We have to retrieve an array of objects (GET).

  2. For each of that objects we have to retrieve (GET) another object inside its parent.

  3. For each of that sub-objects (childs) we have to check a condition and if it is the wanted condition we retrieve the child, otherwise we pass null.

Q: How do I handle multiple API calls that depends from a single API call without getting outside the CY chain?

This is my current implementation (doesn't works but kinda explains the wanted logic)

Cypress.Commands.add('myCommand', (sumCriteria: Function, anotherCriteria: Function) => {
    // I only retrieve parents with certain criteria
    return cy.request('GET', parentsUrl).its('body').then(parentObjects => {
        return parentObjects.filter(parent => parent.childs.length && parent.childs.find(sumCriteria))
    }).then(filteredParents => {
        filteredParents.forEach(parent => {
            // For each parent I retrieve a single child
            const targetChildId = parent.childs.find(sumCriteria).id;
            // For each single child I retrieve its data and evaluate if it has the needed criteria
            cy.request('GET', `${childsUrl}/${targetChildId}`)
                .its('body')
                .then(property => anotherCriteria(property))
        })
    });
})

Thanks in advance!


Solution

  • You almost have the correct pattern, but instead of returning results, put them on the queue.

    Cypress does two things to make this work

    • in a custom command, it waits for any asynchronous commands to resolve
    • it returns whatever is on the queue at the last evaluation
    Cypress.Commands.add('myCommand', (sumCriteria, anotherCriteria) => {
    
      cy.request('GET', fathersUrl)
        .its('body')
        .then(fatherObjects => {
    
          const filteredFathers  = fatherObjects.filter(father => {
            return father.childs.find(sumCriteria)
          });
    
          const results = []
          filteredFathers.forEach(father => { 
            cy.request('GET', father)              // waits for all these to resove
              .its('body')
              .then(property => anotherCriteria(property))   
          })
    
          cy.then(() => results)                   // returns this last queued command
      })
    })
    

    A reproducible example:

    Cypress.Commands.add('myCommand', (sumCriteria, anotherCriteria) => {
    
      const fathersUrl = 'https://jsonplaceholder.typicode.com/todos/1'
    
      cy.request('GET', fathersUrl)
        .then(() => {
    
          // simulated url extraction
          const filteredFathers = [
            'https://jsonplaceholder.typicode.com/todos/2', 
            'https://jsonplaceholder.typicode.com/todos/3'
          ]
    
          const results = []
          filteredFathers.forEach(father => { 
            cy.request('GET', father)
              .then(res => {
                results.push(res.body.id)
              })
          });
    
          cy.then(() => results)
      });
    })
    
    cy.myCommand()
      .should('deep.eq', [2,3])             // ✅ passes