Mục lục
- Mục Tiêu Bài Học
- --grep Match Gì Khi Dùng Với Tag
- Cú Pháp Cơ Bản Filter Tag
- Regex Pattern Cho Tag
- Tag Inheritance Từ Describe + Grep
- Grep Trong playwright.config.ts
- CLI Flag Override Config
- AND Logic Workaround
- Kết Hợp Với --project
- CI Pipeline — Phân Tầng Theo Tag
- Chiến Lược Filter Theo Giai Đoạn
- Reporter Behavior Khi Filter
- Pitfalls
- Tổng Kết
- Quiz
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài này bạn sẽ nắm được:
- Cơ chế --grep match tag field (v1.42+) khác với match thuần title ở chỗ nào.
- Viết regex OR, char-class để filter nhiều tag cùng lúc mà không cần nhiều lần chạy.
- Hiểu tag inheritance từ describe và cách --grep bắt đúng test con.
- Cấu hình
grep/grepInverttrongplaywright.config.tsvà biết khi nào CLI override config. - Mô phỏng AND logic bằng
--grep-inverthoặc lookahead regex. - Tích hợp --grep vào GitHub Actions để phân tầng smoke / regression / nightly.
- Nhận biết 5 pitfall thường gặp khi filter tag qua CLI.
--grep Match Gì Khi Dùng Với Tag
Playwright chạy --grep bằng cách test regex JavaScript trên fullTitle của mỗi test case. fullTitle là chuỗi ghép từ tất cả describe ancestor + title của test case, cách nhau bằng dấu >. Kể từ v1.42, khi test có field tag, Playwright nối tag vào fullTitle trước khi test regex — đây là thay đổi then chốt giúp --grep "@smoke" hoạt động đáng tin cậy ngay cả khi title test không chứa chuỗi @smoke.
Với test sau:
test.describe('Checkout', () => {
test('complete order', { tag: ['@smoke', '@p1'] }, async ({ page }) => {
// ...
});
});
fullTitle Playwright xây dựng để test grep: "Checkout > complete order @smoke @p1"
Ba nguồn khớp —Playwright test regex trên cả ba, bất kỳ match nào là đủ:
| Nguồn | Ví dụ | --grep match |
|---|---|---|
Title của test() | "complete order" | --grep "order" |
| Tag field (v1.42+) | @smoke @p1 | --grep "@smoke" |
| Describe path | "Checkout" | --grep "Checkout" |
Trước v1.42, tag field không tham gia vào fullTitle → --grep "@smoke" chỉ match nếu title test chứa literal @smoke. Nếu project đang dùng Playwright < v1.42, cần kiểm tra version trước khi dựa vào behavior này.
Cú Pháp Cơ Bản Filter Tag
# Chạy chỉ test có tag @smoke
npx playwright test --grep '@smoke'
# Dùng = thay khoảng trắng — cú pháp tương đương
npx playwright test --grep='@smoke'
Single-quote là lựa chọn an toàn nhất trên Bash/zsh: shell không expand bất kỳ ký tự đặc biệt nào bên trong. Nếu dùng double-quote, cẩn thận với $, backtick và backslash.
Ký tự @ không phải metacharacter regex — không cần escape. Pattern '@smoke' thực hiện substring match trên fullTitle: chỉ cần fullTitle chứa chuỗi @smoke ở bất kỳ vị trí nào là match.
Verify nhanh trước khi chạy thật bằng --list:
npx playwright test --grep '@smoke' --list
Output liệt kê test sẽ chạy mà không execute — nếu danh sách trống hoặc sai, điều chỉnh pattern ngay.
Regex Pattern Cho Tag
--grep nhận JavaScript regex — có thể dùng toàn bộ cú pháp regex để filter tag phức tạp.
OR — chạy test có tag A hoặc tag B:
npx playwright test --grep '@smoke|@critical'
Trong YAML CI cần bao trong single-quote để YAML không diễn giải | là block scalar:
run: npx playwright test --grep '@smoke|@critical'
Char-class — chạy test có tag trong một nhóm ký hiệu:
# Match @p0, @p1, @p2 (priority 0, 1, 2)
npx playwright test --grep '@p[012]'
# Tương đương
npx playwright test --grep '@p[0-2]'
Anchor — title bắt đầu bằng chuỗi cụ thể:
npx playwright test --grep '^Login'
Pattern này match test trong describe "Login ..." hoặc test có title bắt đầu bằng Login.
Escape ký tự đặc biệt trong tag: Nếu tag chứa dấu chấm như @v1.0, dấu chấm là metacharacter regex (match mọi ký tự). Dùng escape để match literal:
# Single-quote: escape 1 lớp
npx playwright test --grep '@v1\.0'
# Double-quote: escape 2 lớp (shell + regex)
npx playwright test --grep "@v1\\.0"
Bảng tóm tắt pattern thường dùng:
| Pattern | Ý nghĩa | Match |
|---|---|---|
@smoke | Substring | Test có tag @smoke |
@smoke|@critical | OR | Test có tag @smoke hoặc @critical |
@p[0-2] | Char-class | Test có tag @p0, @p1, hoặc @p2 |
@team-\w+ | Word chars | Test có tag bắt đầu bằng @team- |
@v1\.0 | Literal dot | Test có tag @v1.0 (không phải @v1x0) |
Tag Inheritance Từ Describe + Grep
Khi test.describe có field tag, mọi test con kế thừa tag đó. Playwright merge tag từ tất cả describe ancestor vào fullTitle — --grep có thể bắt bằng tag của describe.
test.describe('Auth', { tag: '@auth' }, () => {
test('login', { tag: '@smoke' }, async ({ page }) => {
// fullTitle: "Auth > login @auth @smoke"
});
test('logout', async ({ page }) => {
// fullTitle: "Auth > logout @auth"
// Tag @auth được kế thừa từ describe dù test không khai báo tag riêng
});
test.describe('OAuth', { tag: '@oauth' }, () => {
test('google login', { tag: '@smoke' }, async ({ page }) => {
// fullTitle: "Auth > OAuth > google login @auth @oauth @smoke"
});
});
});
Kết quả filter:
# Match "login" và "google login" (cả 2 có @smoke)
npx playwright test --grep '@smoke'
# Match cả 3 test trong describe Auth
npx playwright test --grep '@auth'
# Match "google login" — có đủ cả @oauth và @auth
npx playwright test --grep '@oauth'
Đây là cách hiệu quả để gắn tag nhóm cho cả describe block thay vì lặp lại tag trên từng test. Lưu ý: --grep không phân biệt tag đến từ describe hay từ test con — chỉ cần xuất hiện trong fullTitle.
Grep Trong playwright.config.ts
Thay vì gõ flag mỗi lần chạy, có thể đặt filter cố định trong config. Field nhận RegExp object (không phải string):
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Trên CI chỉ chạy @smoke + @regression, local chạy hết
grep: process.env.CI ? /@smoke|@regression/ : undefined,
// Loại trừ @slow ở mọi môi trường
grepInvert: /@slow/,
});
Điểm khác biệt quan trọng so với CLI string:
- Config nhận
RegExpliteral — có thể dùng flag/i(case-insensitive):/@smoke/i. - CLI string luôn case-sensitive; không có cách bật case-insensitive trực tiếp qua CLI.
- Config
grepcó thể là array:grep: [/@smoke/, /@critical/]— Playwright chạy test khớp bất kỳ element (OR logic).
// Array grep — OR logic
export default defineConfig({
grep: [/@smoke/, /@critical/],
});
Array grep trong config tương đương /@smoke|@critical/ nhưng đọc rõ ràng hơn khi có nhiều điều kiện.
CLI Flag Override Config
Quy tắc ưu tiên: CLI flag thay thế hoàn toàn giá trị grep / grepInvert trong config — không merge, không stack.
// playwright.config.ts
export default defineConfig({
grep: /@smoke|@regression/,
grepInvert: /@slow/,
});
# CLI override grep — chỉ @critical chạy, bỏ qua config grep
npx playwright test --grep '@critical'
# Config grepInvert (@slow) VẪN còn hiệu lực vì chỉ override grep, không override grepInvert
# Để override grepInvert cũng, dùng --grep-invert
npx playwright test --grep '@critical' --grep-invert '@flaky'
Khi cả --grep và --grep-invert truyền từ CLI, chúng override đồng thời cả hai field trong config. Nếu chỉ truyền --grep, grepInvert từ config vẫn áp dụng.
Behavior này quan trọng khi debug local: nếu config có grep: /@smoke/ (CI-only), chạy local mà không truyền flag thì chỉ thấy smoke test. Dùng --grep '.*' hoặc xóa tạm config để chạy hết.
AND Logic Workaround
Playwright không có native AND cho --grep. Hai cách workaround:
Cách 1 — --grep-invert mô phỏng AND NOT:
# @smoke AND NOT @slow
npx playwright test --grep '@smoke' --grep-invert '@slow'
Logic: lấy tập match @smoke, loại bỏ những test đồng thời match @slow. Không phải AND thuần túy nhưng đủ cho 90% use case thực tế.
Cách 2 — lookahead regex (AND thuần):
# @smoke AND @critical — cả 2 tag phải xuất hiện trong fullTitle
npx playwright test --grep '(?=.*@smoke)(?=.*@critical)'
Lookahead (?=.*@X) assert chuỗi @X tồn tại ở vị trí bất kỳ phía sau, không tiêu thụ ký tự. Kết hợp nhiều lookahead = AND logic. Cú pháp hoạt động vì Playwright dùng JavaScript regex (V8).
Ví dụ phức tạp hơn:
# @smoke AND @p1 AND NOT @flaky
npx playwright test \
--grep '(?=.*@smoke)(?=.*@p1)' \
--grep-invert '@flaky'
So sánh hai cách:
| Cách | Biểu thức | Đọc được | Giới hạn |
|---|---|---|---|
| --grep-invert | --grep '@smoke' --grep-invert '@slow' | Tốt | Chỉ NOT, không AND thuần |
| Lookahead | --grep '(?=.*@smoke)(?=.*@p1)' | Khó đọc hơn | Không phải mọi shell đều an toàn với (?=) |
Nếu AND logic phức tạp thường xuyên, cân nhắc dùng testInfo.tags trong beforeEach để branch test logic thay vì cố filter trước run.
Kết Hợp Với --project
--grep và --project filter độc lập nhau, kết hợp theo AND: test phải vừa match pattern vừa thuộc project được chỉ định.
# Smoke test, chỉ trên Chromium
npx playwright test --grep '@smoke' --project=chromium
# Critical tests cross-browser (Chromium + WebKit)
npx playwright test --grep '@critical' --project=chromium --project=webkit
# P0 + P1 trên tất cả browser trừ Firefox
npx playwright test --grep '@p[01]' --project=chromium --project=webkit --project=edge
Use case điển hình: smoke test chạy full trên Chromium (nhanh), chỉ @critical chạy cross-browser để tiết kiệm thời gian CI.
# .github/workflows/ci.yml
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- name: Smoke (Chromium only)
run: npx playwright test --grep '@smoke' --project=chromium
cross-browser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- name: Critical cross-browser
run: npx playwright test --grep '@critical' --project=chromium --project=webkit
CI Pipeline — Phân Tầng Theo Tag
Mô hình phân tầng thực tế với 3 pipeline:
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, develop]
pull_request:
schedule:
# Nightly lúc 2 giờ sáng UTC
- cron: '0 2 * * *'
jobs:
smoke:
name: Smoke (PR + Push)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Run smoke tests
run: npx playwright test --grep '@smoke' --project=chromium
regression:
name: Regression (Push to main only)
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- name: Run regression (skip slow)
run: npx playwright test --grep '@regression' --grep-invert '@slow'
nightly:
name: Full Suite Nightly
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- name: Run all tests
run: npx playwright test
Nguyên tắc phân tầng:
- Smoke: bắt buộc pass trên mọi PR. Giữ thời gian dưới 10 phút. Chỉ test critical path.
- Regression: chạy khi merge vào main. Bao gồm @regression, loại trừ @slow và @flaky.
- Nightly: full suite, bao gồm cả @slow. Không block merge — chỉ alert.
Chiến Lược Filter Theo Giai Đoạn
Bốn pattern filter phổ biến tương ứng với từng giai đoạn của vòng đời test:
Quick check (PR, dưới 10 phút):
npx playwright test --grep '@smoke'
Chỉ critical path. Tag @smoke nên gắn cho không quá 10-15% tổng số test.
Pre-release — chỉ priority cao:
npx playwright test --grep '@p[01]'
P0 = blocker, P1 = critical. Chạy trước khi deploy staging hoặc production.
Owner-specific — chỉ test của một team:
npx playwright test --grep '@team-payment'
Hữu ích khi nhiều team cùng chạy trên cùng Playwright suite. Mỗi team tag test của mình, chạy riêng mà không ảnh hưởng team khác.
Debug WIP — chỉ test đang viết:
npx playwright test --grep '@wip' --headed --workers=1
Tag @wip nên được exclude khỏi CI (thêm grepInvert: /@wip/ vào config).
So sánh nhanh thời gian ước tính (suite 200 test, mỗi test ~3s trung bình):
| Pattern | Test match | Thời gian ước tính |
|---|---|---|
@smoke | ~20 test | ~2 phút (4 workers) |
@p[01] | ~40 test | ~4 phút |
@regression | ~160 test | ~15 phút |
| Full suite | 200 test | ~20 phút |
Reporter Behavior Khi Filter
--grep filter trước khi run — test không match không được collect, không xuất hiện trong bất kỳ reporter nào.
- List reporter: chỉ hiển thị test đã match. Không có dòng "skipped" cho test bị lọc — chúng không tồn tại trong run đó.
- HTML report: chỉ chứa test match. Nếu mở report sau khi chạy
--grep '@smoke', chỉ thấy smoke test. - JUnit / JSON report: tương tự — chỉ test match.
Sự khác biệt so với test.skip(): skip xuất hiện trong report với trạng thái "skipped". Test bị grep filter không xuất hiện gì cả — đây là điểm cần lưu ý khi phân tích coverage.
Nếu cần biết "bao nhiêu test bị bỏ qua vì filter", chạy thêm --list không có --grep để đếm tổng, rồi so sánh:
# Tổng số test trong suite
npx playwright test --list | tail -1
# Số test match @smoke
npx playwright test --grep '@smoke' --list | tail -1
Pitfalls
1. Quên prefix @ trong tag khai báo:
// SAI — tag không có @
test('login', { tag: 'smoke' }, async ({ page }) => { /* ... */ });
// ĐÚNG
test('login', { tag: '@smoke' }, async ({ page }) => { /* ... */ });
Nếu khai báo 'smoke' (không có @), fullTitle sẽ chứa smoke thay vì @smoke → --grep '@smoke' không match. Playwright không báo lỗi — CI sẽ miss test âm thầm.
2. Tag typo silent skip:
// Typo: @smok thay vì @smoke
test('checkout', { tag: '@smok' }, async ({ page }) => { /* ... */ });
Không có validation tên tag — --grep '@smoke' sẽ bỏ qua test này. CI không báo gì. Cách phòng: định nghĩa constant tag trong một file chung và import thay vì viết string trực tiếp:
// tags.ts
export const Tags = {
SMOKE: '@smoke',
CRITICAL: '@critical',
P0: '@p0',
} as const;
// test file
import { Tags } from './tags';
test('login', { tag: Tags.SMOKE }, async ({ page }) => { /* ... */ });
3. Shell expand | trong --grep:
# SAI — không quote, shell interpret | như pipe
npx playwright test --grep @smoke|@critical # Chạy 'playwright test --grep @smoke' rồi pipe sang '@critical'
# ĐÚNG — single-quote
npx playwright test --grep '@smoke|@critical'
4. Chạy --grep nhiều lần không stack:
# KHÔNG hoạt động như OR — chỉ lần cuối có hiệu lực
npx playwright test --grep '@smoke' --grep '@critical'
# Thực tế chỉ filter @critical
Nếu muốn OR, dùng '@smoke|@critical' trong một --grep duy nhất.
5. Filter ẩn do config grep còn sót:
// playwright.config.ts — từ CI config cũ
export default defineConfig({
grep: /@smoke/, // Dev quên xóa khi test local
});
Local chạy thấy ít test hơn bình thường nhưng không có cảnh báo. Kiểm tra config định kỳ hoặc log rõ ràng khi grep đang active:
const activeGrep = process.env.CI ? /@smoke|@regression/ : undefined;
if (activeGrep) console.log('[config] grep active:', activeGrep);
export default defineConfig({
grep: activeGrep,
});
Tổng Kết
- Từ v1.42+,
--grepmatch tag field được Playwright nối vào fullTitle — không cần nhúng @smoke vào title string. - Regex OR (
@smoke|@critical) và char-class (@p[0-2]) là hai pattern phổ biến nhất khi filter nhiều tag. - Tag inheritance: describe có tag → mọi test con kế thừa, --grep "@auth" bắt cả block describe.
- Config
grepnhận RegExp (hỗ trợ flag/i); CLI flag override config, không merge. - AND logic: dùng
--grep-invertcho AND NOT, dùng lookahead(?=.*@X)(?=.*@Y)cho AND thuần. - Test bị grep filter không xuất hiện trong report — khác với
test.skip()có ghi "skipped". - 5 pitfall chính: quên @, typo silent, shell pipe expand, multiple --grep không stack, config grep sót.
Quiz
Câu 1. Playwright v1.42 thay đổi gì trong cơ chế --grep liên quan đến tag field?
Đáp án
Trước v1.42, --grep chỉ match trên title string của test và describe. Từ v1.42, Playwright nối tag field ({ tag: '@smoke' }) vào fullTitle trước khi test regex — nên --grep '@smoke' match cả khi title không chứa literal @smoke.
Câu 2. Viết lệnh CLI chạy test có tag @p0 hoặc @p1, loại trừ test có @flaky, chỉ trên project chromium.
Đáp án
npx playwright test --grep '@p[01]' --grep-invert '@flaky' --project=chromium
Char-class [01] match cả @p0 và @p1. --grep-invert '@flaky' loại trừ test flaky sau khi đã filter bằng --grep.
Câu 3. Test sau có fullTitle là gì khi Playwright build để test grep?
test.describe('Payment', { tag: '@payment' }, () => {
test.describe('Stripe', { tag: '@stripe' }, () => {
test('charge card', { tag: '@smoke' }, async ({ page }) => { /* ... */ });
});
});
Đáp án
"Payment > Stripe > charge card @payment @stripe @smoke" — Playwright merge tag từ tất cả describe ancestor vào fullTitle. Thứ tự tag trong fullTitle theo thứ tự describe từ ngoài vào trong, sau cùng là tag của test.
Câu 4. Trong playwright.config.ts đang có grep: /@smoke/. Dev chạy npx playwright test --grep '@critical'. Test nào được chạy?
Đáp án
Chỉ test có tag @critical. CLI flag --grep '@critical' override hoàn toàn config grep: /@smoke/ — không merge, không OR. Nếu muốn chạy cả smoke lẫn critical, phải dùng --grep '@smoke|@critical'.
Câu 5. Liệt kê sự khác biệt giữa test bị grep filter và test có test.skip() trong HTML report.
Đáp án
Test bị grep filter không xuất hiện trong HTML report — chúng không được collect vào run. Test có test.skip() xuất hiện trong report với trạng thái "skipped" và có thể xem lý do skip. Đây là điểm quan trọng khi audit test coverage: grep filter ẩn hoàn toàn, skip hiển thị rõ ràng.
