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
建立目錄
專案根目錄下建立 cypress
、cypress/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/
:放置共用函式或 hookfixtures/
:模擬資料檔案- 自動產生
cypress.config.ts
或cypress.config.js
建立配置檔
若要使用共用函式,需指定 supportFile
為 cypress/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');
});
});
執行測試
- 啟動 Angular 應用:
ng serve
- 執行 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>;
}
}
}
使用共用函式範例
在測試中分別使用 login
和 screenshotWithContext
指令:
// cypress/e2e/login.cy.ts
describe('使用者登入測試', () => {
it('應成功登入並截圖', () => {
cy.login('yourUserId', 'yourPassword');
cy.screenshotWithContext('login-success');
cy.contains('登錄子系統').should('be.visible');
});
});
執行測試後,登入成功的截圖會自動加入報表上下文,並顯示於 mochawesome
測試報告中。