Cypress - 异步行为
Cypress - 理解异步行为
Section titled “Cypress - 理解异步行为”Cypress 命令不像典型的 JavaScript 代码那样立即执行。相反,它们被加入队列并依次串行执行。这是使 Cypress 测试可靠且可预测的核心概念。虽然命令本身是异步的,但 Cypress 会为您管理这种异步性。
您不能直接使用 const result = cy.get(...) 将 Cypress 命令的结果赋给变量,因为 cy.get()(以及大多数 cy 命令)不返回元素或值本身。它们返回一个 ‘chainer’ 对象,它类似于 Promise,允许您链式调用更多命令或使用 .then() 来处理返回的主体(subject)。
命令队列和 .then()
Section titled “命令队列和 .then()”Cypress 命令会被添加到一个队列中。每个命令只有在前一个命令成功完成后才会运行。要与“主体”(命令返回的值或元素)交互,您需要使用 .then()。
// 测试套件describe('Asynchronous Behavior Example', () => { it('Should demonstrate command queuing and .then()', () => { cy.visit('https://your-app.com/login'); // 示例 URL
// 这个 console.log 立即执行,甚至在 cy.visit 开始加载之前 console.log('This logs first (standard JavaScript)');
cy.get('h1').invoke('text').then((headerText) => { // 这段代码在 cy.get() 找到 h1 并 invoke('text') 获取其文本之后运行 console.log('Header text from .then():', headerText); // 您可以在这里使用 headerText 进行断言或进一步的逻辑处理 expect(headerText).to.include('Login Page'); });
cy.get('input[name="email"]').type('test@example.com');
// 由于使用了 cy.then(),这个 console.log 也是一个 Cypress 命令 // 它被加入队列,在 .type() 命令完成后运行 cy.then(() => { console.log('This logs after typing into email field (Cypress queued)'); });
// 另一个标准的 JavaScript console.log // 它在上面的 cy.then() 之前输出,因为它不是 Cypress 链的一部分 console.log('This logs second (standard JavaScript, after first log, before Cypress logs from .then)'); });});控制台中的执行顺序(简化版):
- “This logs first (standard JavaScript)”
- “This logs second (standard JavaScript, after first log, before Cypress logs from .then)”
- //(页面加载且找到 h1 后)
- “Header text from .then(): Login Page Example”
- //(输入电子邮件后)
- “This logs after typing into email field (Cypress queued)”
关键在于 Cypress 命令管理自己的执行流。标准的 JavaScript(如测试中直接使用的 console.log)根据 JavaScript 的事件循环运行,通常在 Cypress 命令完成之前执行。
Promises 与 Cypress
Section titled “Promises 与 Cypress”虽然 Cypress 命令是“promise-like”,但它们不是原生的 JavaScript Promises。您不能像处理原生 Promises 那样直接将 async/await 与 cy 命令一起使用,除非您理解 Cypress 如何处理它们。然而,.then() 允许您接入 Cypress 命令链,类似于 .then() 在 Promises 中的工作方式,以便在命令主体(subject)返回后执行代码。
Cypress 在内部管理命令的“状态”(例如,pending、resolved、rejected)。如果断言失败或未找到元素,它会自动重试命令一定时间(默认:defaultCommandTimeout),这极大地提高了测试的稳定性。
如果您需要在 Cypress 测试中处理实际的 JavaScript Promises(例如,来自应用程序工具函数或第三方库),您可以使用 cy.wrap() 将 Promise 的解析值带入 Cypress 命令链中:
it('Works with a native Promise', () => { const nativePromise = new Promise((resolve) => { setTimeout(() => resolve('Data from Promise'), 100); });
cy.wrap(nativePromise).then((data) => { console.log(data); // '来自 Promise 的数据' expect(data).to.equal('Data from Promise'); });});避免 async/await 的陷阱
Section titled “避免 async/await 的陷阱”虽然您 可以 通过将测试函数声明为 async 并 await cy 命令来在 Cypress 中使用 async/await,但这通常不是必需的,并且如果处理不当有时会导致混淆。Cypress 的链式调用配合 .then() 通常就足够且更符合其惯用法。
// 通常更推荐的 Cypress 方式:cy.get('input').type('hello').then(() => { cy.get('button').click();});
// 使用 async/await 也是可能的,但需要小心:it('Example with async/await', async () => { await cy.get('input').type('hello'); // 这里的 'await' 确保此命令在下一行 JS 代码之前完成, // 但 Cypress 的真正强大之处在于其命令队列,而非传统的 async/await。 await cy.get('button').click();});Cypress 模型的主要优势在于它将动作和断言串行化,等待您的应用达到特定状态后再继续。这避免了其他严重依赖手动等待或休眠的测试工具中常见的 flakiness(不稳定)。
延伸阅读: