Search code examples
typescriptcypressfindelement

In Cypress, can you iterate through a table and have it perform the same functionality for each row?


I have an html/typescript frontend that contains a variable-length table with a column of action buttons, and I would like to implement a Cypress test that clicks the first button for the first row, completes a task, and then moves on to click the first button for the second row, complete the same task for the second row, and then on down the list.

Here's what the table initially looks like - with the button I want to have Cypress click for each row on the left.

enter image description here

Once a left-most button is clicked for a row, a dialog pops up. The user is supposed to enter values into the two text fields and then click OK.

enter image description here

I the actual app, the user can do this for each row as many times as they like.

This is what my code looks like for the confirmation dialog:

<p-dialog class="e2e-mark--confirm-dialog" header="Publish Report" [modal]="true"
  [(visible)]="showPublishReportDialog" (onHide)="cancelPublishReport()" [style]="{width: '30vw'}">
<div class="detail-row">
  <div class="detail-property-container">
    <span class="selector-item-type detail-label">

      Notes: *&nbsp;
    </span>
    <input id="notes" pInputText maxlength="50" formControlName="notes" [(ngModel)]="publishReportNotes"
      [style]="{width: '20vw'}" [attr.data-cy]="'publishReportNotesText'">
  </div>
  <div class="detail-property-container">
    <span class="selector-item-type detail-label">
      Comments: *&nbsp;
    </span>
    <input id="comments" pInputText maxlength="50" formControlName="comments" [(ngModel)]="publishReportComments"
      [style]="{width: '20vw'}" [attr.data-cy]="'publishReportCommentsText'">
  </div>
  <span *ngIf="publishDraftInLaps" class="selector-item-type detail-label">
    This will also create a draft PCD in LAPS Planning & Scheduling - which will take a few minutes.
  </span>
</div>
<hr />
<p-footer>
  <button pButton label="Cancel" (click)="cancelManageWeaponSystemsDialog()"></button>
  <button pButton label="OK" (click)="publishReport(false)"
    [disabled]="publishReportForm.invalid" [attr.data-cy]="'publishReportButton'"></button>
</p-footer>
  </form>
</p-dialog>

And here is a snippet of my test in my test.cy.ts file.

The idea being that I want to iterate through the rows of the table and have it perform the same functionality for each of the rows by clicking on the left button and completing the dialog and clicking OK.

cy.get('tbody > tr')
        .each(($row, $rowIndex) => {
            cy.log('rowIndex:  ' + $rowIndex);
            
            let cell = $row.find('td:nth-child(2)');
            
            
                
            cy.log('publishin the ' + cell.text().trim() + ' file');
            let actionButtons = $row.find('td:nth-child(1)');
            let publishButton = actionButtons.children().first();
            publishButton.click();
            cy.setInputText('[data-cy="publishReportNotesText"]','Report Notes for ' + cell.text().trim());
            cy.setInputText('[data-cy="publishReportCommentsText"]','Report Comments for ' + cell.text().trim());
            cy.get('[data-cy="publishReportButton"]').click();
                
                 
            
    });

All of which works just fine for the first row it processes - but then it dies on the second row.

The dialog never comes up for the second row, and it subsequently cannot type anything into the confirmation dialog for the second row:

cy.type() failed because it targeted a disabled element.

Any suggestions? Thanks much.


Solution

  • To overcome the targeted a disabled element error try waiting for the element to become enabled.

    cy.get('tbody > tr').each(($row, $rowIndex) => {
      ...
      publishButton.click();
    
      cy.get('[data-cy="publishReportNotesText"]').should('be.enabled')
      cy.setInputText('[data-cy="publishReportNotesText"]', ... )
    
      cy.get('[data-cy="publishReportCommentsText"]').should('be.enabled')
      cy.setInputText('[data-cy="publishReportCommentsText"]', ...)
    
      cy.get('[data-cy="publishReportButton"]').click()
    });
    

    Potentially the .each() command is also giving you stale element references, since there are click() actions inside that may change the DOM.

    Since you have only three rows, drop the .each() and handle each row explicitly, something like

    cy.contains('tbody > tr', 'e2e_tests.xml').then(($row, $rowIndex) => {
      ...
    

    Also, try confirming the text you type in has been updated to the DOM. That will confirm each row is complete before moving on to the next one.

    cy.get('[data-cy="publishReportNotesText"]').should('be.enabled')
    const text = 'Report Notes for ' + ...
    cy.setInputText('[data-cy="publishReportNotesText"]', text)
    cy.get('[data-cy="publishReportNotesText"]').should('have.value', text)