Mục lục
- Mục Tiêu Bài Học
- Shard vs Workers — Hai Chiều Parallel
- Cú Pháp --shard=x/y
- Cơ Chế Phân Phối Round-Robin
- GitHub Actions Matrix Pattern
- Kết Hợp --shard và --workers
- Kết Hợp --shard và --project
- Performance Impact
- Khi Nào Nên và Không Nên Dùng Shard
- State Cross-Shard
- Reporter và Merge
- Pitfalls
- Quiz
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài này, bạn sẽ:
- Phân biệt
--shard(cross-machine) vớiworkers(trong 1 machine). - Hiểu Playwright phân phối test theo round-robin và tính deterministic của shard.
- Viết GitHub Actions matrix job để chạy 4 shard song song.
- Biết cách kết hợp
--shardvới--workersvà--project. - Ước tính performance gain và khi nào shard thực sự có lợi.
- Tránh 5 pitfall phổ biến khi dùng shard.
Phạm vi: Bài này tập trung vào CLI flag --shard=x/y. Blob reporter và merge-reports để aggregate kết quả từ nhiều shard thuộc bài 70–71.
Shard vs Workers — Hai Chiều Parallel
Playwright hỗ trợ parallel theo hai chiều độc lập:
| Cơ chế | Đơn vị | Phạm vi | Config / Flag |
|---|---|---|---|
| workers | Node.js child process | Trong 1 machine | workers: N hoặc --workers=N |
| shard | CI machine / runner | Cross machine | --shard=x/y |
workers chia test trong một machine theo số process; shard chia test list ngay từ đầu — mỗi shard chỉ "thấy" subset của toàn suite và không biết gì về các shard còn lại.
Hai cơ chế độc lập và có thể kết hợp. Một machine chạy shard số x với N worker: shard quyết định subset test nào, worker quyết định bao nhiêu test trong subset đó chạy song song.
Cú Pháp --shard=x/y
Flag --shard=x/y nhận hai số nguyên dương: x là index của shard hiện tại (1-based), y là tổng số shard.
# Machine 1 chạy shard đầu tiên trong 4
npx playwright test --shard=1/4
# Machine 2
npx playwright test --shard=2/4
# Machine 3
npx playwright test --shard=3/4
# Machine 4
npx playwright test --shard=4/4
Mỗi machine chạy lệnh riêng với index khác nhau. Tổng hợp lại thì toàn bộ suite được phủ đúng một lần: không test nào bị bỏ, không test nào chạy hai lần.
Quy tắc hợp lệ:
xphải là số nguyên từ 1 đếny(1-based, không có shard 0).yphải là số nguyên dương.x <= y— shard index không vượt tổng số shard.
Playwright báo lỗi ngay khi parse flag nếu vi phạm một trong ba quy tắc trên.
Cơ Chế Phân Phối Round-Robin
Trước khi spawn bất kỳ worker nào, Playwright tạo danh sách tất cả test (sau khi áp filter --grep, --project, v.v.), rồi chia list đó cho các shard theo round-robin theo đơn vị file.
Giả sử 8 file test, chia cho 4 shard:
File index: 0 1 2 3 4 5 6 7
Shard: 1 2 3 4 1 2 3 4
→ Shard 1 chạy: file 0, file 4
→ Shard 2 chạy: file 1, file 5
→ Shard 3 chạy: file 2, file 6
→ Shard 4 chạy: file 3, file 7
Một số điểm quan trọng về cơ chế này:
- Granularity mặc định là file — không phải test riêng lẻ. Một file không bị tách ngang qua hai shard.
- Deterministic — với cùng suite và cùng
--shard=x/y, Playwright luôn assign đúng tập file đó. Không random mỗi lần chạy. - Phụ thuộc thứ tự file — Playwright sắp xếp file theo alphabet trước khi round-robin. Thêm hoặc xóa file có thể thay đổi assignment của từng shard.
Khi số file không chia hết cho số shard:
5 file, 4 shard:
Shard 1: file 0, file 4
Shard 2: file 1 ← chỉ 1 file
Shard 3: file 2 ← chỉ 1 file
Shard 4: file 3 ← chỉ 1 file
Shard 1 có 2 file, các shard còn lại chỉ có 1 — shard 1 sẽ chậm hơn. Đây là một trong các lý do nên cân bằng số test per file.
GitHub Actions Matrix Pattern
Pattern phổ biến nhất: dùng strategy.matrix để sinh N job song song, mỗi job chạy 1 shard.
# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false # Shard khác tiếp tục dù 1 shard fail
matrix:
shard: [1, 2, 3, 4]
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
- name: Run tests (shard ${{ matrix.shard }}/4)
run: npx playwright test --shard=${{ matrix.shard }}/4
GitHub Actions tạo 4 job song song, mỗi job nhận giá trị shard khác nhau từ matrix. Tất cả 4 job chạy đồng thời — tổng thời gian xấp xỉ thời gian của shard chậm nhất.
Lưu ý fail-fast: false: theo mặc định GitHub Actions hủy các job còn lại khi 1 job fail. Với shard, thường muốn toàn bộ suite chạy xong để có report đầy đủ trước khi kết luận.
Upload artifact sau mỗi shard để merge-reports sau (chi tiết bài 70):
- name: Upload blob report
if: always()
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shard }}
path: blob-report/
retention-days: 1
if: always() đảm bảo artifact được upload kể cả khi shard có test fail.
Kết Hợp --shard và --workers
--shard và --workers hoạt động ở hai cấp độ khác nhau nên có thể kết hợp tự do:
# 4 machines × 4 workers mỗi machine = 16 test song song tổng
npx playwright test --shard=1/4 --workers=4
npx playwright test --shard=2/4 --workers=4
npx playwright test --shard=3/4 --workers=4
npx playwright test --shard=4/4 --workers=4
Hoặc trong config kết hợp với matrix:
- name: Run tests
run: npx playwright test --shard=${{ matrix.shard }}/4 --workers=4
Kết hợp này có lợi khi mỗi CI runner có nhiều vCPU. GitHub Actions ubuntu-latest có 2 vCPU — dùng --workers=2 là hợp lý. Runner lớn hơn (4 vCPU) thì --workers=4.
Tính toán tổng parallel:
N shard × M workers = N×M test chạy song song tối đa
Ví dụ:
4 shard × 2 workers = 8 test song song
4 shard × 4 workers = 16 test song song
8 shard × 2 workers = 16 test song song (cost cao hơn)
Chi phí: mỗi shard là 1 runner bill riêng. 4 shard × 4 workers tốn 4 runner nhưng nhanh gấp 16x so với serial. 8 shard × 2 workers tốn 8 runner nhưng cũng nhanh gấp 16x — chi phí cao hơn, performance tương đương.
Kết Hợp --shard và --project
--project filter trước, --shard chia sau. Khi kết hợp, Playwright chỉ tính test list của project đó rồi mới round-robin.
# Shard chỉ chromium tests qua 4 machine
npx playwright test --project=chromium --shard=1/4
npx playwright test --project=chromium --shard=2/4
npx playwright test --project=chromium --shard=3/4
npx playwright test --project=chromium --shard=4/4
Use case phổ biến: chạy browser matrix và shard matrix song song — mỗi combination (browser, shard) là 1 runner.
jobs:
test:
strategy:
fail-fast: false
matrix:
project: [chromium, firefox, webkit]
shard: [1, 2, 3, 4]
steps:
- name: Run tests
run: npx playwright test --project=${{ matrix.project }} --shard=${{ matrix.shard }}/4
Matrix trên tạo 3 × 4 = 12 job song song. Wall-time gần bằng thời gian của 1 job đơn. Nhưng chi phí runner nhân 12x — chỉ hợp lý khi feedback time quan trọng hơn cost.
Lưu ý: khi --project filter ra ít file, một số shard có thể empty (không có test nào). Playwright vẫn exit 0 với shard rỗng, chỉ in thông báo không tìm thấy test.
Performance Impact
Giảm wall-clock time theo lý thuyết:
| Số shard | Wall-time (lý thuyết) | Ghi chú |
|---|---|---|
| 1 (không shard) | 100% | Baseline |
| 2 | ~52–55% | Overhead spawn runner, install deps |
| 4 | ~28–32% | ~70% time reduction |
| 8 | ~18–22% | Diminishing return bắt đầu rõ |
| 16 | ~14–18% | Phần lớn là overhead, không còn lợi nhiều |
Overhead của mỗi shard bao gồm:
- Provision runner: 5–30 giây (tùy CI provider).
npm cihoặc restore node_modules cache: 10–60 giây.npx playwright install: 30–120 giây nếu không cache browser binary.- Global setup chạy lại trên mỗi shard: thời gian phụ thuộc logic setup.
Tổng overhead có thể là 1–3 phút mỗi shard. Nếu suite chạy 5 phút trên 1 machine, tách 4 shard mỗi shard chạy ~1.25 phút nhưng overhead 2 phút → thực tế không nhanh hơn. Shard có lợi khi thời gian test đủ lớn để overhead tỷ lệ nhỏ.
Ngưỡng thực tế:
- Suite < 5 phút: shard thường không có lợi về wall-time.
- Suite 5–15 phút: 2–4 shard bắt đầu có hiệu quả rõ.
- Suite > 15 phút: shard là lựa chọn ưu tiên để giảm feedback time.
Khi Nào Nên và Không Nên Dùng Shard
Nên dùng shard khi:
- Suite có hơn 100 test và tổng thời gian chạy vượt 5 phút.
- CI cost với multiple runner chấp nhận được.
- Cần fast feedback trên PR — 10 phút thay vì 40 phút.
- Suite đã tận dụng hết workers trong 1 machine nhưng vẫn chậm.
Không nên dùng shard khi:
- Suite nhỏ (< 50 test hoặc < 3 phút) — overhead runner vượt benefit.
- CI runner đắt và budget bị kiểm soát chặt — shard nhân cost theo số shard.
- Test có sequential dependency bắt buộc giữa các file — shard phá vỡ thứ tự.
- Global setup tốn nhiều thời gian và không thể cache — overhead nhân lên.
Optimal shard count cho hầu hết case:
Suite 5–10 phút → 2 shard
Suite 10–20 phút → 4 shard
Suite 20–40 phút → 4–8 shard
Suite > 40 phút → 8 shard + tăng workers mỗi shard
Tăng shard từ 4 lên 8 chỉ cắt thêm ~50% thời gian còn lại (không phải 50% so với baseline). Thường 4 shard là điểm cân bằng tốt nhất giữa cost và speed.
State Cross-Shard
Mỗi shard là một lần chạy npx playwright test hoàn toàn độc lập trên machine riêng. Các shard không thể giao tiếp trực tiếp với nhau. Điều này có hệ quả cụ thể:
- Global setup chạy lại trên mỗi shard —
globalSetuptrong config không được chia sẻ. - Worker-scope fixture không share cross-shard — mỗi shard có worker pool riêng.
- In-memory state trong fixtures không thể truyền qua shard khác.
Pattern để share state cross-shard:
Vì shard = process độc lập, cách duy nhất để share state là qua storage ngoài: database, file system, object storage (S3), hoặc Redis.
# .github/workflows/playwright.yml — pre-seed trước khi shard chạy
jobs:
seed:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- name: Seed test database
run: node scripts/seed-db.js # Chạy 1 lần, tất cả shard đọc vào
test:
needs: seed # Chờ seed job xong
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4
Pattern: job seed chạy trước và chuẩn bị DB hoặc fixture data vào external storage. Các shard đọc data đó — không tự generate. Mỗi shard nên dùng data riêng (partitioned) để tránh conflict.
Reporter và Merge
Mỗi shard tạo report riêng biệt. HTML reporter mặc định sinh playwright-report/ độc lập trên mỗi machine — không tự gộp lại.
Để có một report tổng hợp từ tất cả shard, cần dùng blob reporter và merge-reports:
- Mỗi shard dùng
reporter: 'blob'— sinh file nhị phân nén trongblob-report/. - Upload artifact sau mỗi shard.
- Job cuối download tất cả artifact rồi chạy
npx playwright merge-reports.
// playwright.config.ts — dùng blob reporter khi có shard
export default defineConfig({
reporter: process.env.CI ? 'blob' : 'html',
});
Blob reporter và merge-reports là chủ đề của bài 70. Bài này chỉ cần biết: mỗi shard cần upload report riêng, không có report tổng hợp tự động.
Nếu chỉ cần biết pass/fail tổng thể (không cần xem chi tiết từng test), bỏ qua merge và xem exit code của từng shard job trực tiếp trên CI dashboard.
Pitfalls
Pitfall 1: --shard=0/4 — index bắt đầu từ 1, không phải 0
# Sai: --shard=0/4 → Playwright báo lỗi và exit
npx playwright test --shard=0/4
# Đúng: index bắt đầu từ 1
npx playwright test --shard=1/4
Pitfall 2: --shard=5/4 — x phải ≤ y
Playwright báo lỗi khi x > y. Trong matrix CI nếu dùng array động phải đảm bảo index không vượt tổng. Nếu tổng shard thay đổi, cần update cả array lẫn mẫu số.
Pitfall 3: Chỉ chạy flag trên 1 machine — toàn suite vẫn chạy 1 lần
# Chỉ chạy shard 1 mà không có shard 2,3,4
# → Chỉ chạy 25% suite, còn 75% bị bỏ qua
npx playwright test --shard=1/4
Shard không tự nhân bản. Mỗi shard phải được chạy tường minh. Nếu quên một shard trong CI matrix, toàn bộ test của shard đó không chạy và không fail — chúng chỉ vắng mặt trong report.
Pitfall 4: Global setup sinh file vào cùng đường dẫn cố định
// globalSetup ghi vào path cố định — OK vì mỗi shard là machine riêng
// Nhưng nếu dùng mounted volume share → conflict
async function globalSetup() {
// Tránh ghi vào shared path khi dùng mounted storage
await fs.writeFile('./test-data/auth.json', ...);
}
Nếu shard chạy trên cùng machine (hiếm nhưng có thể xảy ra với self-hosted runner), global setup từ hai shard ghi cùng file cùng lúc → race condition. Dùng path có shard index để tránh: auth-shard-${SHARD_INDEX}.json.
Pitfall 5: Không upload report → không thể merge
Khi shard fail, step tiếp theo (upload artifact) bị skip theo mặc định. Phải dùng if: always() trên upload step để đảm bảo artifact được upload kể cả khi shard có test fail:
- name: Upload blob report
if: always() # Không bỏ qua khi test fail
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shard }}
path: blob-report/
Quiz
Câu 1. Suite có 12 file test. Bạn chạy --shard=2/4. File nào (theo index 0–11) được assign cho shard này?
Đáp án
Round-robin theo file index: shard 1 → index 0, 4, 8; shard 2 → index 1, 5, 9; shard 3 → index 2, 6, 10; shard 4 → index 3, 7, 11. Vậy shard 2 chạy file index 1, 5, 9 — tức file thứ 2, 6, 10 (1-based).
Câu 2. CI config dùng matrix.shard: [1, 2, 3, 4] nhưng lệnh chạy là npx playwright test --shard=${{ matrix.shard }}/3 (mẫu số là 3, không phải 4). Điều gì xảy ra với job shard 4?
Đáp án
Playwright nhận --shard=4/3 → vi phạm điều kiện x ≤ y → báo lỗi và exit với non-zero code → job fail. Toàn bộ shard 4 fail mà không chạy test nào. Cần đồng bộ mẫu số trong lệnh với độ dài array trong matrix.
Câu 3. Suite có 200 test trải đều trên 20 file, mỗi file 10 test. Dùng --shard=1/4 --workers=2. Shard 1 sẽ chạy bao nhiêu file, bao nhiêu test, và tối đa bao nhiêu test song song?
Đáp án
20 file chia 4 shard round-robin: mỗi shard nhận 5 file. Shard 1 nhận file index 0, 4, 8, 12, 16 → 5 file × 10 test = 50 test. Với workers=2 và fullyParallel: true (mặc định), tối đa 2 test chạy song song cùng lúc. Nếu fullyParallel: false, tối đa 2 file song song, mỗi file serial — vẫn là 2 test chạy cùng lúc về số lượng nhưng không trộn lẫn file.
Câu 4. Team quyết định tăng shard từ 4 lên 8 để giảm thêm thời gian CI. Suite hiện tại chạy 8 phút với 4 shard. Ước tính wall-time mới với 8 shard, giả sử overhead runner là 2 phút.
Đáp án
Với 4 shard: wall-time = 8 phút (đã tính overhead 2 phút). Test thuần = 8 − 2 = 6 phút. Tăng lên 8 shard: test thuần mỗi shard = 6 / 8 ≈ 0.75 phút. Cộng overhead 2 phút → wall-time ≈ 2.75 phút. Giảm từ 8 phút xuống ~2.75 phút — tiết kiệm ~65%. Nhưng chi phí runner tăng 2x (8 runner thay vì 4). Nếu từ không shard (baseline ~24 phút) thì 4 shard đã giảm ~67%, tăng thêm lên 8 shard chỉ giảm thêm khoảng 65% của 8 phút còn lại — diminishing return rõ.
Câu 5. Workflow có 4 shard jobs. Shard 2 fail vì 3 test fail. Bạn muốn merge report từ cả 4 shard để xem đầy đủ. Nhưng chỉ có artifact từ shard 1, 3, 4 — artifact shard 2 bị thiếu. Nguyên nhân có thể là gì và cách fix?
Đáp án
Nguyên nhân: upload artifact step không có if: always() — khi shard 2 fail, step tiếp theo bị skip. Fix: thêm if: always() vào upload artifact step để nó chạy bất kể kết quả test. Sau khi fix, kể cả shard fail vẫn upload blob report để merge-reports có đủ dữ liệu từ tất cả shard.
Bài Tiếp Theo
Bài 70: Blob Reporter & Sharding — cấu hình reporter: 'blob' để mỗi shard xuất binary report, upload artifact, rồi dùng merge-reports để tạo một HTML report duy nhất từ toàn bộ shard.
