初步認識 Playwright
什麼是 Playwright?
Playwright 是 Microsoft 開發的一套瀏覽器自動化框架。它可以讓你用程式碼控制瀏覽器,做到「人類用瀏覽器能做的任何事」——點擊按鈕、填寫表單、截圖、攔截網路請求等等。它的定位與 Puppeteer 類似,但支援的瀏覽器更多,API 設計也更完善。
底層原理
Playwright 的核心是透過 CDP(Chrome DevTools Protocol) 與 Chromium 溝通,對於 Firefox 則是透過其自家的 protocol,對 WebKit 也有類似的機制。簡單來說,它直接與瀏覽器核心對話,而不是模擬使用者的滑鼠和鍵盤事件(雖然它也可以那樣做)。
整個架構分為幾層:
- Browser:代表一個瀏覽器實例(例如 Chromium、Firefox 或 WebKit)。
- BrowserContext:類似於一個獨立的「無痕視窗」,每個 Context 有自己的 Cookie、LocalStorage 和 Session,彼此完全隔離。
- Page:就是一個分頁,所有操作都發生在 Page 上。
這個設計讓 Playwright 在做平行化測試時非常有優勢。你可以同時開多個 BrowserContext,每個 Context 跑自己的測試,互不干擾,速度極快。
基本安裝與使用
安裝很簡單:
pnpm init playwright@latest
這個指令會幫你初始化設定、安裝瀏覽器 binary,並且建立範例測試檔。
如果只是要在現有專案中加入 Playwright:
pnpm install -D @playwright/test
pnpm exec playwright install
寫一個最基本的測試
// tests/example.spec.ts
import { test, expect } from '@playwright/test';
test('title should be correct', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
執行測試:
pnpm exec playwright test
常用操作
// 點擊
await page.click('button#submit');
// 填寫輸入框
await page.fill('input[name="email"]', 'test@example.com');
// 等待某個元素出現
await page.waitForSelector('.success-message');
// 取得文字內容
const text = await page.textContent('.title');
// 截圖
await page.screenshot({ path: 'screenshot.png' });
Playwright 預設會自動等待元素變成可互動狀態(稱為 Auto-waiting),所以你通常不需要手動加 sleep 或等待語句,這一點比舊世代工具好很多。
特殊功能
1. 多瀏覽器支援
Playwright 同時支援 Chromium、Firefox、WebKit(Safari 核心),只需要在設定檔裡指定,就能讓同一套測試跑在三種引擎上。
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
2. 網路攔截(Network Interception)
你可以攔截特定的 API 請求,並替換為假資料,這對測試非常有用:
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Test User' }]),
});
});
3. 模擬裝置與地理位置
const context = await browser.newContext({
...devices['iPhone 13'],
geolocation: { longitude: 121.5654, latitude: 25.0330 }, // 台北
permissions: ['geolocation'],
});
4. 視覺比對(Visual Comparison)
await expect(page).toHaveScreenshot('homepage.png');
Playwright 會自動比對截圖與之前儲存的基準圖,若有差異就報錯。這對於 UI 回歸測試非常方便。
5. Trace Viewer
當測試失敗時,Playwright 可以錄製一份包含截圖、網路記錄、Console log 的 trace 檔案:
pnpm exec playwright test --trace on
pnpm exec playwright show-trace trace.zip
開啟後你可以像看影片一樣,一步一步重播測試過程,找出哪個步驟出了問題。
奇異的使用技巧
技巧一:用 page.evaluate() 執行任意 JavaScript
page.evaluate() 讓你在瀏覽器的環境裡執行任意 JavaScript,並把結果回傳到 Node.js 端:
const scrollHeight = await page.evaluate(() => document.body.scrollHeight);
console.log('頁面總高度:', scrollHeight);
你甚至可以傳參數進去:
const result = await page.evaluate((selector) => {
return document.querySelector(selector)?.innerText;
}, '.title');
技巧二:攔截並修改 Response
不只是 Request,連 Response 也可以攔截和修改:
await page.route('**/api/config', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.featureFlag = true; // 強制開啟某個 feature flag
await route.fulfill({ response, json });
});
技巧三:多個 Page 同時操作
有些功能需要模擬兩個使用者同時操作(例如聊天測試),可以開兩個 Page:
const page1 = await context.newPage();
const page2 = await context.newPage();
await page1.goto('https://chat-app.example.com/room/1');
await page2.goto('https://chat-app.example.com/room/1');
await page1.fill('#message-input', 'Hello from user 1!');
await page1.click('#send-button');
await expect(page2.locator('.message-list')).toContainText('Hello from user 1!');
技巧四:利用 storageState 儲存登入狀態
每個測試都重新登入非常耗時。Playwright 可以把登入後的 Cookie 和 LocalStorage 存下來,之後直接載入:
// 先登入並儲存狀態
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('/login');
await page.fill('#username', 'admin');
await page.fill('#password', 'password');
await page.click('#login-button');
await context.storageState({ path: 'auth.json' });
// 之後的測試直接使用儲存的狀態
const loggedInContext = await browser.newContext({ storageState: 'auth.json' });
在 playwright.config.ts 中,也可以設定全域的 storageState,讓所有測試都自動帶入登入狀態。
神奇的使用方法
方法一:用 Playwright 做爬蟲
Playwright 並不只能拿來做測試,它是一個完整的瀏覽器控制工具,所以也很適合做爬蟲。特別是對於需要 JavaScript 渲染的頁面,Playwright 比傳統的 HTTP 爬蟲更有優勢:
import { chromium } from 'playwright';
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://some-spa-website.com/products');
// 等待動態渲染完成
await page.waitForSelector('.product-card');
const products = await page.$$eval('.product-card', (cards) =>
cards.map((card) => ({
name: card.querySelector('.product-name')?.textContent,
price: card.querySelector('.product-price')?.textContent,
}))
);
console.log(products);
await browser.close();
})();
方法二:用 codegen 自動生成程式碼
這是 Playwright 內建的一個殺手級功能。你只要執行:
pnpm exec playwright codegen https://example.com
Playwright 會打開瀏覽器,並在你操作瀏覽器的同時,自動把你的每一個動作轉換成對應的程式碼,即時顯示在旁邊的視窗中。對於不熟悉 Playwright API 的人來說,這個功能可以大幅降低學習門檻。
方法三:在 CI/CD 中跑 Headless 測試
Playwright 在無介面(headless)模式下可以完美在 CI/CD 環境(例如 GitHub Actions)中執行:
# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
測試完成後,HTML 報告會被上傳為 artifact,讓你在 CI 介面上就能看到詳細的測試結果。
總結
Playwright 是目前最成熟、功能最完整的瀏覽器自動化工具之一。它的多瀏覽器支援、Auto-waiting、網路攔截、Trace Viewer 等功能,讓撰寫和除錯測試都變得更加容易。不論你是要做 E2E 測試、視覺回歸測試,還是做一些需要操控瀏覽器的自動化任務,Playwright 都是一個值得深入學習的工具。