Playwright annotations help QA teams control test execution, organize large test suites, manage unstable tests, and improve CI/CD execution. In real-world automation projects, annotations are heavily used for:
- Smoke testing
- Regression filtering
- Environment-specific execution
- Handling known bugs
- Managing flaky tests
- Faster debugging
- CI pipeline optimization
Playwright provides built-in annotations such as:
test.only()test.skip()test.fixme()test.fail()test.slow()- Tags like
@smoke,@regression,@sanity
It also supports custom annotations and runtime annotations using testInfo.annotations.
1. Setup a Real Playwright Automation Framework
Install Playwright
npm init playwright@latest
Project structure:
project-root
├── tests
│ ├── login.spec.ts
│ ├── checkout.spec.ts
│ └── dashboard.spec.ts
│
├── pages
├── utils
├── playwright.config.ts
└── package.json
2. Understanding Built-in Playwright Annotations
| Annotation | Purpose |
|---|---|
test.only() | Run only selected tests |
test.skip() | Skip test execution |
test.fixme() | Mark broken test and skip execution |
test.fail() | Expected failure |
test.slow() | Increase timeout |
| Tags | Categorize tests |
3. Using test.only() in Real Projects
Use during debugging.
Example
import { test, expect } from '@playwright/test';
test.only('Verify user login', async ({ page }) => {
await page.goto('https://example.com');
await page.fill('#username', 'admin');
await page.fill('#password', 'admin123');
await page.click('#loginBtn');
await expect(page).toHaveURL(/dashboard/);
});
Real Usage
Developers use test.only() when:
- Debugging failed tests
- Verifying a single feature
- Running one scenario quickly
Important
Never push test.only() to GitHub.
Otherwise only one test executes in CI.
4. Using test.skip()
Skip irrelevant tests.
Example
test.skip('Payment via PayPal', async ({ page }) => {
// Feature temporarily disabled
});
Conditional Skip
test('Safari unsupported feature', async ({ page, browserName }) => {
test.skip(browserName === 'webkit',
'Feature not supported in Safari');
});
5. Real Project Example — Browser Specific Execution
test('File Upload', async ({ page, browserName }) => {
test.skip(browserName === 'firefox',
'Upload issue in Firefox');
await page.goto('https://example.com/upload');
});
Why This Matters
In enterprise projects:
- Some features behave differently across browsers
- Teams temporarily skip unstable browsers
- CI becomes stable
6. Using test.fixme()
Use when:
- Test crashes
- Feature incomplete
- Automation blocked
Example
test.fixme('Search feature automation', async ({ page }) => {
// Application crashes during search
});
Difference Between skip() and fixme()
| Annotation | Runs Test? | Purpose |
|---|---|---|
skip() | No | Intentionally excluded |
fixme() | No | Known broken functionality |
7. Using test.fail()
Use when bug already exists and failure is expected.
Example
test.fail('Incorrect discount calculation', async ({ page }) => {
await page.goto('https://example.com/cart');
// Existing bug
});
Real Benefit
Your CI pipeline remains stable while still tracking known bugs.
If the test unexpectedly passes, Playwright reports failure because the bug may be fixed.
8. Using test.slow()
Useful for:
- API-heavy tests
- Report generation
- Database validation
- Large workflows
Example
test('Generate annual report', async ({ page }) => {
test.slow();
await page.goto('https://example.com/reports');
});
test.slow() triples the timeout automatically.
9. Real Enterprise Tagging Strategy
Tags are extremely important in large automation frameworks.
Example
test('User login test', {
tag: '@smoke',
}, async ({ page }) => {
});
Multiple Tags
test('Checkout flow', {
tag: ['@smoke', '@regression', '@critical'],
}, async ({ page }) => {
});
10. Organizing Tests Using test.describe()
Example
test.describe('Checkout Module', () => {
test('Add product to cart', async ({ page }) => {
});
test('Complete payment', async ({ page }) => {
});
});
11. Add Group Level Tags
test.describe('Smoke Suite', {
tag: '@smoke',
}, () => {
test('Login', async ({ page }) => {
});
test('Logout', async ({ page }) => {
});
});
12. Running Tests by Tags
Run Smoke Tests
npx playwright test --grep @smoke
Run Regression Tests
npx playwright test --grep @regression
Exclude Slow Tests
npx playwright test --grep-invert @slow
13. Real CI/CD Pipeline Usage
GitHub Actions Example
name: Playwright Tests
on:
push:
jobs:
smoke-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm install
- run: npx playwright install
- run: npx playwright test --grep @smoke
14. Real Automation Strategy Used by QA Teams
| Environment | Tags Executed |
|---|---|
| Dev | @sanity |
| QA | @smoke |
| Staging | @regression |
| Production Validation | @critical |
15. Using Custom Annotations
Playwright supports custom annotations.
Example
test('Payment validation', {
annotation: {
type: 'issue',
description: 'BUG-1023',
},
}, async ({ page }) => {
});
16. Runtime Annotations with testInfo
Example
import { test } from '@playwright/test';
test('Dynamic annotation example',
async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'jira',
description: 'JIRA-2025',
});
});
17. Best Real-World Annotation Structure
Example Enterprise Framework
tests
├── smoke
├── regression
├── sanity
├── api
└── ui
Example:
test('Verify login functionality', {
tag: ['@smoke', '@critical', '@ui'],
}, async ({ page }) => {
});
18. Recommended Tag Naming Convention
| Tag | Usage |
|---|---|
@smoke | Critical tests |
@sanity | Basic verification |
@regression | Full suite |
@critical | Business-critical flows |
@api | API tests |
@ui | UI tests |
@mobile | Mobile tests |
@slow | Time-consuming tests |
19. Common Mistakes to Avoid
Avoid test.only() in Git
Use ESLint or pre-commit hooks to block it.
Do Not Overuse skip()
Too many skipped tests reduce automation coverage.
Use Consistent Tags
Bad:
@Smoke
@smoke
@SmokeTest
Good:
@smoke
20. Advanced Real-World Example
Complete Example
import { test, expect } from '@playwright/test';
test.describe('E-Commerce Checkout', {
tag: '@regression',
}, () => {
test('Guest checkout', {
tag: ['@smoke', '@critical'],
annotation: {
type: 'story',
description: 'JIRA-450',
},
}, async ({ page, browserName }) => {
test.slow();
test.skip(browserName === 'firefox',
'Known issue in Firefox');
await page.goto('https://example.com');
await page.click('#checkout');
await expect(page).toHaveURL(/checkout/);
});
});
21. Recommended Annotation Strategy for Real Projects
Small Projects
Use:
@smoke@regression
Medium Projects
Use:
@smoke@sanity@regression@critical
Enterprise Projects
Use:
- Browser-specific annotations
- Environment-based skipping
- Jira-linked annotations
- CI-based tagging
- Runtime annotations
- Module-based grouping
22. Final Recommended Framework Workflow
Daily Execution
npx playwright test --grep @smoke
Nightly Regression
npx playwright test --grep @regression
Release Validation
npx playwright test --grep @critical
23. Best Practices Summary
Recommended
✔ Use tags for filtering
✔ Use fail() for known bugs
✔ Use slow() for long tests
✔ Use fixme() for broken scenarios
✔ Use custom annotations for Jira tracking
✔ Organize tests with describe()
✔ Maintain naming conventions
Avoid
✘ Permanent skipped tests
✘ Random tag naming
✘ Pushing test.only() to CI
✘ Too many annotations on one test