2026-01-16

Playwright性能優(yōu)化:減少測(cè)試執(zhí)行時(shí)間的10個(gè)技巧

當(dāng)你的E2E測(cè)試套件執(zhí)行時(shí)間從幾分鐘膨脹到半小時(shí),每天能完整運(yùn)行的次數(shù)屈指可數(shù)時(shí),優(yōu)化就不再是可選項(xiàng),而是必需品。我們團(tuán)隊(duì)曾面對(duì)一個(gè)45分鐘的測(cè)試套件,通過系統(tǒng)優(yōu)化最終將其縮減到8分鐘。以下是經(jīng)過實(shí)戰(zhàn)驗(yàn)證的10個(gè)技巧。

1. 并行化:性價(jià)比最高的優(yōu)化

這是最能立竿見影的手段。Playwright原生支持并行執(zhí)行,你只需要調(diào)整配置:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left; visibility: visible;">// playwright.config.ts export default { workers: process.env.CI ? 4 : 2, // CI環(huán)境用4個(gè)worker,本地用2個(gè) fullyParallel: true, // 所有測(cè)試文件并行執(zhí)行 // 如果你的測(cè)試有狀態(tài)依賴,可以改為: // fullyParallel: false, // 但依然可以保持worker數(shù)量 }; </pre>

關(guān)鍵在于測(cè)試文件的獨(dú)立性。如果有全局狀態(tài)(如共享數(shù)據(jù)庫),你需要通過beforeAllafterAll來管理測(cè)試隔離。在我們的實(shí)踐中,僅開啟并行化就將執(zhí)行時(shí)間減少了65%。

2. 瀏覽器復(fù)用:減少啟動(dòng)開銷

每次測(cè)試都啟動(dòng)新瀏覽器會(huì)浪費(fèi)大量時(shí)間。改為復(fù)用瀏覽器實(shí)例:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 在配置中開啟 exportdefault { use: { headless: true, // 復(fù)用瀏覽器上下文而非每次都創(chuàng)建新的 launchOptions: { args: ['--no-sandbox'] } }, }; // 測(cè)試文件中 test.describe.configure({ mode: 'parallel' }); // 讓describe內(nèi)測(cè)試也并行 // 或者手動(dòng)管理瀏覽器實(shí)例 let browser: Browser; test.beforeAll(async () => { browser = await chromium.launch(); }); test.beforeEach(async ({ page }) => { // 從已有瀏覽器創(chuàng)建上下文 const context = await browser.newContext(); page = await context.newPage(); }); </pre>

注意:瀏覽器復(fù)用可能導(dǎo)致測(cè)試間狀態(tài)污染,確保每個(gè)測(cè)試有獨(dú)立的browserContext。

3. 選擇性執(zhí)行:只跑必要的測(cè)試

不是每次提交都需要跑全部測(cè)試。建立分層策略:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">{ "scripts": { "test:smoke": "playwright test --grep '@smoke'", "test:regression": "playwright test --grep '@regression'", "test:changed": "playwright test $(git diff --name-only HEAD~1 | grep -E '\\.spec\\.ts/pre>)" } } </pre>

在測(cè)試文件中使用標(biāo)簽:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">test('關(guān)鍵登錄流程 @smoke', async ({ page }) => { // 冒煙測(cè)試,每次CI都執(zhí)行 }); test('邊界條件測(cè)試 @regression', async ({ page }) => { // 回歸測(cè)試,每日?qǐng)?zhí)行 }); </pre>

我們團(tuán)隊(duì)設(shè)置了預(yù)提交鉤子只跑@smoke測(cè)試,夜間CI跑完整套件,平衡了速度和覆蓋率。

4. 智能等待:告別硬編碼sleep

硬編碼的sleep是性能殺手,也是脆弱的根源:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// ? 糟糕的做法 await page.waitForTimeout(5000); // 無論實(shí)際需要多久都等5秒 // ? 正確的做法 // 等待頁面加載完成 await page.waitForLoadState('networkidle'); // 等待特定元素出現(xiàn) await page.locator('.data-loaded').waitFor({ state: 'visible' }); // 等待API請(qǐng)求完成 const responsePromise = page.waitForResponse('/api/data'); await page.click('[#load](javascript:;)-data'); const response = await responsePromise; // 自定義等待條件 await page.waitForFunction( () =>document.querySelectorAll('.item').length >= 10 ); </pre>

優(yōu)化后,我們的測(cè)試平均等待時(shí)間從固定的5秒降到了0.5-2秒,具體取決于網(wǎng)絡(luò)和業(yè)務(wù)邏輯。

5. 數(shù)據(jù)準(zhǔn)備:預(yù)置而非動(dòng)態(tài)生成

避免在測(cè)試中執(zhí)行耗時(shí)操作:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// ? 每個(gè)測(cè)試都創(chuàng)建用戶 test('用戶操作', async () => { const user = await createUserViaAPI(); // 每次調(diào)用都走完整流程 // 測(cè)試邏輯... }); // ? 預(yù)置測(cè)試數(shù)據(jù) // 在全局setup中 import { seedDatabase } from'./test-data'; asyncfunction globalSetup() { await seedDatabase({ standardUsers: 5, adminUsers: 1, products: 50 }); } // 測(cè)試中直接使用 test('用戶操作', async ({ page }) => { await page.goto(/user/${process.env.TEST_USER_ID}); // 使用預(yù)置用戶 // 測(cè)試邏輯... }); </pre>

我們?yōu)闇y(cè)試環(huán)境準(zhǔn)備了快照數(shù)據(jù)庫,每個(gè)測(cè)試套件開始時(shí)恢復(fù)快照,這比每個(gè)測(cè)試都清理數(shù)據(jù)快得多。

6. 資源攔截:阻止不必要的加載

頁面加載的圖片、字體、分析腳本對(duì)測(cè)試毫無價(jià)值:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 在配置中或beforeEach中 await page.route('**/*.{png,jpg,jpeg,gif,svg}', route => route.abort()); await page.route('**/analytics.js', route => route.abort()); await page.route('**/ads/*', route => route.abort()); // 或者更精細(xì)的控制 await page.route('**/*', route => { const resourceType = route.request().resourceType(); // 只允許必要的資源類型 const blockedTypes = ['image', 'media', 'font']; if (blockedTypes.includes(resourceType)) { return route.abort(); } return route.continue(); }); </pre>

這個(gè)簡(jiǎn)單的攔截策略讓我們的頁面加載時(shí)間平均減少了40%。

7. 測(cè)試分割:平衡并行和串行

不是所有測(cè)試都能完美并行?;旌喜呗孕Ч茫?/p>

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 配置文件 exportdefault { // 為串行測(cè)試創(chuàng)建單獨(dú)的項(xiàng)目 projects: [ { name: '串行-關(guān)鍵路徑', testMatch: '**/*.critical.spec.ts', fullyParallel: false, workers: 1 }, { name: '并行-功能測(cè)試', testMatch: '**/*.spec.ts', testIgnore: '**/*.critical.spec.ts', fullyParallel: true, workers: 4 } ] }; // 或者在同一文件中混合 test.describe.serial('用戶注冊(cè)流程', () => { // 這些測(cè)試有嚴(yán)格順序依賴 test('步驟1: 填寫信息', () => {}); test('步驟2: 驗(yàn)證郵箱', () => {}); test('步驟3: 完善資料', () => {}); }); test.describe('商品瀏覽功能', () => { // 這些測(cè)試可并行 test('搜索商品', () => {}); test('篩選結(jié)果', () => {}); test('排序商品', () => {}); }); </pre>

8. 緩存利用:復(fù)用登錄狀態(tài)

登錄通常是測(cè)試中最耗時(shí)的部分:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// storageState.ts - 創(chuàng)建可復(fù)用的認(rèn)證狀態(tài) import { test as setup } from'@playwright/test'; setup('準(zhǔn)備登錄狀態(tài)', async ({ page }) => { await page.goto('/login'); await page.fill('[#username](javascript:;)', 'testuser'); await page.fill('[#password](javascript:;)', 'password123'); await page.click('[#submit](javascript:;)'); await page.waitForURL('/dashboard'); // 保存狀態(tài) await page.context().storageState({ path: 'playwright/.auth/user.json' }); }); // playwright.config.ts exportdefault { use: { // 所有測(cè)試復(fù)用該狀態(tài) storageState: 'playwright/.auth/user.json' }, globalSetup: require.resolve('./storageState.ts') }; // 測(cè)試文件中直接訪問已登錄頁面 test('訪問儀表盤', async ({ page }) => { await page.goto('/dashboard'); // 已經(jīng)是登錄狀態(tài) // 無需再次登錄 }); </pre>

注意:定期更新緩存狀態(tài),避免會(huì)話過期。

9. 基礎(chǔ)設(shè)施優(yōu)化:硬件和環(huán)境

軟件優(yōu)化到極限后,看看硬件:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;"># GitHub Actions示例 jobs: test: runs-on:ubuntu-latest strategy: matrix: shard:[1,2,3,4]# 分片執(zhí)行 steps: -uses:actions/setup-node@v3 with: node-version:'18' -run:| # 只安裝必要依賴 npm ci --omit=dev --ignore-scripts # 單獨(dú)安裝測(cè)試相關(guān)依賴 npm install @playwright/test playwright -run:| # 分片執(zhí)行 npx playwright test --shard=${{ matrix.shard }}/${{ strategy.job-total }} -uses:actions/upload-artifact@v3 if:always() with: name:playwright-report-${{matrix.shard}} path:playwright-report/ </pre>

本地開發(fā)時(shí),確保Node.js是最新LTS版本(V8引擎的持續(xù)優(yōu)化能提升執(zhí)行速度),考慮使用SSD而非HDD。

10. 持續(xù)監(jiān)控:建立性能基線

優(yōu)化不是一勞永逸的。建立監(jiān)控機(jī)制:

<pre data-tool="mdnice編輯器" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 收集性能指標(biāo) test('性能回歸檢查', async ({ page }) => { const startTime = Date.now(); // 執(zhí)行關(guān)鍵操作 await page.goto('/dashboard'); await page.click('[#load](javascript:;)-report'); await page.waitForSelector('.report-loaded'); const duration = Date.now() - startTime; // 記錄到文件或外部系統(tǒng) console.log(執(zhí)行時(shí)間: ${duration}ms); // 斷言性能要求 expect(duration).toBeLessThan(3000); // 必須小于3秒 }); // 使用Playwright的trace功能分析慢測(cè)試 exportdefault { use: { trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure', // 或者只為慢測(cè)試記錄trace trace: { mode: 'on', snapshots: true, screenshots: true } } }; </pre>

我們團(tuán)隊(duì)設(shè)置了一個(gè)儀表板,跟蹤每次提交的測(cè)試執(zhí)行時(shí)間,當(dāng)某個(gè)測(cè)試突然變慢時(shí)立即告警。

實(shí)戰(zhàn)組合拳

沒有單一銀彈,真正有效的是組合策略。我們的優(yōu)化路徑是:先上并行化(見效最快),然后優(yōu)化等待邏輯和數(shù)據(jù)準(zhǔn)備,接著實(shí)現(xiàn)分層測(cè)試和資源攔截,最后通過基礎(chǔ)設(shè)施和監(jiān)控鞏固成果。

記住:優(yōu)化應(yīng)該基于數(shù)據(jù)。使用--reporter=line查看每個(gè)測(cè)試的時(shí)間,優(yōu)先優(yōu)化最耗時(shí)的10%的測(cè)試。有些測(cè)試可能本身就應(yīng)該是慢的(如完整業(yè)務(wù)流程),不要過度優(yōu)化。

現(xiàn)在,你們的測(cè)試套件還在苦苦掙扎嗎?從第一點(diǎn)開始,今天就能看到效果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容