Given a dynamically-loading table with a variable number of rows, how does one assert that the rows are correctly ordered by date?
This problem has two main challenges: (1) how does one compare dates within table rows using cypress; and (2) how does one handle dynamic loading in such a complex scenario?
So far, I have successfully managed to solve the first problem; however, I am unable to solve the second problem. My test works most of the time, but it will sometimes fail because the page hasn't finished loading before the assertions are hit. For example, the dates are out of order when the page is first loaded:
2023-12-23
2024-01-24
2022-02-25
2027-03-26
And then they get ordered following an XHR request:
2022-02-25
2023-12-23
2024-01-24
2027-03-26
Now, before you say anything: yes, I am already waiting for the XHR request to finish before I make any assertions. The problem is that there remains a small delay between when the request finishes, and when the actual DOM gets updated.
Normally this problem is solved automatically by Cypress. In Cypress, every call to .should() will automatically retry until the expected condition is found, or a timeout is reached. This fixes any problems related to dynamic loading.
However, .should() isn't the only way to assert something in Cypress. Alternatively, you can make direct assertions using Chai expressions, which is what Cypress uses under the hood when you call .should(). This is often required when making complex assertions such as the kind that I am making in this scenario.
Let's take a look at what I have so far:
cy.get('tbody tr').each(($row, $index, $rows) => { // foreach row in the table
if ($index > 0) { // (skipping the first row)
cy.wrap($row).within(() => { // within the current row...
cy.get('td').eq(7).then(($current_td) => { // ... get the eighth td (where the date is)
cy.wrap($rows[$index - 1]).within(() => { // within the previous row...
cy.get('td').eq(7).then(($previous_td) => { // ... get the eighth td
expect(dayjs($current_td.text().toString()).unix()) // assert that the date of the current td...
.gt(dayjs($previous_td.text().toString()).unix()) // ... is greater than the previous one.
})
})
})
})
}
})
Now, one option you have in Cypress is to replace .then() with .should(). Doing this allows the user to continue to benefit from the polling nature of .should() while also using multiple Chai expressions directly. Unfortunately, I can't seem to get this to work. Here's some of the attempts that I made:
cy.get('tbody tr').each(($row, $index, $rows) => {
if ($index > 0) {
cy.wrap($row).within(() => {
cy.get('td').eq(7).then(($current_td) => {
cy.wrap($rows[$index - 1]).within(() => {
cy.get('td').eq(7).should(($previous_td) => { // replacing with .should() here doesn't help, because it only attempts to retry on $previous_td, but we actually need to retry $current_td as well
expect(dayjs($current_td.text().toString()).unix())
.gt(dayjs($previous_td.text().toString()).unix())
})
})
})
})
}
})
cy.get('tbody tr').each(($row, $index, $rows) => {
if ($index > 0) {
cy.wrap($row).within(() => {
cy.get('td').eq(7).should(($current_td) => { // causes an infinite loop!
cy.wrap($rows[$index - 1]).within(() => {
cy.get('td').eq(7).then(($previous_td) => {
expect(dayjs($current_td.text().toString()).unix())
.gt(dayjs($previous_td.text().toString()).unix())
})
})
})
})
}
})
The only other solution I can think of is to hardcode my own polling. This is the sort of thing that I do all the time when writing tests in Selenium. However, my experience with Cypress leads me to believe that I shouldn't need to do this, ever. It's just a matter of wrangling Cypress to do what I expect it to do.
That said, I'm coming up empty handed. So, what do?
UPDATE
After learning from gleb's answer, I finally landed on this simple solution:
const dayjs = require('dayjs')
chai.use(require('chai-sorted'));
cy.get('tbody tr td:nth-of-type(8)').should($tds => {
const timestamps = Cypress._.map($tds, ($td) => dayjs($td.innerText).unix())
expect(timestamps).to.be.sorted()
})
I now feel that a core part of my problem was not understanding jQuery well enough to write a single selection statement. Furthermore, I wasn't familiar with lodash map or chai-sorted.