使用Cypress自动化框架进行Web/API测试
@
Cypress介绍
Cypress是基于JavaScript语言的前端自动化测试工具,无需借助外部工具,自集成了一套完整的端到端测试方法,可以对浏览器中运行的所有内容进行快速、简单、可靠的测试,并且可以进行接口测试
Cypress特点
- 时间穿梭:Cypress会在测试运行时拍摄快照。只需将鼠标悬停在命令日志上,即可清楚了解每一步都发生了什么
- 可调试性:无需揣测测试失败原因。直接使用浏览器的DevTools进行调试。清晰的错误原因和堆栈跟踪让调试能够更加快速便捷
- 实时重载:每次对测试进行更改,Cypress都会实时执行新的命令,自动重新加载页面进行测试
- 自动等待:无需在测试中添加等待。在执行下一条命令或断言前Cypress会自动等待元素加载完成,异步操作不再是问题
- 间谍,存根和时钟:Cypress允许验证并控制函数行为,Mock服务器响应或更改系统时间,更便于进行单元测试
- 网络流量控制:Cypress可以Mock服务器返回结果,无须连接后端服务器即可实现轻松控制,模拟网络请求
- 运行结果一致性:Cypress架构不使用Selenium或Webdriver,在运行速度、可靠性、测试结果一致性上均有良好的保障
- 截图和视频:Cypress在测试运行失败时自动截图,在使用命令运行时录制整个测试套件的视频,轻松掌握测试运行情况
Cypress运行原理
Cypress测试代码和被测程序都运行在由Cypress全权控制的浏览器中,它们是运行在同一个域下的不同框架内,所以Cypress的测试代码可以直接操作DOM,也正如此Cypress相对于其它测试工具可以运行的更快,在开始执行Cypress脚本后它会自动运行浏览器,并将编写的代码注入到一个空白页,然后在浏览器中运行代码
在进行接口或数据库测试时,需要向服务端发送请求,此请求由Cypress生成,发送给Node.js Process,由Node.js转发给服务端,因此Cypress不仅可以修改进出浏览器的所有内容,还可以更改可能影响自动化操作浏览器的代码,所以Cypress能够从根本上控制自动化测试的流程,提高了稳定性,使得到测试结果更加可靠,如下图所示
Cypress安装
-
Cypress运行需要依赖Nodejs环境,Node.js安装很简单,官方下载安装即可,建议下载安装长期维护版(LTS)
-
创建项目保存目录,示例目录是
D:\Code\Cypress_test\UItest
-
进入项目目录打开cmd命令行窗口,执行命令
npm init -y
进行初始化操作,初始化后项目文件中会出现package.json
文件,此命令会让自定义名称、版本等信息,加上-y
参数是使用默认值,后续可在文件中修改 -
安装Cypress,此处临时使用了淘宝npm源,推荐使用,官方的下载太慢啦
npm install cypress --save-dev --registry=https://registry.npmmirror.com // 临时使用淘宝npm源
也可以直接修改默认的npm源,修改命令如下
npm config set registry https://registry.npmjs.org // 设置修改配置 npm get registry // 查询当前源配置
-
运行Cypress,每次运行都要在项目所在目录执行命令,运行命令
npx cypress open
,运行成功会出现Cypress窗口 -
使用IDE工具打开项目目录,默认测试用例是在
cypress/integration
下编写,其中的两个示例文件前期不建议删除,供学习使用
Cypress使用
Web页面测试
元素定位方法
Cypress更推荐使用Cypress专有选择器,更稳定,但是需要前端代码支持,尽管id、name、class等方法都是Cypress不推荐的,但目前元素定位还是依它们方式为主
cy.get("[data-cy=submit]").click() // Cypress专有选择器,是Cypress推荐的,但是需要前端代码支持
cy.get("[data-test=submit]").click() // Cypress专有选择器
cy.get("[data-testid=submit]").click() // Cypress专有选择器
cy.contains("Submit").click() // 通过搜索文本定位
cy.find("Submit").click() // 通过搜索文本定位
cy.get("[name=submission]").click() // 通过name定位
cy.get("#main").click() // 通过id选择器定位
cy.get(".btn.btn-large").click() // 通过class选择器定位
cy.get("button").click() // 通过标签选择器定位
cy.get("button[id=\"main\"]").click() // 通过标签+属性方式定位
cy.get("[data-row-key]>:nth-child(9)>:nth-child(5)").click() // 通过:nth-child()选择器定位
还可以通过辅助方法定位元素,如下
cy.get(".btn-large").first() // 匹配找到的第一个元素
cy.get(".btn-large").last() // 匹配找到的最后一个元素
cy.get(".btn-large").children() // 获取DOM元素的所有子元素
cy.get(".btn-large").parents() // 获取DOM元素的所有父元素
cy.get(".btn-large").parent() // 获取上级的第一层父元素
cy.get(".btn-large").siblings() // 获取所有同级元素(即兄弟元素)
cy.get(".btn-large").next() // 匹配当前定位元素的下一个同级元素
cy.get(".btn-large").nextAll() // 匹配当前定位元素之后的所有同级元素
cy.get(".btn-large").nextUntil()// 匹配当前定位元素之后的所有同级元素,直到出现Until中定义的元素为止
cy.get(".btn-large").prev() // 与next()相反,匹配当前定位元素的上一个同级元素
cy.get(".btn-large").prevAll() // 与nextAll()相反,匹配当前定位元素之前的所有同级元素
cy.get(".btn-large").prevUntil()// 与nextUntil()相反,匹配当前定位元素之前的所有同级元素,直到出现Until中定义的元素为止
cy.get(".btn-large").each() // 遍历所有子元素
也可以在Cypress运行的浏览器窗口定位元素,可以做参考,不推荐直接复制定位信息
元素常用操作
更多操作命令及使用方法查看官方介绍吧
cy.screenshot() // 截图
cy.viewport(550, 750) // 设置窗口大小
cy.visit("https://www.baidu.com/") // 访问百度
cy.visit("https://www.baidu.com/").reload() // 重新加载百度页面
cy.go("back").go("forward") // 页面后退、前进操作
cy.get("[type=\"text\"]").type("JavaScript") // 在当前定位元素输入JavaScript
cy.get("[type=\"text\"]").type("123{enter}") // 在当前定位元素输入点击Enter键
cy.get("[type=\"text\"]").clear() // 清空当前定位元素的信息
cy.get("button").click() // 单击定位元素
cy.get("button").dbclick() // 双击定位元素
cy.get("[type="checkbox"]").check() // 勾选全部复选框
cy.get("[type="checkbox"]").uncheck() // 取消勾选全部复选框
cy.get("[type="radio"]").first().check() // 选中单选框第一个值
cy.get("[type="radio"]").check("CN") // 选中value为CN的单选框
cy.get("#saveUserName").check() // 勾选id为saveUserName的元素
cy.get("select").select("下拉选项的值") // 下拉框选择一个
cy.get("select").select(["value1","value2"]) // 下拉框选择多个
cy.get("title").should("have.text","Halo").and("contain","仪表盘") // 通常使用should做断言,它可链接多个断言,更易读,也可使用expect
cy.get("title").then(($title)=> { // ↓获取元素对应的属性值(即文本信息)
let Txt = $title.text() // 定义一个变量,将获取的title信息赋值给Txt
cy.log(Txt)}) // 打印日志、打印返回结果
示例演示
新建一个js文件,编写一个简单的登录脚本,然后打开Cypress窗口,点击文件名就开始自动运行浏览器并进行测试啦,脚本每次修改都会自动运行,若不想运行某个用例,可以使用it.skip()
表示,只想运行某条用例则使用it.only()
表示
// halo_login.js
it("输入正确的账号和密码,应登录成功", function () {
cy.visit("/login") // 访问路径,baseUrl已在cypress.json文件中做配置
cy.get("[type=\"text\"]").type("admin") // 定位并输入登录账号
// cy.get("[type=\"password\"]").type("admin123") // 定位并输入登录密码
// cy.get(".ant-btn").click() // 点击【登录】按钮
cy.get("[type=\"password\"]").type("admin123{enter}") // 输入密码后可通过点击Enter键登录
cy.url().should("include", "/dashboard") // 通过获取URL地址判断登录成功
cy.get("title").should("have.text", "仪表盘 - Halo") // 也可通过获取网页标题判断登录成功
})
参数化测试
使用describe
命令,类似于创建了一个套件,用例在测试套件中编写,使用forEach
遍历数据,进而实现参数化,before
表示在测试用例运行前中执行一次
// param.js
describe("参数化测试搜索功能",function () {
before("先登录成功",function (){ // 前置条件为登录成功
cy.visit("http://192.166.66.24:8090/admin/index.html#/login") // 访问路径
cy.get("[placeholder="用户名/邮箱"]").type("admin") // 定位并输入登录账号
cy.get("[type=\"password\"]").type("admin123{enter}") // 输入密码后可通过点击Enter键登录
cy.visit("/posts/list") // 进入文章列表
});
["test","java","python","JavaScript"].forEach((INFO) => { // 遍历列表中的数据
it("搜索" + INFO, () => { // 名称为搜索与参数的组合
cy.get(".ant-form-item-children>.ant-input").type(INFO) // 获取定位搜索框并输入输入参数
cy.get("[style=\"margin-right: 8px;\"]>.ant-btn").click() // 点击【查询】按钮
cy.get(".ant-form-item-children>.ant-input").clear() // 每次搜索后清空输入框
})
})
})
业务流测试
如下示例,是一个完整的业务流测试,具体步骤含义已做注释
// halo_login.js
describe("文章管理业务流测试",function (){
before("此处是前置操作,当前模块下执行一次!",function (){
cy.log("****** 开始测试文章管理模块喽! ******")
cy.visit("/login") // 访问路径,baseUrl已在cypress.json文件中做配置
cy.get("[type=\"text\"]").type("admin") // 定位并输入登录账号
cy.get("[type=\"password\"]").type("admin123{enter}") // 输入密码后点击Enter键登录
cy.url().should("include", "/dashboard") // 通过获取URL地址判断登录成功
cy.get("title").should("have.text", "仪表盘 - Halo") // 也可通过获取网页标题判断登录成功
cy.visit("/posts/list") // 进入文章列表
})
after("此处是后置操作,当前模块下执行一次!",function (){
cy.log("****** 文章管理模块用例执行完毕! ******")
})
it("查看文章列表", function () {
// 获取文章列表字段,应有“标题状态分类标签评论访问发布时间操作”,使用have.text时,文本内容必须一致,是相等关系
cy.get(".ant-table-column-title").should("have.text", "标题状态分类标签评论访问发布时间操作")
});
it("写文章并保存为草稿", function () {
cy.get("a > .ant-btn").click() // 点击【+写文章】按钮
cy.get("[placeholder=\"请输入文章标题\"]").type("寄黄几复") // 定位并输入文章标题
cy.get(".CodeMirror-line").type("桃李春风一杯酒,江湖夜雨十年灯。") // 定位并输入文章内容
cy.get(".ant-space-item").children(".ant-btn-primary").click() // 点击【发布】按钮
cy.get(".ant-btn-danger").click().should("have.text", "保存成功") // 点击【保存草稿】按钮,应提示“保存成功”
cy.get(".no-underline").first().should("have.text", " 寄黄几复 ") // 获取文章列表应显示新增的草稿文章
});
it("发布文章", function () {
cy.get("[data-row-key]>:nth-child(9)>:nth-child(5)").first().click() // 点击【设置】
cy.get(".ant-modal-footer>:nth-child(3)").click().should("have.text", "保存成功") // 点击【转为发布】
cy.get(".ant-modal-footer>:nth-child(5)").click() // 关闭设置窗口
cy.get("[style=\"margin-right: 8px;\"]>.ant-btn").click() // 刷新页面
cy.get(".ant-badge-status-text").first().should("have.text", "已发布") // 验证文章状态为“已发布”
});
it("文章移到回收站并删除", function () {
cy.get("[data-row-key]>:nth-child(9)>:nth-child(3)").first().click() // 删除第一条文章
cy.get(".ant-popover-buttons>.ant-btn-primary").as("OK").click() // 为元素设置别名,点击确认删除
// 通过获取提示信息判断删除成功
cy.get(".ant-message-notice-content").as("Tips").should("have.text", "操作成功!")
cy.get(".mb-5>.ant-space>:nth-child(2)>.ant-btn").click() // 进入回收站
cy.get("[data-row-key]>:nth-child(7)>:nth-child(3)").first().click() // 删除回收站第一条文章
cy.get("@OK").click() // 使用元素别名,确认删除
cy.get("@Tips").should("have.text", "删除成功!") // 使用元素别名,通过获取提示信息判断删除成功
// 检查回收站列表不应包含已删除文章
cy.get(".ant-table-row-cell-ellipsis").should("not.contain.text", " 寄黄几复 ")
cy.get(".ant-modal-footer>.ant-btn").click() // 关闭回收站窗口
})
})
// 下面的示例是结合上文before用法,介绍以下berfeEach的用法
describe("页面管理",function (){
beforeEach("此处也是前置操作,与上文的before不同的,在每条用例前都会执行一次!",function (){
cy.log("~~~~~~ 开始执行新的用例!~~~~~~")
cy.visit("/login")
cy.get("[type=\"text\"]").type("admin")
cy.get("[type=\"password\"]").type("admin123{enter}")
})
afterEach("此处也是后操作,与上文的after不同的,在每条用例后都会执行一次!",function (){
cy.log("~~~~~~ 此用例执行完毕!~~~~~~")
})
it("查看独立页面",function (){
cy.visit("/sheets/list")
cy.get(".ant-table-column-title").should("have.text","页面名称访问地址状态操作")
cy.wait(4000).log("固定等待4s,否者报“访问过于频繁,请稍后再试!”")
});
it("查看新建页面", function () {
cy.get("[aria-label=\"图标: read\"]").click() // 点击【页面】主菜单
cy.contains("新建页面").click() // 点击【新建页面】子菜单
cy.get(".ant-page-header-heading-title").should("have.text","新页面")
});
})
运行结果如下图所示

使用PO模型
通过上面示例可以看出,大量的定位元素和数据都耦合到整个测试步骤中,会增加后期维护难度,所以尽可能拆分出来,结合PO模型思想,将数据、定位、页面和步骤进行拆分,实现解耦合,以登录为例
-
先将定位分离出来,创建
locator.json
文件,使用json格式定义登录的定位元素信息// locator.json { "login": { "username": "[type=\"text\"]", "passwd": "[type=\"password\"]", "submit": ".ant-btn" } }
-
然后定义页面层,创建
login_page.js
文件,封装页面对象及业务流程// login_page.js import locator from "./data/locator.json" // 导入定位信息文件 export default class Login_page { // 导出class类 constructor() { // 使用构造方法定义URL this.url = "http://192.166.66.24:8090/admin/index.html#/login" } // 封装页面对象 visit(){ cy.visit(this.url) } get username(){ return cy.get(locator.login.username) } get passwd(){ return cy.get(locator.login.passwd) } get submit(){ return cy.get(locator.login.submit) } // 封装登录业务流 loginhalo(user,pwd){ if(user !== ""){ this.username.type(user) } if(pwd !== ""){ this.passwd.type(pwd) } this.submit.click() }
-
最后定义用例层,创建
login_case.js
文件,编写测试用例// login_case.js describe("登录测试",function (){ it("输入正确的账号密码,登录成功", function () { let login = new Login_page() // 定义一个对象 login.visit() // 打开URL login.loginhalo("admin","admin123") // 输入账号密码 cy.url().should("include", "/dashboard") // 根据url判断是否登录成功 }); })
至此元素定位与测试步骤拆分完成,还可以继续将步骤中的测试数据进行拆分,更方便进行参数化测试
-
继续分离测试数据,并实现参数化,创建
login.json
文件,定义登录信息及对应的断言// login.json { "success": [{ "name": "输入正确的账号和密码,应登录成功", "username": "admin", "password": "admin123", "validate": { "checkpoint": ["url","include","/dashboard"]}}], "fail": [{ "name": "输入错误的账号和密码,应提示“用户名或者密码不正确”", "username": "admin", "password": "123456", "validate": {"checkpoint": [".ant-message-custom-content>span","contain","用户名或者密码不正确"]}}, { "name": "输入登录密码,账号为空,应提示“用户名不能为空”", "username": "", "password": "123456", "validate": {"checkpoint": [".ant-form-explain","contain","* 用户名/邮箱不能为空"]}}, { "name": "输入用户名,密码为空,应提示“密码不能为空”", "username": "admin", "password": "", "validate": {"checkpoint": [".ant-form-explain","contain","* 密码不能为空"]}}] }
-
修改测试用例
login_case.js
文件,代码如下import data from "./data/login.json" // 导入登录信息文件 import Login_page from "./login_page" // 导入login_page文件 describe("登录功能验证", function (){ beforeEach(function (){ // 配置前置条件 let loginHL = new Login_page() loginHL.visit() cy.wrap(loginHL).as("testlogin") // 返回传递给loginHL的对象,使用as命令设置别名,方便在测试用例中引用 }) afterEach(function (){ // 配置后置条件 cy.wait(4000) // 因测试平台限制不能短时间内连续登录,故设置每次登录间隔时间为4秒钟 }) data.success.forEach(item => { // 遍历login.json文件中success下的数据 it(item.name,function () { this.testlogin.loginhalo(item.username,item.password) //获取账号密码后登录 cy.url().should(item.validate.checkpoint[1],item.validate.checkpoint[2]) // 断言结果 }) }) data.fail.forEach(item => { // 遍历login.json文件中fail下的数据 it(item.name, function () { this.testlogin.loginhalo(item.username,item.password) cy.get(item.validate.checkpoint[0]).should(item.validate.checkpoint[1],item.validate.checkpoint[2]) }) }) })
至此实现数据、定位、页面对象和测试用例实现分离,当定位信息和数据发生变化时,只需修改
locator.json
和login.json
两个文件中的json数据,下图是运行结果
命令运行测试用例
使用命令行运行会自动保存视频,视频保存在cypress/integration/videos/
目录下,如果存在失败的用例,则同时会保存失败截图,截图保存cypress/integration/screenshots/
目录下
npx cypress run // 运行integration目录下所有用例
npx cypress run --browser chrome // 指定浏览器运行integration目录下所有用例
npx cypress run --spec "cypress/integration/HL_login.js" // 运行指定的用例
生成测试报告
-
先安装mochawesome相关模块
npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator
-
在
cypress.json
文件中添加以下信息{ "reporter": "mochawesome", "reporterOptions": { "reportDir": "cypress/results", "overwrite": false, "html": false, "json": true } }
-
生成测试报告
npx cypress run --reporter mochawesome // 运行integration目录下所有测试用例并生成报告所需数据 npx cypress run --reporter mochawesome --spec "cypress/integration/HL_login.js" // 运行指定用例并生成所需数据 npx mochawesome-merge "cypress/results/*.json" > mochawesome.json // 将生成的数据合并到一起并生成整合报告 // 最终报告HTML报告生成在mochawesome-report目录下 cd cypress/results // 如果要生成指定用例的报告,可以执行"运行指定用例生成数据"的命令后,进入results目录下 npx marge mochawesome001.json // 选择刚刚生成的测试数据,生成报告
报告结果如下图所示
也可以使用JUnit/Allure生成报告,具体看官网介绍吧!
API接口测试
语法
在Cypress中发起HTTP请求需使用cy.request()
,语法如下
cy.request(method,url,headers,body)
单接口
如下示例登录接口测试
it("登录接口", function () {
cy.request({ // 发起接口请求
method:"post", // 请求方式
url:"http://192.166.66.24:8090/api/admin/login", // 请求地址,url可使用baseUrl配置到cypress.json文件中
body:{"username": "admin","password": "admin123","authcode": null} // 请求体
}).then(response =>{ // 两种断言方式,一种是使用then获取响应数据,然后进行断言
expect(response.status).to.be.equal(200)
}).its("body").should("contain",{"status":200,"message":"OK"}) // 另一种断言方式是使用its获取响应结果进行断言
})
接口关联
在接口自动化中肯定会有参数关联的情况,例如登录成功获取的token给后面的接口使用,在cypress中可以使用.as()、sessionStorage.setItem()或定义公共函数的方法保存数据给后面到的接口使用,
-
使用
.as()
方法,只能在同一个用例下使用,示例如下it("查看管理文章列表", function () { cy.request({ // 先登录 method:"post", url:"/api/admin/login", body:{"username": "admin","password": "admin123","authcode": null} }) .its("body.data.access_token").as("token") // 登录成功后获取token值并设置别名“token” .then(function (){ cy.log(this.token) // 打印token,调试时多使用log cy.request({ // 查看文章管理列表 method:"get", url:"/api/admin/posts", headers:{"Content-Type": "application/json","Admin-Authorization":this.token} // 调用token }).its("body").should("contain",{"status":200,"message":"OK"}) }) })
-
使用
sessionStorage.setItem
设置token,其它接口用例都可以调用,更推荐此方式,有利于后面做接口自动化describe("接口测试",function (){ it('登录成功,并提取token给其它的接口使用', function () { cy.request({ method:"post", url:"/api/admin/login", body:{"username": "admin","password": "admin123","authcode": null} }) .its("body.data.access_token").as("token") // 提取token值并设置别名为“token” .then(function (){ cy.wrap(sessionStorage.setItem("Token",this.token)) // 使用sessionStorage.setItem设置token }) }); it('查看文章管理列表', function () { const token = sessionStorage.getItem("Token") // 提取sessionStorage中的Token并赋值给token cy.request({ method:"get", url:"/api/admin/posts", headers: {"Content-Type": "application/json","Admin-Authorization":token} // 调用token }) }); })
-
定义公共函数,生成token,供其它接口调用
// Token.js 文件名 export default class { generateToken(){ cy.request({ method:"post", url:"http://192.166.66.24:8090/api/admin/login", body:{"username": "admin","password": "admin123","authcode": null} }).then(resp=>{ cy.wrap(resp.body.data.access_token).as("token") }) } }
编写用例时导入定义的公共函数,就可以使用token啦,示例如下
import Token from "./Token" // 导入定义公共函数的文件 describe("文章管理->增删改查操作", function () { before( function () { let token = new Token() // 测试前先获取token token.generateToken() }) it("查看文章管理列表", function () { cy.request({ method: "get", url: "/api/admin/posts", headers: {"Admin-Authorization": this.token} }).its("body").should("contain",{"status":200,"message":"OK"}) }); it("发布文章", function () { cy.request({ method:"post", url:"/api/admin/posts", headers: {"Content-Type": "application/json", "Admin-Authorization": this.token}, //调用token body:{"title":"test321","content":"<p>皮之不存,毛将焉附。</p>","status":"PUBLISHED"} }).its("body.data.id").as("articleID").then(function (){ cy.wrap(sessionStorage.setItem("ID",this.articleID)) }) }); it("将文章放到回收站", function () { let artId = sessionStorage.getItem("ID") cy.request({ method:"put", url:"/api/admin/posts/"+artId+"/status/RECYCLE", headers: {"Content-Type": "application/json", "Admin-Authorization": this.token}, //调用token }).its("body").should("contain",{"status":200,"message":"OK"}) }); it("从回收站删除", function () { let deleteArtId = sessionStorage.getItem("ID") cy.request({ method:"delete", url:"/api/admin/posts", headers: {"Content-Type": "application/json", "Admin-Authorization": this.token}, //调用token body:[deleteArtId] }).its("body").should("contain",{"status":200,"message":"OK"}) }); })
接口参数化
-
使用数组做参数化,创建
param_API.js
文件// param_API.js import Token from "./Token" // 导入Token.json describe("查看列表并发布文章",function () { before(function () { // 前置条件,先获取token let token = new Token() token.generateToken() }) let testdatas = [ // 测试数据 { "casename": "查看文章管理列表", "url": "/api/admin/posts", "method": "get", "headers": {"Content-Type": "application/json"}, "body": "", "status": 200, "message":"OK" }, { "casename": "发布文章", "url": "/api/admin/posts", "method":"post", "headers": {"Content-Type": "application/json"}, "body":{"title":"yadian","content":"<p>皮之不存,毛将焉附。</p>","status":"PUBLISHED"}, "status": 200, "message":"OK" } ] for (const data in testdatas) { // 遍历测试数据进行测试 it(`${testdatas[data].casename}`, function () { let url = testdatas[data].url let method = testdatas[data].method let header = testdatas[data].headers let body = testdatas[data].body let status = testdatas[data].status let message = testdatas[data].message cy.request({url: url, method: method, headers: {header,"Admin-Authorization": this.token}, body: body}).then(function (resp) { // 断言,判断状态码和响应信息是否正确 expect(resp.status).to.eq(status) expect(resp.body.message).to.eq(message) }) }); } })
-
使用JSON文件做参数化
也可以将数据单独分离出来,使用json文件做参数化,创建
testdata.json
文件,保存测试数据,如下// testdata.json [ { "casename": "查看文章管理列表", "url": "/api/admin/posts", "method": "get", "headers": {"Content-Type": "application/json"}, "body": "", "status": 200, "message":"OK" }, { "casename": "发布文章", "url": "/api/admin/posts", "method":"post", "headers": {"Content-Type": "application/json"}, "body":{"title":"yadian","content":"<p>皮之不存,毛将焉附。</p>","status":"PUBLISHED"}, "status": 200, "message":"OK" } ]
在用例脚本导入数据即可使用,修改
param_API.js
文件// param_API.js import Token from "./Token" // 导入Token.json import testdatas from "./testdata.json" // 导入数据文件testdata.json describe("查看列表并发布文章",function () { before(function () { // 前置条件,先获取token let token = new Token() token.generateToken() }) for (const data in testdatas) { // 遍历测试数据进行测试 it(`${testdatas[data].casename}`, function () { let url = testdatas[data].url let method = testdatas[data].method let header = testdatas[data].headers let body = testdatas[data].body let status = testdatas[data].status let message = testdatas[data].message cy.request({url: url, method: method, headers: {header,"Admin-Authorization": this.token}, body: body}).then(function (resp) { // 断言,判断状态码和响应信息是否正确 expect(resp.status).to.eq(status) expect(resp.body.message).to.eq(message) }) }); } })
其它
对于Web页面测试,Cypress是支持录制功能的,但是不推荐使用,一些可变元素可能会出现在录制脚本中导致回放失败,写此文章时该功能处于试验阶段,因此默认是隐藏的,需要自行开启,不排除后续平台放弃此功能,开启方法:在cypress.json
文件中添加以下信息
{"experimentalStudio": true}
开启后页面就会出现录制入口啦!如下图演示:

对于非Cypress造成的报错,报uncaught:exception
,此时用例无法完成,可以先忽略应用程序的报错。忽略方法:打开support目录下的index.js
文件,添加以下忽略命令
// 忽略所有uncaught:exception异常
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
// 若不想忽略所有异常,可忽略指定条件的异常,添加以下信息
Cypress.on('uncaught:exception', (err, runnable) => {
if (err.message.includes('HaloRestAPIError')) { // 指定的异常报错,比如HaloRestAPIError
return false
}
})
这个Cypress官方文档还是蛮详细的,其它功能请自行探索吧!