断言动态 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)
})
给定行数可变的动态加载 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)
})