Cypress-端到端-Web-测试-全-

Cypress 端到端 Web 测试(全)

原文:zh.annas-archive.org/md5/CF3AC9E3793BF8801DD5A5B999C00FD9

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Cypress 是一个专门用于进行前端测试的 JavaScript 自动化测试框架。Cypress 在重新发明测试的执行方式方面表现出色,特别是对于现代 Web。与 Selenium WebDriver 等其他测试框架不同,Cypress 运行速度更快,因为它在浏览器中运行,并且与其他测试框架相比,学习曲线更低。

使用前端应用程序的开发人员将能够利用这本实用指南的知识,并在端到端测试中发展他们的技能。这本书采用了实践方法来实施和相关方法,让您可以立即投入运行并提高生产力。

这本书适合对象

这本书适合测试专业人士、软件和 Web 测试人员,以及精通 JavaScript 的 Web 开发人员,可能熟悉或不熟悉自动化测试的概念。前三章提供了一个快速入门指南,将帮助您熟悉 Cypress 的工作原理,以及如何开始,如果您是一个完全的 Cypress 新手。如果您是一个想要迁移到 Cypress 并发现其功能的 Selenium 测试人员,您会发现这本书非常有用。需要对 Web 测试和 JavaScript 有很好的理解。

这本书涵盖了什么

第一章安装和设置 Cypress,带您了解使用 Cypress 的基本知识,包括安装 Cypress 包、默认配置和自定义设置。在本章中,您将了解 Cypress 的工作原理,它运行所需的模块,测试文件命名建议,以及如何开始使用 Cypress。了解 Cypress 的工作原理将确保您能够掌握 Cypress 的内部工作原理,并能够全面理解 Cypress 框架的结构,从而能够独立安装和设置后续项目。

第二章Selenium WebDriver 和 Cypress 之间的区别,我们将探讨 Cypress 与 Selenium WebDriver 的不同之处,并突出选择 Cypress 运行端到端测试的一些优缺点。在本章中,我们还将探讨使 Cypress 比 Selenium 更适合测试的因素,以及用户如何扩展其功能。

第三章使用 Cypress 命令行工具,让您了解不同的 Cypress 命令,您可以使用这些命令来执行 Cypress 命令。本章将解释如何运行命令,以及如何使用 Cypress 命令调试应用程序。

第四章编写您的第一个测试,将带您使用 Cypress 编写您的第一个测试。我们将从一个通过的测试开始,以检查一切是否正常工作,然后转移到一个失败的测试,然后我们将看到 Cypress 的行为以及自动重新加载功能是如何工作的。在本章的第二部分,我们将专注于更高级的场景,让您了解如何正确编写 Cypress 测试。

第五章调试 Cypress 测试,深入探讨了 Cypress 包含的不同类型的工具,以帮助调试应用程序。使用 Cypress 的调试工具,您将学会如何回溯到每个命令的快照,查看执行过程中发生的不同页面事件,并可视化不同命令以及元素隐藏和发现的时间。您还将学会如何在命令快照之间前进和后退,以及以迭代方式暂停和逐步执行命令快照。

第六章, 使用 TDD 方法编写 Cypress 测试,向您介绍了测试驱动开发TDD)的概念以及如何将其应用于编写 Cypress 测试。您将学习如何使用 TDD 方法编写测试,以及如何在尚未开发的应用程序中实际应用 TDD。

第七章, 了解 Cypress 中的元素交互,介绍了如何与 DOM 的各种元素进行交互。本章还将教您如何与动画交互,如何悬停在元素上,以及如何检查元素是否被禁用。通过本章结束时,您将能够舒适地浏览 DOM 元素并为元素编写有意义的测试。

第八章, 了解 Cypress 中的变量和别名,探讨了如何通过使用别名来处理异步命令。我们还将确定通过使用别名可以简化测试的方法。最后,我们将确定如何在路由和请求中使用别名。

第九章, Cypress 测试运行器的高级用法,介绍了如何利用 Cypress 测试运行器编写更好的测试。我们将重点放在仪表板和选择器工具上。我们将学习如何使用仪表板来理解间谍和存根的概念,以及 Cypress 如何解释它们。

第十章, 练习-导航和网络请求,向您展示了实际示例和练习,旨在练习如何使用和进行网络请求的导航。该练习还将结合别名和变量的概念,以确保您能够链接本书第二部分学到的不同概念。

第十一章, 练习-存根和间谍 XHR 请求,介绍了理解 XHR 请求以及 Cypress 如何帮助存根需要太长时间或者复杂以接收响应的请求。Cypress 的存根将对确保实施的测试不会出现问题以及我们可以获得自定义响应而不是等待请求的服务器响应非常重要。

第十二章, Cypress 中的视觉测试,介绍了 Cypress 中的视觉测试工作原理。我们将探讨视觉测试是什么,不同类型的测试,以及现代网络中视觉测试的重要性。我们还将研究视口以及它们如何影响视觉测试的过程,最后看看视觉测试自动化工具,如 Applitools 和 Percy,我们可以使用它们进行视觉验证。

为了充分利用本书

您需要一些 JavaScript 的理解,还需要在您的计算机上安装 Node.js 和 Yarn 和 npm 软件包管理器。所有给出的代码示例都在 macOS 上进行了测试,并且应该在所有 Linux 操作系统上都可以正常工作。对于 Windows 操作系统,特别是最后三章,请在技术要求部分的信息框中阅读有关如何在 Windows 上运行命令的附加说明。在撰写本文时,所有示例都已经使用 Cypress 版本 6.2.1 进行了测试。

重要提示

在出版时,本书是基于 Cypress 版本 6.2.1 编写的,一些功能可能已经被破坏或废弃。请查看我们的 GitHub 存储库以获取最新的代码更新和更改。

如果您使用的是本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做将帮助您避免与复制和粘贴代码相关的潜在错误。

始终尝试练习;它们不仅仅是为了好玩,而是精心设计帮助您学习和掌握章节内容。

下载示例代码文件

您可以从您在www.packt.com的账户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com

  2. 选择支持选项卡。

  3. 点击代码下载

  4. 搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用以下最新版本的解压缩或提取文件夹:

  • Windows 系统下使用 WinRAR/7-Zip

  • Mac 系统下使用 Zipeg/iZip/UnRarX

  • Linux 系统下使用 7-Zip/PeaZip

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有来自我们丰富的书籍和视频目录的其他代码包,可在github.com/PacktPublishing/上找到。去看看吧!

使用的约定

本书中使用了许多文本约定。

文本中的代码:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。例如:"cy.intercept()命令监听 XHR 响应,并知道 Cypress 何时为特定的 XHR 请求返回响应。"

代码块设置如下:

it('can wait for a comment response', () => {
      cy.request('https://jsonplaceholder.cypress.io/comments/6')
    .as('sixthComment');
      cy.get('@sixthComment').should((response) => {
        expect(response.body.id).to.eq(6)
    });
 });

任何命令行输入或输出都以以下方式编写:

npm run cypress:open 

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。例如:"要做到这一点,在浏览器中打开浏览器控制台,然后点击网络选项卡,然后选择XHR 过滤器选项。"

提示或重要说明

看起来像这样。

第一部分:Cypress 作为前端应用的端到端测试解决方案

本节重点介绍了我们将在整本书中使用的基本原则和开发方法论。这些入门章节对于更好地了解 Cypress、如何设置它以及它与 Selenium WebDriver 等其他测试工具的区别至关重要。

我们将首先看如何安装和设置 Cypress。然后,我们将涵盖 Cypress 架构的不同主题以及 Cypress 和 Selenium 之间的区别。然后,我们最终将开始编写我们的第一个测试,从中更好地理解如何正确调试 Cypress 测试。

在这一部分,我们将涵盖以下章节:

  • 第一章, 安装和设置 Cypress

  • 第二章, Selenium WebDriver 和 Cypress 之间的区别

  • 第三章, 使用 Cypress 命令行工具

  • 第四章, 编写您的第一个测试

  • 第五章, 调试 Cypress 测试

第一章:安装和设置 Cypress

Cypress 是一个为现代 Web 应用程序构建和设计的端到端测试自动化框架。它专注于通过确保您可以在浏览器上编写、调试和运行测试而无需额外的配置或额外的包来消除测试中的不一致性。Cypress 作为一个独立的应用程序工作,并且可以在 macOS、Unix/Linux 和 Windows 操作系统上使用连字符应用程序或命令行工具进行安装。Cypress 主要是为使用 JavaScript 编写他们的应用程序的开发人员构建的,因为它可以用来测试在浏览器上运行的所有应用程序。在本章中,我们将涵盖以下主题:

  • 在 Windows 上安装 Cypress

  • 在 macOS 上安装 Cypress

  • 通过直接下载安装 Cypress

  • 打开 Cypress 测试运行器

  • 切换 Cypress 浏览器

  • 添加 npm 脚本

  • 运行 Cypress 测试

通过本章结束时,您将了解如何在 Windows 和 Mac 操作系统上正确设置 Cypress 以及如何运行 Cypress 测试。您还将了解如何使用 npm 脚本来自动化运行测试和打开测试运行器的过程。

技术要求

Cypress 可以作为一个独立的应用程序安装在您的计算机上,并且可以在至少有 2GB RAM 并满足以下任一操作系统要求的机器上运行:

  • macOS 10.9 及以上版本(仅 64 位)

  • Linux Ubuntu 12.04 及以上版本,Fedora 21 和 Debian 8(仅 64 位)

  • Windows 7 及以上

为了在这里列出的操作系统之一上使用 Cypress,必须首先安装 Node.js 8 或更高版本。Node.js 是一个 JavaScript 运行时环境,允许在浏览器之外运行 JavaScript 代码。安装 Node.js 会安装 npm,它允许我们从www.npmjs.com/安装 JavaScript 包。npm 是 Node.js 的默认包管理器,用户可以使用它或使用第三方包管理器,如 Yarn。在本节中,我们将在 macOS 和 Windows 操作系统上安装 Cypress。

在 Windows 上安装 Cypress

在本节中,我们将在 Windows 操作系统上安装 Cypress 和 Node.js,以便我们可以运行我们的测试。

下载并安装 Node.js

以下步骤将指导您完成安装 Node.js:

  1. 访问官方 Node.js 网站(nodejs.org/en/download/)。

  2. 选择 Windows 安装程序选项。

  3. 下载安装程序包。

  4. 按照 Node.js 网站上的说明安装 Node.js 包。

接下来,让我们初始化项目。

初始化项目

作为最佳实践,Cypress 安装在项目所在的目录中;这样,我们可以确保 Cypress 测试属于项目。在我们的情况下,我们将在Documents内创建一个名为cypress-tests的文件夹,然后在安装 Cypress 时导航到该目录。我们可以在 Windows PowerShell 终端中使用以下命令来创建cypress-tests目录并导航到该目录:

$ cd .\Documents
$ cd mkdir cypress-tests

成功运行这些命令后,我们将启动 PowerShell 并导航到我们刚刚创建的目录,使用以下命令:

$ cd .\Documents\cypress-tests

创建目录后,我们将通过在 PowerShell 中运行以下命令来初始化一个空的 JavaScript 项目:

$ npm init –y

这将创建一个默认的package.json文件,用于定义我们的项目。

在 Windows 上安装 Cypress

现在,我们将使用以下命令在我们的项目目录中使用 npm 安装 Cypress:

$ npm install cypress --save-dev

运行此命令后,您应该能够看到 Cypress 的安装和安装进度。这种方法将 Cypress 安装为我们空项目的dev依赖项。

有关 macOS 安装,请参阅下一节主要部分。

总结-在 Windows 上安装 Cypress

在本节中,我们学习了如何在 Windows 操作系统上安装 Cypress。我们还学会了如何使用 PowerShell 向项目添加 Cypress,以及如何初始化一个空项目。在下一节中,我们将看看如何在 macOS 上安装 Cypress。

在 MacOS 上安装 Cypress

在本节中,我将使用 macOS 机器来安装 Cypress 和 Node.js。在本节结束时,您将学会如何初始化一个空的 JavaScript 项目,以及如何将 Cypress 测试框架添加到 macOS 中。我们还将深入探讨如何在我们的项目中使用 npm、Yarn 或直接 Cypress 下载。

安装 Node.js

以下步骤将指导您安装 Node.js:

  1. 访问官方 Node.js 网站(nodejs.org/en/download/)。

  2. 选择 macOS 安装程序选项。

  3. 下载安装程序包。

  4. 按照 Node.js 网站上的说明安装 Node.js 包。

接下来,让我们初始化项目。

初始化项目

要安装 Cypress,我们需要导航到项目文件夹,并在我们希望 Cypress 测试位于的位置进行安装。在我们的情况下,我们将在“文档”中创建一个名为cypress-tests的文件夹,然后在安装 Cypress 时使用我们的终端导航到该目录。然后,我们将启动我们的终端应用程序,并使用以下命令导航到我们刚刚创建的目录:

$ cd  ~/Documents/cypress-tests

创建目录后,我们将通过运行以下命令初始化一个空的 JavaScript 项目:

$ npm init –y

这将创建一个默认的package.json文件,用于定义我们的项目。

在 Mac 上安装 Cypress

要安装 Cypress,我们将使用 Node.js 捆绑的 npm 包管理器。为了实现这一点,我们需要运行以下命令:

$ npm install cypress --save-dev

运行此命令后,您应该能够在package.json文件中看到 Cypress 的安装进度,并在命令行上看到安装进度。这种方法将 Cypress 安装为我们空项目的dev依赖项。

作为 Windows 和 macOS 都可以使用的替代包管理器,您可以使用 Yarn。我们将在下一节中看到如何使用 Yarn 安装 Cypress。

使用 Yarn 安装 Cypress

在 Windows 和 macOS 中,您可以选择另一种包管理器。可用的替代方案之一是 Yarn 包管理器。与 npm 一样,您首先需要使用 macOS Homebrew 包管理器下载 Yarn 包管理器,方法是运行以下命令:

$ brew install yarn

就像 npm 一样,Yarn 可以管理项目的依赖关系,并且可以用作项目管理器。Yarn 比 npm 的一个优势是它能够以一种不需要重新下载依赖项的方式缓存已下载的包,因此更好地利用资源。

安装 Yarn 后,我们可以使用它来安装包,就像我们使用 npm 一样,方法是运行以下命令:

$ yarn add cypress –dev

我们还有最后一种安装方法,即通过直接下载。这将在下一节中介绍。

通过直接下载安装 Cypress

我们可以通过直接下载在 Windows、Linux 或 macOS 上安装 Cypress。如果您不需要安装 Cypress 附带的依赖项,或者只是尝试 Cypress,这种方法是推荐的。重要的是要注意,尽管这是安装 Cypress 的最快方式,但这个版本不具备诸如记录测试到仪表板的功能。

以下步骤将指导您通过直接下载安装 Cypress:

  1. 导航到cypress.io

  2. 选择立即下载链接。

Cypress 将自动下载,因为它将自动检测下载.zip 文件的用户的操作系统。然后,您应该解压缩 zip 文件并在不安装任何其他依赖项的情况下运行 Cypress。

总结-在 macOS 上安装 Cypress

在本节中,我们学习了如何使用 npm 在 macOS 上安装 Cypress 测试框架,以及如何初始化一个将利用 Cypress 测试的空 JavaScript 项目。我们还学习了如何使用 Yarn 软件包管理器安装 Cypress,以及如何在不使用任何软件包管理器的情况下直接下载 Cypress 到我们的项目中。在下一节中,我们将看看如何打开 Cypress 测试框架。

打开 Cypress

安装 Cypress 是编写端到端测试旅程的第一步;现在,我们需要学习如何使用 Cypress 提供的工具来使用图形用户界面和仪表板运行测试。有四种方法可以运行已在您的计算机上安装的 Cypress 可执行文件。打开 Cypress 后,您应该看到 Cypress 测试运行器。无论以哪种方式打开 Cypress,您所看到的测试运行器仪表板都是相同的。以下部分详细介绍了打开和运行 Cypress 的不同方法。

使用 Npx 运行

npx 用于执行 npm 包二进制文件,并且从版本 5.2 开始,所有 npm 版本都带有 npx。也可以使用 npm 从npmjs.com安装 npx。要使用 npx 运行 Cypress,您需要运行以下命令:

 npx cypress open

使用 Yarn 运行

如果使用 Yarn 安装了 Cypress,您可以使用以下命令打开 Cypress:

Yarn run cypress open

使用 node 模块路径运行

Cypress 也可以通过引用 node 模块上的安装根路径来运行。这可以通过使用node_modules bin 中 Cypress 可执行文件的完整路径或使用 npm bin 快捷方式来实现,如下节所示。

使用完整路径启动 Cypress

这种启动 Cypress 的方法引用了node_modules中安装的 Cypress 可执行文件,并通过运行可执行文件来打开 Cypress:

$ ./node_modules/.bin/cypress open

使用快捷方式启动 Cypress

就像使用完整路径启动 Cypress 一样,这种方法以相同的方式启动 Cypress,但是它不是引用完整路径,而是使用 npm bin 变量来定位node_modules bin 文件夹的默认位置:

$(npm bin)/cypress open

桌面应用程序启动

如果您将应用程序下载为桌面应用程序,您可以通过导航到解压后的 Cypress 文件夹的位置,并单击该文件夹中存在的 Cypress 可执行文件来打开 Cypress。

现在我们已经成功通过我们喜欢的方法打开了 Cypress,我们将看看如果我们不想使用 Cypress 捆绑的默认浏览器,我们如何在 Cypress 中选择替代浏览器。

总结-打开 Cypress

在本节中,我们学习了如何打开 Cypress 测试框架仪表板,以及如何以不同的方式运行 Cypress,包括使用npxYarnnode_modules路径运行 Cypress 仪表板。在下一节中,我们将学习如何切换在 Cypress 中运行的测试的浏览器。

切换浏览器

Cypress 在安装时默认使用 Electron 作为浏览器,但它也可以与其他兼容的浏览器集成,这些浏览器包含Chromium 项目,除了 Firefox。目前,Cypress 支持 Firefox 浏览器、Chrome 浏览器、Chromium 和 Edge 浏览器。启动 Cypress 时,它将自动查找运行机器上的所有兼容浏览器,您可以随时使用测试运行器在这些浏览器之间切换。要从一个浏览器切换到另一个浏览器,您需要点击右上角的浏览器按钮,并从下拉链接中选择替代浏览器。

Cypress 测试也可以通过命令行在不同的浏览器上运行或打开,可以通过在打开 Cypress 测试运行器或运行 Cypress 测试时指定浏览器来实现。所有基于 Chromium 的浏览器、Edge 和 Firefox 都可以使用以下命令在命令行中启动:

$ cypress run --browser {browser-name}

命令中指定的browser-name可以是 Edge、Chrome 或 Firefox。要指定 Cypress 应启动的浏览器的路径,您可以选择使用浏览器的可执行二进制文件而不是浏览器的名称来运行浏览器名称,如下所示:

$ cypress run --browser /path/to/binary/of/browser

能够在 Cypress 中切换浏览器可以确保用户可以在不同设备上运行其测试套件,并验证不同浏览器的输出在整个测试套件中是一致的。在 Cypress 上切换浏览器还可以确保测试的验证可以进行,并且所有元素可见或可以在一个浏览器上执行的操作也可以在另一个浏览器上执行。

让我们利用到目前为止所学到的知识来尝试使用 Cypress 进行实际练习。

练习

结合打开 Cypress 和切换浏览器的知识,尝试以下步骤:

  1. 导航到我们在初始化 Cypress 时创建的文件夹。

  2. 运行 Cypress 启动时自动生成的所有默认测试。

  3. 在测试运行器上切换浏览器。

  4. 使用不同的浏览器重新运行测试。

现在我们已经学会了如何在不同的浏览器中运行 Cypress 测试,在接下来的部分中,我们将探讨如何使用 npm 脚本自动化运行测试的过程。

回顾 - 切换浏览器

在本节中,我们学习了 Cypress 支持的不同浏览器以及如何切换不同的 Cypress 浏览器,无论是使用命令行还是使用 Cypress 仪表板。我们还进行了一个简单的练习,以帮助我们了解 Cypress 浏览器切换的工作原理,以及如何使用 Cypress 运行我们的测试。在下一节中,我们将学习如何将 npm 脚本添加到我们的package.json文件中,以自动化一些 Cypress 任务。

添加 npm 脚本

scriptspackage.json的属性,它使用户能够通过 JavaScript 应用程序的命令行运行命令。npm 脚本可用于向应用程序的属性添加环境变量,将应用程序打包成生产就绪的捆绑包,运行测试,或自动化 JavaScript 应用程序中的任何其他活动。npm 脚本可以根据用户的偏好和应用程序进行自定义,也可以按照npmjs.com定义的方式使用。在本节中,我们将学习如何编写 npm 脚本来运行我们的 Cypress 测试,打开我们的 Cypress 测试,甚至组合不同的 npm 脚本以实现不同的结果。

打开 Cypress 命令脚本

要创建一个scripts命令来打开 Cypress,您需要编写脚本名称,然后添加 npm 在执行脚本时将运行的命令。在这种情况下,我们打开 Cypress 的命令将嵌入到一个名为open的脚本中。我们可以通过将以下命令添加到package.json中的scripts对象来实现这一点:

"scripts": {
  "open": "npx cypress open" 
}

要运行open命令,您只需运行npm run open命令,测试运行器应该会在 Cypress 测试运行器中选择的默认浏览器上打开。

回顾 - 添加 npm 脚本

在本节中,我们学习了什么是 npm 脚本以及如何将它们添加到package.json文件中。我们还学会了如何运行我们已经添加到package.json文件中的 npm 脚本,以执行和自动化项目中的任务。接下来,我们将学习如何在 Cypress 中运行测试。

运行 Cypress 测试

在本节中,我们将重点放在如何在浏览器上运行 Cypress 测试。为此,我们将编写测试脚本,可以像打开 Cypress 脚本一样运行测试:

"scripts": {
"test:chrome": "cypress run –browser chrome",
"test:firefox": "cypress run –browser firefox" 
}

前面的脚本将用于在 Chrome 浏览器或 Firefox 浏览器中运行测试,具体取决于用户在命令行终端上运行的命令。要执行测试,您可以运行npm run test:chrome在 Chrome 中运行测试,或者npm run test:firefox在 Firefox 中执行测试。命令的第一部分指示 Cypress 以无头模式运行测试,而第二部分指示 Cypress 在哪个浏览器中运行测试。运行 Cypress 测试不仅限于 Chrome 和 Firefox,并且可以扩展到 Cypress 支持的任何浏览器,还可以根据需要自定义运行脚本的名称。

使用脚本组合 Cypress 命令

package.json中的scripts对象为您提供了灵活性,可以组合命令以创建可以执行不同功能的高级命令,例如将环境变量传递给正在运行的测试,甚至指示 Cypress 根据传递的变量运行不同的测试。组合 Cypress 命令确保我们编写短小的可重用语句,然后可以用来构建执行多个功能的命令。在下面的示例中,我们将使用scripts对象编写一个命令来打开 Cypress,设置端口,设置环境,并根据我们选择运行的命令设置浏览器为 Chrome 或 Firefox:

"scripts": {
"test": "cypress run",
"test:dev": "npm test --env=dev",
"test:uat": "npm test --env=uat",
"test:dev:chrome": "npm run test:dev –browser chrome",
"test:uat:chrome": " npm run test:uat –browser chrome", 
"test:dev:firefox": "npm run test:dev –browser firefox",
"test:uat:firefox": "npm run test:uat –browser firefox" 
}

前面的脚本可以在两个浏览器中运行 Cypress 测试。这些脚本还有助于确定要根据-env变量运行测试的环境。最后两个脚本组合了一系列运行 Cypress 的脚本,附加了一个环境变量,并选择了要在其上运行测试的浏览器,这使得package.json中的脚本功能在编写要在测试套件中执行的 Cypress 命令时非常有用。要在 Firefox 中运行测试,我们只需运行npm run test:uat:firefox命令进行 UAT 测试,或者test:dev:firefox进行dev环境的测试。您还可以使用test:uat:chrome在 Chrome 中运行 UAT 测试,或者test:dev:chrome进行dev环境的测试。

重要提示

要在不同的环境中运行测试,您需要在项目中已经设置好在不同环境中运行测试的配置。

总结-运行 Cypress 测试

在本节中,我们看了如何在 Cypress 中执行我们的测试。我们还看了不同的方法,通过传递环境变量和更改脚本中的参数来执行我们的测试的 npm 脚本。我们还学会了如何组合多个 Cypress 命令来运行我们的测试,从而减少我们需要编写的代码量。

总结

在本章中,我们学习了如何在 Windows 和 Mac 操作系统上安装 Cypress。在两种安装中,我们涵盖了作为下载应用程序或通过命令行安装 Cypress。我们还涵盖了使用 Node.js(npm)自带的默认包管理器或第三方依赖管理器(如 Yarn)。我们学会了如何利用测试运行器来运行我们的测试,以及如何在package.json中自动化我们的脚本,以帮助我们有效地运行测试。为了测试我们的知识,我们还进行了一个练习,练习在不同的 Cypress 浏览器中运行测试。

在下一章中,我们将深入探讨 Selenium 和 Cypress 之间的区别,以及为什么 Cypress 应该是首选。我们将进一步建立在本章中所获得的对 Cypress 的理解基础上。

第二章:Selenium WebDriver 和 Cypress 之间的差异

Cypress 和 Selenium WebDriver 都是支持端到端测试的测试自动化框架,当有人提到 Cypress 时,很快就需要比较或找出哪个比另一个更好。在我们开始了解 Selenium WebDriver 和 Cypress 之间的差异之前,我们首先需要了解两个测试框架开发的不同动机以及它们的预期用户是谁。

了解 Cypress 和 Selenium WebDriver 在架构上为何不同将在帮助您了解 Selenium WebDriver 和 Cypress 框架的不同和相似方面起到重要作用。在本节中,我们将评估 WebDriver 和 Cypress 在不同方面的独特之处、不同之处和相似之处。

我们将探讨 Selenium WebDriver 和 Cypress 的不同用例,并检查每个用例适用的目的。我们还将清楚地确定每个测试框架的受众,以及您可以从两者或每个测试框架中获得什么。我们将描述为什么应该选择 Cypress 作为测试自动化框架,以及为什么它是端到端测试自动化的完美候选者。

在了解了 Cypress 和 WebDriver 之间的差异和相似之处后,我们将总结列出使其在端到端网页测试自动化方面脱颖而出并领先于其他测试框架的因素和工具。以下是本章将涵盖的关键主题:

  • 为什么选择 Cypress?

  • 比较 Cypress 和 Selenium WebDriver

  • 使用 Cypress 进行前端测试

通过本章的学习,您将能够了解 Cypress 与 Selenium WebDriver 之间的差异和相似之处,以及它在前端 Web 自动化测试中的优势。

为什么选择 Cypress?

Cypress 是一个端到端测试框架,由开发人员为开发人员和质量保证(QA)工程师编写。Cypress 专注于测试 Web 应用程序,由于自动化 Web 的唯一方法是使用 JavaScript,因此 Cypress 仅支持使用 JavaScript 编写其测试。

Cypress 专门为利用 JavaScript 开发其产品的前端团队编写,以及需要快速开始编写单元、集成和端到端测试的团队,而无需正确设置测试框架的复杂细节。

Cypress 不仅适合初学者,而且确保开发人员或 QA 工程师需要开始测试的一切都已经打包在从 Cypress 网站下载和安装的捆绑包中。Cypress 捆绑了自己的浏览器、测试运行器和 chai 作为断言框架。

拥有一个包含一切所需的捆绑包,可以让任何人开始测试的过程,而无需了解断言框架、测试运行器的设置过程,甚至不需要添加浏览器驱动程序,就像使用 Selenium WebDriver 的情况一样。

Cypress 使用 JavaScript,这使得 JavaScript 开发人员更容易上手并快速掌握 Cypress 的概念。上手的简便性还确保开发人员和 QA 工程师可以快速掌握使用 Cypress 编写测试的技能。由于 Cypress 是用 JavaScript 开发的,使用 JavaScript 的开发人员和 QA 团队发现它更容易调试,也更容易理解错误,因为它们与 JavaScript 应用程序中的错误类似。

Cypress 使用的通用驱动程序目前与 Firefox、Edge、Chrome 和 Chromium 系列浏览器兼容。与 Selenium 不同,Selenium 使用 WebDriver 并通过 HTTP 网络请求与文档对象模型DOM)进行交互,而 Cypress 驱动程序直接在浏览器中工作,无需进行网络请求。在浏览器中运行测试的能力确保了 Cypress 能够有效地解释命令,而不会在命令从测试传递到驱动程序,然后到浏览器中运行的应用程序时引入超时。

使用通用驱动程序还确保了 Cypress 在所有浏览器中使用的方法的一致性,以及测试的标准格式,无论测试将在哪个浏览器中运行。采用这种方法,QA 团队或个人开发人员可以扩展他们的跨浏览器测试,因为唯一需要做的就是在新支持的浏览器上运行他们现有的测试套件。

Cypress 框架在浏览器上运行,因此在架构上与 Selenium WebDriver 等其他测试自动化工具不同。Cypress 在浏览器上运行的能力使其比其他自动化工具具有竞争优势,因为它自带了自动等待序列,否则需要在测试中定义。因此,Cypress 知道何时等待事件,例如网络请求,否则需要在 Selenium 驱动的测试中指定为显式或隐式等待。

像 JavaScript 框架这样的软件开发技术变化比测试技术和可用框架快得多。Cypress 提供了一个独特的机会,开发人员和 QA 工程师可以快速开始编写测试,而无需担心测试设置的问题。消除对底层测试基础设施的担忧不仅加快了测试过程,还确保团队可以快速开始软件开发生命周期中重要的任务。

总结-为什么选择 Cypress?

在这一部分,我们了解了为什么 Cypress 在网页开发测试方面是首选,以及它与其他测试框架(包括 Selenium WebDriver)的区别和优势。在下一部分,我们将直接比较 Cypress 和 Selenium WebDriver 之间的差异和相似之处。

比较 Cypress 和 Selenium WebDriver

很容易陷入这样的陷阱,认为 Cypress 可以取代 Selenium WebDriver,其使用可能会使 Selenium WebDriver 在测试自动化领域完全过时。虽然直接假设 Cypress 要么更好,要么优于 Selenium,或者反过来,这种想法是错误的,在大多数情况下都是不正确的。

在这一部分,我们将介绍 Cypress 的独特之处,以及它的目的如何更多地是作为 Selenium WebDriver 的补充而不是附加。接下来的部分将概述 Selenium WebDriver 和 Cypress 之间的一些差异。

浏览器驱动程序

Cypress 使用一个通用驱动程序来支持所有浏览器,而另一方面,Selenium WebDriver 使用不同的驱动程序来支持每个不同的浏览器。使用通用驱动程序可以在安装时在所有 Cypress 支持的浏览器上运行测试,而无需安装外部驱动程序。另一方面,Selenium 需要为每个浏览器安装驱动程序才能在不同浏览器中运行测试。通用驱动程序还使 Cypress 具有竞争优势,因为开发团队能够解决 WebDriver 中常见的问题,并将功能扩展到不同的浏览器。

重试和等待

Cypress 内置了显式重试来搜索DOM中的元素,并在测试被视为失败之前显式等待事件发生。Cypress 配备了确定请求是否需要等待的事件,然后浏览器决定它们是失败还是通过。Cypress 能够处理等待和重试,因为它与测试一起在浏览器上运行,并能够了解任何给定时间测试的状态。

另一方面,Selenium 利用 HTTP 请求到 WebDriver,因此在测试运行时很难确定是否需要显式或隐式等待。为了解决这个问题,Selenium 用户必须在测试需要等待请求完成后再执行下一步时自己编写等待。在测试运行时,Selenium 也不自带自动重试的功能,而 Cypress 却具备这一功能。

目标使用

Cypress 是为 JavaScript 开发人员和 QA 工程师设计的,他们希望快速建立自动化框架并开始测试端到端的 Web 应用程序,而不需要花费太多带宽来设置测试框架或理解构建测试框架背后的技术。使用 Cypress,开发人员可以轻松地从编写单元测试转向编写集成测试,甚至是接受测试,包括存根外部依赖项的功能,并测试他们的应用程序的行为。Cypress 目前也更偏向于与 Chromium 系列浏览器兼容的开发人员和 QA 实践,包括 Edge,还有目前处于测试阶段的 Firefox。

另一方面,Selenium WebDriver 是用来测试在网络上运行的任何东西。Selenium 专注于想要测试其 Web 应用程序的每个方面的 QA 团队,并不受浏览器兼容性或单一测试运行器等因素的限制,而这在 Cypress 中是存在的。Selenium WebDriver 为用户提供了使用不同浏览器和插件扩展它的选项,还支持不同的语言,如 Java、Python、Ruby、C#、JavaScript、Perl 和 PHP。很难明确地说 Selenium 是 Cypress 的直接竞争对手,因为我们可以清楚地看到,虽然它们的用例非常相似,但它们的受众和目标用户完全不同。虽然 Selenium 针对所有主要开发语言的用户,甚至支持在诸如 Appium 之类的工具中进行移动自动化,但 Cypress 只专注于为理解 JavaScript 语言的 Web 开发人员和 QA 工程师提供更好的测试。

架构

Cypress 在浏览器上运行,这使其比 Selenium WebDriver 等工具更具优势。在浏览器上运行意味着 Cypress 速度更快,可以在运行时更快地解释命令,因为没有第三方服务代表其解释命令或向浏览器驱动程序发送 HTTP 请求。虽然所有 Cypress 命令都在浏览器内运行,但 Cypress 可以了解浏览器外发生的事情,因为它可以访问应用程序的所有内容,包括窗口对象、DOM、文档对象或任何其他进程和方法。只要您的应用程序有访问权限,Cypress 测试就会有访问权限。以下图表显示了 Cypress 与 Selenium WebDriver 架构的对比。在 Cypress 中,执行在浏览器中进行,而在 Selenium 中,执行在浏览器外进行:

图 2.1– Selenium 与 Cypress 测试执行架构的对比

图 2.1– Selenium 与 Cypress 测试执行架构的对比

跨浏览器兼容性

Cypress 目前不支持像 Selenium WebDriver 那样支持所有主要浏览器。Cypress 目前支持使用 Chromium 开源项目构建的浏览器,Firefox,Edge 和 Electron(Cypress 中的默认浏览器)。另一方面,Selenium 支持所有主要浏览器,这使得它在能够在多个平台上测试应用程序的能力方面具有优势。虽然可以争论跨三个以上浏览器的跨浏览器功能会增加架构复杂性,对测试过程的价值很小,但支持多个浏览器可能会导致识别出优先级较高的错误,即使错误的严重性可能很低。

Cypress 的权衡

如前所述,Cypress 是一个专注于浏览器端到端测试自动化的测试工具。能够在浏览器上运行意味着 Cypress 可以与浏览器上的元素进行更好的交互,但这也意味着 Cypress 具有永久的权衡,由于其架构无法更改。这些权衡在以下子节中描述。

范围限制

Cypress 在作为 QA 工程师和编写测试的开发人员的自动化工具时效果最佳。Cypress 不支持手动自动化工具,并且没有计划在框架中集成手动测试工具。

Cypress 也不适用于诸如 Web 索引和性能测试之类的活动,进行这些活动可能会降低框架的性能能力。

环境限制

Cypress 在浏览器上运行,这意味着它始终支持的语言将是 JavaScript,因为测试代码将始终在浏览器中进行评估。能够在浏览器中运行意味着要连接到数据库或服务器,我们只能使用 Cypress 命令cy.exec()cy.request()cy.task(),这提供了一种公开数据库或服务器的方法,这可能比明确定义它们的配置并让 Cypress 理解它们更费力。在浏览器中运行测试为运行测试提供了很好的体验,但是要插入需要在浏览器外部运行的功能有点麻烦。

多个浏览器和多个标签-限制

Cypress 框架不支持测试在运行时控制多个浏览器的能力。这是一个永久的权衡,因为在一个浏览器中运行测试时无法控制多个浏览器。

Cypress 框架不支持与多个浏览器标签交互的能力,因为这种功能在浏览器内部不可用。但是,Cypress 提供了集成其他工具(如 Selenium 或 Puppeteer)以在需要时操作和驱动多个浏览器标签的能力。

控制来源的限制

Cypress 只支持在同一个测试中访问来自相同来源的 URL。控制来源的限制意味着对于任何特定的测试,您无法访问不属于相同来源的不同 URL。例如,尝试在同一个测试中发送请求到github.comgitlab.com将导致错误。以下示例说明了编写 Cypress 测试时利用跨域的不正确和正确的方式。

正确地利用跨域来运行测试

在以下测试中,用户提示 Cypress 首先导航到github.com GitHub 网站,然后导航到docs.github.com/en(文档链接)以获取 GitHub 资源。这两个链接都属于相同的来源github.com,因此 Cypress 执行请求时不会出现问题。

It('can navigate to code repository hosting service', () => {
    cy.visit('https://github.com');
    cy.visit('https://docs.github.com');  });

不正确地利用跨域来运行测试

在这个测试中,用户首先提示 Cypress 导航到github.com,然后再导航到 https://gitlab.com,这是与第一个 URL 不同源的网站。当运行测试时,这将导致错误被抛出:

It('can navigate to code repository hosting service', () => {
    cy.visit('https://github.com');
    cy.visit('https://gitlab.com');  })

Cypress 和 Selenium 互补的行动

在某些罕见但仍可实现的情况下,我们可以同时利用 Cypress 和 Selenium 编写测试。虽然 Cypress 有不能控制多个浏览器标签的限制,但可以配置 Cypress 使用 Selenium 来运行多个标签。我们还可以利用 Cypress 进行端到端测试,而使用 Selenium 进行诸如负载测试之类的活动。Selenium 能够执行 Cypress 不支持的负载测试等测试,并且在这种情况下,两个测试框架可以一起使用。

总结差异

Cypress 是为网络而构建的,并且经过优化以在浏览器上运行。Cypress 的架构允许它有效地运行测试,同时克服了 WebDriver 的挑战。虽然 Cypress 能够在浏览器上运行,但 WebDriver 使用 HTTP 协议与浏览器交互,因此在运行测试时会导致延迟和未知的等待事件。Cypress 还针对寻求编写测试而不必担心基础架构和断言库和编程语言限制的质量保证工程师和开发人员。Cypress 还承诺未来,计划支持 Safari 和 Internet Explorer,这将确保开发人员和测试人员可以在他们选择的浏览器上尝试 Cypress。

Cypress 捆绑了所有的优势,但也伴随着一些临时和永久的权衡。一些临时的权衡是支持所有主要浏览器或执行某些功能,比如悬停在一个元素上。另一方面,永久的权衡意味着 Cypress 的架构甚至在未来也无法支持它们。它们包括控制多个打开的浏览器和/或在浏览器中操作多个标签,能够连接到外部数据库和服务器,并调用不同的跨源。所有永久的权衡都有解决方法,用户可以随时实施解决方法。然而,Cypress 建议在不应该使用解决方法的情况下不要使用解决方法,因为这可能会导致测试自动化复杂性,从而降低 Cypress 作为自动化工具的效果。

总结-比较 Cypress 和 Selenium WebDriver

在这一部分,我们了解了使用 Cypress 的优势,并将其与使用 Selenium 编写测试进行了比较。我们还确定了为什么 Selenium 在架构上与 Cypress 不同,以及为什么两者更多地是互补而不是补充。我们探讨了 Cypress 存在的权衡以及在 Cypress 自动化框架中克服永久权衡的一些解决方案。在下一节中,我们将深入探讨使 Cypress 成为端到端网页测试自动化的最佳选择的工具。

Cypress 用于前端应用程序

Cypress 是为网络而构建的,这意味着它装载了一些其他框架可能没有的工具和功能。这提高了前端 Web 开发人员和质量保证工程师的测试体验。在这一部分,我们将探讨 Cypress 装载的不同元素,这些元素使其用户可以方便地开始并快速上手。以下是一些使 Cypress 脱颖而出的元素,这些元素使其与其他前端应用程序的测试自动化框架不同。

测试运行器

当 Cypress 安装在用户的计算机上时,默认情况下会附带 Cypress 测试运行器。这是一个交互式用户界面,允许 Cypress 框架的用户查看测试中运行的命令,以及与之交互的应用程序。测试运行器具有显示测试失败次数、测试通过次数、跳过的测试、命令日志以及测试运行时浏览器的视口的能力。

设置过程

如前一章所述,Cypress 的设置过程不仅清晰简单,而且确保 QA 工程师和前端开发人员只需运行一个命令即可安装 Cypress。这消除了配置外部依赖项来开始编写测试的需要。Cypress 文档也非常互动和清晰,这使得开发人员和 QA 工程师可以快速上手并使用 Cypress 的功能。

实施和调试

Cypress 测试运行器带有内置的命令日志,这意味着在调试模式下,用户可以实时检查已通过的命令和断言,以及未通过的命令。突出显示失败的命令并检查未能调用的元素或失败的功能的能力使 Cypress 脱颖而出,因为调试前端应用程序不仅变得轻而易举,而且还节省了用于调查失败原因的时间。命令日志还为 Cypress 用户提供了即时反馈,他们可以通过检查测试运行器上运行的命令来判断测试是否已经正确编写。

全面的测试能力

Cypress 结合了编写功能测试和检查前端发出的 API 调用的响应的能力。它还具有可视化回归功能,可以识别应用程序是否有意进行了更改。

在编写功能测试时,Cypress 框架会检查前端功能是否符合需求文档中规定的要求,这可能涉及点击按钮或注册用户等过程。

API 验证测试另一方面检查返回的 XHR(XMLHttpRequest)请求是否成功,并在请求返回时收到正确的响应。XHR 请求为 API 测试提供了额外的验证层,因为我们可以确认预期数据的结构与前端应用程序中收到的数据类似。

重要说明

XHR 作为 API 工作,但以对象的形式表示,其主要目的是在给定的 Web 服务器和 Web 浏览器之间传输数据。

可视化回归测试通过比较基线的页面快照和最新测试运行的页面快照来检查页面元素的一致性。如果发现差异,那么正在运行的测试将失败。失败时,将创建一个显示预期图像和生成图像之间差异的快照,以显示生成的快照与基线图像之间的差异。测试运行后,QA 工程师或开发人员可以接受或拒绝对前端应用程序所做的更改。

回顾-用于前端应用程序的 Cypress

在本节中,我们了解了为什么 Cypress 在测试前端应用程序时是最合适的。我们了解了使其成为首选测试框架的不同因素,以及如何利用其优势来编写更好、更全面的测试。

总结

毫无疑问,Cypress 是一个强大的工具,可以被前端团队和质量保证工程师利用,快速开始编写测试,而不必担心从头开始构建测试自动化工具所带来的额外开销。在本章中,我们了解了为什么 Cypress 是用于测试的最佳 Web 自动化框架,我们通过比较 Cypress 和现有测试自动化工具之间的不同工具来做到这一点。我们还介绍了 Cypress 和 Selenium 之间的区别,以及两者之间的特定架构相似性和差异性。最后,我们探讨了如何利用这些工具。在下一章中,我们将学习如何使用命令行工具来运行、测试和调试 Cypress 测试。

第三章:使用 Cypress 命令行工具

在上一章中,我们了解了 Cypress 与 Selenium 等其他测试自动化工具的不同之处,以及在 Web 自动化测试方面的突出表现。在本章中,我们将继续使用 Cypress 命令行工具来构建我们对 Cypress 的使用知识。为此,我们将介绍您可以使用的命令,以利用 Cypress 的功能。

其中一些命令将涉及功能,如运行单个或所有测试,调试 Cypress,在不同浏览器上启动 Cypress 测试,以及其他 Cypress 命令行功能。我们将参考本章的 GitHub 存储库文件夹,并将为您的参考和练习包括在存储库中编写的每个命令和代码。

在本章中,我们将涵盖以下关键主题:

  • 运行 Cypress 命令

  • 理解基本的 Cypress 命令

  • Cypress 在命令行上调试

一旦您完成了每个主题,您将准备好编写您的第一个测试。

技术要求

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。

本章的源代码可以在chapter-03目录中找到。

要运行本章中的示例,您需要克隆本书的 GitHub 存储库,并按照READMe.md文件中的说明正确设置和运行测试。您可以在docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/cloning-a-repository上阅读有关如何使用 GitHub 在本地机器上克隆项目的更多信息。

运行 Cypress 命令

有效利用 Cypress 框架需要您了解 Cypress 以及如何使用命令行运行不同功能。Cypress 命令允许 Cypress 框架的用户自动化流程,并在初始化和运行时向框架和测试提供特定指令。

在大多数情况下,通过命令行运行 Cypress 测试比使用浏览器运行测试更快。这是因为通过命令行运行测试可以减少运行特定测试所需的资源数量。原因是在命令行中运行的测试通常是无头的,这意味着分配给运行测试的资源较少,而在有头模式下执行测试则不同。

重要提示

有头模式是指测试可以在浏览器上可视化运行,而无头模式是指测试执行过程不会打开可见的浏览器。相反,所有测试都在命令行上运行并输出。

首先,让我们看看如何运行全局和本地的 Cypress 命令。

全局和本地命令

Cypress 命令可以从包含 Cypress 安装和代码的特定目录中运行,也可以从全局 Cypress 安装中运行。全局安装 Cypress 可确保用户可以从操作系统中的任何目录运行 Cypress,而使用本地 Cypress 安装,Cypress 只能从安装的单个目录中访问。

运行全局 Cypress 命令

在 Cypress 中,全局命令是通过访问全局安装的 Cypress 版本来运行的。运行全局版本的 Cypress 时调用的命令不一定是用户生成或定义的,因为它们内置在框架中。要能够全局运行 Cypress 命令,您需要使用以下命令全局安装 Cypress:

npm install cypress --global
or (shorter version)
npm i -g cypress

上述命令将全局安装 Cypress,并确保从任何 Cypress 安装目录调用任何已知的 Cypress 命令都会产生结果或错误,具体取决于提供的命令的执行情况。

要运行全局命令,您需要使用cypress关键字定义命令,然后是命令;例如,cypress runcypress open

本地运行 Cypress 命令

本地 Cypress 命令源自 Cypress 全局命令,并且是运行命令的另一种选择。要在本地运行 Cypress 命令,您需要使用以下命令在您的目录中安装 Cypress:

npm install cypress 
or (shorter version)
npm i cypress

我们可以通过在package.json文件的scripts部分中定义所需的命令来将其集成到开发环境中,如下所示:

{
  "scripts": {
    "cypress:run": "cypress run",
    "cypress:open": "cypress open"
  }
}

将命令添加到package.json中允许我们以与执行 JavaScript 包的 npm 命令相同的方式使用这些命令。package.json文件中定义的命令在运行时由 Node.js 环境解释,并且在执行时,它们会被执行为全局命令。

重要提示

建议在运行npm install cypress命令之前在终端中运行npm init命令。如果在未初始化项目的情况下运行 Cypress,Cypress 的目录将不可见。通过运行init命令,Cypress 将识别项目目录为现有项目,因此它会初始化并创建其目录,而无需我们在终端上运行额外的命令。

package.json中定义命令不仅使开发人员和质量保证工程师更容易知道要运行哪些命令,而且简化了运行、调试或维护测试时需要运行的命令的性质。

重要提示

Cypress 开发团队建议按项目安装 Cypress,而不是使用全局安装方法。本地安装提供了某些优势,例如用户能够快速更新 Cypress 依赖项,并减少循环依赖问题,这些问题会在不同项目中破坏一些测试,而 Cypress 在另一个项目中运行良好。

要在命令行中运行脚本,您需要调用npm run,然后是命令的名称。在我们之前定义的命令中,您只需要运行以下命令来同时执行这些命令:

npm run cypress:run  // command  to run tests on terminal
npm run cypress:open // command to run tests on cypress runner

是时候快速回顾一下了。

回顾-运行 Cypress 命令

在本节中,我们学习了如何调用本地或全局命令,以及如何从 Cypress 终端或测试运行程序中运行测试,后者利用了图形用户界面。在接下来的部分中,我们将在已经获得的运行 Cypress 命令的知识基础上,了解如何在 Cypress 中使用不同的命令。

了解基本的 Cypress 命令

在本节中,我们将探讨各种 Cypress 命令,我们可以使用这些命令通过终端或使用 Cypress 测试运行程序来运行我们的测试。我们还将观察这些命令如何用于实现不同的结果。本节还将介绍如何定制与我们的应用程序交互的不同测试,以实现特定的结果。我们将深入了解最常见的 Cypress 命令以及如何使用 Cypress 框架中预先构建的选项来扩展这些命令。我们将探讨的命令如下:

  • cypress run

  • cypress open

  • cypress info

  • cypress version

让我们从cypress run开始。

cypress run

cypress run命令以无头方式执行 Cypress 套件中的所有测试,并默认在 Electron 浏览器中运行测试。如果没有使用其他配置扩展,该命令将运行 Cypress 的integration文件夹中所有.spec.js格式的文件。Cypress run命令可以使用以下配置选项运行:

cypress run {configuration-options}

最常见的 Cypress 配置选项包括以下内容:

接下来的几节将扩展前面表格中显示的每个配置选项。

cypress run --env

Cypress 环境变量是动态名称-值对,影响 Cypress 执行测试的方式。当需要在多个环境中运行测试,或者定义的值容易快速更改时,这些环境变量非常有用。

在 Cypress 中,您可以将单个或多个环境变量定义为字符串或 JSON 对象。

在本节中,我们将为一个开源的todoMVC应用程序编写测试。这些测试的代码基础可以在本书的 GitHub 存储库中的chapter 03目录中找到。被测试的应用程序是一个使用 React 开发的待办事项列表应用程序。使用该应用程序,我们可以添加待办事项,标记为已完成,删除它们,查看已完成的项目,甚至在活动全部已完成待办事项之间切换。

使用该应用程序,我们可能计划扩展应用程序以使用HTTPS而不是当前的HTTP协议。即使目前不支持 HTTPS 功能,我们可以在 Cypress 测试中添加对其的支持。为此,我们将把传输协议URL 部分定义为环境变量,然后将其传递给package.json中的命令,如下例所示。

以下代码片段可以在chapter 03的子文件夹中提到的 GitHub 存储库中找到。Todo-app.spec.js文件的完整源代码位于Cypress/integration/examples文件夹下。我们将在本章中探讨的 Cypress 测试是版本 1 的测试。

下面的Todo-app.spec.js文件演示了在导航到 URL 时如何使用环境变量。它是主要的测试文件,位于本书的 GitHub 存储库中的chapter-03/cypress/integration/examples/todo-app.spec.js中。

... 
context('TODO MVC Application Tests', () => {
  beforeEach(() => {
    cy.visit(
      `${Cypress.env('TransferProtocol')}://todomvc.com/examples/react/#/`)
  });
...

下面的package.json文件也位于chapter-03/目录中,包含了用于执行 JavaScript 应用程序的测试或执行命令的所有命令。它位于chapter-03/目录的根位置:

...
"scripts": {
    "cypress:run": "cypress run --env 
    TransferProtocol='http'",
    "cypress:run:v2": "cypress run --env 
    TransferProtocol='https'",
  },
...

上述脚本表明,如果 URL 协议发生变化,我们可以运行前述任何测试命令来替换我们在运行 Cypress 测试时声明的环境变量。我们可以依次执行前述脚本,使用npm run cypress:runnpm run cypress:run:v2

重要提示

HTTPS 与 HTTP 相同,不同之处在于 HTTPS 更安全。这是因为发送请求和接收响应的过程使用 TLS(SSL)安全协议进行加密。

传输协议是 URL 的一部分,它确定 URL 是否使用 HTTP 或 HTTPS 协议。使用 HTTP 协议的 URL 以http://开头,而使用 HTTPS 的 URL 以https://开头。

cypress run --browser

Cypress 命令行具有内置功能,可以在主机计算机上安装的不同浏览器中运行 Cypress 测试,并且这些浏览器受 Cypress 框架支持。Cypress 会尝试自动检测已安装的浏览器,并可以在chromechromiumedgefirefoxelectron中运行测试。要在特定浏览器中运行测试,您需要使用--browser配置选项提供浏览器名称。您还可以选择提供浏览器路径而不是浏览器名称;只要它是有效的并且受 Cypress 支持,Cypress 仍然会在提供的浏览器路径中运行测试。

下面的代码片段显示了在本书 GitHub 存储库的chapter-03目录中的package.jsonscripts部分中定义的脚本。这些脚本定义了我们的测试将在其中运行的浏览器,并且还传递了 URL 的一部分作为环境变量:

...
"scripts": {
    "cypress:chrome": "cypress run --env 
    TransferProtocol='http' --browser chrome",
    "cypress:firefox": " cypress run --env 
    TransferProtocol='http' --browser firefox"
  },
...

在上述命令中,我们可以使用npm run cypress:chromenpm run cypress:firefox命令在 Chrome 或 Firefox 中运行测试。

重要提示

要在特定浏览器中运行测试,该浏览器必须安装在您的计算机上,并且还必须是 Cypress 支持的浏览器列表中的一员。

Cypress run --config <configuration(s)-option>

Cypress 可以使用在终端上运行的命令设置和覆盖配置。Cypress 的配置可以作为单个值、以逗号分隔的多个值,或作为字符串化的 JSON 对象传递。Cypress 中的任何定义的配置都可以通过cypress run --config配置选项进行更改或修改。配置选项可能包括指定替代的viewportHeightViewportWidth、超时和文件更改等其他配置。在我们的脚本中,我们将更改 Cypress 运行测试的视口,而不是默认的视口,即 1000x660,我们将在平板视口的 763x700 上运行测试。

下面的代码片段在我们的chapter-03根目录的package.json文件中定义。以下脚本用于在平板视图中运行测试。为此,您必须覆盖 Cypress 默认配置的视口高度和宽度:

...
"scripts": {
"cypress:tablet-view": "cypress run --env TransferProtocol='http' --config viewportHeight=763,viewportWidth=700",
}
...

前面的脚本可以使用npm run cypress:tablet-view命令运行。

重要提示

在 Cypress 中传递多个配置选项时,不要在不同配置的逗号分隔值之间留下空格(如上面的代码所示);否则,Cypress 会抛出错误。

cypress run --config-file

Cypress 可以覆盖位于/cypressRootDirectory/cypress.json的默认配置文件。您可以定义一个或多个次要的 Cypress 配置文件以运行它们的测试。Cypress 还允许您完全禁用使用配置文件。

下面的脚本位于本书 GitHub 存储库的chapter-03目录中的package.json中,这是一个命令,使 Cypress 能够覆盖用于运行测试的配置文件。执行该命令时,将使用位于chapter-03/config/cypress-config.json下的cypress-config.json文件,而不是使用位于chapter-03中的默认cypress.json文件:

...
"scripts": {
"cypress:run:secondary-configuraton": "cypress run --env TransferProtocol='http' --browser chrome --config-file config/cypress-config.json"
},...

运行上述脚本,您需要运行npm run cypress:run:secondary-configuraton命令,该命令将使用位于/cypressRootDirectory/config/cypress-config.json的配置文件运行测试。

cypress run --headed

Cypress 提供了一个命令,允许您以无头和有头模式运行浏览器。当定义有头模式时,测试在运行时会打开浏览器。此选项可以在 Cypress 捆绑的默认 Electron 浏览器中使用。在 Electron 中使用run命令运行 Cypress 测试的默认模式是无头模式,要覆盖这一点,我们需要在运行测试时传递--headed配置。

下面的脚本可以在本书 GitHub 存储库的chapter-03目录中的package.json文件中找到。运行以下脚本命令将使 Cypress 以有头模式运行,允许在浏览器上看到运行的测试:

...
"scripts": {
"cypress:electron:headed": "cypress run --env TransferProtocol='http' --headed"
},
...

前面的脚本可以使用npm run cypress:electron:headed命令运行。

cypress run --headless

Cypress 在 Chrome 和 Firefox 浏览器中以有头模式运行测试,并且每次运行测试时都会启动一个浏览器。要更改此行为并确保测试在不启动浏览器的情况下运行,您需要配置运行 Chrome 或 Firefox 浏览器的命令,以便它们以无头模式运行。

以下脚本位于本书 GitHub 仓库的 chapter-03 目录中的 package.json 文件中。运行以下命令将使 Cypress 以无头模式运行,测试命令只能在命令行界面上看到:

...
"scripts": {
"cypress:chrome:headless": "cypress run --env TransferProtocol='http' --browser chrome --headless",
"cypress:firefox:headless": "cypress run --env TransferProtocol='http' --browser firefox --headless"
},
...

使用上述命令以无头模式运行 Chrome,您需要运行 npm run cypress:chrome:headless。要在 Firefox 中以无头模式运行命令,您需要运行 npm run cypress:firefox:headless 命令。

cypress run --spec

Cypress 允许我们指定可以运行的不同测试文件。使用此命令,可以指定在目录中运行 单个 测试文件,而不是在目录中运行 所有 测试文件。还可以指定不同目录中的不同测试,以便它们同时运行,并指定与特定目录匹配的正则表达式模式。

以下代码片段是 package.json 文件的一部分,位于本书 GitHub 仓库的 chapter-03 目录中。第一个脚本只能运行目录中的特定文件,而第二个脚本可以运行单个目录中的多个文件:

... 
"scripts": {
  "cypress:integration-v2:todo-app": "cypress run --env 
  TransferProtocol='http' --spec 'cypress/integration/
  integration-v2/todo-app.spec.js'",
  "cypress:integration-v2": "cypress run --env 
  TransferProtocol='http' --spec 'cypress/
  integration/integration-v2/**/'"
},
...

第一个命令指定将运行位于 integration-v2 文件夹中的 todo-app.spec.js 文件的测试。第二个命令将运行位于 integration-v2 文件夹中的所有测试文件。

cypress open

cypress open 命令在测试运行器中运行 Cypress 测试,并将配置选项应用于您正在运行的项目的测试。当运行 cypress open 命令时传递的配置选项也会覆盖 cypress.json 文件中指定的默认值,该文件位于 tests root 文件夹中,如果在运行测试时指定了配置。以下命令显示如何运行任何 cypress open 命令:

cypress open {configuration-options}

命令的第一部分显示了 cypress open 命令,而第二部分显示了可以与其链接的配置选项。

最常见的 Cypress 配置选项包括以下内容:

我们将在接下来的几节中详细介绍每个选项。

cypress open --env <env-variable(s)>

就像运行 cypress run 命令一样,cypress open 命令可以在运行测试时使用指定的环境变量运行。与 cypress run 命令类似,可以使用 --env 配置选项声明一个或多个环境变量来运行测试运行器中的测试。

在前一节中,我们指定了如何通过在 cypress run 命令中传递环境变量来通过命令行运行测试。我们将传递相同的环境变量来使用我们的 Cypress 测试运行器运行测试,并且测试应该正常运行。传递的环境变量将确定 todoMVC 应用程序 URL 的传输协议是 HTTP 还是安全的 HTTPS

以下代码片段位于 Todo-app.spec.js 文件中,该文件是我们 chapter-03/ 目录中的主要测试文件。todo-app.spec.js 文件位于 chapter-03/ 目录中的 integration/examples 下。在以下代码片段中,就像在 cypress run 中一样,我们可以使用 cypress open 命令将环境变量传递给 URL:

... 
context('TODO MVC Application Tests', () => {
  beforeEach(() => {
    cy.visit(
      `${Cypress.env('TransferProtocol')}://todomvc.com/examples/react/#/`)
  });
...

以下代码片段位于本书 GitHub 仓库的 chapter-03/ 根目录中的 package.json 文件中。使用此片段,我们将 'http' 环境变量传递给我们的测试。这是我们可以完成我们的 URL 并执行我们的测试的时候:

...
"scripts": {
    "cypress:open": "cypress open --env 
    TransferProtocol='http'"
  },
...

要打开测试运行器并验证测试运行,您可以运行npm run cypress:open,这应该会自动将TransferProtocol环境变量添加到运行测试的配置中。

cypress open --browser </path/to/browser>

当指定时,--browser选项指向一个自定义浏览器,该浏览器将被添加到测试运行器中的可用浏览器列表中。要添加的浏览器必须得到 Cypress 的支持,并且必须安装在运行 Cypress 测试的计算机上。

默认情况下,在选择要运行的规范之前,可以通过单击测试运行器中的浏览器选择按钮来查看 Cypress 中的所有可用浏览器。浏览器选择下拉菜单包含已在系统上安装并得到 Cypress 支持的所有浏览器。浏览器选择下拉菜单还允许您切换测试浏览器,从而在不同的浏览器下测试功能:

图 3.1 - 测试浏览器选择下拉菜单

图 3.1 - 测试浏览器选择下拉菜单

要指定路径以便添加浏览器(例如 Chromium),您需要具有以下配置以将 Chromium 添加到可用浏览器列表中。在这里,您需要运行npm run cypress:chromium命令。

该书的 GitHub 存储库中的chapter-03/目录下的package.json文件中包含以下脚本。当执行该脚本作为命令时,它将查找指定位置的浏览器,并将其添加到用于运行 Cypress 测试的浏览器列表中。

...
"scripts": {
    "cypress:chromium": "cypress open --browser 
    /usr/bin/chromium"
  },
..

要执行上述脚本来运行我们的测试,我们需要在终端中运行npm run cypress:chromium命令。这将在/usr/bin/chromium位置找到 Chromium 浏览器并用它来运行我们的测试。

cypress open --config <configuration-option(s)>

Cypress 框架允许我们在测试运行器中运行测试并提供必须在初始化测试运行器时传递的配置选项。在传递--config选项时,可以传递一个环境变量或用逗号分隔的多个环境变量。以下脚本指定了视口尺寸应为平板电脑,并通过--config选项传递配置。要运行所需的命令,您需要运行npm run cypress:open:tablet-view

位于该书的chapter-03/根目录下的package.json文件中的以下脚本用于更改可见浏览器上运行的测试的视口配置。

...
"scripts": {
    "cypress:open:tablet-view":"cypress open --env 
    TransferProtocol='http' --config 
    viewportHeight=763,viewportWidth=700"
  },
...

执行时,该命令会修改浏览器尺寸的默认 Cypress 配置。提供的视口高度和视口宽度将以类似于平板显示屏的方式显示内容。

重要提示

使用--config选项指定的配置选项将覆盖cypress.json文件中指定的默认配置。

cypress open --config-file

就像在Cypress run命令的情况下一样,通过测试运行器运行的 Cypress 测试可以具有覆盖默认cypress.json文件的覆盖配置文件,该文件包含默认的 Cypress 配置。它位于测试文件的根文件夹中。

位于chapter-03/目录的根文件夹中的package.json文件中的以下代码片段覆盖了默认的 Cypress 配置文件,该文件被标识为cypress.json。当执行时,该命令将读取一个已在chapter-03/config/cpress-config.json中声明的替代配置文件:

..."scripts": {
"cypress:open:secondary-configuraton": "cypress open --env TransferProtocol='http' --config-file config/cypress-config.json"
},...

要执行上述命令并更改默认的 Cypress 配置文件位置,您需要在命令行界面中运行以下命令:

npm run cypress:open:secondary-configuration

现在,让我们看另一个命令。

cypress open --global

正如我们之前提到的,Cypress 可以全局安装。您可以使用全局 Cypress 安装来运行不同的 Cypress 测试,而不是在每个项目中安装它。这种全局安装还允许您触发全局命令,而不必在调用 Cypress 命令的特定目录中安装 Cypress。要以全局模式打开 Cypress,您需要传递--global选项,如下所示:

cypress open --global 

通过运行此命令,Cypress 将识别我们要使用全局版本的 Cypress 执行测试,而不是我们的本地实例。

cypress open --project

Cypress 具有内置功能,可以覆盖 Cypress 运行测试时的默认路径。当定义了--project选项时,它指示 Cypress 放弃默认的目录/项目路径,而是使用提供的项目路径来运行位于指定项目路径中的 Cypress 测试。在这种设置中,可以在不同的目录或嵌套目录中运行完全不同的 Cypress 项目。

本书chapter-03/根目录中的package.json文件中的以下代码片段执行了完全不同的 Cypress 项目中的测试。该脚本执行了位于chapter-03/cypress/todo-app-v3中的项目:

"scripts": {
"cypress:project:v3": "cypress open --env TransferProtocol='http' --project 'cypress/todo-app-v3/'"
},

在上一个脚本中,用户可以运行位于cypress/todo-app-v3文件夹中的不同 Cypress 项目。要运行该脚本,我们需要运行npm run:cypress:project:v3命令。version-3项目是一个独立的项目,不依赖于父 Cypress 项目。它可以使用自己的cypress.json文件来确定运行配置,如下面的截图所示:

![图 3.2 - todo-app-v3 项目测试文件夹](https://gitee.com/OpenDocCN/freelearn-js-zh/raw/master/docs/e2e-web-test-cprs/img/第三章 _ 图像 02.jpg)

图 3.2 - todo-app-v3 项目测试文件夹

如前面的截图所示,我们已经修改了todo-app-v3项目中的integrationFolder属性,将主测试文件夹中的cypress/integration设置为cypress/tests

cypress open --port

默认情况下,Cypress 在端口8080上运行。在传递--port选项的同时运行cypress run命令,可以覆盖测试运行的默认端口,将其更改为您选择的特定端口。

以下代码片段是本书 GitHub 存储库中chapter-03/目录中的package.json文件的一部分。运行以下命令会更改 Cypress 运行的默认端口:

"scripts": {"cypress:open:changed-port": "cypress open --env TransferProtocol='http' --port 3004"
  },

要运行前面的 Cypress 脚本,您需要运行npm run cypress:open:changed-port命令。运行此命令将确保测试在端口3004上运行,而不是 Cypress 默认在测试运行器上运行的端口:

![图 3.3 - 覆盖默认的 Cypress 测试端口](https://gitee.com/OpenDocCN/freelearn-js-zh/raw/master/docs/e2e-web-test-cprs/img/第三章 _ 图像 03.jpg)

图 3.3 - 覆盖默认的 Cypress 测试端口

前面的截图显示了如何在使用--port选项覆盖后,在端口3004上运行测试,该选项被传递给cypress run命令。这里使用的端口仅用于演示目的;用户的机器上可以传递任何可用端口作为 Cypress 应用程序的覆盖端口。

使用 cypress info 命令

在终端上运行cypress info命令将在终端上打印 Cypress 安装信息和环境配置。该命令打印的信息包括以下内容:

  • 已在计算机上安装并被 Cypress 检测到的浏览器。

  • 有关主机操作系统的信息。

  • Cypress 二进制缓存的位置。

  • 运行时数据存储的位置。

  • 已添加前缀CYPRESS的环境变量,用于控制配置,如系统代理。

使用 cypress 版本命令

cypress version命令打印出 Cypress 的二进制版本和已安装的 Cypress 模块的 npm 版本。大多数情况下,版本应该是相同的,但当安装的模块(如二进制版本)无法作为 npm 包模块安装时,版本可能会有所不同。cypress version命令的输出如下截图所示:

图 3.4 - cypress 版本命令的输出

图 3.4 - cypress 版本命令的输出

前面的截图显示了我机器上安装的 Cypress 包和二进制版本。Cypress 的包版本和二进制版本都是相同的版本。

Cypress 命令使用的可选练习

在我们项目规范中定义的todoMVC项目中,创建一个脚本,可以运行以下测试场景:

  • 使用 edge 浏览器进行无头测试。

  • chapter 03根文件夹中的cypress.json中指定TransferProtocol环境变量的测试运行器上的测试。

通过这个练习,您将了解如何运行有头和无头测试,以及如何向脚本添加环境变量,以便我们可以使用不同的 Cypress 命令来执行。您还将学习如何使用不同的浏览器来执行 Cypress 测试。

总结 - 了解基本的 Cypress 命令

在本节中,我们学习了如何使用不同的 Cypress 命令来使用命令行或 Cypress 测试运行器运行 Cypress,后者使用已安装在系统上的不同浏览器运行。我们了解到,尽管 Cypress 带有默认命令来运行测试,但我们可以扩展这些命令,并通过利用可用的命令和选项来定制 Cypress 以增加运行测试的效率。我们还提供了一个练习,让您应用您对cypress runcypress open命令的使用知识。在下一节中,您将学习如何使用内置的 Cypress 调试器来查看重要的调试信息,这对于使用我们的终端进行故障排除非常重要。

在命令行上进行 Cypress 调试

在本节中,我们将探讨如何使用 Cypress 的命令行调试属性来解决运行测试时可能遇到的问题。我们还将探讨 Cypress 通过命令行提供的不同调试选项。

Cypress 具有内置的调试模块,可以通过在运行测试之前使用cypress runcypress open传递调试命令来向用户公开。要从终端接收调试输出,需要在 Mac 或 Linux 环境中设置DEBUG环境变量,然后再运行 Cypress 测试。

以下脚本可以在chapter-03/根目录的package.json文件中找到,并用于在执行命令时显示调试输出。第一个脚本可用于在使用cypress open命令运行测试时显示调试输出,而第二个脚本可用于在使用cypress run命令运行测试时显示调试输出:

"scripts": {
"cypress:open:debugger": "DEBUG=cypress:* cypress open --env TransferProtocol='http'",
    "cypress:run:debugger": "DEBUG=cypress:* cypress run --
    env TransferProtocol='http'"
  },
} 

如前述命令所示,运行npm run cypress:open:debugger将在终端中运行 Cypress 测试,并记录运行时的调试输出。第二个命令可以通过npm run cypress:run:debugger运行,将在 Cypress 测试运行器上运行测试时运行调试器。

Cypress 使得过滤调试输出变得容易,因为您可以选择有关特定模块的调试信息,例如 Cypress 服务器、CLI 或启动器模块。

以下脚本位于本书 GitHub 存储库的chapter-03/目录中的package.json文件中。运行时,它将为 Cypress 服务器模块下的所有日志提供调试输出:

...
"scripts": {
"cypress:open:server-debugger": "DEBUG=cypress:server:* cypress open --env TransferProtocol='http'"
} 
...

使用npm run cypress:run:server-debugger运行上述命令将只输出与 Cypress 服务器相关的调试器信息。使用过滤命令不仅可以轻松缩小 Cypress 中的问题范围,还有助于过滤噪音,留下对于调试 Cypress 信息重要的日志,并将我们带到问题的源头。

Cypress 调试的可选练习

使用我们项目规范中定义的todoMVC项目,在脚本中创建将运行以下测试场景的脚本:

  • 调试 Cypress CLI 模块

  • 调试cypress:server项目模块

通过本次练习,您将掌握 Cypress 调试的概念,并了解如何在package.json文件中创建和运行 Cypress 脚本。

回顾-在命令行上调试 Cypress

在本节中,我们学习了如何利用 Cypress 通过设置DEBUG环境变量查看有关测试运行的其他信息。我们还学习了如何利用 Cypress 的debug变量来过滤我们需要的调试输出,并进行了一项练习,以扩展我们在命令行上调试的知识。

总结

在本章中,我们学习了cypress opencypress run命令,以及如何使用配置选项将这两个命令链接起来以扩展它们的用途。我们还学习了如何检查已安装在系统上的 Cypress 信息和 Cypress 版本。在最后一节中,我们学习了如何使用 Cypress 提供调试输出,并找出测试失败的原因。在下一章中,我们将深入了解编写 Cypress 测试和理解测试的不同部分。

第四章:编写您的第一个测试

在开始本章之前,您需要了解 Cypress 测试的运行方式,不同的 Cypress 命令,如何设置 Cypress,在命令行上运行 Cypress 以及如何使用测试运行器打开 Cypress 测试。这些信息在前三章中已经涵盖,将帮助您更好地理解我们在本章中编写第一个测试时所要建立的基础知识。

在本章中,我们将介绍创建测试文件和编写基本测试的基础知识,然后我们将继续编写更复杂的测试,并使用 Cypress 断言各种元素。

我们将在本章中涵盖以下主题:

  • 创建测试文件

  • 编写您的第一个测试

  • 编写实用测试

  • Cypress 的自动重新加载功能

  • Cypress 断言

通过完成本章,您将准备好学习如何使用测试运行器调试运行中的测试。

技术要求

本章的 GitHub 存储库可以在以下链接找到:

github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress

本章的源代码可以在chapter-04目录中找到。

创建测试文件

Cypress 中的所有测试必须在测试文件中才能运行。要使测试被认为是有用的,它必须验证我们在测试中定义的所有条件,并返回一个响应,说明条件是否已满足。Cypress 测试也不例外,所有在测试文件中编写的测试都必须有一组要验证的条件。

在本节中,我们将介绍编写测试文件的过程,从 Cypress 中测试文件应该位于的位置开始,Cypress 支持的不同扩展名,以及 Cypress 中编写的测试文件应该遵循的文件结构。

测试文件位置

Cypress 在初始化时默认在cypress/integration/examples目录中创建测试文件。但是,这些文件可以被删除,因为它们旨在展示利用不同的 Cypress 测试类型和断言的正确格式。Cypress 允许您在定位不同模块和文件夹结构时具有灵活性。

建议在您第一次项目中工作时,使用前面段落中提到的位置来编写您的 Cypress 测试。要重新配置 Cypress 文件夹结构,您可以更改 Cypress 默认配置并将新配置传递到cypress.json文件中。更改默认 Cypress 配置的一个很好的例子是将我们的测试目录从cypress/integration/examples更改为cypress/tests/todo-app或其他位置。要更改默认目录,我们只需要更改我们的cypress.json配置,如下所示:

{
"integrationFolder": "cypress/tests"
}

前面的代码块显示了integrationFolder设置,它改变了 Cypress tests字典的配置方式。

测试文件扩展名

Cypress 接受不同的文件扩展名,这使我们能够编写超出正常 JavaScript 默认格式的测试。以下文件扩展名在 Cypress 测试中是可接受的:

  • .js

  • .jsx

  • .coffee

  • .cjsx

除此之外,Cypress 还原生支持 ES2015 和 CommonJS 模块,这使我们可以在没有任何额外配置的情况下使用importrequire等关键字。

测试文件结构

Cypress 中的测试文件结构与大多数其他用于编写测试或甚至普通 JavaScript 代码的结构类似。Cypress 测试的结构考虑了模块导入和声明,以及包含测试的测试主体。这可以在以下示例测试文件中看到:

 // Module declarations
import {module} from 'module-package';
 // test body
describe('Test Body', () => {
   it('runs sample test', () => {
      expect(2).to.eq(2);
   })
})

正如您所看到的,每个测试文件都需要在测试文件的最顶部进行声明。通过这样做,测试可以嵌套在describe块中,这些块指定了将要运行的测试的范围和类型。

创建我们的测试文件

使用技术要求部分中的 GitHub 链接,打开chapter-04文件夹。按照以下步骤创建您的第一个测试文件:

  1. 导航到 Cypress 目录内的integration文件夹目录。

  2. 创建一个名为sample.spec.js的空测试文件。

  3. 为了演示目的,我们已经在chapter-04根目录中为您创建了一个package.json文件。您现在只需要运行命令,不用担心它们的工作原理。

  4. 使用npm run cypress:run命令启动 Cypress 测试运行器。

  5. 检查测试运行器预览,并确认我们添加的测试文件是否可见。

现在是快速回顾的时候了。

总结-创建测试文件

在本节中,我们学习了如何创建测试文件,Cypress 如何接受不同的测试文件格式,以及如何更改 Cypress 测试的默认目录。我们还学习了测试的结构,以及 Cypress 如何借鉴诸如 JavaScript 等语言的测试格式。在下一节中,您将专注于编写您的第一个测试。

编写您的第一个测试

Cypress 测试与任何其他测试没有区别。与所有其他测试一样,当预期结果与被测试应用程序的预期一致时,Cypress 测试应该通过;当预期结果与应用程序应该执行的操作不一致时,测试应该失败。在本节中,我们将探讨不同类型的测试、测试的结构以及 Cypress 如何理解测试文件中的更改并重新运行测试。本节还将介绍如何编写实用测试。

示例测试

在本节中,我们将看一下 Cypress 测试的基本结构。这在本章的大部分测试中保持标准。以下测试检查我们期望的结果和返回的结果是否等于true。当我们运行它时,它应该通过:

describe('Our Sample Test', () => {
  it('returns true', () => {
    expect(true).to.equal(true);
  });
});

在这里,我们可以看到测试中有describe()it()钩子。Cypress 测试中包含的钩子默认来自Chai断言库,Cypress 将其用作默认断言库。这些钩子用于帮助您理解测试的不同阶段。describe钩子帮助将不同的测试封装到一个块中,而it钩子帮助我们在测试块中识别特定的测试。

重要提示

Chai 断言库作为一个包包含在 Cypress 框架中。这是 Cypress 用来验证测试成功或失败的默认断言库。

考虑到我们在本节中看到的测试,我们现在将探讨 Cypress 中不同类型的测试分类。

测试分类

测试可以根据运行后产生的结果进行分类。Cypress 测试也可以根据它们的状态进行分类。测试可以处于以下任何状态:

  • 通过

  • 失败

  • 跳过

在接下来的几节中,我们将详细了解这三个类别。

通过测试

通过测试是正确验证输入是否与预期输出匹配的测试。在 Cypress 中,通过测试会被清晰地标记为通过,并且这在命令日志和 Cypress 测试运行器上是可见的。使用我们之前创建的sample.spec.js文件,我们可以创建我们的第一个通过测试,如下面的代码块所示:

describe('Our Passing Test', () => {
  it('returns true', () => {
    expect(true).to.equal(true);
  });
});

要在使用chapter-04目录作为参考时运行测试,我们可以在命令行界面上运行以下命令:

npm run cypress:open 

在这个测试中,我们正在验证给定的输入true是否与我们期望的测试输出true相似。这个测试可能并不是非常有用,但它的目的是展示一个通过的测试。以下截图显示了一个通过的测试:

图 4.1-通过测试

图 4.1 – 通过的测试

前面的截图显示了在命令日志中通过测试的结果。我们可以通过查看左上角的绿色复选标记进一步验证测试是否通过了所有其他条件。

失败的测试

与通过的测试一样,失败的测试也验证测试输入与测试期望,并将其与结果进行比较。如果预期结果和测试输入不相等,则测试失败。Cypress 在显示失败的测试并描述测试失败方面做得很好。使用我们之前创建的sample.spec.js文件,创建一个失败的测试,如下面的代码块所示:

describe('Our Failing Test', () => {
  it('returns false, () => {
    expect(true).to.equal(false);
  });
});

要运行测试,我们将使用chapter-04目录作为参考,然后在终端中运行以下命令:

npm run cypress:open

在这个失败的测试中,我们将一个true的测试输入与一个false的测试期望进行比较,这导致了一个设置为true的失败测试,它不等于false。由于它未通过确定我们的测试是否通过的验证,测试自动失败。以下截图显示了我们失败测试的结果在命令日志中:

图 4.2 – 失败的测试

图 4.2 – 失败的测试

查看命令日志,我们可以看到我们有两个测试:一个通过,一个失败。在失败的测试中,Cypress 命令日志显示了未满足我们期望的断言。另一方面,测试运行器继续显示一个失败的测试,作为我们测试运行的摘要。当测试失败时,Cypress 允许我们阅读发生的确切异常。在这种情况下,我们可以清楚地看到测试在断言级别失败,原因是断言不正确。

跳过的测试

Cypress 中的跳过测试不会被执行。跳过测试用于省略那些要么失败要么不需要在执行其他测试时运行的测试。跳过测试在其测试钩子后缀为.skip关键字。我们可以通过使用describe.skip跳过整个代码块中的测试,或者通过使用it.skip跳过单个测试。以下代码块显示了两个测试,其中主describe块被跳过,另一个测试在describe块内被跳过。以下代码说明了跳过 Cypress 测试的不同方法:

describe.skip('Our Skipped Tests', () => {
    it('does not execute', () => {
        expect(true).to.equal(true);
    });
    it.skip('is skipped', () => {
        expect(true).to.equal(false);
    });
});

在这里,我们可以看到当我们在itdescribe钩子中添加.skip时,我们可以跳过整个代码块或特定测试。以下截图显示了一个跳过的测试:

图 4.3 – 跳过测试

图 4.3 – 跳过测试

跳过的测试在命令日志和测试运行器中只显示为跳过;对于已跳过的测试块或单个测试,不会发生任何活动。前面的截图显示了我们在sample.spec.js文件中定义的跳过测试的状态,该文件可以在我们的chapter-04GitHub 存储库目录中找到。现在我们知道如何编写不同类型的测试,我们可以开始编写实际的测试。但首先,让我们测试一下我们的知识。

测试分类练习

使用您在本章节阅读中获得的知识,编写符合以下标准的测试:

  • 一个通过的测试,断言一个变量是string类型

  • 一个失败的测试,断言一个有效的变量等于undefined

  • 一个跳过的测试,检查布尔变量是否为true

现在,让我们回顾一下本节我们所涵盖的内容。

总结 – 编写您的第一个测试

在本节中,我们学习了如何识别不同类型的测试,并了解了 Cypress 框架如何处理它们。我们学习了通过测试、失败测试和跳过测试。我们还学习了 Cypress 测试运行器如何显示已通过、失败或已跳过的测试状态。最后,我们进行了一项练习,以测试我们对测试分类的知识。现在,让我们继续撰写一个实际的测试。

撰写实际测试

在上一节中,我们学习了 Cypress 中不同测试分类的基础知识以及分类结果。在本节中,我们将专注于编写超越断言布尔值是否等于另一个布尔值的测试。

对于任何测试都需要有价值,它需要有三个基本阶段:

  1. 设置应用程序的期望状态

  2. 执行要测试的操作

  3. 在执行操作后断言应用程序的状态

在我们的实际测试中,我们将使用我们的Todo应用程序来编写与编写有意义的测试所需的三个基本阶段相对应的测试。为此,我们将完成以下步骤:

  1. 访问 Todo 应用程序页面。

  2. 搜索元素。

  3. 与元素交互。

  4. 对应用程序状态进行断言。

这些步骤将指导我们即将撰写的实际测试,并将帮助我们全面了解 Cypress 测试。

访问 Todo 应用程序页面

这一步涉及访问 Todo 应用程序页面,这是我们将运行测试的地方。Cypress 提供了一个内置的cy.visit()命令用于导航到网页。以下代码块显示了我们需要遵循的步骤来访问我们的 Todo 页面。这个代码块可以在本书的 GitHub 存储库的chapter-04文件夹中的practical-tests.spec.js文件中找到:

describe('Todo Application tests', () => {
  it('Visits the Todo application', () => {
    cy.visit('http://todomvc.com/examples/react/#/')
  })
})

当此测试运行时,在观察命令日志时,我们将看到visit命令,以及我们刚刚访问的应用程序在右侧的 Cypress 应用程序预览中,如下图所示:

图 4.4 - 访问 Todo 应用程序

图 4.4 - 访问 Todo 应用程序

即使我们的应用程序没有任何断言,我们的测试仍然通过,因为没有导致 Cypress 抛出异常从而导致测试失败的错误。Cypress 命令默认会在遇到错误时失败,这增加了我们在编写测试时的信心。

搜索元素

为了确保 Cypress 在我们的应用程序中执行某些操作,我们需要执行一个会导致应用程序状态改变的操作。在这里,我们将搜索一个 Todo 应用程序输入元素,该元素用于添加一个 Todo项目到我们的应用程序中。以下代码块将搜索负责添加新 Todo 项目的元素,并验证它是否存在于我们刚刚导航到的 URL 中:

it('Contains todo input element', () => {
  cy.visit('http://todomvc.com/examples/react/#/')
  cy.get('.new-todo')
});

当 Cypress 的cy.get()命令找不到输入元素时,将抛出错误;否则,Cypress 将通过测试。要获取输入元素,我们不需要验证元素是否存在,因为 Cypress 已经使用大多数 Cypress 命令中链接的默认断言来处理这个问题。

重要提示

Cypress 中的默认断言是内置机制,将导致命令失败,而无需用户声明显式断言。通过这些命令,Cypress 会处理异常的行为,如果在执行该命令时遇到异常。

以下屏幕截图显示了 Cypress 搜索负责向我们的 Todo 列表添加 Todo 项目的 Todo 输入元素:

图 4.5 - 搜索 Todo 输入元素

图 4.5 - 搜索 Todo 输入元素

在这里,我们可以验证 Cypress 访问了 Todo 应用程序的 URL,然后检查添加 Todo 项目的输入元素是否存在。

与待办事项输入元素交互

现在我们已经确认我们的待办事项应用程序中有一个输入元素,是时候与应用程序进行交互并改变其状态了。为了改变待办事项应用程序的状态,我们将使用我们验证存在的输入元素添加一个待办事项。Cypress 将命令链接在一起。为了与我们的元素交互,我们将使用 Cypress 的.type()命令向元素发送一个字符串,并将待办事项添加到应用程序状态中。以下的代码块将使用待办事项输入元素添加一个新的待办事项:

it('Adds a New Todo', () => {
  cy.visit('http://todomvc.com/examples/react/#/')
  cy.get('.new-todo').type('New Todo {enter}')
});

上面的代码块建立在之前的代码基础上,使用了 Cypress 的type()函数来添加一个新的待办事项。在这里,我们还调用了 Cypress type方法的{enter}参数来模拟Enter键的功能,因为待办事项应用程序没有提交按钮供我们点击来添加新的待办事项。以下的截图显示了添加的待办事项。通过这个项目,我们可以验证我们的测试成功地添加了一个新的待办事项。这个项目在待办事项列表上是可见的:

图 4.6 – 与待办事项输入元素交互

图 4.6 – 与待办事项输入元素交互

我们的测试运行器显示已创建了一个新的待办事项。再次,我们的测试通过了,即使没有断言,因为已运行的命令已经通过了默认的 Cypress 断言。现在,我们需要断言应用程序状态已经改变。

断言应用程序状态

现在我们已经添加了我们的待办事项,我们需要断言我们的新待办事项已经被添加,并且应用程序状态已经因为添加待办事项而改变。为了做到这一点,我们需要在添加待办事项后添加一个断言。在下面的代码块中,我们将断言我们对应用程序状态的更改。在这里,我们添加了一个断言来检查.Todo-list类,它包含了列表项,是否等于2

it('asserts change in application state', () => {
      cy.visit('http://todomvc.com/examples/react/#/')

      cy.get('.new-todo').type('New Todo {enter}')
      cy.get('.new-todo').type(Another Todo {enter}')
      cy.get(".todo-list").find('li').should('have.length', 2)
   });

为了进一步验证我们的状态更改,我们可以添加更多的待办事项来验证随着我们添加待办事项,待办事项的数量是否增加。

在 Cypress 中,我们可以使用断言函数,比如.should()expect(),它们都包含在构成 Cypress 的工具中。默认情况下,Cypress 扩展了 Chai 库中的所有函数,这是默认的 Cypress 断言库。下面的截图显示了两个已添加的待办事项和 Cypress 预览中的确认说明,说明这两个已添加的待办事项存在于待办事项列表中:

图 4.7 – 断言应用程序状态

图 4.7 – 断言应用程序状态

在这个测试中,我们可以验证所有添加的待办事项是否在 Cypress 应用程序的预览页面上可见,并且我们的断言通过了。现在我们可以添加更多的断言,特别是检查第一个待办事项的名称是否为New Todo,而另一个添加的待办事项是否叫做Another Todo。为了做到这一点,我们将在我们的测试中添加更多的断言,并检查我们的待办事项的具体细节。在下面的代码块中,我们将验证 Cypress 是否能够检查已添加的待办事项的名称;即New TodoAnother Todo

  it('asserts inserted todo items are present', () => {
     cy.visit('http://todomvc.com/examples/react/#/')

     cy.get('.new-todo').type('New Todo {enter}')
     cy.get('.new-todo').type('Another Todo {enter}')
     cy.get(".todo-list").find('li').should('have.length', 2)
     cy.get('li:nth-child(1)>div>label').should(         'have.text', 'New Todo')
     cy.get('li:nth-child(2)>div>label').should(         'have.text', 'Another Todo')
    });

在这些断言中,我们使用了 Cypress 的cy.get()方法通过它们的 CSS 类来查找元素,然后通过它们的文本标识了第一个和最后一个添加的待办事项。

实际测试练习

使用技术要求部分提到的 GitHub 存储库链接,编写一个测试,导航到待办事项应用程序并向其添加三个新的待办事项。编写测试来检查已添加的待办事项是否存在,通过验证它们的值和数量。

总结 – 编写实际测试

在本节中,我们编写了我们的第一个实际测试。在这里,我们访问了一个页面,检查页面是否有一个输入元素,与该元素进行交互,并断言应用程序状态是否发生了变化。在了解了 Cypress 中测试的流程之后,我们现在可以继续并了解 Cypress 中使测试编写变得有趣的功能,比如自动重新加载。

Cypress 的自动重新加载功能

默认情况下,Cypress 会监视文件更改,并在检测到文件更改时立即重新加载测试。这仅在 Cypress 运行时发生。Cypress 的自动重新加载功能非常方便,因为您无需在对测试文件进行更改后重新运行测试。

通过自动重新加载功能,可以立即获得反馈,并了解他们的更改是否成功,或者他们的测试是否失败。因此,这个功能可以节省本来用于调试测试或检查所做更改是否修复问题的时间。

虽然 Cypress 的自动重新加载功能默认启用,但您可以选择关闭它,并在进行更改后手动重新运行测试。Cypress 允许您停止监视文件更改。这可以通过配置cypress.json文件或使用 Cypress 的命令行配置选项来完成。在使用cypress.json配置文件时,您必须使用以下设置来禁用监视文件更改:

{
 "watchForFileChanges": "false"
}

此设置将持续并永久禁用文件更改,只要 Cypress 正在运行,除非配置被更改为true。在禁用 Cypress 监视文件更改方面的另一个选项是使用此处显示的命令行配置选项:

cypress open --config watchForFileChanges=false

使用这个命令,Cypress 将暂时停止监视文件更改,并且只有在我们在终端窗口停止 Cypress 执行时才会改变这种行为。然后 Cypress 将继续监视文件更改,并在对测试文件进行更改时自动重新加载。

总结 - Cypress 的自动重新加载功能

在本节中,我们学习了 Cypress 如何利用自动重新加载功能来监视文件更改,并在测试文件发生任何更改时立即重新加载和重新运行。我们还学习了如何通过永久禁用它使用cypress.json文件或在运行测试时通过命令行配置传递命令来轻松关闭 Cypress 的自动重新加载功能。接下来,我们将看看 Cypress 断言。

Cypress 断言

正如我们在上一节中学到的,当编写我们的第一个测试时,断言存在是为了描述应用程序的期望状态。Cypress 中的断言就像是测试的守卫,它们验证期望状态和当前状态是否相同。Cypress 断言是独特的,因为它们在 Cypress 命令运行时会重试,直到超时或找到元素为止。

Cypress 断言源自chaichai-jquerysinon-chai模块,这些模块与 Cypress 安装捆绑在一起。Cypress 还允许您使用 Chai 插件编写自定义断言。但是,在本节中,我们将重点放在 Cypress 捆绑的默认断言上,而不是可以扩展为插件的自定义断言。

我们可以以两种方式编写 Cypress 断言:要么显式定义主题,要么隐式定义主题。Cypress 建议在断言中隐式定义主题,因为它们与 Cypress 命令正在处理的元素直接相关。以下是 Cypress 框架中断言的分类方式:

  • 隐式主题:.should().and()

  • 显式主题:expect()

让我们详细看看每一个。

隐式主题

shouldand命令是 Cypress 命令,这意味着它们可以直接作用于 Cypress 立即产生的主题。这些命令也可以与其他 Cypress 命令链接,这使它们易于使用,同时在调用它们时保证立即响应。以下代码块演示了如何测试隐式主题。在这里,我们将使用cy.get命令的输出来对我们的测试进行断言:

describe('Cypress Assertions', () => {
    it('Using Implicit subjects - should', () => {
        cy.visit('http://todomvc.com/examples/react/#/')
        // Check if todo input element has expected 
        // placeholder value
        cy.get(".new-todo").should('have.attr', 'placeholder',
        'What needs to be done?')
    });
});

在这里,我们使用should()命令来断言 Todo 项目的输入元素是否具有占位符值。should命令是从cy.get()命令链接的。这不仅使其易于使用,而且还减少了断言占位符是什么的代码量。在以下代码块中,我们正在组合cy.get命令返回的隐式主题的不同断言:

it('Using Implicit subjects - and()', () => {
        cy.visit('http://todomvc.com/examples/react/#/')
        // Check if todo input element has expected   
        // placeholder value
        cy.get(".new-todo")
         .should('have.attr', 'placeholder',
          'What needs to be done?')
        .and('have.class', 'new-todo')
    });

在这里,我们使用了.and()Cypress 命令来进一步验证刚刚产生的元素既有一个占位符,又有一个名为new-todo的 CSS 类。通过这些隐式断言,我们可以验证通过隐式主题,我们可以从 Cypress 的相同产生的响应中链接多个命令,并且还可以断言不同的项目。以下代码块显示了使用显式主题进行的代码断言,其中我们必须声明我们正在断言的每个主题:

it('Using Explicit subjects', () => {
        cy.visit('http://todomvc.com/examples/react/#/')
        cy.get(".new-todo").should( ($elem) => {
        expect($elem).to.have.class('new-todo')
        expect($elem).to.have.attr('placeholder','What needs 
        to be done?')
        })
    });

正如您所看到的,当使用隐式主题时,我们可以进行更清晰的断言,并减少我们编写的代码量。在这个代码块中,每个断言都必须在同一行上并且单独执行。

显式主题

当我们想要断言在运行测试时定义的特定主题时,我们使用expect()。显式主题在单元测试中很常见,在需要在执行断言之前执行一些逻辑或者对同一主题进行多个断言时非常有用。以下代码块显示了使用expect方法进行显式主题断言:

it('can assert explicit subjects', () => {
  const eqString = 'foo';  
  expect(eqString).to.eq('foo');
  expect(eqString).to.have.lengthOF(3);
  expect(eqString).to.be.a('string');
})

这个代码块显示了对实例化的string与我们的期望进行显式比较。声明的string是一个显式主题,这意味着它可以被断言多次,并且在执行断言之前也可以被操作。

对于复杂的断言,我们可以使用.should()方法来断言显式主题。这允许传递一个回调函数作为第一个参数,该回调函数具有作为第一个参数产生的主题。我们可以在should函数内添加断言,如下所示:

it('Using Should with Explicit subjects', () => {
        cy.visit('http://todomvc.com/examples/react/#/')
        cy.get(".new-todo").should( ($elem) => {
        expect($elem).to.have.class('new-todo')
        })
 });

在这里,我们访问了 URL,然后使用从cy.get('new-todo')产生的元素来断言名为new-todo的 CSS 类是否存在。这个测试允许我们查询一个元素,并且根据需要为主题编写不同的断言。

练习-隐式和显式主题

使用您从本节中获得的知识,并使用技术要求部分提到的 GitHub 存储库链接作为参考点,完成以下练习。

转到 Todo 应用程序 URL(todomvc.com/examples/react/#/)并添加一个 Todo:

  • 使用隐式主题断言编写一个测试,以断言 Todo 已添加,并且输入的名称与 Todo 项目列表上显示的名称相同。

  • 在 Todo 应用程序 URL 上,将一个 Todo 标记为已完成。然后,使用显式主题的断言,编写一个测试来验证已完成的 Todo 是否已标记为已完成。

总结- Cypress 断言

在本节中,我们学习了如何断言显式和隐式主题,并看了它们之间的不同和相似之处。我们还了解到不同的断言类型可以用于不同的主题。然后我们有机会进行练习,以练习我们断言隐式和显式主题的技能。

Summary

在本章中,我们学习了如何通过理解 Cypress 中的通过、失败和跳过测试的含义以及 Cypress 在测试运行器和命令日志中查看和表示测试来对测试进行分类。我们还了解了测试文件的结构以及 Cypress 测试的可接受文件扩展名。然后,我们编写了我们的第一个实际测试,测试了一个待办事项应用程序能够添加、删除和标记待办事项为已完成。本章的重点是学习 Cypress 如何监视文件更改以及我们如何在 Cypress 中进行断言,无论是通过显式断言我们的测试对象还是隐式断言它们。通过完成本章,您将了解如何通过使用元素并理解可用的断言来在 Cypress 中编写基本测试。在下一章中,我们将学习如何在 Cypress 中调试运行测试以及我们可以用于此目的的工具。

第五章:调试 Cypress 测试

调试是识别和消除软件应用程序中的错误的能力。了解 Cypress 中的调试并学习如何解释 Cypress 的调试输出对于使用 Cypress 框架至关重要。Cypress 以其能够立即提供关于测试是否通过或失败的反馈而自豪。为了让 Cypress 实现即时反馈机制,它必须在调试消息的结构上有效,以便为用户提供解释的便利性。

要在本章取得成功,您需要阅读前几章,因为它们将帮助您了解测试的运行方式,Cypress 的工作原理以及我们可以运行 Cypress 测试的不同方式。在本章中,我们将专注于在测试运行器中运行 Cypress 测试时调试 Cypress 测试。

虽然本章将探讨使用测试运行器调试 Cypress,但 Cypress 捆绑了其他调试工具,我们可能不会在本章中涵盖,因为它们要么已经在前几章中涵盖过,要么超出了本书的范围。在本章中,我们将学习 Cypress 调试在测试运行器中的工作原理。为此,我们将涵盖以下主题:

  • 理解页面事件

  • 理解测试运行器上的错误

  • 理解执行测试的时间旅行

  • 理解测试快照

  • 理解控制台调试输出

  • 特殊调试命令

一旦您完成了这些主题中的每一个,您就准备好开始本书的第二部分,其中涉及使用测试驱动开发TDD)方法编写 Cypress 测试。

技术要求

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。

本章的源代码可以在chapter-05目录中找到。

理解页面事件

Cypress 记录测试运行时发生的每个主要事件。它可以检测到 URL 的更改,按钮的点击,甚至断言的执行。页面事件捕获了测试运行时 DOM 经历的重要事件。

为了演示页面事件的工作原理,我们将使用我们的待办事项应用程序,就像在上一章中一样。在我们的 GitHub 存储库中的chapter-05目录中,我们将在 Cypress 集成子目录中创建我们的测试文件,并将其命名为debugging.spec.js。然后,我们将在新创建的规范文件中创建我们的测试,该测试将导航到待办事项应用程序,添加一个待办事项,并检查在我们的 Cypress 测试运行器中弹出的页面事件。以下代码块将处理将待办事项添加到我们的应用程序中:

it('can add a todo', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".todo-list").find('li').should('have.length', 
      1)
 });

在这个测试中,我们正在添加一个待办事项,并检查我们添加的项目是否可以从待办事项列表中查看。以下屏幕截图显示了一个 XHR 页面事件:

图 5.1 - XHR 页面事件

图 5.1 - XHR 页面事件

上述屏幕截图显示了前一个测试的命令日志的一部分。名为xhr的突出显示部分是在 Cypress 中加载新页面的页面事件。页面事件由 Cypress 机制自动检测到,并自动记录 - 不是作为需要执行的命令,而是作为触发应用程序状态变化的事件。

Cypress 记录以下页面事件:

  • 提交表单

  • 加载新页面

  • 用于网络调用的 XHR 请求

  • 测试 URL 的哈希更改

要识别 Cypress 页面事件,我们需要查找 Cypress 命令日志中灰色且没有任何编号的日志,例如在执行 Cypress 测试中的命令。

总结 - 理解页面事件

在本节中,我们介绍了页面事件是什么,它们何时以及如何被记录,以及如何在 Cypress 中识别它们。我们还了解到页面事件在追踪测试执行时发生的主要事件方面是有用的。在下一节中,我们将看看当测试抛出错误时如何获得进一步的调试信息。我们将通过理解可能抛出的错误消息来做到这一点。

了解测试运行器上的错误

在本节中,我们将解析测试运行器上的 Cypress 错误,从而解开 Cypress 抛出的错误的内容以及如何解释它们。我们将涵盖 Cypress 错误中存在的不同类型的信息,包括错误名称、错误消息、代码框架文件、堆栈跟踪、打印到控制台选项和了解更多。了解 Cypress 中的错误不仅有助于我们编写更好的测试,还将在测试失败时指导我们进行调试过程。

Cypress 在测试失败事件中记录异常方面做得非常出色。Cypress 不仅记录了哪些测试失败,而且还深入挖掘了遇到的错误的具体信息。例如,Cypress 命令日志上可见成功的测试执行以及提供了可能导致遇到错误的描述性信息。有时,Cypress 甚至会在命令日志上打印出解决错误所需的建议。

在本节中,我们将在debugging.spec.js中添加一个测试,当在 Cypress 中运行时会抛出一个错误。在接下来的测试中,我们将探索 Cypress 在遇到错误时提供的信息,并尝试理解为什么这些信息与调试过程相关。

it('Error Test: can add a todo', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".todo-list").find('li').should('have.length', 
      2)
 });

这个测试应该故意抛出一个错误,因为我们期望待办事项的数量等于2,尽管我们只添加了一个名为New Todo的待办事项。

Cypress 抛出的每个错误都包含以下信息。这些信息将帮助您确定问题的根源以及导致 Cypress 抛出错误的原因:

  • 错误名称

  • 错误消息

  • 代码框架文件

  • 代码框架

  • 堆栈跟踪

  • 打印到控制台选项

  • 了解更多(可选)

让我们详细看看每一个。

错误名称

Cypress 会抛出不同类型的错误,这取决于它遇到的错误。Cypress 中的错误通过它们的类型进行识别,它们可以按照 Cypress 错误和断言错误等类型进行分类。Cypress 抛出的错误类型有助于调试。这是因为我们可以充分了解测试是从正在运行的测试失败还是 Cypress 内部遇到的错误。这个错误显示在图 5.2中,被引用为1,带有错误名称。

错误消息

每个错误都伴随着一条消息。这条消息详细解释了测试运行时出了什么问题。错误消息因测试而异。虽然有些消息可能很直接地告诉您出了什么问题,但其他消息会更进一步,甚至详细说明您可以采取哪些步骤来修复错误。一些错误消息包含一个了解更多部分,它将引导您查阅与遇到的错误相关的 Cypress 文档。这个错误消息显示在图 5.2中,被引用为2

代码框架文件

这是包含 Cypress 遇到的错误的文件。该文件显示为堆栈跟踪的最顶部项目。代码框文件显示了在 Cypress 错误框中突出显示的行号和列号。当单击堆栈跟踪中的代码框文件时,它将在首选编辑器中打开,并突出显示发生错误的行和列,如果用于打开文件的编辑器支持代码突出显示的话。我们可以在图 5.2 中看到代码框文件,它被引用为数字3

代码框

这是 Cypress 标记为错误原因的代码片段。它可以在先前提到的代码框文件中找到。Cypress 在代码框片段中突出显示了导致测试执行问题的特定行,以及列。我们可以通过检查图 5.2 中标有4的代码片段来确定导致失败的代码框。

堆栈跟踪

堆栈跟踪显示了在错误发生时正在执行的不同方法,导致了异常。在 Cypress 错误中,您可以切换堆栈跟踪,它可以在错误的代码框下方找到。这应该向您显示测试在遇到错误并失败时正在执行的函数。图 5.2 中的数字5显示了堆栈跟踪区域。

打印到控制台

Cypress 错误还为您提供了将遇到的错误打印到 DevTools 控制台的选项。将遇到的错误打印到命令提示符的选项使我们能够选择堆栈跟踪中的一行并将其打印到控制台。我们可以在图 5.2 中看到这一点,标有6

了解更多

正如我们之前提到的,一些测试失败会打印出一个了解更多的链接,单击该链接将为我们提供有关发生的错误的相关 Cypress 文档的指引。当错误可能需要调整断言或正在测试的期望之外的更多时,Cypress 失败会提供了解更多的链接:

图 5.2 - 测试错误时显示的信息

图 5.2 - 测试错误时显示的信息

前面的截图显示了测试抛出异常时显示的错误信息的时间结构。正如我们所看到的,测试只向待办事项列表添加了一个项目,但期望找到两个。错误发生在测试断言上,因为 Cypress 期望找到两个项目,但只找到了一个,导致了错误。

失败测试提供的信息对于调试过程至关重要。这是因为不仅易于确定测试失败的原因,而且还帮助我们了解需要进行哪些更改才能将测试从失败状态恢复到通过状态。

总结 - 了解测试运行器上的错误

在本节中,我们了解了 Cypress 错误的信息量有多大。我们得以调查嵌入在 Cypress 错误消息中的不同信息片段以及它们在调试过程中的作用。了解 Cypress 在发生错误时如何呈现其错误使我们能够知道如何处理 Cypress 错误以及了解这些错误来自何处。在接下来的部分,我们将看一下 Cypress 的时间旅行功能。

了解执行测试的时间旅行

时间旅行,就像科幻电影中的情节一样,但现在是在测试的背景下,它是指能够回到测试执行时的状态。当 Cypress 测试执行时,它们会创建 DOM 快照,我们可以利用这些快照来回溯时间,检查测试在不同时间和不同操作发生时的状态。通过时间旅行,我们可以检查预期的操作是否发生以及它是如何发生的。时间旅行还允许我们调查和审计测试运行时采取了哪些操作以及为什么会出现错误。

为了研究 Cypress 测试中的时间旅行,我们将导航到本书的 GitHub 存储库中的chapter-05文件夹,并在debugging.spec.js文件中创建一个新的测试,这是我们之前创建的。以下代码块是一个测试,将标记添加的待办事项为已完成。通过时间旅行,我们可以识别应用程序的不同状态,当我们添加待办事项时,然后将它们标记为已完成:

it('can mark a todo as completed', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".new-todo").type("Another New Todo {Enter}");
      cy.get('.todo-list>li:nth-
      child(1)').find('.toggle').click();
      cy.get('.todo-list>li:nth-
      child(2)').find('.toggle').click();
    });

上面的代码块向待办事项列表中添加了两个待办事项,然后将待办事项标记为已完成。使用 Cypress 的时间旅行功能,我们可以参考 Cypress 来检查我们添加第一个待办事项时的状态,甚至是添加第二个待办事项时的状态。通过使用时间旅行功能,如下截图所示,我们可以进一步验证在将它们标记为已完成之前,这两个项目在正确的状态下,并且在执行过程中进行了适当的导航:

图 5.3-测试中的时间旅行和 DOM 快照

图 5.3-测试中的时间旅行和 DOM 快照

在上面的截图中,我们可以看到测试已经完成运行并且已经通过了。我们还可以看到我们可以倒退时间并调查当待办事项列表中的第一个待办事项被点击时发生了什么。由于 Cypress 可以倒退时间并在特定时间点向我们显示 DOM,我们实际上可以验证达到测试的最终结果的步骤——无论是测试通过还是测试失败。所示的数字显示了 Cypress 时间旅行机制的主要部分和事件发生的顺序。

时间旅行的第一步是等待测试运行完成,然后选择要倒退时间的步骤。Cypress 不仅显示测试步骤,还允许您将步骤的 DOM 快照固定到 Cypress 预览窗口。

选择时间旅行步骤后,我们选择的感兴趣的步骤被固定为 DOM 快照。我们可以查看步骤在当时的状态以及在操作发生后转变为的新状态。这可以在上面截图的预览窗口中看到。

时间旅行检查过程的第三步是在之后之前之间选择 DOM 快照。在之后之前之间切换显示 DOM 快照中的更改。这种切换帮助我们了解我们正在检查的 Cypress 步骤的操作如何改变了那个特定阶段的 DOM。当我们完成检查时,我们可以继续到下一个执行步骤并固定测试在那个特定执行步骤的状态。

重要提示

Cypress 时间旅行在测试仍在执行并且尚未通过或失败时不起作用。为了获得正确的结果,您必须等待执行完成,然后才能看到所有相关步骤的最终结果。

总结-了解执行测试的时间旅行

在本节中,我们了解了 Cypress 如何为我们提供时间旅行功能,以便我们可以返回到 Cypress 执行测试的不同步骤。在 Cypress 中进行时间旅行允许我们检查 Cypress 执行测试的步骤,无论是将其声明为失败还是通过。我们还有机会看到时间旅行功能如何与快照功能配合使用,我们将在下一节中介绍。

了解测试快照

当我们解释 Cypress 中的时间旅行过程时,我们简要介绍了快照的概念。然而,这并不意味着我们已经充分利用了快照功能的优势。

快照非常强大,因为它们让我们一睹测试的执行过程以及所采取的步骤,这些步骤要么导致测试失败,要么导致测试成功。当我们固定 DOM 快照时,Cypress 会冻结测试并突出显示所有已执行的操作。固定的快照允许我们检查 DOM 的状态,同时查看在该特定步骤中发生的所有事件。例如,在前面的屏幕截图中,在步骤 2中,有一个显示第一个待办事项被点击的事件点击框。以下屏幕截图显示了 Cypress 在测试运行时如何解释发生的事件:

图 5.4 - 一个切换的待办事项的事件点击框

图 5.4 - 一个切换的待办事项的事件点击框

在前面的屏幕截图中显示了事件点击框的作用。在这里,我们可以看到发生了一个点击事件,影响了待办事项应用程序的状态。

重要提示

事件点击框是在固定的 Cypress 快照上弹出的突出显示,以显示测试与元素的交互。事件点击框可以由 Cypress 事件触发,例如.click()方法。

快照菜单允许我们在快照的状态之间切换。如果发生了改变 DOM 的事件,我们可以切换以查看改变发生前的状态,然后切换以查看改变发生后的状态。之前快照切换将显示所选测试步骤触发的任何事件之前的状态。另一方面,之后切换将显示所选步骤触发事件后应用程序的状态。以下屏幕截图显示了固定的 DOM 快照的切换,显示了事件发生前快照的样子以及事件发生后快照的样子:

图 5.5 - 一个 DOM 快照菜单

图 5.5 - 一个 DOM 快照菜单

在前面的屏幕截图中,我们可以看到快照菜单项。第一个类似窗口的图标将隐藏或显示固定的 DOM 快照上的事件点击框,而之前之后菜单用于显示所选步骤的 DOM 的转换。快照菜单的关闭图标在点击时会取消固定 DOM 快照,并将其恢复到没有固定 DOM 快照的测试完成步骤。

重要提示

快照菜单项的之前之后事件的显示取决于发生的事件。在改变 DOM 状态的事件中,之前和之后的快照将是不同的。当执行的操作不直接改变 DOM 时:可能会在测试步骤的之前和之后状态中有相似的快照。

总结 - 理解测试快照

在本节中,我们学习了 Cypress 在每次测试运行后如何将重要的调试信息存储在 DOM 快照中。我们还学习了如何利用 Cypress 快照来检查测试步骤的前后状态,然后在调试的调查过程中使用这些信息。在接下来的部分中,我们将学习如何利用控制台的调试输出来获取信息。

理解控制台调试输出

在本节中,我们将了解如何利用 Cypress 的控制台调试输出来理解应用程序状态的变化。我们将在浏览器的控制台中打开并与控制台输出进行交互。理解浏览器控制台中的输出将使我们能够更好地调试测试,因为我们可以调查 Cypress 抛出的错误并快速解决问题。

Cypress 非常擅长提供调试信息。由于快照提供的所有信息可能不足够,Cypress 提供了额外的步骤,以便您可以查看特定步骤的信息及其对元素的影响。要查看控制台调试输出,我们需要打开 DevTools。要打开 Cypress 测试浏览器的 DevTools 控制台,我们需要按照一定的步骤进行操作,所有这些步骤将在以下各节中讨论。

macOS

要在 macOS 上打开 Cypress 测试浏览器的DevTools控制台,请按照以下步骤操作:

  1. 在 Cypress 测试浏览器预览时,用两根手指按住触控板。

  2. 从弹出菜单中选择Inspect选项。

  3. DevTools控制台中选择Console选项卡。

您还可以使用Option + J快捷键在 Mac 上打开DevTools菜单。

Windows/Linux 操作系统

要在 Windows 和 Linux 操作系统上打开 Cypress 测试浏览器的DevTools控制台,请按照以下步骤操作:

  1. 在 Cypress 测试预览时,右键单击 Cypress 测试浏览器。

  2. 从浏览器弹出菜单中选择Inspect选项。

  3. DevTools控制台中选择Console选项卡。

您还可以使用Shift + Ctrl + J快捷键在 Windows 操作系统或 Linux 上打开DevTools控制台。

一旦您可以看到控制台输出,请选择一个测试步骤,如下面的屏幕截图所示:

图 5.6-浏览器控制台上的调试输出

图 5.6-浏览器控制台上的调试输出

上述屏幕截图显示了在命令提示符上选择的 Cypress 命令的输出。正如我们所看到的,当单击特定命令步骤时,DOM 快照会固定到 Cypress 浏览器的预览屏幕上。固定 DOM 快照使我们能够在固定快照上无间断地与元素交互。

在上述屏幕截图中,我们选择了get方法和第一个待办事项,可以通过.todo-list>li:nth-child(1)CSS 选择器进行识别。我们还可以看到 Cypress get方法找到了第一个待办事项的 CSS 选择器,并将其切换为已完成状态。通过查看控制台调试信息,我们可以看到 Cypress 在控制台上打印的与操作步骤相关的附加信息,现在已固定到 DOM 上。

Console区域,我们可以看到以下内容:

  • Command:这是我们发出的命令。在我们的情况下,它是一个cy.get()命令。

  • Yielded:这会打印由调用的命令返回的语句。在我们的情况下,它将打印与输入相同的内容。这是因为我们没有改变元素的状态。

  • Elements:这会打印从我们的get命令返回的元素。在我们的情况下,我们只有一个元素是通过 CSS 选择器找到的。但是,如果我们有多个元素,我们将能够看到找到的元素。

  • Selector:这是指我们用来在 DOM 中识别待办事项的 CSS 选择器。

重要提示

由于发出和检查的不同命令,控制台上显示的信息可能会发生变化。这并不是所有在控制台日志上检查的 Cypress 命令的标准。

使用这些调试信息,并将其与我们之前介绍的方法的调试信息相结合,将使您了解 Cypress 测试失败的原因。在大多数情况下,您只需要学习如何阅读常见的 Cypress 错误,以了解错误是如何抛出的以及为什么会出现这些错误。

总结-了解控制台调试输出

在本节中,我们学习了如何利用 Cypress 中的控制台调试输出来了解应用程序状态的变化。我们还学习了如何打开和访问控制台信息并与其交互。在下一节中,我们将学习如何利用 Cypress 的特殊调试命令。

特殊调试命令

如果跳转命令不是您的菜,或者您发现难以理解如何通过时间倒流来显示测试执行顺序,Cypress 会帮助您。Cypress 包括对调试有帮助的命令,甚至为您提供了在使用普通代码调试器时会有的选项。我们将在本节中探讨的两个命令如下:

  • cy.debug()

  • cy.pause()

使用这些 Cypress 调试命令,我们可以了解如何从测试本身调试 Cypress。这两个特殊的调试命令将允许我们在执行测试时直接控制调试过程。在测试本身中停止执行的能力使我们能够只调试在 Cypress 中抛出错误的特定部分。

cy.debug

cy.debug()命令是 Cypress 默认提供的调试命令。该命令将记录到控制台,并记录其链式调用的命令的输出。要使用cy.debug()命令,您需要从任何cy命令进行链式调用,或者将其用作独立的 Cypress 命令。在我们的上下文中,我们将通过从cy.get()命令进行链式调用来使用该命令。

该命令在调用时暂停测试的执行,并显示系统地从命令向前步进并暂停调试器的选项。实际上,调试器允许我们以所需的速度执行测试,同时检查执行步骤时发生了什么。除了调试器界面外,该 Cypress 命令还会在控制台输出中显示详细信息,例如命令名称、命令类型,甚至是我们从中链式调用调试器的主题。

现在我们已经添加了我们的两个待办事项,并检查了控制台日志和 Cypress 测试运行器预览窗格,我们可以添加调试器。以下代码块显示了一个将待办事项标记为已完成的测试。但是,我们将在添加第二个待办事项后打开调试器,而不是执行整个测试:

it('Special commands-debug : can mark a todo as completed', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".new-todo").type("Another New Todo 
      {Enter}").debug();
      cy.get('.todo-list>li:nth-
      child(1)').find('.toggle').click();
      cy.get('.todo-list>li:nth-
      child(2)').find('.toggle').click();
    });

在上述代码块中,我们希望在添加第二个待办事项后检查我们的应用程序状态。以下屏幕截图显示了在添加第二个待办事项后打开的调试器:

图 5.7 - 运行测试的调试器

图 5.7 - 运行测试的调试器

正如我们所看到的,调试器在添加第二个待办事项后暂停了我们的运行测试。在这里,我们可以观察到,一旦调试器暂停了我们的运行测试,我们就可以以自己的步调与应用程序进行交互和检查元素。有了调试器,我们可以看到应用程序状态的变化,以及在控制台输出中显示的其他调试信息。完成检查状态后,我们可以删除.debug()命令,或者将其放在我们希望检查的另一行中。

cy.pause

Cypress 的pause命令与cy.debug()命令非常相似,但它不是链式调用其他命令,而是可以独立使用,就像调试器一样。当使用pause命令时,Cypress 会减慢执行速度,并且只有在单击前进按钮时才执行下一步。与调试器一样,Cypress 的pause命令将控制权交给执行测试的人,并允许他们调查每个测试步骤。以下代码块显示了一个将待办事项标记为已完成的测试。但是,在执行完成之前,我们在添加第一个待办事项后暂停测试:

it('Special commands - Pause: can mark a todo as completed', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.pause();
      cy.get('.todo-list>li:nth-
      child(1)').find('.toggle').click();
 });

在这里,我们添加了一个待办事项,然后在标记为已完成之前暂停了执行:

图 5.8 - 运行测试的暂停菜单

图 5.8 - 运行测试的暂停菜单

正如我们所看到的,添加待办事项后,执行会暂停,直到我们在暂停菜单的顶部部分按下步进按钮。当所有步骤都执行完毕后,测试将退出,并根据执行的步骤的输出结果,要么通过要么失败。在我们的案例中,我们有一个通过的测试 - 万岁!

重要提示

Cypress 特殊调试命令只应在我们调查运行中的测试状态或进行调试时使用。它们不应该在持续集成CI)中运行的测试中使用,因为这可能会导致超时,随后导致测试失败。

回顾 - 特殊调试命令

在本节中,我们了解了 Cypress 特殊命令,这些命令可用于提供额外的调试信息。我们了解到,当我们想要减慢测试的执行速度时,Cypress 的debugpause命令都非常有用。我们还了解到调试命令可以作为补充工具,用于 Cypress 测试运行器提供的工具,例如 DOM 快照。

总结

在本章中,我们探讨了调试在执行测试时的作用。我们确定了 Cypress 框架的一些方面,这些方面使得 Cypress 中的调试过程对于任何编写测试和实施 Cypress 框架的人都非常有用。我们还了解到,Cypress 捆绑了不同的工具,可以用于实现不同的目的或相同的目的。最重要的是,无论遇到什么错误,Cypress 都会为您找到一种方法来识别和解决它。

通过完成本章,您已经了解了 Cypress 中的页面事件是什么,如何解释 Cypress 测试运行器的错误,执行测试中时间旅行的工作原理,以及如何解释测试快照。您还学会了如何解释来自 Cypress 的控制台输出信息,以及如何使用可用的两个特殊调试命令。

现在我们了解了调试及其对我们测试的影响,我们可以舒适地深入本书的第二部分,这将涉及使用 Cypress 进行测试驱动开发TDD)方法。在下一章中,我们将通过测试优先的方法开发应用程序,我们将在开始开发应用程序之前编写测试。稍后我们将使用这些测试来指导我们完成应用程序开发的过程。

第二部分:使用 TDD 方法进行自动化测试

本节构成本书的支柱,并将向您介绍与 Cypress 相关的更高级主题以及如何使用它。在本节中,将介绍如何通过测试驱动开发(TDD)来思考一个想法并将其从构思阶段发展到开发阶段。在本章中,我们还将学习诸如使用 Cypress 与元素交互、使用别名以及 Cypress 测试运行器等主题。

本节包括以下章节:

  • 第六章,使用 TDD 方法编写 Cypress 测试

  • 第七章,了解 Cypress 中的元素交互

  • 第八章,了解 Cypress 中的变量和别名

  • 第九章,Cypress 测试运行器的高级用法

第六章:使用 TDD 方法编写 Cypress 测试

现在我们已经完成了本书的第一部分 - 也就是作为前端应用的端到端测试解决方案的 Cypress - 是时候转向本书的第二部分了,它将专注于使用 TDD 方法进行自动化测试

在我们开始使用TDD(测试驱动开发)方法编写 Cypress 测试之前,我们需要了解如何正确地编写 Cypress 测试。这在本书的前几章中已经涵盖过。要在这个主题上取得成功,您需要了解 Cypress 测试的工作原理,测试的结构以及 Cypress 测试可以用来进行断言的不同方式。这些背景信息将帮助您了解如何在 Cypress 中使用 TDD 以及在软件开发生命周期中使用它所带来的优势。在本章中,我们将利用测试驱动的方法编写测试,这将极大地增加我们对应用程序和软件解决方案的信任和信心。

本章我们的重点将放在如何利用 Cypress 来帮助我们在开始开发之前全面思考一个应用的使用上。我们将应用测试我们的应用在开始开发之前。在这样做的过程中,我们将利用 Cypress 框架作为我们测试的核心。

本章将涵盖以下关键主题:

  • 理解 TDD

  • 在 Cypress 中编写 TDD 测试

  • 修改 TDD 测试

一旦你完成了这些主题,你就准备好学习 Cypress 中的元素交互了。

技术要求

要开始,我们建议您克隆本书的 GitHub 存储库,其中包含我们将在本章中构建的应用程序和所有我们将编写的测试。

本章的 GitHub 存储库可以在以下链接找到

github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress

本章的源代码可以在chapter-06目录中找到。

我们将使用 ReactJS 库来开发我们的应用。

你可以通过运行以下命令来运行 ReactJS 应用程序:

  • cd chapter-6/tdd-todo-app

  • npm install(安装所有必需的依赖项)

  • npm run start(启动 React 应用程序进行测试)

以下链接:

理解 TDD

TDD 是一个依赖于将需求转化为非常具体测试用例的软件开发过程。编写这些测试用例后,代码被编写并根据其他测试用例进行检查。TDD 过程的最后一步是迭代和改进代码,以确保它符合所需的最佳实践,并且测试用例通过。TDD 方法的循环包括以下步骤:

  1. 定义需要实现的功能

  2. 编写新的测试

  3. 运行测试以检查测试是否失败

  4. 编写测试用例的代码

  5. 运行测试以确保测试通过

  6. 重构代码

  7. 重复这个过程

TDD 的目的是在开发开始之前可视化最终结果。这样,就可以预见在开发过程中可能出现的问题或障碍。能够使用 TDD 方法开发功能有助于对解决方案进行批判性思考,并且有助于在应用程序开发过程中需要测试的场景。

假设我们正在创建一个登录功能;从测试的角度来看,我们需要为登录功能想出所有不同的场景。思考这些测试场景将使我们清楚地了解在开发阶段需要发生什么,使得在开发这个应用功能时需求更加清晰。

TDD 有助于减少范围蔓延的可能性,因为从一开始,我们就可以理解项目的目标。有了测试用例,我们可以确定功能并将范围限制在已编写的测试用例之内。了解此功能涉及的内容使开发人员能够制定代码的实现方式。从长远来看,这可能会导致减少开发时间。

重要提示

范围蔓延是指软件开发项目在项目开始后不受控制地增长或范围扩大。

接下来,让我们来看看 TDD 方法的优势。

TDD 的优势

在本节中,我们将更详细地了解在软件开发生命周期中实施 TDD 方法所带来的好处。

更好的项目设计

在使用 TDD 方法进行开发时,开发人员需要考虑代码片段的目标。因此,开发人员将始终以最终目标为出发点。以特定目标开发功能的能力确保开发人员只编写所需和必要的代码,从而导致应用程序具有清晰的结构。

使用 TDD 还可以确保更高的代码质量,因为 TDD 强调使用“不要重复自己”(DRY)原则,这种原则在编写代码时会阻止重复。因此,通过使用 TDD,可以保持函数简单而简洁,代码库易于理解。清洁和简单的代码库易于维护和测试,这对开发人员和代码库维护者是一个额外的优势。

重要提示

DRY 原则是应用开发原则,强调软件模式的不重复和使用抽象来避免或减少冗余。

详细文档

TDD 强制执行引用正在开发的功能的严格文档;开发人员需要提出这样的规范,其中可能包括用户的操作。理解这些操作并将步骤分解为用户故事有助于开发人员实施功能,因此开发的功能非常接近定义的目标。

在编写测试的阶段开发适当的文档也减轻了其他参与方理解特性以重现文档的角色,因为这已经是软件开发过程的一部分。

减少开发时间

可以假设 TDD 在开发应用程序时需要更多时间,在大多数情况下,这是事实。根据这一说法,我们可以假设 TDD 很可能会延迟项目交付日期,但事实并非如此。

采用 TDD 方法,可以覆盖在开发中如果不使用 TDD 方法可能会出现错误的情况。虽然 TDD 可能最初比非 TDD 方法消耗更多时间,但它显著减少了开发人员维护项目和测试产品及其特性所需的工作量。

由于 TDD 强调清晰的代码,可以毫不夸张地说,即使发现了错误,也比在不使用 TDD 的项目中更容易修复。TDD 项目对高质量代码标准和持续反馈的关注使 TDD 项目的代码库易于维护,而非 TDD 项目则不然。

节约成本

在任何项目中,发现并修复错误在开发阶段比错误已经进入生产阶段时更便宜。TDD 专注于在开发过程中消除错误,大大减少了缺陷通过特性的开发和测试阶段的机会。这强化了代码重构原则和错误预防。TDD 方法大大节省了公司在与在生产中发现的错误和缺陷直接相关的行动上的支出。

作为缺陷直接结果而产生的成本可能包括直接收入损失、额外的时间和成本来修复发现的缺陷,甚至可能会失去公司利益相关者(如客户)的信任。了解 TDD 降低这些成本的能力使得公司的节约是值得的,因为开发软件需要花钱,而修复相同的软件则需要花费更多的钱。

可靠的解决方案

TDD 解决方案是可靠的,因为它们在开发开始之前经过了审查。TDD 确保了开发的概念就是实现的内容。这是通过在功能仍然是一个想法的时候编写的测试场景来实现的,并且以需求的形式存在。

没有使用 TDD,开发人员无法在不考虑程序的不同部分如何与新功能交互的情况下构建强大的解决方案。然而,使用 TDD,这些测试用例帮助开发人员了解新功能和现有功能如何集成,因此了解应用程序在新功能构建后的行为。这种方法使开发人员在开始开发之前就对解决方案有信心。

TDD 的缺点

尽管 TDD 的大部分结果都是积极的,可以提高生产力和良好的开发流程,但对于结构不适合使用 TDD 的团队来说,TDD 也可能会带来负面影响。在本节中,我们将重点介绍使用 TDD 的缺点以及为什么它可能不适合某些团队。

组织准备工作

TDD 要求组织在实施过程中参与其中。TDD 要求在实施之前为组织定义需求,以确保成功,组织需要以适当的方式定位 TDD 适用于他们。在某些情况下,组织可能没有耐心等待所有需求在实施开始之前,也可能不愿意牺牲额外的时间来仔细审查需求。

TDD 是结构化的,需要管理层和开发团队一致同意承担与事先规划相关的成本,以便后期在维护上花费更少。并非所有团队都愿意采取等待 TDD 好处的方法,这意味着组织可能不愿意为目前看不到的成本付费。

理解问题

TDD 侧重于在实施开始之前构建测试。这种方法有助于团队更好地理解问题并提出坚实的实施解决方案。编写测试的最大挑战在于它们无法解决已经在实施代码中引入的逻辑错误。测试只能识别它们所测试的内容,可能无法测试代码中未明确定义的内容。

使用 TDD 可能会因为对问题的理解而导致错误;测试可能无法捕捉到设计解决方案的人员对需求理解不正确的情况。

总结 - 理解 TDD

在本节中,我们了解了 TDD,为什么我们需要它以及它如何在软件开发生命周期中使用。我们还了解了使用 TDD 的优势,以及它如何可以防止在后期开发和测试阶段发现的错误和缺陷带来的成本。我们还了解了利用 TDD 的缺点,其中一些可能源于测试的好坏取决于编写测试时的推理。因此,了解正在开发的问题对于为手头的问题制定测试是至关重要的。在接下来的部分中,我们将专注于在 Cypress 中编写 TDD 测试,以及这个过程如何帮助提出功能代码的坚实解决方案和实施。

在 Cypress 中编写 TDD 测试

在本节中,我们将专注于使用 Cypress 编写 TDD 测试。在本节中,我们将构建一个待办事项应用程序,并应用 TDD 原则。首先,我们需要有一个设计,这样我们才能编写适当的测试,并且还要对我们应用程序的功能进行批判性思考。本章的目标将是创建一个应用程序,可以添加待办事项,删除待办事项,显示已添加的待办事项,并显示已添加的待办事项的数量。下面的截图显示了最终应用程序的模拟。我们遵循的每一步都将帮助我们实现我们想要的模拟:

图 6.1 - 待办事项应用程序模拟

图 6.1 - 待办事项应用程序模拟

上面的截图显示了我们将要构建的待办事项应用程序的模拟。我们将使用 Cypress 中编写的 TDD 方法。该应用程序将具有以下功能:

  • 添加新的待办事项

  • 删除待办事项

  • 查看已添加的待办事项

  • 查看已添加的待办事项数量

这些功能构成了我们待办事项应用程序的要求。在本章中,我们将在开发测试和实现应用程序时将这些功能称为要求。

设置应用程序

为了避免在本节中增加任何进一步的复杂性,我们不会关注如何构建应用程序,而是关注在构建应用程序时如何实现测试。在背景上下文中,我们将构建的应用程序将使用 ReactJS 库,该库是用 JavaScript 编写的。

了解了我们的应用程序的外观之后,我们将采取逐步的方法来编写我们的测试,然后再开始开发我们的应用程序的过程。正如我们之前提到的,我们已经编写了我们将要构建的应用程序功能。我们将首先编写 TDD 测试,以便我们可以添加新的待办事项。

添加新的待办事项

我们将专注于的第一个 TDD 测试是负责检查新的待办事项是否已添加到我们的待办事项列表中的测试。要按照这些步骤进行,请使用以下命令导航到您从 GitHub 克隆的tests目录:

 cd chapter-6/tdd-todo-app/integration/todo-v1.spec.js

上面的命令将引导您进入我们将在本章中使用的 TDDtests目录。该文件中的测试是我们在 TDD 过程中编写的测试的第一个版本。稍后,我们将修改它们,使其适应我们将添加的最终应用程序功能。

重要提示

在为我们的待办事项应用程序编写 TDD 测试时,请注意 Cypress 目录位于测试应用程序内部。这确保我们跟踪和识别属于正在开发的应用程序的 Cypress 测试。

以下代码片段是一个测试,检查我们是否可以向我们的应用程序添加新的待办事项,这是我们应用程序的要求之一:

it('can create and display new todo', () => {
      cy.get('[data-testid="todo-item-input"]')
        .type('New todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      cy.contains('New Todo');
});

在上面的代码片段中,我们编写了一个 TDD 测试,以检查在功能完成后,我们可以添加我们的待办事项并检查已添加的项目是否存在。请注意,在这个阶段,添加待办事项的功能尚未构建。如果我们在 Cypress 中运行这段代码片段,它应该会自动失败。为了验证这一点,我们可以运行以下命令来运行 Cypress 测试:

npm run cypress:open 

以下截图显示了一个失败的 TDD 测试,用于创建和显示一个新的待办事项:

图 6.2 - 在 Cypress 上运行 TDD 测试

图 6.2 - 在 Cypress 上运行 TDD 测试

在前面的屏幕截图中,我们正在执行测试,以检查它是否失败以及 Cypress 是否能够执行它。在这个测试中,Cypress 尝试执行针对运行在端口3000上的本地运行的待办应用程序的测试。测试失败,因为 Cypress 找不到负责将待办事项添加到待办事项列表中的输入元素。从前面的屏幕截图中,我们可以验证应用程序成功导航到运行在本地主机上的应用程序。为了继续构建这个功能并确保测试通过,稍后,我们将添加添加待办事项的功能,并重新运行我们的测试。

删除待办事项

我们的待办应用程序要求说明,我们应该有能力删除已添加的待办事项。已删除的待办事项的要求之一是,一旦删除,它就不应再出现在待办事项列表上。为了编写我们的 TDD 测试,我们需要确保我们实际上已删除了待办事项,方法是验证一旦从待办事项列表中删除后,待办事项不再存在。我们将使用以下代码片段来实现删除功能测试要求:

it(can delete added todo item', () => {
      cy.get('[data-testid="todo-item-input"]')
        .type('New todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      cy.get('[data-testid="delete-todo-1-button"]')
        .click();
      expect('[data-testid="todolist"]'
      ).not.to.contain('New Todo')
   });

在上述代码块中,我们添加了一个待办事项,然后将其删除。后来,我们验证了已删除的待办事项不再存在,并通过使用 Cypress 断言方法来断言。这个测试片段不仅检查了待办事项的正确删除,还检查了删除后,待办事项将不再存在于 DOM 中。如前面的屏幕截图所示,使用 Cypress 运行此测试失败,因为我们的应用程序尚未构建。

查看已添加的待办事项

根据我们的应用程序要求,当添加待办事项时,它们应该在待办事项列表中可见。添加的待办事项应该与待办事项列表中的待办事项相同。为了进行适当的测试,我们需要确保我们的测试覆盖了确保添加的待办事项在待办事项列表上可见的情况。我们还需要验证已添加到待办应用程序的项目是否与待办事项列表上可见的项目相同。我们将再次策划一个 TDD 测试,旨在覆盖能够显示我们的待办事项的情况。以下代码块是用于显示已添加的待办事项的 TDD 测试:

it(can view added todo item', () => {
      cy.get('[data-testid="todo-item-input"]')
        .type('New todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      expect('[data-testid="todolist"]').to.contain(
      'New Todo')
 });

在这个代码块中,TDD 测试将使用应用程序的输入元素添加一个新的待办事项,然后验证添加的元素是否存在于待办事项列表中。有了这个测试,就可以排除待办事项被添加但在待办事项列表上不可见的可能性。

查看已添加的待办事项的数量

根据我们应用的要求,我们需要确保能够查看添加的待办事项的数量。根据我们的模拟,也可以在chapter-06/mockups/todo-mockup.png目录中找到,待办事项的数量应该对应于待办事项列表中的项目。根据我们的待办事项应用程序的要求,我们的 TDD 测试应该测试诸如添加多个待办事项并检查待办事项的数量增加或减少的情况,具体取决于它们是添加还是从待办事项列表中删除。

重要提示

在编写测试之前,了解 Cypress 如何理解要与之交互的元素,要单击哪个按钮,或者在输入字段上输入。Cypress 使用元素标识符,这些标识符唯一标识 Cypress 要与之交互的元素。网页上元素的唯一元素标识符可能包括唯一元素 ID CSS 选择器、XPath 定位器,甚至是我们选择的自定义元素标识符,格式为[data-testid="our-unique-identifier"]

与添加、删除或查看待办事项的测试场景不同,这个测试将包含多个步骤和多个断言。以下代码块显示了一个查看已添加到待办事项列表中的待办事项数量的 TDD 测试:

it('can view number of added todo items', () => {
      cy.get('[data-testid="todo-item-input"]')
        .type('New todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      cy.get('[data-testid="todo-item-input"]')
        .type('Another todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      expect('[data-testid="todo-item-number"]').to.eq('2')
      cy.get('[data-testid="delete-todo-1-button"]')
      .click();
      expect('[data-testid="todo-item-number"]').to.eq('1')
    });

这段代码片段将作为最终测试的模板,用于检查待办事项的数量随着待办事项的添加和删除而增加和减少。在这里,我们可以看到我们添加了两个待办事项,然后验证两个待办事项都存在。在验证待办事项列表中存在两个项目后,我们删除了一个待办事项,并检查待办事项的计数随着项目数量的减少而减少。

重要提示

在编写 TDD 测试时,我们并不太关心测试中可能存在的语法错误,而是关注场景和测试覆盖率。当我们在构建功能后开始修改测试时,我们将在再次运行测试时修复错误,这次针对添加的功能。

现在,是时候进行快速回顾了。

回顾-设置应用程序

在本节中,我们学习了如何编写 TDD 测试以及它们如何帮助塑造我们的思维,因为我们开发解决方案。我们涵盖了编写 TDD 测试的过程,用于添加待办事项、查看待办事项、删除待办事项以及查看待办事项列表中的总数。我们还了解到 TDD 测试帮助我们理解开发过程,并且这些测试不是在功能完成时我们将拥有的最终测试。在下一节中,我们将看看在应用程序的功能完成后如何修改 TDD 测试。

修改 TDD 测试

在前一节中,我们看了 TDD 测试的结构以及它们是如何根据正在开发的应用程序进行开发的原理。正如我们之前提到的,我们不会详细介绍如何开发应用程序,而是专注于如何将测试集成到正在开发的应用程序中。这里提到的应用程序可以在本书的 GitHub 存储库中找到(github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress/tree/master/chapter-06/)。

在本节中,我们将使用在上一节中创建的 TDD 测试。我们将要构建的 TDD 测试负责测试应用程序的定义要求,这些要求如下:

  • 添加新的待办事项

  • 删除待办事项

  • 查看已添加的待办事项

  • 查看已添加的待办事项数量

现在我们已经编写了测试,我们将在修改它们时向应用程序添加功能。首先,我们将运行第一个测试,因为我们已经构建了添加待办事项的功能。为了将 TDD 测试和应用程序中的最终测试分开,我们将创建一个名为todo-v2.spec.js的新测试文件,我们将在其中添加我们的最终测试。测试文件位于chapter-06/tdd-todo-app/integration/todo-v2.spec.js目录中。

添加新的待办事项

在这里,我们想要验证我们之前编写的用于验证添加新待办事项的测试是否有效。为了运行这个测试,我们将确保我们的应用程序(使用 ReactJS 构建)在本地运行。我们将针对本地托管的应用程序运行我们的测试。一旦添加新的待办事项功能完成,我们的应用程序将如下所示:

图 6.3-添加新的待办事项功能

图 6.3-添加新的待办事项功能

在前面的截图中,我们可以验证我们的添加待办事项功能是否正常工作,因为我们已经添加了待办事项。现在我们的代码似乎工作正常,是时候检查我们的测试在运行时是否实际通过了。为此,我们将使用todo-v2.spec.js测试文件,这是todo-v1.spec.js的修改版本。

我们修改了位于todo-v1.spec.js的版本 1 测试文件的测试,并且还修改了测试,使其适应我们在应用程序中创建的待办事项添加功能。新测试应如下所示:

it('can create and displays new todo', () => {
      cy.visit('http://localhost:3000/')
      cy.get('[data-testid="todo-input-element"]')
        .type('New todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      cy.get('[data-testid="todolist"]'
        .contains('New todo');
    });

就像在我们的初始测试中一样,要测试的初始场景并没有改变。我们首先导航到本地运行的应用程序的默认 URL。然后,我们使用 Cypress 添加一个待办事项,然后验证添加的待办事项是否与我们最初添加到输入元素中的内容相同。我们可以清楚地查看以下截图中发生的操作,该截图显示了成功的测试:

图 6.4 – 通过测试添加待办事项

图 6.4 – 通过测试添加待办事项

在上述截图中,我们可以看到 Cypress 导航到本地托管的应用程序并添加了一个待办事项,然后检查添加的待办事项是否出现在待办事项列表中。

重要提示

我们为我们的应用程序添加了以data-testid=*为前缀的元素标识符,以唯一标识元素。元素标识符在选择 Web 应用程序中的元素时非常方便。通过添加唯一标识符并且不使用应用程序的默认 CSS 选择器,即使应用程序的选择器发生变化,我们的测试也不会受到影响,仍将正常运行。

通过这样,我们成功完成了 TDD 中的第一个任务。在本节中,我们实现了以下目标:

  • 确定了我们想要开发并创建原型的应用程序

  • 在开发开始之前编写了 TDD 测试

  • 开发了向我们的应用程序添加待办事项的功能

  • 修改了 TDD 测试以使其符合我们开发的功能

以下截图显示了添加新待办事项的 TDD 版本和最终功能版本的测试的并排比较:

图 6.5 – TDD 测试与最终功能测试的比较

图 6.5 – TDD 测试与最终功能测试的比较

正如您所看到的,测试的第二个版本显示,尽管测试结构或目标没有改变,但我们不得不修改测试,以使其适应已开发的待办事项添加功能。识别需求、开发功能,然后修改测试以针对该功能运行是 TDD 的主要目标,我们成功实现了这一点。

删除待办事项

现在,我们将学习如何删除已添加的待办事项。根据我们的需求,删除的待办事项将从待办事项列表中移除,并且一旦单击待办事项的删除按钮,它将不再可见。再次强调,我们不会关注开发该功能的过程,而是关注该功能的测试。在以下截图中,我们可以看到为每个新添加的待办事项显示的删除按钮:

图 6.6 – 删除待办事项功能

图 6.6 – 删除待办事项功能

突出显示为红色的图标是出现在每个待办事项上的删除图标。如果单击删除按钮,添加的待办事项将从我们的待办事项列表中消失,就像我们的需求描述的那样。为了验证该功能是否按照我们设想的那样工作,我们现在将修改我们的 TDD 测试以针对删除功能运行测试。以下代码块是一个测试,用于删除已添加到待办事项列表中的待办事项:

it('can delete an added todo item', () => {
      cy.visit('http://localhost:3000/')      
      cy.get('[data-testid="todo-input-element"]')
        .type('New todo');
      cy.get('[data-testid="add-todo-button"]')
        .click();
      cy.get('[data-testid="delete-todo-0-button"]')
        .click();
      expect('[data-testid="todolist"]'
        .not.to.contain('New todo')
});

这段代码显示了修改后的 TDD 测试,以确认一旦删除了待办事项,它将不再出现在待办事项列表中。我们还必须对我们最初编写的 TDD 测试进行一些微小修改,以使所有选择器和操作与已开发的功能匹配。从以下 Cypress 截图中可以看到,我们的测试通过了,添加的待办事项已被删除,正如我们预期的那样:

图 6.7 – 删除已添加的待办事项

图 6.7 – 删除已添加的待办事项

在这里,Cypress 快照功能帮助我们可视化了 Cypress 点击新添加的待办事项的删除按钮的过程。我们还编写了一个断言来验证一旦删除后,已删除的待办事项在待办事项列表中不存在。我们的测试通过了,这意味着我们已经使用 TDD 向待办事项列表中添加了一个待办事项,并且还删除了这个待办事项并测试了它在待办事项列表中不存在。在我们的下一个测试中,我们将专注于查看已添加的待办事项。

查看已添加的待办事项

我们应用程序的要求之一是查看待办事项列表中已添加的待办事项。在添加待办事项时,我们已经能够看到这个功能在运行,但还没有进行测试。为了验证这个功能,我们将添加一个新的待办事项,并检查创建的待办事项是否出现在待办事项列表中。以下代码块是一个检查已添加的待办事项是否在我们创建的应用程序中可见的测试:

it('can view added todo items', () => {
      cy.visit('http://localhost:3000/')      
      cy.get('[data-testid="todo-input-element"]')
      .type('New todo, {enter}')
      cy.get('[data-testid="todo-input-element"]')
      .type('Another todo, {enter}')
      cy.get('[data-testid="todolist"]').contains(
      'New todo');
      cy.get('[data-testid="todolist"]'
      .contains('Another todo');
    });

在这里,我们修改了我们的 TDD 测试。不仅仅是检查我们是否可以查看单个待办事项,我们添加了两个项目,并添加了一个断言来检查这两个项目是否存在于待办事项列表中。我们将在 Cypress 中运行我们的测试,并使用应用程序预览来验证这两个待办事项是否存在,如下截图所示:

图 6.8 – 查看已添加的待办事项

图 6.8 – 查看已添加的待办事项

万岁!我们的测试通过了!

这个截图显示了我们正确构建了一个添加待办事项的功能的需求,并且我们对查看待办事项在待办事项列表中的测试需求也得到了满足。在这里,我们已经实现了查看我们的待办事项功能的目标。我们还使用了 TDD 来检查在查看我们的待办事项时需要测试的场景。

查看已添加的待办事项数

现在我们已经修改了用于添加待办事项、删除待办事项和查看待办事项的 TDD 测试,我们还想添加一个功能,用于检查已添加的待办事项的数量。查看我们已添加的待办事项数的功能如下截图所示:

图 6.9 – 查看已添加的待办事项数

图 6.9 – 查看已添加的待办事项数

这个功能显示了当前在我们的待办事项列表中可用的待办事项数量。随着添加更多的待办事项,待办事项的数量将增加,并且当从列表中删除待办事项时,数量将减少。在这里,我们将使用我们为此功能编写的 TDD 测试,并修改它以便我们的应用程序可以使用。在我们的测试中,我们将专注于添加和删除待办事项,并验证在添加和删除时,待办事项的数量会相应地改变。以下代码块显示了不同的断言,检查该功能是否按照我们的要求正常工作:

it('can view number of added todo items', () => {
      cy.visit('http://localhost:3000/')      
      cy.get('[data-testid="todo-input-element"]')
      .type('New todo, {enter}')
      cy.get('[data-testid="todo-input-element"]')
      .type('Another todo, {enter}')
      cy.get('[data-testid="todo-item-number"]')
      .should(($header) => {
        expect($header.get(0).innerText).to.contain('2')
      })
      cy.get('[data-testid="delete-todo-1-button"]')
      .click();
      cy.get('[data-testid="todo-item-number"]')
 	.should(($header) => {
        expect($header.get(0).innerText).to.contain('1')
      })
    });

上面的代码片段向我们展示了添加新的待办事项,验证列表中的项目是否被删除,以及计数在应用程序不同状态变化中保持一致。在这里,我们修改了我们最初的 TDD 测试,并且能够使用它们来测试我们是否实际上可以增加或减少可用的待办事项的数量。通过在 Cypress 上运行相同的测试,我们可以验证 Cypress 是正常的,并且我们有一个未被删除的待办事项,如下截图所示:

图 6.10 – 测试待办事项数

图 6.10 – 测试待办事项数

从前面的截图中,我们可以验证,随着应用程序状态的改变,比如添加和删除待办事项,数量会相应地增加或减少。

总结 – 修改 TDD 测试

在本节中,我们学会了如何修改 TDD 测试,一旦功能已经开发完成,使其符合我们应用程序的构建方式。我们还学会了 Cypress 在测试运行时如何独特地识别要与之交互的元素。最后,我们学会了如何将已经编写的 TDD 测试转换为已为我们的应用程序开发的测试功能。

总结

在本章中,我们了解了 TDD 工作的过程以及在任何团队中拥抱 TDD 的重要性,并探讨了 TDD 的优缺点。我们还探讨了如何将 TDD 应用于实际应用程序。通过这样做,我们为一个尚未构建的 Todo 应用程序创建了需求。在开发应用程序之前,我们为我们认为重要的功能编写了 TDD 测试,然后使用这些需求和 TDD 测试来开发我们的功能。在开发完功能后,我们修改了我们的第一个 TDD 版本的测试,使其适用于我们开发的功能,从而完成了展示如何在实际应用程序中利用 TDD 的过程。

现在,您应该了解什么是 TDD,如何编写 TDD 测试,以及如何修改和使用 TDD 测试在实际应用程序中,使其符合已开发的应用程序。

既然我们已经了解了 TDD 以及如何在项目中实施它,接下来我们将专注于如何与 Cypress DOM 的不同元素进行交互。

第七章:理解 Cypress 中的元素交互

在开始运行测试时,了解 Cypress 与元素交互的方式之前,最好先对构成 Cypress 的原则,它的工作原理,不同的 Cypress 命令,甚至 Cypress 的使用实际示例有一个坚实的理解。要完全理解本章,您需要已经学习了前几章,这将使您在学习过程中取得成功。

在本章中,我们将介绍 Cypress 如何与元素交互以及它如何响应交互过程中元素的不同状态。我们还将介绍 Cypress 如何通过 Cypress 命令中的内置机制确定元素是否准备好进行交互。

我们将在本章中涵盖以下关键主题:

  • 理解可操作性

  • 强制可操作性

一旦您完成了这些主题中的每一个,您将具有理解 Cypress 如何解释测试的知识,以及它在执行测试时如何解释发生的错误所需的知识。

技术要求

要开始,请克隆包含源代码和我们将在本章中编写的所有测试的存储库从 GitHub。

本章的 GitHub 存储库可以在以下网站找到:

github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress

本章的源代码可以在chapter-07目录中找到。

理解可操作性

现在我们知道了 Cypress 命令是什么,以及何时何地使用它们,我们需要了解 Cypress 在执行测试时的思考和操作过程。在本节中,我们将介绍 Cypress 如何与命令交互,如何确保元素可见和可操作,甚至如何处理元素中的动画。我们还将介绍 Cypress 在完成任何命令之前如何确定可操作性

可操作性是 Cypress 在文档对象模型DOM)中执行操作的能力。Cypress 具有命令,其唯一目的是与 DOM 元素交互。这些命令像“用户”一样行事,并模拟与应用程序用户界面的交互。Cypress 事件负责命令的行为,因为它将事件发送到浏览器,使其看起来像是在应用程序的用户界面上与用户进行交互。

以下是 Cypress 中直接与 DOM 交互的一些命令;要完成操作,DOM 元素必须是可操作的。这些命令带有内置的 Cypress 机制,用于检查它们交互的元素的可操作性。这些命令包括以下内容:

  • cy.type(): 在 DOM 元素中输入

  • cy.clear(): 清除文本区域或输入框的值

  • cy.click(): 在 DOM 元素上执行单击操作

  • cy.dbclick(): 在 DOM 元素上执行双击操作

  • cy.rightclick(): 在 DOM 元素上执行右键单击操作

  • cy.select(): 从<select>下拉菜单中选择一个<option>选项

  • cy.trigger(): 在 DOM 元素上执行触发事件

  • cy.check(): 检查 DOM 上的单选按钮和复选框

  • cy.uncheck(): 取消 DOM 上的单选按钮和复选框

重要说明

cy.rightclick()命令不会打开浏览器菜单,而是会检查您的元素与浏览器的上下文菜单的行为。

在运行任何上述命令之前,Cypress 会采取行动来确保 DOM 准备好接收操作。为了执行任何命令,Cypress 会执行自己的检查,以验证条件是否适合在 DOM 元素上执行命令。

所有这些检查都在指定的时间内进行,可以通过defaultCommandTimeout配置选项进行配置,该选项可以在cypress.json文件中进行修改。以下是 Cypress 执行的检查 DOM 元素准备就绪的操作:

  • 可见性:滚动元素以查看

  • 残疾:确保元素未隐藏

  • 分离:检查元素是否已从 DOM 中移除

  • 只读:检查元素是否处于只读状态

  • 动画:检查动画是否已完成

  • 覆盖:检查元素是否未被父元素覆盖

  • 滚动:检查被固定位置元素覆盖的元素的滚动

  • 坐标:检查事件是否在所需坐标处触发

为了更好地理解 Cypress 如何解释 DOM 的响应以及如何确定可操作性,我们将逐个讨论这些列出的操作,并描述 Cypress 在执行可操作命令时如何通过每个动作检查状态。

可见性

Cypress 使用不同的因素来确定元素是否可见。Cypress 确定元素的可见性的默认方式是通过检查该元素的层叠样式表CSS)属性。任何元素的 CSS 属性定义了元素的行为,如果默认情况下 CSS 属性以一种意味着元素被隐藏的方式定义,Cypress 将自动知道该元素由于其属性而不可见。

如果满足以下任一条件,Cypress 认为元素是隐藏的:

  • 元素的 CSSwidthheight0

  • 元素或其祖先具有visibility: hidden的 CSS 属性。

  • 元素或其祖先具有display: none的 CSS 属性。

  • 元素具有position: fixed的 CSS 属性,并且被遮盖或在屏幕上不存在。

此外,Cypress 使用hidden overflow CSS 属性来确定在测试执行期间元素是否隐藏。以下是 Cypress 用于确定元素是否隐藏的一些其他实例:

  • 祖先元素具有隐藏的溢出和widthheight值为0,并且在祖先元素和具有position: absolute的元素之间有一个元素。

  • 祖先元素具有隐藏的溢出,并且该元素具有position: relative的 CSS 属性,并且位于祖先元素的边界之外。

重要提示

隐藏的溢出意味着 CSS 属性可以是以下任何一种溢出:hiddenoverflow: autooverflow: scrolloverflow-x: hiddenoverflow-y: hidden

所有这些转换和平移的计算都由 Cypress 处理,如果 Cypress 偶然发现元素不可见,则测试将失败,并显示错误,指出 Cypress 试图与之交互的元素的可见性被隐藏。

残疾

在检查可操作性时,Cypress 还会检查元素是否已禁用。当元素具有disabled: true的 CSS 属性时,Cypress 无法与其交互,因为在 DOM 上禁用元素时无法对元素执行任何操作。当 Cypress 遇到禁用的元素并需要对其执行操作时,它将返回一个错误,描述禁用元素的状态以及为什么无法通过 Cypress 可操作命令与元素交互。

分离

分离的元素是已从 DOM 中移除但由于 JavaScript 的原因仍然存在于内存中的元素。大多数应用程序通过从 DOM 中移除元素并在 DOM 中插入其他元素来工作,因此不断地分离和附加元素在 DOM 中。在评估元素是否可操作时,Cypress 会在对元素运行任何可操作的命令之前检查元素是否未分离。如果 Cypress 遇到分离的元素,它会在测试中执行可操作的命令之前抛出错误。

重要的是要注意,Cypress 只会在 DOM 中搜索元素,不会检查分离的元素是否存在于内存中。

只读

只读元素是仅用于查看的,不能接受新内容或编辑的。Cypress 在.type()命令中检查readonly CSS 属性;如果遇到readonly CSS 属性,测试将以错误失败。

动画

Cypress 具有内置机制,用于确定元素中是否存在动画。在评估元素是否可操作时,Cypress 会等待动画完成,然后才开始与元素交互。

为了确定测试中的元素是否正在进行动画,Cypress 必须使用元素的最后坐标的样本,然后应用其算法来计算斜率。

重要提示

斜率是通过选择两个不同的点并记录它们的坐标来计算的。然后记录 y 坐标和 x 坐标之间的差异。然后进行 y 坐标和 x 坐标之间的差异的除法,以确定元素的斜率。

通过检查元素的当前和上一个位置来确定元素的动画和斜率。Cypress 带有内置的动画阈值,用于检查元素必须超过的像素距离以被认为是正在进行动画。您可以在cypress.json文件中配置此项,并按以下代码块中所示更改默认值:

{
"animationDistanceThreshold": 10
}

当这个值被改变时,无论是增加还是减少,Cypress 都会改变其灵敏度和确定元素是否正在进行动画的行为。较高的动画阈值意味着 Cypress 在检测像素变化的距离时会降低其灵敏度,而较低的动画阈值意味着 Cypress 在检测正在进行动画的元素时会更加敏感。

在运行测试时也可以关闭动画。为了做到这一点,我们需要配置cypress.json配置文件来忽略动画并继续执行我们的命令。以下配置可以通过以下代码块实现:

{
"waitForAnimations": false
}

当我们指定我们的测试不应等待动画时,如此处所示,我们的测试将忽略动画,并且将执行,就好像动画不存在一样。但是,可以将此配置更改回true值,以继续执行我们的测试,同时等待元素中的动画执行。

覆盖

Cypress 在发出命令之前,会检查元素是否被父元素覆盖,作为验证可操作性的一部分。有许多情况下,元素可能在 DOM 中可见,但只是被父元素覆盖,比如模态框、弹出窗口或对话框。如果有一个父元素覆盖了元素,Cypress 将不允许执行命令。

在父元素覆盖 Cypress 应该执行操作的元素的情况下,Cypress 会抛出错误,因为即使在现实生活中,用户也无法与被覆盖的元素进行交互。

重要提示

如果子元素覆盖了元素,Cypress 将继续向子元素发出事件,并且执行会在没有任何问题的情况下继续进行。

在下面的代码块中,我们有一个button元素,它部分或完全被span元素覆盖,而不是直接点击button元素本身:

<button>
  <span> Submit </span>
</button>

在这个代码块中,尽管span元素覆盖了button元素,Cypress 将向子span元素发出命令,这将触发对我们的button元素的点击事件,而不会遇到错误。

滚动

Cypress 在元素上执行滚动,并且在本节开头指定的可操作命令中默认启用了此行为。默认情况下,在与元素交互之前,Cypress 会滚动到该元素的位置,并确保它在视图中。

提示

诸如cy.get()cy.find()之类的命令在其中没有内置 Cypress 滚动到视图的机制,就像 Cypress 中的可操作命令一样。

Cypress 中的滚动是通过算法启用的,该算法首先尝试确定元素是否在 DOM 上可见。然后,它使用坐标从当前元素到 Cypress 操作的元素的期望位置计算坐标,以导航到实际元素。

Cypress 滚动算法会不断滚动,直到元素变得可见,或者直到元素不再被其他元素遮挡。该算法非常好地确保了 DOM 上的大多数元素在视图中可以滚动并进行交互。

坐标

在 Cypress 完成了检查元素是否可操作的验证过程之后,默认情况下,它会向元素的中心触发事件。Cypress 提供了一种机制来覆盖触发事件的默认位置,并且大多数命令的行为都可以自定义。

以下代码块显示了更改按钮上点击事件的触发行为:

it('can mark a todo as completed - with changed hitbox position', () => {
cy.visit('http://todomvc.com/examples/react/#/')
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".new-todo").type("Another New Todo {Enter}");
      cy.get('.todo-list>li:nth-child(1)').find(
      '.toggle').click({ position: 'topLeft' });
    });

在这个代码块中,我们导航到我们的 Todo 应用程序并添加了两个待办事项,然后标记其中一个待办事项为已完成。当标记我们的第一个待办事项为完成时,我们改变了点击的位置,并指示 Cypress 点击topLeft位置,而不是默认的center位置。以下截图显示了click命令在被点击的待办事项动作的事件点击框的顶部左侧部分:

图 7.1 – 更改 Cypress 点击位置的坐标

图 7.1 – 更改 Cypress 点击位置的坐标

重要提示

事件点击框是在固定的 Cypress 快照上弹出的高亮显示,以显示测试与元素的交互。事件点击框可以由 Cypress 事件触发,例如.click()方法。

正如图 7.1所示,Cypress 有能力计算元素的坐标,并确定在哪里点击元素。此外,当触发行为的坐标发生变化时,Cypress 会将它们记录在 Cypress 测试运行器的命令日志中。我们可以进一步检查控制台,查看 Cypress 在执行元素的顶部左侧点击后打印的坐标。以下图显示了第一个已完成待办事项的click事件的打印坐标:

图 7.2 – 新的点击位置坐标

图 7.2 – 新的点击位置坐标

截图中显示的坐标是我们指示 Cypress 使用的新的.click()命令坐标,而不是带有可操作命令的默认命令。

总结-理解可操作性

在本节中,我们了解了 Cypress 如何确定元素的可操作性以及如何评估不同元素的条件,例如可见性、禁用、分离模式、动画、滚动属性、坐标,甚至readonly属性。我们还学习了 Cypress 如何计算元素中的动画以及如何增加动画阈值以减少 Cypress 检测动画的敏感度。

在下一节中,我们将学习如何强制 Cypress 在元素的可操作性检查失败时继续执行操作,并在可以安全执行强制操作的元素上执行强制操作。

强制可操作性

理解了可操作性是什么,以及 Cypress 需要进行的检查来确定元素是否可操作,也很重要了解我们如何覆盖 Cypress 设置的机制来检查可操作性。在本节中,我们将专注于执行操作和命令,即使元素未通过 Cypress 为可操作命令执行的可操作性检查。我们还将学习如何安全地实现一些元素和测试的覆盖机制。

覆盖 Cypress 可操作性检查

在 Cypress 测试中,可操作性非常有用,因为它帮助我们找到用户可能无法与应用程序元素交互的情况。但有时,可操作性检查可能会妨碍正确的测试,这就引出了我们的下一个任务:覆盖安全检查。

在某些测试中,“像用户一样操作”可能并不值得,因为归根结底,目标是编写可以以自动化方式防止错误和缺陷的有意义的测试。诸如嵌套导航结构和界面之类的情况可能导致复杂的测试,可以通过消除嵌套导航结构,而是直接与我们想要的元素进行交互来实现。

为了覆盖 Cypress 的可操作性检查,我们可以向 Cypress 可操作命令传递{force: true}参数选项。该选项将指示 Cypress 覆盖所有检查,以检查可操作性,并继续执行默认操作。以下代码块是一个测试,使用我们的 Todo 应用程序中的toggle-all按钮将所有待办事项标记为已完成:

it('can mark all todo as completed - with no forced toggle option (Failure)', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".new-todo").type("Another New Todo {Enter}");
      cy.get('.todo-list>li:nth-child(1)').find(
      '.toggle').click();
      cy.get('#toggle-all').click();
    });

当此测试运行时,它将失败,因为尝试切换第一个元素并标记为完成将导致测试失败和错误,因为它已经标记为完成。以下截图显示了 Cypress 可操作性的运行情况,测试失败,因为待办事项由于被另一个元素覆盖而无法标记为完成:

图 7.3 - 未通过 Cypress 可操作性检查的测试

图 7.3 - 未通过 Cypress 可操作性检查的测试

进一步调查图 7.3,我们可以验证第一个项目无法标记为已完成,因为它已经完成,这导致了失败。我们可以通过告诉 Cypress 在切换所有待办事项完成之前忽略可操作性检查来覆盖此测试行为,如下面的代码块所示:

it('can mark all todo as completed - with forced toggle option (Success)', () => {
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get(".new-todo").type("Another New Todo {Enter}");
      cy.get('.todo-list>li:nth-child(1)').find(
      '.toggle').click();
      cy.get('#toggle-all').click({force: true});
    });

在运行代码块中显示的测试时,它会通过,因为我们已经阻止了 Cypress 检查我们需要点击的元素是否被另一个元素覆盖。以下截图显示了代码运行情况,并成功测试了通过点击 toggle-all 下拉按钮标记两个待办事项为已完成:

图 7.4 - 覆盖 Cypress 可操作性检查的通过测试

图 7.4 - 覆盖 Cypress 可操作性检查的通过测试

图 7.4中,Cypress 忽略了与项目可操作性相关的检查,而是继续执行默认操作,我们的情况下是切换两个待办事项并标记为已完成。我们通过向 toggle 按钮的click命令传递{force: true}选项来实现覆盖。

当使用强制选项强制发生 Cypress 事件时,Cypress 会执行以下操作:

  • 继续执行所有默认操作

  • 强制在元素上触发事件

然而,Cypress 不会执行以下操作:

  • 确保元素可见

  • 滚动元素以查看

  • 确保元素未被禁用

  • 确保元素未被分离

  • 确保元素未处于动画状态

  • 确保元素没有被覆盖

  • 确保元素不是只读的

  • 在后代元素上触发事件

重要提示

强制可操作性在某些情况下非常有用,特别是当你不需要花费时间自动化不值得自动化的步骤时;然而,有时强制可操作性并不是解决问题的最佳方案。当我们强制可操作性时,大多数问题都可以通过编写更好的应用程序代码和确保项目的正确对齐来解决,以确保没有元素阻挡其他元素。我们还可以利用 Cypress 来克服诸如动画之类的情况,等待动画停止运行,然后在确保页面动画已完成后执行我们的测试。

当在命令上强制可操作性时,Cypress 放弃了确保在对元素执行任何操作之前满足正确条件的角色,而是直接在测试中执行发出的条件。

总结 - 强制可操作性

在本节中,我们学习了可以在元素上强制可操作性,并且可以通过在发出的可操作命令上传递{force: true}参数来实现。我们还看到了当我们强制执行 Cypress 命令时的显著差异,例如在测试中切换我们的待办事项为完成状态。在本节中,我们还了解了何时重写 Cypress 的可操作性是重要的,以及它如何潜在地减少测试的复杂性。

摘要

在本章中,我们学习了 Cypress 如何通过确保元素处于正确状态来强制元素的可操作性,然后才对元素执行命令。我们了解到 Cypress 在执行任何元素操作之前会检查可见性、禁用状态、DOM 分离、readonly模式、动画、覆盖、滚动和元素坐标。我们还了解了 Cypress 如何计算元素的动画,甚至在对元素执行操作时如何改变坐标。我们还学习到可以通过在测试中强制可操作性来覆盖 Cypress 设置的默认检查。

完成了本章后,我相信你已经掌握了理解 Cypress 如何确定元素的可操作性以及如何在测试中覆盖可操作性的技能,以减少复杂性。在下一章中,我们将学习使用变量和别名,并深入研究如何多次重用我们在测试中定义的变量和别名。

第八章:理解 Cypress 中的变量和别名

在我们开始讨论 Cypress 中变量和别名的工作原理之前,重要的是要了解我们在前几章中涵盖了什么,如何在 Cypress 中编写测试,如何配置测试,甚至如何使用 Cypress 按照测试驱动开发的方式编写应用程序。本书前几章提供的背景信息将为我们提供一个良好的基础,让我们深入了解变量和别名的工作原理。通过探索变量和别名,我们将了解如何在 Cypress 中创建引用,这将简化我们的测试编写过程和测试的复杂性。了解如何使用变量和别名不仅可以让您编写更好的测试,还可以编写易于阅读和维护的测试。

在本章中,我们将专注于编写异步命令,以利用 Cypress 捆绑的变量和别名。我们还将了解如何通过使用别名简化我们的测试,以及如何在测试的不同区域利用我们创建的别名和变量,例如元素的引用、路由和请求。

本章将涵盖以下关键主题:

  • 理解 Cypress 变量

  • 理解 Cypress 别名

一旦您了解了这些主题,您将完全了解如何在 Cypress 测试中使用别名和变量。

技术要求

要开始,请克隆包含本章中将编写的所有源代码和测试的存储库从 GitHub 中获取。

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。

本章的源代码可以在chapter-08目录中找到。

理解 Cypress 变量

本节将重点介绍 Cypress 中的变量是什么,它们在测试中如何使用以及它们在测试中的作用,特别是在减少测试复杂性方面。我们还将探讨可以在哪些不同区域使用 Cypress 变量来增加测试的可读性。通过本节的学习,您将能够使用变量编写测试,并了解在编写测试时应该在哪里使用变量。

为了更好地理解 Cypress 中变量的工作原理,重要的是要了解 Cypress 如何执行其命令。以下代码块是一个测试,首先选择一个按钮,然后选择一个输入元素,然后点击按钮:

it('carries out asynchronous events', () => {
   const button = cy.get('#submit-button');
   const username = cy.get('#username-input');
   button.click()
});

上述代码块说明了一个测试,应该首先识别一个按钮,然后识别一个用户名输入,最后点击按钮。然而,测试和执行不会按照我们通常的假设方式进行。在我们的假设中,我们可能会认为第一个命令将在第二个命令运行之前执行并返回结果,然后第三个命令将是最后执行的。Cypress 利用 JavaScript 的异步 API来控制 Cypress 测试中命令的执行方式。

重要提示

异步 API 被实现为它们在收到命令或请求时提供响应,并不一定等待某个特定请求获得响应后再处理其他请求。相反,API 会返回收到的第一个响应,并继续执行尚未收到响应的响应。请求和接收响应的非阻塞机制确保可以同时进行不同的请求,因此使我们的应用程序看起来是多线程的,而实际上,它的本质是单线程的。

在前面的代码块中,Cypress 以异步顺序执行命令,响应不一定按照测试中发出请求的顺序返回。然而,我们可以强制 Cypress 按照我们的期望执行测试,我们将在下一节中介绍闭包时进行讨论。

闭包

当 Cypress 捆绑测试函数和对函数周围状态的引用时,就会创建闭包。闭包是 Cypress 大量借鉴的 JavaScript 概念。因此,Cypress 中的测试闭包将能够访问测试的外部范围,并且还将能够访问由测试函数创建的内部范围。我们将测试的局部功能范围称为词法环境,就像 JavaScript 函数中的情况一样。在下面的代码块中,我们可以看到 Cypress 中的闭包是什么,以及变量在闭包中如何被利用:

describe('Closures', () => {
    it('creates a closure', () => {
       // { This is the external environment for the test }
      cy.get('#submit-button').then(($submitBtn) => {
       // $submitBtn is the Object of the yielded cy.get()
       // response
       // { This is the lexical environment for the test }
      })
	 // Code written here will not execute until .then()  
      //finishes execution
    })
  });

$submitBtn变量用于访问从cy.get('#submit-button')命令获取的响应。使用我们在测试中刚刚创建的变量,我们可以访问返回的值并与之交互,就像在普通函数中一样。在这个测试中,我们使用了$submitBtn变量创建了一个测试闭包函数。.then()函数创建了一个回调函数,使我们能够在代码块中嵌套其他命令。闭包的优势在于我们可以控制测试执行命令的方式。在我们的测试中,我们可以等待.then()方法内部的所有嵌套命令执行完毕,然后再运行测试中的其他命令。测试代码中的注释进一步描述了执行行为。

重要提示

回调函数是作为参数传递到其他函数中的函数,并在外部函数中被调用以完成一个动作。当我们的.then()函数内部的命令完成运行时,函数外部的其他命令将继续执行它们的执行例程。

在下面的代码块中,我们将探讨如何使用变量编写测试,并确保在闭包内部的代码执行之前,任何其他代码在闭包之外和闭包开始执行之后都不会执行。该测试将添加两个待办事项,但在添加第二个待办事项之前,我们将使用闭包来验证闭包内部的代码是否首先执行:

it('can Add todo item - (Closures)', () => {
      cy.visit('http://todomvc.com/examples/react/#/')
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get('.todo-list>li:nth-child(1)').then(($todoItem) => {
        // Storing our todo item Name 
        const txt = $todoItem.text()
        expect(txt).to.eq('New Todo')
      });
      // This command will run after all the above commands  
      // have finished their execution.  
      cy.get(".new-todo").type("Another New Todo {Enter}");
    });

在前面的代码块中,我们已经向待办事项列表中添加了一个待办事项,但在添加第二个项目之前,我们验证添加的待办事项确实是我们创建的。为了实现这一点,我们使用了闭包和一个需要在执行下一个命令之前返回true的回调函数。以下截图显示了我们运行测试的执行步骤:

图 8.1 - Cypress 中的闭包

图 8.1 - Cypress 中的闭包

图 8.1中,我们可以看到 Cypress 执行了获取添加的待办事项的命令,并断言添加的待办事项是我们在列表中拥有的,然后执行最后一个命令将新的待办事项添加到我们的待办事项列表中。

Cypress 中的闭包不能存在于变量之外。要使用闭包,我们需要利用变量将从我们的命令中接收的值传递给闭包函数,而利用变量是唯一的方法。在这个代码块中,我们使用了$todoItem变量将cy.get()命令的值传递给了断言找到的待办事项是我们创建的确切项目的闭包。

Cypress 像 JavaScript 一样利用变量作用域。在 Cypress 中,用户可以使用constvarlet标识符来指定变量声明的范围。在接下来的部分中,我们将看到可以在测试中使用的不同范围。

Var

var关键字用于声明函数或全局作用域变量。为了初始化目的,为变量提供值是可选的。使用var关键字声明的变量在遇到测试函数时会在任何其他代码执行之前执行。可以使用var关键字在全局范围内声明变量,并在测试函数内的功能范围内覆盖它。以下代码块显示了使用var关键字声明的全局作用域变量的简单覆盖:

describe('Cypress Variables', () => {
  var a = 20;
  it('var scope context', () => {
    a = 30; // overriding global scope
    expect(a).to.eq(30) // a = 30
  });
 it('var scope context - changed context', () => {
    // Variable scope remains the same as the change affects 
    // the global scope     expect(a).to.eq(30) //a = 30
  });
});

在这段代码中,我们在测试的全局上下文中声明了一个a变量,然后在测试中改变了全局变量。新更改的变量将成为我们的全局a变量的新值,除非它被明确更改,就像我们在测试中所做的那样。因此,var关键字改变了变量的全局上下文,因为它在全局重新分配了全局变量的值。

Let

let变量声明的工作方式与使用var声明的变量相同,唯一的区别是定义的变量只能在声明它们的范围内使用。是的,我知道这听起来很混乱!在下面的代码块中,两个测试展示了在使用let关键字时的范围声明的差异:

describe('Cypress Variables', () => {
  // Variable declaration
  let a = 20;
  it('let scope context', () => {
    let a = 30;
    // Local scoped variable
    expect(a).to.eq(30) // a = 30
  });
  it('let scope context - global', () => {
    // Global scoped variable
    expect(a).to.eq(30) // a = 20
  });

在这第二个测试中,由于let关键字只会使更改后的a变量对更改它的特定测试可用,而不会对整个测试套件的全局范围可用,因此我们有一个测试失败,就像使用var变量声明一样。在下面的截图中,我们可以看到测试失败,因为它只选择了在describe块中声明的变量,而没有选择前面测试中的变量:

图 8.2 – let 关键字

图 8.2 – let 关键字

图 8.2所示,在编写测试时,可以在不影响声明变量的范围的情况下在不同的测试中对同一变量进行声明,因为每个变量都将属于并拥有自己的上下文,不会影响全局上下文。

Const

const关键字用于声明只读的对象和变量,一旦声明后就不能被改变或重新赋值。使用const关键字分配的变量是“最终的”,只能在其当前状态下使用,其值不能被改变或改变。在下面的代码块中,我们试图重新分配使用const关键字声明的变量,这将导致失败:

describe('const Keyword', () => {
    const a = 20;
    it('let scope context', () => {
      a = 30;
      // Fails as We cannot reassign
      // a variable declared with a const keyword
      expect(a).to.eq(30) // a = 20
    });
});

从这段代码中,考虑到a变量是用const声明的,它是不可变的,因此 Cypress 会因为错误而失败,如下面的截图所示:

图 8.3 – const 关键字

图 8.3 – const 关键字

就像在 JavaScript 中一样,Cypress 不能重新分配使用const关键字声明的变量。使用const声明的变量是在程序执行期间不需要在全局或局部范围内改变的变量。

总结 – 理解 Cypress 变量

在本节中,我们了解了 Cypress 中变量的利用。我们看了一下变量在闭包中的使用方式,以及它们如何在不同的范围和上下文中声明。在这里,我们还了解了变量范围的含义以及它们在测试中的使用方式。现在我们知道了变量是什么以及它们代表什么,我们将在下一节中深入了解 Cypress 测试中别名的使用。

理解 Cypress 别名

别名是一种避免在我们的测试中使用.then()回调函数的方法。我们使用别名来创建引用或某种“内存”,Cypress 可以引用,从而减少我们需要重新声明项目的需求。别名的常见用途是避免在我们的beforebeforeEach测试钩子中使用回调函数。别名提供了一种“清晰”的方式来访问变量的全局状态,而无需在每个单独的测试中调用或初始化变量。在本节中,我们将学习如何正确地在我们的测试执行中利用别名,并介绍使用别名的不同场景。

在某些情况下,别名非常方便,其中一个变量被测试套件中的多个测试所使用。以下代码块显示了一个测试,我们希望验证在将待办事项添加到待办事项列表后,我们的待办事项确实存在:

context('TODO MVC - Aliases Tests', () => {
  let text;
  beforeEach(() => {
    cy.visit('http://todomvc.com/examples/react/#/')
    cy.get(".new-todo").type("New Todo {Enter}");
    cy.get('.todo-list>li:nth-child(1)').then(($todoItem) => {
      text = $todoItem.text()
    });
  });
  it('gets added todo item', () => {
    // todo item text is available for use
    expect(text).to.eq('New Todo')
  });
});

要在beforeEachbefore钩子中外部使用声明的变量,我们在代码块中使用回调函数来访问变量,然后断言由我们的beforeEach方法创建的变量的文本与我们期望的待办事项相同。

重要提示

代码结构仅用于演示目的,不建议在编写测试时使用。

虽然前面的测试肯定会通过,但这是 Cypress 别名存在的反模式。Cypress 别名存在的目的是为了在 Cypress 测试中提供以下目的:

  • 在钩子和测试之间共享对象上下文

  • 访问 DOM 中的元素引用

  • 访问路由引用

  • 访问请求引用

我们将研究别名的每个用途,并看看它们在覆盖的用途中如何使用的示例。

在测试钩子和测试之间共享上下文

别名可以提供一种“清晰”的方式来定义变量,并使它们在测试中可访问,而无需在测试钩子中使用回调函数,就像在前一个代码块中所示的那样。要创建别名,我们只需将.as()命令添加到我们要共享的内容,然后可以使用this.*命令从 Mocha 的上下文对象中访问共享的元素。每个测试的上下文在测试运行后都会被清除,因此我们的测试在不同的测试钩子中创建的属性也会被清除。以下代码块显示了与前一个相同的测试,以检查待办事项是否存在,但这次利用了别名:

describe('Sharing Context between hooks and tests', () => {
    beforeEach(() => {
      cy.visit('http://todomvc.com/examples/react/#/');
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get('.todo-list>li:nth-
        child(1)').invoke('text').as('todoItem');
    });
    it('gets added todo item', function () {
      // todo item text is available for use
      expect(this.todoItem).to.eq('New Todo');
    });
  });

在上述代码块中,我们可以验证 Mocha 在其上下文中具有this.todoItem并成功运行,从而验证确实创建了待办事项。测试的进一步验证可以如下截图所示,突出显示了在使用别名引用我们待办事项列表中创建的待办事项后,Cypress 测试的通过状态:

图 8.4 – 上下文共享

图 8.4 – 上下文共享

图 8.4中,我们看到 Cypress 突出显示了别名文本,并显示了它在我们的测试中是如何被调用的。Cypress 打印出了已使用的别名元素和命令,这样在失败时很容易识别和调试,并跟踪导致别名元素失败的原因。

重要提示

在您的 Cypress 测试中,无法使用箭头函数与this.*,因为this.*将指向箭头函数的词法上下文,而不是 Mocha 的上下文。对于任何使用this关键字的地方,您需要将 Cypress 测试切换为使用常规的function () {}语法,而不是() => {}

别名在共享上下文方面的另一个很好的用途是与 Cypress fixtures 一起使用。Fixtures 是 Cypress 用于提供用于测试的模拟数据的功能。Fixtures 在文件中创建,并可以在测试中访问。

重要提示

固定提供测试数据,我们利用固定提供与应用程序期望的输入或在执行操作时生成的输出一致的数据。固定是我们为测试提供数据输入的简单方法,而无需在测试中硬编码数据或在测试运行时自动生成数据。使用固定,我们还可以为不同的测试利用相同的测试数据集。

假设我们有一个包含所有已创建待办事项列表的 todos fixture,我们可以编写类似以下代码块的测试:

describe('Todo fixtures', () => {
    beforeEach(() => {
      // alias the todos fixtures
      cy.get(".new-todo").type("New Todo {Enter}");
      cy.get('.todo-list>li:nth-
        child(1)').invoke('text').as('todoItem')
      cy.fixture('todos.json').as('todos')
    })

    it('todo fixtures have name', function () {
      // access the todos property
      const todos = this.todos[0]

      // make sure the first todo item contains the first
      // todo item name
      expect(this.todoItem).to.contain(todos.name)
    })
  })

在前面的代码块中,我们为创建的待办事项和包含已创建待办事项的 todos.json 固定文件都创建了别名。我们可以在所有测试中利用待办事项的固定,因为我们在测试的 beforeEach 钩子中加载了固定。在这个测试中,我们使用 this.todo[0] 来访问我们的第一个固定值,它是我们待办事项数组中的第一个对象。要进一步了解如何使用固定和我们正在使用的确切文件,请查看我们在本章开头克隆的 GitHub 存储库,位于 cypress/fixtures 目录下。

重要提示

Cypress 仍然使用异步命令工作,尝试在 beforeEach 钩子之外访问 this.todos 将导致测试失败,因为测试首先需要加载固定才能使用它们。

在共享上下文时,Cypress 命令还可以使用特殊的 '@' 命令,这消除了在引用已声明别名的上下文时使用 this.* 的需要。以下代码块显示了在引用 Cypress 别名时使用 '@' 语法的用法:

it('todo fixtures have name', () => {
      // access the todos property
      cy.get('@todos').then((todos) => {
        const todo = todos[0]
      // make sure the first todo item contains the first
      // todo item name
      expect(this.todoItem).to.contain(todo.name)
      });
    });

在前面的代码块中,我们使用了 cy.get() 命令来消除在访问我们的固定文件时使用 this.* 语法,以及使用旧式函数声明方法的需要。当我们使用 this.todos 时,我们是同步访问 todos 对象,而当我们引入 cy.get('@todos') 时,我们是异步访问 todos 对象。

如前所述,当 Cypress 以同步方式运行代码时,命令按照调用顺序执行。另一方面,当我们以异步方式运行 Cypress 测试时,由于命令的执行不是按照调用顺序进行的,所以执行的命令的响应也不会按照调用顺序返回。在我们的情况下,this.todo将作为同步命令执行,它将按照执行顺序返回 todo 对象结果,而 cy.get('@todos') 将像异步命令一样行为,并在可用时返回 todo 对象响应。

访问元素引用

别名还可以用于访问 DOM 元素以便重用。引用元素可以确保我们在引用别名后不需要重新声明 DOM 元素。在下面的代码块中,我们将为添加新待办事项的输入元素创建一个别名,并在创建待办事项时稍后引用它:

it('can add a todo - DOM element access reference', () => {
      cy.get(".new-todo").as('todoInput');
      // Aliased todo input element
      cy.get('@todoInput').type("New Todo {Enter}");
      cy.get('@todoInput').type("Another New Todo {Enter}");
      cy.get(".todo-list").find('li').should('have.length', 2)
  });

这个测试展示了使用别名来访问已存储为引用的 DOM 元素。在测试中,Cypress 查找我们保存的 'todoInput' 引用并使用它,而不是运行另一个查询来查找我们的输入项。

访问路由引用

我们可以使用别名来引用测试应用程序的路由。路由管理网络请求的行为,通过使用别名,我们可以确保在进行请求时进行正确的请求,发送服务器请求,并在进行请求时创建正确的 XHR 对象断言。以下代码块显示了在处理路由时使用别名的用法:

it('can wait for a todo response', () => {
      cy.server()
      cy.intercept('POST', '/todos', { id: 123 }).as('todoItem')
      cy.get('form').submit()
      cy.wait('@todoItem').its('requestBody')
        .should('have.property', 'name', 'New Todo')
      cy.contains('Successfully created item: New Todo')
    });

在这个代码块中,我们将我们的todoItem请求引用为别名。路由请求将检查我们提交的表单是否已成功提交并返回响应。在路由中使用别名时,我们不必保持引用或调用路由,因为 Cypress 已经从我们之前创建的别名中存储了路由的响应。

访问请求引用

就像访问路由引用时使用别名一样,我们可以使用 Cypress 访问 Cypress 请求并在以后使用请求的属性。在下面的代码块中,我们标识了一个特定评论的请求,并使用别名检查评论的属性:

it('can wait for a comment response', () => {
      cy.request('https://jsonplaceholder.cypress.io/comments/6')
    .as('sixthComment');
      cy.get('@sixthComment').should((response) => {
        expect(response.body.id).to.eq(6)
    });
 });

测试对特定评论进行断言,并检查断言是否与评论的 ID 匹配。我们使用别名引用请求 URL,这样当运行我们的测试时,我们只需要引用我们已经别名的 URL,而不必完整输入它。运行测试的下面的屏幕截图显示了 Cypress 如何创建别名,并在运行测试时引用它:

图 8.5 - 请求引用

图 8.5 - 请求引用

在上面的屏幕截图中,第一个sixthComment命令是 Cypress 创建别名的命令,第二个是运行测试识别别名并对从别名 URL 获取的响应进行断言的命令。

总结 - 了解 Cypress 别名

在本节中,我们学习了别名及其如何用于编写测试的“干净”代码,通过提供一种方式让我们可以访问和引用我们在测试中可能需要的请求、元素、路由和命令。我们还学习了 Cypress 别名的访问方式:通过异步方法,该方法在别名之前使用@符号,或者直接使用this关键字访问别名对象的同步方法。最后,我们学习了如何在测试中利用别名引用元素,使我们能够在测试中使用别名路由和请求。

总结

在本章中,我们学习了别名和变量以及如何在 Cypress 中利用它们。我们介绍了 Cypress 测试中的变量是什么,不同类型的变量及其作用域,以及如何利用它们。我们还介绍了 Cypress 中变量如何帮助创建闭包,以及如何创建只能被变量访问的环境,除了测试可访问的全局上下文。最后,我们看了如何使用别名以及别名的不同上下文。我们学习了如何在测试中引用别名,如何与元素、路由和请求一起使用它们,甚至用于测试钩子和测试本身之间的上下文共享。

通过本章,您已经掌握了了解别名和变量如何工作,别名如何在异步和同步场景中使用,以及何时以及如何创建和实现测试中变量的作用域的技能。

现在您完全了解了别名和变量的工作原理,我们已经准备好进行下一章,我们将了解测试运行器的工作原理。我们将深入研究测试运行器的不同方面以及如何解释测试运行器上发生的事件。

第九章:Cypress 测试运行器的高级用法

在我们开始讨论测试运行器的高级用法之前,您必须了解 Cypress 的工作原理、测试运行器的作用以及测试在测试运行器中的执行方式是至关重要的。本章是在您在前八章中所学到的 Cypress 知识的基础上构建的,并将重点放在帮助您理解测试运行器的高级功能上,这些功能在本书中尚未探讨过。

在本章中,我们将利用测试运行器,并通过利用测试运行器的内置功能来学习如何编写更好的测试。通过学习如何使用测试运行器,我们将更深入地了解测试的运行方式,当测试失败时会发生什么,以及如何改进它们。本章将涵盖以下关键主题:

  • 理解仪表板

  • 理解选择器游乐场

  • 测试运行器键盘快捷键

一旦您学习了这些主题,您将完全了解测试运行器,以及如何充分利用它来编写您的测试。

技术要求

要开始,我们建议您从 GitHub 克隆包含本章中将编写的所有源代码和测试的存储库。

重要说明

我们已经在第五章 调试 Cypress 测试中介绍了如何阅读和解释测试运行器中的 Cypress 错误。在该章节中,我们还介绍了如何与测试运行器中的 DOM 快照进行交互,其中我们涵盖了元素和命令日志之间的交互。在本章中,我们可能会参考第五章 调试 Cypress 测试,或者进一步阐述该章节提供的信息。

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。

本章的源代码可以在chapter-09目录中找到。

理解仪表板

仪表板是 Cypress 测试运行器中的一个特殊面板,只有当 Cypress 为您提供有关测试的其他信息时才可见。仪表板的出现是由特定命令触发的,这些命令提供了有关测试的更多信息。触发仪表板的命令包括cy.stub()cy.intercept()cy.spy()。在本节中,我们将探讨如何使用仪表板来显示有关测试的其他信息。

为了实现我们理解仪表板工作原理的目标,我们将不得不了解拦截存根间谍的工作原理,以及在 Cypress 测试中调用存根、路由和间谍时仪表板显示的具体信息。

拦截

Cypress 使用cy.intercept()命令来管理测试的网络层中的 HTTP 请求的行为。要理解拦截,我们首先需要了解 Cypress 中网络请求是如何进行的。Cypress 会在测试运行器上自动指示当运行测试时发出XHRXMLHttpRequest)请求。Cypress 还会在请求被调用和响应被接收时创建 DOM 快照,这让我们了解了请求之前和之后 DOM 的情况。以下代码块是一个示例,用于从我们的 Todo 应用程序获取对 XHR 请求的响应:

describe(Routing a request', () => {
    it('can wait for a app initialization, () => {
 	cy.intercept('POST','**/j/** 
     ').as('initializeTodoApp');
      cy.visit('http://todomvc.com/examples/react/#/');
      cy.wait('@initializeTodoApp'); // wait for intercept
      response
    })
  });

上述代码块显示了 Cypress 的cy.intercept()命令监听对初始化应用程序时 Cypress 预期进行的 XHR 响应。在测试中,我们正在验证确实已经向应用程序发出了请求,因为我们正在等待路由响应在我们的测试执行完成之前被调用。

Cypress 有能力解释请求,这使得框架可以轻松地通过监听测试发出的 HTTP 请求并知道请求调用返回的响应来管理 HTTP 请求。

在 Cypress 中,使用cy.intercept()命令进行拦截提供了覆盖 Cypress 测试执行期间由请求发出的 XHR 响应的能力。覆盖我们应用程序发出的 XHR 响应就是我们所谓的存根,我们将在本章后面讨论这个概念。

Cypress 在仪表板上记录了所有拦截的信息,通过查看面板,我们可以知道在我们的测试中匹配的路由数量,是否有任何与我们的路由匹配的响应,以及它们是否被存根。以下截图说明了使用仪表板来详细说明 Cypress 记录的有关路由的信息:

图 9.1 – Cypress 仪表板

图 9.1 – Cypress 仪表板

图 9.1显示了仪表板上标有路由的区域,其中包含了不同类型的信息列,当测试完成运行时,路由响应的信息就会显示在这里。路由仪表板中的不同列具有不同的目的,并且对于运行测试和仪表板都是重要的。以下是不同的列,每个列都有其在 Cypress 路由中的用途和重要性的描述:

  • 方法1):方法列代表cy.intercept()命令期望的请求,根据预期的请求,它可以是GETPOSTPUTPATCH甚至DELETE

  • URL2):URL列将显示运行 Cypress 测试时cy.intercept()命令期望的 URL。在这种情况下,我们告诉 Cypress 查找任何以learn.json结尾的路由,如果遇到它,那么我们的测试应该通过。

  • 存根3):存根列将显示我们的路由是否已被存根。当路由被存根时,Cypress 不会返回接收到的响应,而是返回我们传递给路由的响应。

  • 别名4):别名列显示了我们在 Cypress 中给予路由的别名。在第八章中,了解 Cypress 中的变量和别名,我们学习了别名以及当我们需要访问元素、路由或请求的信息时它们可以是有用的。别名列中提供的别名是我们用来调用我们的路由的,我们将在别名前加上@前缀来做到这一点。

  • #5):这个匹配列将显示与我们的路由匹配的响应的计数。在我们的情况下,对我们的 URL 的请求只被发出了一次,因此我们的路由在我们的测试中只被调用了一次。

路由的仪表板信息足以让您了解在我们的测试中是否有任何 XHR 请求发送到已在我们的测试中声明的路由,并且方法和请求次数是否与应用程序中预期的一致。

存根

Cypress 中的存根用于替换函数,控制其行为或记录其使用情况。存根可用于用我们自己编写的合成响应替换实际方法。在下面的代码块中,我们将验证我们在测试运行时可以存根名为foo的方法:

it('can stub a method', () => {
      let obj = {
        foo () {},
      }
      const stub = cy.stub(obj, 'foo').as('foo')
      obj.foo('foo', 'bar')
      expect(stub).to.be.called
    })

在前面的代码块中显示的foo()方法说明了存根的实际操作,从代码中我们可以看到我们期望 Cypress 知道我们的存根在测试中被调用了。以下截图显示了测试执行和通过测试的详细信息,包括存根类型、存根名称以及存根被调用的次数:

图 9.2 – Cypress 存根

图 9.2 – Cypress 存根

图 9.2中,Cypress 显示了我们在仪表板中创建的存根,并显示了在执行测试过程中调用存根的次数。存根非常方便,因为我们可以存根化我们不想在范围内进行测试的依赖项或函数。

间谍

间谍的行为与存根完全相同,不同之处在于它们包装了间谍方法中的方法,以记录对函数的调用和参数。间谍仅用于验证 Cypress 中工作元素或方法。在测试中最常见的用法是验证测试中是否进行了某些调用,而不一定是为了改变对调用的期望,就像存根的情况一样。以下屏幕截图显示了我们验证foo方法是否在我们的cy.spy()方法中被调用的间谍:

图 9.3-Cypress 间谍

图 9.3-Cypress 间谍

图 9.3中,仪表板在显示调用我们的spy函数时发挥了关键作用,函数的名称,分配给我们的间谍方法的别名以及我们的测试方法的类型。

总结-理解仪表板

在本节中,我们学习了如何利用仪表板来理解 Cypress 中的拦截、间谍和存根。我们还学习了拦截、间谍和存根实际上是如何工作的,以及仪表板上的信息对于理解我们的实现是否正确是有用的。在下一节中,我们将深入了解 Cypress 中的选择器游乐场以及它的工作原理。

理解选择器游乐场

选择器游乐场是 Cypress 测试运行器的一个交互式功能。选择器游乐场使您能够确定唯一选择器,检查与特定选择器匹配的元素,并检查与 Cypress 应用程序中特定文本匹配的元素。在本节中,我们将看看 Cypress 用于选择元素的不同策略,以及从测试运行器中如何识别我们可以在测试中使用的选择器。在本节结束时,您将学会如何使用 Cypress 使用选择器游乐场来唯一选择元素,以及如何使用 Cypress 使用的选择器策略来运行测试。

选择唯一元素

选择器游乐场可能是 Cypress 测试运行器中最未充分利用的功能之一,但对于想要编写具有有意义选择器的测试的人来说,它也是最有用的。选择器游乐场使我们能够识别测试应用程序中元素的有效选择器和唯一选择器。

在选择器游乐场中,Cypress 计算了目标元素的唯一选择器,并通过评估测试框架中默认启用的内置选择器策略来确定选择器。以下显示了两个添加的待办事项和一个打开的 Cypress 选择器游乐场,显示了我们如何唯一选择任何待办事项:

图 9.4-Cypress 选择器游乐场

图 9.4-Cypress 选择器游乐场

第一步是点击选择器游乐场按钮,一旦点击它,选择器游乐场菜单就会出现,如图 9.4所示。在选择器游乐场菜单中,您可以选择将选择器的类型更改为使用cy.get()选择其选择器的元素,或者使用元素文本,这可以通过将选择器切换为cy.contains()来找到。在cy.get()命令或cy.contains()命令中,是我们想要从应用程序预览中获取的特定元素或文本。为了确保任何元素或文本有资格成为唯一的元素选择器,匹配元素的数量,由选择器游乐场上的灰色表示,应为1,以确保我们没有元素或文本的重复。匹配元素标签旁边的按钮代表将选择器复制到剪贴板的复制命令,而下一个按钮是一个打印按钮,将我们选择或选择的命令打印到浏览器的控制台日志中。

当点击选择器游乐场下方的鼠标按钮时,Cypress 会自动显示一个弹出窗口,当用户悬停在元素上时,会自动选择一个可以用于在我们的测试中识别元素的唯一选择器。在图 9.4中,我们可以看到一旦悬停在New Todo项目上,Cypress 会将唯一选择器显示为工具提示,并且在单击元素时还会填充cy.get()命令。当在选择器游乐场菜单上选择元素时,Cypress 将在选择器游乐场上返回唯一的选择器。

选择器的确定

为了确定选择器游乐场中的唯一选择器,Cypress 使用了一种偏好策略,选择的选择器基于 Cypress 已知的一系列策略。Cypress 在选择和分配唯一选择器给元素时,偏好以下策略:

  • data-cy

  • data-test

  • data-testid

  • id

  • class

  • tag

  • attributes

  • nth-child

重要提示

选择器游乐场更喜欢以data-*开头的选择器策略作为它们的识别格式。在大多数情况下,选择器策略是自定义的,因此消除了由于应用程序中使用动态 ID、类名或 CSS 更改而导致测试不稳定的可能性。使用自定义的data-*标签,选择器标识符不会改变,并且可以在应用程序的整个生命周期中持续存在。

当元素可以通过任何这些选择器策略进行识别时,Cypress 将显示元素的唯一选择器。虽然这些策略是 Cypress 偏好的策略,但可以通过更改配置来使 Cypress 识别您的选择器策略,并将其添加到可识别的选择器策略列表中。

编辑选择器元素

选择器游乐场使用户能够编辑所选元素的选择器。编辑选择器元素的能力很重要,因为可以生成更有针对性的选择和更精细的选择器标记,这是 Cypress 本身可能无法做到的。Cypress 会自动识别对选择器游乐场所做的更改,并且当编辑的选择器元素有匹配时,将选择器游乐场突出显示为蓝色,如果在应用程序预览的选择器游乐场中对编辑的选择器标识符没有匹配时,则突出显示为红色。图 9.5图 9.6显示了使用正确的元素选择器编辑选择器游乐场以及使用不正确的元素选择器:

图 9.5 - 游乐场中的有效元素选择器

图 9.5 - 游乐场中的有效元素选择器

图 9.5中,使用无效的元素选择器编辑选择器游乐场会显示错误,并用红色突出显示选择器游乐场,以向我们显示使用我们提供的选择器元素未找到任何元素。另一方面,图 9.6显示编辑选择器游乐场元素选择器是成功的:

图 9.6-游乐场中的无效元素选择器

图 9.6-游乐场中的无效元素选择器

图 9.6所示,我们能够使用在选择器游乐场中编辑的选择器选择我们的两个待办事项。蓝色显示了 Cypress 找到了我们正在搜索的元素,并通过在选择器游乐场中的元素选择器输入右侧显示元素数量来实现这一点。

总结-理解选择器游乐场

在这一部分,我们学习了选择器游乐场是什么,以及在使用测试运行器运行测试时它有多重要。我们学习了如何使用选择器游乐场来选择元素,修改元素,甚至从 Cypress 测试运行器的应用程序预览中选择和复制唯一元素。我们还学习了 Cypress 如何识别元素以及在选择元素时首选的选择器策略。最后,我们学习了如何在选择器游乐场中编辑定位器,以及如何确定我们的选择器是否有效。在下一节中,我们将看看测试运行器上的键盘快捷键是如何工作的。

测试运行器键盘快捷键

键盘快捷键在我们不想在浏览器上执行一系列步骤的情况下特别方便。在本节中,我们将学习如何使用三个键盘快捷键来控制 Cypress 测试运行器并有效地运行我们的测试。通过测试运行器,我们将比使用浏览器操作显式触发操作更快地执行常见操作。

以下是不同键盘键的映射及其关联的操作:

  • R - 重新运行规范文件的测试

  • S - 停止运行测试

  • F - 查看规范窗口中的所有测试

这些键盘键将根据用户的按键触发测试运行器上的不同操作。

总结-测试运行器键盘快捷键

在本节中,我们学习了如何使用 Cypress 键盘快捷键来控制测试运行器的常见操作,只需键盘上的三个键。我们还了解到,使用键盘执行操作比使用浏览器操作触发相同操作时更快。

总结

在本章中,我们学习了仪表板、选择器游乐场和 Cypress 测试运行器中的键盘快捷键。我们探讨了仪表板如何与存根、间谍和路由一起工作,并探讨了路由、存根和间谍的工作原理,以及仪表板中显示的信息。我们还看了选择器游乐场在 Cypress 中的应用以及我们如何利用它来识别应用程序测试中的元素,以及优化 Cypress 用于唯一选择元素的选择器。最后,我们学习了 Cypress 键盘快捷键的作用以及哪些键与使用浏览器功能可用的操作相对应。

现在我们知道并理解了 Cypress 中不同元素是如何联系在一起的,我们可以进一步通过练习来测试我们所学到的知识。在下一章中,我们将测试我们对导航、网络请求和测试的导航配置选项的了解。

第三部分:您的 Web 应用程序的自动化测试

本书的这一部分将让您接触到一些练习,这些练习将帮助您将在第一和第二部分中获得的知识融会贯通。本节包括在测试时的最佳实践,以及涵盖使用 Cypress 来测试大型应用程序。

在本节中,我们将涵盖以下章节:

  • 第十章练习 - 导航和网络请求

  • 第十一章练习 - 桩和监听 XHR 请求

  • 第十二章Cypress 中的视觉测试

第十章:练习-导航和网络请求

在开始本章之前,重要的是要理解,我们在本书的第三部分的重点将放在练习和示例上,这将帮助您磨练测试技能并建立我们在本书之前可能无法涵盖的知识。在本节中,我们将采取实践方法,目标是尽可能多地进行示例和练习。在深入研究本章之前,重要的是您已经阅读了每一章,并且现在希望在我们学习 Cypress 如何用于测试时,建立在您获得的理论知识基础上。

在本章中,我们将专注于涵盖以下主题的练习和示例:

  • 实现导航请求

  • 实现网络请求

  • 高级导航请求配置

一旦您完成了这些练习,您将有信心成为更好的测试人员,并在导航和网络请求领域进行更复杂的测试。

技术要求

为了开始,建议您从 GitHub 克隆包含源代码和本章中将编写的所有测试的存储库。

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。

本章的源代码可以在chapter-10目录中找到。

在我们的 GitHub 存储库中,我们有一个财务测试应用程序,我们将在本章中的不同示例和练习中使用 Cypress 导航和 Cypress 请求。

重要说明:在 Windows 中运行命令

注意:默认的 Windows 命令提示符和 PowerShell 无法正确解析目录位置。

请遵循以下列出的 Windows 命令,这些命令仅适用于以*windows结尾的 Windows 操作系统。

为了确保测试应用程序在您的机器上运行,请从应用程序的根文件夹目录中在您的终端上运行以下命令。

npm run cypress-init命令将安装应用程序运行所需的依赖项,另一方面,npm run cypress-app命令将启动应用程序。可选地,您可以使用npm run cypress-app-reset命令重置应用程序状态。重置应用程序会删除任何不属于应用程序的已添加数据,将应用程序状态恢复到克隆存储库时的状态。我们可以在终端中运行这些命令,就像它们在这里显示的那样:

$ cd cypress/chapter-10;
$ npm install -g yarn or sudo npm install -g yarn
$ npm run cypress-init; (for Linux or Mac OS)
$ npm run cypress-init-windows; (for Windows OS)
// run this command if it's the first time running the application
or
$ npm run cypress-app (for Linux or Mac OS)
$ npm run cypress-app-windows; (for Windows OS)
// run this command if you had already run the application previously
Optionally
$ npm run cypress-app-reset; (for Linux or Mac OS)
$ npm run cypress-app-reset-windows; (for Windows OS)
// run this command to reset the application state after running your tests

重要说明

在我们的chapter-10目录中有两个主要文件夹,一个文件夹包含我们将用于示例和测试练习的应用程序,而第二个文件夹包含我们的 Cypress 测试。为了正确运行我们的测试,我们必须同时运行我们的应用程序和 Cypress 测试,因为测试是在我们本地机器上运行的实时应用程序上运行的。还要注意,应用程序将要求我们使用端口3000用于前端应用程序和端口3001用于后端应用程序。

掌握上述命令将确保您能够运行应用程序,重置应用程序状态,甚至安装应用程序的依赖项。现在让我们开始导航请求。

实现导航请求

Cypress 导航涉及导航到应用程序的网页的行为。在本书中我们已经涵盖了很多测试,在测试之前,您可能还记得cy.visit()命令,其中包含我们要导航到或正在测试的页面的 URL。cy.visit()命令是导航命令的一个例子,它帮助我们在 Cypress 前端测试中进行导航请求。在本节中,我们将通过示例和练习介绍不同的 Cypress 导航命令。通过本节结束时,我们将更深入地了解 Cypress 导航命令,这将帮助我们在本书前几章已经具备的导航知识基础上构建更多的知识。

cy.visit()

在 Cypress 中,我们使用cy.visit()导航到被测试应用程序的远程页面。通过使用这个命令,我们还可以传递配置信息给命令,并配置选项,如方法、URL、超时选项,甚至查询参数;我们将在本章后面深入探讨该命令的配置选项。

在我们的 GitHub 存储库中,在chapter-10/cypress-realworld-app目录中,我们有一个应用程序,我们将在示例和练习中使用。

重要提示

我们的金融应用程序位于chapter-10/cypress-realworld-app目录中,记录交易。通过该应用程序,我们可以通过请求或支付用户来创建交易,这些交易已经存在于应用程序中。我们可以看到已发生交易的通知,还可以查看联系人和已发生交易的日志。

该应用程序使用 JSON 数据库,因此在将所有数据加载到我们的应用程序时会有点慢。在我们的测试中,我们已经实现了一个“安全开关”,通过确保在beforeEach方法中,我们等待所有初始的 XHR(XMLHttpRequest)请求加载数据,以防止测试失败。在下面的代码块中查看有关beforeEach方法的更多信息。

在我们的第一个示例中,在navigation.spec.js中,如下所示的代码块,我们将使用cy.visit()命令导航到应用程序的通知页面:

describe('Navigation Tests', () => {
    beforeEach(() => {
 	cy.loginUser();
	cy.server();
	cy.intercept('bankAccounts').as('bankAccounts');
     	cy.intercept('transactions/public').as('transactions')
     ;
     	cy.intercept('notifications').as('notifications');
     	cy.wait('@bankAccounts');
     	cy.wait('@transactions');
     	cy.wait('@notifications');
});
    afterEach(() => { cy.logoutUser()});
    it('Navigates to notifications page', () => {
        cy.visit('notifications', { timeout: 30000 });
        cy.url().should('contain', 'notifications');
    });
});

这个代码块说明了cy.visit()命令的用法,我们访问远程 URL 到通知路由(http://localhost:3000/notifications),然后验证我们访问的远程 URL 是否符合预期。在我们的导航命令中,我们还添加了超时选项,确保在失败导航测试之前,Cypress 将等待 30 秒的“页面加载”事件。

以下截图显示了我们的测试正在执行,Cypress 正在等待从后端接收的 XHR 请求加载所有必须从我们的 JSON 数据库中加载的数据:

图 10.1 - XHR API 请求和响应

图 10.1 - XHR API 请求和响应

在这个截图中,我们正在导航到/signin页面,然后等待所有资源加载完成后,我们使用 Cypress 的cy.visit()命令导航到/notifications页面,在测试应用程序预览的右侧可见。这进一步通过我们的测试断言进行验证,该断言验证访问的 URL 是否包含名称notifications。以下练习将帮助您更好地了解如何使用cy.visit()命令实现测试。

练习 1

使用 GitHub 存储库中提供的金融应用程序,位于cypress-real-world-app文件夹的根目录中,进行以下练习,测试您对cy.visit()命令的了解:

  1. 登录到我们的测试应用程序,并使用cy.visit()命令导航到http://localhost:3000/bankaccounts URL。

  2. 创建一个新的银行账户,然后检查在创建新的银行账户后应用程序是否重定向回/bankaccounts URL。

  3. 登录应用程序,并使用cy.visit()命令尝试导航到http://localhost:3000/signin

  4. 成功登录测试用户后,验证 URL 重定向到仪表板而不是/signin页面。

练习的解决方案可以在chapter-10/integration/navigation/navigation-exercise-solutions目录中找到。

此练习将测试您理解cy.visit()命令的能力,确保作为 Cypress 用户,您可以有效地使用该命令导航到不同的 URL,并将参数和配置选项传递给命令。

cy.go()

Cypress 的cy.go()导航命令使用户能够在测试应用程序中向前或向后导航。在使用cy.go()命令时,将'back'选项传递给命令将导致浏览器导航到浏览器历史记录的上一页,而'forward'选项将导致浏览器导航到页面的前进历史记录。我们还可以使用此命令通过传递数字选项作为参数来单击前进和后退按钮,其中'-1'选项将导航应用程序后退,而传递'1'将导致前进从浏览器历史记录中导航。

通过使用cy.go(),我们能够通过能够向后跳转到浏览器历史记录的上一页,以及向前跳转到浏览器历史记录的下一页,来操纵浏览器的导航行为。

重要提示

我们在cy.visit()命令中只使用/bankaccounts,因为我们已经在cypress.json文件中声明了baseUrlbaseUrl是 URL 的完整版本,我们在使用cy.visit()cy.intercept()命令时不需要每次重复。您可以在开始本章时克隆的 GitHub 存储库中查看更多信息。

在以下代码块中,我们将使用我们的金融应用程序来验证我们可以在导航到/bankaccounts页面后返回到仪表板:

describe('Navigation Tests', () => {
    it('cy.go(): Navigates front and backward', () => {
        cy.visit('bankaccounts');
        cy.url().should('contain', '/bankaccounts');
        cy.go('back');
        cy.url().should('eq', 'http://localhost:3000/');
    });
});

在此测试中,导航到/bankaccounts URL 后,我们然后使用 Cypress 内置的cy.go('back')命令导航回仪表板 URL,然后验证我们已成功导航回去。以下练习将更详细地介绍如何使用cy.go()命令。

练习 2

使用 GitHub 存储库中提供的金融应用程序,位于chapter-10/cypress-real-world-app目录中,执行以下练习以测试您对cy.go()命令的了解:

  1. 登录后,在交易仪表板上,单击Friends选项卡,然后单击Mine选项卡。

  2. 使用 Cypress 使用cy.go()命令返回到Friends选项卡。

  3. 登录后,单击应用程序导航栏右上角的New按钮,并创建一个新交易。

  4. 然后使用cy.go()Cypress 命令返回到仪表板页面,然后返回到新交易。

练习的解决方案可以在chapter-10/integration/navigation/navigation-exercise-solutions目录中找到。

此练习将帮助您建立使用cy.go()命令测试前进和后退导航的技能。它还将帮助您在测试应用程序时建立对导航的信心。

cy.reload()

Cypress 的cy.reload()命令负责重新加载页面。该命令只有一组选项可以传递给它,即重新加载页面时清除缓存或重新加载页面时保留应用程序内存中的缓存。当将true的布尔值传递给cy.reload()方法时,Cypress 不会重新加载带有缓存的页面;相反,它会清除缓存并加载有关页面的新信息。省略布尔值会导致 Cypress 重新加载启用缓存的页面。在以下代码块中,我们在登录到我们的应用程序后重新加载仪表板;这将刷新我们仪表板页面的状态:

it('cy.reload(): Navigates to notifications page', () => {
    cy.reload(true);
    });

在这个测试中,如果我们的浏览器中有任何缓存项目,Cypress 将重新加载页面并使缓存无效,以确保在执行我们的测试时创建页面的新状态和缓存。让我们看看下面的练习,了解更多关于使用cy.reload()命令的情景。

练习 3

使用 GitHub 存储库中提供的金融应用程序,位于chapter-10/cypress-real-world-app目录中,进行以下练习,以测试您对cy.reload()命令的了解:

  1. 转到账户菜单项,那里有用户设置。

  2. 在点击保存按钮之前,编辑您测试用户的名字和姓氏。

  3. 重新加载页面并验证cy.reload()命令是否重置了尚未保存的所有设置。

练习的解决方案可以在chapter-10/integration/navigation/navigation-exercise-solutions目录中找到。

在这个练习中,我们已经学会了 reload 命令只会重置浏览器中暂时存储的项目。通过使用cy.reload()命令,我们了解了如何重置我们应用程序的缓存存储以及如何对其进行测试。

总结 - 实现导航请求

在本节中,我们通过评估示例和进行练习来学习了 Cypress 上的导航请求是如何工作的。我们还探讨了各种导航命令,如cy.visit()cy.go()cy.reload(),它们在执行 Cypress 中的导航请求时都扮演着重要角色。在下一节中,我们将深入研究如何使用练习和示例来实现网络请求。

实现网络请求

网络请求涉及处理向后端服务的 AJAX 和 XHR 请求。Cypress 使用其内置的cy.request()cy.intercept()命令来处理这一点。在本节中,我们将采用实践方法,深入探讨如何使用示例和练习在 Cypress 中实现网络请求。在本书的第九章中,我们已经与网络请求进行了交互,本章将帮助您建立在您已经熟悉的理论知识和概念基础上。

cy.request()

Cypress 的cy.request()命令负责向 API 端点发出 HTTP 请求。该命令可用于执行 API 请求并接收响应,而无需创建或导入外部库来处理我们的 API 请求和响应。我们的 Cypress 金融应用程序使用基于 JSON 数据库的后端 API。为了了解cy.request()命令的工作原理,我们将请求数据库并检查响应。以下代码块是一个请求,用于从我们的 API 中获取所有交易:

it('cy.request(): fetch all transactions from our JSON database', () => {
        cy.request({
            url: 'http://localhost:3001/transactions',
            method: 'GET',
        }).then((response) => {
            expect(response.status).to.eq(200);
            expect(response.body.results).to.be.an
            ('array');
        })
    });

在上面的测试中,我们正在验证我们的后端是否以200状态代码和交易数据(数组)做出响应。我们将在下一个练习中了解更多关于cy.request()命令的内容。

练习 4

使用 GitHub 存储库中提供的金融应用程序,位于chapter-10/cypress-real-world-app目录中,进行以下练习,以测试您对cy.server()命令的了解。练习的解决方案可以在chapter-10/integration/navigation/network-requests-excercise-solutions目录中找到:

  1. 登录后,使用您的浏览器,调查我们的cypress-realworld应用程序在我们首次登录时加载的 XHR 请求。

  2. 根据观察,编写一个返回以下数据的测试:

应用程序中的联系人

应用程序中的通知

通过进行这个练习,您将更好地理解cy.request()命令,并增加对 Cypress 请求工作原理的了解。接下来,我们将看一下 Cypress 路由。

cy.intercept()

cy.intercept()命令管理测试的网络层的 HTTP 请求的行为。通过该命令,我们可以了解是否进行了 XHR 请求,以及我们的请求的响应是否与我们的预期相匹配。我们甚至可以使用该命令来存根路由的响应。使用cy.intercept(),我们可以解析响应并确保我们实际上对于我们测试的应用程序有正确的响应。cy.intercept()命令使我们可以在所有阶段完全访问我们 Cypress 测试的所有 HTTP 请求。

重要提示

我们必须在测试中引用路由之前调用cy.intercept(),以便在测试中调用它们之前记录路由,并且从下面的测试中,我们可以观察到beforeEach()命令块中的行为。在接下来的测试中,我们在开始运行 Cypress 测试之前调用了cy.intercept命令。

network-request.spec.js文件中找到的以下代码块中,我们正在验证在测试应用程序进行正确登录请求时,我们是否有用户信息的响应:

describe('Netowork request routes', () => {
        beforeEach(() => {        
        cy.intercept('POST','login').as('userInformation');
        });

        it('cy.intercept(): verify login XHR is called when
        user logs in', () => {
            cy.login();
            cy.wait('@userInformation').its('
            response.statusCode').should('eq', 200)
        });
    });

在这个代码块中,我们正在验证应用程序是否向登录端点发出了POST请求,并且我们收到了成功的200状态,这是一个成功的登录。cy.login()命令导航到应用程序的登录页面。我们将在下一个练习中进一步使用cy.intercept()命令。

练习 5

使用 GitHub 存储库中提供的金融应用程序,位于chapter-10/cypress-real-world-app目录中,进行以下练习,以测试您对cy.intercept()命令的了解。练习的解决方案可以在chapter-10/integration/navigation/network-requests-exercise-solutions目录中找到:

  1. 登录到测试应用程序并转到账户页面。

  2. 使用 Cypress 的cy.route()命令来检查 Cypress 是否在更改用户信息时验证用户是否已登录。

是时候进行快速总结了。

总结-实施网络请求

在本节中,我们探讨了 Cypress 网络请求的工作原理,通过示例和练习来理解cy.request()cy.intercept()在 Cypress 测试中的应用。通过示例和练习,我们还扩展了对如何使用诸如cy.intercept()之类的命令来操作和存根的知识。现在我们了解了网络请求,并且可以轻松地编写涉及 Cypress 网络请求的测试,在下一节中,我们将深入研究导航请求的高级配置。

高级导航请求配置

导航是正确运行测试的最重要的方面之一。通过使用cy.visit()cy.go()甚至cy.reload()命令,我们能够知道在编写测试时应该采取什么样的快捷方式。导航命令还显著简化了测试工作流程。大多数前端测试都需要导航,因此掌握高级配置不仅会让您的生活更轻松,而且在编写测试时也会带来更顺畅的体验。在本节中,我们将主要关注 Cypress 的cy.visit()命令的高级命令配置,因为它是 Cypress 的主要导航命令。

cy.visit() 配置选项

下表显示了cy.visit()命令的配置选项以及当没有传递选项给选项对象时加载的默认值:

cy.visit()命令接受不同类型的参数,这决定了传递给它的配置和选项。以下是该命令接受的参数:

  • 只使用 URL 进行配置:
cy.visit(url)
e.g. cy.visit('https://test.com');
  • 使用 URL 和选项作为对象的配置:
cy.visit(url, options)
e.g. cy.visit('https://test.com', {timeout: 20000});
  • 只使用选项作为对象进行配置:
cy.visit(options);
e.g. cy.visit('{timeout: 30000}');

现在是总结时间!

总结-高级导航请求配置

在本节中,我们学习了如何使用不同的选项配置cy.visit()命令,以及该命令接受的不同类型的参数。我们还学习了 Cypress 在没有传递选项对象时为我们提供的不同默认选项,这使得使用cy.visit()命令的过程变得简单,因为我们只需要向命令提供我们需要在测试中覆盖的选项。

总结

在本章中,我们学习了 Cypress 如何执行导航,如何创建请求以及 Cypress 如何解释和返回它们以进行我们的测试执行过程。我们采用了实践方法来学习三个基本的 Cypress 导航命令,以及 Cypress 用于创建和解释请求的三个命令。这些练习为您提供了一个渠道,让您走出舒适区,并对 Cypress 的高级用法进行一些研究,以及如何将我们在本书中获得的逻辑和知识整合到编写有意义的测试中,从而为被测试的应用程序增加价值。最后,我们看了一下cy.visit()命令的高级配置选项。我相信在本章中,您学会了处理和实现测试中的导航和网络请求的技能,以及配置导航请求。

现在我们已经实际探索了使用 Cypress 进行导航和请求,接下来我们将在下一章中使用相同的方法来处理使用 Cypress 进行测试的存根和间谍。

第十一章:练习-存根和监视 XHR 请求

在开始本章之前,您需要了解为什么需要存根或监视请求和方法,为此,您需要了解 Cypress 请求以及如何测试单个方法。之前的章节已经介绍了如何轻松开始使用 Cypress,并且我们已经涵盖了与网络请求和功能测试相关的概念。在本章中,我们将在之前章节中获得的概念基础上进行构建,重点是通过示例和练习的实践方法。

在本章中,我们将涵盖以下关键主题:

  • 理解 XHR 请求

  • 理解如何存根请求

  • 理解如何在测试中监视方法

完成每个主题后,您将准备好开始使用 Cypress 进行视觉测试。

技术要求

为了开始,我们建议您从 GitHub 克隆包含源代码、所有测试、练习和解决方案的存储库,这些内容将在本章中编写。

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。本章的源代码可以在chapter-11目录中找到。

在我们的 GitHub 存储库中,我们有一个财务测试应用程序,我们将在本章的不同示例和练习中使用。

重要提示:在 Windows 中运行命令

注意:默认的 Windows 命令提示符和 PowerShell 无法正确解析目录位置。

请遵循进一步列出的适用于 Windows 操作系统的 Windows 命令,并在末尾加上*windows

为了确保测试应用程序在您的计算机上运行,请在终端中从应用程序的根文件夹目录运行以下命令:

$ cd cypress/chapter-11;
$ npm install -g yarn or sudo npm install -g yarn
$ npm run cypress-init; (for Linux or Mac OS)
$ npm run cypress-init-windows; (for Windows OS)
// run this command if it's the first time running the application
or
$ npm run cypress-app (for Linux or Mac OS)
$ npm run cypress-app-windows; (for Windows OS)
// run this command if you had already run the application previously
Optionally
$ npm run cypress-app-reset; (for Linux or Mac OS)
$ npm run cypress-app-reset-windows; (for Windows OS)
// run this command to reset the application state after running your tests

重要提示

我们的测试位于chapter-11目录中,测试应用程序位于存储库的根目录中。为了正确运行我们的测试,我们必须同时运行我们的应用程序和 Cypress 测试,因为测试是在我们本地计算机上运行的实时应用程序上运行的。还要注意,应用程序将需要使用端口3000用于前端应用程序和端口3001用于服务器应用程序。

第一个命令将导航到我们的应用程序所在的cypress-realworld-app目录。然后,npm run cypress-init命令将安装应用程序运行所需的依赖项,npm run cypress-app命令将启动应用程序。可选地,您可以使用npm run cypress-app-reset命令重置应用程序状态。重置应用程序会删除任何不属于应用程序的已添加数据,将应用程序状态恢复到克隆存储库时的状态。

理解 XHR 请求

XMLHttpRequestXHR)是现代浏览器中存在的 API,它以对象的形式存在,其方法用于在 Web 浏览器发送请求和 Web 服务器提供响应之间传输数据。XHR API 是独特的,因为我们可以使用它在不重新加载页面的情况下更新浏览器页面,请求和接收页面加载后的服务器数据,甚至将数据作为后台任务发送到服务器。在本节中,我们将介绍 XHR 请求的基础知识以及在编写 Cypress 测试过程中的重要性。

在测试中利用 XHR 请求

XHR 请求是开发人员的梦想,因为它们允许您悄悄地发送和接收来自服务器的数据,而不必担心诸如错误或等待时间等问题,当客户端应用程序需要重新加载以执行操作时。虽然 XHR 对开发人员来说是一个梦想,但对测试人员来说却是一场噩梦,因为它引入了诸如无法知道请求何时完成处理,甚至无法知道数据何时从服务器返回等不确定性。

为了解决 XHR 不确定性的问题,Cypress 引入了cy.intercept()命令,我们在第九章第十章中深入研究了这个命令,分别是高级 Cypress 测试运行练习-导航和网络请求中的网络请求部分。cy.intercept()命令监听 XHR 响应,并知道 Cypress 何时返回特定 XHR 请求的响应。使用cy.intercept()命令,我们可以指示 Cypress 等待直到接收到特定请求的响应,这使得我们在编写等待来自服务器的响应的测试时更加确定。

xhr-requests/xhr.spec.js文件中的以下代码块显示了将用户登录到我们的财务测试应用程序的代码。当用户登录时,应用程序会向服务器发送请求,以加载应用程序所需的通知、银行账户和交易明细。这些详细信息作为 XHR 响应从 API 服务器返回:

describe('XHR Requests', () => {
    it('logs in a user', () => {
        cy.intercept('bankAccounts').as('bankAccounts');
        cy.intercept('transactions/public').
        as('transactions');
        cy.intercept('notifications').as('notifications');
        cy.visit('signin'); 
        cy.get('#username').type('Katharina_Bernier');
        cy.get('#password').type('s3cret');
        cy.get('[data-test="signin-submit"]').click()
        cy.wait('@bankAccounts').its('response.statusCode'
        ).should('eq', 200);
        cy.wait('@transactions').its('response.statusCode
        ').should('eq', 304);
        cy.wait('@notifications').its('response.statusCode
        ').should('eq', 304);
    });
});

在上述代码块中,我们正在登录用户,并等待 Cypress 返回我们从服务器发送的交易、通知和银行账户请求的 XHR 响应。只有在成功的用户登录尝试时才会发送响应。我们可以通过以下截图来可视化 Cypress 如何在测试中处理 XHR 请求:

图 11.1-XHR 请求和来自服务器的响应

图 11.1-XHR 请求和来自服务器的响应

这个截图显示了应用程序向我们的服务器发送/bankAccounts/transactions/notifications的 XHR 请求。为了使我们的测试具有确定性,并且为了等待指定的时间以确保成功登录,我们使用cy.intercept()命令来检查 XHR 请求的响应何时被服务器发送回来,以及它们是否以正确的状态码发送回来。

在测试中等待 XHR 响应给我们带来了明显的优势,这些优势超过了没有处理等待的失败机制或者具有显式时间等待的测试。等待 XHR 响应的替代方法是显式等待特定的时间量,这只是一个估计值,并不是 Cypress 等待特定响应的确切时间。在运行我们的测试时等待路由响应的一些优势如下:

  • 能够断言从路由返回的 XHR 响应对象

  • 创建健壮的测试,从而减少不稳定性

  • 具有精确性的失败消息

  • 能够存根响应和“伪造”服务器响应

通过突出这些优势,使用 XHR 请求帮助我们确定地知道何时收到响应,以及 Cypress 何时可以继续执行我们的命令,已经收到了应用程序所需的所有响应。

总结-在测试中利用 XHR 请求

在本节中,我们了解了 XHR 请求,它们是什么,以及 Cypress 如何利用它们向应用程序服务器发送和获取请求。我们还学习了如何等待 XHR 响应以减少不稳定的测试,通过确定性地等待来自我们服务器响应的响应。我们还学习了 XHR 如何帮助我们,我们如何拥有精确的失败消息,甚至如何断言来自我们服务器响应的响应。最后,我们将通过使用cy.intercept()命令与 XHR 响应以及能够通过减少测试不确定性来控制测试执行的潜在好处。在下一节中,我们将看看如何使用存根来控制来自服务器的 XHR 响应。

了解如何存根请求

现在我们知道了什么是 XHR 请求,重要的是要知道我们如何帮助 Cypress 测试 XHR 请求,更重要的是,我们如何避免来自服务器的实际响应,而是创建我们自己的“假”响应,我们的应用程序将解释为来自服务器发送的实际响应。在本节中,我们将看看如何存根 XHR 请求到服务器,何时存根请求以及存根服务器请求对我们的测试的影响。

存根 XHR 请求

Cypress 灵活地允许用户要么使他们的请求到达服务器,要么在应用程序发出对服务器端点的请求时,使用存根响应。有了 Cypress 的灵活性,我们甚至可以允许一些请求通过到服务器,同时拒绝其他请求并代替它们进行存根。存根 XHR 响应为我们的测试增加了一层控制。通过存根,我们可以控制返回给客户端的数据,并且可以访问更改bodystatusheaders的响应,甚至在需要模拟服务器响应中的网络延迟时引入延迟。

存根请求的优势

存根请求使我们对返回给测试的响应以及由应用程序向服务器发出请求时将收到的数据具有更多控制。以下是存根请求的优势:

  • 对响应的 body、headers 和 status 具有控制权。

  • 响应的快速响应时间。

  • 服务器不需要进行任何代码更改。

  • 可以向请求添加网络延迟模拟。

接下来,让我们也看看一些缺点。

存根请求的缺点

虽然存根是处理 Cypress 客户端应用程序测试中的 XHR 响应的一种好方法,但它也有一些缺点,如下所示:

  • 无法对某些服务器端点进行测试覆盖。

  • 无法保证响应数据和存根数据匹配

建议在大多数测试中存根 XHR 响应,以减少执行测试所需的时间,并且还要在存根和实际 API 响应之间有一个健康的混合。在 Cypress 中,XHR 存根也最适合与 JSON API 一起使用。

xhr-requests/xhr-stubbing.spec.js文件的以下代码块中,我们将对bankAccounts端点进行存根,并在运行应用程序时避免实际请求到服务器:

describe('XHR Stubbed Requests', () => {    
    it('Stubs bank Account XHR server response', () => {
        cy.intercept('GET', 'bankAccounts',
        {results: [{id :"RskoB7r4Bic", userId :"t45AiwidW",
        bankName: "Test Bank Account", accountNumber 
        :"6123387981", routingNumber :"851823229", 
        isDeleted: false}]})
        .as('bankAccounts');
        cy.intercept('GET', 'transactions/public').
        as('transactions');
        cy.intercept('notifications').as('notifications');
        cy.wait('@transactions').its('
        response.statusCode').should('eq', 304);
        cy.wait('@notifications').its('
        response.statusCode').should('eq', 304);
        cy.wait('@bankAccounts').then((xhr) => {
            expect(xhr.response.body.results[0].
            bankName).to.eq('Test Bank Account')
            expect(xhr.response.body.results[0].
            accountNumber).to.eq('6123387981')  
        });
    });
});

在上述代码块中,我们对/bankaccounts服务器响应进行了存根,并且我们提供了一个几乎与服务器将要发送的响应几乎相同的响应,而不是等待响应。以下截图显示了成功的存根响应以及我们使用存根响应向客户端提供的“假”存根银行账户信息:

图 11.2 - 客户端应用程序中的存根 XHR 响应

图 11.2 - 客户端应用程序中的存根 XHR 响应

图 11.2中,我们可以看到几乎不可能判断我们的响应是存根化的还是从服务器接收的。使用 Cypress,客户端应用程序无法识别响应是真正从服务器发送的还是存根化的,这使得 Cypress 成为拦截请求并发送响应的有效工具,否则这些响应将需要很长时间才能从服务器发送。我们将在以下练习中了解更多关于存根化 XHR 响应的知识。

练习 1

使用 GitHub 存储库中提供的金融应用程序,位于cypress-realworld-app目录中,进行以下练习,以测试您对存根化 XHR 响应的了解。练习的解决方案可以在chapter-11/integration/xhr-requests-exercises目录中找到:

  1. 存根化应用程序的登录 POST 请求,而不是在仪表板中返回测试用户的名称,而是更改为反映您的名称和用户名。

断言返回的响应确实包含您的用户名和名称信息,这些信息已经被存根化。以下屏幕截图显示了应更改的页面信息:

图 11.3 - 通过存根化登录响应来更改名称和用户名

图 11.3 - 通过存根化登录响应来更改名称和用户名

重要提示

要正确地存根化响应,您需要了解服务器在路由未存根化时发送的响应。为此,请在浏览器上打开浏览器控制台,单击网络选项卡,然后选择XHR 过滤器选项。现在您可以看到所有发送到服务器并由客户端接收的响应和请求。要获取特定请求以进行存根化,您应单击确切的请求并从浏览器控制台的网络窗口的响应选项卡中复制响应。确切的响应(或结构类似的响应)是我们应该用来存根化我们的服务器响应,以确保对客户端的响应一致性。从网络窗口,我们还可以获取有关与请求一起发送和接收的标头以及用于将请求发送到服务器的实际 URL 的信息。

以下屏幕截图显示了服务器返回的通知XHR 响应的示例:

图 11.4 - Chrome 浏览器控制台上通知端点的服务器 XHR 响应

图 11.4 - Chrome 浏览器控制台上通知端点的服务器 XHR 响应

  1. 成功登录后,从Everyone Dashboard选项卡中选择一个随机交易,并将交易金额修改为$100。

通过这个练习,您不仅学会了如何存根化 XHR 响应,还学会了客户端如何处理从服务器接收到的数据。通过了解 XHR 响应存根化的好处,您现在已经准备好处理涉及存根化响应的复杂 Cypress 测试了。

总结 - 了解如何存根请求

在本节中,我们学习了如何使用 XHR 服务器请求来接收请求,还学习了如何通过存根来拦截发送的请求。我们还学习了如何使用存根来控制我们发送回客户端应用程序的响应的性质,以及如何断言我们的存根化响应看起来与我们从服务器接收到的客户端响应类似。最后,我们学习了如何使用浏览器来识别哪些响应需要存根,并使用我们正在存根的响应的内容。在下一节中,我们将看看间谍工作的原理以及如何在我们的 Cypress 方法中利用它。

了解如何在测试中对方法进行间谍

间谍和存根密切相关,不同之处在于,与可以用于修改方法或请求的数据的存根不同,间谍仅获取方法或请求的状态,并且无法修改方法或请求。它们就像现实生活中的间谍一样,只跟踪和报告。间谍帮助我们了解测试的执行情况,哪些元素已被调用,以及已执行了什么。在本节中,我们将学习 Cypress 中间谍的概念,监视方法的优点以及如何利用监视编写更好的 Cypress 测试。

为什么要监视?

我们在 Cypress 中使用间谍来记录方法的调用以及方法的参数。通过使用间谍,我们可以断言特定方法被调用了特定次数,并且使用了正确的参数进行了调用。我们甚至可以知道方法的返回值,或者在调用时方法的执行上下文。间谍主要用于单元测试环境,但也适用于集成环境,例如测试两个函数是否正确集成,并且在一起执行时能够和谐工作。执行cy.spy()命令时返回一个值,而不是像几乎所有其他 Cypress 命令一样返回一个 promise。cy.spy()命令没有超时,并且不能进一步与其他 Cypress 命令链接。

间谍的优点

以下是在测试中使用间谍的一些优点:

  • 间谍不会修改调用的请求或方法。

  • 间谍使您能够快速验证方法是否已被调用。

  • 它们提供了一种测试功能集成的简单方法。

监视是一个有趣的概念,因为它引入了一种在不必对结果采取行动的情况下监视方法的方式。在下面的代码块中,我们有一个测试,其中包含一个简单的函数来求两个数字的和:

  it('cy.spy(): calls sum method with arguments', () => {
        const obj = {
            sum(a, b) {
                return a + b
            }
        }
        const spyRequest = cy.spy(obj, 'sum').as('sumSpy');
        const spyRequestWithArgs = spyRequest.withArgs(1, 
        2).as('sumSpyWithArgs')
        obj.sum(1, 2); //spy trigger 
        expect(spyRequest).to.be.called;
        expect(spyRequestWithArgs).to.be.called;
        expect(spyRequest.returnValues[0]).to.eq(3);
    });

在上述方法中,我们设置了cy.spy()来监视我们的sum方法,并在调用方法或使用参数调用方法时触发间谍。每当方法被调用时,我们的间谍将记录它被调用的次数,我们还可以继续检查我们的方法是否被调用了任何参数。sum函数位于 JavaScript 对象中,触发间谍方法的是obj.sum(1, 2) sum 函数调用,在我们的测试中的断言执行之前被调用。以下屏幕截图显示了间谍、调用次数和测试的别名:

图 11.5 - 监视 sum 方法

图 11.5 - 监视 sum 方法

查看使用cy.spy()方法在sum()函数上的方法,我们可以看到sum方法的间谍和使用参数调用的sum方法在sum方法开始执行时都被触发了一次。

在下一个示例中,我们将探索一个更复杂的场景,我们将尝试监视一个从服务器返回我们 JSON 数据库中所有交易的方法。以下代码块显示了将获取所有交易的方法的间谍:

it('cy.spy(): fetches all transactions from our JSON database', () => {
        const obj = {
          fetch(url, method) {
                return cy.request({
                    url,
                    method
                }).then((response) => response);
            }
        }
        const spyRequest = cy.spy(obj, 
        'fetch').as('reqSpy');
        obj.fetch('http://localhost:3001/transactions', 
        'GET');
        expect(spyRequest).to.be.called;
        expect(spyRequest.args[0][0]).to.eq
        ('http://localhost:3001/transactions')
        expect(spyRequest.args[0][1]).to.eq('GET');
    });

在这个测试中,我们正在验证从数据库获取交易的请求是否发生。通过这个测试,我们可以监视我们的方法,并检查在调用方法时是否传递了正确的参数。

很明显,使用间谍,我们能够确定调用了哪些方法,它们被调用了多少次,以及在调用方法时使用了什么参数。我们将在下一个练习中了解更多关于间谍的知识。

练习 2

使用 GitHub 存储库中提供的金融应用程序,位于cypress-realworld-app目录中,进行以下练习,以测试您对存根 XHR 响应的了解。练习的解决方案可以在chapter-11/integration/spies-exercise目录中找到:

  1. 创建一个名为Area的方法来计算三角形的面积,对area方法进行间谍活动,并调用该方法来断言确实使用了cy.spy()area方法进行了间谍活动。断言该方法也是使用baseheight参数进行调用的。

  2. 使用我们的应用程序,以用户身份登录并对 API 请求方法进行间谍活动,以获取该已登录用户的所有银行账户。断言已调用该方法向服务器发出 API 请求,并且参数作为方法的参数传递。

这个练习将帮助你了解间谍在 Cypress 中是如何工作的,以及可以使用cy.spy()的不同方法来查找被监视的方法的内容。通过监视方法,我们还能够判断方法参数是否被调用以及它们是如何被调用的,还有返回值。

总结-了解如何在测试中对方法进行间谍活动

在这一部分,我们学习了关于间谍活动的重要性以及它与存根活动的不同之处,因为我们不被允许改变被监视方法或请求的值。我们还学会了如何使用存根活动来识别方法的参数、方法被调用的次数、执行上下文,以及被监视方法的返回值。通过例子和练习,我们还学会了如何与cy.spy()命令进行交互,这帮助我们了解了该命令以及它在方法上下文中的工作方式。

总结

这一章的重点主要是 XHR 请求和响应以及它们如何与客户端和服务器进行交互。我们首先了解了 XHR 请求和响应是什么,以及当我们想要从客户端发送请求并从服务器接收请求时它们是多么重要。在这一章中,我们还看到了如何通过使用内置在cy.intercept()命令中的 Cypress 存根功能来存根 XHR 响应来“伪造”服务器响应。最后,我们探讨了 Cypress cy.spy()命令,这进一步让我们了解了如何在 Cypress 中监视方法,并获得找出方法被执行的次数、它们是如何被执行的、它们的参数,甚至它们的返回值的能力。在最后一节中,我们学会了知道通过间谍,我们只能“观察”执行的过程,而不一定有能力改变被测试的请求或方法的执行过程。

我相信通过这一章,你已经掌握了 XHR 请求是什么,它们是如何工作的,如何对它们进行存根,以及如何在 Cypress 中进行间谍活动的技能。在下一章中,我们将看看 Cypress 中的视觉测试。

第十二章:Cypress 中的视觉测试

在开始进行视觉测试之前,您应该了解其他形式的测试以及我们如何使用 Cypress 来完成这些测试。本书的前几章介绍了如何轻松入门 Cypress,如何配置 Cypress,以及如何优化您使用 Cypress 来为测试编写过程开发更有创意的工作流程。前几章的背景信息将为您提供解决本章所需的上下文。本书的最后一章将重点介绍使用 Cypress 进行视觉测试。

在本章中,我们将介绍视觉测试的基础知识,并了解为什么我们需要它。我们还将学习一些工具,可以用来进行视觉测试。本章的主题将帮助你作为工程师或测试人员理解为什么视觉测试对于 Web 应用程序很重要,以及我们如何利用它来编写更好的测试。

我们将在本章中涵盖以下关键主题:

  • 视觉测试

  • 理解视口

  • Cypress 测试中的视觉测试工具

一旦您完成了每个主题,您将准备好开始使用 Cypress 作为您的选择工具进入自动化测试世界的旅程。

技术要求

要开始,请克隆本书的 GitHub 存储库,其中包含本章中将编写的所有源代码、测试、练习和解决方案。

本章的 GitHub 存储库可以在github.com/PacktPublishing/End-to-End-Web-Testing-with-Cypress找到。

本章的源代码可以在chapter-12目录中找到。

在我们的 GitHub 存储库中,我们有一个金融测试应用程序,我们将在本章的不同示例和练习中使用它。

重要提示:在 Windows 中运行命令

注:默认的 Windows 命令提示符和 PowerShell 无法正确解析目录位置。

请遵循以下列出的 Windows 命令,这些命令仅适用于 Windows 操作系统,并以*windows结尾。

为了确保测试应用程序在您的计算机上运行,请从存储库的根文件夹目录中在您计算机的终端上运行以下命令:

$ cd cypress/chapter-12;
$ npm install -g yarn or sudo npm install -g yarn
$ npm run cypress-init; (for Linux or Mac OS)
$ npm run cypress-init-windows; (for Windows OS)
// run this command if it's the first time running the
application
or
$ npm run cypress-app (for Linux or Mac OS)
$ npm run cypress-app-windows; (for Windows OS)
// run this command if you had already run the application
previously
Optionally
$ npm run cypress-app-reset; (for Linux or Mac OS)
$ npm run cypress-app-reset-windows; (for Windows OS)
// run this command to reset the application state after
running your tests

重要提示

我们的测试位于chapter-12目录中,测试应用程序位于存储库的根目录中。为了正确运行我们的测试,我们必须同时运行我们的应用程序和 Cypress 测试,因为测试是在实时应用程序上运行的,而这个应用程序必须在我们的计算机上本地运行。重要的是要注意,测试应用程序将需要使用端口3000用于前端应用程序和端口3001用于服务器应用程序。

第一个命令将导航到cypress-realworld-app目录,这是我们的应用程序所在的位置。npm run cypress-init命令将安装应用程序运行所需的依赖项,而npm run cypress-app命令将启动应用程序。可选地,您可以使用npm run cypress-app-reset命令重置应用程序状态。重置应用程序会删除任何不属于应用程序的已添加数据,从而将应用程序状态恢复到克隆存储库时的状态。

视觉测试

无论您是 Web 开发人员还是测试人员,都需要确保正在开发的应用程序保留了项目概念时预期的外观和感觉。作为开发人员,您可能希望验证应用程序在发布之间没有发生视觉方面的变化。作为测试人员,您可能希望验证应用程序的用户界面在发布之间保持一致,并且与设计一致。

功能测试可以用来检查视觉方面,比如验证按钮或输入框是否存在。然而,这可能需要编写大量代码,并且大多数情况下,它不会允许您测试应用程序的每个方面,比如在验证用户界面元素时使用 CSS 更改。视觉测试是验证应用程序用户界面的视觉方面,并确保它们与预期一致的能力。

在本节中,我们将学习什么是视觉测试,视觉测试的不同类型是什么,手动和自动视觉测试之间的区别,以及何时使用不同类型的视觉测试方法。

为什么要进行视觉测试?

视觉测试采取了实际的方法,因为您必须直接映射页面的视觉方面,并将这些方面与预期的设计进行比较。我们可能会忽略视觉测试的想法,因为我们认为我们的眼睛对于验证目的足够准确,这是一个错误的假设。虽然肉眼可以注意到可见的页面变化,但对于眼睛来说,检测微小的细节,比如改变 CSS 属性导致输入元素移动了几个像素或者像素变化很小,可能会更加困难。

视觉测试的存在是为了让开发人员和测试人员确信网页的用户界面没有被任何开发人员的更改破坏。例如,通过视觉测试,如果部署到生产环境的应用程序版本缺少注册按钮,而以前的应用程序版本有该按钮,就不需要担心。

有两种类型的视觉测试,如下所示:

  • 手动视觉测试

  • 自动化视觉测试

这两种类型的视觉测试将向我们展示视觉测试的重要性,以及我们如何利用这两种测试方法来编写更好的测试。

手动视觉测试

手动视觉测试涉及使用肉眼验证开发团队所做的更改是否破坏了任何可见的用户界面功能。手动视觉测试要么由测试人员,要么由开发团队进行,他们会对开发的用户界面进行视觉测试,并将其与最初创建的设计进行比较。通过视觉测试应用程序的过程确认了行为、外观和用户界面的变化是否符合预期。手动视觉测试适用于用户界面的小改变,但这可能不是一种非常准确的验证应用程序的方式,特别是对于具有许多页面和视觉元素或不同视口的应用程序。为了识别手动视觉测试的局限性,以下图片由Atlantide Phototravel显示了埃菲尔铁塔的并排比较。它们非常相似,但第二帧中省略了微小的细节。花几秒钟比较这些图像,试图找出视觉上的差异,而不看第二张图像中的圆形区域:

图 12.1 - 发现埃菲尔铁塔图像中的差异

图 12.1 - 发现埃菲尔铁塔图像中的差异

即使对于训练有素的眼睛来说,也有一些细节,比如鸟的图案、缺少的人和甚至缺少的云,这几乎让人无法通过视觉来判断两张照片之间是否真的有差异。通过应用手动视觉测试的相同想法,很可能会忽略细节,无法找到它们之间的任何差异,即使一些元素在测试应用程序中丢失或添加了。

自动化视觉测试

自动化视觉测试涉及测试页面的视觉元素。与手动方法不同,使用自动化流程来检查应用程序页面的一致性。要正确运行自动化视觉测试,我们必须将所需的用户界面保存和定义为基线。然后我们可以在测试中使用这个基线来检查是否需要更新基线或修改应用程序所做的更改。

自动化视觉测试源于功能测试。自动化视觉测试采用了检查整个页面的方法,而不是断言页面中的每个元素并检查元素的属性。

自动化视觉测试有两种类型:

  • 快照测试

  • 视觉 AI 测试

让我们详细看看每一种。

快照测试

快照测试是一种自动化视觉测试,当测试运行时,记录特定屏幕的光栅图形或位图。然后检查记录的位图与先前记录的基线位图(基线)是否一致。快照测试工具中的算法仅通过比较十六进制颜色代码来检查位图中是否存在像素差异。如果识别出任何颜色代码差异,则报告快照错误或生成显示视觉差异的图像。

与手动测试相比,快照测试是识别用户界面中的错误的一种更快的方法。如果应用程序在某种程度上是静态的,并且用户界面中没有太多的动态内容更改,那么快照测试是测试 Web 应用程序的首选方式。快照测试无法正确处理动态内容,因为算法将内容中的任何更改都视为视觉差异,由于像素更改。由于所有视觉变化都被识别为视觉差异或潜在的错误,因此在包含动态数据的页面上拥有一致的快照图像将是不可能的。

视觉 AI 测试

视觉 AI 测试是自动化视觉测试的新一代,利用了人工智能(AI)。视觉 AI 测试的主要目标是改进快照测试的缺点,例如在测试应用程序时处理动态内容。通过使用计算机视觉,视觉 AI 算法可以识别图像和测试可以运行的区域,甚至在动态内容的情况下,它们可以识别内容允许动态的区域和应保持不变的区域。

视觉 AI 测试还使开发人员和测试人员更容易进行跨浏览器测试。对于跨浏览器应用程序,用户可以编写单个测试,然后在应用程序支持的不同视口中运行该测试。视口测试是一个方便的工具,因为它消除了开发人员或测试人员为每个设备编写快照测试的负担,以验证是否存在视觉变化。

回顾-视觉测试

在本节中,我们了解了什么是视觉测试,不同类型的视觉测试以及何时使用每种类型的视觉测试。我们了解了自动化视觉测试和手动视觉测试之间的区别,还了解了不同类型的自动化视觉测试。然后我们了解了为什么视觉测试是手动测试的首选方法,以及为什么有一种新一代的视觉测试工具改进了第一代视觉测试工具存在的缺点。现在我们已经了解了关于视觉测试的一切,在下一节中,我们将通过了解视口是什么以及如何测试它们来探索更多需要视觉测试的领域。

理解视口

视口是用户网页的可见区域。因此,视口这个术语用于测量用户设备上的矩形查看区域。当计算机首次发明时,只有少数可用的视口,但由于创建了更多的设备,这种情况已经显著增加。在撰写本文时,由折叠手机或翻转屏幕以及具有不同尺寸的智能电视等设备创建了新的视口,因此开发人员需要确保他们的应用与用户的设备兼容。使用不同的视口,会出现新的挑战,使应用与这些视口兼容,这对测试人员来说是一个更大的噩梦,因为几乎不可能通过每个可用的视口测试应用。

在这一部分,我们将探讨视口的重要性,如何在不同的视口中进行测试以及视口在视觉测试中的作用。

视口和测试

视口在测试网页应用程序时起着重要作用,因为它们显示了实际用户将如何查看正在测试的网页应用程序。在撰写本文时,移动视口是最常用的视口。这是因为手机已经发展成为最强大的便携式技术设备。为了为用户提供良好的体验,视口测试应该是首要任务。通过视口测试,我们可以检查网页应用对不同屏幕尺寸的响应性等特性。

开发响应式网页应用程序比非响应式网页应用程序具有优势,因为与独立的 iOS 或 Android 移动应用程序相比,它们需要更少的时间和资源来开发,并且执行相同的功能。

所有现代浏览器都允许我们在构建应用时检查和测试响应性。下面的截图显示了在 Chrome 浏览器上呈现的 iPhone 6 视口,显示了 Cypress 文档页面在手机上的呈现方式:

图 12.2- iPhone 6 移动视口

图 12.2- iPhone 6 移动视口

我们可以在浏览器上使用切换设备工具栏在正常网页视图和移动设备视图之间切换。这使我们能够看到不同的网页应用在不同视口上的呈现方式。在网页应用响应的情况下,测试不同视口不会有问题,因为应用会自动适应不同的视口。然而,对于不响应的网页应用来说情况就不同了。在下面的截图中,您可以看到当前视口的选项,以及添加浏览器未定义的自定义视口的能力:

图 12.3-浏览器视口选择

图 12.3-浏览器视口选择

如前面的截图所示,可以添加 Chrome 浏览器设备列表中不存在的新测试视口。

在选择视口时,Chrome 网页区域会自动调整浏览器上可见的内容。作为开发人员或测试人员,很容易找出应用是否需要进行更改。

视口和自动化视觉测试

考虑到前面截图中显示的视口数量,手动测试每个单独的视口并验证是否有破坏应用用户界面或引入不必要的未预期变化的变化是很繁琐的。为了确保视口被测试,我们可以使用自动化视觉测试来检查应用在不同视口下的一致性。通过视觉测试,我们可以验证我们的应用在测试中配置的不同视口中是否发生了意外变化。

回顾-视口

视口是视觉测试的关键方面,特别是因为大多数关于 Web 应用响应性的主要问题都是由于视口错误造成的。在本节中,我们了解了不同类型的视口以及如何使用浏览器的切换选项来检查我们的 Web 应用的响应性,该选项可以在不同设备视口和正常计算机视口之间切换。我们还了解到,通过使用自动化视觉测试,我们可以为不同的视口自动化不同的测试用例,并自动知道应用程序是否发生了意外更改。在下一节中,我们将探讨如何使用 Cypress 编写自动化视觉测试,使用自动化视觉 AI 工具和 Percy,后者利用快照记录视觉测试。

自动化视觉测试工具

视觉测试是 Cypress 的重要组成部分,因为它是从我们熟悉的功能测试转变而来的。通过视觉测试,Cypress 为我们提供了一个新的机会,可以在不必为了断言页面上的单个元素而编写数百行功能代码的情况下测试用户界面。

在本节中,我们将深入研究如何通过将它们与 Cypress 集成来使用两个自动化视觉测试工具,然后学习如何使用它们来实现我们的视觉测试应用程序的目标。其中一个工具使用快照记录一个基线位图,并逐个比较位图图像像素,检查十六进制颜色是否存在任何差异。另一个工具使用 AI 算法来比较来自我们 Web 应用程序的快照。

到本节结束时,我们将了解使用什么工具,在什么时候以及 Cypress 如何在创建测试工具和测试本身的简单集成中发挥作用。我们将研究的两个工具是 Applitools Eyes SDK 和 Percy。

Percy

Percy 是一个与测试工具集成的视觉测试和审查平台。这使开发人员和质量保证工程师能够识别视觉错误,否则这些错误将很难识别和报告。Percy 使视觉测试变得轻而易举 - 您只需要下载 Percy npm 模块,配置BrowserStack,并将 Percy 添加到您的测试中。完成所有必要的配置后,您可以复制 Percy 提供的TOKEN,该 TOKEN 将作为环境变量在您的机器上使用,如果您希望上传您的测试快照到 Browserstack 云以审查和识别可能存在的视觉差异。

重要提示

Browserstack 是一个视觉测试和审查工具,拥有Percy工具。要配置 Percy,您需要配置 Browserstack;所有配置将在两个平台之间同步。

Percy 主要依赖于 Firefox 和 Chrome 浏览器。为了测试应用程序,Percy 会在各种视口中运行一组浏览器,并记录各种视口的任何更改。当第一张图像被记录并保存时,Percy 会将图像作为您的测试基线,并在后续测试运行中使用该图像来检查类似图像的任何更改,然后突出显示可能发生的任何视觉差异。

设置 Percy

设置 Percy 并不复杂,涉及以下步骤:

  1. 使用 BrowserStack 创建一个帐户(www.browserstack.com/)。

  2. 验证您的 BrowserStack 电子邮件地址。

  3. 在 Browserstack 仪表板中创建一个组织。

  4. 使用您的 BrowserStack 帐户登录 Percy 仪表板。

  5. 在 Percy 仪表板上创建一个项目。

  6. 使用 Percy 网站上的说明在本地项目上配置 Percy(docs.percy.io/docs/cypress)。

  7. 将 Percy TOKEN 添加到您的本地机器作为环境变量。

  8. 哇!您现在已经准备好编写您的测试了!

完成步骤 1-4后,Percy 会提供一个令牌,您必须在执行测试之前将其添加到机器环境变量中。您可以访问 Percy 文档(docs.percy.io/docs/cypress)了解如何使用 Cypress 设置 Percy 的更多信息。

一切都设置好后,我们可以运行第一个测试,这将涉及检查当内容更改时我们的登录页面是否有视觉差异。如下截图所示,我们在登录页面上输入用户名和密码运行了我们的测试:

图 12.4 – Percy – 新快照

图 12.4 – Percy – 新快照

在这里,我们可以看到上传到 Percy 仪表板的快照图像。上传的快照是我们的登录页面。在上传快照后,Percy 让我们可以切换 Chrome 和 Firebox 浏览器,以便我们可以检查快照的一致性。在主 Percy 仪表板上,我们可以批准所有快照,拒绝和接受单个快照,甚至在桌面视口和移动视口之间切换。

重要提示

只有当测试执行结束并关闭运行测试的终端时,Percy 才会将快照上传到仪表板。这与 Applitools 工具不同,后者在测试执行结束后立即连续上传测试快照。

正如我们之前提到的,我们可以使用 Percy 来比较我们记录的基准图像和新生成的位图图像。然后涉及的算法逐像素检查差异,当基准图像与在测试应用程序的第二次运行中生成的新图像不相似时,这些差异被记录为视觉差异。以下截图显示了我们在 Percy 仪表板上测试的第二次构建。在这里,我们省略了用户名和密码字段中的一些字符,并且我们想检查 Percy 是否识别出这些差异:

图 12.5 – Percy 像素差异

图 12.5 – Percy 像素差异

如前面的截图所示,当我们运行第二个构建时,我们省略了用户名和密码字段中的一些字符。当快照上传到 Percy 时,程序通过检查不同图像的像素来识别视觉差异,并为我们提供识别出像素差异的区域。在我们的第二次运行中,当我们批准这些更改时,Percy 采用我们的第二个图像作为基准。如果我们在图像上请求更改,Percy 将保留我们的第一个图像作为此特定快照的基准。

仔细检查后,我们发现第一个快照的登录用户名是Kathe,而在第二个快照中,登录用户名是Kat。密码中的一些字符和用户名中的一些字符的省略是触发 Percy 显示这些视觉差异的原因。这使我们可以选择接受更改并更改我们的基准,或者如果更改与我们的期望不一致,则向开发人员请求更改。

提醒

要成功运行测试并将快照上传到 Percy 仪表板,您需要在 BrowserStack 上创建一个帐户,在 BrowserStack 中创建一个组织,在 Percy 仪表板上使用 Browserstack 登录,创建一个 Percy 项目,并将 Percy 项目仪表板上提供的令牌添加到机器的环境变量中。

Percy 在本地机器上和测试中都很容易设置。要调用 Percy,只需要在测试中添加一行代码。以下代码块显示了生成第一和第二快照,以及传递给cy.percySnapshot()命令的参数来命名快照:

describe('Percy Login Snapshots', () => {
    it('percy: signin page snapshot - first build ', () => 
      {
        cy.visit('signin'); 
        cy.get('#username').type('Kathe');
        cy.get('#password').type('passwor');
        cy.percySnapshot('first');
    });
    it('percy: signin page snapshot - second build, () => {
        cy.visit('signin'); 
        cy.get('#username').type('Kat');
        cy.get('#password').type('passd');
        cy.percySnapshot('second');
    });  
});

在第一个构建中运行了前面代码块中的第一个测试,而第二个测试是在第二个构建中运行的,同时修改了用户名和密码详细信息,以提供我们登录页面中的像素差异。要自行运行这些测试,您只需要按照之前提到的 Percy 设置过程获取 Percy 令牌,并将您的 Percy 项目令牌添加为计算机的环境变量。这些测试的完整源代码可以从本书的 GitHub 存储库中的chapter-12目录中获取。

练习 1

在这个练习中,我们将练习之前学到的知识:学习如何使用 Percy 进行视觉测试,然后与 Percy 配置和仪表板进行交互。按照以下步骤:

  1. 使用 Percy 和 Cypress,登录到我们的测试应用程序并导航到仪表板。然后,使用Percy命令,对公共交易页面进行快照。

  2. 通过单击应用程序上的新交易按钮并添加交易详细信息来添加新交易。

  3. 拍摄另一个快照,并使用 Percy 比较添加另一个交易时的交易页面差异。

重要提示

在运行测试之前,记得将您的 Percy TOKEN变量添加到本地计算机,以便 Percy 拍摄的快照可以成功上传到 Percy 仪表板。

此练习的解决方案可以在chapter-12/cypress/integration/percy/percy-excercise.spec.js目录中找到。

通过完成这个练习并能够在 Cypress 中正确设置 Percy,我相信你现在了解了如何使用 Percy 来识别测试中的视觉差异,以及在应用程序用户界面发生变化时快速识别差异。你可以通过对我们应用程序的位图图像进行逐像素比较来实现这一点。

Applitools

Applitools 是一种利用人工智能来进行视觉测试和监控应用程序的工具。就像 Percy 一样,使用 Cypress 设置 Applitools 很容易,并专注于改进 Percy 等工具的不足之处。Percy 通过比较单个像素来识别视觉差异,而 Applitools 通过使用其 AI 算法来检查变化是否是预期的变化或错误来识别视觉差异。使用 Applitools,更容易测试动态变化,因为我们可以省略我们不希望 Applitools 检查视觉差异的区域。

通过指定应该检查的区域和应该忽略的区域来识别错误的能力,使 Applitools 成为测试涉及动态内容的应用程序时更好的工具。

设置 Applitools

就像 Percy 一样,使用 Cypress 设置 Applitools Eyes SDK 相对容易。可以通过以下步骤实现:

  1. 使用 Applitools 创建一个帐户(auth.applitools.com/users/register)。

  2. 验证您的 Applitools 电子邮件地址。

  3. 导航到 Applitools 仪表板以获取 API 密钥。

  4. 在本地项目上配置 Applitools。

  5. 将 Applitools 的APPLITOOLS_API_KEY添加到您的本地计算机作为环境变量。

  6. 派对!

一旦步骤 1步骤 2完成,Applitools 会为您提供一个APPLITOOLS_API_KEY,类似于 Percy 的TOKEN,您必须在执行测试之前将其添加为计算机的环境变量。您可以访问 Applitools 和 Cypress 文档(applitools.com/tutorials/cypress.html)了解有关如何使用 Cypress 设置 Applitools Eyes SDK 的更多信息。

一切都设置好后,我们现在可以使用 Cypress 和 Applitools Eyes SDK 运行我们的第一个测试。Applitools 是一个非常丰富的工具,所以我们无法涵盖它捆绑的所有功能。相反,我们将专注于 Applitools 作为视觉测试工具的优势。在下面的屏幕截图中,我们有相同的登录测试,我们在 Percy 示例中运行了该测试,但修改为 Applitools Eyes 测试:

图 12.6 – Applitools 登录页面快照

图 12.6 – Applitools 登录页面快照

在这里,我们可以看到代表 Applitools Eyes SDK 拍摄并上传到 Applitools 仪表板的第一个登录页面快照的快照。Applitools 使用三个命令来控制 Cypress 测试。第一个命令cy.eyesOpen()用于初始化和启动测试,第二个命令cy.eyesCheckWindow()负责拍摄屏幕截图,就像前面的情况一样,第三个命令eyesClose()完成 Applitools Eyes 会话并将屏幕截图上传到仪表板。

我们的登录测试可以以以下格式编写。这会打开 Applitools Eyes SDK,拍摄屏幕截图,并在上传屏幕截图到 Applitools 仪表板之前关闭 SDK,以便 Applitools AI 算法可以通过视觉比较进行比较。以下代码块显示了前面屏幕截图中提供的第二个构建:

it('applitools: can signin on login page - second build snapshot', () => {
    cy.eyesOpen({
      appName: 'SignIn Page',
      browser: { width: 1200, height: 720 },
    });
    cy.get('#username').type('Kat');
    cy.get('#password').type('passd');
    cy.eyesCheckWindow('loginPage');

    cy.eyesClose();
  });

在这里,我们可以观察到,为了运行测试,我们需要初始化 Applitools Eyes SDK,然后在关闭测试之前拍摄屏幕截图。Eyes SDK 使用的所有三种方法都可以具有配置参数,这些参数可以根据您的需求进行更改。例如,在我们的代码块中,我们已配置cy.eyesOpen()命令,以便我们有测试批次名称和浏览器窗口可见的配置。

在报告错误方面,Applitools 更进一步。在 Percy 中,我们发现由于其逐像素比较,用户界面的任何更改都被检测为视觉差异,可能是用户界面错误。在下面的屏幕截图中,我们可以看到,在使用不同用户界面渲染运行类似测试后,我们可以告诉 Applitools 忽略屏幕中的某些区域,并将我们的测试标记为通过:

图 12.7 – Applitools 忽略区域选项

图 12.7 – Applitools 忽略区域选项

在这里,我们可以看到 Applitools 提供的不同选项。即使不同区域有不同的视觉元素,如果它们不是视觉错误,或者它们是从动态内容生成的,也可以忽略这些区域。在忽略具有视觉差异的区域后,我们继续将屏幕截图标记为已接受。

重要提示

请记住在运行测试之前将APPLITOOLS_API_KEY变量添加到本地机器作为环境变量,该变量是从 Applitools 仪表板获取的。此令牌确保 Applitools Eyes SDK 拍摄的快照成功上传到 Applitools 仪表板。

下面的屏幕截图显示了 Cypress 重新运行测试并在本地通过测试。这是因为我们已指示 Applitools Eyes SDK 接受与我们基准快照相比存在的视觉变化:

图 12.8 – 在 Applitools 仪表板中忽略测试区域后通过测试

图 12.8 – 在 Applitools 仪表板中忽略测试区域后通过测试

哇,我们的测试通过了!对 Applitools 测试仪表板所做的任何更改都会反映在本地测试运行中。这是通过在运行 Applitools 视觉测试之前必须添加到环境变量中的 API 密钥实现的。您可以阅读更多关于 Applitools Eyes 的信息(applitools.com/tutorials/cypress.html)以了解如何使用 Applitools 在动态现代网页上测试用户界面。

练习 2

在这个练习中,我们将测试我们对 Applitools Eyes SDK 工具的了解以及如何使用它进行视觉测试。这个练习将帮助我们实际实施本章的理论部分,并找出如何使用 Cypress 和 Applitools 编写视觉测试。执行以下步骤:

  1. 使用 Applitools 和 Cypress,登录到我们的测试应用程序并导航到仪表板。然后,使用Applitools Eyes SDK的快照命令,对公共交易页面进行快照。

  2. 通过单击应用程序上的新交易按钮并添加交易详细信息来添加另一个新交易。

  3. 再次拍摄快照,并使用 Applitools 比较交易页面的差异,从新交易创建时起。

  4. 忽略 Applitools 仪表板中新交易创建的区域,并使用忽略区域重新运行测试。

前面练习的解决方案可以在chapter-12/cypress/integration/applitools/applitools-excercise.spec.js目录中找到。

通过这样,我相信您已经学会了如何使用 Applitools 的自动化视觉测试,并且这个练习已经帮助您掌握了使用 Cypress 进行自动化视觉测试的技能和知识。通过这样,我们已经到达了本书的结尾,我宣布您是一名合格的“bug 猎手”!

总结-自动化视觉测试工具

在本节中,我们了解了两种自动化视觉测试工具,Percy 和 Applitools,以及它们如何与 Cypress 测试集成。然后,我们了解了 Percy 和 Applitools 之间的区别,以及 Percy 使用快照测试方式与 Applitools 使用 AI 分析测试中的视觉差异的不同之处。最后,我们了解了我们可以如何利用诸如 Applitools 之类的工具进行测试。我们通过了解浏览器上的内容随时间变化以及更多动态网站需要能够“适应”现代网页上动态内容的工具来实现这一点。

总结

在本章中,我们着手了解如何进行视觉测试及其重要性,以及我们可以使用的视口和工具来进行自动化视觉测试。在本章中,我们学习了如何正确进行视觉测试。这涉及理解如何创建视口,如何在不同的视口上进行测试,以及为什么我们需要在多个视口上运行自动化视觉测试。然后,我们探讨了两种测试工具,Percy 和 Applitools Eyes SDK,并广泛涵盖了它们的用例、设置过程以及如何使用它们编写 Cypress 测试。最后,我们进行了一些练习,以提高我们对这些工具的熟悉度和互动性。

通过这样,我们已经到达了本书的结尾。如果您一直在阅读本书的所有章节,我相信您现在对 Cypress 的了解比起开始时更加深入。我希望这本书挑战了您的思维方式,让您对 Cypress 作为测试工具产生了热爱,并且在成为更好的测试人员或开发人员方面也有所改变。

posted @ 2024-05-22 12:06  绝不原创的飞龙  阅读(50)  评论(0编辑  收藏  举报