Wiki

← Tất cả bài wiki

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.
  • retries trong 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-scope test.describe.configure({ retries: 2 }) — scope nhỏ override scope lớn.
  • trace: 'on-first-retry' trong use — 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 result có trường retry; 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.retry trong 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: 2 trê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 cho waitForTimeout, đả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.retry tạ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: 5 nghĩ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.retry bắt đầu từ 0: điều kiện "đang chạy lại" phải viết retry > 0, nhiều người nhầm check retry === 1.
  • --retries khô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=10 chạy lặp một test nhiều lần để đo độ ổn định trước khi merge vào suite.