跳到主要內容
前端開發

E2E 測試現在選什麼:Playwright 與 Cypress 的實際差異

對照幾年前用 Cypress 的做法,說明 Playwright 為什麼變成多數新專案的預設選擇:真正的多瀏覽器支援、免費的平行執行、trace viewer 除錯,以及兩者在架構上的根本差異與各自仍然適合的場景。

W

  • 幾年前做端對端測試時,Cypress 是很常見的選擇,互動式 Test Runner、npx cypress open 與鏈式 API 都很適合入門。
  • 這幾年 Playwright 起來得很快,現在多數新專案會直接選它。
  • 這篇講兩者的實際差異,以及什麼情況下 Cypress 還是好選擇——這不是「Cypress 已死」的文章,它仍然是個好工具。

架構上的根本差異

兩者最大的不同在測試程式跑在哪裡。

Cypress 把測試程式跑在瀏覽器裡,跟被測試的應用共用同一個執行環境。這帶來很好的除錯體驗(可以直接 debugger、在 DevTools 裡看),但也帶來限制:

  • 難以跨分頁、跨網域操作
  • 沒辦法直接碰檔案系統或發原生的 HTTP 請求(要透過 cy.task 跳到 Node 端)
  • 受同源政策約束

Playwright 把測試程式跑在 Node 裡,透過 CDP(Chrome DevTools Protocol)之類的協定遠端驅動瀏覽器。 測試程式與應用完全分離,所以:

  • 多分頁、多網域、多使用者情境都是原生支援
  • 可以隨意讀寫檔案、呼叫 API、操作資料庫來準備測試資料
  • 一份測試可以同時跑在 Chromium、Firefox、WebKit

這個架構差異解釋了後面幾乎所有的功能差別。

主要差異

項目CypressPlaywright
測試程式執行位置瀏覽器內Node 端
瀏覽器引擎Chromium、Firefox、ElectronChromium、Firefox、WebKit
平行執行需付費的 Cypress Cloud內建免費
多分頁 / 多網域受限原生支援
語言JavaScript / TypeScriptTS/JS、Python、Java、.NET
自動等待有有
除錯工具互動式 Test RunnerTrace Viewer、UI Mode、Codegen
授權MIT(部分功能需付費雲端)MIT,全部功能免費

Playwright 的三個關鍵優勢

1. 真正的 WebKit 測試

Cypress 沒有 WebKit 支援(曾有實驗性支援但不完整)。這代表 Safari 專屬的 bug 測不到——而那正是最常出問題的地方。

Playwright 的 webkit 用的是真正的 WebKit 引擎。雖然仍不完全等於 iOS 上的 Safari(沒有 iOS 的系統整合),但比完全測不到好太多。

// playwright.config.ts
export default defineConfig({
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
  ],
});

同一份測試自動在三個引擎上跑。

2. 平行執行免費

Cypress 的平行執行需要 Cypress Cloud(付費)。Playwright 的 workers 是內建的:

export default defineConfig({
  workers: process.env.CI ? 4 : undefined,
});

E2E 測試最大的痛點就是慢,一套跑 20 分鐘的測試沒有人願意在每次 PR 都等。平行執行是不是要另外付費,實務上影響很大。

3. Trace Viewer

這是我覺得最有價值的功能。測試失敗時,Playwright 可以錄下完整的執行軌跡:

export default defineConfig({
  use: {
    trace: 'on-first-retry',   // 第一次重試時錄
  },
});

失敗後用 npx playwright show-trace trace.zip 打開,會看到:

  • 每一步操作的畫面快照,可以來回拖曳
  • 每一步的 DOM 狀態(可以用 DevTools 檢查)
  • 網路請求、Console log
  • 完整的操作時間軸

CI 上偶發失敗的測試最難查,因為你重跑一次就好了。有 trace 之後,那次失敗當下的完整現場都留著。這件事 Cypress 的影片錄製做不到同樣的深度。

寫法對照

概念幾乎一樣,語法有些差異。

Cypress:

describe('登入', () => {
  it('用正確帳密可以登入', () => {
    cy.visit('/login');
    cy.get('[data-testid=email]').type('user@example.com');
    cy.get('[data-testid=password]').type('password');
    cy.get('button[type=submit]').click();
    cy.url().should('include', '/dashboard');
  });
});

Playwright:

import { test, expect } from '@playwright/test';

test('用正確帳密可以登入', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('密碼').fill('password');
  await page.getByRole('button', { name: '登入' }).click();
  await expect(page).toHaveURL(/dashboard/);
});

最明顯的差別是 Playwright 用 await,而 Cypress 用鏈式的隱含佇列。Cypress 的寫法看起來比較短,但那個「看起來像同步、其實不是」的特性也是新手最常困惑的地方——在 Cypress 裡寫 const el = cy.get(...) 拿不到元素。

建議用語意化的定位器

getByRole、getByLabel 這類定位器比 CSS selector 好,理由是它們同時在測無障礙:如果 getByRole('button', { name: '登入' }) 找不到東西,通常代表那顆按鈕對螢幕閱讀器來說也是有問題的。

Cypress 也支援 Testing Library 的同類定位器,這點兩邊都做得到。

從 Cypress 遷移

有幾件事要有心理準備:

  • cy.intercept 對應 page.route,API 略有不同
  • 自訂 command 要改寫成 fixture,Playwright 的擴充機制是 fixture 而不是掛在 cy 上
  • Cypress 的隱含等待與重試邏輯要改成 expect 的 web-first assertion:await expect(locator).toBeVisible() 會自動重試,這是 Playwright 處理非同步的核心機制

不建議一次全部搬。實務上的做法是新測試用 Playwright 寫,舊的 Cypress 測試繼續跑,等哪天舊測試需要大改時再順手轉。

Cypress 什麼時候還是好選擇

講完 Playwright 的優勢,也要講清楚 Cypress 仍然有優勢的地方:

  • 既有投資:已經有幾百個 Cypress 測試而且跑得好好的,沒有理由為了換而換。
  • 互動式除錯體驗:Cypress 的 Test Runner 讓你在測試暫停的狀態下直接操作頁面,這個體驗仍然很好。
  • Component Testing:兩邊都有,但 Cypress 的元件測試整合度較成熟。
  • 團隊熟悉度:工具再好,團隊不會用就是負擔。

該用哪一個

  • 新專案 → Playwright。免費的平行執行、WebKit 支援與 trace viewer 是實打實的差距。
  • 既有 Cypress 專案跑得好 → 不用急著換。
  • 需要測 Safari 行為 → Playwright,這點沒有替代方案。
  • 需要多分頁、多使用者情境(聊天室、協作編輯) → Playwright。
  • 主要做元件測試 → 兩邊都行,Cypress 略成熟。

參考資料

分享這篇