[手把手導入專案] Cypress E2E 自動化測試:核心觀念與使用指南
Cypress 是 E2E 測試的測試工具,之所以被稱為 E2E 測試(端對端測試 End-to-end testing)在於其能撰寫腳本在最終端模擬使用者的操作行為,並確保頁面的功能符合產品設計的故事線,其他流行的 E2E 測試框架還有 Protractor、Selenium、Nightwatch…等。
Cypress 是 E2E 測試的測試工具,之所以被稱為 E2E 測試(端對端測試 End-to-end testing)在於其能撰寫腳本在最終端模擬使用者的操作行為,並確保頁面的功能符合產品設計的故事線,其他流行的 E2E 測試框架還有 Protractor、Selenium、Nightwatch…等。
※ 註:本文實際安裝的環境為 Next.js 12
一、測試的種類
關於自動化測試,有一個測試金字塔模型,該模型把測試從下到上分為了單元測試、集成測試和 UI 自動化測試(E2E 測試 / UI 界面測試)。
1. 單元測試
單元測試又稱為模塊測試,主要針對程序中最小可測試單元(一般指方法,類)的測試,具備投入小、收益產出高的特徵,可以較早期地發現代碼缺陷,適用於公共函數庫的測試。
2. 集成測試(接口自動化測試)
接口自動化主要包括模塊接口測試,子功能模塊集成起來的功能模塊測試等,目的是為了驗證在單元測試的基礎上,所有模塊集成起來的子系統、子功能是否仍然滿足質量目標。
3. UI 自動化測試(端到端 E2E 測試 )
UI 測試的主要目的是,從軟件使用者的角度來檢驗軟件的質量,而 UI 自動化測試則是以自動化的方式來代替人工執行測試。
二、快速建置 Cypress
- npm 一鍵安裝
npm install cypress --save-dev
- 執行 Cypress
npm run cypress
(要先 npm run dev)或npm run e2e
- 選擇 E2E Testing
- 選擇 Chrome
- 點選在 e2e 資料夾內撰寫好的測試腳本
去 package.json 更改執行腳步
// Before
{
...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
...
}
// After
{
...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"build-start": "next build && next start",
"lint": "next lint",
"cypress": "cypress open",
"cypress:headless": "cypress run",
"e2e": "start-server-and-test dev http://localhost:3000 cypress",
"e2e:headless": "start-server-and-test dev http://localhost:3000 cypress:headless"
},
...
}
三、Cypress 的資料夾結構和設定
Cypress 的檔案結構
.
├── Components
├── ...
├── cypress
│ ├── downloads // 放置在執行測案時下載下來的資料
│ ├── e2e // 放測試案例,以前的 integration (release in Cypress 10)
│ ├── fixture // 放假資料(靜態資料),json 檔案
│ ├── support // 自定我們想要的 Command (API)
├── pages
└── public
├── ....
└── package.json
└── cypress.config.ts // 一些設定
└── ...
- support/command.ts
...
// 可以設定,讓專案希望執行的 uncaught:exception 不被視為 Cypress 的錯誤
Cypress.on("uncaught:exception", (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false;
});
// 例如登入就是大部分的後台在進入前需要執行的動作,為了避免重複的程式碼
// Cypress 提供了自定義的功能,我們可以將一個個腳本定義為方法,並在需要使用時叫用
Cypress.Commands.add('typeLogin', (user) => {
cy.get('input[name=email]').type(user.email);
cy.get('input[name=password]').type(user.password);
});
...
之後就可以在撰寫腳本時使用如 cy.typeLogin({ email: 'fake@email.com', password: 'Secret1' });
呼叫自定義加入的方法。
- cypress.config.ts
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: "http://localhost:3000",
setupNodeEvents(on, config) {
// implement node event listeners here
},
viewportWidth: 1440,
viewportHeight: 1029,
},
env: {
apiUrl: "https://boison.tw",
},
});
可以設定 baseUrl, apiUrl 和訂定測試時要測試的螢幕寬高。
四、Cypress 測試腳本撰寫的方法
1. 範例
describe("search-agent.component.spec", () => {
beforeEach(() => {
cy.visit(
`${Cypress.env("host")}/searchAgent?identifyBy=${Cypress.env("identifyBy")}&identifyValue=${Cypress.env(
"identifyValue"
)}&chatKeepId=${Cypress.env("chatKeepId")}&department=${Cypress.env(
"department"
)}&groupShowAll=false&userName=CypressTest`
);
});
it("是否進入專員綁定頁面", () => {
cy.get(".title").should("have.length", 1);
cy.get(".sub-title").should("have.length", 1);
cy.wait(500);
cy.get(".result").children().should("have.class", "blocker");
});
it("按下綁定專員按鈕", () => {
cy.get(":nth-child(1) > .btn").click();
cy.intercept("POST", `${Cypress.env("backend_host")}/ecp/expressChat/contactBindAgent`).as("backendAPI");
cy.wait("@backendAPI").then((xhr) => {
expect(xhr.response.statusCode).to.equal(200);
});
});
});
2. 常用方法
(1). DOM 元素相關
4/ 引用 DOM 元素或網絡請求資源
cy.get().as()
// 為路由或 DOM 元素起名,之後使用時在名稱前加@
3/ 操作 DOM 元素
cy.get().click()
cy.get().dblclick()
cy.get().rightclick()
// 單擊;雙擊;右擊點擊
cy.get().type()
// 向輸入框輸入文本元素
cy.get().trigger()
// 對 DOM 元素觸發事件,如鼠標移入移出
cy.get().submit()
// 提交表單
cy.get().focus()
// 聚焦 DOM 元素
cy.get().blur()
// 使 DOM 元素失去焦點
cy.get().check()
cy.get().uncheck()
// 選中單選框;複選框;取消選取
cy.get().select()
// 用於元素
cy.get().scrollTo()
// 將 DOM 元素滾動到指定位置
cy.get().scrollIntoView()
// 將 DOM 元素滾動到可視區域
2/ 遍歷 DOM 元素
cy.get().eq()
// 通過索引從 DOM 元素列表中獲取元素
cy.get().first()
// 獲取 DOM 元素列表中的第一個元素
cy.get().last()
// 獲取 DOM 元素列表中的最後一個元素
cy.get().children()
// 獲取子元素
cy.get().parent()
// 獲取父元素
cy.get().parents()
// 獲取所有父元素,包括爺爺等
1/ 查找 DOM 元素
cy.get()
// 通過選擇器獲取元素,如類獲取到的 subject
cy.contains()
// 通過獲取內容元素,如文本獲取到的 subject
cy.get().find()
// 通過選擇器在指定元素的後代中檢索元素獲取到的 subject
cy.get().within()
// 在指定元素內部查找元素,如表單獲取到的 subject
(2). 用於連接命令
1/ 連接命令
cy.get().its()
// 獲取當前 subject 的屬性值
cy.get().invoke()
// 調用方法來操作當前 subject,比如 jQuery 中的效果
cy.get().then()
// 將上一條命令返回的結果注入到下一個命令中
(3). HTTP 請求相關
3/ 獲取 URL
cy.url()
// 獲取當前頁面的 url
cy.hash()
// 獲取當前頁面的 url 哈希值
cy.location()
// 獲取當前頁面的 window.location 對象
2/ 等待
cy.wait()
// 強制等待數毫秒或别名資源解析完成,再繼續執行下一个命令
// 等待的時間時,生成上一命令的 subject
// 等待的是别名资源时,生成的是一个包含 request 和 response 的對象
1/ 處理網絡請求
cy.request()
// 發起 HTTP 請求
cy.intercept()
// 監聽、存取和修改 request 和 response
(4). 瀏覽器相關
2/ 管理瀏覽器存储
cy.getCookie()
// 獲取指定名稱的 cookie
cy.getCookies()
// 獲取所有 cookie
cy.setCookie()
// 設置一個 cookie
cy.clearCookie()
// 清除指定名稱的 cookie
cy.clearCookies()
// 清除所有 cookie
cy.clearLocalStorage()
// 清除當前域名和子域名的所有 localStorage
1/ 訪問瀏覽器頁面
cy.visit()
// 訪問指定的 url
cy.go()
// 前進或者後退頁面
cy.reload()
// 刷新頁面
(5). Windows 相關
1/ Windows 相關命令
cy.viewport()
// 設置屏幕的大小和方向
cy.window()
// 獲取當前頁面的 window 對象
cy.document()
// 獲取當前頁面的 windowd.document 對象
cy.title()
// 獲取當前頁面的 title
(6). 斷言
1/ 斷言相關
cy.get().should()
// 聲明當前 subject
cy.get().should().and().should()
// 斷言當前 subject,並連接多個斷言
(7). 操作文件
1/ 操作文件相關
cy.fixture()
// 從 fixtures 中加載數據
cy.readFile()
// 讀取文件
cy.writeFile()
// 向文件寫入數據
(8). 其他
1/ 其他方法
cy.exec()
// 執行系統命令
cy.screenshot()
// 屏幕截圖
cy.wrap()
// 返回一个傳遞给它的對象,若是 Promise 則返回 resolved 後的值
參考資料
1. 前端自动化测试框架 cypress
2. E2E 測試框架 Cypress 教學與其他框架比較
3. What’s new in Cypress 10?
4. 常用 Cypress 命令大全
5. 使用Cypress自动化框架进行Web/API测试