【问题标题】:How do I use cypress with components that are prefetched or preloaded with webpack?如何将 cypress 与使用 webpack 预取或预加载的组件一起使用?
【发布时间】:2021-07-20 20:57:27
【问题描述】:

我正在使用Cypress 7.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 来说很普遍。

【问题讨论】:

    标签: javascript vue.js webpack cypress prefetch


    【解决方案1】:

    您的问题似乎是您已经在加载支持它的代码之前渲染了一个按钮。正如您所注意到的,这不仅是快速自动化机器人的问题,甚至是“普通”用户的问题。

    简而言之,解决方案是不提前显示按钮,而是显示加载对话框。赛普拉斯允许 waiting for a DOM element to be visible 甚至带有超时选项。这比脆弱的随机等待更稳健。

    【讨论】:

    • 感谢您的回复!同样,您认为在后台加载模式而不是完全摆脱按钮时,在按钮上显示加载微调器或其他内容会提供更好或更差的用户体验吗?
    • 按钮上/中的小微调器似乎很理想。比闪烁(当 webpack 块几乎立即加载时)或令人讨厌地覆盖屏幕(当块加载缓慢或根本不加载时)的大加载模式要好。就像 Web 开发中的任何事情一样,您确实希望给用户一个清晰的信号,表明某事已经发生或正在发生,所以您的想法听起来不错。
    【解决方案2】:

    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) 相同的效果,即您实际上不需要轮询事件处理程序,您只需要让应用喘口气。

    【讨论】:

    • 非常感谢您的解决方案!下次在我的应用程序上工作时,我会试试看!我认为cy.wait(0) 没有我想的那么脏?为了抛出想法,您是否认为尝试设计按钮以在组件加载时显示微调器是矫枉过正的?
    • 矫枉过正 - 用户永远不会在浏览器中看到它,因为应用程序 100% 的时间都在使用 CPU。赛普拉斯的问题在于它尽可能快地运行测试命令并且没有屈服。
    • 在 Vue 术语中,cy.wait(0) 类似于 Vue.nextTick()
    【解决方案3】:

    尽管有几个选项可供我使用,但我最终还是等待网络空闲。

    我用来做这个的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,但它对我来说“感觉很丑”(不讨厌任何以这种方式编程的人)

    【讨论】:

      猜你喜欢
      • 2011-08-06
      • 2016-03-11
      • 2022-01-24
      • 1970-01-01
      • 2019-01-19
      • 2019-10-31
      • 1970-01-01
      • 2019-05-05
      • 1970-01-01
      相关资源
      最近更新 更多