如何将柏树与预取或预加载webpack的组件一起使用



我使用的是Cypress7.7.0(也在8.0.0上进行了测试(,我遇到了一个有趣的比赛条件。我正在测试一个页面,其中Cypress所做的第一个交互就是单击按钮打开模态。为了保持小的束大小,我将模态拆分为它自己预取的webpack块。我的Cypress测试从cy.get('#modal-button').click()开始,但这没有加载模态,因为模态还没有完成下载/加载。相反,它什么也不做(甚至不会向控制台抛出任何错误(。换句话说,Cypress与页面交互过快。这也是通过手动测试复制的(页面加载后,我快速点击按钮(。我试着将模态设置为预加载,但也没有成功。

我能够通过在页面加载和按钮交互之间引入更多延迟来解决这个问题。例如,在单击按钮之前插入任何Cypress命令(甚至是cy.wait(0)(都可以修复解决方案。然而,众所周知Cypress不需要插入这些脆弱的解决方案。有什么好办法绕过这个吗?我希望将模态保留在自己的块中。

仅供参考:我使用Vue作为我的前端库,并使用一个简单的defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue'))来加载模态组件。我认为这个问题对于Cypress来说是普遍的。

cy.wait(0)没有任何问题。

您所要做的就是将测试的控制权交给JS队列中的下一个进程,在这种情况下,可能是应用程序的启动脚本在等待将点击处理程序添加到按钮中。

我最近发现,React hooks应用程序也需要这一点,以允许钩子完成它的过程。你可能也会在Vue 3中遇到这种情况,因为他们引入了类似钩子的功能。

如果你想凭经验测试事件处理程序是否已经到达,你可以使用这里给出的方法(针对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()
})

但请注意,setTimeout(isReady, 0)可能只会在你的应用程序中实现与cy.wait(0)相同的效果,即你真的不需要轮询事件处理程序,你只需要应用程序喘口气。

您的问题似乎是在加载支持按钮的代码之前就已经呈现了按钮。正如你所注意到的,这不仅是快速自动化机器人的问题,甚至是一个"问题";常规的";使用者

简而言之,解决方案是不提前显示按钮,而是显示加载对话框。Cypress允许等待DOM元素可见,甚至可以使用超时选项。这比脆弱的随机等待更健壮。

我最终还是在等待网络空闲,尽管我有几个选择。

我用来做这件事的柏树函数如下,这在很大程度上受到了这个在网络上等待的解决方案的影响:

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本身指示何时准备好那么有效

这是我选择的解决方法,但以下解决方案也会起作用:

  • 在下载时创建轻量级占位符组件以取代异步组件,并让柏树等待实际组件渲染(例如,在后台下载实际模态时,只显示一个微调器的默认模态(
    • 优点:不必等待网络资源,如果实施得当,可以避免所有竞争条件
    • 缺点:必须创建一个用户可能永远看不到的组件,增加捆绑包的大小
  • "睡眠;cy.wait(...)的任意数量(尽管这很脆(
    • 优点:易于实现
    • 缺点:脆性,Cypress不建议直接使用它,如果使用eslint插件Cypress,会导致linter问题(你可以在使用它的行上禁用eslint,但对我来说"感觉很难看"(不讨厌任何这样编程的人(

最新更新