QA How-To
Playwright tag and grep filters: Examples and Best Practices
Use Playwright tag and grep filters examples for smoke, feature, risk, CI, production-safe, AND, OR, and exclusion workflows with TypeScript at scale.
24 min read | 2,546 words
TL;DR
The most useful Playwright tag and grep filters examples combine structured tags with small, readable regular expressions. Use alternation for OR, positive lookaheads for AND, grepInvert for exclusions, and named projects for recurring CI lanes.
Key Takeaways
- Treat each tag as one selection dimension so filters can compose without ambiguous synonyms.
- Use structured tag arrays for tests that belong to scope, capability, and risk categories at once.
- Prefer grep plus grepInvert for readable include and exclude policies.
- Model recurring filtered lanes as Playwright projects and keep ad hoc focus on the CLI.
- Use a positive @production-safe allow list for live-environment checks.
- Validate cookbook patterns with known inclusions, known exclusions, and --list output.
- Track quarantine separately so removing a test from gating never removes accountability.
These Playwright tag and grep filters examples solve concrete suite-selection problems: pull-request smoke, one-capability regression, multi-tag risk gates, quarantine, production-safe checks, and project-based CI lanes. Each recipe uses supported Playwright Test syntax and includes a way to verify collection before execution.
Copying a command is only the first step. Adapt tag meanings, shell quoting, project names, test data, and safety controls to your repository. The goal is a selection policy that another SDET can understand from the command and confirm from the report.
TL;DR
| Recipe | Pattern | Use it when |
|---|---|---|
| One tag | --grep @smoke |
One classification defines scope |
| Either tag | `--grep '@smoke | @critical'` |
| Both tags | --grep '(?=.*@api)(?=.*@checkout)' |
Two independent dimensions are required |
| Include and exclude | --grep @regression --grep-invert @quarantine |
A broad lane has explicit exceptions |
| Named CI lane | Project grep and grepInvert |
The same policy runs repeatedly |
| Capability preview | Path plus tag plus --list |
A changed area needs focused feedback |
| Live target | @production-safe allow list |
Tests run against a protected environment |
The snippets use TypeScript, Bash-style quoting, and @-prefixed tags. On PowerShell or Windows command shells, keep the regex logic but adapt quoting according to that shell.
1. Start the Playwright Tag and Grep Filters Examples Suite
The recipes share a small sample application vocabulary. Tests are classified across three dimensions: execution scope, product capability, and risk or safety. Structured tags live in the details object, not in a custom wrapper.
// tests/catalog.spec.ts
import { test, expect } from '@playwright/test';
test('guest can search the catalog', {
tag: ['@smoke', '@search', '@production-safe'],
}, async ({ page }) => {
await page.goto('https://example.com');
await page.getByRole('searchbox').fill('keyboard');
await expect(page.getByRole('main')).toContainText(/keyboard/i);
});
test('member can save a product', {
tag: ['@regression', '@catalog', '@auth'],
}, async ({ page }) => {
await page.goto('https://example.com/products/1');
await page.getByRole('button', { name: 'Save product' }).click();
await expect(page.getByText('Saved')).toBeVisible();
});
The code compiles and runs with Playwright Test, but the selectors, authentication state, and routes must match the target application. The second test assumes authentication is configured through storage state or a fixture. It should not hide login inside the assertion flow.
Preview all collected tests before applying a recipe:
npx playwright test --list
The output shows project and test identity. That matters because grep evaluates more than explicit tags. Project names, paths, describe titles, and test titles also participate. In a production suite, choose distinctive @ tokens so capability selection is not accidentally satisfied by a file name or ordinary title word.
2. Example: Smoke, Sanity, and Regression Selection
A clean scope convention makes smoke a small release signal and regression a broader behavioral set. Decide whether smoke is also part of regression. The following example assigns both tags explicitly when the test belongs to both lanes, removing any need to infer hierarchy.
// tests/account.spec.ts
import { test, expect } from '@playwright/test';
test('member opens the account page', {
tag: ['@smoke', '@regression', '@account'],
}, async ({ page }) => {
await page.goto('https://example.com/account');
await expect(page.getByRole('heading', { name: 'My account' })).toBeVisible();
});
test('member changes communication preferences', {
tag: ['@regression', '@account'],
}, async ({ page }) => {
await page.goto('https://example.com/account/preferences');
await page.getByLabel('Product updates').uncheck();
await page.getByRole('button', { name: 'Save preferences' }).click();
await expect(page.getByRole('status')).toHaveText('Preferences saved');
});
Run each lane independently:
npx playwright test --grep @smoke
npx playwright test --grep @regression
If your organization uses sanity as an alternative label for a post-deployment subset, do not silently treat it as a synonym for smoke. Define the distinction and select either lane with alternation:
npx playwright test --grep '@smoke|@sanity'
An OR filter can execute a test only once even when its identity contains both alternatives. The regular expression decides whether the case matches, not how many times it should run. Projects create repeated executions, not multiple tag matches.
Best practice: tie scope tags to observable constraints. Define smoke by criticality, isolation, determinism, and target duration policy, without fabricating a universal time limit. Define regression by coverage purpose. A tag that only means someone wanted the test to run frequently will become inconsistent.
3. Example: Filter One Feature Across Several Files
Capability tags are valuable when a feature spans UI, API, accessibility, and integration folders. A path filter alone cannot select all checkout-related tests without depending on repository layout.
// tests/api/orders.spec.ts
import { test, expect } from '@playwright/test';
test('creates an order through the API', {
tag: ['@checkout', '@api', '@regression'],
}, async ({ request }) => {
const response = await request.post('https://example.com/api/orders', {
data: { sku: 'SKU-1', quantity: 1 },
});
expect(response.ok()).toBeTruthy();
});
// tests/ui/payment.spec.ts
test('shows a validation error for an empty card number', {
tag: ['@checkout', '@ui', '@regression'],
}, async ({ page }) => {
await page.goto('https://example.com/checkout');
await page.getByRole('button', { name: 'Pay now' }).click();
await expect(page.getByText('Card number is required')).toBeVisible();
});
Select the entire feature:
npx playwright test --grep @checkout --list
npx playwright test --grep @checkout
Select only its API cases by requiring both tags:
npx playwright test --grep '(?=.*@checkout)(?=.*@api)'
This pattern scales better than tags such as @checkout-api-regression because independent labels compose into many useful queries. Combined labels multiply vocabulary and make renaming harder.
Keep capability names stable and aligned with product language. Do not tag every low-level component unless CI or reporting genuinely consumes that classification. For one-off developer focus, Playwright can filter files and titles without permanent metadata. Review Playwright APIRequestContext examples if the API cases need shared authentication and cleanup patterns.
4. Example: Use OR and AND Without Regex Surprises
OR uses a pipe. AND requires lookaheads because a normal regular expression consumes text in an order, while tags may appear in a different order after group inheritance and declaration.
# Either capability
npx playwright test --grep '@checkout|@account'
# Both scope and capability
npx playwright test --grep '(?=.*@smoke)(?=.*@checkout)'
# Critical tests in either of two capabilities
npx playwright test --grep '(?=.*@critical)(?=.*@checkout|.*@account)'
The third expression has two requirements. The first lookahead requires @critical. The second accepts either capability. Keep expressions grouped and comment named package scripts so future maintainers do not have to reverse-engineer intent.
In TypeScript config, the same logic uses regular-expression literals:
import { defineConfig } from '@playwright/test';
export default defineConfig({
grep: [/@smoke/, /@sanity/],
});
The array above is OR. A test matching either pattern qualifies. It is not AND. Require both with one lookahead expression:
import { defineConfig } from '@playwright/test';
export default defineConfig({
grep: /(?=.*@smoke)(?=.*@checkout)/,
});
Best practice: test regex patterns against cases with tags in both orders, one missing tag, and an unrelated tag whose prefix looks similar. For example, decide whether @api should match @api-contract. If not, adopt delimiters or a naming convention that prevents prefix ambiguity. Simpler, distinctive tag names reduce the need for boundary-heavy expressions.
5. Example: Exclude Quarantine and Destructive Tests
A common regression lane includes the intended scope and excludes tests with a temporary or unsafe state. Express those concerns separately:
npx playwright test \
--grep @regression \
--grep-invert '@quarantine|@destructive'
The command is readable: include regression, remove quarantine or destructive cases. The same policy can live in a project:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'gating-regression',
use: { ...devices['Desktop Chrome'] },
grep: /@regression/,
grepInvert: /@quarantine|@destructive/,
},
{
name: 'quarantine-observation',
use: { ...devices['Desktop Chrome'] },
grep: /@quarantine/,
retries: 0,
},
],
});
Quarantine should change the release decision, not erase the test. Run quarantined cases in a visible non-gating job, retain diagnostics, and require a linked defect, owner, and exit condition. Zero retries in the observation lane can expose the raw failure pattern, although your organization may choose a different policy for diagnosis.
A destructive label identifies data or state impact, not necessarily bad quality. Such tests may be essential in an isolated staging tenant. Give them a dedicated environment and credentials rather than deleting them from regression everywhere. Never run an exclusion-only production command and assume untagged tests are safe. Production needs positive approval, covered in the next recipe.
6. Example: Create a Production-Safe Allow List
Live-environment checks require stronger controls than ordinary staging selection. Apply an explicit @production-safe tag only after reviewing read behavior, data creation, notifications, external integrations, and credential privilege.
// tests/production/health.spec.ts
import { test, expect } from '@playwright/test';
test('public status and catalog are reachable', {
tag: ['@production-safe', '@smoke', '@public'],
}, async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('main')).toBeVisible();
const response = await page.request.get('/api/catalog?limit=1');
expect(response.ok()).toBeTruthy();
});
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'production-safe',
use: {
...devices['Desktop Chrome'],
baseURL: process.env.PRODUCTION_URL,
trace: 'retain-on-failure',
},
grep: /@production-safe/,
grepInvert: /@destructive|@email-sending/,
retries: 0,
workers: 1,
},
],
});
The project combines a positive allow list with defensive exclusions. workers: 1 limits this profile intentionally, but it does not make unsafe tests safe. The code must still use nonmutating journeys or approved synthetic data. Validate PRODUCTION_URL in the pipeline so a missing variable cannot direct the job somewhere unintended.
Inspect scope before every policy change:
npx playwright test --project=production-safe --list
Treat edits that add @production-safe like changes to deployment authorization. Require reviewers who understand the test and target. Keep secrets out of traces, source code, and reports.
7. Example: Inherit Tags From Describe Groups
Group tags reduce repetition when a whole behavioral area shares classification. Individual tests can contribute scope or risk tags.
// tests/billing/invoices.spec.ts
import { test, expect } from '@playwright/test';
test.describe('invoice history', {
tag: ['@billing', '@authenticated'],
}, () => {
test('lists the latest invoice', { tag: '@smoke' }, async ({ page }) => {
await page.goto('https://example.com/account/invoices');
await expect(page.getByRole('heading', { name: 'Invoices' })).toBeVisible();
});
test('downloads a PDF invoice', {
tag: ['@regression', '@download'],
}, async ({ page }) => {
await page.goto('https://example.com/account/invoices');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: /download invoice/i }).first().click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toMatch(/pdf$/i);
});
});
The first test matches @billing AND @smoke. The second matches @billing AND @download. Use this command for the first class:
npx playwright test --grep '(?=.*@billing)(?=.*@smoke)'
Do not assume tags are inherited from directory names. Only details declared on test and describe definitions create structured tag metadata. Conversely, remember that moving a test under a tagged describe changes its selected lanes even if the test declaration itself is untouched.
Limit group tags to attributes that are true for every child. A describe named Billing should not automatically assign @smoke if only one journey meets the smoke contract. Place variable scope at the test level. Review nested groups carefully because several inherited tags can make a test eligible for a complex filter unexpectedly. Playwright download handling examples covers event ordering and file validation beyond this filtering recipe.
8. Example: Build Named PR, Nightly, and Release Projects
Projects are useful when a filter recurs and needs configuration beyond selection. The following matrix gives each lane an explicit browser profile, retry policy, and grep contract.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
const chrome = devices['Desktop Chrome'];
export default defineConfig({
testDir: './tests',
use: {
baseURL: process.env.BASE_URL ?? 'https://example.com',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'pr-smoke',
use: { ...chrome },
grep: /@smoke/,
grepInvert: /@quarantine/,
retries: 0,
},
{
name: 'nightly-regression',
use: { ...chrome },
grep: /@regression/,
grepInvert: /@quarantine/,
retries: 1,
},
{
name: 'release-critical',
use: { ...chrome },
grep: /@critical/,
grepInvert: /@quarantine|@destructive/,
retries: 0,
},
],
});
Run one lane by project name:
npx playwright test --project=pr-smoke
npx playwright test --project=nightly-regression
npx playwright test --project=release-critical --list
One test may match more than one project and execute once per selected project. If the default npx playwright test runs all projects, the same smoke and regression test could run several times. CI should select intended project names, and local documentation should explain whether running all projects is meaningful.
Projects work well when the lane also changes browser, base URL, trace, or retry policy. For a developer's one-time focus on @billing, a CLI filter is lighter. For deeper matrix choices, see Playwright projects config examples.
9. Example: Package Scripts and CI Commands
Package scripts give humans stable command names while keeping Playwright arguments reviewable. Use double quotes required by JSON and escape inner quotes when an expression contains regex punctuation.
{
"scripts": {
"pw:list:smoke": "playwright test --grep @smoke --list",
"pw:test:smoke": "playwright test --project=pr-smoke",
"pw:test:checkout": "playwright test --grep @checkout --grep-invert @quarantine",
"pw:test:critical-api": "playwright test --grep '(?=.*@critical)(?=.*@api)'"
}
}
A CI job can call the named script and upload the report regardless of pass or failure. Keep YAML logic limited to scheduling and environment selection. Put complicated regular expressions in package scripts or config so quoting is tested in one place.
For changed-feature pipelines, map owned paths to stable capability tags in a reviewed script. Do not build a regex directly from untrusted branch names or commit text. If no mapping is found, fall back to a safe baseline such as the smoke lane rather than producing an empty success.
Sharding comes after selection. For example, npx playwright test --project=nightly-regression --shard=2/4 selects the project and then executes the second shard of its cases. Ensure all shard jobs use identical config, grep policy, and environment. Merge supported reports so the nightly result represents the complete selected set. Review GitHub Actions for Playwright for installation and artifact structure.
10. Example: Audit Selection With a Tag Matrix
A small matrix makes expected selection reviewable. This is not a replacement for executable tests, but it is a powerful design artifact before adding tags to hundreds of cases.
| Test | Tags | PR smoke | Nightly regression | Production safe |
|---|---|---|---|---|
| Public catalog search | @smoke @regression @search @production-safe |
Yes | Yes | Yes |
| Expired coupon validation | @regression @checkout |
No | Yes | No |
| Submit payment | @regression @checkout @destructive |
No | Yes if staging allows | No |
| Flaky invoice download | @regression @billing @quarantine |
No | Excluded from gate | No |
| Health endpoint | @smoke @api @production-safe |
Yes | Only if also regression | Yes |
Turn each column into a command and compare it with --list. Sample the boundaries, not only happy matches:
npx playwright test --project=pr-smoke --list
npx playwright test --project=nightly-regression --list
npx playwright test --project=production-safe --list
Ask four questions during review. Which rows must appear? Which must not appear? Which tests have no scope tag? Which tests match several projects? The last two often uncover invisible gaps and unexpected cost.
For a mature suite, a custom reporter or static analysis can inventory TestCase.tags and compare them with an allowed vocabulary. Keep enforcement pragmatic. Some helper or setup tests may not need product capability labels. Fail only on conventions that protect a real decision, such as approval for a production target or ownership of quarantine.
11. Apply Playwright Tag and Grep Filters Examples as Best Practices
Use these adoption rules when converting recipes into repository standards:
- Begin with two or three valuable dimensions, not a complete taxonomy invented upfront.
- Define each tag in operational terms and show one qualifying and nonqualifying example.
- Keep tags lowercase and regex-friendly.
- Prefer structured test details over parsing title suffixes.
- Keep recurring policy in a named project or script.
- Use positive approval for sensitive environments.
- Preview the exact filtered list in the same shell and config used by CI.
- Make excluded tests visible in a separate report.
- Review overlaps when a test gains a new tag.
- Delete labels that no longer influence selection or reporting.
Do not optimize for the shortest command. Optimize for a selection that is obvious in code review and defensible after a missed defect. grep and grepInvert together are often clearer than one dense negative lookahead. A project name is often clearer than a copied regex in several workflows.
Finally, tag changes deserve test evidence. Include before and after lists for important lanes in a pull request description or generated artifact. That turns an abstract metadata edit into a visible coverage change.
Interview Questions and Answers
Q: Give an example of selecting either of two Playwright tags.
I use regex alternation: npx playwright test --grep '@smoke|@sanity'. The quotes protect the pipe from the shell. A test matching both still runs once per project execution.
Q: Give an example of selecting both a feature and test type.
For checkout API cases, I use --grep '(?=.*@checkout)(?=.*@api)'. The lookaheads make order irrelevant. I verify the expression against cases with both tags and cases missing either one.
Q: How would you configure a non-gating quarantine lane?
The normal project uses grepInvert: /@quarantine/, while a separate project uses grep: /@quarantine/. The observation job always publishes results but does not block the primary gate. Every quarantined test has ownership and a removal condition.
Q: How do group tags affect a nested test?
Tags declared in a test.describe details object apply to tests in that group. A nested test may add its own labels, so its searchable identity contains both. Moving a test between groups can therefore change filtering even when its declaration does not change.
Q: Why use a project for a filtered lane?
A project gives the lane a stable name and binds selection to browser, environment, retries, artifacts, or worker policy. That is ideal for recurring CI behavior. I keep ad hoc feature focus on the CLI to avoid unnecessary projects.
Q: What is wrong with selecting production tests by excluding @destructive?
An unsafe test without that label still qualifies. I require a positively reviewed @production-safe tag, then add exclusions as defense in depth. The target, credentials, data, and external side effects also need separate controls.
Q: How do you audit a complex tag filter?
I create known positive and negative examples, run the exact command with --list, and compare the resolved project and tests with a small matrix. I also check config-level filters and shell quoting. For critical lanes, CI validates sentinel coverage.
Q: Are several grep patterns in a config array AND or OR?
They operate as alternatives, so matching any pattern qualifies. For AND semantics I use one regular expression with positive lookaheads. I document the intent because this distinction is a common source of accidental breadth.
Common Mistakes
- Copying Bash quoting into another shell without testing it.
- Creating compound tags such as
@checkout-api-smokeinstead of composable dimensions. - Assuming smoke automatically belongs to regression without encoding that policy.
- Putting
@smokeon a describe block when only some nested tests qualify. - Treating a configured regex array as AND logic.
- Hiding quarantine by excluding it from every scheduled report.
- Allowing all projects to run when overlapping filtered lanes were intended as alternatives.
- Using exact test-count guards that break on every legitimate suite edit.
- Building regex input from untrusted CI text.
- Tagging live-environment tests without reviewing side effects and credentials.
- Failing to compare the selected list before and after taxonomy changes.
Conclusion
The best Playwright tag and grep filters examples remain small, composable, and observable. Use structured tags for independent dimensions, alternation for either condition, lookaheads for both conditions, grepInvert for clear exceptions, and projects for recurring lanes.
Choose one recipe that matches an existing workflow, define its qualifying rules, and apply it to a representative set of tests. Validate inclusions, exclusions, overlaps, and reports before expanding the convention across the suite.
Interview Questions and Answers
Show a Playwright smoke and regression tagging strategy.
I give tests explicit scope tags based on documented criteria. A critical smoke case may carry both `@smoke` and `@regression`, while a deeper case carries only `@regression`. Separate named projects select each lane and exclude quarantine, making overlap visible.
How would you select critical tests in checkout or account?
I use `(?=.*@critical)(?=.*@checkout|.*@account)`. One lookahead requires the risk tag and the other accepts either capability. I place the expression in reviewed config or a package script and validate it with a list.
What is your preferred quarantine implementation?
Gating projects invert `@quarantine`, and a scheduled observation project positively selects it. Results remain visible with traces and ownership metadata. The tag is temporary and cannot replace root-cause analysis.
When are capability tags better than path filtering?
They are better when one capability spans UI, API, accessibility, or integration directories. A stable `@checkout` tag follows product meaning while files can move. I avoid duplicating path information when no cross-cutting workflow consumes the tag.
How would you prevent accidental live-environment execution?
I use a dedicated project with a validated production URL, least-privilege credentials, one worker if appropriate, and a positive `@production-safe` allow list. Defensive exclusions add protection, but code and data review establish actual safety.
Why can a filtered test execute several times?
Tag matching only decides eligibility. If several selected Playwright projects match that test, each project creates a separate execution. I select explicit project names in CI and preserve project identity in reports.
How do you validate a tag migration?
I update declarations, config, scripts, and documentation together. Then I capture old and new `--list` output for every important lane and compare inclusions, exclusions, and project overlap. Temporary aliases are removed on a defined schedule.
What is a maintainable limit for regex complexity?
There is no numeric universal limit, but a filter should communicate its business rule at a glance. I split inclusion and exclusion, use named projects, and avoid compound negative expressions. If maintainers need to decode it repeatedly, the taxonomy or lane needs redesign.
Frequently Asked Questions
What is a basic Playwright tag and grep example?
Declare `test('name', { tag: '@smoke' }, async ({ page }) => {})`, then run `npx playwright test --grep @smoke`. Use `--list` to preview the cases before execution.
How do I run Playwright tests with either of two tags?
Use regular-expression alternation, for example `npx playwright test --grep '@smoke|@critical'`. Quote the expression so the shell does not interpret the pipe.
How do I run Playwright tests that contain two required tags?
Use positive lookaheads, for example `--grep '(?=.*@checkout)(?=.*@api)'`. This requires both tokens regardless of their order in the full test identity.
What is a good Playwright quarantine filter example?
Use `--grep @regression --grep-invert @quarantine` for the gate, then run `--grep @quarantine` in a visible non-gating job. Track ownership and removal criteria for every quarantined test.
Can Playwright projects have different grep filters?
Yes. Each project can define its own `grep` and `grepInvert` alongside browser and runtime settings. This works well for named PR, nightly, release, and production-safe lanes.
How should I tag production-safe Playwright tests?
Apply an explicit `@production-safe` tag only after reviewing side effects, data, permissions, and external integrations. Select that allow list in a dedicated project and add defensive exclusions for known unsafe categories.
Do tags on test.describe apply to nested tests?
Yes. Tests inside the group inherit its tags and may add individual tags. This makes group classification concise, but moving or retagging the group changes every nested test's selection.
How can I verify a Playwright grep recipe?
Run the exact command with `--list`, including its config, project, path, grep, and invert options. Check known positive and negative cases, not just the total count.
Related Guides
- Playwright drag and drop: Examples and Best Practices
- Playwright mock date and time: Examples and Best Practices
- Playwright popup and new tab handling: Examples and Best Practices
- Playwright APIRequestContext: Examples and Best Practices
- Playwright aria snapshot: Examples and Best Practices
- Playwright clock API: Examples and Best Practices