Danh sách bài viết

Bài 61: CLI --project '*chromium*' — Wildcard Pattern

Từ Playwright v1.46, --project chấp nhận glob pattern: * match bất kỳ chuỗi ký tự, ? match đúng 1 ký tự. Bài này đi sâu vào cú pháp wildcard, shell escape trên các shell khác nhau, naming convention để pattern có ý nghĩa, filter theo dimension (browser/env/device), combine nhiều pattern, CI matrix với biến env, dependency behavior khi wildcard match nhiều project, và 4 pitfall hay gặp trên 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ẽ:

  • Hiểu rõ khi nào --project chuyển sang wildcard mode và behavior khác gì so với exact match.
  • Dùng đúng *? để viết pattern có nghĩa.
  • Biết cách escape pattern trên Bash, Zsh, PowerShell, CMD để tránh shell expand nhầm.
  • Thiết kế naming convention cho project config để pattern wildcard có thể filter theo browser, env, hoặc device.
  • Combine nhiều --project wildcard để chạy tập hợp project theo yêu cầu.
  • Tránh 4 pitfall: quên quote, pattern không match ai, match ngoài ý muốn (kéo cả setup project vào), hành vi khác nhau trên PowerShell.
2

Wildcard vs Exact Match — Ranh Giới Rõ

Playwright quyết định chế độ match dựa trên sự có mặt của * hoặc ? trong giá trị truyền vào:

  • Không có ký tự glob → exact match, case-sensitive. chromium chỉ match project tên chính xác là chromium.
  • * hoặc ?wildcard match, case-insensitive (từ v1.46). *chromium* match chromium, mobile-chromium, Chromium-Staging, v.v.
npx playwright test --project=chromium        # exact match — chỉ match 'chromium'
npx playwright test --project='*chromium*'    # wildcard — match mọi tên chứa "chromium"

Case-insensitive của wildcard là điểm khác biệt đáng chú ý: '*CHROMIUM*''*chromium*' cho kết quả như nhau. Exact match thì không — Chromium (chữ hoa C) là tên khác với chromium.

3

Ký Tự Glob: * và ?

Playwright wildcard hỗ trợ 2 ký tự glob:

  • * — match bất kỳ chuỗi ký tự, kể cả chuỗi rỗng.
  • ? — match đúng 1 ký tự bất kỳ.

Không có **, character class [abc], hay regex. Pattern đơn giản chủ đích để dễ đọc và dễ dự đoán.

Ví dụ thực tế với config nhiều project:

// playwright.config.ts — config nhiều project theo dimension
export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    { name: 'chrome-desktop-staging', use: { ...devices['Desktop Chrome'] } },
    { name: 'chrome-desktop-prod',    use: { ...devices['Desktop Chrome'] } },
    { name: 'chrome-mobile-staging',  use: { ...devices['Pixel 5'] } },
    { name: 'chrome-mobile-prod',     use: { ...devices['Pixel 5'] } },
    { name: 'firefox-desktop-staging',use: { ...devices['Desktop Firefox'] } },
    { name: 'firefox-desktop-prod',   use: { ...devices['Desktop Firefox'] } },
  ],
});

Với config trên, các pattern và kết quả tương ứng:

# Match mọi project tên chứa "chromium" (bài dùng "chrome" trong ví dụ cụ thể)
npx playwright test --project='*chrome*'
# → chrome-desktop-staging, chrome-desktop-prod, chrome-mobile-staging, chrome-mobile-prod

# Match mọi project bắt đầu bằng "firefox"
npx playwright test --project='firefox*'
# → firefox-desktop-staging, firefox-desktop-prod

# Match mọi project kết thúc bằng "-prod"
npx playwright test --project='*-prod'
# → chrome-desktop-prod, chrome-mobile-prod, firefox-desktop-prod

# Match project tên có "-desktop-" ở giữa
npx playwright test --project='*-desktop-*'
# → chrome-desktop-staging, chrome-desktop-prod, firefox-desktop-staging, firefox-desktop-prod

# Match project tên có đúng 1 ký tự trước "-desktop-staging"
npx playwright test --project='?hrome-desktop-staging'
# → chrome-desktop-staging (c là ký tự match ?)

Ký tự ? ít dùng thực tế hơn *, nhưng hữu ích khi muốn match các variant chỉ khác nhau 1 ký tự — ví dụ chrome-v1, chrome-v2, chrome-v3'chrome-v?'.

4

Shell Escape — Vì Sao Phải Quote

Shell (Bash, Zsh) xử lý * như glob file trước khi truyền argument vào process. Nếu không quote, *chromium* bị expand thành danh sách file trong thư mục hiện tại trước khi Playwright nhìn thấy nó:

# Không quote — nguy hiểm
npx playwright test --project=*-prod
# Shell expand * thành tên file khớp pattern trong CWD
# Nếu có file "auth-prod.spec.ts" → thành: --project=auth-prod.spec.ts
# Playwright báo lỗi "project not found" với tên file kỳ lạ
# Không có file nào khớp → shell giữ nguyên literal (Bash mặc định)
# Nhưng hành vi này phụ thuộc shell option (failglob, nullglob) → không đáng tin cậy

Cách quote đúng theo từng shell:

# Bash / Zsh — Single quote: an toàn nhất, shell KHÔNG expand bất cứ gì bên trong
npx playwright test --project='*-prod'
npx playwright test --project='*chromium*'

# Bash / Zsh — Double quote: $ và ` vẫn được expand, nhưng * thì KHÔNG
# Thực tế double quote cũng an toàn cho wildcard
npx playwright test --project="*-prod"

# Tuy nhiên khi kết hợp với biến CI (xem phần CI matrix), double quote cần thiết
npx playwright test --project="*-$ENV_NAME"  # $ được expand trong double quote
                                              # single quote sẽ giữ nguyên $ENV_NAME literal

Trên PowerShell:

# PowerShell không expand * trong argument thông thường
# Nhưng recommend vẫn quote để explicit
npx playwright test --project='*-prod'
npx playwright test --project="*-prod"

# Dùng dấu = nối (không space) để tránh ambiguity trên PowerShell
npx playwright test --project='*-prod'   # OK
npx playwright test --project '*-prod'   # Có thể parse nhầm nếu token trước là option khác

Trên CMD (Windows Command Prompt):

:: CMD không hỗ trợ single quote. Dùng double quote hoặc = sign
npx playwright test --project="*-prod"

Tóm tắt nhanh:

Shell Single quote '…' Double quote "…" Không quote
Bash / Zsh An toàn, khuyên dùng An toàn (chỉ $ expand) Nguy hiểm — * bị expand
PowerShell An toàn An toàn Thường OK nhưng không đáng tin
CMD Không hỗ trợ An toàn Tránh dùng
5

Naming Convention Để Wildcard Có Ý Nghĩa

Wildcard chỉ hữu ích khi tên project được đặt theo quy tắc nhất quán. Nếu tên project tùy ý (chrome1, myFirefox, test-env-A), không thể viết pattern có nghĩa.

Convention phổ biến: đặt tên theo format {browser}-{device}-{env}:

chrome-desktop-staging
chrome-desktop-prod
chrome-mobile-staging
chrome-mobile-prod
firefox-desktop-staging
firefox-desktop-prod
webkit-desktop-staging
webkit-desktop-prod

Convention này cho phép filter theo từng dimension độc lập:

--project='chrome-*'         # mọi Chrome project
--project='*-mobile-*'       # mọi mobile project (bất kể browser, bất kể env)
--project='*-staging'        # mọi staging project (bất kể browser, bất kể device)
--project='*-desktop-prod'   # mọi prod trên desktop

Một convention khác khi cần env nhiều hơn: {browser}-{env}:

chromium-dev
chromium-staging
chromium-prod
firefox-staging
firefox-prod
--project='chromium-*'   # mọi chromium env
--project='*-staging'    # mọi staging browser

Điều cần tránh: tên project không theo dimension rõ ràng.

// Khó filter — tên không có cấu trúc
{ name: 'e2e-chrome' },
{ name: 'smoke-firefox' },
{ name: 'regression-webkit' },
// Pattern '--project=*-chrome' chỉ match e2e-chrome
// Nhưng ý nghĩa "chrome" ở đây là browser, không phải env hay scope
// Sau này thêm 'chrome-smoke' → bị bỏ sót nếu pattern là '*-chrome'

Quy tắc thực tế: quyết định trước sẽ filter theo dimension nào (browser? env? device?) rồi đặt dimension đó ở vị trí nhất quán trong tên — luôn ở đầu, cuối, hoặc giữa hai dấu gạch ngang. Trộn lẫn vị trí khiến pattern không thể viết ngắn gọn.

6

Filter Theo Dimension

Với naming convention {browser}-{device}-{env}, các use case filter thực tế:

Filter theo browser

# Chạy mọi Chrome variant (desktop staging, desktop prod, mobile staging, mobile prod)
npx playwright test --project='chrome-*'

# Chỉ Firefox
npx playwright test --project='firefox-*'

Hữu ích khi cần verify nhanh 1 browser family trước khi push — không cần viết 4 --project riêng lẻ.

Filter theo device

# Chạy mọi mobile project (chrome-mobile-staging, chrome-mobile-prod, webkit-mobile-*)
npx playwright test --project='*-mobile-*'

# Chỉ desktop
npx playwright test --project='*-desktop-*'

Filter theo environment

# Chỉ staging
npx playwright test --project='*-staging'

# Chỉ production
npx playwright test --project='*-prod'

Use case CI phổ biến: PR check chỉ chạy staging, merge to main mới chạy prod:

# PR check
npx playwright test --project='*-staging'

# Main branch
npx playwright test --project='*-prod'

Filter kết hợp 2 dimension

# Chrome desktop prod
npx playwright test --project='chrome-desktop-prod'   # exact match rõ hơn

# Nhưng nếu có nhiều chrome desktop variant:
# chrome-desktop-prod-us, chrome-desktop-prod-eu
npx playwright test --project='chrome-desktop-prod-*'

Khi cần filter kết hợp 2 dimension không liền kề nhau trong tên (ví dụ: browser + env, bỏ qua device), dùng multiple --project hoặc đặt tên theo dimension đó ở vị trí cuối — xem phần tiếp theo.

7

Combine Nhiều --project Wildcard

Mỗi lần lặp --project thêm 1 pattern vào OR list. Project match bất kỳ pattern nào sẽ được chạy:

# Chạy mọi staging VÀ mọi prod — bỏ qua dev
npx playwright test --project='*-staging' --project='*-prod'

# Chạy chrome + firefox, bỏ qua webkit
npx playwright test --project='chrome-*' --project='firefox-*'

# Chạy mobile staging + desktop prod
npx playwright test --project='*-mobile-staging' --project='*-desktop-prod'

Combine wildcard và exact match trong cùng lệnh — hoàn toàn hợp lệ:

# Chạy setup (exact) + mọi prod project (wildcard)
npx playwright test --project=setup --project='*-prod'

Không có operator "exclude" (invert/negate) trong --project. Nếu muốn chạy "tất cả trừ dev", phải liệt kê tất cả env còn lại:

# Không thể: --project NOT '*-dev'
# Workaround: liệt kê env muốn chạy
npx playwright test --project='*-staging' --project='*-prod'

Khi config có nhiều env (dev, staging, uat, prod), workaround này không mở rộng tốt. Cách khác là đặt tên với prefix để exclude dễ hơn — ví dụ tất cả non-dev đều bắt đầu bằng ci-:

ci-chrome-staging
ci-chrome-prod
ci-firefox-staging
ci-firefox-prod
dev-chrome   ← local only
npx playwright test --project='ci-*'   # bỏ qua toàn bộ dev-* một cách tự nhiên
8

CI Matrix Với Wildcard

Wildcard kết hợp CI matrix variable cho phép 1 workflow chạy nhiều env mà không cần hardcode từng project:

# .github/workflows/playwright.yml
jobs:
  e2e:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        env: [staging, prod]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --project="*-${{ matrix.env }}"

Matrix trên tạo 2 job: 1 job chạy --project='*-staging', 1 job chạy --project='*-prod'. Khi thêm env mới vào matrix.env, không cần sửa gì trong config hay test file — chỉ cần project name theo đúng convention.

Lưu ý: dùng double quote (không phải single quote) khi kết hợp với biến ${{ ... }}. Single quote giữ nguyên literal, ${{ matrix.env }} sẽ không được expand:

# ĐÚNG — double quote, biến được expand
- run: npx playwright test --project="*-${{ matrix.env }}"

# SAI — single quote, biến không được expand
- run: npx playwright test --project='*-${{ matrix.env }}'
# Playwright nhận pattern: *-${{ matrix.env }}
# Không match project nào → error hoặc empty run

Pattern CI phức tạp hơn — matrix 2 chiều (browser + env):

strategy:
  matrix:
    browser: [chrome, firefox]
    env: [staging, prod]
steps:
  - run: npx playwright test --project="${{ matrix.browser }}-*-${{ matrix.env }}"

4 job: chrome-*-staging, chrome-*-prod, firefox-*-staging, firefox-*-prod. Mỗi job filter đúng tập project theo cả 2 dimension.

9

Dependency Behavior Khi Wildcard Match Nhiều Project

Khi wildcard match nhiều project, dependency resolution hoạt động bình thường: mỗi matched project tự include dependency của nó. Dependency project shared (nhiều project cùng depend vào) chỉ chạy 1 lần:

// Config
projects: [
  { name: 'setup', testMatch: /.*\.setup\.ts/ },
  {
    name: 'chrome-desktop-staging',
    dependencies: ['setup'],
    use: { ...devices['Desktop Chrome'] },
  },
  {
    name: 'chrome-mobile-staging',
    dependencies: ['setup'],
    use: { ...devices['Pixel 5'] },
  },
  {
    name: 'chrome-desktop-prod',
    dependencies: ['setup'],
    use: { ...devices['Desktop Chrome'] },
  },
]
npx playwright test --project='*-staging'
# Matched: chrome-desktop-staging, chrome-mobile-staging
# Playwright resolve dependency: cả 2 depend vào 'setup'
# setup chạy 1 lần → chrome-desktop-staging + chrome-mobile-staging chạy song song

Lưu ý quan trọng: setup project trong ví dụ trên KHÔNG bị match bởi '*-staging' (tên setup không kết thúc bằng -staging). Setup chỉ được kéo vào vì dependency, không phải vì wildcard match.

Nếu vô tình đặt tên setup project theo convention có thể match wildcard, setup sẽ bị chạy như 1 project thông thường, không chỉ như dependency:

// Tên nguy hiểm — dễ bị match ngoài ý muốn
{ name: 'chrome-setup-staging', testMatch: /.*\.setup\.ts/ }
// --project='*-staging' sẽ match cả setup project này
// Setup chạy 2 lần: 1 lần do wildcard match, 1 lần do dependency của project khác

Best practice: tên setup project không theo convention browser-device-env, ví dụ setup, global-setup, auth-setup — tên không chứa env suffix để không bị wildcard vô tình kéo vào.

10

Reporter Với Wildcard

Reporter hiển thị tên project đầy đủ (không phải pattern) — giúp biết chính xác project nào được chạy sau khi wildcard resolve:

npx playwright test --project='*-staging'
Running 6 tests using 3 workers

  ✓  [chrome-desktop-staging] › login.spec.ts:5:5 › login flow (1.1s)
  ✓  [chrome-desktop-staging] › cart.spec.ts:8:3 › add to cart (0.9s)
  ✓  [chrome-mobile-staging]  › login.spec.ts:5:5 › login flow (1.3s)
  ✓  [chrome-mobile-staging]  › cart.spec.ts:8:3 › add to cart (1.1s)
  ✓  [firefox-desktop-staging]› login.spec.ts:5:5 › login flow (1.4s)
  ✓  [firefox-desktop-staging]› cart.spec.ts:8:3 › add to cart (1.2s)

6 passed (2.8s)

Project không match wildcard không xuất hiện trong output — không có dòng "skipped" hay "filtered". Nếu cần verify pattern match đúng project nào trước khi chạy, dùng --list:

npx playwright test --list --project='*-staging'
# In danh sách test + project sẽ chạy, không thực sự chạy test

--list đặc biệt hữu ích khi dùng trên CI để debug trước khi commit workflow — không mất thời gian chờ toàn bộ suite chạy.

11

Giới Hạn Của Wildcard

Wildcard của Playwright là glob đơn giản — chỉ *?. Không có:

  • Regex: [abc], (a|b), ^start, end$.
  • Negation pattern: !pattern.
  • Nested glob: **.

Không có invert/exclude operator. Không thể viết "chạy tất cả trừ project X". Workaround là liệt kê rõ tập muốn chạy (xem phần 7).

Pattern stale khi rename project. Nếu đổi tên chrome-desktop-prod thành chrome-laptop-prod, pattern '*-desktop-*' không còn match project đó nữa. Không có cảnh báo nào — project bị bỏ qua silent. Cần audit lại pattern sau mỗi lần rename project.

Wildcard khác --grep:

  • --project='*-staging': filter theo tên project.
  • --grep='staging': filter theo tên test (hoặc tag @staging). Không liên quan đến project name.

Hai flag hoạt động theo lớp AND — --project='*-staging' --grep='@smoke' chạy test có tag @smoke trong các staging project.

12

4 Pitfalls

Pitfall 1: Quên quote — shell expand * thành tên file

# Không quote trên Bash/Zsh
npx playwright test --project=*-prod
# Shell tìm file/dir trong CWD khớp *-prod
# Nếu có file "some-prod.txt" → thành --project=some-prod.txt
# Playwright báo lỗi "project not found: some-prod.txt"
# Nếu không có file nào khớp + Bash option nullglob → --project= (rỗng)

Fix: luôn dùng single quote trên Bash/Zsh, double quote trên CMD.

Pitfall 2: Pattern không match project nào — silent empty run

Từ Playwright v1.49+, --project với wildcard không match project nào sẽ báo error và exit non-zero:

npx playwright test --project='*-uat'
# Nếu không có project nào chứa "-uat":
# Error: No projects matched filter "*-uat". Available projects: "chrome-desktop-staging", ...

Tuy nhiên, trên các version cũ hơn, behavior có thể khác. Kiểm tra bằng --list trước khi đưa pattern vào CI là thói quen tốt bất kể version.

Pitfall 3: '*' match cả setup project — setup chạy như regular project

npx playwright test --project='*'
# Match MỌI project kể cả 'setup'
# setup chạy 1 lần do wildcard match, THÊM 1 lần do dependency
# Kết quả: setup chạy 2 lần — không phải lỗi nghiêm trọng nhưng lãng phí thời gian
# Hoặc setup có side effect (tạo/xóa data) → chạy 2 lần gây vấn đề

Dùng '*' khi thực sự muốn chạy tất cả project (tương đương không truyền --project) nhưng cần chú ý setup project sẽ bị include thêm lần nữa qua wildcard match. Tốt hơn là không truyền --project nếu muốn chạy tất cả.

Pitfall 4: PowerShell handle pattern khác — cần test thực tế

PowerShell có globbing riêng và cách parse argument khác Bash. Một số trường hợp pattern hoạt động khác không dự đoán được:

# PowerShell — dùng = sign thay vì space để tránh parse nhầm
npx playwright test --project='*-prod'   # OK trong hầu hết trường hợp

# Nhưng khi dùng trong CI YAML, GitHub Actions runner trên Windows dùng PowerShell
# YAML double quote + PowerShell có thể strip quote
# Safe pattern trên Windows runner: dùng = sign
- run: npx playwright test --project="*-${{ matrix.env }}"

Nếu workflow chạy trên cả Ubuntu và Windows runner, test pattern trên cả 2 OS trước khi rollout. Hành vi trên ubuntu-latest không đảm bảo giống windows-latest.

13

Quiz + Bài Tiếp

Quiz

1. Config có 4 project: chrome-desktop-prod, chrome-mobile-prod, firefox-desktop-prod, setup. Lệnh npx playwright test --project='*-prod' sẽ match những project nào? setup có được chạy không và tại sao?

Đáp án

Match 3 project: chrome-desktop-prod, chrome-mobile-prod, firefox-desktop-prod. setup không match vì tên "setup" không kết thúc bằng "-prod". Nếu 3 project kia có dependencies: ['setup'], setup vẫn được chạy — nhưng do dependency resolution, không phải do wildcard match.

2. Trên Bash, tại sao npx playwright test --project=*-prod (không quote) có thể gây ra error về "project not found" với tên kỳ lạ?

Đáp án

Bash expand *-prod thành danh sách file/dir trong thư mục hiện tại khớp pattern đó trước khi truyền cho npx. Nếu có file playwright-prod.config.ts chẳng hạn, argument trở thành --project=playwright-prod.config.ts. Playwright nhận tên file đó, không tìm thấy project với tên đó, báo lỗi. Fix: luôn dùng single quote '*-prod'.

3. Lệnh YAML trong GitHub Actions: npx playwright test --project='*-${{ matrix.env }}'. Chạy lệnh này với matrix.env=staging thì Playwright nhận pattern gì? Đúng hay sai?

Đáp án

Sai. Single quote ngăn shell expand ${{ matrix.env }}. Playwright nhận pattern literal *-${{ matrix.env }}, không match project nào tên chứa chuỗi đó. Cần dùng double quote: "*-${{ matrix.env }}" để biến được expand thành *-staging.

4. Wildcard --project khác --grep thế nào? Lệnh npx playwright test --project='*-staging' --grep='@smoke' hoạt động ra sao?

Đáp án

--project filter theo tên project; --grep filter theo tên test hoặc tag trong test file. Hai filter áp dụng AND: lệnh trên chạy các test có tag @smoke trong tất cả project kết thúc bằng -staging. Test không có @smoke bị bỏ qua dù thuộc staging project.

5. Tại sao đặt tên setup project theo convention chrome-setup-staging là bad practice khi dùng wildcard?

Đáp án

Pattern '*-staging' sẽ match cả chrome-setup-staging. Setup project bị kéo vào như 1 project thông thường qua wildcard match, đồng thời cũng được chạy qua dependency resolution nếu các project khác depend vào nó. Kết quả: setup chạy 2 lần. Nếu setup có side effect (seed data, login, tạo file), chạy 2 lần gây vấn đề. Best practice: tên setup project không theo convention dimension — dùng setup, global-setup, auth-setup.

Bài Tiếp Theo

Bài 62: CLI --no-deps — Skip Dependency