cypresscypress-custom-commands

How to implement a command to get a table cell in Cypress?


I want to create a command with this interface:

cy.getTableCell({ column: 'Name', row: 42 }).contains('Douglas Adams')

Where getTableCell would return the table cell (td) corresponding to the 42-th row of the table on the column 'Name'. I came up with this implementation:

type GetTableCellParams = {
  columnName: string;
  rowIndex: number;
};

Cypress.Commands.add(
  'getTableCell',
  ({ columnName, rowIndex }: GetTableCellParams) => {
    cy.contains('th', columnName)
      .invoke('index')
      .then((index) => {
        cy.get('tr')
          .eq(rowIndex)
          .within((row) => {
            return cy.get('td').eq(index);
          });
      });
  }
);

It does find the right table cell. However, since it does so inside a callback, I can't do anything with it - I would like to be able to call chainable methods such as contains, click, etc. How can I refactor this so the caller has access to this element, being able to call contains, click and other chainable methods?

I could also use some help on the function readability. It looks like a mess - I guess the problem lies with the nested callbacks...


Solution

  • It works with no returns at all.

    Cypress uses a command stack, and the last subject on the stack is the value returned.

    The problem with .within() is it reverts the subject after it finishes.

    Cypress.Commands.add('getTableCell', ({ columnName, rowIndex }: GetTableCellParams) => {
    
      cy.contains('th', columnName).invoke('index')
        .then(colIndex => {
          cy.get('tr').eq(rowIndex)
            .find('td').eq(colIndex)
        });
      }
    )
    

    To illustrate, try aliasing the <td> and follow the .within() by getting the alias value

    Cypress.Commands.add('getTableCell', ({ columnName, rowIndex }) => {
      cy.contains('th', columnName)
        .invoke('index')
        .then(colIndex => {
          cy.get('tr')
            .eq(rowIndex)
            .within((row) => {
              cy.get('td').eq(colIndex).as('cell')
            })
            cy.get('@cell')   // last command, it's result will be returned
        });
      }
    )