APK
APK

Reputation: 23

Cypress: Async function using for loop giving same results

I'm new to cypress framework and trying to achieve the below functionality using cypress.

I have a page with table rows and a dropdown menu on page header. On selecting the option, the dropdown menu gets closed and the body content gets changed/loaded up according to the selected menu options.

Problem: Getting the same length for the table rows for all the menu options selected sequentially, although the table rows count is different for the options.

Here is my code:

it.only('Validate table Row changed length on menu option selection', {defaultCommandTimeout: 10000}, () => {
  // opening the dropdown menu
  page.openDropdownMenu();
  // getting the dropdown options and calculating the length
  cy.get('dropdownOptions').then($options => {
    // calculating the length
    const menuOptionCount = $options.length;
    // closing the dropdown menu
    page.closeDropdownMenu();
    for (let i = 0; i < menuOptionCount; i++) {
      // opening the dropdown menu
      page.openDropdownMenu();
      // clicking the menu option
      $options[i].click();
      // closing the dropdown menu
      page.closeDropdownMenu();
      cy.get("body").then($body => {
        // always getting the same length for the table rows for all selected options
        const rowsLength = $body.find('.table.rows').length;
        cy.log('****************Rows length************', rowsLength);
      });
    }
  });
});

Is there any way to write the asynchronous statement to synchronous like (await async in promises) without using any external utility in cypress. As in my previous assignment using Protractor the same thing could be handled using async await as below.

const elementCount = await element(
  by.css('[title="Locked By"] .med-filter-header-button div')
).count();

Upvotes: 2

Views: 468

Answers (2)

Fody
Fody

Reputation: 31882

After click() the app rewrites the table, but Cypress does not know that happens and gets the row count before the change occurs.

TLDR - You need to give Cypress more information test correctly. Generally, your test data should be known (not "discovered" by the test code).


Problem #1

You need some way to wait for the row change to finish. Either some text element changes (maybe the first row text), or by adding a .should() on the actual row count.

Something like

const expectedRowCount = [5, 4, 3, 2]

cy.get('dropdownOptions').each(($option, index) => {

  page.openDropdownMenu()
  $option.click()
  page.closeDropdownMenu()

  cy.get('.table.rows')
    .should('have.length', expectedRowCount[index])  // this will retry until rowsLength changes
    .then(rowsLength => {
      cy.log('****************Rows length************', rowsLength)
    })
})

Problem #2

If "the body content gets changed/loaded" means that the dropdown also gets rewritten with every click, then the loop will fail because $options gets refreshed each time.

You might use the expectedRowCount to loop instead

const expectedRowCount = [5, 4, 3, 2]

expectedRowCount.forEach((expectedCount, index) => {

  page.openDropdownMenu()
  cy.get('dropdownOptions').eq(index).click()
  page.closeDropdownMenu()

  cy.get('.table.rows')
    .should('have.length', expectedCount)  // retries until rowsLength changes
    .then(rowsLength => {
      cy.log('****************Rows length************', rowsLength)
    })
})

The above strategies do not really give you the most solid test.

If you can, check some text that changes upon each iteration,

page.openDropdownMenu()
cy.get('dropdownOptions').then($options => {

  let firstRowText = '';   // to control the loop, waiting for this to change

  const menuOptionCount = $options.length;
  page.closeDropdownMenu();
  for (let i = 0; i < menuOptionCount; i++) {
  
    page.openDropdownMenu();
    cy.get('dropdownOptions').eq(i).click();  // fresh query each time through the loop

    page.closeDropdownMenu();

    cy.get('.table.rows').first().invoke('text')
      .should('not.eq', firstRowText);            // retry until text has changed
      .then(newText => firstRowText = newText);   // save for next loop

    cy.get('.table.rows').then($rows => {
      const rowsLength = $rows.length;
      cy.log('****************Rows length************', rowsLength);
    });
  }
})

Upvotes: 2

Alapan Das
Alapan Das

Reputation: 18650

You can condense your code to something like this. Instead of using a for loop, use each which is a cypress inbuilt method for looping.

it.only(
  'Validate table Row changed length on menu option selection',
  {defaultCommandTimeout: 10000},
  () => {
    page.openDropdownMenu()
    cy.get('dropdownOptions').each(($options, index) => {
      cy.wrap($options).eq(index).click()
      page.closeDropdownMenu()
      cy.get('.table.rows')
        .its('length')
        .then((rowsLength) => {
          cy.log('****************Rows length************', rowsLength)
        })
      page.openDropdownMenu()
    })
    page.closeDropdownMenu()
  }
)

Upvotes: 0

Related Questions