简体   繁体   English

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

[英]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:以下是我采用的解决方案的优缺点:

  • pros: no need to increase bundle size or modify client code when the user will likely never run into this problem优点:当用户可能永远不会遇到这个问题时,无需增加包大小或修改客户端代码
  • cons: technically still possible to have a race condition where the click event happens after the assets were downloaded, but before they could all execute and render their contents, but very unlikely, not as efficient as waiting on the UI itself for indication of when it is ready缺点:技术上仍然可能存在竞争条件,即点击事件发生在资源下载之后,但在它们都可以执行和呈现其内容之前,但不太可能,不如等待 UI 本身指示何时有效准备好了

This was the way I chose solve it but the following solutions would have also worked:这是我选择的解决方法,但以下解决方案也有效:

  • creating lightweight placeholder components to take the place of asychronous components while they download and having cypress wait for the actual component to render (eg a default modal that just has a spinner being shown while the actual modal is downloaded in the background)创建轻量级占位符组件以在下载时代替异步组件,并让 cypress 等待实际组件呈现(例如,当实际模式在后台下载时,只显示一个微调器的默认模式)
    • pros: don't have to wait on network resources, avoids all race conditions if implemented properly优点:不必等待网络资源,如果实施得当,可以避免所有竞争条件
    • cons: have to create a component the user may never see, increases bundle size缺点:必须创建一个用户可能永远不会看到的组件,增加了包的大小
  • "sleeping" an arbitrary amount (although this is brittle) with cy.wait(...)使用cy.wait(...) “睡眠”任意数量(尽管这很脆弱cy.wait(...)
    • pros: easy to implement优点:易于实施
    • cons: brittle, not recommended to use this directly by Cypress, will cause linter problems if using eslint-plugin-cypress (you can disable eslint on the line that you use this on, but it "feels ugly" to me (no hate on anyone who programs that way)缺点:脆弱,不建议 Cypress 直接使用它,如果使用eslint-plugin-cypress会导致linter问题(你可以在你使用它的线路上禁用 eslint,但它对我来说“感觉很丑”(不讨厌)任何以这种方式编程的人)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM