Playwright E2E Test Suite Builder
When to use
Use this skill when you need to:
- Set up Playwright from scratch in an existing project
- Build E2E tests for critical user flows (signup, checkout, dashboards)
- Implement Page Object Model for maintainable test architecture
- Configure authentication state persistence across tests
- Set up visual regression testing with screenshots
- Integrate Playwright into CI/CD with sharding and retries
Phase 1: Explore (Plan Mode)
Enter plan mode. Before writing any tests, explore the existing project:
Project structure
- Find the tech stack: is this React, Next.js, Vue, SvelteKit, or another framework?
- Check if Playwright is already installed (
playwright.config.ts, @playwright/test in package.json)
- Look for existing test directories (
e2e/, tests/, __tests__/)
- Check for existing E2E tests in Cypress, Selenium, or other frameworks (migration context)
- Find the dev server command and port (
npm run dev, next dev, etc.)
Application structure
- Identify the main routes/pages (look at router config, pages directory, or route files)
- Find authentication flow (login page URL, auth API endpoints, token storage)
- Check for test IDs in components (
data-testid, data-test, data-cy attributes)
- Look for API routes that tests might need to seed data through
- Check
.env files for test-specific environment variables
CI/CD
- Check for existing CI config (
.github/workflows/, .gitlab-ci.yml, Jenkinsfile)
- Look for Docker or docker-compose setup (useful for consistent test environments)
- Check if there's a staging/preview environment URL pattern
Phase 2: Interview (AskUserQuestion)
Use AskUserQuestion to clarify requirements. Ask in rounds.
Round 1: Scope and critical flows
Question: "What are the critical user flows to test?"
Header: "Flows"
multiSelect: true
Options:
- "Authentication (signup, login, logout, password reset)" โ Core auth flows
- "Core CRUD (create, read, update, delete main resources)" โ Primary data operations
- "Checkout/payments (cart, billing, confirmation)" โ E-commerce or payment flows
- "Dashboard/admin (data views, filters, exports)" โ Admin panel interactions
Question: "How many pages/routes does the application have approximately?"
Header: "App size"
Options:
- "Small (< 10 routes)" โ Landing page, auth, a few feature pages
- "Medium (10-30 routes)" โ Multiple feature areas, settings, profiles
- "Large (30+ routes)" โ Complex app with many sections and user roles
Round 2: Authentication strategy for tests
Question: "How does your app handle authentication?"
Header: "Auth type"
Options:
- "Cookie/session based (Recommended)" โ Server sets httpOnly cookies after login
- "JWT in localStorage" โ Token stored in browser localStorage
- "OAuth/SSO (Google, GitHub, etc.)" โ Third-party auth provider redirect flow
- "No auth (public app)" โ No login required
Question: "How should tests authenticate?"
Header: "Test auth"
Options:
- "Login via UI once, reuse state (Recommended)" โ storageState pattern: login in setup, share cookies across tests
- "API login in beforeEach" โ Call auth API directly before each test, skip UI login
- "Seed auth token in fixtures" โ Inject pre-generated tokens, no login flow needed
- "Test login UI every time" โ Actually test the login form in each test suite
Round 3: Test data and environment
Question: "How should test data be managed?"
Header: "Test data"
Options:
- "API seeding in fixtures (Recommended)" โ Call API endpoints to create/clean test data before each test
- "Database seeding (direct SQL)" โ Run SQL scripts or ORM commands to populate test database
- "Shared test environment (pre-populated)" โ Tests run against a persistent staging environment with existing data
- "Mock API responses" โ Intercept network requests and return mock data
Question: "What environment do E2E tests run against?"
Header: "Environment"
Options:
- "Local dev server (Recommended)" โ Start dev server before tests, run against localhost
- "Preview/staging URL" โ Run against a deployed preview or staging environment
- "Docker Compose stack" โ Full stack in containers, tests run outside or inside
Round 4: CI and parallelization
Question: "How should tests run in CI?"
Header: "CI"
Options:
- "GitHub Actions (Recommended)" โ Native Playwright support with sharding
- "GitLab CI" โ Docker-based runners with Playwright image
- "Local only (no CI yet)" โ Just local test runs for now
- "Other CI (Jenkins, CircleCI)" โ Custom CI configuration
Question: "Do you need visual regression testing?"
Header: "Visual"
Options:
- "No โ functional tests only (Recommended)" โ Assert behavior, not pixels
- "Yes โ screenshot comparisons" โ Capture and compare page screenshots
- "Yes โ component screenshots" โ Capture specific components, not full pages
Phase 3: Plan (ExitPlanMode)
Write a concrete implementation plan covering:
- Directory structure โ test files, page objects, fixtures, config
- Playwright config โ projects (browsers), base URL, retries, workers
- Auth setup โ global setup for storageState or API-based auth
- Page objects โ classes for each page with locators and actions
- Test fixtures โ custom fixtures for data seeding, auth, API client
- Test suites โ test files for each critical flow from the interview
- CI config โ workflow file with sharding, artifact upload, reporting
Present via ExitPlanMode for user approval.
Phase 4: Execute
After approval, implement following this order:
Step 1: Playwright config
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI
? [['html', { open: 'never' }], ['github']]
: [['html', { open: 'on-failure' }]],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'mobile',
use: {
...devices['iPhone 14'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
Step 2: Auth setup (global)
import { test as setup, expect } from '@playwright/test';
const authFile = 'e2e/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL || '[email protected]');
await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD || 'testpassword');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('/dashboard');
await expe