Skip to content
ExponaBLOG
PLAYWRIGHT & BROWSER AUTOMATION · DAY 045 / 100

Tagging and filtering tests: what developers should know

Run the right tests at the right time.

Expona Admin8 min read

By day 45 of your automation journey, you've likely built a solid test suite. Now comes the challenge: running the right tests at the right time without waiting for hundreds of scenarios to execute. Tagging and filtering tests is what separates a maintainable test strategy from one that slows down your team. This guide covers practical patterns for organizing, filtering, and executing your Playwright tests efficiently.

Why test filtering matters in practice

Test suites grow quickly. A project that starts with 50 tests reaches 500 within months. Running everything before every commit becomes a bottleneck—developers stop waiting for results and push code without confirmation. Running nothing means bugs slip through. Tagging and filtering lets you run smoke tests in 2 minutes on every PR, then run the full suite overnight when you have time.

We've seen teams adopt filtering at different maturity levels. Early adopters report catching integration issues faster. Late adopters spend hours debugging in staging environments what would have been caught by a focused test run.

Tags in test titles or test.describe options

Playwright supports tagging through test metadata. You can apply tags at the test level or test suite level. The most common approach is using the @tagname convention in your test title or via the tag option in test.describe.

typescript
import { test, expect } from '@playwright/test';

// Tag via test title
test('@smoke User can log in', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.fill('[name="email"]', 'user@test.com');
  await page.fill('[name="password"]', 'password123');
  await page.click('button:has-text("Sign in")');
  await expect(page).toHaveURL('/dashboard');
});

// Tag via test.describe options
test.describe('@critical @checkout', () => {
  test('User can add item to cart', async ({ page }) => {
    await page.goto('https://example.com/products');
    await page.click('[data-testid="add-to-cart"]');
    await expect(page.locator('[data-testid="cart-count"]')).toContainText('1');
  });

  test('User can proceed to payment', async ({ page }) => {
    await page.goto('https://example.com/checkout');
    await page.fill('[name="card"]', '4111111111111111');
    await page.click('button:has-text("Pay")');
    await expect(page).toHaveURL('/confirmation');
  });
});

// Multiple tags on a single test
test('@slow @integration @payment Process refund', async ({ page }) => {
  // test implementation
});

Tags applied via test.describe cascade to all tests within that block. This is useful for grouping related functionality. Individual tests can still have additional tags. A single test can have multiple tags—separate them with spaces in the title or pass an array to the tag option in newer Playwright versions.

Choose a tagging convention early and enforce it. Common patterns include @smoke, @critical, @integration, @slow, @payment, @auth. Keep tag names lowercase and meaningful to your team. Avoid tags that change frequently or are too granular—you want to run filters, not maintain a taxonomy.

Using --grep to run specific tests

The --grep flag filters tests by matching against test names and tags. This is the workhorse of test filtering in Playwright.

typescript
// Command line examples:

// Run only smoke tests
npx playwright test --grep @smoke

// Run tests matching a regex pattern
npx playwright test --grep "login|authentication"

// Run tests containing "checkout" in the title
npx playwright test --grep "checkout"

// Combine with other flags
npx playwright test --grep @smoke --project chromium

The --grep flag accepts regular expressions, so you can build complex filters. For instance, --grep "(@smoke|@critical) && !@slow" would match tests tagged with smoke or critical that don't have the slow tag—though this specific pattern requires --grep-invert as shown below.

In your CI/CD pipeline, smoke tests run on every PR:

typescript
// .github/workflows/test.yml
name: Test
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm install
      - run: npx playwright install
      
      # Smoke tests on every PR
      - name: Run smoke tests
        if: github.event_name == 'pull_request'
        run: npx playwright test --grep @smoke
      
      # Full suite nightly
      - name: Run full suite
        if: github.event_name == 'schedule'
        run: npx playwright test

Developers can also run smoke tests locally before pushing code, reducing CI feedback loops. A 2-minute smoke run gives confidence that basic flows work before a longer full-suite run happens later.

Using --grep-invert to exclude tests

The --grep-invert flag does the opposite—it excludes tests matching the pattern. This is useful for skipping expensive or flaky tests during development.

typescript
// Run all tests except slow ones
npx playwright test --grep-invert @slow

// Run all tests except payment-related (useful during development)
npx playwright test --grep-invert @payment

// Run smoke + critical, but skip slow tests
npx playwright test --grep "@smoke|@critical" --grep-invert @slow

A common pattern is excluding slow tests during active feature development, then running them before merging to main. This keeps feedback loops tight while ensuring comprehensive coverage before integration.

typescript
// package.json scripts for common workflows
{
  "scripts": {
    "test": "playwright test",
    "test:smoke": "playwright test --grep @smoke",
    "test:critical": "playwright test --grep @critical",
    "test:fast": "playwright test --grep-invert @slow",
    "test:integration": "playwright test --grep @integration",
    "test:feature": "playwright test --grep-invert '@slow|@integration'"
  }
}

Add these to your project's package.json and share them with the team. Developers then run npm run test:smoke instead of memorizing filter syntax. This also makes CI configuration simpler—it references the same commands developers use locally.

Smoke on PR, full suite nightly

The industry standard workflow is: fast tests on PRs, comprehensive tests on a schedule. This balances speed and confidence.

PR workflow: Smoke tests run within 2–5 minutes. They verify login flows, core navigation, and critical functionality. Developers get feedback quickly and can iterate. If smoke passes, the PR is likely safe to merge from an automation perspective.

Nightly workflow: Full suite including slow integration and API tests runs 8+ hours. Results are reviewed the next morning. Failures trigger investigation—usually flakiness or a real issue that smoke missed.

typescript
// .github/workflows/smoke.yml
name: Smoke Tests
on: [pull_request]

jobs:
  smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm install && npx playwright install
      - run: npm run test:smoke
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: smoke-results
          path: playwright-report/
typescript
// .github/workflows/nightly.yml
name: Full Test Suite
on:
  schedule:
    - cron: '0 2 * * *'  # 2 AM UTC daily
  workflow_dispatch:  # Also allow manual trigger

jobs:
  full-suite:
    runs-on: ubuntu-latest
    timeout-minutes: 720  # 12 hours
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm install && npx playwright install
      - run: npm test
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: full-suite-results
          path: playwright-report/

This setup ensures:

  • Developers get feedback within minutes on PRs
  • Comprehensive validation happens when it's expected
  • Nightly runs can be longer and more thorough without blocking development
  • Manual trigger allows running the full suite on-demand if needed

Some teams add a third tier: @critical tests run on merge to main. These are fast but more comprehensive than smoke. This catches issues that slipped through PR smoke tests before they hit staging.

Common mistakes

Over-tagging: If every test has three tags, filters become useless. Tag strategically. Most tests shouldn't be tagged; only tag when you have a specific reason to run or exclude a group.

Neglecting tag maintenance: As code changes, tags drift. A test tagged @slow may no longer be slow after optimization. A test tagged @payment that was refactored to use a mock API should lose that tag. Review tags quarterly with your team.

Mixing test tier logic into assertions: Don't let test tags influence test behavior. A test should pass or fail the same way whether it runs in smoke or full suite. If you find yourself writing conditional logic based on tags, refactor instead.

Forgetting to document the tag strategy: New team members won't know what @e2e means or why @integration exists. Add a brief section to your testing docs explaining tag purposes and when each is used.

Running smoke tests serially when they could run in parallel: Playwright runs tests in parallel by default. Ensure your smoke tests are independent and don't share state. This keeps smoke execution under 5 minutes even as the number of smoke tests grows.

Key takeaways

  • Tag tests using @tagname in test titles or via the tag option in test.describe. Keep tags meaningful and few.
  • Use --grep @smoke to run tagged subsets. Add npm scripts to your package.json for common filters so developers don't memorize CLI syntax.
  • Use --grep-invert @slow to exclude tests, useful for staying in development flow while skipping expensive runs.
  • Structure CI/CD so smoke tests run on PRs (2–5 min feedback), full suite runs nightly (comprehensive validation). Add optional critical tests on main merge.
  • Keep tags synchronized with code reality. Slow tests may become fast; payment tests may become mocked. Update tags as your codebase evolves.
  • Enforce consistency early. Decide on tag naming, document it, and review it as new patterns emerge in your test suite.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles