Cypress.io 如何处理异步代码

Dan*_*aby 18 javascript testing async-await cypress

我正在将我们的旧水豚测试迁移到 cypress.io,因为我们的应用程序正在采用 SPA 方式。

在我们的例子中,我们有 2000 多个测试涵盖了很多功能。因此,测试功能的常见模式是让用户创建和发布商品。

一开始我写了一个案例,其中柏树通过页面并点击所有内容。它起作用了,但我看到要约创建 + 发布花了将近 1.5 分钟才能完成。有时我们需要多个报价。所以我们有一个需要 5 分钟的测试,我们还有 1999 年要重写。

我们想出了 REST API 来创建报价和用户,基本上是测试环境准备的快捷方式。

我到了使用async/await. 所以这就是事情。如果我想在 cypress 中使用普通的异步 JS 代码,我会得到Error: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

这是它的样子:

    const faker = require('faker')
    import User from '../../support/User';

    describe('Toggle button for description offer', () => {
      const user = new User({
        first_name: faker.name.firstName(),
        last_name: faker.name.firstName(),
        email: `QA_${faker.internet.email()}`,
        password: 'xxx'
      })
      let offer = null

      before(async () => {
        await user.createOnServer()
        offer = await user.createOffer()
        await offer.publish()
      })

      beforeEach(() => {
        user.login()
        cy.visit(`/offers/${offer.details.id}`)
        cy.get('.offer-description__content button').as('showMoreButton')
      })

      it('XXX', function () {
        ...some test
      })
    })
Run Code Online (Sandbox Code Playgroud)

此代码段按预期工作。首先它在之前触发并创建整个 env 然后当它完成时它会进一步到 beforeEach 并开始测试。

现在我想合并之前和之前每个都喜欢

  before(async () => {
    await user.createOnServer()
    offer = await user.createOffer()
    await offer.publish()
    user.login()
    cy.visit(`/offers/${offer.details.id}`)
    cy.get('.offer-description__content button').as('showMoreButton')
  })
Run Code Online (Sandbox Code Playgroud)

由于 async 关键字,这将失败。现在的问题是:如何重写它以同时使用 async/await 和 cypress 命令?我试图用普通的 Promise 重写它,但它也不起作用......

任何帮助表示赞赏。

Tim*_*ion 22

我使用以下代码片段来确保在执行下一个 cypress 命令之前在 cypress 中执行异步函数:

cy.wrap(null).then(() => myAsyncFunction());
Run Code Online (Sandbox Code Playgroud)

例子:

function sleep(milliseconds) {
    return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

async function asyncFunction1() {
    console.log('started asyncFunction1');
    await sleep(3000);
    console.log('finalized asyncFunction1');
}

async function asyncFunction2() {
    console.log('started asyncFunction2');
    await sleep(3000);
    console.log('finalized asyncFunction2');
}

describe('Async functions', () => {
    it('should be executed in sequence', () => {
        cy.wrap(null).then(() => asyncFunction1());
        cy.wrap(null).then(() => asyncFunction2());
    });
});
Run Code Online (Sandbox Code Playgroud)

导致以下输出:

started asyncFunction1
finalized asyncFunction1
started asyncFunction2
finalized asyncFunction2
Run Code Online (Sandbox Code Playgroud)

  • @Claude:这是文档的链接:https://docs.cypress.io/api/utilities/promise#Waiting-for-Promises (4认同)
  • 似乎是正确的答案,但是如果您可以通过文档链接来支持您的主张(保证在执行下一个 cy 语句之前等待),那就太好了。另外,据我所知,这仅适用于您不关心结果的异步语句(我认为结果很多,但最好指定) (2认同)

Gui*_*mmi 17

您的问题源于这样一个事实,即cypress 命令不是 promises,尽管表现得像 promises。

我能想到两个选择:

  • 尝试重构您的测试代码以不使用 async/await,因为在 cypress 上运行您的代码时,这些命令的行为不符合预期(检查此错误)。赛普拉斯已经有了处理异步代码的完整方法,因为它创建了一个始终按预期顺序运行的命令队列。这意味着您可以在继续测试之前观察异步代码的效果以验证它是否发生。例如,如果User.createUserOnServer必须等待成功的 API 调用,请使用cy.server()、cy.route() 和 cy.wait()将代码添加到您的测试中,以等待请求完成,如下所示:

    cy.server();
    cy.route('POST', '/users/').as('createUser');
    // do something to trigger your request here, like user.createOnServer()
    cy.wait('@createUser', { timeout: 10000});
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用另一个第三方库来改变 cypress 与 async/await 的工作方式,例如cypress-promise。这个库可以帮助您将 cypress 命令视为您可以awaitbefore代码中的承诺(在本文中阅读更多相关信息)。

  • @GuilhermeLemmi 我有同样的问题,发现 cy.route() 仅适用于从应用程序发出的请求。是否也有任何 cypress 工具可以等待测试中发出的请求? (2认同)

Tha*_*hai 11

将异步代码放入cy.then()

  before(() => {
    cy.then(async () => {
      await user.createOnServer()
      offer = await user.createOffer()
      await offer.publish()
      user.login()
      cy.visit(`/offers/${offer.details.id}`)
    })

    // This line can be outside `cy.then` because it does not
    // use any variables created or set inside `cy.then`.
    cy.get('.offer-description__content button').as('showMoreButton')
  })
Run Code Online (Sandbox Code Playgroud)


Dom*_*erg 9

虽然@isotopeee的解决方案基本上有效,但我确实遇到了问题,尤其是在使用wait(@alias)之后的 await 命令时。问题似乎是,赛普拉斯函数返回一个内部 Chainable 类型,它看起来像一个 Promise 但不是一个。

但是,您可以利用它来发挥自己的优势,而不是编写

describe('Test Case', () => {
  (async () => {
     cy.visit('/')
     await something();
  })()
})
Run Code Online (Sandbox Code Playgroud)

你可以写

describe('Test Case', () => {
  cy.visit('/').then(async () => await something())
})
Run Code Online (Sandbox Code Playgroud)

这应该适用于每个赛普拉斯命令

  • 这会导致您的报告出现问题。如果发生故障,Mocha 将不会显示错误。测试总会通过。网络上还有其他几篇文章不建议使用这种方法。 (2认同)