機制詳解:從 Worker 進程隔離到源碼級重試調(diào)度)
Playwright 測試重試Retries機制詳解從 Worker 進程隔離到源碼級重試調(diào)度【免費下載鏈接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.項目地址: https://gitcode.com/GitHub_Trending/pl/playwright本文圍繞 Playwright 官方文檔中的 Retries 指南展開先講清測試失敗時 Worker 進程如何被整體丟棄與重建再覆蓋--retries命令行、playwright.config.ts配置、test.describe.configure({ retries })分組重試、TestInfo.retry運行時檢測以及 Serial 模式整組重試等全部官方用法并結(jié)合 runner/dispatcher.ts、common/config.ts 等倉庫源碼說明重試候選如何被收集、隔離調(diào)度與分類為 flaky 的完整鏈路。為什么需要重試測試失敗與 Worker 進程生命周期測試重試Retries是一種在測試失敗后自動重新運行的機制專門用于應對**間歇性失敗flaky**的測試。理解它的前提是理解 Playwright Test 的 Worker 進程模型測試運行在worker 進程中。這些進程是獨立的操作系統(tǒng)進程由測試運行器test runner統(tǒng)一編排所有 worker 擁有完全一致的環(huán)境每個 worker 都會啟動自己獨立的瀏覽器實例。文檔用一段典型用例演示了三種場景。假設存在如下測試文件import { test } from playwright/test; test.describe(suite, () { test.beforeAll(async () { /* ... */ }); test(first good, async ({ page }) { /* ... */ }); test(second flaky, async ({ page }) { /* ... */ }); test(third good, async ({ page }) { /* ... */ }); test.afterAll(async () { /* ... */ }); });場景一全部通過。所有測試按順序在同一個 worker 進程中運行* Worker process starts * beforeAll hook runs * first good passes * second flaky passes * third good passes * afterAll hook runs場景二任一測試失敗。Playwright Test 會連同瀏覽器一起丟棄整個 worker 進程并啟動一個新進程。測試從下一個測試開始在新 worker 中繼續(xù)* Worker process #1 starts * beforeAll hook runs * first good passes * second flaky fails * afterAll hook runs * Worker process #2 starts * beforeAll hook runs again * third good passes * afterAll hook runs場景三啟用重試后。第二個 worker 進程會以重試失敗的測試作為起點然后繼續(xù)后續(xù)測試* Worker process #1 starts * beforeAll hook runs * first good passes * second flaky fails * afterAll hook runs * Worker process #2 starts * beforeAll hook runs again * second flaky is retried and passes * third good passes * afterAll hook runs這種失敗即換進程的方案對相互獨立的測試非常有效并保證了失敗的測試不會污染健康的測試。這一設計在源碼中可以得到印證。在 runner/dispatcher.ts 中Job的onDone路徑在 job 結(jié)束后會調(diào)度剩余測試與重試任務當 worker 進程意外退出時onExit會生成worker process exited unexpectedly錯誤并跳過相關測試。而每個 job 完成后runInWorker通過 IPC 把retry: test.results.length傳給 worker——當前已運行次數(shù)即本次的重試序號這保證了新 worker 從正確的重試輪次開始執(zhí)行。配置重試命令行與配置文件Playwright 支持測試重試啟用后失敗的測試會反復重試直到通過或達到最大重試次數(shù)。默認情況下失敗的測試不會被重試。命令行方式# 給失敗的測試 3 次重試機會 npx playwright test --retries3該選項在 program.ts 中定義官方描述為[--retries retries, { description: Maximum retry count for flaky tests, zero for no retries (default: no retries) }]即傳入0表示不重試不傳時默認為不重試。配置文件方式import { defineConfig } from playwright/test; export default defineConfig({ // Give failing tests 3 retry attempts retries: 3, });從源碼結(jié)構(gòu)看retries的生效順序在 common/config.ts 中明確定義retries: takeFirst(configCLIOverrides.retries, projectConfig.retries, config.retries, 0),即優(yōu)先級為CLI--retries 單個 project 的retries 全局config.retries 默認 0。這意味著你可以在projects數(shù)組里為不同瀏覽器項目配置不同的重試次數(shù)同時仍可用命令行臨時覆蓋。結(jié)果分類passed、flaky、failed啟用重試后Playwright Test 會把測試歸為三類passed首次運行即通過的測試flaky首次運行失敗、但在重試后通過的測試failed首次運行失敗且所有重試均失敗的測試。一次典型的運行輸出如下Running 3 tests using 1 worker ? example.spec.ts:4:2 ? first passes (438ms) x example.spec.ts:5:2 ? second flaky (691ms) ? example.spec.ts:5:2 ? second flaky (522ms) ? example.spec.ts:6:2 ? third passes (932ms) 1 flaky example.spec.ts:5:2 ? second flaky 2 passed (4s)這一分類邏輯同樣體現(xiàn)在倉庫的報告器實現(xiàn)中。在 reporters/base.ts 中TestSummary包含獨立的flaky: TestCase[]字段摘要消息會把 flaky 測試以黃色高亮單獨列出${flaky.length} flaky并在失敗明細中優(yōu)先展示 flaky 項。因此flaky 不會導致整個 run 失敗但會在匯總中顯式暴露方便持續(xù)追蹤不穩(wěn)定測試。運行時檢測重試TestInfo.retry 與分組配置TestInfo.retry可以在測試內(nèi)部通過testInfo.retry屬性檢測當前是否為重試0 表示首次運行該屬性對任何測試、hook 和 fixture 都可用。典型用途是在重試前清理服務端狀態(tài)import { test, expect } from playwright/test; test(my test, async ({ page }, testInfo) { if (testInfo.retry) await cleanSomeCachesOnTheServer(); // ... });test.describe.configure 指定分組重試針對特定測試組或單個文件可以用test.describe.configure({ retries })覆蓋全局配置import { test, expect } from playwright/test; test.describe(() { // All tests in this describe group will get 2 retry attempts. test.describe.configure({ retries: 2 }); test(test 1, async ({ page }) { // ... }); test(test 2, async ({ page }) { // ... }); });該機制在源碼中的落點是 common/test.tsTestSuite持有_retries字段TestCase持有retries字段且TestCase.retries初始值為0當配置序列化時suite 層的_retries會被展開到該組下的每個測試上最終由 dispatcher 用test.results.length test.retries 1判斷該測試還有沒有重試額度。Serial 模式整組測試一起重試使用test.describe.serial等價于test.describe.configure({ mode: serial })可以分組管理相互依賴的測試確保它們始終在一起按順序運行。組內(nèi)任一測試失敗時其后的所有測試會被跳過而整組測試會作為一個整體一起重試。import { test } from playwright/test; test.describe.configure({ mode: serial }); test.beforeAll(async () { /* ... */ }); test(first good, async ({ page }) { /* ... */ }); test(second flaky, async ({ page }) { /* ... */ }); test(third good, async ({ page }) { /* ... */ });未啟用重試時失敗后的測試全部被跳過* Worker process #1: * beforeAll hook runs * first good passes * second flaky fails * third good is skipped entirely啟用重試時整組一起重試* Worker process #1: * beforeAll hook runs * first good passes * second flaky fails * third good is skipped * Worker process #2: * beforeAll hook runs again * first good passes again * second flaky passes * third good passes源碼印證了整組重試的調(diào)度細節(jié)。在 runner/dispatcher.ts 中onDone會為每個失敗測試向上查找最外層 serial suite_parallelMode serial把該 suite 下全部測試加入重試候選集for (const serialSuite of serialSuitesWithFailures) { // Add all tests from failed serial suites for possible retry. // These will only be retried together, because they have the same // retries setting and the same number of previous runs. serialSuite.allTests().forEach(test retryCandidates.add(test)); }同時dispatcher 還會把屬于某個已失敗 serial suite 的后續(xù)剩余測試整體標記為跳過_massSkipTestsFromRemaining這正是文檔中 third good is skipped 的實現(xiàn)來源。注意官方建議通常更好的做法是讓測試保持相互隔離從而能夠被高效地獨立運行和重試。Serial 模式應作為依賴場景的例外手段。源碼視角重試如何被調(diào)度isolated 與 immediate除了文檔顯式描述的行為倉庫源碼還揭示了重試調(diào)度的兩個關鍵策略適合希望深入理解 runner 內(nèi)部機制的讀者。重試候選的收集條件在 runner/dispatcher.ts 中job 結(jié)束后的重試候選集由三個條件共同決定非可重試錯誤的測試被排除_failedWithNonRetriableError命中的失敗測試例如 plan 標注跳過等不會進入重試重試額度檢查只有滿足test.results.length test.retries 1的測試才會被安排重試其中test.results.length即已運行次數(shù)serial 組擴展失敗測試所在的最外層 serial suite 會被整體納入候選。retryStrategyimmediate 與 isolatedcommon/config.ts 定義了retryStrategy配置項取值immediate | isolated默認值為immediateconfigLoader.ts 對非法取值會直接報錯。兩種策略在 dispatcher.ts 中的行為差異為for (const test of retryCandidates) { if (test.results.length test.retries 1) { // Immediate retries run together with the remaining tests, in a single job. if (this._testRun.config.retryStrategy immediate) remaining.push(test); else isolatedRetries.push(test); } }immediate重試任務與剩余未跑的測試合并在同一個新 job 中執(zhí)行運行效率更高isolated重試任務被拆出獨立的isolatedRetriesJob只包含失敗測試避免與正常測試混合——便于隔離觀察不穩(wěn)定測試。此外program.ts 中 trace 模式還有一組與重試聯(lián)動的取值on-first-retry、on-all-retries、retain-on-failure、retain-on-first-failure、retain-on-failure-and-retries。例如配置trace: { mode: on-first-retry }可以在首次失敗后的重試中自動采集 trace幫助定位 flaky 根因。在 Serial 組內(nèi)跨測試復用單個 PagePlaywright Test 默認為每個測試創(chuàng)建隔離的Page對象。如果確實需要跨測試復用同一個Page可以在test.beforeAll中自行創(chuàng)建、在test.afterAll中關閉通常需要配合 serial 模式import { test, type Page } from playwright/test; test.describe.configure({ mode: serial }); let page: Page; test.beforeAll(async ({ browser }) { page await browser.newPage(); }); test.afterAll(async () { await page.close(); }); test(runs first, async () { await page.goto(https://playwright.dev/); }); test(runs second, async () { await page.getByText(Get Started).click(); });JavaScriptCommonJS版本等價寫法// ts-check const { test } require(playwright/test); test.describe.configure({ mode: serial }); /** type {import(playwright/test).Page} */ let page; test.beforeAll(async ({ browser }) { page await browser.newPage(); }); test.afterAll(async () { await page.close(); }); test(runs first, async () { await page.goto(https://playwright.dev/); }); test(runs second, async () { await page.getByText(Get Started).click(); });這種模式常用于登錄態(tài)復用等場景第一個測試完成登錄后后續(xù)測試直接復用同一瀏覽器上下文避免重復鑒權。最佳實踐小結(jié)默認不重試按需在 CI 中開啟本地開發(fā)時保持默認無重試以快速暴露問題CI 中用npx playwright test --retries2或配置文件兜底 flaky把 flaky 當作信號而非噪音報告器會把 flaky 單獨計數(shù)并在匯總中展示長期 flaky 的測試應回到隔離測試 穩(wěn)定等待的路線而不是無限加重試次數(shù)重試時清理副作用利用testInfo.retry在重試前清理緩存、數(shù)據(jù)庫或服務器狀態(tài)避免上一輪失敗的殘留影響本輪判定依賴測試用 serial 整組重試describe.serial保證失敗組內(nèi)后續(xù)測試被跳過且整組重跑但官方建議優(yōu)先改造為相互隔離的獨立測試配合 trace 模式定位 flakyon-first-retry/on-all-retries等 trace 模式與重試聯(lián)動只記錄失敗路徑的追蹤數(shù)據(jù)控制產(chǎn)物體積。相關參考配置指南、測試分類指南、重試調(diào)度實現(xiàn) dispatcher.ts、配置解析 config.ts、報告器 flaky 分類 base.ts?!久赓M下載鏈接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.項目地址: https://gitcode.com/GitHub_Trending/pl/playwright創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考