Mục lục
- Mục Tiêu Bài Học
- Cú Pháp Và Cơ Chế
- Khác Biệt Với --retries
- Use Case Stability Check
- Output Và Cách Đọc Kết Quả
- Stability Metric — Pass Rate
- Stability Budget
- Pattern Trước Khi Push
- Pattern Nightly CI Schedule
- Combine Với --workers
- Combine Với --max-failures
- Combine Với --grep Và --project
- Config repeatEach Trong playwright.config.ts
- Performance Overhead
- So Sánh Với bisect
- Limitation
- Pitfalls
- Quiz
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài này, bạn sẽ:
- Biết phân biệt mục đích của
--repeat-each(chạy đủ N lần) và--retries(chỉ retry khi fail). - Hiểu stability metric: pass rate = passed instances / total instances, và ngưỡng đánh giá định lượng theo tier.
- Xây dựng được stability budget cho test suite: critical 99.9%, stable 99%, acceptable 95%, flaky <95%.
- Viết được workflow nightly CI stability check với GitHub Actions scheduled job.
- Kết hợp
--repeat-eachvới--workers,--max-failures,--grepđúng mục đích. - Nhận diện 4 pitfall phổ biến và biết cách tránh.
Lưu ý phạm vi: Series 1 bài 71 đã đào sâu cú pháp, workflow detect flaky, combine với --retries=0, và script wrapper. Bài này tập trung vào angle stability check: đo pass rate theo định nghĩa định lượng, stability budget, nightly CI pattern, và limitation của approach này.
Cú Pháp Và Cơ Chế
--repeat-each=N chạy mỗi test đúng N lần, bất kể pass hay fail ở lần trước. Đây là cách Playwright cung cấp số lần chạy cố định để đo tần suất pass/fail.
# Chạy mỗi test 10 lần
npx playwright test --repeat-each=10
# Combine với filter theo tag
npx playwright test --grep "@flaky" --repeat-each=50
# Combine với project và file cụ thể
npx playwright test --project=chromium --repeat-each=20 path/to/test.spec.ts
Số lượng instances thực tế:
- 10 test ×
--repeat-each=5= 50 instances. Mỗi instance có outcome riêng. - Nhân thêm số project: 10 test × 2 project ×
--repeat-each=5= 100 instances. - Mỗi instance xuất hiện trong report như một entry độc lập với label
[repeat-each: 1],[repeat-each: 2], ...
Verify số lượng trước khi chạy bằng --list:
npx playwright test --grep "@critical" --list
# Đếm số dòng output, nhân N để biết tổng instances
Khác Biệt Với --retries
Hai flag có hành vi ngược nhau về điều kiện kích hoạt lần chạy tiếp theo:
| Khía cạnh | --retries=N |
--repeat-each=N |
|---|---|---|
| Kích hoạt khi nào | Chỉ khi test fail | Luôn chạy đủ N lần, dù pass hay fail |
| Test pass ngay lần đầu | Chạy 1 lần, không retry | Vẫn chạy đủ N lần |
| Mục đích | Giảm noise từ flaky trong CI để pipeline pass | Đo pass rate thực tế, xác nhận stability |
| Outcome cuối | Pass nếu có ít nhất 1 lần pass | Mỗi instance tính riêng, tổng hợp pass/fail |
| Thời gian chạy | Best-case: 1× (pass ngay). Worst-case: (N+1)× | Cố định = N× thời gian 1 lần |
Nếu dùng --repeat-each khi config có retries: 2 mà không override, kết quả đo được sẽ sai: mỗi instance có 3 cơ hội pass, fail rate bị che. Luôn kèm --retries=0 khi mục đích là đo stability thật:
npx playwright test --repeat-each=20 --retries=0
Use Case Stability Check
4 use case chính khi dùng --repeat-each với góc nhìn stability:
- Confirm stability của test mới viết — test mới viết xong pass một lần không đủ. Chạy 20 lần xem có phát sinh fail không. Nếu 0/20 fail, test đủ stable để merge.
- Reproduce intermittent bug — CI báo fail không đều, không có error rõ ràng. Chạy 50 lần local để catch đủ sample, trace lần fail, tìm pattern.
- Regression check critical path — sau release hoặc infrastructure change, chạy test critical path nhiều lần để đảm bảo không có flaky mới xuất hiện.
- Đo variance duration — nếu cần biết test mất bao lâu trong worst/best/avg case, lặp N lần và đọc duration từng instance.
Phân biệt với use case detect flaky (Series 1 bài 71): stability check nhấn mạnh việc xác nhận định lượng bằng pass rate, không chỉ confirm "có flaky không".
Output Và Cách Đọc Kết Quả
Giả sử 2 test, --repeat-each=5, --retries=0, --reporter=list:
Running 10 tests using 2 workers
✓ 1 [chromium] › checkout.spec.ts:5 › completes checkout (1.2s)
✓ 2 [chromium] › checkout.spec.ts:5 › completes checkout (1.4s)
✘ 3 [chromium] › checkout.spec.ts:5 › completes checkout (5.0s)
✓ 4 [chromium] › checkout.spec.ts:5 › completes checkout (1.3s)
✓ 5 [chromium] › checkout.spec.ts:5 › completes checkout (1.2s)
✓ 6 [chromium] › login.spec.ts:3 › user can login (0.8s)
✓ 7 [chromium] › login.spec.ts:3 › user can login (0.9s)
✓ 8 [chromium] › login.spec.ts:3 › user can login (0.8s)
✓ 9 [chromium] › login.spec.ts:3 › user can login (0.9s)
✓ 10 [chromium] › login.spec.ts:3 › user can login (0.8s)
1 failed, 9 passed (14.5s)
Cách đọc:
checkout: 4/5 pass → pass rate 80%. Cần điều tra.login: 5/5 pass → pass rate 100%. Stable.- Instance fail thường có duration cao hơn (5.0s vs 1.2s) vì hit assertion timeout trước khi fail.
- Dùng
--reporter=listđể mỗi instance in thành dòng riêng. Reporterdotgộp lại, khó tra cứu pattern. - HTML reporter giữ mỗi instance như một row, có thể click vào xem trace của lần fail cụ thể.
Stability Metric — Pass Rate
Pass rate là số liệu cốt lõi khi đánh giá stability bằng --repeat-each:
pass rate = passed instances / total instances × 100%
Ví dụ: 18/20 passed → 90% stable
20/20 passed → 100% stable
1/20 failed → 95% stable
Chú ý về statistical significance: N nhỏ có thể cho kết quả không chính xác.
| Fail rate thật | N=10 | Xác suất quan sát 0 fail |
|---|---|---|
| 5% | 10 | 59.9% — quá cao, dễ bỏ sót |
| 5% | 20 | 35.8% |
| 5% | 50 | 7.7% |
| 1% | 100 | 36.6% |
Với fail rate thấp (≤5%), cần N ≥ 50 mới có ý nghĩa thống kê. N=20 phù hợp để phát hiện fail rate ≥ 10%. Khi cần xác nhận stability cao (>99%), cần N ≥ 100–200.
Stability Budget
Stability budget là ngưỡng pass rate tối thiểu theo tier, giúp team có quyết định rõ ràng thay vì đánh giá chủ quan "ổn rồi":
| Tier | Pass rate tối thiểu | Mức fail chấp nhận | Áp dụng cho |
|---|---|---|---|
| Critical | ≥ 99.9% | ≤ 1/1000 lần chạy | Payment, auth, data integrity |
| Stable | ≥ 99% | ≤ 1/100 lần chạy | Core business flow, checkout, profile |
| Acceptable | ≥ 95% | ≤ 5/100 lần chạy | Edge case, UI detail, non-critical path |
| Flaky | < 95% | > 5/100 lần chạy | Cần fix trước khi merge hoặc đánh tag @flaky |
Tier "Critical" yêu cầu N lớn hơn để verify. Để xác nhận 99.9% stable với độ tin cậy 95%, cần N ≈ 300. Trong thực tế, chạy 100–200 lần local + nightly CI là trade-off hợp lý.
Team có thể điều chỉnh ngưỡng theo đặc thù product. Điểm quan trọng là định nghĩa rõ ràng bằng số, không phải "trông ổn".
Pattern Trước Khi Push
Khi viết test mới, chạy stability check local trước khi push để không introduce flaky vào CI:
# Confirm test mới stable trước khi push
npx playwright test new-feature.spec.ts --repeat-each=20 --retries=0 --reporter=list
Thêm vào package.json để tiện gọi:
{
"scripts": {
"test:stability": "playwright test --retries=0 --reporter=list"
}
}
npm run test:stability -- new-feature.spec.ts --repeat-each=20
Threshold khuyến nghị local:
- Test thường: 0/20 fail trước khi push.
- Test critical path (payment, auth): 0/50 fail.
- Nếu thấy 1+ fail trong 20 lần: investigate ngay, không push.
Pattern Nightly CI Schedule
Stability check trên CI được tổ chức tốt nhất dưới dạng scheduled job chạy ngoài giờ, tách biệt hoàn toàn khỏi pipeline PR thường ngày:
jobs:
stability:
runs-on: ubuntu-latest
# Chỉ chạy theo schedule — không trigger trên push/PR thường
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx playwright install --with-deps chromium
# Chạy critical tests 10 lần mỗi test
- run: npx playwright test --grep "@critical" --repeat-each=10 --retries=0 --reporter=list
continue-on-error: true
id: stability_run
# Report kết quả — có thể gửi Slack/webhook nếu có fail
- name: Upload stability report
uses: actions/upload-artifact@v4
if: always()
with:
name: stability-report
path: playwright-report/
on:
schedule:
- cron: '0 2 * * *' # 2am hàng ngày
Điểm quan trọng trong config này:
continue-on-error: true— cho phép job tiếp tục upload report dù có fail. Điều chỉnh tuỳ theo policy: setfalsenếu muốn fail job khi phát hiện flaky.- Dùng
--grep "@critical"thay vì full suite để kiểm soát chi phí CI. - Chromium đủ để đo stability. Chạy multi-browser nhân N lần thêm tốn kém trong nightly job.
- Kết quả upload artifact để có thể phân tích sau.
Với project lớn hơn, có thể tách thành 2 job: một cho @critical với N=20, một cho @suspect với N=10.
Combine Với --workers
Số worker ảnh hưởng đến wall-clock time và có thể ảnh hưởng kết quả nếu test có race condition:
# 50 instances trên 5 workers → 10 lần parallel, nhanh hơn
npx playwright test --repeat-each=10 --workers=5
# Serial — cô lập race condition, chạy chậm hơn nhưng kết quả rõ hơn
npx playwright test --repeat-each=20 --workers=1 --retries=0
Hướng dẫn chọn worker count:
- Nếu đang đo stability lần đầu và chưa biết nguyên nhân flaky: chạy với
--workers=1trước. Kết quả baseline không bị ảnh hưởng bởi parallelism. - Sau đó chạy lại với
--workers=4hoặc cao hơn. Nếu fail rate tăng đáng kể → flaky do race condition giữa workers. - Nếu fail rate tương đương cả 2 mode → nguyên nhân trong logic test, không phải parallelism.
- Với nightly CI stability check, dùng workers vừa đủ để tiết kiệm thời gian mà không che race condition: thường
--workers=2là balance tốt.
Combine Với --max-failures
--max-failures=K dừng toàn bộ run khi có K instances fail — hữu ích để không chờ hết N lần khi flaky đã rõ:
# Dừng ngay khi 1 fail — verify fix cần 0/20 clean
npx playwright test new.spec.ts --repeat-each=20 --retries=0 --max-failures=1
# Dừng sau 3 fail — biết "có flaky" mà không cần chờ hết 50 lần
npx playwright test --grep "@suspect" --repeat-each=50 --retries=0 --max-failures=3
Chọn giá trị K:
--max-failures=1: dùng khi verify fix — kỳ vọng 0 fail. Fail ngay lần đầu → dừng và debug ngay.--max-failures=3–5: dùng khi chạy stability check nhanh — chỉ cần xác nhận "có flaky không" mà không cần đếm đủ pass rate chính xác.- Không đặt
--max-failureskhi cần đo pass rate chính xác — cần chạy đủ N lần để tính pass rate.
Combine cả ba flag cho nightly CI:
npx playwright test \
--grep "@critical" \
--repeat-each=20 \
--retries=0 \
--max-failures=5 \
--workers=2 \
--reporter=list
Combine Với --grep Và --project
Giới hạn scope là bắt buộc khi dùng --repeat-each — full suite × N instance rất tốn thời gian:
# Filter theo tag
npx playwright test --grep "@critical" --repeat-each=10
# Filter theo tên test (regex)
npx playwright test --grep "checkout|payment" --repeat-each=20 --retries=0
# Filter theo file + project
npx playwright test --project=chromium --repeat-each=20 checkout.spec.ts
# Exclude tag không cần stability check
npx playwright test --grep-invert "@slow" --repeat-each=10
Tính tổng instances trước khi chạy:
# Đếm số test match grep (mỗi dòng là 1 test × project)
npx playwright test --grep "@critical" --list | wc -l
# Giả sử 15 dòng, repeat-each=10 → 150 instances
Convention tagging cho stability check:
@critical— test cần stability ≥ 99.9%, run nightly với N=20.@suspect— test đang nghi flaky, cần monitor, run nightly với N=10.- Test không có tag: chỉ run stability check khi cần investigate cụ thể.
Config repeatEach Trong playwright.config.ts
Option repeatEach trong config tương đương với flag CLI:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Chỉ dùng repeatEach khi môi trường CI cụ thể cần stability check
repeatEach: process.env.STABILITY_CHECK ? 10 : 1,
});
Trigger từ CI:
- name: Stability check
env:
STABILITY_CHECK: 'true'
run: npx playwright test --grep "@critical"
Khi nào dùng config vs CLI flag:
- CLI flag — dùng khi investigate ad-hoc, debug local, hoặc chạy on-demand. Linh hoạt, không ảnh hưởng run thường ngày.
- Config với env var — dùng khi có dedicated stability job trên CI cần tái sử dụng logic. Tránh đặt giá trị cứng trong config vì mọi lần chạy bình thường sẽ bị ảnh hưởng.
Không đặt repeatEach: 10 cứng trong config (không có env guard) — pipeline PR thường ngày sẽ chạy chậm gấp 10 lần.
Performance Overhead
Thời gian chạy tăng tuyến tính theo N — đây là overhead cố định, không thể giảm bằng cách tối ưu logic test:
| Scope | Số test | repeat-each | Workers | Tổng instances | Ước tính (2s/test) |
|---|---|---|---|---|---|
| 1 test critical | 1 | 50 | 1 | 50 | ~1.7 phút |
| File mới viết | 5 | 20 | 2 | 100 | ~1.7 phút |
| Tag @critical (10 test) | 10 | 20 | 4 | 200 | ~1.7 phút |
| Nightly 30 test × N=10 | 30 | 10 | 4 | 300 | ~2.5 phút |
| Full suite × N=10 | 100 | 10 | 4 | 1000 | ~8 phút |
Workers song song giảm wall-clock time tỉ lệ thuận. Với 4 workers và 200 instances, wall-clock time xấp xỉ 200 × 2s / 4 = 100s. Tuy nhiên setup/teardown overhead và browser launch time làm thực tế cao hơn một chút.
Quy tắc thực tế: nightly stability check nên hoàn thành trong 5–10 phút để không ảnh hưởng luồng CI ban ngày. Nếu cần hơn, chia thành nhiều job song song trên CI.
So Sánh Với bisect
--repeat-each và npx playwright test --bisect (hoặc --bisect-on-failure trước v1.38) có mục đích khác nhau hoàn toàn:
| Công cụ | Mục đích | Câu hỏi trả lời |
|---|---|---|
--repeat-each=N |
Xác nhận flakiness tồn tại và đo pass rate | "Test này flaky không? Fail bao nhiêu lần / N lần?" |
--bisect |
Tìm test nào trong suite gây ra flaky cho một test khác | "Test nào trong suite, khi chạy trước, làm test X fail?" |
Workflow kết hợp cả hai:
- Dùng
--repeat-each=20để xác nhận test X thực sự flaky (pass rate < 100%). - Nếu nghi flaky do thứ tự chạy (test X stable khi chạy riêng nhưng fail khi chạy cùng suite), dùng
--bisectđể tìm test nào gây ra. - Fix isolation issue, sau đó dùng
--repeat-each=20lại để verify đã hết flaky.
Limitation
1. Không reveal root cause
--repeat-each chỉ trả lời "fail bao nhiêu lần". Không cho biết tại sao fail — phải xem trace, screenshot, log từng instance fail thủ công.
2. Không deterministic về kết quả
Pass 0/N lần không guarantee test sẽ stable trong tương lai. Test có fail rate 1% pass 0/100 lần với xác suất 36.6%. Stability check bằng --repeat-each cung cấp confidence level, không phải proof.
3. Time-bounded — có thể miss flaky rate thấp
Test flaky với fail rate 1/100 lần cần N ≥ 300 để có 95% confidence catch ít nhất 1 fail. Trong thực tế, chạy N=20–50 là phổ biến — miss được flaky rate <5%.
4. Environment-dependent
Test pass 0/50 lần local không đồng nghĩa stable trên CI. CI runner có khác biệt: CPU load, network latency, filesystem speed. Flaky chỉ xuất hiện trên CI cần chạy stability check ngay trên CI runner, không phải local.
5. Heavy cost trên CI
10× test count = 10× CI time và 10× CI cost. Không phù hợp cho mọi PR. Phải tổ chức thành scheduled job riêng, không mix vào pipeline thường ngày.
Pitfalls
Pitfall 1: repeat-each quá lớn trên full suite
Chạy npx playwright test --repeat-each=100 trên full suite 50 test = 5000 instances. Pipeline nổ, blocker team. Luôn dùng --grep hoặc đường dẫn file để giới hạn scope trước khi tăng N.
Pitfall 2: Nhầm repeat-each với retries
--repeat-each=5 chạy đúng 5 lần, kể cả khi pass ngay. --retries=4 chạy tối đa 5 lần chỉ khi fail. Nếu dùng nhầm --retries để đo stability, test pass ngay sẽ chỉ chạy 1 lần — không đo được gì.
Pitfall 3: Đặt repeatEach cứng trong config không có env guard
repeatEach: 10 trong playwright.config.ts mà không check env var → mọi lần dev chạy test local hoặc pipeline PR đều chậm 10×. Luôn guard bằng env var hoặc chỉ dùng CLI flag.
Pitfall 4: Pass 0/20 local → kết luận "ổn" mà không kiểm CI
0/20 lần fail local không đủ để kết luận test stable trên CI. Flaky do CI runner load hoặc Docker network thường không reproducible local. Sau khi pass local, vẫn cần chạy ít nhất 1 nightly stability check trên CI runner để confirm.
Pitfall 5: Pin repeat-each cao trong pipeline chính
Stability check phải là job riêng, chạy schedule, không phải step trong pipeline PR. Nếu nhét --repeat-each=10 vào pipeline thường ngày, mỗi PR mất 10× thời gian — team sẽ tắt nó đi hoặc không review kết quả.
Quiz
Câu 1. Bạn chạy npx playwright test checkout.spec.ts --repeat-each=20 và thấy 18/20 pass. Pass rate là bao nhiêu và test thuộc tier nào theo stability budget?
Đáp án
Pass rate = 18/20 = 90%. Theo stability budget: <95% → tier Flaky, cần fix trước khi merge. Với 2/20 fail, fail rate ≈ 10% — rõ ràng cần investigate root cause ngay.
Câu 2. Test có fail rate thật là 5%. Bạn chạy --repeat-each=20 và thấy 0/20 fail. Có thể kết luận test stable không?
Đáp án
Không thể kết luận chắc chắn. Xác suất quan sát 0/20 fail khi fail rate thật là 5% là khoảng 36%. Cần tăng N ≥ 50 (xác suất miss còn ~7.7%) hoặc N ≥ 100 để có confidence cao hơn. Kết quả 0/20 chỉ cho biết "fail rate có thể thấp", không phải "test stable".
Câu 3. Bạn cần verify fix cho test payment critical. Yêu cầu: dừng ngay khi có fail đầu tiên, chạy tối đa 50 lần, serial, không retry. Viết command.
Đáp án
npx playwright test --grep "payment" \
--repeat-each=50 \
--retries=0 \
--workers=1 \
--max-failures=1 \
--reporter=list
--max-failures=1 dừng ngay khi fail đầu tiên. --workers=1 đảm bảo serial, loại trừ race condition. --retries=0 không che kết quả. 50 lần đủ để có confidence với fail rate <5%.
Câu 4. Team muốn đặt nightly stability check cho 15 test critical, mỗi test chạy 20 lần, 4 workers. Ước tính thời gian chạy nếu mỗi test mất trung bình 3s?
Đáp án
Tổng instances = 15 × 20 = 300. Thời gian tổng (serial) = 300 × 3s = 900s = 15 phút. Với 4 workers: 900s / 4 = 225s ≈ 3.75 phút wall-clock time (chưa tính overhead khởi động worker). Nằm trong ngưỡng chấp nhận được cho nightly job.
Câu 5. Tại sao không nên dùng --repeat-each kết hợp với --update-snapshots?
Đáp án
Mỗi iteration ghi đè baseline file snapshot. Iteration cuối thắng — không tạo ra "ảnh trung bình", chỉ lưu snapshot của lần chạy cuối cùng. Nếu render không deterministic (animation, font fallback), lần cuối có thể ra ảnh khác với lần trước, gây confusion khi review diff. Cách đúng để xử lý snapshot không ổn định là cấu hình maxDiffPixels, threshold, hoặc disable animation trước khi chụp.
Bài Tiếp Theo
Bài 81: Timeout Hierarchy — globalTimeout, testTimeout, actionTimeout — mở nhóm A.9 Timeouts: thứ bậc timeout từ global xuống action, cách override theo layer, và pitfall khi cấu hình timeout không nhất quán.
