Playwright E2E testing setup and configuration. Use when setting up end-to-end tests.
/plugin marketplace add IvanTorresEdge/molcajete.ai/plugin install ivantorresedge-react-tech-stacks-js-react@IvanTorresEdge/molcajete.aiThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill covers Playwright end-to-end testing setup for React applications.
Use this skill when:
TEST USER JOURNEYS - E2E tests verify complete user flows. Focus on critical paths that matter to users.
npm init playwright@latest
Or manually:
npm install -D @playwright/test
npx playwright install
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['json', { outputFile: 'test-results/results.json' }],
['list'],
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
});
tests/
├── e2e/
│ ├── pages/ # Page Objects
│ │ ├── LoginPage.ts
│ │ ├── DashboardPage.ts
│ │ └── BasePage.ts
│ ├── fixtures/ # Custom fixtures
│ │ └── index.ts
│ ├── auth.spec.ts
│ ├── dashboard.spec.ts
│ └── checkout.spec.ts
└── playwright.config.ts
import { test, expect } from '@playwright/test';
test.describe('Home Page', () => {
test('displays welcome message', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});
test('navigates to about page', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL('/about');
});
});
// tests/e2e/pages/BasePage.ts
import { Page, Locator } from '@playwright/test';
export class BasePage {
readonly page: Page;
readonly header: Locator;
readonly footer: Locator;
constructor(page: Page) {
this.page = page;
this.header = page.getByRole('banner');
this.footer = page.getByRole('contentinfo');
}
async navigate(path: string): Promise<void> {
await this.page.goto(path);
}
async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState('networkidle');
}
}
// tests/e2e/pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
import { BasePage } from './BasePage';
export class LoginPage extends BasePage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
super(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(): Promise<void> {
await this.navigate('/login');
}
async login(email: string, password: string): Promise<void> {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string): Promise<void> {
await expect(this.errorMessage).toContainText(message);
}
async expectLoggedIn(): Promise<void> {
await expect(this.page).toHaveURL('/dashboard');
}
}
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
test.describe('Authentication', () => {
test('logs in with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await loginPage.expectLoggedIn();
});
test('shows error with invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'wrongpassword');
await loginPage.expectError('Invalid credentials');
});
});
// tests/e2e/fixtures/index.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
interface Pages {
loginPage: LoginPage;
dashboardPage: DashboardPage;
}
interface TestFixtures {
authenticatedPage: void;
}
export const test = base.extend<Pages & TestFixtures>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
dashboardPage: async ({ page }, use) => {
await use(new DashboardPage(page));
},
authenticatedPage: async ({ page }, use) => {
// Setup: Login 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');
// Run the test
await use();
// Teardown: Logout after test (if needed)
},
});
export { expect } from '@playwright/test';
// Usage
import { test, expect } from '../fixtures';
test.describe('Dashboard', () => {
test('shows user info when authenticated', async ({ page, authenticatedPage }) => {
await expect(page.getByText('Welcome back')).toBeVisible();
});
});
// tests/e2e/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
const authFile = 'tests/.auth/user.json';
setup('authenticate', async ({ page }) => {
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');
await page.context().storageState({ path: authFile });
});
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'tests/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
test('displays mocked data', async ({ page }) => {
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Mock User' }]),
});
});
await page.goto('/users');
await expect(page.getByText('Mock User')).toBeVisible();
});
# Run all tests
npx playwright test
# Run specific file
npx playwright test auth.spec.ts
# Run with UI mode
npx playwright test --ui
# Run in headed mode
npx playwright test --headed
# Run specific project
npx playwright test --project=chromium
# Debug mode
npx playwright test --debug
# Show report
npx playwright show-report
# Update snapshots
npx playwright test --update-snapshots
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run tests
run: npx playwright test
- name: Upload report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
--debug to step through testsThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.