Cypress.io How to handle async code
由于我们的应用程序正在采用SPA方式,因此我正在将旧的水豚测试移至cypress.io。
在我们的案例中,我们有2000多个测试涵盖了许多功能。
因此,测试功能的常见模式是让用户拥有创建和发布的报价。
在开始的时候,我写了一个案例,其中柏树进入低谷页面并单击所有内容。它奏效了,但我看到要约创建发布花费了将近1.5分钟才能完成。有时我们需要多个报价。因此,我们进行了一个需要5分钟的测试,并且还有1999年可以重写。
我们提出了REST API来创建商品和用户,这基本上是准备测试环境的捷径。
我到了使用
这是它的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 }) }) |
此代码段符合预期。首先,它会在之前触发并创建整个环境,然后在完成后进一步进行操作,然后再开始测试。
现在我想合并之前和之前每个类似
1 2 3 4 5 6 7 8 | 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') }) |
这将由于async关键字而失败。
现在的问题是:如何将其重写以一起使用async / await和cypress命令?我试图用普通的Promise重写它,但是它也无法正常工作...
任何帮助表示赞赏。
您的问题源于赛普拉斯命令不是诺言,尽管其行为与诺言类似。
我可以想到两种选择:
-
尝试重构测试代码以不使用异步/等待,因为在cypress上运行代码时,这些命令的行为不符合预期(请检查此bug)。赛普拉斯已经创建了处理异步代码的完整方法,因为它创建了始终按预期顺序顺序运行的命令队列。这意味着您可以在继续测试之前观察异步代码的效果,以验证它是否已发生。例如,如果
User.createUserOnServer 必须等待成功的API调用,则使用cy.server(),cy.route()和cy.wait()将代码添加到测试中以等待请求完成,如下所示:1
2
3
4cy.server();
cy.route('POST', '/users/').as('createUser');
// do something to trigger your request here, like user.createOnServer()
cy.wait('@createUser', { timeout: 10000}); -
使用另一个第三方库来更改cypress与async / await一起工作的方式,例如cypress-promise。此lib可以帮助您将cypress命令视为可以在
before 代码中进行await Promise的Promise(在本文中了解有关此内容的更多信息)。
虽然@isotopeee的解决方案基本可行,但我确实遇到了问题,尤其是在使用
不过,您可以利用它来发挥自己的优势,而不必编写
1 2 3 4 5 6 | describe('Test Case', () => { (async () => { cy.visit('/') await something(); })() }) |
您可以写
1 2 3 | describe('Test Case', () => { cy.visit('/').then(async () => await something()) }) |
这应与每个赛普拉斯命令一起使用
我在
1 2 3 4 5 | describe('Test Case', () => { (async () => { // expressions here })() }) |
我将分享我的方法,因为我在编写涉及大量AWS开发工具包调用(全部Promise)的测试时会感到头疼。我想出的解决方案提供了良好的日志记录,错误处理,似乎可以解决我遇到的所有问题。
以下是其提供的摘要:
- 一种package懒惰Promise并在Cypress可链接的内部调用promise的方法
-
提供给该方法的别名将出现在UI的Cypress命令面板中。执行开始,完成或失败时,也会将其记录到控制台。错误将在Cypress命令面板中整齐地显示,而不是丢失(如果在
before/after 挂钩中运行异步功能,则可能会发生)或仅出现在控制台中。 -
使用
cypress-terminal-report ,有望将日志从浏览器复制到stdout,这意味着您将拥有在CI / CD设置中调试测试所需的所有信息,该配置会使运行后浏览器日志丢失 -
作为无关的奖励,我分享了我的
cylog 方法,该方法有两件事:- 登录消息赛普拉斯命令面板
-
使用Cypress任务将消息记录到stdout,该任务使用Node而不是在浏览器中执行。我可以登录浏览器并依靠
cypress-terminal-report 进行记录,但是当在before挂钩中发生错误时,它并不总是记录日志,因此我更喜欢在可能的情况下使用Node。
希望这不会让您不知所措,而且很有用!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /** * Work around for making some asynchronous operations look synchronous, or using their output in a proper Cypress * {@link Chainable}. Use sparingly, only suitable for things that have to be asynchronous, like AWS SDK call. */ export function cyasync< T >(alias: string, promise: () => Promise< T >, timeout?: Duration): Chainable< T > { const options = timeout ? { timeout: timeout.toMillis() } : {} return cy .wrap(null) .as(alias) .then(options, async () => { try { asyncLog(`Running async task"${alias}"`) const start = Instant.now() const result = await promise() const duration = Duration.between(start, Instant.now()) asyncLog(`Successfully executed task"${alias}" in ${duration}`) return result } catch (e) { const message = `Failed"${alias}" due to ${Logger.formatError(e)}` asyncLog(message, Level.ERROR) throw new Error(message) } }) } /** * Logs both to the console (in Node mode, so appears in the CLI/Hydra logs) and as a Cypress message * (appears in Cypress UI) for easy debugging. WARNING: do not call this method from an async piece of code. * Use {@link asyncLog} instead. */ export function cylog(message: string, level: Level = Level.INFO) { const formatted = formatMessage(message, level) cy.log(formatted) cy.task('log', { level, message: formatted }, { log: false }) } /** * When calling from an async method (which you should reconsider anyway, and avoid most of the time), * use this method to perform a simple console log, since Cypress operations behave badly in promises. */ export function asyncLog(message: string, level: Level = Level.INFO) { getLogger(level)(formatMessage(message, level)) } |
对于日志记录,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | modules.export = (on, config) => { setUpLogging(on) // rest of your setup... } function setUpLogging(on) { // this task executes Node code as opposed to running in the browser. This thus allows writing out to the console/Hydra // logs as opposed to inside of the browser. on('task', { log(event) { getLogger(event.level)(event.message); return null; }, }); // best-effort attempt at logging Cypress commands and browser logs // https://www.npmjs.com/package/cypress-terminal-report require('cypress-terminal-report/src/installLogsPrinter')(on, { printLogsToConsole: 'always' }) } function getLogger(level) { switch (level) { case 'info': return console.log case 'error': return console.error case 'warn': return console.warn default: throw Error('Unrecognized log level: ' + level) } } |
和
1 2 3 | import installLogsCollector from 'cypress-terminal-report/src/installLogsCollector' installLogsCollector({}) |