Skip to content

Cypress - 异步行为

Cypress 命令不像典型的 JavaScript 代码那样立即执行。相反,它们被加入队列并依次串行执行。这是使 Cypress 测试可靠且可预测的核心概念。虽然命令本身是异步的,但 Cypress 会为您管理这种异步性。

您不能直接使用 const result = cy.get(...) 将 Cypress 命令的结果赋给变量,因为 cy.get()(以及大多数 cy 命令)不返回元素或值本身。它们返回一个 ‘chainer’ 对象,它类似于 Promise,允许您链式调用更多命令或使用 .then() 来处理返回的主体(subject)。

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)');
});
});

控制台中的执行顺序(简化版):

  1. “This logs first (standard JavaScript)”
  2. “This logs second (standard JavaScript, after first log, before Cypress logs from .then)”
  3. //(页面加载且找到 h1 后)
  4. “Header text from .then(): Login Page Example”
  5. //(输入电子邮件后)
  6. “This logs after typing into email field (Cypress queued)”

关键在于 Cypress 命令管理自己的执行流。标准的 JavaScript(如测试中直接使用的 console.log)根据 JavaScript 的事件循环运行,通常在 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 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(不稳定)。

延伸阅读: