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

546 Views Asked by At

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...

2

There are 2 best solutions below

0
On BEST ANSWER

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
    });
  }
)
0
On
  1. Your command is almost working -- the main issue is that .within() yields the same element it was given. Instead, we should use .find().

  2. You'll need to yield the entire Cypress chain to have the command chainable as you'd like. In this case, after subbing out .within() for .find(), that means just adding a return before the initial cy.contains()

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