Cypress 测试框架

🍰 Cypress是一款开源的基于NodeJS的JavaScript自动化测试框架,专为端到端(E2E)测试设计,用于简化Web应用的测试自动化。

1 Cypress简介

1-1 Node.js

1
2
3
4
5
6
# 安装好Node.js,命令确认是否安装成功
node -v
v22.14.0

npm -v
10.9.2

1-2 权限设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 若使用VSCode或PowerShell进行操作,则需确认权限是否给到
# 查看当前命令终端权限
get-executionpolicy
Restricted

# 显示为Restricted,则限制,需授权
# 否则使用VSCode或PowerShell操作时,可能报错提示
# 无法加载文件...\nodejs\npm.ps1,因为在此系统上禁止运行脚本

# npm.ps1是npm在PowerShell中运行时的启动脚本
# PowerShell的执行策略阻止了npm.ps1脚本的运行

# 仅对当前会话设置执行策略:推荐,风险最小,每次重启都需执行一次
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

# 为当前用户永久更改执行策略:风险中等
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# 为本地计算机设置执行策略:不推荐,风险较高
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

1-3 安装Cypress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 安装Cypress,创建一个新的文件夹,命名为Cypress
mkdir Cypress

# 进入Cypress文件夹
cd Cypress

# 快速初始化Node.js项目,生成一个package.json文件
npm init -y

Wrote to ...\Cypress\package.json:
{
"name": "cypress",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}

# 安装Cypress及其依赖,--save-dev使Cypress作为dev dependency被安装
npm install cypress --save-dev

added 175 packages, and audited 176 packages in 3m

40 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

1-4 启动Cypress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 命令启动Cypress【完整路径启动】
./node_modules/.bin/cypress open

It looks like this is your first time using Cypress: 14.2.1
✔ Verified Cypress! C:\Users\...\Cypress\Cache\14.2.1\Cypress
Opening Cypress...
DevTools listening on ws://127.0.0.1:54830/devtools/browser/...

# 命令启动Cypress【npm命令启动】
# ①将Cypress添加到package.json的scripts中
{
"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run"
}
}
# ②然后使用npm命令启动Cypress
npm run cypress:open

> cypress@1.0.0 cypress:open
> cypress open
DevTools listening on ws://127.0.0.1:55024/devtools/browser/...

# 命令启动Cypress【npx命令启动】,npm版本需要高于5.2
npx cypress open

1-5 配置Cypress

  • 配置Cypress
    • 在“Welcome to Cypress!”界面,选择“E2E Testing”,默认勾选配置并继续。
    • 选浏览器,这里使用Edge,Start E2E Testing in Edge,弹窗Edge的Cypress主页。
    • 在Edge的Cypress主页,选择“Scaffold example specs”,生成一些示例测试脚本。
1
2
3
4
5
6
7
8
9
# Cypress目录结构介绍,可以在VSCode安装Cypress时创建的Cypress文件夹里查看
cypress: # 核心目录,包含所有测试相关文件(启动命令后自动生成)
e2e: # 存放测试用例
fixtures: # 存储测试用例的外部静态数据
support: # 存放辅助文件,如命令、配置等
node_modules: # 存储Cypress和其他npm依赖项的文件
- cypress.config.js # 全局配置文件,设置Cypress的运行参数
- package.json # 定义Cypress相关依赖和运行脚本
- package-lock.json # 记录项目中所有已安装依赖项的精确版本

2 测试工作原理

  • 测试工作原理
    • 用户与应用交互后,测试脚本向Cypress Runner发送了指令,Cypress Runner与代理服务器通信。
    • 代理向应用服务器发送请求,应用服务器处理请求并返回响应,Cypress Runner记录快照和视频。

测试工作原理

3 编写测试用例

  • 编写测试用例
    • 测试文件存放:/cypress/e2e/目录中,这里新建文件命名为sampleTest.cy.js
    • 测试文件类型:JavaScript类型,测试文件后缀名必须是.cy.{js,jsx,ts,tsx}
1
2
3
4
5
6
describe("Cypress测试", () => {
it("访问网站并验证标题", () => {
cy.visit("https://www.baidu.com/");
cy.title().should("include", "百度一下");
});
});

4 运行测试用例

  • 运行测试用例
    • 点击测试文件,图形化界面的Cypress测试运行器将直接运行选中的测试文件并展示结果。
    • 或命令执行测试文件:npx cypress run --spec "./cypress/e2e/sampleTest.cy.js"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
npx cypress run --spec "./cypress/e2e/sampleTest.cy.js"

DevTools listening on ws://127.0.0.1:53250/devtools/browser/...
>
=============================================================================

(Run Starting)

┌─────────────────────────────────────────────────────────────────────────┐
│ Cypress: 14.2.1
│ Browser: Electron 130 (headless) │
│ Node Version: v22.14.0 (...\nodejs\node.exe) │
│ Specs: 1 found (sampleTest.cy.js) │
│ Searched: ...\Cypress\cypress\e2e\sampleTest.cy.js │
└─────────────────────────────────────────────────────────────────────────┘


─────────────────────────────────────────────────────────────────────────────
Running: sampleTest.cy.js (1 of 1)

Cypress测试
√ 访问网站并验证标题 (1848ms)

1 passing (4s)

(Results)

┌─────────────────────────────────────────────────────────────────────────┐
│ Tests: 1
│ Passing: 1
│ Failing: 0
│ Pending: 0
│ Skipped: 0
│ Screenshots: 0
│ Video: false │
│ Duration: 3 seconds │
│ Spec Ran: sampleTest.cy.js │
└─────────────────────────────────────────────────────────────────────────┘

=============================================================================
(Run Finished)

Spec Tests Passing Failing Pending Skipped
┌─────────────────────────────────────────────────────────────────────────┐
│ √ sampleTest.cy.js 00:03 1 1 - - - │
└─────────────────────────────────────────────────────────────────────────┘
√ All specs passed! 00:03 1 1 - - -

4-1 通配符执行

1
2
3
4
5
# 执行e2e目录下的所有测试文件
npx cypress run --spec "cypress/e2e/**/*.cy.js"

# 执行多个特定的测试文件,逗号分隔,注意逗号前后不要带空格
npx cypress run --spec "cypress/e2e/sampleTest.cy.js,cypress/e2e/1-getting-started/todo.cy.js"

4-2 运行脚本执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 修改package.json文件配置
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"cypress:open": "cypress open",
"cypress:run": "cypress run",

"cy:run:all": "cypress run --spec 'cypress/e2e/**/*.cy.js'",
"cy:run:astart": "cypress run --spec 'cypress/e2e/2-advanced-examples/a*.cy.js'",
"cy:run:spectest": "cypress run --spec 'cypress/e2e/sampleTest.cy.js'"
}
}

# 运行所有测试文件
npm run cy:run:all

# 运行“a”开头的测试文件
npm run cy:run:astart

# 运行指定的测试文件
npm run cy:run:spectest

4-3 默认执行所有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用cypress.config.js的specPattern默认行为
# 不指定--spec选项,则Cypress将运行所有测试文件
npx cypress run

# 添加specPattern值,可用于指定需要执行的测试文件
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {
# 修改该值,再重新执行命令“npx cypress run”
specPattern: "cypress/e2e/sampleTest.cy.js",
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

5 生成测试报告

  • 生成测试报告
    • JUnit Reporter:适用于将测试结果集成到CI/CD系统中,生成JUnit xml标准格式的报告。
    • Mochawesome:适用于生成美观并且易于阅读的HTML测试报告,适合手动分析测试结果。
    • Cypress-multi-reporters:适用于需要多种格式的报告,例如同时生成HTML和JUnit报告。

5-1 JUnit Reporter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 安装JUnit Reporter报告生成器
npm install --save-dev mocha-junit-reporter

// 配置cypress.config.js文件
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
reporter: "mocha-junit-reporter",
reporterOptions: {
mochaFile: "cypress/reports/junit/results-[hash].xml",
toConsole: true,
},
},
});

// 执行命令
npx cypress run

5-2 Mochawesome

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 安装Mochawesome报告生成器
npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator

// 配置cypress.config.js文件
const { defineConfig } = require("cypress");
const mochawesome = require("mochawesome");

module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on("after:spec", (spec, results) => {
if (results && results.video) {
const hasResults =
Array.isArray(results.results) &&
results.results.some(testResult => {
return Array.isArray(testResult.passes) &&
testResult.passes.length > 0;
});
if (!hasResults) {
fs.unlinkSync(results.video);
}
}
});

on(
"before:browser:launch",
(browser = {}, launchOptions) => {
if (
browser.name === "chrome" || browser.name === "edge"
) {
launchOptions.args.push("--disable-dev-shm-usage")
}
return launchOptions
}
)

on("after:run", (results) => {
if (results) {
const marge = require("mochawesome-merge")
const generator = require("mochawesome-report-generator")

return marge.merge({
files: [
"cypress/reports/mochawesome/*.json"
]
}).then(
report => generator.create(
report,
{
reportDir: "cypress/reports/mochawesome/"
}
)
)
}
})

on("task", {
log(message) {
console.log(message)
return null
},
table(message) {
console.table(message)
return null
},
})
},
reporter: "mochawesome",
reporterOptions: {
reportDir: "cypress/reports/mochawesome/",
overwrite: false,
html: true,
json: true,
},
},
});

// 执行命令
npx cypress run

5-3 Cypress-multi-reporters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 安装Cypress-multi-reporters报告生成器
npm install --save-dev cypress-multi-reporters mocha-junit-reporter mochawesome

// 配置cypress.config.js文件
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
reporter: "cypress-multi-reporters",
reporterOptions: {
reporterEnabled: "mochawesome, mocha-junit-reporter",
mochawesomeReporterOptions: {
reportDir: "cypress/reports/multi/mochawesome",
overwrite: false,
html: true,
json: true,
},
mochaJunitReporterReporterOptions: {
mochaFile: "cypress/reports/multi/junit/results-[hash].xml",
},
},
},
});

// 执行命令
npx cypress run

6 Cypress测试器

  • Cypress测试器
    • Cypress测试器中,鼠标移动到用例的任意行并悬停,可回溯到该命令行的执行时状态。
    • 单击测试用例的任意行,则可将其固定,左侧会显示一个紫高亮的固定图标,方便截图。

Cypress测试器

6-1 事件触发

  • 事件触发
    • 在Cypress测试器中,click()动作命令被固定时,右侧界面的type处会显示一个红点。
    • 红点代表此处有事件触发,底部可选before或after来查看动作命令执行前后的页面区别。

Cypress测试器-事件触发

6-2 报错信息

  • 报错信息
    • 执行测试用例时发生错误,Cypress将提供以下信息。
    • 错误名称:显示错误的类型,例如CypressError、AssertionError等。
    • 错误信息:显示错误的具体信息,可能还提供修复方法。
    • Learn more:点击此项可以打开相关的Cypress文档页面。
    • Print to console:点击此项可将完整的错误信息打印到DevTools中。
    • View stack trace:查看堆栈轨迹。

Cypress测试器-报错信息

6-3 控制台输出

  • 控制台输出
    • Cypress可以在控制台中提供额外的调试信息,F12打开浏览器的DevTools。
    • 选中Get选择器命令行将其固定,在DevTools的Console中可看到额外信息。
      • Command:触发的命令。
      • Selector:使用的参数。
      • Yielded:命令返回的内容。
      • Elements:找到的元素数量。
    • 如果返回了多个元素,则可以在DevTools中展开以查看每个元素的具体信息。

Cypress测试器-控制台输出

7 Cypress的用法

  • Cypress的用法
    • 测试构成:Given(设定当前状态)、When(执行某操作)、Then(断言状态变更)。
    • Cypress还提供了调试使用的特殊命令,例如:cy.pause()cy.debug()等。
    • Cypress使用链式语法,当涉及页面加载时,等待加载完毕才继续执行后续步骤。
      • 单个测试套件describe()可以包含多个测试用例it()
      • 单个测试套件describe()可嵌套测试套件describe()
      • 单个测试用例it()还能包含多个平行或顺序断言should()
    • Cypress记录的页面事件:网络XHR的请求、URL Hash的变更、页面加载、表单提交。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
describe("Cypress的用法", () => {
it("测试构成 && 调试 && 输出日志", () => {
// Given:设定
cy.visit("https://www.baidu.com/");

// 输出日志
cy.log("访问百度首页");

// 调试:特殊命令
// cy.debug();

// When:执行
cy.get("#kw").click();
cy.log("点击了搜索框");

// 调试:特殊命令
cy.pause();

// Then:断言
cy.title().should("include", "百度一下");
cy.log("验证页面标题");
});
});

7-1 context()

1
2
3
4
5
6
7
8
9
10
// context()是describe()的别名,行为方式一致
context("Cypress的用法", () => {
it("context()", () => {
cy.visit("https://www.baidu.com/");
cy.log("访问百度首页");

cy.title().should("include", "百度一下");
cy.log("验证页面标题");
});
});

7-2 钩子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
describe("Cypress的用法", () => {
// 在当前测试套件的第一个测试用例中仅调用一次
before(() => {
cy.log("before()")
});

// 在当前测试套件中的所有测试用例(包括子测试套件)使用前调用
beforeEach(() => {
cy.log("beforeEach()")
});

// 在当前测试套件中的所有测试用例(包括子测试套件)完成后调用
afterEach(() => {
cy.log("afterEach()")
});

// 在当前测试套件的最后一个测试用例(包括子测试套件)中仅调用一次
after(() => {
cy.log("after()")
});

it("Hook钩子函数1", () => {
cy.log("打印日志1");
});

context("Hook钩子函数2", () => {
it("二级测试套件", () => {
cy.log("打印日志2")
});
});
});

7-3 获取元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
describe("Cypress的用法", () => {
it("获取元素", () => {
cy.visit("https://www.baidu.com/");
// cy.get(selector):在DOM树中查找selector对应的DOM元素
cy.get("#kw");

// cy.contains(content):获取包含指定文本的DOM元素
cy.contains("#kw");

// 辅助方法
// cy.get(selector).parents()···获取所有父元素
// cy.get(selector).parent()····获取第一层父元素
// cy.get(selector).children()··获取所有子元素
// cy.get(selector).siblings()··获取所有同级元素
// cy.get(selector).first()·····返回第一个元素
// cy.get(selector).last()······返回最后一个元素
// cy.get(selector).nextUntil(selector)
// 获取后面紧跟的所有同级元素,直到下一个指定的元素
// cy.get(selector).prevUntil()
// 获取前面紧跟的所有同级元素,直到下一个指定的元素
// cy.get(selector).next()······获取下一个同级元素
// cy.get(selector).prev()······获取上一个同级元素
// cy.get(selector).nextAll()···获取后面所有的同级元素
// cy.get(selector).prevAll()···获取前面所有的同级元素
// cy.get(selector).closest()···获取与选择器匹配的第一个父元素
// cy.get(selector).eq()········获取特定索引处的元素
// cy.get(selector).each(($element) => {});
// 遍历数据或类似结构,对象包含length属性即可

cy.pause();
});
});

7-4 断言方式

  • 断言方式
    • 重试(Retry-ability)是Cypress的核心概念之一,即断言失败后自动重新查询。
    • 重试有条件限制,当命令可能改变被测应用程序的状态时,该命令将不会重试。
    • Cypress仅会重试一些查询DOM的命令,例如:cy.get()cy.contains()等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
describe("Cypress的用法", () => {
it("断言方式", () => {
cy.visit("https://www.baidu.com/");

// 值:have.value,eq()从0开始选择特定索引的元素
cy.get("input").eq(5).should("have.value", "baidu");

// 类:not.have.class,验证表单元素是否没有类“楷书”
cy.get("form").should("not.have.class", "楷书");

// 长度:have.length,确认搜索输入框的数量是否为1
cy.get("#kw").should("have.length", 1);

// 文本内容:not.contain,确认搜索框不包含特定文本
cy.get("#kw").should("not.contain", "测试文本");

// 元素是否可见:be.visible,确认搜索框可见
cy.get("#kw").should("be.visible");

// 元素是否存在:exist,确认搜索框存在
cy.get("#kw").should("exist");

// 元素状态:be.checked
// 确认搜索框未被选中,一般用于checkbox、radio
cy.get("#kw").should("not.be.checked");

// css:have.css,确认搜索框的CSS属性包含border属性值
cy.get("#kw").should("have.css", "border");

// 回调函数
cy.get("#kw").should(($input) => {
// 断言长度
expect($input).to.have.length(1);
// 确认元素是输入框
expect($input).to.have.prop("tagName", "INPUT");
// 确认是否具有特定的类
expect($input).to.have.class("s_ipt");
});
});
});

7-5 点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe("Cypress的用法", () => {
it("点击事件", () => {
cy.visit("https://www.baidu.com/");

// 单击搜索框并输入文字
cy.get("#kw").click().type("Cypress");

// 双击搜索框
cy.get("#kw").dblclick();

// 右键点击搜索按钮
cy.get("#su").rightclick();
});
});

7-6 页面操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
describe("Cypress的用法", () => {
it("页面操作", () => {
cy.visit("https://www.baidu.com/");

// 在搜索框输入文本
cy.get("#kw").type("Cypress自动化测试");

// 强制聚焦搜索框,即使已默认聚焦
cy.get("#kw").focus();

// 输入后让搜索框失去焦点
cy.get("#kw").type("文本").blur();

// 清空已输入的内容
cy.get("#kw").type("文本").clear();

// 提交搜索表单
cy.get("#form").submit();

// 模拟鼠标悬停在“设置”上,可能触发下拉菜单
cy.get("#s-usersetting-top").trigger("mouseover");
cy.contains("搜索设置").click();

// 进入高级搜索
cy.get("[data-tabid='advanced']").click();

// 滚动到页面底部
cy.scrollTo("bottom");

// 滚动到指定坐标
cy.scrollTo(0, 500);
});
});

Cypress 测试框架
https://stitch-top.github.io/2025/04/16/ce-shi-kuang-jia/tf08-cypress/tf01-cypress-ce-shi-kuang-jia/
作者
Dr.626
发布于
2025年4月16日 15:50:00
许可协议