iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0

手動做跨瀏覽器測試,同一條流程要在三種瀏覽器各點一遍;自動化在這裡的優勢最直接——同一批測試,加幾行設定,五種環境自己跑。這篇的程式碼可以直接用官方的 TodoMVC 示範站實跑,不需要自己架環境。

projects:一份測試,多種環境
https://ithelp.ithome.com.tw/upload/images/20260819/201618098ylA97GSV9.png
圖 1:projects 設定讓同一批測試在多環境執行,報告各自標記環境

Playwright 的 projects 機制,讓你在設定檔宣告要跑哪些環境:Chromium(Chrome 和 Edge 的核心)、Firefox、WebKit(Safari 的引擎),加上行動裝置模擬——指定一個裝置名(Pixel 7、iPhone 14),它就用該裝置的螢幕尺寸、觸控行為來跑。

設定可以交給 Claude Code:

我要測的網站是 https://demo.playwright.dev/todomvc,請把 baseURL 設成它。
然後設定 projects:桌面跑 Chromium、Firefox、WebKit,行動裝置模擬 Pixel 7 和 iPhone 14。
設定好之後告訴我:怎麼只跑其中一種環境?怎麼一次全跑?報告上怎麼分辨是哪個環境的結果?

留意第一行:測試對象的網址是講出來的,不是它猜出來的。Claude Code 不會通靈,它只知道兩件事——你 prompt 裡寫的,和它從專案檔案讀到的。新專案的第一個 prompt,一定要把測試對象講清楚;有了這一步,baseURL 就進了 playwright.config.ts,之後的 prompt 說「TodoMVC」它會自己去讀設定檔對上,不必每次重講網址。反過來說,如果你發現它跑去測了別的網站或自己編了一個 URL,先回頭檢查:是不是第一個 prompt 就沒講。

它會幫你把設定檔改成大致這個樣子——關鍵就是 projects 陣列,一個項目一個環境:

 playwright.config.ts
 import { defineConfig, devices } from '@playwright/test';
 
 export default defineConfig({
   testDir: './tests',
   retries: process.env.CI ? 2 : 0,   // 業界慣例:只在 CI 開重試
   reporter: 'html',
   use: {
     baseURL: 'https://demo.playwright.dev/todomvc',
     trace: 'on-first-retry',
   },
   projects: [
     { name: 'chromium',  use: { ...devices['Desktop Chrome'] } },
     { name: 'firefox',   use: { ...devices['Desktop Firefox'] } },
     { name: 'webkit',    use: { ...devices['Desktop Safari'] } },
     { name: 'pixel-7',   use: { ...devices['Pixel 7'] } },
     { name: 'iphone-14', use: { ...devices['iPhone 14'] } },
   ],
 });

跑完,報告會把每個測試乘上五份,各自標記環境。原本 20 個測試變 100 個執行。這帶出本篇真正的重點:全跑嗎?每次都跑嗎?業界的答案是:不會全跑,而且跑什麼、在哪個環境跑,是設計出來的。

業界實務一:哪些測試值得跨環境跑

看過不少團隊第一次開多環境,直接把整批測試乘上五,然後被執行時間和失敗排查淹沒,一個月後把 projects 又關掉。實務上的做法是先把測試依「對環境的敏感度」分類,再決定哪類值得乘上幾個環境。

https://ithelp.ithome.com.tw/upload/images/20260819/20161809Ewmy1oW1rS.png
圖 2:測試類型 × 環境的取捨矩陣——依環境敏感度決定跑多寬

• 冒煙測試(@smoke)。每個環境都跑:登入得進去、清單開得起來,十來個測試確認「這個環境基本能動」。它存在的目的就是驗環境,不跨環境跑等於沒跑。

• 核心流程(@critical)。使用者最常走、出錯代價最高的三五條路。電商是搜尋、加購物車、結帳;SaaS 是登入、建立資料、儲存。這類測試連發版前的真機抽測都要包含。

• 響應式版面與視覺比對。這正是引擎差異最會現形的地方——字型渲染、捲動行為、彈性版面的斷點。實務上用 Playwright 內建的 toHaveScreenshot() 做視覺比對,而且每個 project 各存一份基準圖(baseline),因為不同引擎渲染出來的像素本來就不同,共用一份基準圖只會天天誤報。

• 表單與輸入行為。日期選擇器、檔案上傳、鍵盤操作、自動完成——瀏覽器對原生元件的實作差異集中在這裡,歷史上跨瀏覽器 bug 的高發區。

• 資料驅動的整批案例只跑主力瀏覽器。資料驅動測出來的是「輸入與規則」的問題,不是「引擎」的問題。整批案例在主力瀏覽器跑,其他環境挑一兩個代表案例確認流程通即可。

• API 測試與純邏輯驗證不必跨環境。完全不經過瀏覽器渲染,跑五個環境得到的是五份一模一樣的結果,純粹浪費機器。在設定檔裡把這類測試排除在多環境 projects 之外。

這個矩陣的判斷標準只有一個問題:這個測試失敗的原因,可能因環境而異嗎?可能,才值得乘上環境數;不可能,乘了只是把同一個答案抄五遍。

業界實務二:用標籤把測試分流

上面的分類要能落地,靠的是標籤(tag)。實務上團隊會在測試名稱加上 @smoke、@critical 這類標記,然後用 --grep 分流。你可以這樣對 Claude Code 說:

幫我在 tests/todo.spec.ts 寫三個測試,對象就是 config 裡 baseURL 指到的 TodoMVC:
1. 頁面載入後看得到輸入框,加 @smoke 標籤
2. 新增一筆待辦,驗證出現在清單上,同時加 @smoke 和 @critical
3. 把待辦勾成完成後刪除,驗證清單清空,加 @critical
定位方式用 getByPlaceholder、getByRole、getByTestId 這類語意化定位,不要用 CSS selector。
寫完告訴我:怎麼只跑 @smoke?怎麼驗證標籤真的有作用?

這裡就用上了前面說的原則:因為第一個 prompt 已經把 baseURL 寫進設定檔,這次只要說「config 裡 baseURL 指到的 TodoMVC」,它讀得到,不用重貼網址。它會寫出大致像這樣的測試——三條 TodoMVC 的基本流程:

 tests/todo.spec.ts
 import { test, expect } from '@playwright/test';
 
 test.beforeEach(async ({ page }) => {
   await page.goto('/');
 });
 
 test('頁面載入看得到輸入框 @smoke', async ({ page }) => {
   await expect(
     page.getByPlaceholder('What needs to be done?')
   ).toBeVisible();
 });
 
 test('新增一筆待辦 @smoke @critical', async ({ page }) => {
   const input = page.getByPlaceholder('What needs to be done?');
   await input.fill('買牛奶');
   await input.press('Enter');
   await expect(page.getByTestId('todo-title')).toHaveText('買牛奶');
 });
 
 test('完成後可刪除待辦 @critical', async ({ page }) => {
   const input = page.getByPlaceholder('What needs to be done?');
   await input.fill('寫測試');
   await input.press('Enter');
   await page.getByRole('checkbox', { name: 'Toggle Todo' }).check();
   await expect(page.getByTestId('todo-item')).toHaveClass(/completed/);
 
   await page.getByTestId('todo-item').hover();  // 刪除鈕滑過才出現
   await page.getByRole('button', { name: 'Delete' }).click();
   await expect(page.getByTestId('todo-item')).toHaveCount(0);
 });

注意第三個測試裡的 hover():TodoMVC 的刪除鈕要滑鼠移過去才出現。這種「桌面靠 hover、手機沒有 hover」的互動,正是跨環境測試會抓到的典型差異——先記著,下面的行動模擬測試會回來處理它。
標籤加上 projects,等於給了 CI 一組可以自由組合的開關:

 常用指令
 npx playwright test                                  # 五環境全跑
 npx playwright test --project=chromium               # 只跑單一環境
 npx playwright test --grep @smoke                    # 只跑冒煙(所有環境)
 npx playwright test --grep @smoke --project=chromium # 冒煙 × 主力瀏覽器
 npx playwright test --grep @critical --project=webkit --project=pixel-7
 npx playwright test --update-snapshots               # 建立/更新視覺基準圖
 npx playwright show-report                           # 開報告,看環境標記

PR 觸發時跑「@smoke × 主力瀏覽器」,每晚排程跑「全部 × 全環境」,發版前跑「@critical × 全環境 + 行動模擬」——就是這幾個指令的排列組合,寫進 CI 設定而已。

動手做:視覺比對與行動模擬

矩陣裡另外兩類環境敏感的測試,一樣用 prompt 交給 Claude Code。先看視覺比對:

幫我加一個視覺比對測試 tests/visual.spec.ts:開啟 baseURL 的首頁,
用 toHaveScreenshot 比對空清單的版面,容忍 2% 的像素差。
然後告訴我:第一次跑為什麼會失敗?基準圖存在哪個資料夾?
五個環境是不是各存一張?之後版面被改壞,報告會怎麼呈現差異?

產出大致是這樣:

tests/visual.spec.ts
 import { test, expect } from '@playwright/test';
 
 test('空清單的版面視覺比對', async ({ page }) => {
   await page.goto('/');
   // 每個 project 各存一份基準圖,例如
   // empty-list-chromium-linux.png、empty-list-webkit-linux.png
   await expect(page).toHaveScreenshot('empty-list.png', {
     maxDiffPixelRatio: 0.02,   // 容忍 2% 像素差,吸收反鋸齒雜訊
   });
 });

第一次跑會失敗並提示沒有基準圖,用 --update-snapshots 建立。建完去看 visual.spec.ts-snapshots 資料夾:同一個測試,每個 project 各有一張圖,檔名帶著環境。這就是「每個環境各存一份 baseline」的具體樣子。之後每次跑,Playwright 拿當下畫面跟該環境自己的基準圖比,版面被改壞就會紅。

再看行動模擬:

幫我寫一個只在行動裝置模擬環境執行的測試 tests/mobile.spec.ts:
用觸控(不是滑鼠點擊)操作輸入框,新增一筆待辦並驗證。
桌面環境要自動跳過,不能算失敗。
跑完告訴我:報告上哪些環境是 skipped、哪些是 passed?

關鍵在 isMobile 這個 fixture——它讓同一個測試檔在桌面環境自動跳過、只在行動模擬跑,並改用觸控操作:

 tests/mobile.spec.ts
 import { test, expect } from '@playwright/test';
 
 test('行動裝置上用觸控新增待辦 @smoke', async ({ page, isMobile }) => {
   test.skip(!isMobile, '此測試只在行動模擬環境執行');
 
   await page.goto('/');
   const input = page.getByPlaceholder('What needs to be done?');
   await input.tap();               // 觸控,不是 click
   await input.fill('手機上新增');
   await input.press('Enter');
   await expect(page.getByTestId('todo-title')).toHaveText('手機上新增');
 });

留意這幾個 prompt 的共同結尾:都要求 Claude Code「告訴我怎麼驗證」。它寫程式很快,但程式對不對、跑出來的結果是不是你要的,驗證的責任還是在你手上——這條原則貫穿整個系列,多環境測試也不例外。

如果你把前面 todo.spec.ts 的刪除測試放到 Pixel 7 跑,很可能卡在 hover 那步——行動裝置沒有滑鼠。這不是測試寫壞,是它替你問了一個產品問題:手機使用者到底怎麼刪待辦?查規格、問開發,答案可能是「行動版本來就有常駐刪除鈕」或「這是個沒人發現的缺口」。多環境測試的價值,常常就藏在這種地方。

先誠實面對:模擬不等於真機

行動裝置「模擬」,是在你電腦的瀏覽器裡仿製手機的尺寸與行為。它抓得到響應式版面的問題:選單有沒有收合、按鈕會不會太小疊在一起。

但它不是真的 iPhone。真機上的鍵盤行為、系統手勢、效能差異,模擬層看不到。定位要擺對:模擬負責大量、日常、便宜的把關;真機留給發版前的重點抽測。

業界對「真機」這塊的實務做法,通常不是自己養一櫃手機,而是接雲端真機服務(BrowserStack、Sauce Labs、LambdaTest 這類),Playwright 測試可以直接指到它們的遠端環境跑。但要注意成本結構:雲端真機按時計費,速度也比本機模擬慢,所以合理的用法是「發版前,核心流程,少量裝置」,而不是把每晚全量測試都丟上去。模擬與真機是分工,知道工具的邊界在哪,本來就是測試專業的一部分。

常見疑問:WebKit 就是 Safari 嗎

嚴格說,測的是「引擎」不是「瀏覽器」。WebKit 是 Safari 的核心引擎,在 Windows 或 Linux 上跑 WebKit,測的是同一套網頁解讀邏輯,但不是 Mac 上那個完整的 Safari。絕大多數相容性問題出在引擎層,所以這樣測有效;要百分之百還原,還是得真機真瀏覽器,這跟前面「模擬不等於真機」是同一件事。

另一個常見疑問:五倍執行量會不會慢五倍?不會那麼慘。Playwright 預設會平行執行(還記得報告上的 worker 嗎),多環境會分頭同時跑。實際時間看你機器的力氣,通常是兩三倍而不是五倍。CI 上還有一招業界常用的 sharding:用 --shard=1/4 這類參數把整批測試切成幾份,丟到多台機器同時跑,全環境每晚跑一輪也能壓在可接受的時間內。

策略:用風險決定跑多少

https://ithelp.ithome.com.tw/upload/images/20260819/20161809V0SP9h4xCJ.png
圖 3:不同時機,不同的執行範圍

五倍的執行量,就是五倍的時間和五倍的失敗排查。全部場合全環境跑,很快拖垮回饋速度。務實的分層:

• 日常開發、每次 PR:只跑主力瀏覽器(使用者占比最高那個),搭配 @smoke 與改動相關的測試,快速回饋優先。

• 每晚排程:全環境跑一輪,趁沒人用機器,慢沒關係。需要更快就上 sharding。

• 發版前:全環境加行動裝置跑 @critical,再加雲端真機重點抽測。風險最高的時刻,最完整的驗證。

這個分層的輸入是什麼?你們的瀏覽器占比、行動流量比例、哪些功能在哪些環境出過事。全是測試人員手上的資訊。企業內部系統可能根本不用測 WebKit,行動流量七成的電商該把行動模擬放進日常。策略是算出來的,不是抄來的。

順帶一提設定檔裡那行 retries:業界普遍只在 CI 開重試(一到兩次),本機開發不開。CI 重試能吸收環境抖動造成的偶發失敗,但同一個測試若得靠重試才過,報告會標記為 flaky——那是待修清單,不是可以視而不見的綠燈。

跨環境失敗怎麼判讀

開始多環境跑之後,會出現新形態的失敗:四個環境綠,只有 WebKit 紅。

https://ithelp.ithome.com.tw/upload/images/20260819/20161809QPGRtOPp9O.png
圖 4:跨環境失敗的判讀順序——先排除測試自己的問題,再談瀏覽器 bug

先別急著喊 Safari 的 bug。依經驗,先檢查是不是測試自己的時序問題——不同引擎速度不同,第 8 篇的等待知識在此複習。判讀順序照圖走:先重跑分辨偶發還是穩定,偶發回頭修等待寫法;穩定失敗就打開 trace,比對通過與失敗環境的差異。丟給 Claude Code 時把環境差異講清楚:

「新增待辦」這個測試只在 webkit 失敗,chromium、firefox、兩個行動模擬都過。
重跑三次都一樣失敗,不是偶發。trace 顯示卡在等 todo-title 出現,逾時 5 秒。
請先判斷是測試的等待寫法問題,還是產品在 WebKit 上的行為差異,
說明你的判斷依據,再給我修法。不要直接加 sleep。

這個 prompt 有三個值得學的地方:講清楚「哪些環境過、哪個環境失敗」,附上重跑結果排除偶發,以及要求它先判斷再動手、並禁止用 sleep 硬蓋過去。資訊給得越完整,它越不會亂猜。

真是產品在特定瀏覽器的問題?恭喜,你抓到了手動測試最難覆蓋的那類 bug,這正是多環境測試存在的意義。開單時記得附上環境資訊和 trace,開發者才能重現。


上一篇
Day19: 截圖與視覺比對:畫面長歪了怎麼抓
下一篇
Day 21: Page Object Model:請 Claude Code 幫你重構
系列文
AI 時代下最值得投資的 UI 自動化:30 天用 Claude Code 學會寫 Playwright22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言