使用Cypress自动化框架进行Web/API测试
文章目录
- Cypress介绍
- Cypress特点
- Cypress运行原理
- Cypress安装
- Cypress使用
- Web页面测试
- 元素定位方法
- 元素常用操作
- 示例演示
- 参数化测试
- 使用PO模型
- 命令运行测试用例
- 生成测试报告
- 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信息赋值给Txtcy.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() { // 使用构造方法定义URLthis.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() // 打开URLlogin.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,调试时多使用logcy.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并赋值给tokency.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() // 测试前先获取tokentoken.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}, //调用tokenbody:{"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}, //调用tokenbody:[deleteArtId]}).its("body").should("contain",{"status":200,"message":"OK"})}); })
接口参数化
使用数组做参数化,创建
param_API.js
文件// param_API.js import Token from "./Token" // 导入Token.jsondescribe("查看列表并发布文章",function () {before(function () { // 前置条件,先获取tokenlet 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].urllet method = testdatas[data].methodlet header = testdatas[data].headerslet body = testdatas[data].bodylet status = testdatas[data].statuslet message = testdatas[data].messagecy.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.jsondescribe("查看列表并发布文章",function () {before(function () { // 前置条件,先获取tokenlet token = new Token()token.generateToken()})for (const data in testdatas) { // 遍历测试数据进行测试it(`${testdatas[data].casename}`, function () {let url = testdatas[data].urllet method = testdatas[data].methodlet header = testdatas[data].headerslet body = testdatas[data].bodylet status = testdatas[data].statuslet message = testdatas[data].messagecy.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')) { // 指定的异常报错,比如HaloRestAPIErrorreturn false}
})
这个Cypress官方文档还是蛮详细的,其它功能请自行探索吧!
使用Cypress自动化框架进行Web/API测试相关推荐
- 如何编写干净流畅的Web API测试
前言 当我们为Web API编写测试用例时,代码基本是这样的: public class UnitTest1 {private readonly TestServer _server;private ...
- python 收发微信之二:获取微信上行信息(利用 flask 框架实现 Web API,获取 WxPusher 上行微信)
目 录 〇.摘要 一.前言 二.实操 1. 找一台可以给互联网提供服务的计算机 2. 实现一个 flask 最小服务并在互联网上访问到 3. 根据 WxPusher 上行信息接口实现 POST 命令的 ...
- 用java构建企业级自动化框架(首篇-制定测试者使用语言1)
这个是我后来写的一本书,http://www.ituring.com.cn/minibook/10775.这个是我后来找到的自动化完美解决方案. 首先我们谈论下Automation语言组织管理,因为一 ...
- 测试自动化框架的重要性– iSAFE的优势
测试自动化并不像某些人想象的那样容易或经济. 仅应在必要和适用的情况下使用.而且,最重要的是,它不能替代手动测试.相反,它补充了手动测试. 自动化框架和正确的测试自动化工具对于增强测试自动化过程至关重 ...
- Web API应用架构在Winform混合框架中的应用(3)--Winfrom界面调用WebAPI的过程分解...
最近一直在整合WebAPI.Winform界面.手机短信.微信公众号.企业号等功能,希望把它构建成一个大的应用平台,把我所有的产品线完美连接起来,同时也在探索.攻克更多的技术问题,并抽空写写博客,把相 ...
- 推荐几个好用的API测试工具?我保证你一定会喜欢的
目录 1.Rest-Assured 2.Postman 3.SoapUI 4.JMeter 5.Fiddler 分享一下我认为不错的五种API测试工具,无论哪种方式,它们都是不错的选择. 1.Rest ...
- API测试基础知识(基本概念、测试方法、测试工具)
目录 什么是API? 什么是API测试 API测试的测试用例: API测试方法: 如何进行API测试 API测试的最佳做法: API测试检测到的错误类型 API测试工具 API测试的挑战 结论: 总结 ...
- 如何选择API测试工具
没有最好,只有最合适. 如今,越来越多的公司正在向DevOps的方向左转,以实现持续集成和持续部署开发.这意味着我们的反馈需要比以往更快,以便确定我们的应用程序是否准备好交付.这就是API测试如此重要 ...
- 使用VS 2019,.NET Core 3和Web API创建ASP.NET Core Blazor CRUD应用程序
目录 介绍 Blazor Blazor客户端应用程序 Blazor服务器应用程序 背景 先决条件 使用代码 第1步-创建数据库和表 第2步-创建ASP.NET Core Blazor服务器应用程序 运 ...
最新文章
- 【百度地图API】百度API卫星图使用方法和卫星图对比工具
- vs编译idl文件_Unity中 .asmdef文件的作用
- Linux下多线程同步方式之互斥量,信号量,条件变量
- AIProCon在线大会笔记之Google李双峰:TensorFlow的最新进展
- 前端:HTML5/36/HTML5简介,文档类型定义,网页字符集,页面结构标记,文章相关的标记,其它标记,音频标记,视频标记,表单中新增的属性,表单input元素type属性的值
- pytorch torch.empty
- Win 2008 r2 安装SSH服务器
- iphonexr电池容量_xr电池容量多少毫安,iphonexr换电池多少钱
- 电力拖动计算机控制系统讲什么,电力拖动自动控制系统
- Svn内外网切换技巧
- opencv-python:17_图像经典边缘检测算子(边缘检测、图像梯度、Roberts算子、Prewitt算子、Sobel 算子、Laplacian 算子、Canny算子、算子优缺点对比)
- POI解析docx与doc文档中的难点归纳
- mysql timediff函数极限值
- NR-PRACH接受端如何检测出preambleid和TA的
- su [user] 和 su - [user]的区别
- Python 办公小助手:读取 PDF 中表格并重命名
- Liferay7开发系列(二)环境搭建
- 线上剧本杀平台值得做吗?来看看剧本杀的行业现状
- Cisco思科交换机WS-C2960X-24TD-L使用ftp上传更新丢失IOS方法
- 使用计算机先按ON键,计算器的开机键是什么关机键是什么