Danh sách bài viết

Bài 71: `npx playwright merge-reports` — Gộp Blob Cross Shard

Sau khi mỗi shard xuất một blob ZIP, lệnh `npx playwright merge-reports` tổng hợp tất cả blob đó thành một report duy nhất. Bài này đào sâu cú pháp, các reporter output được hỗ trợ, cách chỉ định output location, CI job pattern cho final step, xử lý shard fail, partial re-run, và các pitfall hay gặp trong thực tế.

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

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-reports và 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-reports depend vào matrix test, 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.

2

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.

3

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.

4

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.

5

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: --output là folder chứa index.html và assets.
  • json: --output là folder, file tạo ra bên trong là test-results.json.
  • junit: --output là 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, --output khô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.

6

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

7

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.

8

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.

9

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

10

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 (M trong --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.

11

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

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

13

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.