reactjstypescriptcypresssingle-page-applicationcypress-each

Proper way to repeat test logic in Cypress with dynamic DOM re-rendering


I am having a problem with testing my app using Cypress. Suppose I have a table with items, and I need to test that its possible to delete all the items from the table. It's rendered using React and re-rendered after some item being deleted. DOM looks something like this:

<table>
  <tr><td>Item 1</td><td><button>Delete</button></td></tr>
  <tr><td>Item 2</td><td><button>Delete</button></td></tr>
  <tr><td>Item 3</td><td><button>Delete</button></td></tr>
  /* ... */
</table>

I tried following code:

cy.get('table tr').each(($row) => {
  cy.wrap($row).find('button').click()
});

But this doesn't work, I am having following error:

Timed out retrying after 15000ms: cy.find() failed because the page updated as a result of this command, but you tried to continue the command chain. The subject is no longer attached to the DOM, and Cypress cannot requery the page after commands such as cy.find().

This test case looks very common for me, so, what is an idiomatic way to do this in a Cypress?


Solution

  • If you use a recursive method in Cypress you need to confirm each row has been removed before the next row is clicked. Otherwise the recursion can over-run each step
    (hence failed because the page updated as a result of this command).

    It is likely that the click() action causes the page to re-render, so adding a .should() condition will ensure each step is complete.

    function deleteRows(count) {
      if (count < 1) return
    
      cy.get('table tr').first().find('button').click();
    
      cy.get('table tr')
        .should('have.length.lt', count)   // confirming row is removed
        .then(() => {
          deleteRows(--count) 
        })
    }
    
    cy.get('tbody tr').then(rows => deleteRows(rows.length)) 
    

    The same applies in your original code, something like

    let count;
    cy.get('table tr')
      .then(rows => count = rows.length)  // initial count
      .each(($row) => {
        cy.wrap($row).find('button').click()
        cy.get('table tr').should('have.length.lt', count)  // confirm
          .then(() => --count)                              // reduce count
      })