






















今天在 GitHub Trending 上看到一个有意思的项目:Cypress,这是一款专为现代 Web 应用打造的前端测试工具。不同于传统的 Selenium 等测试框架,Cypress 从底层重新思考了测试的运行方式,让 Web 测试变得快速、简单且可靠。
Cypress 是一个下一代前端测试工具,专为现代 Web 构建。它的核心理念是:“Web 已经进化,测试工具也终于跟上了。"(The web has evolved. Finally, testing has too.)
核心特性:
解决的问题:
传统测试工具(如 Selenium)存在诸多痛点:
Cypress 通过架构创新解决了这些问题,它不使用 WebDriver,而是直接与浏览器交互。
Cypress 的架构与传统测试工具完全不同。从源码的 package.json 可以看出,它采用了 Lerna Monorepo 架构管理多个包:
"workspaces": {
"packages": [
"cli",
"packages/*",
"npm/*",
"tooling/*",
"system-tests",
"scripts"
]
}
核心架构分层:
从 package.json 的 devDependencies 可以看出技术选型:
运行时环境:
开发语言:
测试框架:
构建工具:
GraphQL 集成:
// apollo.config.js
module.exports = {
client: {
service: {
name: 'cypress-io',
localSchemaFile: path.join(__dirname, 'packages/data-context/schemas/schema.graphql'),
},
tagName: 'gql',
includes: [
'packages/{launchpad,app,frontend-shared}/src/**/*.{vue,ts,js,tsx,jsx}'
],
},
}
Cypress 内部使用 GraphQL 进行数据管理,通过 Apollo Client 与后端 API 交互。
1. 异步命令队列
Cypress 所有命令都是异步的,但通过链式调用和内部命令队列实现"同步式"编写:
cy.get('.submit').click() // 自动等待元素出现
cy.get('.input').type('hello') // 自动等待输入框可交互
cy.contains('Submit').should('be.visible') // 自动重试断言
源码中通过 cy 对象的命令封装,实现了自动等待和重试机制。
2. 网络代理
Cypress 通过内置的代理服务器拦截所有网络请求:
// 从 package.json scripts 可以看出
"cypress:run": "cypress run --dev"
在测试运行时,Cypress 会启动一个代理服务器(@packages/proxy),所有 fetch 和 XHR 请求都会被拦截,从而实现:
3. V8 Snapshot 优化
从构建脚本可以看出:
"build-v8-snapshot-prod": "node --max-old-space-size=8192 tooling/v8-snapshot/scripts/setup-v8-snapshot-in-cypress.js"
Cypress 使用 V8 Snapshot 技术预编译 JavaScript 代码,大幅提升启动速度。
测试执行的数据流:
用户命令 (cypress open/run)
↓
CLI 解析参数,启动 Node Server
↓
Node Server 启动 Electron 浏览器
↓
浏览器加载 Cypress Driver(注入到测试页面)
↓
Driver 执行测试代码,通过 WebSocket 与 Node Server 通信
↓
Node Server 控制浏览器行为(访问 URL、截图、录制等)
↓
测试结果实时反馈到 Test Runner UI
package.json 的 engines 字段可知)方式一:npm
npm install cypress --save-dev
方式二:yarn
方式三:pnpm
pnpm add cypress --save-dev
安装完成后,Cypress 二进制文件会自动下载。如果下载失败,可以设置镜像:
# 使用淘宝镜像
export CYPRESS_DOWNLOAD_MIRROR=https://registry.npmmirror.com/-/binary/cypress
1. 打开 Cypress Test Runner
首次运行会初始化项目结构:
cypress/
├── e2e/ # 端到端测试文件
├── fixtures/ # 测试数据
├── support/ # 全局配置和辅助函数
└── tsconfig.json # TypeScript 配置
2. 编写第一个测试
创建 cypress/e2e/sample.cy.js:
describe('My First Test', () => {
it('Visits the Kitchen Sink', () => {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
cy.url().should('include', '/commands/actions')
cy.get('.action-email')
.type('[email protected]')
.should('have.value', '[email protected]')
})
})
3. 运行测试
# 打开 Test Runner(交互式)
npx cypress open
# 或 Headless 模式运行
npx cypress run
1. 元素选择与交互
// 通过 CSS 选择器获取元素
cy.get('.submit-button').click()
// 通过文本内容获取元素
cy.contains('Submit').click()
// 通过 data-cy 属性(推荐)
cy.get('[data-cy=submit]').click()
2. 断言
Cypress 集成了 Chai、Sinon 和 Mocha,支持 BDD 和 TDD 断言风格:
// 隐式断言
cy.get('.todo-list li').should('have.length', 2)
// 显式断言
expect(2 + 2).to.equal(4)
// 链式断言
cy.get('.error').should('be.red')
.and('contain', 'Error message')
3. 网络请求 Mock
// 拦截并模拟 API 响应
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [{ id: 1, name: 'John' }],
}).as('getUsers')
// 等待请求完成
cy.wait('@getUsers').then((interception) => {
expect(interception.response.statusCode).to.equal(200)
})
1. 自定义命令
在 cypress/support/commands.js 中定义全局命令:
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login')
cy.get('[data-cy=username]').type(username)
cy.get('[data-cy=password]').type(password)
cy.get('[data-cy=submit]').click()
})
然后在测试中使用:
cy.login('testuser', 'password123')
2. 组件测试
Cypress 10+ 支持组件测试(Component Testing):
import React from 'react'
import { mount } from 'cypress/react'
describe('MyComponent', () => {
it('renders correctly', () => {
mount(<MyComponent title="Hello" />)
cy.contains('Hello').should('be.visible')
})
})
3. 插件系统
Cypress 提供丰富的插件生态,例如:
// cypress.config.js
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
setupNodeEvents(on, config) {
// 安装插件
require('@cypress/code-coverage/task')(on, config)
return config
},
},
})
场景:测试登录功能
describe('Login Flow', () => {
beforeEach(() => {
// 每个测试前访问登录页
cy.visit('/login')
})
it('should display error with invalid credentials', () => {
cy.get('[data-cy=username]').type('wronguser')
cy.get('[data-cy=password]').type('wrongpass')
cy.get('[data-cy=submit]').click()
// 验证错误消息
cy.get('.error-message')
.should('be.visible')
.and('contain', 'Invalid credentials')
})
it('should redirect to dashboard after successful login', () => {
cy.get('[data-cy=username]').type('validuser')
cy.get('[data-cy=password]').type('validpass')
cy.get('[data-cy=submit]').click()
// 验证跳转
cy.url().should('include', '/dashboard')
cy.get('[data-cy=welcome]').should('contain', 'Welcome back!')
})
})
问题:Cypress 二进制文件下载失败
错误信息:
Error: read ECONNRESET
解决方案:
export CYPRESS_DOWNLOAD_MIRROR=https://registry.npmmirror.com/-/binary/cypress
npm install cypress --save-dev
# 下载对应版本的 zip 文件
export CYPRESS_INSTALL_BINARY=/path/to/cypress.zip
npm install cypress --save-dev
问题:元素未找到(Element not found)
解决方案:
cy.get('.slow-loading-element', { timeout: 10000 }).should('be.visible')
cy.wait() 等待特定条件:cy.intercept('GET', '/api/data').as('getData')
cy.wait('@getData') // 等待网络请求完成
cy.get('.data-table').should('be.visible')
问题:跨域错误(CORS)
解决方案:
在 cypress.config.js 中配置:
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
chromeWebSecurity: false, // 禁用 Web Security(仅开发环境)
},
})
问题:测试运行缓慢
解决方案:
cypress run --parallel --record --key <record-key>
使用 Cypress Cloud 的智能调度
优化测试代码:
cy.wait()cy.session() 缓存登录状态// 缓存登录状态
Cypress.Commands.add('loginBySession', () => {
cy.session('user-session', () => {
cy.request({
method: 'POST',
url: '/api/login',
body: { username: 'test', password: 'test' },
}).then((response) => {
window.localStorage.setItem('token', response.body.token)
})
})
})
问题:与旧版浏览器不兼容
Cypress 只支持现代浏览器(Chrome、Firefox、Edge、Electron)。
解决方案:
对于需要测试 IE 的场景,可以:
Cypress 通过架构创新和技术优化,为现代 Web 应用测试带来了革命性的体验。其核心优势包括:
从 Cypress 的源码可以看出,它不仅是一个测试工具,更是一个精心设计的技术产品:
如果你正在寻找一款现代、高效、易用的 Web 测试工具,Cypress 绝对值得尝试。它不仅能够提升测试效率,还能改善整个开发团队的协作流程。
相关资源:
本文基于 Cypress 最新源码分析,感谢开源社区的贡献!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。