Mục lục
Mục Tiêu Bài Học
Sau bài này, bạn sẽ:
- Biết cú pháp đầy đủ của
npx playwright merge-reportsvà các flag. - Nắm các reporter output được hỗ trợ:
html,json,junit,list,dot,github. - Biết cách đổi output location với
--output. - Khai báo nhiều reporter trong một lần merge.
- Xây dựng CI job
merge-reportsdepend vào matrixtest, bao gồm xử lý failed shard. - Thực hiện partial re-run: chạy lại một shard fail và re-merge.
- Tránh 4 pitfall phổ biến gây lỗi khi merge.
Phạm vi bài: Bài này tập trung vào lệnh merge-reports. Cơ chế blob reporter và output options của nó đã được đào sâu ở bài 70. Deep dive GitHub Actions matrix strategy sẽ thuộc bài 72.
Cú Pháp Lệnh
Dạng đầy đủ:
npx playwright merge-reports [options] <blob-folder>
Ví dụ thực tế:
# Merge tất cả blob trong ./all-blobs/, xuất HTML report
npx playwright merge-reports --reporter=html ./all-blobs
# Merge và xuất vào thư mục custom
npx playwright merge-reports --reporter=html --output=./reports/merged ./all-blobs
# Merge và xuất JSON
npx playwright merge-reports --reporter=json ./all-blobs
Các flag hiện có:
| Flag | Mô tả | Default |
|---|---|---|
--reporter |
Reporter output. Nhận tên hoặc JSON array. | html |
--output |
Folder đích cho output file. | playwright-report/ |
--config |
Đọc reporter config từ playwright.config.ts (nếu cần reporter options phức tạp). |
Không đọc config |
Argument cuối (<blob-folder>) là path đến folder chứa các file *.zip blob. Lệnh tự đọc tất cả ZIP trong folder — không cần liệt kê từng file.
Input: Folder Blob
merge-reports đọc folder theo quy tắc: quét tất cả file *.zip trong folder đó, mỗi file là một blob từ một shard. Thứ tự file trong folder không quan trọng — metadata trong mỗi blob đã chứa thông tin shard index.
Ví dụ: folder all-blobs/ chứa 4 file sau khi CI download artifacts:
all-blobs/
├── report-a1b2c3d4.zip ← blob shard 1 (tên random)
├── report-e5f6g7h8.zip ← blob shard 2
├── report-i9j0k1l2.zip ← blob shard 3
└── report-m3n4o5p6.zip ← blob shard 4
Lệnh merge:
npx playwright merge-reports --reporter=html ./all-blobs
Yêu cầu quan trọng: Folder chỉ được chứa blob ZIP files. Bất kỳ file .zip nào không phải blob Playwright trong folder đều gây lỗi khi parse. File không phải .zip (như .txt, .json) bị bỏ qua, nhưng tốt nhất không để file lạ trong folder.
Reporters Hỗ Trợ
Tất cả built-in reporter của Playwright đều hoạt động với merge-reports:
| Reporter | Output | Use case |
|---|---|---|
html |
Folder playwright-report/ (HTML + JS) |
Review kết quả tổng hợp, xem trace/screenshot inline. Khuyến nghị cho CI final step. |
json |
File test-results.json |
Parse programmatic, tích hợp với dashboard nội bộ, post-process kết quả. |
junit |
File XML theo JUnit schema | Tích hợp với Jenkins, Bamboo, GitLab CI test reports. |
list |
Stdout (tên test + status) | Xem nhanh toàn bộ kết quả trên terminal sau merge local. |
dot |
Stdout (dấu chấm per test) | Summary ngắn gọn, phù hợp CI log dài. |
github |
GitHub Actions annotations | Hiển thị test fail trực tiếp trên PR diff trong GitHub UI. |
Lưu ý về default: Nếu không chỉ định --reporter, lệnh dùng html làm mặc định. Output luôn vào playwright-report/ trừ khi dùng --output.
Reporter trong test run khác reporter trong merge: Shard chạy với --reporter=blob để xuất dữ liệu trung gian. Sau đó merge với --reporter=html để generate report cuối. Đây là hai bước độc lập — reporter blob không ảnh hưởng đến format output của merge.
Output Location
Mặc định merge-reports ghi output vào playwright-report/ — giống HTML reporter khi chạy test thông thường. Để đổi location, dùng --output:
# Output vào ./reports/ci-run-123/
npx playwright merge-reports --reporter=html --output=./reports/ci-run-123 ./all-blobs
# Output JSON vào custom path
npx playwright merge-reports --reporter=json --output=./artifacts ./all-blobs
# → file: ./artifacts/test-results.json
Behavior theo reporter:
html:--outputlà folder chứaindex.htmlvà assets.json:--outputlà folder, file tạo ra bên trong làtest-results.json.junit:--outputlà folder, file tạo ra làresults.xml(tên mặc định). Để đặt tên cụ thể, cần dùng cú pháp JSON array (xem mục 6).list,dot,github: chỉ ghi ra stdout,--outputkhông có tác dụng.
Folder output được tạo tự động nếu chưa tồn tại. Nếu đã tồn tại, nội dung cũ bị overwrite.
Multiple Reporters Cùng Lúc
Merge có thể xuất nhiều format cùng một lần — tránh phải chạy lệnh merge nhiều lần trên cùng một bộ blob:
# Comma-separated: html và junit đồng thời
npx playwright merge-reports --reporter=html,junit ./all-blobs
Dạng comma-separated dùng default options cho mỗi reporter. Để truyền options (như đổi tên file JUnit output), dùng JSON array:
npx playwright merge-reports \
--reporter='[["html"], ["junit", { "outputFile": "results/junit.xml" }]]' \
./all-blobs
JSON array có format: [["reporterName", optionsObject], ...]. Options object là tùy chọn — nếu không cần options thì chỉ cần ["html"].
Ví dụ đầy đủ: HTML report để upload artifact + JUnit để GitLab CI parse test result:
npx playwright merge-reports \
--reporter='[["html"], ["junit", { "outputFile": "test-results/junit.xml" }], ["github"]]' \
./all-blobs
Khi dùng JSON array trên shell, cần escape quotes cẩn thận. Trên Windows PowerShell, cú pháp quote khác — nên đặt reporter config trong file playwright.config.ts và dùng --config thay thế.
CI Pattern — Final Step Sau Sharding
Pattern chuẩn: job test chạy matrix 4 shard song song, job merge-reports depend vào toàn bộ matrix và chạy sau cùng:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Run shard ${{ matrix.shard }}/4
run: npx playwright test --shard=${{ matrix.shard }}/4 --reporter=blob
- name: Upload blob artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: blob-${{ matrix.shard }}
path: blob-report/
retention-days: 1
merge-reports:
needs: test
runs-on: ubuntu-latest
if: always()
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Download all blob artifacts
uses: actions/download-artifact@v4
with:
path: all-blobs/
pattern: blob-*
merge-multiple: true
- name: Merge reports
run: npx playwright merge-reports --reporter=html ./all-blobs
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: html-report
path: playwright-report/
retention-days: 30
Tại sao job merge-reports cần riêng biệt: Job merge cần chờ tất cả 4 shard hoàn thành (kể cả shard fail) trước khi chạy. Nếu đặt bước merge trong matrix job, mỗi shard sẽ chạy merge ngay sau khi nó xong — khi đó chưa có đủ blob từ các shard còn lại.
Lưu ý needs: test: GitHub Actions hiểu needs: test là phụ thuộc vào toàn bộ matrix job test, không phải chỉ một instance. Job merge tự động chờ tất cả 4 matrix instance hoàn thành.
Xử Lý Failed Shard
Khi một shard có test fail, blob vẫn được tạo bình thường — blob chứa toàn bộ kết quả của shard đó bao gồm cả pass lẫn fail. Vấn đề xảy ra ở bước upload, không phải bước generate blob.
Có hai điều kiện cần đảm bảo để failed shard vẫn được aggregate:
1. if: always() trên upload artifact:
# Đây là điểm quan trọng nhất
- name: Upload blob artifact
if: always() # ← thiếu dòng này → failed shard không upload → merge thiếu data
uses: actions/upload-artifact@v4
with:
name: blob-${{ matrix.shard }}
path: blob-report/
2. if: always() trên job merge-reports:
merge-reports:
needs: test
if: always() # ← thiếu dòng này → GitHub Actions skip merge job khi test job fail
Khi cả hai điều kiện được đảm bảo, merge job nhận đủ blob từ tất cả shard (kể cả shard fail) và report tổng cho biết chính xác test nào fail ở shard nào.
Điều gì xảy ra nếu một blob bị thiếu: merge-reports không báo lỗi vì thiếu blob — nó chỉ merge những gì có trong folder. Nếu shard 2 không upload được blob, report tổng sẽ thiếu toàn bộ test của shard 2 mà không có cảnh báo rõ ràng. Đây là lý do if: always() quan trọng.
Trace, Video, Screenshot Trong Merged Report
Blob file chứa reference path đến artifact (trace, video, screenshot) — không embed binary artifact trực tiếp vào blob ZIP. Điều này giữ blob nhỏ hơn khi trace/video không được bật, nhưng cũng có hệ quả quan trọng khi merge.
Khi merge tạo HTML report, các link artifact trong report trỏ đến file artifact theo path tương đối. Để click link trace/video trong merged HTML report hoạt động, artifact files cần có mặt cạnh folder playwright-report/:
Sau merge, cấu trúc folder:
playwright-report/
├── index.html
├── data/
│ └── ...
test-results/ ← artifact files (trace, video, screenshot)
├── test-a-chromium/
│ ├── trace.zip
│ └── screenshot.png
└── ...
Trên CI, khi upload artifact html-report bao gồm cả test-results/:
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: html-report
path: |
playwright-report/
test-results/
Nếu chỉ upload playwright-report/ mà không có test-results/, link trace/screenshot trong HTML report sẽ broken khi mở offline. Trong HTML report viewer online (GitHub Actions artifact preview), link vẫn có thể hoạt động nếu Playwright serve trace qua URL nội bộ.
Với setup sharding, artifact từ mỗi shard cần được tập hợp về merge job. Blob report đã nhúng artifact inline kể từ Playwright v1.45 khi bật attachments: 'inline' option cho blob reporter — tuy nhiên làm blob file lớn hơn đáng kể.
Partial Re-run
Khi một shard fail do lý do môi trường (flaky network, timeout CI), bạn có thể chạy lại riêng shard đó và re-merge — không cần chạy lại toàn bộ suite:
# Bước 1: Download tất cả blob từ CI run đó về ./all-blobs/
# (4 blob: shard 1, 2, 3, 4 — shard 2 có thể là blob cũ của lần fail)
# Bước 2: Re-run shard 2
npx playwright test --shard=2/4 --reporter=blob
# Bước 3: Thay thế blob cũ của shard 2 bằng blob mới
# Xóa blob cũ của shard 2 khỏi ./all-blobs/
rm ./all-blobs/report-e5f6g7h8.zip
# Copy blob mới vào
cp ./blob-report/*.zip ./all-blobs/
# Bước 4: Re-merge với bộ blob mới
npx playwright merge-reports --reporter=html ./all-blobs
Điều kiện để partial re-run hoạt động đúng:
- Playwright version phải giống với lần chạy gốc — blob của v1.48 và v1.49 không tương thích cross-shard.
- Tổng số shard (
Mtrong--shard=N/M) phải giữ nguyên. Không thể mix blob từ run 4-shard với blob từ run 8-shard. - Test files không được thay đổi giữa lần chạy gốc và re-run. Nếu code thay đổi, shard phân phối test có thể khác nhau → blob không còn tương thích.
Partial re-run phù hợp nhất khi fail do lý do infrastructure (không phải bug trong test). Nếu có fix code, chạy lại toàn bộ suite là an toàn hơn.
Pitfalls
Pitfall 1: Blob folder chứa file ZIP lạ (non-blob) → merge error
merge-reports parse tất cả *.zip trong folder. Nếu folder chứa file ZIP không phải blob Playwright (ví dụ file artifact từ bước CI khác, hoặc backup zip), lệnh sẽ fail với lỗi parse:
Error: Failed to read blob report from all-blobs/random-archive.zip
Giải pháp: đảm bảo folder input chỉ chứa blob ZIP files. Trong CI, download artifact vào folder riêng chỉ dành cho blob (all-blobs/) và không copy file khác vào đó.
Pitfall 2: Mix Playwright version cross shard → merge fail hoặc report sai
Blob format là internal schema và có thể thay đổi giữa minor version. Nếu shard 1 dùng v1.48.0 và shard 2 dùng v1.49.0 (do cache lock file cũ hoặc CI install không pin version), merge-reports có thể báo lỗi hoặc generate report với dữ liệu không nhất quán.
Giải pháp: pin version chính xác trong package.json và đảm bảo tất cả CI job install từ cùng package-lock.json:
// package.json — pin chính xác, không dùng ^ hay ~
"devDependencies": {
"@playwright/test": "1.49.1"
}
Pitfall 3: Quên merge-multiple: true trong download-artifact → blob nằm subfolder
Khi download nhiều artifact (blob-1, blob-2, blob-3, blob-4) vào cùng một path, actions/download-artifact@v4 mặc định tạo subfolder cho mỗi artifact:
# Không có merge-multiple: true
all-blobs/
├── blob-1/
│ └── report-xxx.zip
├── blob-2/
│ └── report-yyy.zip
...
# Khi chạy: npx playwright merge-reports ./all-blobs
# → không tìm thấy *.zip trực tiếp trong all-blobs/ → merge 0 files
Thêm merge-multiple: true để tất cả blob được đặt phẳng vào cùng folder:
- uses: actions/download-artifact@v4
with:
path: all-blobs/
pattern: blob-*
merge-multiple: true # ← bắt buộc
Pitfall 4: Reporter chỉ định cho merge-reports khác với reporter test run → output không nhất quán
Đây không phải lỗi kỹ thuật mà là nhầm lẫn về ý nghĩa: reporter trong test run (--reporter=blob) chỉ định format lưu trữ intermediate. Reporter trong merge-reports (--reporter=html) chỉ định format output cuối. Hai reporter này hoàn toàn độc lập.
Nhầm lẫn phổ biến: dùng --reporter=html trong test run (tạo HTML per shard) và --reporter=blob trong merge (vô nghĩa vì merge không xuất blob). Workflow đúng luôn là: test dùng blob, merge dùng reporter khác:
Test run: --reporter=blob → *.zip (intermediate)
merge-reports: --reporter=html → playwright-report/ (final output)
Quiz
Câu 1. Chạy lệnh npx playwright merge-reports ./all-blobs không có --reporter. Output được tạo ở đâu và dạng gì?
Đáp án
Default reporter là html. Output là folder playwright-report/ trong thư mục hiện tại, chứa index.html và assets. Để xem, chạy npx playwright show-report.
Câu 2. CI workflow có 4 shard, shard 3 fail. Workflow có fail-fast: false và if: always() đúng chỗ. Sau khi merge, report tổng có chứa kết quả test của shard 3 không?
Đáp án
Có. Shard 3 fail có nghĩa là một số test trong shard fail, nhưng blob vẫn được tạo và upload (nhờ if: always()). Merge job nhận đủ 4 blob, tổng hợp tất cả — test fail của shard 3 hiển thị đầy đủ trong report tổng với trạng thái failed.
Câu 3. Cần xuất đồng thời HTML report và JUnit XML với tên file ci-results.xml trong một lần merge. Lệnh nào đúng?
Đáp án
npx playwright merge-reports --reporter='[["html"], ["junit", { "outputFile": "ci-results.xml" }]]' ./all-blobs. Dùng JSON array để truyền options cho JUnit reporter, trong đó outputFile chỉ định tên file output. Comma-separated (--reporter=html,junit) không cho phép truyền options.
Câu 4. Sau khi merge, folder all-blobs/ chứa 4 blob zip và một file README.txt. Điều gì xảy ra khi chạy npx playwright merge-reports --reporter=html ./all-blobs?
Đáp án
Lệnh chạy bình thường và thành công. merge-reports chỉ đọc *.zip trong folder, file README.txt không phải ZIP nên bị bỏ qua hoàn toàn. Chỉ file *.zip không phải blob Playwright mới gây lỗi.
Câu 5. Download artifact từ CI về local. Folder all-blobs/ có cấu trúc: all-blobs/blob-1/report-aaa.zip, all-blobs/blob-2/report-bbb.zip, ... (mỗi blob nằm trong subfolder riêng). Chạy npx playwright merge-reports --reporter=html ./all-blobs cho kết quả gì?
Đáp án
Merge tạo report rỗng hoặc báo không tìm thấy blob. merge-reports chỉ quét *.zip trực tiếp trong folder chỉ định (all-blobs/), không đệ quy vào subfolder. Với cấu trúc trên, blob nằm trong blob-1/, blob-2/ — không được tìm thấy. Giải pháp: copy tất cả *.zip về thẳng all-blobs/, hoặc khi download trên CI dùng merge-multiple: true.
Bài Tiếp Theo
Bài 72: GitHub Actions Matrix Sharding — đào sâu cách thiết kế matrix strategy cho sharding: fail-fast, concurrency, artifact retention, và tối ưu CI runtime.
