0

I'm hoping someone can help, but I've posted this as a Cypress discussion as well, although it might just be my understanding that's wrong

I need to get the Cypress.Chainable<JQuery<HTMLElement>> of the cell of a table using the column header and another cell's value in the same row.

Here's a working example JQuery TS Fiddle: https://jsfiddle.net/6w1r7ha9/

My current implementation looks as follows:

static findCellByRowTextColumnHeaderText(
    rowText: string,
    columnName: string,
) {
  const row = cy.get(`tr:contains(${rowText})`);
  const column = cy.get(`th:contains(${columnName})`)
  const columnIndex = ???
  return row.find(`td:eq(${columnIndex})`)
}

This function is required because I want to write DRY code to find cells easily for content verification, clicking elements inside of it etc.

The only example I've seen is this https://stackoverflow.com/a/70686525/1321908, but the following doesn't work:

const columns = cy.get('th')
let columnIndex = -1
columns.each((el, index) => {
  if (el.text().includes(columnName) {
    columnIndex = index
  }
  cy.log('columnIndex', columnIndex) // Outputs 2 as expected
})
cy.log('finalColumnIndex', columnIndex) // Outputs -1

My current thinking is something like:

const columnIndex: number = column.then((el) => el.index())

This however returns a Chainable<number> How to turn it into number, I have no idea. I'm using this answer to guide my thinking in this regard.

Werner Raath
  • 1,322
  • 3
  • 16
  • 34

2 Answers2

1

Using .then() in a Cypress test is almost mandatory to avoid flaky tests.

To avoid problems with test code getting ahead of web page updating, Cypress uses Chainable to retry the DOM query until success, or time out.

But the Chainable interface isn't a promise, so you can't await it. You can only then() it.

It would be nice if you could substitute another keyword like unchain

const column = unchain cy.get(`th:contains(${columnName})`)

but unfortunately Javascript can't be extended with new keywords. You can only add methods like .then() onto objects like Chainable.


Having said that, there are code patterns that allow extracting a Chainable value and using it like a plain Javascript variable.

But they are limited to specific scenarios, for example assigning to a global in a before() and using it in an it().


If you give up the core feature of Cypress, the automatic retry feature, then it's just jQuery exactly as you have in the fiddle (but using Cypress.$() instead of $()).

But even Mikhail's thenify relys on the structure of the test when you add a small amount of asynchronicity

Example app

<foo>abc</foo>
<script>
  setTimeout(() => {
    const foo = document.querySelector('foo')
    foo.innerText = 'def'
  }, 1000)
</script>

Test

let a = cy.get("foo").thenify()
// expect(a.text()).to.eq('def')           // fails 
// cy.wrap(a.text()).should('eq', 'def')   // fails
cy.wrap(a).should('have.text', 'def')      // passes

let b = cy.get("foo")                      // no thenify
b.should('have.text', 'def')               // passes
Fody
  • 23,754
  • 3
  • 20
  • 37
0

Based on your working example, you will need to get the headers first, map out the text, then find the index of the column (I've chosen 'Col B'). Afterwards you will find the row containing the other cell value, then get all the cells in row and use .eq() with the column index found earlier.

// get headers, map text, filter to Col B index
cy.get("th")
  .then(($headers) => Cypress._.map($headers, "innerText"))
  .then(cy.log)
  .invoke("indexOf", "Col B")
  .then((headerIndex) => {
    // find row containing Val A
    cy.contains("tbody tr", "Val A")
      .find("td")
      // get cell containing Val B
      .eq(headerIndex)
      .should("have.text", "Val B");
  });

Here is the example.

jjhelguero
  • 2,281
  • 5
  • 13