[手把手導入專案] Cypress E2E 自動化測試:核心觀念與使用指南

Cypress 是 E2E 測試的測試工具,之所以被稱為 E2E 測試(端對端測試 End-to-end testing)在於其能撰寫腳本在最終端模擬使用者的操作行為,並確保頁面的功能符合產品設計的故事線,其他流行的 E2E 測試框架還有 Protractor、Selenium、Nightwatch…等。

[手把手導入專案] Cypress E2E 自動化測試:核心觀念與使用指南
Photo by sydney Rae / Unsplash

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测试