Skill Library

intermediate Testing Quality

Playwright E2E Testing Expert

Create comprehensive end-to-end tests using Playwright for web applications. Write reliable, maintainable test suites with proper page objects, fixtures, and CI/CD integration.

When to Use This Skill

  • Building E2E test suites for web applications
  • Migrating from Cypress, Selenium, or other frameworks
  • Setting up CI/CD test automation
  • Creating visual regression tests
  • Testing complex user flows
  • Cross-browser compatibility testing

How to use this skill

1. Copy the AI Core Logic from the Instructions tab below.

2. Paste it into your AI's System Instructions or as your first message.

3. Provide your raw data or requirements as requested by the AI.

#playwright#testing#e2e#automation#web#ci-cd

System Directives

## Curation Note Playwright has become the dominant choice for E2E testing in 2025, surpassing Cypress and Selenium in developer satisfaction surveys. This skill draws from Anthropic's webapp-testing skill and community best practices. The emphasis on the Page Object Model and proper test isolation addresses the most common sources of flaky tests. Teams adopting these patterns report 70%+ reduction in test maintenance burden. ## Core Patterns ### Page Object Model ```typescript // pages/LoginPage.ts import { Page, Locator } from '@playwright/test'; export class LoginPage { 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'); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } async expectError(message: string) { await expect(this.errorMessage).toContainText(message); } } ``` ### Test Fixtures ```typescript // fixtures/auth.ts import { test as base } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; import { DashboardPage } from '../pages/DashboardPage'; type AuthFixtures = { loginPage: LoginPage; dashboardPage: DashboardPage; authenticatedPage: DashboardPage; }; export const test = base.extend<AuthFixtures>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await use(loginPage); }, dashboardPage: async ({ page }, use) => { const dashboardPage = new DashboardPage(page); await use(dashboardPage); }, authenticatedPage: async ({ page }, use) => { // Set auth state before test await page.goto('/login'); await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Password').fill('password123'); await page.getByRole('button', { name: 'Sign In' }).click(); await page.waitForURL('/dashboard'); const dashboardPage = new DashboardPage(page); await use(dashboardPage); } }); export { expect } from '@playwright/test'; ``` ### Writing Stable Tests ```typescript import { test, expect } from '../fixtures/auth'; test.describe('User Dashboard', () => { test('displays user profile information', async ({ authenticatedPage }) => { // Use semantic locators over CSS selectors await expect(authenticatedPage.profileName).toBeVisible(); await expect(authenticatedPage.profileEmail).toContainText('@'); // Wait for network idle before assertions await authenticatedPage.page.waitForLoadState('networkidle'); // Take screenshot for visual regression await expect(authenticatedPage.page).toHaveScreenshot('dashboard.png'); }); test('allows user to update settings', async ({ authenticatedPage }) => { await authenticatedPage.navigateToSettings(); // Use explicit waits over implicit await authenticatedPage.settingsForm.waitFor({ state: 'visible' }); await authenticatedPage.updateSetting('notifications', false); // Verify with API call, not just UI const response = await authenticatedPage.page.request.get('/api/settings'); expect(await response.json()).toHaveProperty('notifications', false); }); }); ``` ## Best Practices ### Locator Strategy Priority 1. `getByRole()` - Most accessible, most stable 2. `getByLabel()` - For form inputs 3. `getByText()` - For visible text content 4. `getByTestId()` - When semantic options unavailable 5. CSS selectors - Last resort ### Test Isolation ```typescript // Each test should be independent test.beforeEach(async ({ page }) => { // Reset state before each test await page.request.post('/api/test/reset'); }); // Use test.describe.serial() only when truly necessary test.describe.serial('Order Flow', () => { // Tests that MUST run in sequence }); ``` ### Parallel Execution ```typescript // playwright.config.ts export default defineConfig({ workers: process.env.CI ? 4 : undefined, fullyParallel: true, retries: process.env.CI ? 2 : 0, use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' } }); ``` ## CI/CD Integration ```yaml name: E2E Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - run: npx playwright install --with-deps - run: npm run test:e2e - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/ ``` ## Related Resources - [Playwright Documentation](https://playwright.dev/) - [Best Practices Guide](https://playwright.dev/docs/best-practices)

Procedural Integration

This skill is formatted as a set of persistent system instructions. When integrated, it provides the AI model with specialized workflows and knowledge constraints for Testing Quality.

Skill Actions


Model Compatibility
🤖 Claude Opus🤖 Gemini 2.5 Pro
Code Execution: Required
MCP Tools: Optional
Footprint ~1,407 tokens