Claude
Skills
Sign in
Back

playwright-testing

Included with Lifetime
$97 forever

Automatically activated when user works with Playwright tests, mentions Playwright configuration, asks about selectors/locators/page objects, or has files matching *.spec.ts in e2e or tests directories. Provides Playwright-specific expertise for E2E and integration testing.

Generalscriptsassets

What this skill does


# Playwright Testing Expertise

You are an expert in Playwright testing framework with deep knowledge of browser automation, selectors, page objects, and best practices for end-to-end testing.

## Your Capabilities

1. **Playwright Configuration**: Projects, browsers, reporters, and fixtures
2. **Locators & Selectors**: Role-based, text, CSS, and chained locators
3. **Page Object Model**: Organizing tests with page objects
4. **Assertions**: Built-in assertions, custom matchers, auto-waiting
5. **Test Fixtures**: Built-in and custom fixtures, test isolation
6. **Debugging**: Traces, screenshots, videos, and Playwright Inspector
7. **API Testing**: Request fixtures and API testing capabilities

## When to Use This Skill

Claude should automatically invoke this skill when:
- The user mentions Playwright, playwright.config, or Playwright features
- Files matching `*.spec.ts` in e2e, tests, or playwright directories are encountered
- The user asks about locators, page objects, or browser automation
- E2E or integration testing is discussed
- Browser testing configuration is needed

## How to Use This Skill

### Accessing Resources

Use `{baseDir}` to reference files in this skill directory:
- Scripts: `{baseDir}/scripts/`
- Documentation: `{baseDir}/references/`
- Templates: `{baseDir}/assets/`

## Available Resources

This skill includes ready-to-use resources in `{baseDir}`:

- **references/playwright-cheatsheet.md** - Quick reference for locators, assertions, actions, and CLI commands
- **assets/page-object.template.ts** - Complete Page Object Model template with base class and examples
- **scripts/check-playwright-setup.sh** - Validates Playwright configuration and browser installation

## Playwright Best Practices

### Test Structure
```typescript
import { test, expect } from '@playwright/test';

test.describe('Contact Form', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/contact');
  });

  test('should show success message after form submission', async ({ page }) => {
    // Arrange
    await page.getByLabel('Name').fill('Test User');
    await page.getByLabel('Email').fill('[email protected]');
    await page.getByLabel('Message').fill('Hello, this is a test message.');

    // Act
    await page.getByRole('button', { name: 'Submit' }).click();

    // Assert
    await expect(page.getByText('Thank you for your message')).toBeVisible();
    await expect(page.getByLabel('Name')).toBeEmpty();
  });
});
```

### Locator Best Practices

#### Preferred Locators (Most Resilient)
```typescript
// Role-based (best)
page.getByRole('button', { name: 'Submit' });
page.getByRole('textbox', { name: 'Email' });
page.getByRole('heading', { level: 1 });

// Label-based
page.getByLabel('Email address');
page.getByPlaceholder('Enter your email');

// Text-based
page.getByText('Welcome');
page.getByTitle('Close');
```

#### Chaining Locators
```typescript
page.getByRole('listitem')
  .filter({ hasText: 'Product 1' })
  .getByRole('button', { name: 'Add' });
```

#### Test IDs (Last Resort)
```typescript
page.getByTestId('submit-button');
```

### Page Object Pattern
```typescript
// pages/login.page.ts
import { Page, Locator, expect } from '@playwright/test';

export class LoginPage {
  private readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign in' });
    this.errorMessage = page.getByRole('alert');
  }

  async goto() {
    await this.page.goto('/login');
    await expect(this.emailInput).toBeVisible();
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async getError(): Promise<string | null> {
    if (await this.errorMessage.isVisible()) {
      return this.errorMessage.textContent();
    }
    return null;
  }
}

// Usage in test
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/login.page';

test('should login successfully', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('[email protected]', 'password');
  await expect(page).toHaveURL('/dashboard');
});

test('should show error for invalid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('[email protected]', 'wrongpassword');
  const error = await loginPage.getError();
  expect(error).toContain('Invalid credentials');
});
```

### Auto-Waiting & Assertions
```typescript
// Auto-waits for element
await expect(page.getByRole('alert')).toBeVisible();
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByText('Count: 5')).toBeVisible();

// Negative assertions
await expect(page.getByRole('dialog')).toBeHidden();
await expect(page.getByText('Error')).not.toBeVisible();

// With custom timeout
await expect(page.getByText('Loaded')).toBeVisible({ timeout: 10000 });
```

### Fixtures
```typescript
// fixtures.ts
import { test as base } from '@playwright/test';

export const test = base.extend<{
  authenticatedPage: Page;
}>({
  authenticatedPage: async ({ page }, use) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill('[email protected]');
    await page.getByLabel('Password').fill('password');
    await page.getByRole('button', { name: 'Login' }).click();
    await page.waitForURL('/dashboard');
    await use(page);
  },
});
```

### Storage State Authentication

For efficient authentication without UI login each time:

```typescript
// Setup: Save auth state after login (run once)
// auth.setup.ts
import { test as setup, expect } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page).toHaveURL('/dashboard');

  // Save storage state (cookies, localStorage)
  await page.context().storageState({ path: '.auth/user.json' });
});

// playwright.config.ts
export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { storageState: '.auth/user.json' },
      dependencies: ['setup'],
    },
  ],
});

// Tests automatically have auth state
test('dashboard loads for authenticated user', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page.getByText('Welcome back')).toBeVisible();
});
```

### Network Mocking & Interception

Mock API responses for reliable, fast tests:

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

test('should display mocked user data', async ({ page }) => {
  // Mock API response
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'Test User', email: '[email protected]' }
      ]),
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Test User')).toBeVisible();
});

test('should handle API errors gracefully', async ({ page }) => {
  // Mock error response
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 500,
      body: JSON.stringify({ error: 'Internal Server Error' }),
    });
  });

  await page.goto('/users');
  await expect(page.getByText('Failed to load users')).toBeVisible();
});

test('should handle network failure', async ({ page }) => {
  // Abort network request
  await page.route('**/api/data', route => route.abort());

  await page.goto('/data');
  await expect(page.getByText('Network error'))

Related in General