skip to Main Content

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?

2

Answers


  1. You can create a function to delete rows from the table one at a time. It allows Cypress to re-query the DOM after each deletion. Like this:

    const deleteAllRows = () => {
      cy.get('table').then(($table) => {
        if ($table.find('tr').length > 0) {
          cy.get('table tr').first().find('button').click();
    
          deleteAllRows();
        }
      });
    };
    
    describe('Table deletion test', () => {
      it('should delete all rows from the table', () => {
        cy.visit('/page-url');
    
        deleteAllRows();
    
        cy.get('table').should('not.have.descendants', 'tr');
      });
    });
    
    Login or Signup to reply.
  2. 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
      })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search