npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@playwright-labs/decorators

v1.0.1

Published

decorators support for Playwright for your OOP style testing

Readme

@playwright-labs/decorators

TypeScript decorators for Playwright Test - Write elegant, object-oriented tests with class-based syntax

npm version License: MIT

Table of Contents

Quick Start

import { describe, test, beforeEach, afterEach } from '@playwright-labs/decorators';

@describe('Login Tests')
class LoginTests {
  @beforeEach()
  async setup() {
    await this.page.goto('https://example.com/login');
  }

  @test('should login successfully')
  async testLogin() {
    await this.page.fill('#username', '[email protected]');
    await this.page.fill('#password', 'password123');
    await this.page.click('#login-button');
    await expect(this.page).toHaveURL('/dashboard');
  }

  @afterEach()
  async cleanup() {
    await this.page.context().clearCookies();
  }
}

Features

Class-Based Tests - Organize tests using OOP principles
🎯 Lifecycle Decorators - @beforeAll, @beforeEach, @afterEach, @afterAll
⏱️ Timeout Control - @timeout() for classes, methods, and fixtures
🔧 Test-Specific Hooks - @before() and @after() for individual tests
📝 Rich Annotations - @tag(), @skip(), @fixme(), @slow(), @annotate()
🎭 Page Object Support - Perfect for Page Object Model pattern
📊 Data-Driven Tests - @test.each() with template strings
🔌 Fixture Support - @use() and @use.define() for custom fixtures
📎 Attachments - @attachment() for test artifacts
🔍 Full Type Safety - Complete TypeScript support
CI/CD Ready - Comprehensive runtime validations

Installation

npm install @playwright-labs/decorators @playwright/test

Basic Usage

Simple Test Class

import { describe, test } from '@playwright-labs/decorators';

@describe('Calculator Tests')
class CalculatorTests {
  @test('should add numbers')
  async testAddition() {
    const result = 2 + 2;
    expect(result).toBe(4);
  }

  @test('should multiply numbers')
  async testMultiplication() {
    const result = 3 * 4;
    expect(result).toBe(12);
  }
}

With Lifecycle Hooks

import { describe, test, beforeAll, afterAll, beforeEach, afterEach } from '@playwright-labs/decorators';

@describe('API Tests')
class ApiTests {
  static apiUrl: string;

  @beforeAll()
  static async setupAll() {
    this.apiUrl = 'https://api.example.com';
  }

  @beforeEach()
  async setupEach() {
    console.log('Running test...');
  }

  @test('should fetch users')
  async testFetchUsers() {
    // Test implementation
  }

  @afterEach()
  async cleanupEach() {
    console.log('Test completed');
  }

  @afterAll()
  static async cleanupAll() {
    // Global cleanup
  }
}

Core Decorators

Test Decorators

| Decorator | Description | Example | |-----------|-------------|---------| | @describe() | Define test suite | @describe('User Tests') | | @test() | Define test case | @test('should login') | | @test.each() | Data-driven tests | @test.each([...], 'test $1') | | @skip() | Skip test | @skip() | | @fixme() | Mark test as broken | @fixme() | | @slow() | Mark test as slow | @slow() |

Lifecycle Decorators

| Decorator | Scope | Runs | Example | |-----------|-------|------|---------| | @beforeAll() | Suite | Once before all tests | Setup database | | @beforeEach() | Test | Before each test | Reset state | | @before() | Test | Before specific test | Test-specific setup | | @after() | Test | After specific test | Test-specific cleanup | | @afterEach() | Test | After each test | Clear cookies | | @afterAll() | Suite | Once after all tests | Close connections |

Utility Decorators

| Decorator | Description | Example | |-----------|-------------|---------| | @timeout() | Set timeout | @timeout(5000) | | @tag() | Add test tags | @tag('smoke', 'critical') | | @annotate() | Add annotations | @annotate('type', 'description') | | @attachment() | Add attachments | @attachment('screenshot.png', ...) | | @use() | Use fixtures | @use('page', 'context') |

Lifecycle Hooks

Complete Lifecycle Order

@describe
  ↓
@beforeAll (static, once per suite)
  ↓
┌─ FOR EACH TEST ────────────┐
│ @beforeEach (per test)     │
│   ↓                         │
│ @before (test-specific)    │
│   ↓                         │
│ @test method               │
│   ↓                         │
│ @after (test-specific)     │ ← Always runs, even on failure
│   ↓                         │
│ @afterEach (per test)      │
└─────────────────────────────┘
  ↓
@afterAll (static, once per suite)

Example with All Hooks

@describe('Full Lifecycle Example')
@timeout(30000)
class FullLifecycleTests {
  @beforeAll()
  static async setupSuite() {
    // Runs once before all tests
  }

  @beforeEach()
  async setupTest() {
    // Runs before each test
  }

  @test('with individual hooks')
  @timeout(5000)
  @before(async (self) => {
    // Runs before this specific test
    self.testData = await loadTestData();
  })
  @after(async (self) => {
    // Runs after this specific test (even if test fails)
    await self.cleanup();
  })
  async myTest() {
    // Test implementation
  }

  @afterEach()
  async cleanupTest() {
    // Runs after each test
  }

  @afterAll()
  static async cleanupSuite() {
    // Runs once after all tests
  }
}

Documentation

📚 Full Documentation

🔧 Additional Resources

Examples

Data-Driven Tests

@describe('Login - Data Driven')
class LoginDataDrivenTests {
  @test.each([
    ['[email protected]', 'password123'],
    ['[email protected]', 'admin456'],
    ['[email protected]', 'test789'],
  ], 'should login with $1')
  async testLogin(email: string, password: string) {
    await this.page.fill('#email', email);
    await this.page.fill('#password', password);
    await this.page.click('#login');
    await expect(this.page).toHaveURL('/dashboard');
  }
}

Page Object Model

class LoginPage {
  constructor(private page: Page) {}
  
  async login(email: string, password: string) {
    await this.page.fill('#email', email);
    await this.page.fill('#password', password);
    await this.page.click('#login');
  }
}

@describe('Login with Page Objects')
class LoginPOMTests {
  private loginPage: LoginPage;

  @beforeEach()
  async setup() {
    this.loginPage = new LoginPage(this.page);
    await this.page.goto('/login');
  }

  @test('should login successfully')
  async testLogin() {
    await this.loginPage.login('[email protected]', 'password123');
    await expect(this.page).toHaveURL('/dashboard');
  }
}

Timeout Configuration

@describe('Performance Tests')
@timeout(60000) // 60 seconds for all tests
class PerformanceTests {
  @test('fast test')
  @timeout(5000) // Override: 5 seconds
  async testFast() {
    // Quick test
  }

  @test('slow test')
  @timeout(120000) // Override: 2 minutes
  async testSlow() {
    // Long-running test
  }
}

Resource Cleanup

@describe('Database Tests')
class DatabaseTests {
  @test('should handle transaction')
  @before(async (self) => {
    self.transaction = await db.beginTransaction();
  })
  @after(async (self) => {
    // Always runs, even if test fails
    if (self.transaction.isActive()) {
      await self.transaction.rollback();
    }
  })
  async testTransaction() {
    await this.transaction.insert({ name: 'test' });
    // Test logic
  }
}

Tags and Annotations

@describe('E2E Tests')
@tag('e2e', 'critical')
class E2ETests {
  @test('checkout flow')
  @tag('smoke')
  @annotate('type', 'user-journey')
  @timeout(120000)
  async testCheckout() {
    // Complete checkout flow
  }

  @test('slow operation')
  @slow()
  @annotate('performance', 'requires-optimization')
  async testSlowOperation() {
    // Slow test
  }
}

TypeScript Configuration

Add to your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": false,
    "target": "ES2022",
    "lib": ["ES2022"]
  }
}

Note: This library uses TC39 Stage 3 decorators (NOT legacy experimentalDecorators).

Why Use Decorators?

Before (Traditional Playwright)

test.describe('Login Tests', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('should login', async ({ page }) => {
    await page.fill('#email', '[email protected]');
    await page.click('#login');
    await expect(page).toHaveURL('/dashboard');
  });
});

After (With Decorators)

@describe('Login Tests')
class LoginTests {
  @beforeEach()
  async setup() {
    await this.page.goto('/login');
  }

  @test('should login')
  async testLogin() {
    await this.page.fill('#email', '[email protected]');
    await this.page.click('#login');
    await expect(this.page).toHaveURL('/dashboard');
  }
}

Benefits

  • Better Organization - Class-based structure groups related tests
  • Reusability - Share setup/teardown logic via class inheritance
  • Type Safety - Full TypeScript support with proper this context
  • Page Object Pattern - Natural fit for POM architecture
  • Less Boilerplate - Cleaner, more readable test code
  • Flexible Lifecycle - More granular control with @before/@after
  • IDE Support - Better autocomplete and refactoring

License

MIT

Contributing

Contributions are welcome! Please read our contributing guidelines first.

Support


Made with ❤️ by the Playwright community