如何将 cypress 与使用 webpack 预取或预加载的组件一起使用?
How do I use cypress with components that are prefetched or preloaded with webpack?
我正在使用 Cypress
7.7.0
(也在 8.0.0
上测试过),我 运行 进入了一个有趣的竞争条件。我正在测试一个页面,其中 Cypress
执行的第一个交互是单击按钮打开模式。为了保持较小的包大小,我将模式拆分为它自己的预取 webpack
块。我的 Cypress
测试以 cy.get('#modal-button').click()
开始,但这不会加载模态,因为模态尚未完成 downloading/loading。它什么都不做(甚至不向控制台抛出任何错误)。换句话说,Cypress
与页面交互的速度太快了。这也通过手动测试重现(我在页面加载后超快地点击了按钮)。我试过将模式设置为预加载,但这也不起作用。
我能够通过在页面加载和按钮交互之间引入更多延迟来解决问题。例如,在我单击按钮之前插入任何 Cypress
命令(甚至是 cy.wait(0)
)即可修复解决方案。然而,Cypress
以不需要插入这些脆弱的解决方案而闻名。有解决这个问题的好方法吗?我想将模态保留在它自己的块中。
仅供参考:我使用 Vue
作为我的前端库,并使用简单的 defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue'))
来加载模态组件。我认为这个问题对 Cypress
来说是普遍的。
您的问题似乎是您在加载支持按钮的代码之前就已经渲染了按钮。正如您所注意到的,这不仅是快速自动机器人的问题,甚至是“普通”用户的问题。
简而言之,解决方案是不提前显示按钮,而是显示一个加载对话框。赛普拉斯甚至允许 waiting for a DOM element to be visible 超时选项。这比脆弱的随机等待更稳健。
cy.wait(0)
没问题。
您所做的只是将控制权从测试传递给 JS 队列中的下一个进程,在本例中,它是应用程序的启动脚本,它可能正在等待将点击处理程序添加到按钮。
我最近发现 React hooks 应用程序也需要这样做,以允许 hook 完成它的过程。您可能还会在 Vue 3 中遇到这种情况,因为它们引入了类似钩子的功能。
如果你想凭经验测试事件处理程序是否已经到达,你可以使用这里给出的方法(为click()
修改)- When Can The Test Start?
let appHasStarted
function spyOnAddEventListener (win) {
const addListener = win.EventTarget.prototype.addEventListener
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'click') {
appHasStarted = true
win.EventTarget.prototype.addEventListener = addListener // restore original listener
}
return addListener.apply(this, arguments)
}
}
function waitForAppStart() {
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0) // recheck "appHasStarted" variable
}
isReady()
})
}
it('greets', () => {
cy.visit('app.html', {
onBeforeLoad: spyOnAddEventListener
}).then(waitForAppStart)
cy.get('#modal-button').click()
})
但请注意 setTimeout(isReady, 0)
可能只会在您的应用中实现与 cy.wait(0)
相同的效果,即您实际上不需要轮询事件处理程序,您只需要应用采取呼吸。
我最终还是等待网络空闲,尽管我有多种选择。
我用来执行此操作的 cypress 函数如下,它深受 this solution for waiting on the network 的影响:
Cypress.Commands.add('waitForIdleNetwork', () => {
const idleTimesInit = 3
let idleTimes = idleTimesInit
let resourcesLengthPrevious
cy.window().then(win =>
cy.waitUntil(() => {
const resourcesLoaded = win.performance.getEntriesByType('resource')
if (resourcesLoaded.length === resourcesLengthPrevious) {
idleTimes--
} else {
idleTimes = idleTimesInit
resourcesLengthPrevious = resourcesLoaded.length
}
return !idleTimes
})
)
})
以下是我采用的解决方案的优缺点:
- 优点:当用户可能永远不会 运行 遇到此问题时,无需增加包大小或修改客户端代码
- 缺点:技术上仍然可能出现竞争条件,点击事件发生在资产下载之后,但在它们全部执行和呈现它们的内容之前,但不太可能,不如等待 UI 本身用于指示何时就绪
这是我选择的解决方法,但以下解决方法也有效:
- 创建轻量级占位符组件以在异步组件下载时代替异步组件,并让 cypress 等待实际组件呈现(例如,在后台下载实际模态时仅显示微调器的默认模态)
- 优点:不必等待网络资源,如果实施得当可避免所有竞争条件
- 缺点:必须创建用户可能永远看不到的组件,增加包大小
- 用
cy.wait(...)
“睡”任意量(虽然这很脆弱)
- 优点:易于实施
- 缺点:脆弱,Cypress 不建议直接使用它,如果使用 eslint-plugin-cypress 会导致 linter 问题(您可以在使用它的行上禁用 eslint,但它“感觉很难看”我(不讨厌任何以这种方式编程的人)
我正在使用 Cypress
7.7.0
(也在 8.0.0
上测试过),我 运行 进入了一个有趣的竞争条件。我正在测试一个页面,其中 Cypress
执行的第一个交互是单击按钮打开模式。为了保持较小的包大小,我将模式拆分为它自己的预取 webpack
块。我的 Cypress
测试以 cy.get('#modal-button').click()
开始,但这不会加载模态,因为模态尚未完成 downloading/loading。它什么都不做(甚至不向控制台抛出任何错误)。换句话说,Cypress
与页面交互的速度太快了。这也通过手动测试重现(我在页面加载后超快地点击了按钮)。我试过将模式设置为预加载,但这也不起作用。
我能够通过在页面加载和按钮交互之间引入更多延迟来解决问题。例如,在我单击按钮之前插入任何 Cypress
命令(甚至是 cy.wait(0)
)即可修复解决方案。然而,Cypress
以不需要插入这些脆弱的解决方案而闻名。有解决这个问题的好方法吗?我想将模态保留在它自己的块中。
仅供参考:我使用 Vue
作为我的前端库,并使用简单的 defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue'))
来加载模态组件。我认为这个问题对 Cypress
来说是普遍的。
您的问题似乎是您在加载支持按钮的代码之前就已经渲染了按钮。正如您所注意到的,这不仅是快速自动机器人的问题,甚至是“普通”用户的问题。
简而言之,解决方案是不提前显示按钮,而是显示一个加载对话框。赛普拉斯甚至允许 waiting for a DOM element to be visible 超时选项。这比脆弱的随机等待更稳健。
cy.wait(0)
没问题。
您所做的只是将控制权从测试传递给 JS 队列中的下一个进程,在本例中,它是应用程序的启动脚本,它可能正在等待将点击处理程序添加到按钮。
我最近发现 React hooks 应用程序也需要这样做,以允许 hook 完成它的过程。您可能还会在 Vue 3 中遇到这种情况,因为它们引入了类似钩子的功能。
如果你想凭经验测试事件处理程序是否已经到达,你可以使用这里给出的方法(为click()
修改)- When Can The Test Start?
let appHasStarted
function spyOnAddEventListener (win) {
const addListener = win.EventTarget.prototype.addEventListener
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'click') {
appHasStarted = true
win.EventTarget.prototype.addEventListener = addListener // restore original listener
}
return addListener.apply(this, arguments)
}
}
function waitForAppStart() {
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0) // recheck "appHasStarted" variable
}
isReady()
})
}
it('greets', () => {
cy.visit('app.html', {
onBeforeLoad: spyOnAddEventListener
}).then(waitForAppStart)
cy.get('#modal-button').click()
})
但请注意 setTimeout(isReady, 0)
可能只会在您的应用中实现与 cy.wait(0)
相同的效果,即您实际上不需要轮询事件处理程序,您只需要应用采取呼吸。
我最终还是等待网络空闲,尽管我有多种选择。
我用来执行此操作的 cypress 函数如下,它深受 this solution for waiting on the network 的影响:
Cypress.Commands.add('waitForIdleNetwork', () => {
const idleTimesInit = 3
let idleTimes = idleTimesInit
let resourcesLengthPrevious
cy.window().then(win =>
cy.waitUntil(() => {
const resourcesLoaded = win.performance.getEntriesByType('resource')
if (resourcesLoaded.length === resourcesLengthPrevious) {
idleTimes--
} else {
idleTimes = idleTimesInit
resourcesLengthPrevious = resourcesLoaded.length
}
return !idleTimes
})
)
})
以下是我采用的解决方案的优缺点:
- 优点:当用户可能永远不会 运行 遇到此问题时,无需增加包大小或修改客户端代码
- 缺点:技术上仍然可能出现竞争条件,点击事件发生在资产下载之后,但在它们全部执行和呈现它们的内容之前,但不太可能,不如等待 UI 本身用于指示何时就绪
这是我选择的解决方法,但以下解决方法也有效:
- 创建轻量级占位符组件以在异步组件下载时代替异步组件,并让 cypress 等待实际组件呈现(例如,在后台下载实际模态时仅显示微调器的默认模态)
- 优点:不必等待网络资源,如果实施得当可避免所有竞争条件
- 缺点:必须创建用户可能永远看不到的组件,增加包大小
- 用
cy.wait(...)
“睡”任意量(虽然这很脆弱)- 优点:易于实施
- 缺点:脆弱,Cypress 不建议直接使用它,如果使用 eslint-plugin-cypress 会导致 linter 问题(您可以在使用它的行上禁用 eslint,但它“感觉很难看”我(不讨厌任何以这种方式编程的人)