how to store multiple elements’ value under one root element in list of array with cypress

For the following result like structure

<div class="results">
  <div class="result">
    <div class="name">x</div>
    <div class="price">99</div>
  </div>
<div class="result">
    <div class="name">y</div>
    <div class="price">88</div>
  </div>
</div>

I want to store in an list of array like this

var resultList = [{"name":"x","amount":"99"}, {"name":"y","amount":"88"}]

And in cypress, I have the following code in cypress but it prints null and I am not sure how to resolve it?

let listOfResults = [];
let singleResult = {};
  cy.get('[class="results"]').each((resultItem) => {
    singleResult = {};

    //Retrive name
    cy.wrap(resultItem)
      .find('div[class$="name"]')
      .invoke("text")
      .then((val) => {
        cy.log("singleResult value for Title:" + val);
        singleResult["title"] = val;
        cy.log("singleResult::" + JSON.stringify(singleResult));//prints correctly
      });

   
    //Retrive price
    cy.wrap(resultItem)
      .find('div[class$="amount"]')
      .invoke("text")
      .then((val) => {
        cy.log("singleResult value for Title:" + val);
        singleResult["amount"] = val;
        cy.log("singleResult::" + JSON.stringify(singleResult));  //prints correctly          
      });
 });

 listOfResults.push(JSON.stringify(singleResult)); //singleResult became blank

 cy.log("list:" + JSON.stringify(listOfResults));

Answer

You must wrap code following your extractor in .then() because Cypress commands are asynchronous.

let listOfResults = [];
let singleResult = {};
cy.get('[class="results"]')
  .each((resultItem) => {
    ...
  })
  .then(() => {
    listOfResults.push(JSON.stringify(singleResult)); 
    cy.log("list:" + JSON.stringify(listOfResults));
  })

When you call cy.log() it always takes it’s value before the test runs, unless it is inside a .then(). So cy.log("list:" + JSON.stringify(listOfResults)) sees the empty listOfResults.

Three more things:

Iterate class=”result”

cy.get('[class="results"]') should be cy.get('[class="result"]') because class="results" is the overall div, but you want to iterate class="result" divs (assuming your cut-down HTML is accurate)

Push inside the .each()

listOfResults.push(JSON.stringify(singleResult)) should be inside the .each() because you are pushing the singleResult of each iteration. But you also need a .then() to do that, to ensure you push after singleResult is populated (same problem as above, async commands).

Don’t stringify

You don’t need to stringify for the logs, just add a comma. That way you keep the object structure for easier/better down-test usage.

const listOfResults = [];
// let singleResult = {};  // don't need this here

cy.get('[class="results"]').each((resultItem) => {

  const singleResult = {};   // declare it fresh each iteration

  //Retrive name
  cy.wrap(resultItem)
    .find('div[class$="name"]')   // also 'div.name'
    .invoke("text")
    .then((val) => {
      cy.log("singleResult value for Title:" + val);
      singleResult["title"] = val;
      cy.log("singleResult::", singleResult);
    });

  //Retrive price
  cy.wrap(resultItem)
    .find('div[class$="amount"]')  // also 'div.amount'
    .invoke("text")
    .then((val) => {
      cy.log("singleResult value for Title:" + val);
      singleResult["amount"] = val;         
      cy.log("singleResult::", singleResult);
    });

  // Now push to list
  cy.then(() => {
    listOfResults.push(singleResult)
  })
})
.then(() => {
  cy.log("list:", listOfResults);
})

Or use an alias

const listOfResults = [];
cy.get('[class="result"]').each((resultItem) => {
  ...
})
.then(() => {
  cy.wrap(listOfResults').as('listOfResults')  // save for later in the test
})

// later on  
cy.get('@listOfResults')
  .then(listOfResults => {
    cy.log("list:", listOfResults) // logs the value of parameter
                                   // not the outer listOfResults
  })

enter image description here