测试你的应用
你将学习到
- 如何启动服务器并使用Cypress测试你的应用
- 如何配置Cypress
- 测试应用的策略,包括数据播种、服务器桩模块和登录
第一步:启动你的服务器
假设你已经成功 安装Cypress并在项目中 打开Cypress, 首先需要启动托管应用的本地开发服务器。
服务器地址通常类似于 **http://localhost:8080**。
不要尝试从Cypress脚本中启动web服务器。阅读 最佳实践了解更多。
为什么要启动本地开发服务器?
你可能会疑惑——为什么不能直接测试已经部署到生产环境的应用程序?
虽然你确实可以测试已部署的应用,但这并不是Cypress的最佳使用场景。
Cypress是为日常本地开发而构建和优化的工具。事实上,在使用Cypress一段时间后,你可能会发现它甚至能用于整个开发过程。
最终,你将不仅能同时进行开发和测试,还能在获得"免费"测试的同时更快地构建应用。
更重要的是——由于Cypress支持模拟网络请求,你甚至可以在不需要服务器提供有效JSON响应的情况下构建web应用。
最后但同样重要的是——试图为已构建的应用添加测试比边写测试边开发要困难得多。你可能会遇到一系列前期挑战/障碍,而这些在从一开始就编写测试时可以避免。
最重要的一点是,测试本地服务器能让你控制它们。当应用在生产环境运行时,你无法控制它。
但在开发环境中,你可以:
- 使用快捷方式
- 通过运行脚本播种数据
- 暴露测试环境特定的路由
- 禁用使自动化困难的安全功能
- 重置服务器/数据库状态
尽管如此——你仍然可以选择两者兼顾。
许多用户主要针对本地开发服务器运行大部分集成测试,同时保留一小部分冒烟测试仅针对部署的生产应用运行。
第二步:访问你的服务器
服务器启动后,就可以访问它了。
让我们删除之前教程中创建的spec.cy.js
文件,因为它不再需要。
rm cypress/e2e/spec.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.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()
命令添加此前缀。
每当修改配置文件,Cypress会自动重启并关闭所有打开的浏览器。这是正常现象。再次点击测试文件重新启动浏览器。
现在可以访问相对路径,省略主机名和端口。
describe('首页', () => {
it('成功加载', () => {
cy.visit('/')
})
})
很好 !一切应该仍然是绿色的。
Cypress还有许多其他配置选项可用于自定义其行为。比如测试文件位置、默认超时时间、环境变量、使用的报告器等。
查看配置了解更多!
测试策略
你即将开始为应用编写测试,只有你了解自己的应用,所以我们无法给出太多具体建议。
测试什么、边缘案例和接缝在哪里、可能遇到哪些回归等问题完全取决于你、你的应用和你的团队。
也就是说,现代web测试有一些常见问题,以下是你可能遇到的快速提示。
数据播种
根据应用的构建方式——你的web应用很可能会受到服务器的影响和控制。
通常现在服务器通过JSON与前端应用通信,但你也可能在运行传统的服务器端渲染HTML web应用。
通常服务器负责发送反映其状态的响应——通常存储在数据库中。
传统上在使用Selenium编写e2e
测试时,在自动化浏览器之前会对服务器进行设置和拆卸。
可能需要生成用户,并为其添加关联和记录。你可能熟悉使用fixtures或factories。
要测试各种页面状态——如空视图或分页视图——需要为服务器播种数据以便测试这些状态。
虽然这种策略还有很多内容,但你通常有三种方法在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')
// 在数据库中播种一个我们可以控制的帖子
cy.request('POST', '/test/seed/post', {
title: '第一篇帖子',
authorId: 1,
body: '...',
})
// 在数据库中播种一个我们可以控制的用户
cy.request('POST', '/test/seed/user', { name: 'Jane' })
.its('body')
.as('currentUser')
})
it('成功加载', () => {
// this.currentUser现在指向cy.request()的响应体
// 可以用来登录或以某种方式使用
cy.visit('/')
})
})
虽然这种方法没有真正的问题,但它确实增加了复杂性。你需要在服务器和浏览器之间同步状态——并且总是需要在测试前设置/拆卸这些状态(这很慢)。
好消息是我们不是Selenium,也不是传统的e2e测试工具。这意味着我们不受相同限制的约束。
使用Cypress,有几种其他方法可以提供更好的体验。
服务器桩模块
另一种有效的方法是绕过服务器。
虽然你仍然会从服务器接收所有常规的HTML/JS/CSS资源,并继续以相同方式cy.visit()
它——但你可以模拟来自服务器的JSON响应。
这意味着不需要重置数据库或播种所需状态,你可以强制服务器返回任何你想要的数据。这样,我们不仅避免了在服务器和浏览器之间同步状态的需要,还防止了测试改变状态。这意味着测试不会积累可能影响其他测试的状态。
另一个好处是这使你能够构建应用而无需服务器的_契约_存在。你可以按照你希望的数据结构构建它,甚至测试所有边缘案例,而无需服务器。
然而— —这里可能需要两种策略的平衡(你可能应该都做)。
虽然模拟很好,但它意味着这些响应负载实际上与服务器发送的内容不匹配。不过,仍有几种有效的方法可以解决这个问题:
提前生成模拟数据
可以让服务器提前为你生成所有模拟数据。这意味着它们的数据将反映服务器实际发送的内容。
编写一个不使用模拟的e2e测试,然后模拟其余部分
另一种更平衡的方法是整合两种策略。你可能想要有一个单一测试采用真正的e2e
方法且不使用模拟。它将真实使用功能——包括播种数据库和设置状态。
一旦确认它正常工作,就可以使用模拟来测试所有边缘案例和其他场景。在绝大多数情况下使用真实数据没有好处。我们建议绝大多数测试使用模拟数据。它们将快几个数量级,且复杂度低得多。
请阅读我们的网络请求指南以获取更全面的分析和实现方法。
登录
测试中首先要克服的(也可以说是最困难的)障碍之一就是登录应用。
没有什么比必须登录更能拖慢测试套件了,但应用中所有好的部分很可能都需要认证用户!以下是一些提示。
完整测试登录流程——但只测一次!
将注册和登录流程纳入测试覆盖是个好主意,因为它对所有用户都非常重要,你绝不希望它出问题。
登录是关键任务功能之一,应该涉及你的服务器。我们建议你像真实用户一样通过UI测试注册和登录:
以下是一个结合数据库播种的例子:
describe('登录页面', () => {
beforeEach(() => {
// 每次测试前重置并播种数据库
cy.exec('npm run db:reset && npm run db:seed')
// 在数据库中播种一个我们可以控制的用户
// 假设它为我们生成随机密码
cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
.its('body')
.as('currentUser')
})
it('通过表单提交登录时设置认证cookie', function () {
// 解构this.currentUser对象
const { username, password } = this.currentUser
cy.visit('/login')
cy.get('input[name=username]').type(username)
// {enter}导致表单提交
cy.get('input[name=password]').type(`${password}{enter}`)
// 应该被重定向到/dashboard
cy.url().should('include', '/dashboard')
// 认证cookie应该存在
cy.getCookie('your-session-cookie').should('exist')
// UI应反映该用户已登录
cy.get('h1').should('contain', 'jane.lane')
})
})
你可能还想测试登录UI的以下情况:
- 无效用户名/密码
- 用户名已存在
- 密码复杂度要求
- 账户锁定/删除等边缘案例
每种情况都可能需要完整的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}导致表单提交
cy.get('input[name=password]').type(`${password}{enter}`, { log: false })
// 应该被重定向到/dashboard
cy.url().should('include', '/dashboard')
// 认证cookie应该存在
cy.getCookie('your-session-cookie').should('exist')
// UI应反映该用户已登录
cy.get('h1').should('contain', username)
})
// 在你的测试文件中
it('在安全页面上执行某些操作', function () {
const { username, password } = this.currentUser
cy.login(username, password)
// ...测试的其余部分
})
提高性能
你可能想知道我们关于"只登录一次"的建议怎么样了。上面的自定义命令对测试安全页面很有效,但如果有多个测试,每个测试前都登录会增加整个套件的运行时间。
幸运的是,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()
文档获取更深入的解释。
开始吧
好了,讨论到此为止。现在就开始测试你的应用吧!
从这里你可能想探索更多指南:
- Cypress API 学习可用的命令
- Cypress简介 解释Cypress_真正_的 工作原理
- 命令行 通过
cypress run
在外部运行所有测试 - 持续集成 在CI中运行Cypress
- 跨浏览器测试 在CI中跨Firefox和Chrome系列浏览器优化运行测试
- 真实世界应用(RWA) 真实项目中Cypress测试实践、配置和策略的演示。