Mục lục
- Mục Tiêu Bài Học
- Wildcard vs Exact Match — Ranh Giới Rõ
- Ký Tự Glob: * và ?
- Shell Escape — Vì Sao Phải Quote
- Naming Convention Để Wildcard Có Ý Nghĩa
- Filter Theo Dimension
- Combine Nhiều --project Wildcard
- CI Matrix Với Wildcard
- Dependency Behavior Khi Wildcard Match Nhiều Project
- Reporter Với Wildcard
- Giới Hạn Của Wildcard
- 4 Pitfalls
- Quiz + Bài Tiếp
Mục Tiêu Bài Học
Sau bài này bạn sẽ:
- Hiểu rõ khi nào
--projectchuyển sang wildcard mode và behavior khác gì so với exact match. - Dùng đúng
*và?để 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
--projectwildcard để 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.
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.
chromiumchỉ match project tên chính xác làchromium. - Có
*hoặc?→ wildcard match, case-insensitive (từ v1.46).*chromium*matchchromium,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*' và '*chromium*' cho kết quả như nhau. Exact match thì không — Chromium (chữ hoa C) là tên khác với chromium.
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?'.
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 |
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.
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.
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
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.
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.
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.
Giới Hạn Của Wildcard
Wildcard của Playwright là glob đơn giản — chỉ * và ?. 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.
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.
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.
