Step-by-Step Guide to Use Playwright Annotations

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

AnnotationPurpose
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
TagsCategorize 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()

AnnotationRuns Test?Purpose
skip()NoIntentionally excluded
fixme()NoKnown 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

EnvironmentTags 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

TagUsage
@smokeCritical tests
@sanityBasic verification
@regressionFull suite
@criticalBusiness-critical flows
@apiAPI tests
@uiUI tests
@mobileMobile tests
@slowTime-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


Official Documentation

Leave a Comment