Skip to main content

Cypress - 自動化測試

Cypress是一套可以執行 E2E、整合測試、單元測試的 Web 自動化測試工具。 功能特色:

  • 支援多種瀏覽器測試
  • 支援即時更新,撰寫測試時可以即時看到測試結果
  • 支援互動性測試,例如:可以模擬用戶點擊、輸入等操作

環境建立

// 安裝 cypress
npm install cypress --save-dev

// 啟動 cypress 介面
npx cypress open

撰寫測試

測試通常包含三種階段

  1. 設定應用程式狀態(Setup): 設定測試的初始狀態
  2. 採取行動(Action): 模擬用戶的互動,對應用程式執行特定的操作
  3. 對應用程式狀態結果做出斷言(Assert):驗證應用程式的行為是否符合預期
  • describe: 用於定義一個測試主題,底下會包含相關測試案例
  • beforeEach: 設定測試的初始狀態
  • it: 用於定義單獨的測試案例。裡面的 callback 定義測試的內容
  • should: 斷言,用於定義預測結果

describe 和 it 來自 Cypress 綁定的 Mocha 函式庫 expect 來自 Chai

簡單的範例如下:

describe("example to-do app", () => {
beforeEach(() => {
cy.visit("https://example.cypress.io/todo");
});

it("displays two todo items by default", () => {
// cy.get()為選擇器
cy.get(".todo-list li").should("have.length", 2);
cy.get(".todo-list li").first().should("have.text", "Pay electric bill");
cy.get(".todo-list li").last().should("have.text", "Walk the dog");
});

// context 可以將測試案例分組,以讓測試案例結構更清楚(ex:將新增單個/多個todo放在同一個context)
context("with a checked task", () => {
it("can add a new todo", () => {
const newItem = "Feed the cat";

//.type表示在輸入匡中輸入newItem內容,並模擬觸發enter鍵
cy.get("[data-test=new-todo]").type(`${newItem}{enter}`);
cy.get(".todo-list li")
.should("have.length", 3)
.last()
.should("have.text", newItem);
});
it("can add multiple new todos", () => {});
});

it("can check off an item as completed", () => {
// 除了cy.get之外,也可以用contains 來或許有特定內容的元素
// 此時會抓到 label 標籤,所以要往一層父層,再找對應的input元素中type為checkbox的元素並勾選
cy.contains("Pay electric bill")
.parent()
.find("input[type=checkbox]")
.check();

// 此時相對應的 li 上有 completed 的 css class
cy.contains("Pay electric bill")
.parents("li")
.should("have.class", "completed");
});
});

使用別名 Alias: as()

Cypress 可以將選擇的元素指定一個自定義的別名,以提高測試的可讀性。若在 beforeEach 中定義,則此命名可在不同測試案例中共用。

it("can check and uncheck items", () => {
beforeEach(() => {
// 選擇所有的待辦事項並命名為todos
cy.get(".todo-list li").as("todos");
});

// 使用自定義別名來進行操作和斷言
cy.get("@todos").first().find("input[type=checkbox]").check();
cy.get("@todos").eq(1).find("input[type=checkbox]").uncheck();
});

檢查路徑

可以使用 cy.location()驗證目前所在的路徑和相關資訊,包括:hash、host、hostname、href、origin 、pathname、port、protocol、search 等

it('should navigate to "/sign-in" when you click the "Sign In" button', () => {
cy.get('[data-test="sign-in"]').click();
cy.location("pathname").should("contain", "sign-in");
//也可以寫成下面程式碼
cy.location().should((loc) => {
expect(loc.pathname).to.include("sign-in");
});
});

Task

Task 可以執行自定義的非同步操作,例如:操作 database 、模擬後端 API 回傳等。首先,要先 plugins 中進行定義。

// reset 為重設資料庫的非同步函式
import reset from "../../prisma/reset.cjs";

const plugins: Cypress.PluginConfig = (on) => {
on("task", {
reset() {
return reset;
},
});
};

export default plugins;

定義完之後就可以在測試中使用

// 在執行測試之前都會執行 reset
beforeEach(() => cy.task("reset"));

Commands 自定義命令

可以將重複性的操作定義成 command 如下

Cypress.Commands.add("signIn", (email, password) => {
cy.get('[data-test="sign-in-email"]').type(user.email);
cy.get('[data-test="sign-in-password"]').type(user.password);
cy.get('[data-test="sign-in-submit"]').click();
});

如此,就可以這樣使用

cy.signIn({ email: "steve@example.com", password: "password" });

也很適合用作獲取資料屬性

Cypress.Commands.add("getData", (attribute) => {
return cy.get(`[data-test="${attribute}"]`);
});

//在測試案例中使用
cy.getData("sign-in-email").type(user.email);

API 請求

常見有 2 種方式處理

  1. 使用真實的 API: 直接向後端 server 發送請求,並處理真正的數據
  2. 使用 mock data: 測試攔截 API 請求,並使用 mock data 來模擬後端的 response

cy.intercept 用來模擬和控制瀏覽器對 API 請求的處理,可以攔截和修改請求。

舉例來說,下面是 mock search API 的請求

//自定義 getSearch command
//攔截 /search* 的 GET 請求,並設定 response body 的 mock data
cy.intercept("/search*", [{ item: "Book 1" }, { item: "Book 2" }]).as(
"getSearch"
);
cy.get('[data-testid="autocomplete"]').type("Book");
cy.wait("@getSearch");
cy.get('[data-testid="results"]')
.should("contain", "Book 1")
.and("contain", "Book 2");

更多關於Network requests

Cookies

cy.getCookiecy.setCookie用來讀寫 cookies。

// 將real API response 資料設至 cookies
// 如果在 beforeEach 中 設置 user token ,就不用每次都登入了
cy.request("/echo-chamber/api/users")
.then((response) => {
const [user] = response.body.users;
cy.setCookie("jwt", encodeToken(user)).then(() => user);
})
.as("user");

更多 Cypress commands

如何整合 Cypress github action

撰寫測試的原則

1. 保持簡單 Keep it simple

只測試單一功能或行為,避免在一個測試案例中包含過多的測試目標

2. 獨立性 Independence

每個測試案例應該是相互獨立的,不應該依賴其他測試的結果

3. 易讀性 readability and clarity

測試案例的名稱和描述應簡潔易懂,使用有意義的變數名稱和註釋,避免過多的巢狀結構

4. 最小斷言 One Assertion per test

每個測試案例應該包含一個主要斷言,用於驗證測試單一目標

5. 極端條件 Edge cases

測試應包含各種極端狀況,包括:極端值、異常情況和無效輸入,確保程式在不同情況下都能正常運作

6. 初始化和清理 Setup and Teardown

確保測試的初始狀態和結束狀態(清理)都是已知的,且狀態不會互相干擾

7. 持續更新 Continuous Maintenance

測試案例應隨著程式碼變更而持續更新維護

8. 可擴展性 Scalability

考量未來程式碼可能的變化,避免過多的 hard code,讓測試易於調整和擴展

參考資料