使用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安装

  1. Cypress运行需要依赖Nodejs环境,Node.js安装很简单,官方下载安装即可,建议下载安装长期维护版(LTS)

  2. 创建项目保存目录,示例目录是D:\Code\Cypress_test\UItest

  3. 进入项目目录打开cmd命令行窗口,执行命令npm init -y进行初始化操作,初始化后项目文件中会出现package.json文件,此命令会让自定义名称、版本等信息,加上-y参数是使用默认值,后续可在文件中修改

  4. 安装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	// 查询当前源配置
    
  5. 运行Cypress,每次运行都要在项目所在目录执行命令,运行命令npx cypress open,运行成功会出现Cypress窗口

    请添加图片描述

  6. 使用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模型思想,将数据、定位、页面和步骤进行拆分,实现解耦合,以登录为例

  1. 先将定位分离出来,创建locator.json文件,使用json格式定义登录的定位元素信息

    // locator.json
    {
          
          
      "login": {
          
          
        "username": "[type=\"text\"]",
        "passwd": "[type=\"password\"]",
        "submit": ".ant-btn"
      }
    }
    
  2. 然后定义页面层,创建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()
        }
    
  3. 最后定义用例层,创建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判断是否登录成功
        });
    })
    

    至此元素定位与测试步骤拆分完成,还可以继续将步骤中的测试数据进行拆分,更方便进行参数化测试

  4. 继续分离测试数据,并实现参数化,创建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","* 密码不能为空"]}}]
    }
    
  5. 修改测试用例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.jsonlogin.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"	// 运行指定的用例
生成测试报告
  1. 先安装mochawesome相关模块

    npm install --save-dev mochawesome mochawesome-merge mochawesome-report-generator
    
  2. cypress.json文件中添加以下信息

    {
          
          
      "reporter": "mochawesome",
      "reporterOptions": {
          
          
        "reportDir": "cypress/results",
        "overwrite": false,
        "html": false,
        "json": true
      }
    }
    
  3. 生成测试报告

    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()或定义公共函数的方法保存数据给后面到的接口使用,

  1. 使用.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"})
            })
    })
    

    请添加图片描述

  2. 使用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
            })
        });
    })
    
  3. 定义公共函数,生成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"})
        });
    })
    
接口参数化
  1. 使用数组做参数化,创建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)
                })
            });
        }
    })
    
  2. 使用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官方文档还是蛮详细的,其它功能请自行探索吧!

猜你喜欢

转载自blog.csdn.net/Q0717168/article/details/123617411