Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Give agents more agency

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    thapaliyabikendra

    e2e-testing-patterns

    thapaliyabikendra/e2e-testing-patterns
    Coding
    4
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Master end-to-end testing with Playwright to build reliable test suites that catch bugs, improve confidence, and enable fast deployment...

    SKILL.md

    E2E Testing Patterns

    Build reliable, fast, and maintainable end-to-end test suites that provide confidence to ship code quickly and catch regressions before users do.

    When to Use This Skill

    • Implementing end-to-end test automation
    • Debugging flaky or unreliable tests
    • Testing critical user workflows
    • Setting up CI/CD test pipelines
    • Testing across multiple browsers
    • Validating accessibility requirements
    • Testing responsive designs
    • Establishing E2E testing standards

    Core Concepts

    1. E2E Testing Fundamentals

    What to Test with E2E:

    • Critical user journeys (login, checkout, signup)
    • Complex interactions (drag-and-drop, multi-step forms)
    • Cross-browser compatibility
    • Real API integration
    • Authentication flows

    What NOT to Test with E2E:

    • Unit-level logic (use unit tests)
    • API contracts (use integration tests)
    • Edge cases (too slow)
    • Internal implementation details

    2. Test Philosophy

    The Testing Pyramid:

            /\
           /E2E\         ← Few, focused on critical paths
          /─────\
         /Integr\        ← More, test component interactions
        /────────\
       /Unit Tests\      ← Many, fast, isolated
      /────────────\
    

    Best Practices:

    • Test user behavior, not implementation
    • Keep tests independent
    • Make tests deterministic
    • Optimize for speed
    • Use data-testid, not CSS selectors

    Playwright Patterns

    Setup and Configuration

    // playwright.config.ts
    import { defineConfig, devices } from '@playwright/test';
    
    export default defineConfig({
        testDir: './e2e',
        timeout: 30000,
        expect: {
            timeout: 5000,
        },
        fullyParallel: true,
        forbidOnly: !!process.env.CI,
        retries: process.env.CI ? 2 : 0,
        workers: process.env.CI ? 1 : undefined,
        reporter: [
            ['html'],
            ['junit', { outputFile: 'results.xml' }],
        ],
        use: {
            baseURL: 'http://localhost:3000',
            trace: 'on-first-retry',
            screenshot: 'only-on-failure',
            video: 'retain-on-failure',
        },
        projects: [
            { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
            { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
            { name: 'webkit', use: { ...devices['Desktop Safari'] } },
            { name: 'mobile', use: { ...devices['iPhone 13'] } },
        ],
    });
    

    Pattern 1: Page Object Model

    // pages/LoginPage.ts
    import { Page, Locator } from '@playwright/test';
    
    export class LoginPage {
        readonly page: Page;
        readonly emailInput: Locator;
        readonly passwordInput: Locator;
        readonly loginButton: Locator;
        readonly errorMessage: Locator;
    
        constructor(page: Page) {
            this.page = page;
            this.emailInput = page.getByLabel('Email');
            this.passwordInput = page.getByLabel('Password');
            this.loginButton = page.getByRole('button', { name: 'Login' });
            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.loginButton.click();
        }
    
        async getErrorMessage(): Promise<string> {
            return await this.errorMessage.textContent() ?? '';
        }
    }
    
    // Test using Page Object
    import { test, expect } from '@playwright/test';
    import { LoginPage } from './pages/LoginPage';
    
    test('successful login', async ({ page }) => {
        const loginPage = new LoginPage(page);
        await loginPage.goto();
        await loginPage.login('user@example.com', 'password123');
    
        await expect(page).toHaveURL('/dashboard');
        await expect(page.getByRole('heading', { name: 'Dashboard' }))
            .toBeVisible();
    });
    
    test('failed login shows error', async ({ page }) => {
        const loginPage = new LoginPage(page);
        await loginPage.goto();
        await loginPage.login('invalid@example.com', 'wrong');
    
        const error = await loginPage.getErrorMessage();
        expect(error).toContain('Invalid credentials');
    });
    

    Pattern 2: Fixtures for Test Data

    // fixtures/test-data.ts
    import { test as base } from '@playwright/test';
    
    type TestData = {
        testUser: {
            email: string;
            password: string;
            name: string;
        };
        adminUser: {
            email: string;
            password: string;
        };
    };
    
    export const test = base.extend<TestData>({
        testUser: async ({}, use) => {
            const user = {
                email: `test-${Date.now()}@example.com`,
                password: 'Test123!@#',
                name: 'Test User',
            };
            // Setup: Create user in database
            await createTestUser(user);
            await use(user);
            // Teardown: Clean up user
            await deleteTestUser(user.email);
        },
    
        adminUser: async ({}, use) => {
            await use({
                email: 'admin@example.com',
                password: process.env.ADMIN_PASSWORD!,
            });
        },
    });
    
    // Usage in tests
    import { test } from './fixtures/test-data';
    
    test('user can update profile', async ({ page, testUser }) => {
        await page.goto('/login');
        await page.getByLabel('Email').fill(testUser.email);
        await page.getByLabel('Password').fill(testUser.password);
        await page.getByRole('button', { name: 'Login' }).click();
    
        await page.goto('/profile');
        await page.getByLabel('Name').fill('Updated Name');
        await page.getByRole('button', { name: 'Save' }).click();
    
        await expect(page.getByText('Profile updated')).toBeVisible();
    });
    

    Pattern 3: Waiting Strategies

    // ❌ Bad: Fixed timeouts
    await page.waitForTimeout(3000);  // Flaky!
    
    // ✅ Good: Wait for specific conditions
    await page.waitForLoadState('networkidle');
    await page.waitForURL('/dashboard');
    await page.waitForSelector('[data-testid="user-profile"]');
    
    // ✅ Better: Auto-waiting with assertions
    await expect(page.getByText('Welcome')).toBeVisible();
    await expect(page.getByRole('button', { name: 'Submit' }))
        .toBeEnabled();
    
    // Wait for API response
    const responsePromise = page.waitForResponse(
        response => response.url().includes('/api/users') && response.status() === 200
    );
    await page.getByRole('button', { name: 'Load Users' }).click();
    const response = await responsePromise;
    const data = await response.json();
    expect(data.users).toHaveLength(10);
    
    // Wait for multiple conditions
    await Promise.all([
        page.waitForURL('/success'),
        page.waitForLoadState('networkidle'),
        expect(page.getByText('Payment successful')).toBeVisible(),
    ]);
    

    Pattern 4: Network Mocking and Interception

    // Mock API responses
    test('displays error when API fails', async ({ page }) => {
        await page.route('**/api/users', route => {
            route.fulfill({
                status: 500,
                contentType: 'application/json',
                body: JSON.stringify({ error: 'Internal Server Error' }),
            });
        });
    
        await page.goto('/users');
        await expect(page.getByText('Failed to load users')).toBeVisible();
    });
    
    // Intercept and modify requests
    test('can modify API request', async ({ page }) => {
        await page.route('**/api/users', async route => {
            const request = route.request();
            const postData = JSON.parse(request.postData() || '{}');
    
            // Modify request
            postData.role = 'admin';
    
            await route.continue({
                postData: JSON.stringify(postData),
            });
        });
    
        // Test continues...
    });
    
    // Mock third-party services
    test('payment flow with mocked Stripe', async ({ page }) => {
        await page.route('**/api/stripe/**', route => {
            route.fulfill({
                status: 200,
                body: JSON.stringify({
                    id: 'mock_payment_id',
                    status: 'succeeded',
                }),
            });
        });
    
        // Test payment flow with mocked response
    });
    

    Advanced Patterns

    Pattern 1: Visual Regression Testing

    // With Playwright
    import { test, expect } from '@playwright/test';
    
    test('homepage looks correct', async ({ page }) => {
        await page.goto('/');
        await expect(page).toHaveScreenshot('homepage.png', {
            fullPage: true,
            maxDiffPixels: 100,
        });
    });
    
    test('button in all states', async ({ page }) => {
        await page.goto('/components');
    
        const button = page.getByRole('button', { name: 'Submit' });
    
        // Default state
        await expect(button).toHaveScreenshot('button-default.png');
    
        // Hover state
        await button.hover();
        await expect(button).toHaveScreenshot('button-hover.png');
    
        // Disabled state
        await button.evaluate(el => el.setAttribute('disabled', 'true'));
        await expect(button).toHaveScreenshot('button-disabled.png');
    });
    

    Pattern 2: Parallel Testing with Sharding

    // playwright.config.ts
    export default defineConfig({
        projects: [
            {
                name: 'shard-1',
                use: { ...devices['Desktop Chrome'] },
                grepInvert: /@slow/,
                shard: { current: 1, total: 4 },
            },
            {
                name: 'shard-2',
                use: { ...devices['Desktop Chrome'] },
                shard: { current: 2, total: 4 },
            },
            // ... more shards
        ],
    });
    
    // Run in CI
    // npx playwright test --shard=1/4
    // npx playwright test --shard=2/4
    

    Pattern 3: Accessibility Testing

    // Install: npm install @axe-core/playwright
    import { test, expect } from '@playwright/test';
    import AxeBuilder from '@axe-core/playwright';
    
    test('page should not have accessibility violations', async ({ page }) => {
        await page.goto('/');
    
        const accessibilityScanResults = await new AxeBuilder({ page })
            .exclude('#third-party-widget')
            .analyze();
    
        expect(accessibilityScanResults.violations).toEqual([]);
    });
    
    test('form is accessible', async ({ page }) => {
        await page.goto('/signup');
    
        const results = await new AxeBuilder({ page })
            .include('form')
            .analyze();
    
        expect(results.violations).toEqual([]);
    });
    

    Best Practices

    1. Use Data Attributes: data-testid or data-cy for stable selectors
    2. Avoid Brittle Selectors: Don't rely on CSS classes or DOM structure
    3. Test User Behavior: Click, type, see - not implementation details
    4. Keep Tests Independent: Each test should run in isolation
    5. Clean Up Test Data: Create and destroy test data in each test
    6. Use Page Objects: Encapsulate page logic
    7. Meaningful Assertions: Check actual user-visible behavior
    8. Optimize for Speed: Mock when possible, parallel execution
    // ❌ Bad selectors (Playwright)
    await page.locator('.btn.btn-primary.submit-button').click();
    await page.locator('div > form > div:nth-child(2) > input').fill('text');
    
    // ✅ Good selectors (Playwright)
    await page.getByRole('button', { name: 'Submit' }).click();
    await page.getByLabel('Email address').fill('user@example.com');
    await page.getByTestId('email-input').fill('user@example.com');
    

    Common Pitfalls

    • Flaky Tests: Use proper waits, not fixed timeouts
    • Slow Tests: Mock external APIs, use parallel execution
    • Over-Testing: Don't test every edge case with E2E
    • Coupled Tests: Tests should not depend on each other
    • Poor Selectors: Avoid CSS classes and nth-child
    • No Cleanup: Clean up test data after each test
    • Testing Implementation: Test user behavior, not internals

    Debugging Failing Tests

    // Playwright debugging
    // 1. Run in headed mode
    npx playwright test --headed
    
    // 2. Run in debug mode
    npx playwright test --debug
    
    // 3. Use trace viewer
    await page.screenshot({ path: 'screenshot.png' });
    await page.video()?.saveAs('video.webm');
    
    // 4. Add test.step for better reporting
    test('checkout flow', async ({ page }) => {
        await test.step('Add item to cart', async () => {
            await page.goto('/products');
            await page.getByRole('button', { name: 'Add to Cart' }).click();
        });
    
        await test.step('Proceed to checkout', async () => {
            await page.goto('/cart');
            await page.getByRole('button', { name: 'Checkout' }).click();
        });
    });
    
    // 5. Inspect page state
    await page.pause();  // Pauses execution, opens inspector
    

    Resources

    • references/playwright-best-practices.md: Playwright-specific patterns
    • references/flaky-test-debugging.md: Debugging unreliable tests
    • assets/e2e-testing-checklist.md: What to test with E2E
    • assets/selector-strategies.md: Finding reliable selectors
    • scripts/test-analyzer.ts: Analyze test flakiness and duration
    Recommended Servers
    Hostsmith
    Hostsmith
    OpenZeppelin
    OpenZeppelin
    EduBase
    EduBase
    Repository
    thapaliyabikendra/ai-artifacts
    Files