Danh sách bài viết

Bài 82: globalTimeout — Cap Thời Gian Toàn Run

globalTimeout đặt giới hạn cứng cho toàn bộ thời gian chạy của một test run — từ lúc Playwright bắt đầu chạy đến khi kết thúc. Bài này đào sâu cú pháp, behavior khi abort, use case CI hang protection và cost cap, pattern layered với CI job timeout, sharding, cùng 4 pitfall thường gặp.

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 globalTimeout hoạt động ở scope nào và khác gì với timeout per-test.
  • Hiểu behavior khi run vượt globalTimeout: test nào bị interrupt, test nào bị skip, exit code ra sao.
  • Nhận biết 3 use case chính trên CI: hang protection, cost cap, SLA enforcement.
  • Áp dụng pattern layered: Playwright globalTimeout nhỏ hơn CI job timeout để cleanup graceful.
  • Tính toán giá trị globalTimeout hợp lý từ số test, thời gian trung bình, và số worker.
  • Tránh 4 pitfall phổ biến khi dùng globalTimeout với retry và sharding.
2

Nhóm A.9 — Timeouts

Nhóm A.9 đào sâu hệ thống timeout nhiều tầng của Playwright: từ giới hạn toàn run đến từng action riêng lẻ.

Các bài trong nhóm:

  • globalTimeout (bài này) — giới hạn tổng thời gian toàn test run, behavior abort, pattern CI.
  • test timeout (bài 83) — giới hạn thời gian một test duy nhất, override per-test và per-describe.
  • action & navigation timeout (bài 84) — giới hạn thời gian cho từng action (click, fill) và navigation (goto).
  • expect timeout (bài 85) — giới hạn thời gian polling cho expect(locator).toBeVisible() và các assertion auto-retry.

Bài trước (bài 81) đã đặt nền tảng về timeout hierarchy — thứ tự ưu tiên từ globalTimeout → test timeout → action timeout → expect timeout. Bài này đi sâu vào tầng cao nhất trong hierarchy đó.

3

globalTimeout — Định Nghĩa Và Cú Pháp

globalTimeout là tổng thời gian tối đa (tính bằng millisecond) mà toàn bộ test run được phép chạy. Đây là giới hạn cứng ở cấp cao nhất — bao phủ tất cả worker, tất cả project, tất cả test.

Giá trị mặc định là 0, nghĩa là không có giới hạn.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  globalTimeout: 60 * 60 * 1000,  // 1 giờ = 3 600 000 ms
});

Đơn vị luôn là millisecond. Cách viết rõ ràng nhất là dùng phép nhân:

Giá trị mong muốn Cách viết Giá trị ms
15 phút 15 * 60 * 1000 900 000
30 phút 30 * 60 * 1000 1 800 000
1 giờ 60 * 60 * 1000 3 600 000
Không giới hạn 0 hoặc bỏ qua

globalTimeout được khai báo trong defineConfig() ở cấp top-level, không nằm trong use block và không có per-project override. Đây là điểm khác biệt so với timeout (có thể override per-project).

4

Behavior Khi Vượt globalTimeout

Khi đồng hồ globalTimeout hết, Playwright phát tín hiệu abort cho toàn run. Hành vi cụ thể theo trạng thái của từng test:

  • Test đã chạy xong trước khi abort — kết quả được ghi bình thường (passed, failed, flaky).
  • Test đang chạy tại thời điểm abort — bị interrupt. Status trong report là interrupted. Playwright dừng test giữa chừng, không chờ assertion hay action hoàn thành.
  • Test chưa bắt đầu — bị bỏ qua. Có thể hiển thị là skipped hoặc không xuất hiện trong report tùy reporter.

Exit code: Khi run bị abort do globalTimeout, Playwright exit với code khác 0, dù tất cả test đã chạy đều passed. CI sẽ đánh dấu build failed.

Sơ đồ minh họa với globalTimeout hết lúc test 150/200 đang chạy:

Test 1–149:   đã chạy xong → kết quả ghi bình thường
Test 150:     đang chạy  → status: interrupted
Test 151–200: chưa chạy → không có data (hoặc skipped)

Exit code: non-zero → CI build: FAILED

Report sinh ra là partial — chỉ có kết quả của các test đã hoàn thành. Không có dữ liệu cho phần còn lại của suite.

5

Use Case

CI Hang Protection

Kịch bản: một test rơi vào vòng lặp vô hạn, hoặc wait mãi trên một element không bao giờ xuất hiện (do setup môi trường lỗi). Nếu không có globalTimeout, run sẽ hang cho đến khi CI platform kill job theo timeout mặc định của platform đó (thường 6 giờ trên GitHub Actions free tier). Điều này:

  • Chặn pipeline, không release được.
  • Tiêu tốn CI minutes của cả team trong lúc hung.
  • Không sinh report — không biết chuyện gì xảy ra.

Với globalTimeout: 30 * 60 * 1000, Playwright tự abort sau 30 phút, sinh partial report, và exit non-zero để CI fail rõ ràng.

CI Cost Cap

Trên các CI platform tính phí theo phút (GitHub Actions, CircleCI, GitLab CI paid tier), một run hung đồng nghĩa với phút bị tính tiền. globalTimeout đặt trần trên chi phí tối đa của một run — dù test có hang thế nào, sau X phút Playwright tự kết thúc.

SLA Enforcement

Một số team có cam kết rằng toàn bộ test suite phải xong trong dưới 20 phút để không block merge queue. globalTimeout enforce cam kết này ở cấp config — nếu suite vượt ngưỡng (do test chậm mới thêm vào, hoặc môi trường chậm), run fail và team nhận được cảnh báo để điều tra.

6

CLI Override

Flag --global-timeout trên CLI thắng giá trị trong config:

# Set globalTimeout = 1 giờ khi chạy từ CLI
npx playwright test --global-timeout=3600000

# Disable globalTimeout tạm thời (kể cả config có đặt giá trị)
npx playwright test --global-timeout=0

# Kết hợp với project filter
npx playwright test --project=chromium --global-timeout=1800000

Flag hữu ích khi cần chạy thủ công một run debug không giới hạn thời gian, trong khi config vẫn đặt giới hạn cho CI. Truyền 0 để tắt hoàn toàn.

Lưu ý: CLI flag là --global-timeout (có dấu gạch ngang giữa), không phải --globalTimeout.

7

Phân Biệt globalTimeout Và test timeout

Đây là hai khái niệm khác nhau hoàn toàn về scope:

Thuộc tính globalTimeout timeout (test)
Scope Toàn bộ run (tất cả test) Một test duy nhất
Đặt trong Top-level defineConfig() use.timeout, per-project, per-describe, per-test
Default 0 (không giới hạn) 30 000 ms (30 giây)
Khi vượt Abort toàn run, partial report Test đó fail với timeout error, run tiếp tục
Per-project override Không có
CLI flag --global-timeout --timeout

Quan hệ giữa hai giá trị: globalTimeout phải lớn hơn hoặc bằng tổng thời gian thực tế của toàn run (cộng overhead). Một cách tính an toàn:

globalTimeout ≥ (tổng số test × test timeout / số worker) + overhead

Nếu globalTimeout nhỏ hơn tổng test time, run sẽ abort dù tất cả test đang chạy bình thường — chỉ vì mất nhiều thời gian hơn dự kiến.

8

Tính Toán Giá Trị globalTimeout

Công thức cơ bản:

estimated_run_time = (số test × thời gian trung bình 1 test) / số worker
globalTimeout = estimated_run_time × buffer_factor

buffer_factor thường từ 1.3 đến 2.0 tùy mức độ ổn định của môi trường CI.

Ví dụ thực tế:

Suite: 200 test
Thời gian trung bình mỗi test: 10 giây
Workers: 4
retries: 0 (không retry)

estimated = 200 × 10s / 4 = 500s ≈ 8.3 phút
buffer 50% → 500s × 1.5 = 750s = 12.5 phút
→ Set globalTimeout = 15 phút (làm tròn lên)

Điều chỉnh khi có retry: Nếu dùng retries: 2 và ước tính 10% test sẽ flaky:

Test flaky: 200 × 10% = 20 test
Mỗi test flaky worst-case: 10s × 3 lần = 30s
Overhead retry: 20 × 30s / 4 workers = 150s ≈ 2.5 phút thêm

Tổng: 12.5 phút (không retry) + 2.5 phút (retry overhead) = 15 phút
→ Set globalTimeout = 20 phút (buffer thêm)

Cách đo thời gian trung bình: Xem thời gian run trong CI history 5–10 lần gần nhất, lấy giá trị max (không phải average) làm baseline trước khi cộng buffer.

9

Pattern CI: CI vs Local

Trên máy local, developer thường debug một vài test và muốn run mở không giới hạn. Trên CI, ngược lại, cần cap rõ ràng. Pattern phổ biến:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  globalTimeout: process.env.CI ? 30 * 60 * 1000 : undefined,
  // CI: cap 30 phút
  // Local: không giới hạn (undefined = 0 = no limit)
});

Dùng undefined thay vì 0 để rõ ý định: undefined nghĩa là "không set" (Playwright dùng default là 0), còn 0 là "set rõ ràng là không giới hạn". Về runtime cả hai như nhau, nhưng undefined rõ ràng hơn trong context điều kiện.

Nếu cần giá trị linh hoạt hơn qua env var:

export default defineConfig({
  globalTimeout: process.env.PLAYWRIGHT_GLOBAL_TIMEOUT
    ? parseInt(process.env.PLAYWRIGHT_GLOBAL_TIMEOUT, 10)
    : process.env.CI ? 30 * 60 * 1000 : undefined,
});

Pattern này cho phép từng CI job override timeout riêng mà không cần sửa config — hữu ích khi có nhiều pipeline với suite size khác nhau.

10

Pattern Layered — Playwright Và CI Job Timeout

CI platform có timeout riêng ở cấp job (không phải cấp test). Ví dụ GitHub Actions:

# .github/workflows/test.yml
jobs:
  e2e:
    timeout-minutes: 60  # CI hard kill sau 60 phút
    steps:
      - uses: actions/checkout@v4
      - run: npx playwright test
        # playwright.config.ts có globalTimeout: 50 phút

Quy tắc thiết yếu: globalTimeout (Playwright) phải nhỏ hơn CI job timeout để Playwright có cơ hội:

  1. Phát hiện run đã vượt ngưỡng.
  2. Dừng test đang chạy.
  3. Cleanup fixture và teardown.
  4. Sinh report (HTML, JUnit, JSON).
  5. Exit với non-zero code.

Nếu CI job timeout nhỏ hơn hoặc bằng globalTimeout, CI platform sẽ kill process thô trước khi Playwright kịp abort gracefully. Kết quả: không có report, không có exit code rõ ràng, không biết bao nhiêu test đã chạy.

Khuyến nghị margin: Để ít nhất 5–10 phút giữa globalTimeout và CI job timeout để Playwright có đủ thời gian cleanup và generate report:

CI job timeout globalTimeout (Playwright) Margin
30 phút 20–25 phút 5–10 phút
60 phút 50–55 phút 5–10 phút
120 phút 100–110 phút 10–20 phút
11

globalTimeout Với Sharding

Khi dùng sharding (--shard=1/4, --shard=2/4, ...), mỗi shard chạy trong một process riêng biệt. globalTimeout apply per shard, không phải cho toàn bộ suite gộp lại.

Suite 200 test chia 4 shard → mỗi shard ~50 test

Shard 1: globalTimeout 15 phút → abort sau 15 phút (riêng shard 1)
Shard 2: globalTimeout 15 phút → abort sau 15 phút (riêng shard 2)
Shard 3: globalTimeout 15 phút → abort sau 15 phút (riêng shard 3)
Shard 4: globalTimeout 15 phút → abort sau 15 phút (riêng shard 4)

Điều này có nghĩa là khi sharding:

  • Không cần chia globalTimeout theo số shard. Mỗi shard dùng toàn bộ giá trị globalTimeout.
  • Nếu muốn toàn bộ suite (tất cả shard) xong trong X phút, cần set CI matrix job timeout là X phút, còn globalTimeout là giới hạn per-shard.
  • Shard bị abort do globalTimeout → shard đó exit non-zero → merge-reports sẽ thiếu data của shard đó.

Ví dụ cấu hình thực tế với sharding 4 shard:

// playwright.config.ts
export default defineConfig({
  // 50 test/shard × 10s avg / 2 workers per shard = 250s ≈ 4 phút
  // Buffer 3× → 12 phút, làm tròn lên 15 phút
  globalTimeout: process.env.CI ? 15 * 60 * 1000 : undefined,
});
# .github/workflows/test.yml
jobs:
  test:
    timeout-minutes: 20  # CI hard kill: 20 phút (margin 5 phút)
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - run: npx playwright test --shard=${{ matrix.shard }}/4
12

Kết Quả Reporter Khi Abort

Khi run bị abort do globalTimeout, Playwright cố gắng flush report trước khi exit. Dữ liệu trong report:

  • Test đã hoàn thành — đầy đủ thông tin: status, duration, steps, attachments (screenshot, trace, video nếu config).
  • Test bị interrupt — có entry với status interrupted. Không có toàn bộ steps (chỉ có steps đã chạy xong trước khi bị cắt). Trace (nếu bật) có thể thiếu một phần cuối.
  • Test chưa chạy — tùy reporter. HTML reporter và JSON reporter thường không có entry cho test chưa chạy. JUnit reporter có thể liệt kê chúng là skipped.

Với HTML reporter, report vẫn mở được bình thường nhưng sẽ có ít entry hơn tổng số test trong suite. Không có cảnh báo rõ ràng nào nói "run bị abort" ngay trong UI — cần đọc exit code hoặc kiểm tra log CI.

Với blob reporter (dùng khi sharding), blob file được ghi cho test đã hoàn thành. merge-reports sẽ ghép partial report từ shard bị abort với các shard hoàn chỉnh khác.

13

Limitation

Abort không hoàn toàn graceful: Test đang chạy tại thời điểm globalTimeout hết sẽ bị cắt giữa chừng. Playwright không đợi assertion hiện tại hoàn thành hay action hoàn thành. Fixture teardown có thể không chạy hoàn chỉnh.

Partial report: Test chưa chạy sẽ không có dữ liệu trong report. Nếu abort xảy ra quá sớm (ví dụ shard chậm bất thường), toàn bộ một phần suite không có coverage data — gây nhầm lẫn khi đánh giá test coverage của run đó.

Không retry sau abort: Không có cơ chế tự động re-run các test bị abort. Nếu cần retry, phải chạy lại toàn bộ run (hoặc shard bị abort).

Không có per-project globalTimeout: Không thể set globalTimeout khác nhau cho từng project trong cùng một run. Tất cả project trong run chia sẻ cùng một globalTimeout.

14

Pitfalls

Pitfall 1: globalTimeout nhỏ hơn tổng test time

Nếu suite có 300 test, mỗi test mất 10s, 4 workers → estimated 750s = 12.5 phút, nhưng set globalTimeout: 10 * 60 * 1000 (10 phút). Run sẽ abort dù tất cả test đều pass — chỉ vì cần nhiều thời gian hơn mong đợi. CI fail dù không có test nào broken. Cần tính toán cẩn thận (xem bài 8) và thêm buffer.

Pitfall 2: globalTimeout lớn hơn CI job timeout

CI platform kill process trước khi Playwright kịp abort. Hậu quả: không có report, không có exit code Playwright, CI chỉ hiện "job timed out" mà không biết bao nhiêu test đã chạy. Giải pháp: luôn đặt globalTimeout nhỏ hơn CI job timeout ít nhất 5–10 phút.

Pitfall 3: Không set globalTimeout → CI hang vô thời hạn

Default 0 là không giới hạn. Nếu một test rơi vào vòng lặp vô hạn hoặc await không bao giờ resolve (do mock server crash, port không bind được), run sẽ hang cho đến khi CI platform kill job theo giới hạn platform (6 giờ trên GitHub Actions free). Không có report, không có signal rõ ràng. Với CI môi trường production, luôn set globalTimeout cho run CI.

Pitfall 4: Set globalTimeout quá thấp khi có retry

Retry nhân thời gian chạy của test flaky. Ví dụ: retries: 2, test timeout 30s, 20 test flaky → worst case thêm 20 × 30s × 2 retry / 4 workers = 300s = 5 phút. Nếu globalTimeout không tính overhead retry, run sẽ abort trong khi các test flaky đang được retry. Test cuối cùng có thể pass nếu được retry đủ lần, nhưng bị cắt giữa chừng vì hết globalTimeout. Khi tính globalTimeout, cần cộng cả worst-case retry overhead.

15

Quiz

Câu 1. Suite có 100 test, tất cả đều pass. Run mất 35 phút. Config đặt globalTimeout: 30 * 60 * 1000. Exit code của Playwright là bao nhiêu?

Đáp án

Non-zero. Run vượt globalTimeout 30 phút → Playwright abort lúc 30 phút. Tại thời điểm đó có test đang chạy (interrupted) và test chưa chạy. Exit code non-zero → CI build fail, dù các test đã chạy đều passed.

Câu 2. CI job timeout là 45 phút. Playwright config có globalTimeout: 50 * 60 * 1000. Điều gì xảy ra nếu run mất 47 phút?

Đáp án

CI platform kill job tại 45 phút trước khi Playwright có cơ hội abort (globalTimeout là 50 phút, chưa kích hoạt). Process bị kill thô → không có Playwright report, không có exit code rõ ràng từ Playwright. CI chỉ hiện "job timed out". Cần đặt globalTimeout < CI job timeout (ví dụ 40 phút) để Playwright abort graceful trước khi CI platform kill.

Câu 3. Dùng 4 shard. globalTimeout: 15 * 60 * 1000. Shard 3 chạy test chậm hơn bình thường và mất 18 phút. Điều gì xảy ra với shard 3 và ảnh hưởng đến merge-reports?

Đáp án

Shard 3 bị abort sau 15 phút. Test đang chạy tại lúc abort → interrupted. Test chưa chạy → không có data. Shard 3 exit non-zero → CI matrix job của shard 3 fail. Khi merge-reports: blob file của shard 3 chứa partial data. Report sau merge sẽ thiếu kết quả của các test chưa chạy trong shard 3.

Câu 4. Config có globalTimeout: 20 * 60 * 1000, retries: 2, timeout: 30000 (30s per test). Suite có 50 test, 10% flaky. Ước tính xem có đủ globalTimeout không?

Đáp án

5 test flaky, worst case mỗi test chạy 3 lần × 30s = 90s. 45 test còn lại: 45 × 30s = 1350s. Tổng sequential: 1350s + 5 × 90s = 1800s. Với 4 workers: 1800s / 4 = 450s = 7.5 phút. Với buffer và overhead: ~10–12 phút thực tế. globalTimeout 20 phút đủ thoải mái. Tuy nhiên nếu số test tăng hoặc test timeout tăng lên, cần tính lại.

Câu 5. Lệnh CLI nào disable globalTimeout kể cả khi config đặt 30 phút?

Đáp án

npx playwright test --global-timeout=0. Flag --global-timeout=0 truyền 0, tắt giới hạn. Lưu ý flag là --global-timeout (có dấu gạch ngang), không phải --globalTimeout.

16

Bài Tiếp Theo

Bài 83: test timeout — Giới Hạn Thời Gian Cho Một Test — cú pháp timeout trong config, override per-project, per-describe, per-test, CLI --timeout, và cách timeout tương tác với retries.