菜单

命令执行

相关源文件

本文档解释了 Cypress 中的命令执行系统,这是测试中命令排队和执行的核心机制。它涵盖了命令的创建、入队、执行以及它们如何与被测应用程序 (AUT) 交互。有关网络拦截的信息,请参阅 网络拦截

命令执行概述

Cypress 测试使用链式 API (cy.command()) 编写,但调用时不会立即执行。相反,Cypress 使用命令队列以顺序、异步的方式管理执行。此队列是 Cypress 处理时序、重试命令和提供详细日志能力的基础。

来源: packages/driver/src/cypress/command_queue.ts, packages/driver/src/cypress/cy.ts

核心命令架构

命令执行系统包含四个主要组件

来源: packages/driver/src/cypress/cy.ts, packages/driver/src/cypress/command_queue.ts, packages/driver/src/cypress/command.ts, packages/driver/src/cypress/chainer.ts

$Cy

The $Cy 类是命令执行系统的主要控制器。它提供了创建、排队和执行命令的接口。每个测试都有一个 $Cy 实例,该实例作为全局 cy 对象可用。

主要职责

  • 将命令添加到命令队列
  • 管理命令执行
  • 维护 subject 链(在命令之间传递的结果)
  • 处理重试和超时
  • 提供断言和实用程序

来源: packages/driver/src/cypress/cy.ts:142-783

CommandQueue

The CommandQueue 类管理待执行命令的队列。它通过特定于 Cypress 命令执行的附加功能扩展了一个基础的 Queue 类。

主要职责

  • 按顺序存储命令
  • 一次执行一个命令
  • 管理命令状态转换
  • 处理命令之间的 subject 流
  • 处理命令的重试

来源: packages/driver/src/cypress/command_queue.ts:114-624

$Command

The $Command 类代表队列中的单个命令。每个命令都有一组属性和在处理过程中会发生变化的 state。

关键属性

  • name: 命令的名称(例如,“get”、“click”)
  • args: 传递给命令的参数
  • fn: 要执行的函数
  • type: 命令类型(父命令、子命令、双命令、查询命令)
  • subject: 传递给命令或从命令返回的 subject
  • prev/next: 指向队列中相邻命令的引用

状态

  • queued: 命令正在等待执行
  • pending: 命令正在执行中
  • passed: 命令成功完成
  • failed: 命令遇到错误
  • skipped: 命令已被跳过

来源: packages/driver/src/cypress/command.ts:6-183

$Chainer

The $Chainer class提供了链式 API,该 API 允许在 Cypress 测试中使用流畅的语法。每个链式命令都会创建一个新的 $Chainer 实例。

主要职责

  • 提供方法链式调用
  • 捕获用户调用堆栈以用于错误消息
  • 创建唯一的 chainerId 以跟踪命令关系

来源: packages/driver/src/cypress/chainer.ts:5-38

命令执行流程

Cypress 中的命令执行流程遵循一系列不同的步骤

来源: packages/driver/src/cypress/cy.ts:619-782, packages/driver/src/cypress/command_queue.ts:231-402

1. 命令创建和入队

当用户编写 cy.get('.button') 时,会发生以下情况:

  1. 在全局 cy 对象上调用 get 方法
  2. 创建一个新的 $Chainer 实例
  3. 创建一个命令对象,名称为“get”,参数为 [‘.button’] 等
  4. 命令被入队到命令队列中
  5. 返回 chainer 实例以允许进一步的链式调用

来源: packages/driver/src/cypress/cy.ts:720-782, packages/driver/src/cypress/command_queue.ts:150-177

2. 命令执行

一旦命令进入队列,它们就会被逐一执行

  1. 队列选择下一个要运行的命令
  2. 将命令的 state 设置为“pending”
  3. 使用适当的 subject 执行命令函数
  4. 结果成为下一个命令的 subject
  5. 将命令的 state 设置为“passed”或“failed”
  6. 队列移动到下一个命令

来源: packages/driver/src/cypress/command_queue.ts:231-402

3. Subject 链式调用

每个命令都接收前一个命令的 subject,并可以返回一个新的 subject 给下一个命令。这构成了“subject 链”,使命令能够操作前一个命令的结果。

例如,在 cy.get('.button').click()

  1. cy.get('.button') 返回一个 jQuery 元素作为其 subject
  2. .click() 接收该元素作为其 subject 并执行点击操作

此 subject 链通过 chainerId 属性进行管理,该属性将命令链接在一个特定的链中。

来源: packages/driver/src/cypress/cy.ts:242-244, packages/driver/src/cypress/command_queue.ts:342-386

命令类型

Cypress 具有不同类型的命令,每种命令都有特定的行为

父命令

父命令启动新的链,不需要前一个 subject

  • 示例: cy.get(), cy.visit(), cy.wait()
  • 它们总是启动一个新的 subject 链

子命令

子命令需要前一个命令的 subject

  • 示例: .click(), .type(), .should()
  • 它们操作前一个命令提供的 subject
  • 如果它们在没有父命令的情况下调用,则会失败

双命令

双命令可以作为父命令或子命令

  • 示例: cy.contains(), cy.screenshot()
  • 当用作父命令时,它们会启动一个新的 subject 链
  • 当用作子命令时,它们操作前一个 subject

查询 vs 命令

Cypress 区分了查询和命令

  • 查询: 返回一个可以重试的函数(例如,cy.get()
    • 它们会自动重试,直到成功或超时
    • 示例包括 DOM 查询,如 get, find, 和 contains
  • 命令: 执行无法重试的操作(例如,cy.click()
    • 它们只执行一次,成功或失败
    • 示例包括操作,如 click, type, 和 select

来源: packages/driver/src/cypress/command_queue.ts:72-112, packages/driver/src/cypress/commands.ts:87-122

自定义命令

Cypress 允许用户通过自定义命令扩展其命令 API。这些命令使用 Cypress.Commands.add() 方法进行注册。

在内部,自定义命令遵循与内置命令相同的执行流程。

  1. 命令使用 Commands 模块进行注册。
  2. 将一个方法添加到 $Chainer.prototype
  3. 调用时,该命令与其他任何命令一样被入队并执行。

根据 prevSubject 选项,自定义命令可以是父命令、子命令或双命令。

  • prevSubject: false (默认): 父命令,不需要前一个 subject
  • prevSubject: true: 子命令,需要前一个 subject
  • prevSubject: 'optional': 双命令,可以带或不带前一个 subject 使用

还可以通过使用 Cypress.Commands.addQuery() 使自定义命令成为查询。

来源: packages/driver/src/cypress/commands.ts:43-204, packages/driver/cypress/e2e/commands/commands.cy.js:41-156

高级命令概念

命令重试性

Cypress 的一个关键特性是自动重试查询和断言,直到它们成功或超时。

此重试机制是在 $Cy 类的 retry 方法中实现的,并适用于

  • DOM 查询(例如,cy.get(), cy.find()
  • 断言(例如,.should('exist'), .should('have.text', 'Hello')

来源: packages/driver/src/cypress/cy.ts:259-269

稳定性和时序

Cypress 在继续执行命令之前会等待应用程序稳定。这确保了在与之交互之前应用程序处于一致的状态。

稳定性系统

  • 等待页面加载完成
  • 暂停动画期间的执行
  • 跟踪网络请求
  • 在命令之前和之后管理应用程序的稳定性

来源: packages/driver/src/cy/commands/navigation.ts:170-410

错误处理

当命令失败时,Cypress 会

  1. 停止命令队列
  2. 用上下文信息增强错误
  3. 在 UI 中显示错误
  4. 提供堆栈跟踪和附加上下文

错误处理系统旨在为开发人员提供有关问题所在以及发生原因的清晰信息。

来源: packages/driver/src/cypress/cy.ts:364-469, packages/driver/src/cypress/error_messages.ts

命令队列和异步性

与大多数 JavaScript 测试库不同,Cypress 不在其公共 API 中使用 Promise 来处理异步性。相反,它使用命令队列来顺序执行命令。这种方法具有几个优点:

  1. 更简单的 API,无需 Promise 链或 async/await
  2. 自动等待元素、动画和网络请求
  3. 更好的错误消息和更多上下文
  4. 带快照的时间旅行调试

在内部,Cypress 使用 Promise 和 async 函数来实现命令队列。 CommandQueue.run() 方法按顺序处理命令,并使用 Promise 来处理异步性。

来源: packages/driver/src/cypress/command_queue.ts:406-459

结论

命令执行系统是 Cypress 运行的核心。它为 Cypress 特有的测试方法提供了基础,包括自动等待、重试和详细的错误消息。理解这个系统是编写有效的 Cypress 测试和使用自定义命令扩展 Cypress 的关键。

其核心在于系统由:

  • 管理执行流程的命令队列
  • 在命令之间传递结果的 subject 链
  • 具有特定行为的不同类型的命令
  • 用于查询和断言的重试机制
  • 稳定性跟踪,以确保应用程序状态的一致性

这种架构在复杂的浏览器环境中,在提供强大的测试执行能力的同时,也带来了 Cypress 用户友好的 API。