[英]How do I use cypress with components that are prefetched or preloaded with webpack?
I'm using Cypress
7.7.0
(also tested on 8.0.0
), and I'm running into an interesting race condition.我正在使用
Cypress
7.7.0
(也在8.0.0
进行了测试),并且遇到了一个有趣的竞争条件。 I'm testing a page where one of the first interactions that Cypress
does is click a button to open a modal.我正在测试一个页面,其中
Cypress
执行的第一个交互之一是单击按钮以打开模态。 To keep bundle sizes small, I split the modal into its own prefetched webpack
chunk.为了保持包的大小较小,我将模态拆分为自己的预取
webpack
块。 My Cypress
test starts with cy.get('#modal-button').click()
but this doesn't load the modal because the modal hasn't finished downloading/loading.我的
Cypress
测试从cy.get('#modal-button').click()
但这不会加载模态,因为模态尚未完成下载/加载。 It does nothing instead (doesn't even throw any errors to the console).它什么都不做(甚至不向控制台抛出任何错误)。 In other words,
Cypress
interacts with the page too quickly.换句话说,
Cypress
与页面交互的速度太快。 This was also reproduced with manual testing (I clicked on the button super fast after page load).这也是通过手动测试重现的(我在页面加载后超快地点击了按钮)。 I have tried setting the modal to be preloaded instead, but that didn't work either.
我曾尝试将模态设置为预加载,但这也不起作用。
I am able to solve the problem by introducing more delay between page load and button interaction.我能够通过在页面加载和按钮交互之间引入更多延迟来解决这个问题。 For example, inserting any
Cypress
command (even a cy.wait(0)
) before I click on the button fixes the solution.例如,在单击按钮之前插入任何
Cypress
命令(甚至是cy.wait(0)
)可修复解决方案。 Cypress
, however, is known for not needing to insert these brittle solutions.然而,
Cypress
以不需要插入这些脆弱的解决方案而闻名。 Is there a good way to get around this?有没有好办法解决这个问题? I'd like to keep the modal in its own chunk.
我想将模态保留在自己的块中。
FYI: I'm using Vue
as my front end library and am using a simple defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue'))
to load the modal component.仅供参考:我使用
Vue
作为我的前端库,并使用一个简单的defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue'))
来加载模态组件。 I figure that this problem is general to Cypress
though.我认为这个问题对
Cypress
来说是普遍的。
There's nothing wrong with cy.wait(0)
. cy.wait(0)
没有任何问题。
All you are doing is handing control from the test to the next process in the JS queue, in this case it's the app's startup script which is presumably waiting to add the click handler to the button.您所做的就是将控制权从测试传递到 JS 队列中的下一个进程,在这种情况下,它是应用程序的启动脚本,它可能正在等待将单击处理程序添加到按钮。
I recently found that this is also needed in a React hooks app to allow the hook to complete it's process.我最近发现 React 钩子应用程序也需要这样做,以允许钩子完成它的过程。 You will likely also come across that in Vue 3, since they have introduced a hook-like feature.
您可能还会在 Vue 3 中遇到这种情况,因为它们引入了类似钩子的功能。
If you want to empirically test that the event handler has arrived, you can use the method given here (modified for click()
) -When Can The Test Start?如果您想凭经验测试事件处理程序是否已到达,您可以使用这里给出的方法(修改为
click()
) -测试何时开始?
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()
})
But note setTimeout(isReady, 0)
will probably just achieve the same as cy.wait(0)
in your app, ie you don't really need to poll for the event handler, you just need the app to take a breath.但请注意,
setTimeout(isReady, 0)
可能只会在您的应用程序中实现与cy.wait(0)
相同的cy.wait(0)
,即您实际上并不需要轮询事件处理程序,您只需要让应用程序喘口气。
It seems like your problem is that you're already rendering a button before the code backing it is loaded.您的问题似乎是在加载支持按钮的代码之前已经渲染了按钮。 As you noticed, this isn't only an issue for fast automated bots, but even a "regular" user.
正如您所注意到的,这不仅是快速自动化机器人的问题,甚至是“普通”用户的问题。
In short, the solution is to not display the button early, but show a loading dialog instead.简而言之,解决方案是不提前显示按钮,而是显示加载对话框。 Cypress allows waiting for a DOM element to be visible with even a timeout option.
赛普拉斯允许等待 DOM 元素可见,甚至带有超时选项。 This is more robust than a brittle random wait.
这比脆弱的随机等待更健壮。
I ended up going with waiting for the network to be idle, although there were several options available to me.我最终还是等待网络空闲,尽管我有多种选择。
The cypress function I used to do this was the following which was heavily influenced by this solution for waiting on the network :我用来执行此操作的 cypress 函数如下,它受此等待网络的解决方案的影响很大:
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
})
)
})
Here are the pros and cons of the solution I went with:以下是我采用的解决方案的优缺点:
This was the way I chose solve it but the following solutions would have also worked:这是我选择的解决方法,但以下解决方案也有效:
cy.wait(...)
cy.wait(...)
“睡眠”任意数量(尽管这很脆弱cy.wait(...)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.