第一步:启动您的服务器
假设您已成功安装了 Cypress 并在您的项目中打开了 Cypress,您首先要做的事情就是启动托管应用程序的本地开发服务器。
它应该类似于 http://localhost:8080。
反模式
不要尝试从 Cypress 脚本中启动 Web 服务器。请阅读最佳实践。
为什么要启动本地开发服务器?
您可能会想 - 为什么我不能只是访问已经部署的应用程序呢?
虽然您确实 可以 测试已经部署的应用程序,但那真的不是 Cypress 的最佳应用场景。
Cypress 是为您的日常本地开发构建和优化的工具。事实上,当您使用 Cypress 一段时间后,我们相信您可能会发现甚至在其中完成所有开发工作也会很有用。
最终,您不仅能够同时测试和开发,而且您实际上还能够在编写测试时更快地构建应用程序,而获得 “免费 “的测试。
更重要的是 - 由于 Cypress 能够执行诸如存根网络请求之类的操作,您甚至可以在不需要服务器提供有效的 JSON 响应的情况下构建您的 Web 应用程序。
最后但同样重要的一点 - 尝试将测试适应已构建的应用程序比较困难,而构建应用程序时编写测试则更容易。您可能会遇到一系列初始的挑战/障碍,而这些挑战/障碍在开始编写测试时本应该避免的。
最后,可能也是最重要的一个原因是您希望针对本地服务器进行测试,这是因为您可以控制它们。当您的应用程序在生产中运行时,您无法控制它。
当它运行在开发中时,您可以:
- 捷径
- 运行可执行脚本以生成数据
- 暴露测试环境特定的路由
- 禁用使自动化困难的安全功能
- 重置服务器/数据库上的状态
话虽如此 - 您仍然可以选择两者兼顾。
我们的许多用户对本地开发服务器运行的 大部分 集成测试,然后仅对部署的生产应用程序运行的一小部分烟雾测试进行保留。
第二步:访问您的服务器
一旦您的服务器正在运行,就是时候访问它了。
让我们删除上一个教程中创建的 sample.cy.js
文件,因为它现在不再需要了。
rm cypress/e2e/sample.cy.js
现在让我们创建我们自己的规范文件,命名为 home_page.cy.js
。
touch cypress/e2e/home_page.cy.js
创建该文件后,您应该会在规范文件列表中看到它。
现在您需要在您的测试文件中添加以下代码来访问您的服务器:
describe('首页', () => {
it('成功加载', () => {
cy.visit('http://localhost:8080') // 将 URL 更改为匹配您的开发 URL
})
})
现在点击 home_page.cy.js
文件,观察 Cypress 打开您的浏览器。
如果您忘记启动服务器,您将看到下面的错误:
如果您已经启动了服务器,那么您应该会看到您的应用程序加载并正常运行。
第三步:配置 Cypress
如果您考虑得足够周到,您很快就会意识到您将会频繁地输入此 URL,因为每个测试都需要访问您应用程序的某个页面。幸运的是,Cypress 提供了一个配置选项来解决这个问题。让我们现在利用它。
打开您的配置文件。它一开始是空的,但是让我们添加 baseUrl
选项。
配置 Cypress
- cypress.config.js
- cypress.config.ts
const { defineConfig } = require('cypress')module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:8080', },})
import { defineConfig } from 'cypress'export default defineConfig({ e2e: { baseUrl: 'http://localhost:8080', },})
这将自动在 cy.visit()
和 cy.request()
命令前添加基本 URL。
信息
每当您修改配置文件时,Cypress 将自动重新启动并关闭所有打开的浏览器。这是正常的。单击规范文件以重新启动浏览器。
现在,我们可以访问相对路径,省略主机名和端口号。
describe('首页', () => { it('成功加载', () => { cy.visit('/') })})
很好!一切应该仍然是绿色的。
配置选项
Cypress 还有许多其他配置选项,可用于自定义其行为。诸如测试文件的存储位置、默认超时时间、环境变量、要使用的报告器等。
在配置中查看它们!
测试策略
您即将开始为您的应用程序编写测试,只有 您 知道您的应用程序,因此我们没有太多具体的建议可供您参考。
测试什么、边缘情况和关键点在哪里、可能遇到的回归问题等完全取决于您、您的应用程序和您的团队。
尽管如此,现代 Web 测试有一些让每个团队都会遇到的难题,因此在这里有一些关于您可能遇到的常见情况的快速提示。
数据预设
根据您的应用程序构建方式,您的 Web 应用程序可能会受到服务器的影响和控制。
如今,服务器通常通过 JSON 与前端应用程序进行通信,但您也可能正在运行传统的服务器端渲染 HTML Web 应用程序。
通常,服务器负责发送反映其持有的某种状态的响应 - 通常是在数据库中。
传统上,在使用 Selenium 编写 e2e
测试时,在自动化浏览器之前会在服务器上进行一些 设置和拆卸。
也许您需要生成一个用户,并使用相关记录对其进行预设。您可能熟悉使用诸如夹具或工厂之类的工具。
为了测试各种页面状态 - 如空视图或分页视图,您需要在服务器上进行预设,以便可以测试该状态。
虽然这种策略还有更多内容,但一般来说,您有三种方法可以通过 Cypress 实现这一点:
cy.exec()
- 运行系统命令cy.task()
- 通过 setupNodeEvents 函数在 Node 中运行代码cy.request()
- 发送 HTTP 请求
如果您的服务器正在运行 node.js
,您可能会添加一个在测试环境下运行的 before
或 beforeEach
钩子来执行一个 npm
任务。
describe('首页', () => { beforeEach(() => { // 在每次测试之前重置并预设数据库 cy.exec('npm run db:reset && npm run db:seed') }) it('成功加载', () => { cy.visit('/') })})
与其仅执行系统命令不同,您可能需要更灵活性,并且可以在测试环境中运行时公开一系列路由。
例如,您可以将几个请求组合在一起,以便告诉您的服务器要创建的确切状态。
describe('首页', () => { beforeEach(() => { // 在每次测试之前重置并预设数据库 cy.exec('npm run db:reset && npm run db:seed') // 在我们的测试中生成一个我们可以控制的 DB 中的帖子 cy.request('POST', '/test/seed/post', { title: 'First Post', authorId: 1, body: '...', }) // 在我们的测试中生成一个我们可以从中控制的 DB 中的用户 cy.request('POST', '/test/seed/user', { name: 'Jane' }) .its('body') .as('currentUser') }) it('成功加载', () => { // 现在,this.currentUser 将指向 cy.request() 的响应 // body,我们可以在某种方式上使用它来登录或进行其他操作 cy.visit('/') })})
虽然这种方法没有什么真正的 错误,但它确实增加了很多复杂性。您将不得不在服务器和浏览器之间同步状态 - 您始终需要在测试之前设置 / 拆卸此状态(这很慢)。
好消息是,我们不是 Selenium,也不是传统的 e2e 测试工具。这意味着我们不受相同的限制。
使用 Cypress,还有几种其他方法可以提供一个可能更好、更快的体验。
替换服务器
与其预设并与服务器通信,另一种有效的方法是完全绕过服务器。
虽然您仍将从服务器接收到所有常规的 HTML / JS / CSS 资源,并且仍将像以前一样使用 cy.visit()
,但您可以强制服务器响应 您想要的任何内容。通过这种方式,我们不仅避免了需要在服务器和浏览器之间同步状态,而且还防止了从我们的
测试中改变状态。这意味着测试不会积累可能影响其他测试的状态。
另一个优点是,这样可以使您能够 构建出您想要的应用程序,而无需服务器的 合同。您可以按照您希望数据结构的方式构建它,甚至测试所有边缘情况,而无需服务器。
然而 - 在这里可能仍然存在一种平衡,两种 策略都是有效的(您可能应该同时执行它们)。
虽然替换是很好的,但这意味着您无法保证这些响应有效载荷实际上与服务器发送的内容匹配。然而,仍然有许多有效的方法可以解决这个问题:
提前生成固定桩
您可以要求服务器提前为您生成所有固定桩。这意味着它们的数据将反映服务器实际发送的内容。
编写一个不带桩的单个 e2e 测试,然后进行桩化
另一种更平衡的方法是整合这两种策略。您可能希望有一个采用真实 e2e
方法的 单个测试,并且不对其进行桩化。它将实际使用该功能 - 包括种子数据库和设置状态。
一旦确认它可以正常工作,您可以使用桩来测试所有边缘情况和其他方案。在绝大多数情况下,使用真实数据没有任何好处。我们建议绝大多数测试使用桩数据。它们将更快速,复杂度更低。
指南:网络请求
请阅读我们关于网络请求的指南,以获得更全面的分析和实现方法。
重复使用登录代码
到目前为止,您可以将上面的登录代码复制并粘贴到每个需要经过身份验证的用户的测试中。或者,您甚至可以将所有测试放在一个大的规范文件中,并将登录代码放在 beforeEach
块中。但这两种方法都不太可维护,而且显然不太优雅。一个更好的解决方案是编写自定义 cy.login()
命令。
自定义命令允许您轻松地封装和重用 Cypress 测试逻辑。它们让您可以向测试套件添加自己的功能,并且使用与内置 Cypress 命令相同的链式和异步 API。让我们将上面的登录示例制作成一个自定义命令,并将其添加到 cypress/support/commands.js
中,以便在任何规范文件中使用:
// 在 cypress/support/commands.js 文件中Cypress.Commands.add('login', (username, password) => { cy.visit('/login') cy.get('input[name=username]').type(username) // {enter} causes the form to submit cy.get('input[name=password]').type(`${password}{enter}`, { log: false }) // we should be redirected to /dashboard cy.url().should('include', '/dashboard') // our auth cookie should be present cy.getCookie('your-session-cookie').should('exist') // UI should reflect this user being logged in cy.get('h1').should('contain', username)})// 在您的规范文件中it('在受保护页面上执行某些操作', function () { const { username, password } = this.currentUser cy.login(username, password) // ...rest of test})
提高性能
您可能想知道我们关于仅登录 “一次” 的建议发生了什么。上面的自定义命令将完美地用于测试您的受保护页面,但是如果您有超过几个测试,那么在每个测试之前登录都会增加测试套件的整体运行时间。
幸运的是,Cypress 提供了 cy.session()
命令,这是一个强大的性能工具,它允许您缓存与您的用户关联的浏览器上下文,并在多个测试中重复使用它,而无需进行多次登录流程!让我们修改前面示例中的自定义 cy.login()
命令以使用 cy.session()
:
Cypress.Commands.add('login', (username, password) => { cy.session( username, () => { cy.visit('/login') cy.get('input[name=username]').type(username) cy.get('input[name=password]').type(`${password}{enter}`, { log: false }) cy.url().should('include', '/dashboard') cy.get('h1').should('contain', username) }, { validate: () => { cy.getCookie('your-session-cookie').should('exist') }, } )})
第三方登录
如果您的应用程序实现了通过第三方身份验证提供程序(例如 Auth0 或 Okta)进行登录,您可以使用 cy.origin()
命令将它们的登录页面包含在您的身份验证测试中。
这里涉及的内容超出了本介绍的范围。请查看 cy.session()
文档以获取更深入的解释。
身份验证示例
登录可能比我们刚刚介绍的更复杂。
我们创建了几个示例,涵盖了额外的场景,比如处理 CSRF 令牌或测试基于 XHR 的登录表单。
请随时探索这些额外的登录示例。