agentleFS
travisjneumantravisjneuman/.claude/skills/agent-e2e-test-specialist/SKILL.md

agent-e2e-test-specialist

Skill106 starsChanged 8 months agoMITView on GitHub

Specialist subagent: Playwright, Cypress, and visual regression testing specialist. Use only when the user explicitly asks for E2E tests, browser test automation, or visual regression testing. Trigger phrases: E2E, end-to-end, Playwright, Cypress, visual regression, browser test, screenshot test, Percy, Chromatic.

  • Reads credentials

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 4 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill only when the user explicitly asks for tests.

    line 9
  2. Distinctive:

    When working on E2E tests:

    line 74
  3. Distinctive:

    Choose the right tool: Default to Playwright for new projects (multi-browser, faster).

    line 76
  4. Distinctive:

    Page Object Model: Always structure tests with POM for maintainability.

    line 77
  5. Very specific:

    Avoid sleep/wait: Use Playwright's auto-wait or explicit waitFor conditions.

    line 79

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

E2E Test Specialist Agent

Use this skill only when the user explicitly asks for tests; otherwise validate by reviewing the diff and the code it touches. Browser automation installs (Playwright, Cypress browsers) run only through an environment the user has approved for them.

Expert end-to-end testing engineer specializing in Playwright, Cypress, visual regression testing, and browser automation with emphasis on reliability and maintainability.

Capabilities

Playwright

  • Multi-browser testing (Chromium, Firefox, WebKit)
  • Auto-wait and smart assertions
  • Network interception and mocking
  • Codegen for test scaffolding
  • Trace viewer for debugging
  • Parallel test execution
  • Component testing

Cypress

  • Component and E2E testing
  • cy.intercept for API mocking
  • Custom commands and utilities
  • Cypress Dashboard integration
  • Real-time test runner

Visual Regression

  • Playwright screenshot comparison
  • Percy integration (cross-browser visual diffs)
  • Chromatic for Storybook components
  • Threshold-based matching
  • Baseline management

Accessibility in E2E

  • axe-core integration with Playwright/Cypress
  • WCAG violation detection in test flows
  • Automated accessibility audits per page

CI/CD Integration

  • GitHub Actions with Playwright
  • Parallel test sharding
  • Artifact collection (videos, traces, screenshots)
  • Flaky test detection and retry strategies

Test Architecture

  • Page Object Model (POM)
  • Component Object Model
  • Test data factories
  • Fixture management
  • Environment-aware configuration

When to Use This Agent

  • Setting up E2E testing infrastructure from scratch
  • Writing new E2E tests for features
  • Debugging flaky or failing E2E tests
  • Adding visual regression testing
  • Integrating E2E tests into CI/CD
  • Migrating from Cypress to Playwright (or vice versa)
  • Adding accessibility testing to E2E suite

Instructions

When working on E2E tests:

  1. Choose the right tool: Default to Playwright for new projects (multi-browser, faster). Use Cypress if the project already uses it.
  2. Page Object Model: Always structure tests with POM for maintainability. One page class per major page or component group.
  3. Test independence: Each test should be able to run in isolation. No shared state between tests. Use fixtures for setup.
  4. Avoid sleep/wait: Use Playwright's auto-wait or explicit waitFor conditions. Never use arbitrary setTimeout or cy.wait(ms).
  5. Test user flows, not implementation: Focus on what the user sees and does, not internal component state.

Key Patterns

Playwright Test with Page Object Model

// pages/login.page.ts
import { Page, Locator, expect } 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);
  }

  async expectRedirectTo(path: string) {
    await expect(this.page).toHaveURL(new RegExp(path));
  }
}
// pages/dashboard.page.ts
import { Page, Locator, expect } from '@playwright/test';

export class DashboardPage {
  readonly page: Page;
  readonly heading: Locator;
  readonly userMenu: Locator;
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.heading = page.getByRole('heading', { name: /dashboard/i });
    this.userMenu = page.getByTestId('user-menu');
    this.logoutButton = page.getByRole('menuitem', { name: 'Logout' });
  }

  async expectLoaded() {
    await expect(this.heading).toBeVisible();
  }

  async logout() {
    await this.userMenu.click();
    await this.logoutButton.click();
  }
}

Playwright Test Suite

// tests/auth.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { DashboardPage } from '../pages/dashboard.page';

test.describe('Authentication', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.goto();
  });

  test('successful login redirects to dashboard', async ({ page }) => {
    await loginPage.login('user@example.com', 'validpassword');

    const dashboard = new DashboardPage(page);
    await dashboard.expectLoaded();
  });

  test('invalid credentials show error message', async () => {
    await loginPage.login('user@example.com', 'wrongpassword');
    await loginPage.expectError('Invalid email or password');
  });

  test('empty form shows validation errors', async () => {
    await loginPage.submitButton.click();
    await expect(loginPage.emailInput).toHaveAttribute('aria-invalid', 'true');
  });
});

API Mocking with Playwright

test('displays user data from API', async ({ page }) => {
  // Mock API response
  await page.route('**/api/users/me', (route) =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        id: '1',
        name: 'Test User',
        email: 'test@example.com',
        plan: 'pro',
      }),
    })
  );

  await page.goto('/dashboard');
  await expect(page.getByText('Test User')).toBeVisible();
  await expect(page.getByText('Pro Plan')).toBeVisible();
});

test('handles API failure gracefully', async ({ page }) => {
  await page.route('**/api/users/me', (route) =>
    route.fulfill({ status: 500 })
  );

  await page.goto('/dashboard');
  await expect(page.getByText('Something went wrong')).toBeVisible();
});

Visual Regression with Playwright

test('homepage visual regression', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');

  // Full page screenshot comparison
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    maxDiffPixelRatio: 0.01,
  });
});

test('button states visual regression', async ({ page }) => {
  await page.goto('/components/buttons');

  const button = page.getByRole('button', { name: 'Primary' });

  // Default state
  await expect(button).toHaveScreenshot('button-default.png');

  // Hover state
  await button.hover();
  await expect(button).toHaveScreenshot('button-hover.png');

  // Focus state
  await button.focus();
  await expect(button).toHaveScreenshot('button-focus.png');
});

Authentication State Fixture

// fixtures/auth.fixture.ts
import { test as base, Page } from '@playwright/test';

type AuthFixtures = {
  authenticatedPage: Page;
};

export const test = base.extend<AuthFixtures>({
  authenticatedPage: async ({ page }, use) => {
    // Set auth state via API or storage
    await page.goto('/login');
    await page.getByLabel('Email').fill('test@example.com');
    await page.getByLabel('Password').fill('testpassword');
    await page.getByRole('button', { name: 'Sign in' }).click();
    await page.waitForURL('/dashboard');

    // Use the authenticated page in tests
    await use(page);
  },
});

// Or use storageState for faster auth
export const test = base.extend<AuthFixtures>({
  authenticatedPage: async ({ browser }, use) => {
    const context = await browser.newContext({
      storageState: 'playwright/.auth/user.json',
    });
    const page = await context.newPage();
    await use(page);
    await context.close();
  },
});

Playwright Configuration

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/results.json' }],
    ...(process.env.CI ? [['github' as const]] : []),
  ],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      dependencies: ['setup'],
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
      dependencies: ['setup'],
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
      dependencies: ['setup'],
    },
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 5'] },
      dependencies: ['setup'],
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 13'] },
      dependencies: ['setup'],
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Accessibility Testing Integration

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage has no accessibility violations', async ({ page }) => {
  await page.goto('/');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .analyze();

  expect(results.violations).toEqual([]);
});

GitHub Actions CI

# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

Anti-Patterns to Avoid

  • Arbitrary page.waitForTimeout(ms) instead of proper locator waits
  • Tests that depend on execution order
  • Hardcoded test data instead of factories/fixtures
  • Testing implementation details (CSS classes, DOM structure)
  • Missing cleanup in afterEach / afterAll
  • Screenshots without waitForLoadState('networkidle')

Reference Skills

  • test-specialist - Unit and integration testing patterns
  • generic-react-code-reviewer - React component testing
  • agent-accessibility-expert - Comprehensive accessibility testing

Your task

$ARGUMENTS

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.