断言动态 table 按日期正确排序

Assert that a dynamic table is correctly ordered by date

给定行数可变的动态加载 table,如何断言行按日期正确排序?

这个问题有两个主要挑战:(1) 如何使用 cypress 比较 table 行内的日期;以及 (2) 如何在如此复杂的场景中处理动态加载?

至此,我已经成功解决了第一个问题;但是,我无法解决第二个问题。我的测试大部分时间都有效,但有时会失败,因为在命中断言之前页面尚未完成加载。例如,首次加载页面时日期乱序:

2023-12-23
2024-01-24
2022-02-25
2027-03-26

然后他们在 XHR 请求后得到命令:

2022-02-25
2023-12-23
2024-01-24
2027-03-26

现在,在你说什么之前:是的,在我做出任何断言之前,我已经在等待 XHR 请求完成。问题是在请求完成与实际 DOM 更新之间仍然存在一小段延迟。

通常这个问题会被赛普拉斯自动解决。在 Cypress 中,每次调用 .should() 都会自动重试,直到找到预期的条件或达到超时。这修复了与动态加载相关的任何问题。

但是,.should() 并不是在 Cypress 中断言某些内容的唯一方法。或者,您可以使用 Chai 表达式进行直接断言,这是赛普拉斯在您调用 .should() 时在后台使用的内容。这在进行复杂断言时通常是必需的,例如我在这种情况下所做的断言。

让我们看看我目前有什么:

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

现在,您在 Cypress 中的一个选择是将 .then() 替换为 .should()。这样做允许用户继续受益于 .should() 的轮询性质,同时还可以直接使用多个 Chai 表达式。不幸的是,我似乎无法让它发挥作用。以下是我所做的一些尝试:

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()) 
          })
        })
      })
    })
  }
})

我能想到的唯一其他解决方案是对我自己的轮询进行硬编码。这是我在用 Selenium 编写测试时经常做的事情。然而,我使用 Cypress 的经验让我相信我永远不需要这样做。这只是争论赛普拉斯做我期望它做的事情。

也就是说,我空手而归。那么,怎么办?

更新

在学习了 gleb 的回答后,我终于找到了这个简单的解决方案:

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()
})

我现在觉得我的问题的核心部分是对 jQuery 的理解不够好,无法编写单个选择语句。此外,我不熟悉 lodash 地图或 chai-sorted。

您需要使用单个 cy.get(...).should(...) 回调,其中回调获取所有日期字符串,转换为时间戳,然后检查时间戳是否已排序。然后赛普拉斯重试 cy.get 命令 - 直到 table 被排序并且 should 回调通过。这是示例代码,请参阅 https://glebbahmutov.com/cypress-examples/recipes/sorted-list.html

处的完整动态示例
// assuming you want to sort by the second column
cy.get('table tbody td + td').should($cells => {
  const timestamps = Cypress._.map($cells, ($cell) => $cell.innerText)
    .map((str) => new Date(str))
    .map((d) => d.getTime())
  // check if the timestamps are sorted
  const sorted = Cypress._.sortBy(timestamps)
  expect(timestamps, 'sorted timestamps').to.deep.equal(sorted)
})