Cypress是基于JavaScript语言的前端自动化测试工具,无需借助外部工具,自集成了一套完整的端到端测试方法,可以对浏览器中运行的所有内容进行快速、简单、可靠的测试,并且可以进行接口测试
Cypress测试代码和被测程序都运行在由Cypress全权控制的浏览器中,它们是运行在同一个域下的不同框架内,所以Cypress的测试代码可以直接操作DOM,也正如此Cypress相对于其它测试工具可以运行的更快,在开始执行Cypress脚本后它会自动运行浏览器,并将编写的代码注入到一个空白页,然后在浏览器中运行代码
在进行接口或数据库测试时,需要向服务端发送请求,此请求由Cypress生成,发送给Node.js Process,由Node.js转发给服务端,因此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更推荐使用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模型思想,将数据、定位、页面和步骤进行拆分,实现解耦合,以登录为例
先将定位分离出来,创建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生成报告,具体看官网介绍吧!
在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":"皮之不存,毛将焉附。
","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":"皮之不存,毛将焉附。
","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":"皮之不存,毛将焉附。
","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官方文档还是蛮详细的,其它功能请自行探索吧!