1. Flaky là gì và vì sao nó giết niềm tin ở quy mô lớn
Một test flaky là test cho kết quả khác nhau trên cùng một commit mà không có thay đổi nào ở code sản phẩm: lúc xanh, lúc đỏ. Ở một dự án nhỏ mười test, một ca flaky chỉ gây khó chịu. Nhưng ở quy mô SaaS với năm nghìn test chạy trên hàng trăm pull request mỗi ngày, dù chỉ 1% flake rate cũng có nghĩa là gần như mọi lần chạy đều đỏ vì một lý do vô nghĩa. Đội ngũ mất niềm tin vào bộ test, bắt đầu bấm rerun theo phản xạ và cuối cùng bỏ qua cả những lần đỏ thật.
Bài viết này tiếp cận flaky như một bài toán vận hành ở quy mô tổ chức chứ không phải mẹo vặt lẻ tẻ. Chúng ta sẽ đi qua năm nhóm nguyên nhân gốc, cơ chế web-first auto-waiting của Playwright giúp diệt phần lớn chúng, chiến lược retry và trace đúng cách, cô lập test, quy trình quarantine có SLA, cách đo flake rate như một chỉ số sức khoẻ, và cuối cùng là chính sách cấp tổ chức để giữ tỉ lệ flaky luôn dưới ngưỡng. Mọi ví dụ đều xoay quanh nguyên tắc oracle: khẳng định bất biến nghiệp vụ, không khẳng định 'màn hình hiện ra'.
2. Năm nhóm nguyên nhân gốc của flaky
Trước khi chữa, phải phân loại. Gần như mọi ca flaky rơi vào năm nhóm. Nhóm race: test kiểm tra một phần tử trước khi ứng dụng render xong, hoặc bấm nút trước khi handler được gắn. Nhóm animation: một transition CSS làm phần tử dịch chuyển đúng lúc click, khiến toạ độ trượt. Nhóm network: API chậm bất thường, retry ngầm, hoặc thứ tự response không xác định. Nhóm thời gian: test phụ thuộc đồng hồ thật, timezone, hoặc mốc nửa đêm. Nhóm dữ liệu: nhiều test dùng chung một tài khoản, một bản ghi, và giẫm chân nhau khi chạy song song.
Bảng quyết định: triệu chứng → nhóm → cách chữa
- Đỏ ngẫu nhiên ở 'element not found' → race → dùng locator + auto-wait, bỏ sleep.
- Click trượt hoặc 'element is not stable' → animation → chờ animation xong hoặc tắt animation.
- Timeout rải rác ở bước gọi API → network → mock hoặc route, tăng timeout đúng chỗ.
- Đỏ chỉ vào buổi sáng hoặc cuối tháng → thời gian → freeze clock, cố định timezone.
- Đỏ khi tăng workers, xanh khi chạy một mình → dữ liệu → cô lập state từng test.
3. Web-first auto-waiting: vũ khí gốc chống race
Điểm khác biệt lớn nhất giữa Playwright và các công cụ đời cũ là cơ chế auto-waiting của web-first assertions. Khi bạn viết expect(locator).toBeVisible(), Playwright không chụp trạng thái một lần rồi so sánh; nó thăm dò lặp lại cho tới khi điều kiện đúng hoặc hết timeout. Tương tự, mỗi action như click sẽ tự chờ phần tử actionable: hiển thị, ổn định vị trí, bật, nhận được sự kiện và không bị phần tử khác che. Nhờ đó phần lớn race biến mất mà bạn không phải viết một dòng chờ nào.
import { test, expect } from '@playwright/test';
test('giỏ hàng cập nhật tổng tiền — auto-wait làm việc thay ta', async ({ page }) => {
await page.goto('/cart');
// KHÔNG cần waitForSelector: click tự chờ nút actionable
await page.getByRole('button', { name: 'Thêm gói Pro' }).click();
// web-first assertion: thăm dò lặp tới khi tổng = 490.000đ hoặc hết timeout
await expect(page.getByTestId('cart-total')).toHaveText('490.000₫');
// oracle nghiệp vụ: số dòng hàng khớp, không âm
await expect(page.getByRole('listitem')).toHaveCount(2);
});Chìa khoá là phân biệt hai loại chờ. Chờ ngầm qua assertion và action là loại đúng: nó gắn với một điều kiện xác định và tự dừng ngay khi điều kiện thoả. Chờ tường minh bằng thời gian cố định là loại sai: nó luôn hoặc quá ngắn ở máy chậm, hoặc quá dài làm CI ì ạch. Nguyên tắc vàng: mọi lúc bạn định gõ sleep, hãy hỏi 'tôi đang chờ điều kiện gì?' rồi viết đúng assertion cho điều kiện đó.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!