Provides patterns for E2E testing using Playwright and a BasePage object model.

Install

mkdir -p .claude/skills/e2e-testing-coldstartlabs-ca && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14476" && unzip -o skill.zip -d .claude/skills/e2e-testing-coldstartlabs-ca && rm skill.zip

Installs to .claude/skills/e2e-testing-coldstartlabs-ca

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

When writing E2E/client tests for this codebase, follow the established patterns using Playwright and the BasePage pattern.
123 chars · catalog description✓ has a “when” trigger
Intermediate

Key capabilities

  • Write E2E tests using Playwright
  • Implement the BasePage pattern for page objects
  • Manage test context for resource handling
  • Extend Playwright fixtures for browser automation
  • Handle modals, toasts, and loading states in tests
  • Create users with a fluent builder API for test setup

How it works

This skill provides a structured approach to writing E2E tests using Playwright, use a BasePage pattern for common UI interactions and a TestContext for resource management.

Inputs & outputs

You give it
User stories or critical user flows to test
You get back
Playwright E2E tests and test reports

When to use e2e-testing

  • Writing end-to-end UI tests
  • Testing critical user flows
  • Maintaining consistent test page objects

About this skill

E2E Testing Skill

When writing E2E/client tests for this codebase, follow the established patterns using Playwright and the BasePage pattern.

Overview

  • Framework: Playwright E2E tests (tests/e2e/*.e2e.spec.ts)
  • Pattern: Enhanced Page Object Model with BasePage class
  • Test Context: TestContext - centralized resource management
  • Fixtures: Extended Playwright fixtures in tests/test-fixtures.ts

Directory Structure

tests/
├── e2e/              # End-to-end browser tests (.e2e.spec.ts)
├── pages/            # Page object models
│   └── BasePage.ts   # Base class with common UI patterns
├── helpers/
│   └── TestContext.ts # User factory and cleanup
└── test-fixtures.ts  # Playwright fixture extensions

BasePage Pattern

All page objects extend BasePage for common functionality:

import { BasePage } from '@tests/pages/BasePage';

export class LoginPage extends BasePage {
  // Locators
  get googleSignInButton(): Locator {
    return this.page.getByRole('button', { name: 'Continue with Google' });
  }

  get emailInput(): Locator {
    return this.page.getByPlaceholder(/email/i);
  }

  // Actions
  async openLoginModal(): Promise<void> {
    await this.signInButton.click();
    await this.waitForModal();
  }

  async fillLoginForm(email: string, password: string): Promise<void> {
    await this.fillField(/email/i, email);
    await this.fillField(/password/i, password);
  }

  async submitForm(): Promise<void> {
    await this.clickButton('Sign In');
  }

  // Assertions
  async assertModalVisible(): Promise<void> {
    await expect(this.modal).toBeVisible();
  }
}

BasePage Available Methods

Navigation

await page.goto('/'); // Navigate to path
await page.waitForURL('/dashboard'); // Wait for URL

Modal Handling

await this.waitForModal(); // Wait for modal to appear
await this.closeModal(); // Close active modal
await this.clickModalButton('Submit'); // Click button in modal

Toast/Notifications

await this.waitForToast('Success!'); // Wait for toast with text
await this.dismissToast(); // Dismiss active toast

Loading States

await this.waitForLoadingComplete(); // Wait for page load
await this.waitForAuthLoadingComplete(); // Wait for auth load

Network Handling

await this.waitForApiResponse('/api/user'); // Wait for API response
await this.waitForApiRequest('/api/upscale'); // Wait for API request

Form Helpers

await this.fillField(/email/i, '[email protected]');
await this.selectOption(/country/i, 'United States');

Accessibility

await this.checkBasicAccessibility(); // Basic a11y checks
await this.checkAriaLabels(); // Verify ARIA labels

Writing E2E Tests

Basic Test Structure

import { test, expect } from '@tests/e2e/fixtures';
import { LoginPage } from '@tests/pages/LoginPage';

test.describe('Authentication', () => {
  test('should show login form when clicking sign in', async ({ page }) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto('/');
    await loginPage.openLoginModal();
    await loginPage.assertModalVisible();
  });
});

TestContext Setup

import { TestContext } from '@tests/helpers/TestContext';

let ctx: TestContext;

test.beforeAll(async () => {
  ctx = new TestContext();
});

test.afterAll(async () => {
  await ctx.cleanup();
});

User Factory

// Simple user
const user = await ctx.createUser();

// Fluent builder API
const user = await ctx.createUser().withSubscription('active', 'pro').withCredits(500).build();

// Preset configurations
const proUser = await ctx.userFactory.proUser(500);
const businessUser = await ctx.userFactory.businessUser(1000);

Common E2E Test Patterns

Form Submission

test('should submit login form', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto('/');

  await loginPage.openLoginModal();
  await loginPage.fillLoginForm('[email protected]', 'password123');
  await loginPage.submitForm();

  await loginPage.waitForToast('Welcome back!');
});

Navigation

test('should navigate to dashboard', async ({ page }) => {
  const dashboardPage = new DashboardPage(page);
  await dashboardPage.goto('/');
  await dashboardPage.clickNavLink('Dashboard');
  await dashboardPage.waitForURL('/dashboard');
});

Loading States

test('should show loading state during upload', async ({ page }) => {
  const uploadPage = new UploadPage(page);
  await uploadPage.goto('/upload');

  await uploadPage.uploadImage(testImage);
  await uploadPage.waitForLoadingComplete();

  await expect(uploadPage.resultPreview).toBeVisible();
});

Network Requests

test('should handle API errors gracefully', async ({ page }) => {
  const uploadPage = new UploadPage(page);

  // Intercept and mock API response
  await page.route('**/api/upscale', route =>
    route.fulfill({ status: 500, body: '{"error":"Server error"}' })
  );

  await uploadPage.goto('/upload');
  await uploadPage.uploadImage(testImage);

  await uploadPage.waitForToast('Something went wrong');
});

Keyboard Navigation

test('should handle keyboard navigation properly', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto('/');
  await loginPage.openLoginModal();

  await page.keyboard.press('Tab');
  await expect(loginPage.emailInput).toBeFocused();

  await page.keyboard.press('Tab');
  await expect(loginPage.passwordInput).toBeFocused();
});

Accessibility Testing

Always include accessibility checks in E2E tests:

test('should have proper accessibility attributes', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto('/');
  await loginPage.openLoginModal();

  // Check basic accessibility
  await loginPage.checkBasicAccessibility();

  // Check ARIA labels
  await loginPage.checkAriaLabels();
});

Manual Accessibility Checks

test('should be keyboard accessible', async ({ page }) => {
  const page = new BasePage(page);
  await page.goto('/');

  // Test Tab navigation
  await page.keyboard.press('Tab');
  // Verify focus indicator visible
});

test('should have proper ARIA labels', async ({ page }) => {
  await page.goto('/');

  const button = page.getByRole('button', { name: /submit/i });
  await expect(button).toHaveAttribute('aria-label', 'Submit form');
});

Mobile Testing

The Playwright config includes mobile device projects. Tests automatically run on:

  • Desktop (chromium)
  • Mobile iPhone (iPhone 12)
  • Mobile Android (Pixel 5)
  • Tablet (iPad)

Write mobile-specific tests:

test.describe('Mobile Navigation', () => {
  test('should show hamburger menu on mobile', async ({ page }) => {
    // This test runs on mobile devices
    const nav = new Navigation(page);
    await nav.goto('/');

    await expect(nav.hamburgerMenu).toBeVisible();
    await nav.openMobileMenu();
    await expect(nav.mobileMenuItems).toBeVisible();
  });
});

Common Pitfalls to Avoid

1. Fragile Selectors

Bad: Text-based selectors break easily

await page.click('Submit');

Good: Role-based selectors

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

Good: Test IDs (most stable)

await page.getByTestId('submit-button').click();

2. Hardcoded Waits

Bad: Fixed timeouts are flaky

await page.waitForTimeout(2000);

Good: Wait for specific condition

await page.waitForURL('/dashboard');
await expect(element).toBeVisible();
await this.waitForLoadingComplete();

3. Not Cleaning Up

Always clean up test data:

let ctx: TestContext;

test.beforeAll(async () => {
  ctx = new TestContext();
});

test.afterAll(async () => {
  await ctx.cleanup(); // Don't forget this!
});

4. Skipping Accessibility

Don't skip accessibility checks:

test('should work', async ({ page }) => {
  // Missing accessibility checks!
  await page.goto('/');
});

test('should work with accessibility', async ({ page }) => {
  await page.goto('/');
  await new BasePage(page).checkBasicAccessibility();
});

5. Ignoring Mobile

Don't only test desktop - tests run on all devices:

// This runs on desktop AND mobile/tablet
test('should work on all devices', async ({ page }) => {
  await page.goto('/');
  // Test works on all screen sizes
});

Best Practices

Test Naming

  • Use descriptive names: "should show login form when clicking sign in"
  • Group by feature: test.describe('Authentication')
  • Test user flows: "should complete checkout flow"

Page Object Structure

export class FeaturePage extends BasePage {
  // Locators (getters for lazy evaluation)
  get element(): Locator {
    return this.page.locator('...');
  }

  // Actions (async methods)
  async doSomething(): Promise<void> {}

  // Assertions (async methods with 'assert' prefix)
  async assertSomething(): Promise<void> {
    await expect(this.element).toBeVisible();
  }
}

Selector Priority

  1. Test IDs - Most stable: getByTestId()
  2. Role + Name - Semantic: getByRole('button', { name })
  3. Labels/Placeholders - Form inputs: getByPlaceholder(), getByLabel()
  4. Text - Only when necessary: getByText()
  5. CSS - Last resort: locator()

Testing Commands

# Run all E2E tests
yarn test:e2e

# Run specific test file
yarn test:e2e tests/e2e/auth.e2e.spec.ts

# Playwright UI mode (interactive)
yarn test:e2e:ui

# Debug mode with inspector
yarn test:e2e:debug

# Run on specific device
yarn test:e2e --project=mobile-iphone

# 

---

*Content truncated.*

When not to use it

  • When writing unit tests or integration tests
  • When not using Playwright as the E2E testing framework
  • When not adhering to the BasePage pattern for page objects

Limitations

  • All page objects must extend `BasePage`.
  • Tests run on all devices (desktop, mobile, tablet) by default.
  • Selector priority is enforced, with test IDs being most stable.

How it compares

This workflow standardizes E2E testing with a Page Object Model and custom fixtures, making tests more maintainable and readable than unstructured scripts.

Compared to similar skills

e2e-testing side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
e2e-testing (this skill)07moReviewIntermediate
ui-ux-expert-skill919moReviewAdvanced
playwright-browser-automation298moReviewIntermediate
frontend-testing113moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

ui-ux-expert-skill

fercracix33

Technical workflow for implementing accessible React user interfaces with shadcn/ui, Tailwind CSS, and TanStack Query. Includes 6-phase process with mandatory Style Guide compliance, Context7 best practices consultation, Chrome DevTools validation, and WCAG 2.1 AA accessibility standards. Use after Test Agent, Implementer, and Supabase agents complete their work.

91244

playwright-browser-automation

lackeyjb

Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.

29146

frontend-testing

langgenius

Generate Vitest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Vitest, RTL, unit tests, integration tests, or write/review test requests.

1152

flutter

alinaqi

Flutter development with Riverpod state management, Freezed, go_router, and mocktail testing

1343

feature-flags

facebook

Use when feature flag tests fail, flags need updating, understanding @gate pragmas, debugging channel-specific test failures, or adding new flags to React.

642

vue-testing-best-practices

vuejs-ai

Use for Vue.js testing. Covers Vitest, Vue Test Utils, component testing, mocking, testing patterns, and Playwright for E2E testing.

534

Search skills

Search the agent skills registry