Mục lục
- Mục Tiêu Bài Học
- Vấn Đề Flaky Test Mask Qua Retry
- Cú Pháp CLI Và Config Equivalent
- Behavior Exit Code
- Reporter Behavior
- testInfo.outcome() Không Đổi
- Pattern CI Strict vs Lenient
- Use Case
- Progressive Enforcement — 3 Phase
- Kết Hợp Với retries
- Khác Biệt Với --max-failures
- Anti-Pattern
- Pitfalls
- Quiz
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài này, bạn sẽ:
- Hiểu tại sao retry mặc định mask flaky và khi nào đó là vấn đề.
- Biết cú pháp CLI
--fail-on-flaky-testsvà config equivalentfailOnFlakyTests. - Giải thích được sự thay đổi exit code: default exit 0 vs strict exit 1 khi có flaky.
- Thiết kế pattern CI với nhánh main strict và PR lenient.
- Triển khai progressive enforcement 3 phase để migrate không gây CI đỏ đột ngột.
- Phân biệt
--fail-on-flaky-testsvới--max-failures. - Nhận ra 4 pitfall khi bật flag trong team.
Phạm vi: Bài này tập trung vào flag --fail-on-flaky-tests và chiến lược CI. Cú pháp retries đã đào sâu ở bài 74. Kỹ thuật diagnose root cause flaky là chủ đề bài 78.
Vấn Đề Flaky Test Mask Qua Retry
Khi retries: 2 được bật và một test fail ở lần chạy đầu nhưng pass ở retry 1, Playwright đánh dấu test đó là flaky. Với cấu hình mặc định, CI vẫn exit code 0 — pipeline xanh.
Điều này tạo ra một vấn đề ngầm: flaky tests tồn tại trong codebase mà không ai phải xử lý. Mỗi lần pipeline chạy, flaky test consume thêm thời gian retry, nhưng không có gì thúc đẩy team fix nó vì CI luôn xanh.
Theo thời gian, flaky accumulate:
- Pipeline mất thêm thời gian vì retry overhead.
- Developers mất niềm tin vào suite — "pipeline đỏ thì cũng chỉ là flaky thôi".
- Flaky che khuất failure thực sự: một test đang fail vì regression nhưng bị nhầm tưởng là flaky.
--fail-on-flaky-tests giải quyết vấn đề này bằng cách thay đổi định nghĩa "pipeline pass": chỉ pass nếu không có flaky nào, không chỉ không có failed.
Cú Pháp CLI Và Config Equivalent
Flag CLI (yêu cầu v1.45+):
npx playwright test --fail-on-flaky-tests
Config equivalent trong playwright.config.ts:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
failOnFlakyTests: true,
});
Thứ tự ưu tiên: CLI flag thắng config. Nếu failOnFlakyTests: false trong config nhưng chạy với --fail-on-flaky-tests trên CLI, flag CLI có hiệu lực.
Flag này không có tham số số (không giống --retries=N). Đây là boolean — có mặt nghĩa là bật.
Giá trị mặc định: false — behavior cũ không thay đổi nếu không bật.
Behavior Exit Code
So sánh behavior trước và sau khi bật flag:
| Kịch bản | Không có flag (default) | Có --fail-on-flaky-tests |
|---|---|---|
| Tất cả test pass lần đầu | exit 0 | exit 0 |
| Có test flaky (pass sau retry) | exit 0 | exit 1 |
| Có test failed (fail mọi retry) | exit 1 | exit 1 |
| Có cả flaky lẫn failed | exit 1 | exit 1 |
Thay đổi duy nhất là dòng thứ hai: trước đây flaky không làm fail pipeline, sau khi bật flag thì có.
Playwright vẫn tiếp tục chạy hết tất cả tests (không dừng sớm) khi gặp flaky — chỉ exit code thay đổi. Nếu muốn dừng sớm, dùng --max-failures riêng biệt.
Reporter Behavior
Flag --fail-on-flaky-tests không thay đổi cách reporter hiển thị kết quả. Flaky test vẫn được đánh dấu là "flaky" trong HTML report, console output, và JSON/JUnit export — không phải "failed".
Điều này có nghĩa:
- Trong HTML report, flaky test vẫn hiển thị nhãn flaky với thông tin lần fail và lần pass.
- Developer có thể phân biệt được đâu là flaky, đâu là failed thực sự.
- CI pipeline fail (exit 1), nhưng report vẫn cho thấy đây là flaky chứ không phải regression.
Đây là thiết kế đúng: exit code nói với CI system phải làm gì (fail build), còn report nói với developer phải fix gì (flaky, không phải bug logic).
testInfo.outcome() Không Đổi
Khi dùng --fail-on-flaky-tests, testInfo.outcome() bên trong test và fixture vẫn trả về 'flaky' — không trả về 'failed'. Flag này chỉ ảnh hưởng tới exit code của process, không thay đổi outcome object.
test.afterEach(async ({}, testInfo) => {
// Với --fail-on-flaky-tests, flaky test vẫn trả về 'flaky'
// không trả về 'failed'
if (testInfo.outcome() === 'flaky') {
console.log('Test flaky — kiểm tra ở retry', testInfo.retry);
// Logic này vẫn chạy đúng bất kể có flag hay không
}
});
Điều này quan trọng nếu bạn có logic trong afterEach hoặc fixture dựa trên outcome() để quyết định có chụp screenshot hay collect log không. Logic đó không bị ảnh hưởng bởi flag.
Flag chỉ thay đổi CI exit code, không thay đổi outcome semantics bên trong Playwright.
Pattern CI Strict vs Lenient
Pattern phổ biến nhất: áp dụng strict chỉ trên nhánh main, lenient trên PR để không block developer khi đang làm việc.
# .github/workflows/playwright.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
# Strict: main branch không chấp nhận flaky
- name: Run tests (strict)
if: github.ref == 'refs/heads/main'
run: npx playwright test --fail-on-flaky-tests --retries=2
# Lenient: PR cho phép flaky pass để không block review
- name: Run tests (lenient)
if: github.event_name == 'pull_request'
run: npx playwright test --retries=2
Lý do tách biệt:
- PR pipeline — developer đang phát triển feature, một flaky test không liên quan đến thay đổi của họ không nên block merge.
- Main pipeline — code đã được review và merge. Đây là nơi enforce chất lượng. Flaky trên main là tech debt phải xử lý.
Biến thể: áp dụng strict trên cả release branch:
- name: Run tests (strict — main and release)
if: |
github.ref == 'refs/heads/main' ||
startsWith(github.ref, 'refs/heads/release/')
run: npx playwright test --fail-on-flaky-tests --retries=2
Use Case
CI strictness — enforce stable suite: Khi team cam kết duy trì test suite không có flaky, flag này là enforcement mechanism. Pipeline fail = có việc phải làm.
Pre-release gate: Trước khi tạo release tag hoặc deploy production, chạy pipeline với --fail-on-flaky-tests. Đây là điều kiện cứng: không cho release nếu có flaky. Flaky test trên production = potential reliability issue.
Tech debt visibility: Số lần pipeline fail vì flaky là metric trực tiếp của tech debt. Nếu pipeline chỉ fail khi có failed thực sự, flaky tích lũy ngầm và không hiện ra trong bất kỳ dashboard nào. Với flag, mỗi flaky làm pipeline đỏ → đội nhìn thấy số lượng tích lũy trong CI history.
Onboarding enforcement: Khi team mới join project hoặc khi refactor lớn, bật flag tạm thời để phát hiện flaky được introduce bởi code mới trước khi chúng tích lũy.
Progressive Enforcement — 3 Phase
Bật --fail-on-flaky-tests đột ngột trên một codebase đã có flaky accumulate → CI đỏ ngay lập tức, team frustrated. Approach đúng là migrate dần qua 3 phase:
Phase 1 — Visibility (không block): Chạy với --reporter=html, không dùng --fail-on-flaky-tests. Mục tiêu: xây dựng danh sách flaky, đo tần suất mỗi test flaky, ưu tiên fix theo impact.
# Phase 1: chỉ log, không fail
npx playwright test --retries=2 --reporter=html
Phase 2 — Warn (annotate không block): Thêm step post-process report để annotate PR comment khi có flaky. CI vẫn xanh, nhưng developer thấy warning trong PR. Có thể dùng GitHub Actions để parse JSON report và comment:
- name: Run tests
run: npx playwright test --retries=2 --reporter=json
continue-on-error: true
- name: Annotate flaky tests
if: always()
run: node scripts/annotate-flaky.js # parse report.json, comment PR
Phase 3 — Enforce (strict): Sau khi backlog flaky đã được xử lý xuống mức chấp nhận được, bật flag trên main branch. Duy trì flaky count = 0 từ đây trở đi.
# Phase 3: strict enforcement
npx playwright test --fail-on-flaky-tests --retries=2
Không có quy tắc cứng về timeline mỗi phase — phụ thuộc vào backlog flaky hiện tại và capacity fix của team.
Kết Hợp Với retries
Flag --fail-on-flaky-tests chỉ có ý nghĩa khi dùng cùng với retries > 0. Lý do: flaky được định nghĩa là test pass sau retry. Nếu không có retry, không có flaky — chỉ có passed hoặc failed.
| Kết hợp | Behavior | Khi nào dùng |
|---|---|---|
--fail-on-flaky-tests --retries=0 |
Không retry, test fail ngay → không có flaky, flag vô nghĩa | Không nên dùng (flag không có tác dụng) |
--fail-on-flaky-tests --retries=1 |
Retry 1 lần. Flaky → exit 1. Conservative. | Suite tương đối ổn định, muốn strict sớm |
--fail-on-flaky-tests --retries=2 |
Retry 2 lần. Flaky → exit 1. Phổ biến nhất. | Pattern CI chuẩn trên main branch |
--fail-on-flaky-tests --retries=3 |
Retry 3 lần. Test phải fail mọi 4 lần mới là failed. Rất khó trigger failed. | Chỉ khi external service cực kỳ không ổn định |
Lưu ý về retries cao với flag: retries=3 + --fail-on-flaky-tests làm tăng ngưỡng "failed" lên rất cao (phải fail 4/4 lần), đồng thời vẫn fail pipeline nếu pass lần thứ 4. Kết hợp này hiếm khi có giá trị thực tế.
Khác Biệt Với --max-failures
Hai flag thường bị nhầm lẫn vì cùng liên quan đến behavior khi test không pass:
| Flag | Mục đích | Khi nào kích hoạt |
|---|---|---|
--max-failures=N |
Dừng sớm sau N failures | Khi đủ N test failed (không tính flaky) |
--fail-on-flaky-tests |
Thay đổi định nghĩa "pass" | Khi có ít nhất 1 flaky, dù không có failed nào |
--max-failures trả lời câu hỏi: "có bao nhiêu failures trước khi dừng?" — điều khiển execution flow.
--fail-on-flaky-tests trả lời câu hỏi: "flaky có được coi là pass không?" — điều khiển exit code.
Hai flag có thể dùng cùng nhau:
# Dừng sau 5 failures, nhưng cũng fail nếu có flaky
npx playwright test --max-failures=5 --fail-on-flaky-tests --retries=2
Anti-Pattern
Bật flag rồi disable khi CI đỏ: Team bật --fail-on-flaky-tests, CI đỏ vì flaky, áp lực tăng, team disable flag để CI xanh lại. Kết quả: quay về điểm ban đầu, lần sau không ai dám bật lại. Cần có roadmap fix flaky trước khi bật strict.
Bật strict mà không có visibility trước: Không biết hiện tại có bao nhiêu flaky, bật strict ngay → CI đỏ nhiều build liên tiếp, team không biết ưu tiên fix cái nào trước. Phase 1 (visibility) là bắt buộc.
Dùng flag để thay thế fix: Bật flag chỉ để tạo pressure mà không allocate thời gian fix. Flag là enforcement mechanism, không phải solution. Nếu không có capacity fix flaky, flag chỉ tạo CI đỏ liên tục mà không cải thiện gì.
Áp dụng đồng đều mọi nhánh: Bật failOnFlakyTests: true trong playwright.config.ts ảnh hưởng mọi nhánh, kể cả feature branch của developer. Feature branch flaky do external service không ổn định → developer bị block. Nên dùng CI condition thay vì config-level.
Pitfalls
Pitfall 1 — Quên thêm retries khi dùng flag:
# Sai: không có retries, không có flaky, flag vô nghĩa
npx playwright test --fail-on-flaky-tests
# Đúng: cần retries để flaky có thể xảy ra
npx playwright test --fail-on-flaky-tests --retries=2
Nếu quên --retries=N, flag không gây hại nhưng cũng không có tác dụng gì. Flaky không thể xảy ra khi không có retry.
Pitfall 2 — Đặt flag trong config và quên ảnh hưởng local: Developer chạy local với retries: 2 và failOnFlakyTests: true trong config. Một flaky test làm script local exit 1, tools như pre-commit hook hay IDE test runner hiểu sai là "failed". Tốt hơn: chỉ dùng CLI flag trong CI, không đặt trong config.
Pitfall 3 — Không phân biệt severity flaky: Flag treat tất cả flaky như nhau — flaky 1% (rất hiếm) và flaky 50% (rất thường xuyên) đều fail CI với cùng exit code 1. Nếu cần phân loại, phải implement tracking bên ngoài (parse report, tính tần suất) chứ Playwright không có built-in severity.
Pitfall 4 — Migration không gradual trên monorepo: Monorepo với nhiều projects trong playwright.config.ts. Bật failOnFlakyTests: true globally ảnh hưởng tất cả projects cùng lúc. Nếu một project cũ có nhiều flaky, toàn bộ CI đỏ. Nên migrate từng project bằng cách override CLI theo project (--project=stable-suite --fail-on-flaky-tests) trước khi áp dụng global.
Quiz
Câu 1. Suite có 100 tests, 3 test flaky (pass sau retry), 0 test failed. Chạy với --fail-on-flaky-tests --retries=2. Exit code là bao nhiêu? Trong HTML report, 3 test đó hiển thị trạng thái gì?
Đáp án
Exit code là 1 (pipeline fail). Trong HTML report, 3 test vẫn hiển thị nhãn flaky — không phải "failed". Flag chỉ thay đổi exit code, không thay đổi trạng thái trong report.
Câu 2. Config có failOnFlakyTests: false. Developer chạy CI với lệnh npx playwright test --fail-on-flaky-tests --retries=2. Flag config hay CLI có hiệu lực?
Đáp án
CLI flag thắng. --fail-on-flaky-tests trên CLI override failOnFlakyTests: false trong config. Pipeline sẽ fail nếu có flaky.
Câu 3. Trong afterEach, bạn gọi testInfo.outcome() cho một test vừa flaky với --fail-on-flaky-tests đang bật. Giá trị trả về là gì?
Đáp án
'flaky'. Flag không thay đổi outcome semantics. testInfo.outcome() vẫn trả về 'flaky', không trả về 'failed'.
Câu 4. Team muốn bật strict enforcement nhưng hiện có 15 flaky tests chưa fix. Nên áp dụng chiến lược nào và tại sao không bật flag ngay?
Đáp án
Dùng progressive enforcement: Phase 1 — chạy với HTML report để map 15 flaky, đo tần suất, lập backlog ưu tiên. Phase 2 — annotate PR comment khi có flaky (CI vẫn xanh). Phase 3 — sau khi backlog giảm xuống mức chấp nhận, bật --fail-on-flaky-tests trên main. Bật ngay → CI đỏ liên tục → team frustrated → flag bị disable.
Câu 5. Sự khác biệt core giữa --max-failures=3 và --fail-on-flaky-tests là gì?
Đáp án
--max-failures=3 kiểm soát execution flow: dừng suite sau 3 failures (không tính flaky). --fail-on-flaky-tests kiểm soát exit code definition: thay đổi điều kiện pipeline pass. Hai flag giải quyết vấn đề khác nhau và có thể dùng cùng nhau.
Bài Tiếp Theo
Bài 78: Diagnose Flaky Tests — kỹ thuật tìm nguyên nhân flaky: trace, --repeat-each, screenshot on retry, network isolation, và pattern phân loại flaky theo root cause.
