使用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官方文檔還是蠻詳細的,其它功能請自行探索吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM