Skip to content

Cypress E2E 測試

在 Angular 專案中,E2E 測試 (End-to-End testing) 可使用 Cypress 實現,透過模擬使用者實際操作流程,確保整體應用系統的功能完整性。

套件介紹

在本文中,我們介紹了以下套件及其用途:

  • Cypress:主要的 E2E 測試框架,提供強大的 UI 測試介面與除錯工具,並與 TypeScript 整合良好。
  • @types/cypress:為 Cypress 提供 TypeScript 型別支援,提升開發體驗。
  • mochawesome:報告生成工具,與 Cypress 整合後可產生 HTML 和 JSON 格式的測試報告。
  • cypress-mochawesome-reporter:用於生成包含截圖與影片的詳細測試報告,方便分析測試結果。

延伸套件

以下套件可作為進一步提升測試報告功能的延伸項目:

  • mochawesome-merge:用於合併多個測試報告,生成統一的測試結果。適合大型專案中需要整合多次測試結果的情境,尤其是在手動執行多次測試後需要生成一份完整報告的情況。
  • 使用範例:執行多次 npx cypress run 後,手動合併所有測試結果。
  • 指令:

    mochawesome-merge cypress/reports/*.json > cypress/reports/merged-report.json
    

  • mochawesome-report-generator:生成最終的 HTML 測試報告,支持嵌入截圖與影片。適合需要更高可視化效果的測試報告需求,尤其是在手動生成報告以供分享或存檔的情況。

  • 使用範例:基於合併的 JSON 測試結果生成 HTML 報告。
  • 指令:
    mochawesome-report-generator cypress/reports/merged-report.json
    

這些延伸項目可根據專案需求選擇性使用,進一步提升測試報告的整合性與可視化效果,特別適合手動執行測試並整理報告的情境。

安裝 Cypress

npm install --save-dev cypress 

如果希望支援 TypeScript,需額外設定型別:

npm install --save-dev @types/cypress

設定 Cypress

建立目錄

專案根目錄下建立 cypresscypress/e2e 等目錄:

cypress/                # Cypress 根目錄
  e2e/                  # 放置測試檔案的目錄
    login.cy.ts           # 登入測試範例檔案
  support/              # 放置共用函式或 hook
    commands.ts           # 自訂共用函式
    e2e.ts                # e2e 初始化程式檔案 (載入個項套件)
  fixtures/             # 放置測試用的模擬資料檔案
cypress.config.ts       # Cypress 配置檔案

補充說明: 你也可以透過執行以下指令來自動產生 Cypress 所需的預設目錄與範例檔案:

npx cypress open

Cypress 第一次啟動時會建立 cypress/ 目錄,並包含:

  • e2e/:放置測試檔案
  • support/:放置共用函式或 hook
  • fixtures/:模擬資料檔案
  • 自動產生 cypress.config.tscypress.config.js

建立配置檔

若要使用共用函式,需指定 supportFilecypress/support/e2e.ts,例如:

import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.ts'
  }
});

⚠️ 注意: 若你要使用共用命令或初始化程式碼(如自訂命令、beforeEach),請確保 supportFile 有正確指定。

撰寫測試

// filepath: cypress/e12e/login.cy.ts
/// <reference types="cypress" />

describe('使用者登入測試', () => {
  it('應成功登入並導向首頁', () => {
    cy.visit('/#/login');

    cy.get('input[formcontrolname="userId"]').type('yourUserId');
    cy.get('input[formcontrolname="userPwd"]').type('yourPassword');
    cy.contains('button', '登入').click();

    cy.url().should('include', '/home');
    cy.contains('登錄子系統').should('be.visible');
  });
});

/// <reference types="cypress" /> 的作用是告訴 TypeScript 編譯器引用 Cypress 的型別定義檔案。它提供了 Cypress 的型別支援,例如 cy 命令的自動補全和型別檢查。如果未添加此行,TypeScript 可能無法識別 Cypress 的相關型別,導致編譯錯誤或缺少 IntelliSense 提示。

共用函式

在 Cypress 測試中,若有需要重複使用的程式碼(例如登入作業),可以將其抽離並放置到 commands.ts 中,作為共用函式供其他測試程式使用。這樣可以提升程式碼的可維護性與重用性。

登錄範例

以下是將登入作業移至 commands.ts 的範例:

// filepath: cypress/support/commands.ts
/// <reference types="cypress" />

Cypress.Commands.add('login', (userId, password) => {
  cy.visit('/#/login');
  cy.get('input[formcontrolname="userId"]').type(userId);
  cy.get('input[formcontrolname="userPwd"]').type(password);
  cy.contains('button', '登入').click();
  cy.url().should('include', '/home');
});

export {}; // 讓此檔案成為一個模組,否則 declare global 會錯

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * 自訂指令:登入
       */
      login(userId: string, password: string): Chainable<void>;
    }
  }
}

使用共用函式

在測試程式中使用 login 共用函式:

// filepath: cypress/e2e/login.cy.ts
/// <reference types="cypress" />

describe('使用者登入測試', () => {
  it('應成功登入並導向首頁', () => {
    cy.login('yourUserId', 'yourPassword');
    cy.contains('登錄子系統').should('be.visible');
  });
});

執行測試

  1. 啟動 Angular 應用:
ng serve
  1. 執行 Cypress UI 模式:
npx cypress open

或執行 headless 模式:

npx cypress run

整合報告器

為了產出更完整的測試報告,包括 截圖(screenshots)影片(videos),可使用 cypress-mochawesome-reporter

安裝套件

以下是本文中提到的主要套件及延伸套件的安裝指令:

主要套件

npm install --save-dev mochawesome cypress-mochawesome-reporter

延伸套件

若需要使用延伸功能,可選擇性安裝以下套件:

npm install --save-dev mochawesome-merge mochawesome-report-generator

這些延伸套件可用於手動合併測試報告並生成更高可視化效果的 HTML 測試報告。

配置報告器

import { defineConfig } from "cypress";

export default defineConfig({
  reporter: 'cypress-mochawesome-reporter',
  video: true,
  screenshotsFolder: 'cypress/reports/screenshots',
  reporterOptions: {
    reportDir: 'cypress/reports',
    charts: true,
    embeddedScreenshots: true,
    inlineAssets: true,
    saveAllAttempts: true
  },
  e2e: {
    baseUrl: 'http://localhost:4200',
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    setupNodeEvents(on) {
      // eslint-disable-next-line @typescript-eslint/no-require-imports
      require('cypress-mochawesome-reporter/plugin')(on);
    },
  },
});

// eslint-disable-next-line @typescript-eslint/no-namespace 的作用是停用 ESLint 規則 @typescript-eslint/no-namespace。該規則通常禁止使用 TypeScript 的命名空間(namespace),因為 ES6 模組已經提供了更好的替代方案。然而,在 Cypress 型別擴充的情境中,命名空間是必要的,因為需要擴充 Cypress 的內建型別。

修改 cypress/support/e2e.ts

import './commands';
import 'cypress-mochawesome-reporter/register';

import 'cypress-mochawesome-reporter/register'; 的作用是註冊 cypress-mochawesome-reporter 的功能。它會啟用該報告器的相關功能,例如嵌入截圖和影片到測試報告中。如果未添加此行,cypress-mochawesome-reporter 的功能可能無法正常運作。

產生測試報告

npx cypress run

執行後會於 cypress/reports 中產生整合截圖與影片的 index.html 測試報告,可直接開啟瀏覽分析。

增加截圖共用程式

若希望在測試報告中包含截圖,可將截圖功能與 login 分開成兩個獨立的 function,並利用 mochawesome/addContext 將截圖加入報表上下文。

// filepath: cypress/support/commands.ts
/// <reference types="cypress" />

// ======================
// 自訂指令實作區
// ======================

// eslint-disable-next-line @typescript-eslint/no-require-imports
const addContext = require('mochawesome/addContext');

// 登入功能
Cypress.Commands.add('login', (userId, password) => {
  cy.visit('/#/login');
  cy.get('input[formcontrolname="userId"]').type(userId);
  cy.get('input[formcontrolname="userPwd"]').type(password);
  cy.contains('button', '登入').click();
  cy.url().should('include', '/home');
});

// 截圖功能
Cypress.Commands.add('screenshotWithContext', (fileName: string) => {
  cy.screenshot(fileName, {
    capture: 'viewport',
    overwrite: true,
  });

  cy.once('test:after:run', (test) => {
    const screenshotPath = `screenshots/${Cypress.spec.name}/${fileName}.png`;
    addContext({ test }, screenshotPath);
  });
});

// ======================
// 型別擴充區(使用 declare global)
// ======================

export {}; // 讓此檔案成為一個模組,否則 declare global 會錯

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace Cypress {
    interface Chainable {
      /**
       * 自訂指令:登入
       */
      login(userId: string, password: string): Chainable<void>;

      /**
       * 自訂指令:截圖並加入報表上下文
       */
      screenshotWithContext(fileName: string): Chainable<void>;
    }
  }
}

使用共用函式範例

在測試中分別使用 loginscreenshotWithContext 指令:

// cypress/e2e/login.cy.ts
describe('使用者登入測試', () => {
  it('應成功登入並截圖', () => {
    cy.login('yourUserId', 'yourPassword');
    cy.screenshotWithContext('login-success');
    cy.contains('登錄子系統').should('be.visible');
  });
});

執行測試後,登入成功的截圖會自動加入報表上下文,並顯示於 mochawesome 測試報告中。