Danh sách bài viết

Bài 77: --fail-on-flaky-tests — Force Fix Flaky [v1.45]

Flag --fail-on-flaky-tests (v1.45+) thay đổi exit code của Playwright khi có flaky test: thay vì exit 0 (pass) như mặc định khi test pass sau retry, pipeline nhận exit code 1 (fail). Bài này đào sâu cú pháp CLI và config equivalent, behavior exit code, pattern CI strict vs lenient trên nhánh khác nhau, progressive enforcement 3 phase, sự khác biệt với --max-failures, và 4 pitfall thường gặp khi migrate.

28/05/2026
0 lượt xem
1

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-tests và config equivalent failOnFlakyTests.
  • 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-tests vớ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.

2

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.

3

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.

4

Behavior Exit Code

So sánh behavior trước và sau khi bật flag:

Kịch bản Không có flag (default) --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.

5

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).

6

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.

7

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
8

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.

9

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.

10

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ế.

11

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
12

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.

13

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: 2failOnFlakyTests: 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.

14

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--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.

15

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.