Retries & Xử lý Flaky Test (Test chập chờn)
automationplaywright
Retry là cơ chế tự động chạy lại test khi fail — giúp CI đỡ fail oan vì test chập chờn (flaky) do timing hay môi trường. Nhưng retry chỉ là "băng gạc" tạm thời: flaky test cần được phát hiện qua report, bật trace để soi nguyên nhân, và sửa tận gốc chứ không phải giấu dưới thảm bằng retry cao.
Khái niệm & Bản chất
- Flaky test = cùng code, cùng môi trường nhưng kết quả lúc pass lúc fail. Nguyên nhân kinh điển: race condition/timing (wait không đủ), dữ liệu dùng chung giữa test, animation/render chậm, third-party không ổn định.
retriestrong config hoặc--retries=N: test fail sẽ được chạy lại tối đa N lần trong cùng run. Pass ở lần retry nào thì kết quả cuối là "flaky" — report đánh dấu riêng, không phải "passed" sạch.- Fail hết mọi lần retry → test failed. Vậy retry không che được bug thật, chỉ hấp thụ nhiễu.
testInfo.retry= index lần chạy hiện tại (0 = lần đầu, >0 = đang retry) — dùng trong fixture/hook để đổi hành vi giữa các lần chạy, ví dụ tạo user mới.- Mỗi lần retry là một lần chạy độc lập hoàn toàn: worker khởi tạo lại fixture, page, context — nhưng side-effect lần trước (data đã ghi DB) VẪN CÒN nếu không dọn.
Cách hoạt động & API chính
- Ba cấp đặt retry: config
retries: 2, CLI--retries=2, và per-scopetest.describe.configure({ retries: 2 })— scope nhỏ override scope lớn. trace: 'on-first-retry'tronguse— chỉ bắt đầu ghi trace từ lần retry ĐẦU TIÊN: lần chạy đầu không tốn dung lượng, lần fail có full trace để debug.- Kết hợp
screenshot: 'only-on-failure'vàvideo: 'retain-on-failure'để artefact chỉ tồn tại khi test fail thật sự. testInfo.retryđọc được trong test, hook, và fixture (tham số thứ 3 của hàm fixture).- Trong report JSON/JUnit, mỗi
resultcó trườngretry; HTML report nhóm các attempt của một test lại để soi từng lần. test.fail()/test.fixme()đánh dấu test "expected fail" — liên quan quy trình xử lý test lỗi nhưng KHÔNG phải cơ chế retry.
Ví dụ & Cú pháp thực tế
- Config retry chỉ trên CI + bật trace đúng lúc cần:
``
ts // playwright.config.ts export default defineConfig({ retries: process.env.CI ? 2 : 0, // local fail ngay để thấy vấn đề use: { trace: 'on-first-retry', // trace bắt đầu ghi từ retry lần 1 screenshot: 'only-on-failure', video: 'retain-on-failure', }, });`` - Retry riêng cho một nhóm test vốn hay chập chờn:
``
ts import { test, expect } from '@playwright/test'; // chỉ nhóm này được retry 3 lần, phần còn lại theo config test.describe.configure({ retries: 3 }); test.describe('payment flow', () => { test('thanh toán thẻ thành công', async ({ page }) => { // flow qua cổng thanh toán sandbox vốn hay timeout }); });`` - Dùng
testInfo.retrytrong fixture để tạo data mới mỗi lần chạy lại: ``ts export const test = base.extend<{ freshUser: { email: string } }>({ freshUser: async ({}, use, testInfo) => { // mỗi lần retry tạo user mới — tránh data bị "cháy" ở attempt trước const user = await createUser(qa-r${testInfo.retry}-${Date.now()}); await use(user); await deleteUser(user.id); // teardown: dọn sạch cho lần retry sau }, });`` - Phân biệt attempt trong log của custom reporter:
``
ts onTestEnd(test: TestCase, result: TestResult) { const tag = result.retry > 0 ?retry #${result.retry}: 'lần đầu'; console.log(${result.status} [${tag}] ${test.title}); }``
Best practices & Khi nào dùng
retries: 2trên CI,0ở local — local cần fail ngay để developer thấy vấn đề; CI cần hấp thụ nhiễu môi trường không kiểm soát được.- Luôn đi kèm
trace: 'on-first-retry'+ screenshot/video retain-on-failure — retry mà không có artefact thì không debug được gì. - Coi flaky là bug: report đánh dấu "flaky" thì phải có quy trình — ticket, quarantine (tách nhóm cho phép fail), ưu tiên sửa. Suite nhiều flaky = suite không ai tin.
- Sửa gốc trước khi tăng retry: dùng auto-waiting assertion (
toBeVisible,toHaveText) thay chowaitForTimeout, đảm bảo mỗi test data riêng, mock third-party không ổn định. - Test phải idempotent — chạy lại không hỏng: cleanup trong teardown của fixture, hoặc dùng
testInfo.retrytạo tài nguyên mới mỗi attempt.
Gotchas, So sánh & Lưu ý
- Retry cao che giấy lỗi thật và làm suite chậm —
retries: 5nghĩa là chấp nhận một test có thể chạy 6 lần; giới hạn thực tế 1-2, tối đa 3 cho nhóm đặc biệt. - Test không dọn side-effect sẽ retry fail lặp lại theo cùng một kiểu — ví dụ lần 1 tạo user trùng email xong fail, lần 2 fail ngay ở bước tạo user.
fullyParallel+ data chung giữa worker là combo flaky kinh điển — mỗi worker/test nên có account và dataset riêng.testInfo.retrybắt đầu từ 0: điều kiện "đang chạy lại" phải viếtretry > 0, nhiều người nhầm checkretry === 1.--retrieskhông giúp test fail deterministic — logic sai thì fail cả N lần, chỉ tốn thêm thời gian CI.- Công cụ phát hiện flaky chủ động:
--repeat-each=10chạy lặp một test nhiều lần để đo độ ổn định trước khi merge vào suite.
