为什么在 nuxt 网络应用程序中通过 cypress 单击时按钮不触发?

Why does a button not fire when clicked through cypress in a nuxt web app?

我在 Nuxt.js 网络应用程序中有一个按钮组件。单击按钮组件时会发出 click 事件。我在 Nuxt 页面上使用该按钮组件,并提供一个页面方法作为事件处理程序。当我手动单击该按钮时,事件被触发并且应用程序流程按预期继续。这是我的代码:

<MyButton id="button" @click="openModal()">
  {{ $t('open') }}
</MyButton>

但是,当我使用 Cypress 单击同一个按钮时,什么也没有发生,也没有调用事件处理程序:

cy.get('#button').click()

测试失败后,我可以手动按下按钮(在Cypress中),它会起作用。这导致我访问了这个博客 post,建议竞争条件:https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/

博客post确实是正确的,这是一个竞争条件。按钮被呈现 server-side 并发送到浏览器。赛普拉斯等待页面加载,但它不知道还要等待 hydration. So the event handler is attached by some client-side code. Cypress is too fast here and clicks the button before the event handler is attached. That's also why it always works manually. Additionally, the click() method of cypress will not be retried automatically,所以测试只会失败。

所以基本上,我们希望等待来自 Nuxt 的一些信号,表明它已完成补水。不幸的是,我没有找到 API 来做到这一点。我想出的最佳解决方案是在您的布局中添加一个 class 以表示准备就绪并在 Cypress 中等待它:

<template>
  <div :class="['default-layout', hydrated ? 'hydrated' : 'hydrating']">
    <nuxt />
  </div>
</template>

<script>
export default {
  name: 'DefaultLayout',
  data () {
    return {
      hydrated: false,
    }
  },
  mounted () {
    this.$nextTick(() => {
      this.hydrated = true
    })
  }
}
</script>

然后在cypress/support/commands.ts你可以添加一个自定义命令来等待hydrated class:


Cypress.Commands.add('waitForHydration', (timeout: number = 20_000) => {
  cy.log('Waiting for hydration ...')
  cy.get('.default-layout.hydrated', { log: false, timeout })
})

如果您使用的是打字稿,请在 cypress/support/index.d.ts 中添加打字:

/// <reference types="cypress" />

declare namespace Cypress {
  interface Chainable {
    waitForHydration(timeout?: number): Chainable<void>
  }
}

最后,在您的测试套件中,像这样使用它:

it('can click the button', () => {
  cy.visit('/')
  cy.waitForHydration()
  cy.get('#button').click()
})