Danh sách bài viết

Bài 49: CLI --grep @smoke — Filter Theo Tag

Series 1 bài 67 đã cover --grep cơ bản (match by title, cú pháp regex). Bài này đặt trọng tâm khác: dùng --grep để filter theo tag field (v1.42+), regex OR / char-class cho nhiều tag, tag inheritance từ describe kết hợp với grep, cấu hình grep trong playwright.config.ts, AND logic workaround qua lookahead và --grep-invert, tích hợp CI pipeline, 5 pitfall thường gặp khi filter tag, và quiz 5 câu.

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

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 / grepInvert trong playwright.config.ts và biết khi nào CLI override config.
  • Mô phỏng AND logic bằng --grep-invert hoặ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.
2

--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ồnVí 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.

3

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.

4

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ĩaMatch
@smokeSubstringTest có tag @smoke
@smoke|@criticalORTest có tag @smoke hoặc @critical
@p[0-2]Char-classTest có tag @p0, @p1, hoặc @p2
@team-\w+Word charsTest có tag bắt đầu bằng @team-
@v1\.0Literal dotTest có tag @v1.0 (không phải @v1x0)
5

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.

6

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 RegExp literal — 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 grep có 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.

7

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

8

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áchBiểu thứcĐọc đượcGiới hạn
--grep-invert--grep '@smoke' --grep-invert '@slow'TốtChỉ NOT, không AND thuần
Lookahead--grep '(?=.*@smoke)(?=.*@p1)'Khó đọc hơnKhô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.

9

Kết Hợp Với --project

--grep--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
10

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

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

PatternTest matchThờ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 suite200 test~20 phút
12

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
13

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,
});
14

Tổng Kết

  • Từ v1.42+, --grep match 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 grep nhận RegExp (hỗ trợ flag /i); CLI flag override config, không merge.
  • AND logic: dùng --grep-invert cho 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.
15

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.