iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
Software Development

30天打造一套企業PLM系列 第 20

Day 20:E2E 測試——Playwright 實戰

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260906/20161290CSDtMOvpWv.jpg

系列:30 天打造企業級 PLM|面向:前端

問題場景

單元測試全綠,使用者一點就炸。因為單元測試的世界裡沒有 antd 的動畫、沒有 SSE 的斷線、沒有「登入後語系切換觸發 reload」。企業系統的信心來源必須是真的用瀏覽器把業務動線跑過一遍。Mini-PLM 的 E2E 建在 Playwright 上,32 個 spec 分三個模組(admin / form / item),今天講組織方式,以及那些只有 E2E 才抓得到的坑。

商業邏輯設計

  • 測試場景從業務動線來:開料號、發變更單、簽核、放行改版,這條主動線壞掉公司就停擺。E2E 覆蓋順序按業務關鍵度排,不是按程式模組排
  • seed 資料是業務規則的再確認。簽核矩陣、權限組合的測試前置資料(簽核矩陣 Seed 腳本)建的是一套微型公司,寫 seed 的過程常常反過來抓到規則設計的漏洞
  • 報告紀律:跑完 E2E 必附截圖加 PASS/FAIL 清單,不允許只說全部 PASS。證據文化是品質文化的地基

技術選型與取捨

架構演進:從 Applet 無法自動化/脆弱的 Selenium 到 Playwright 高速 E2E

在以前 Oracle Agile PLM 的時代,企業級端對端自動化測試是一大痛點:

  1. Java Client(Swing/Applet)測試盲區:桌面胖客戶端幾乎無法納入 Web 自動化管線,企業往往只能依賴人工手動點擊回歸,測試成本極高。
  2. Web Client 的 Selenium 惡夢:舊 Web Client 充斥著巢狀 iframe 與動態生成的隨機元素 ID。早期的 Selenium 腳本極度脆弱(Flaky),動輒遭遇 StaleElementReferenceException 或時序逾時,維護測試腳本的時間比寫功能還長。

Mini-PLM 採用現代 Playwright 自動化測試框架,利用其原生 Auto-waiting、強大選擇器與完整的 Headless 執行能力,建立起一套高穩定度的 E2E 防護網。

認證:fixture 一次登入、全 worker 共享

JWT 存在 zustand in-memory(Day 6),Playwright 的 storageState 存不到它,所以走自訂 fixture(Auth Fixture 實碼註解):

/**
 * Mini-PLM 的 JWT 存於 zustand in-memory state(非 localStorage / cookie),
 * 因此 storageState 無法保存登入狀態 — 改採每個 worker 各自登入一次的 fixture 機制。
 *  - jwt:worker scope,整個 worker 共享同一 token
 *  - api:APIRequestContext,已注入 Authorization header
 *  - authedPage:已自動登入的 page
 */

jwt fixture 用了一個聰明的招:開一個真的登入頁,攔截第一個帶 Authorization header 的 request 把 token 抓出來。不逆向登入 API 的實作,UI 怎麼登入就怎麼拿。api fixture 讓測試能直接打 API 做前置資料與驗證,UI 操作留給真正要測 UI 的部分。

外部依賴一律 mock

External Lookup 相關測試需要人事系統,用 Day 10 的 mock-lookup-server.ps1 頂上。E2E 測的是我們的系統,外部系統的不穩定不該讓測試變紅。mock 回應固定測資,測試才可重複。

SPA 導航:navigateInApp

in-memory JWT 的另一個後果:page.goto() 整頁重載會把登入狀態洗掉。導航一律走 SPA 內部跳轉(utils/paths.ts 實碼):

/** SPA 內部跳轉,不觸發 page reload,避免 in-memory JWT 失效。 */
export async function navigateInApp(page: Page, path: string) {
  await page.evaluate((p) => {
    window.history.pushState({}, "", p);
    window.dispatchEvent(new PopStateEvent("popstate"));
  }, path);
}

路由集中在 ROUTES 常數,vite base 或 context path 改了只動一處。

踩坑記錄:只有 E2E 才會教你的事

這批坑全部來自實戰,包含寫本系列截圖腳本時現場重演的幾個:

  1. isVisible() 不等待,元件還在動畫中就回 false,一律 waitFor
  2. AutoComplete 的 Enter 被攔,被解讀成選取建議而不是提交,先 Escape 再點按鈕
  3. 按鈕 accessible name 帶空格:antd 給兩字中文按鈕插空格(「登 入」),selector 要寫 name: /登\s*入/
  4. 登入後語系 reload 洗掉 token:Day 6 講過的機制在 E2E 重現,對策是預先把 localStorage 語系設成登入帳號的語系,繞開 reload 路徑
  5. SSE 讓 networkidle 永遠不 idle:長連線掛著,waitForLoadState("networkidle") 等到逾時。給它短 timeout 並 catch,或改等具體元素
  6. 關鍵字搜尋的假陽性:keyword=contains 語意下,260 筆資料抽查「-1」會誤中「-10」到「-199」。斷言要抽不可能是其他值子字串的樣本,例如序號最大值
  7. 截圖別存 playwright-report:reporter 每次跑會清掉該目錄,珍貴的失敗現場一起陪葬。證據類輸出放獨立目錄
  8. 靜默跳過的 fixture:登入 fixture 判斷「登入頁是否可見」決定要不要登入,UI 文案改版後判斷失效,fixture 靜默跳過登入、測試照樣綠(後續斷言恰好沒踩到)

第 8 條值得展開,它是「測試比產品更容易靜默壞掉」的典型:測試碼沒有測試。對策是讓 fixture 的每一步都有斷言(登入後必須看得到主版面),以及定期讓測試失敗一次,改壞一個東西看測試會不會紅,驗證測試還活著。

小結

fixture 解決認證與資料前置(worker 級共享攤平成本),mock 隔離外部不穩定,選擇器與等待策略對抗元件庫的 DOM 現實。E2E 是升級(Day 18)、重構、與每一次上線的安全網,這筆投資在 Day 30 的建議清單裡排前三。明日 Day 21:壓測與效能調校,250 個併發使用者下,誰先倒?


上一篇
Day 19:SSE 即時通知——比 WebSocket 輕的選擇
下一篇
Day 21:壓測與效能調校
系列文
30天打造一套企業PLM28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言