From playwright-e2e
Generate E2E spec files from natural language descriptions, URLs, or user flow descriptions. Produces fully-typed .spec.ts files with test.describe/test blocks, framework-aware selectors, auth handling via storageState, and correct import paths.
How this skill is triggered — by the user, by Claude, or both
Slash command
/playwright-e2e:e2e-test-generationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The skill accepts three input formats. Detect which is provided and adapt accordingly.
The skill accepts three input formats. Detect which is provided and adapt accordingly.
| Format | Example | Notes |
|---|---|---|
| Natural language | "Test the checkout flow for a logged-in user" | Infer steps from domain knowledge + page structure |
| URL + flow description | http://localhost:3000/checkout + "add item, proceed to payment" | Navigate to URL first, derive actions from description |
| Step list | numbered or bulleted steps describing user interactions | Map each step to a Playwright action |
Each generation produces a single .spec.ts file placed in the testDir configured in .playwright-agent.json (default: tests/e2e/specs/). File names follow the pattern <feature-name>.spec.ts using kebab-case.
All generated tests follow the test.describe → test structure:
import { test, expect } from '../fixtures/test';
test.describe('Checkout Flow', () => {
test('should complete checkout as authenticated user', async ({ page, authenticatedUser }) => {
// arrange
await page.goto('/checkout');
// act
await page.getByRole('button', { name: 'Add to cart' }).click();
await page.getByRole('link', { name: 'Proceed to payment' }).click();
// assert
await expect(page).toHaveURL('/payment');
await expect(page.getByRole('heading', { name: 'Payment Details' })).toBeVisible();
});
});
Rules:
test.describe block per logical feature or page.test block per distinct user scenario or outcome.// arrange, // act, // assert comments.test.describe more than one level deep.Always import test and expect from the project fixtures barrel, not directly from @playwright/test:
// DO — uses extended fixtures (auth, test data, etc.)
import { test, expect } from '../fixtures/test';
// DON'T — bypasses project fixtures
import { test, expect } from '@playwright/test';
Adjust the relative path based on the spec file's location within testDir.
Map input steps to Playwright actions using these rules:
| User Action | Playwright Call |
|---|---|
| Navigate to URL / page | page.goto('/path') |
| Click button / link | page.getByRole('button' | 'link', { name }).click() |
| Fill text input | page.getByLabel('Label text').fill('value') |
| Select dropdown | page.getByRole('combobox', { name }).selectOption('value') |
| Check / uncheck | page.getByRole('checkbox', { name }).check() |
| Upload file | page.getByLabel('Upload').setInputFiles('path/to/file') |
| Assert visible | expect(locator).toBeVisible() |
| Assert URL | expect(page).toHaveURL('/path') |
| Assert text | expect(locator).toHaveText('expected text') |
Detect the UI framework from .playwright-agent.json (framework key) and apply the correct locator strategy per the e2e-selectors skill. Default to getByRole and getByText when the framework is not set.
When the flow requires an authenticated user, use the storageState fixture rather than performing login steps inline:
// In playwright.config.ts — reference the saved auth state
use: {
storageState: 'tests/e2e/.auth/user.json',
},
// In the spec — use the authenticated fixture
test('should view account dashboard', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.getByRole('heading', { name: 'My Account' })).toBeVisible();
});
Generate a separate auth.setup.ts file if one does not already exist (see fixture-generation skill for the template).
import { test, expect } from '../fixtures/test';
test.describe('Login Page', () => {
test('should log in with valid credentials', async ({ page }) => {
// arrange
await page.goto('/login');
// act
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('secret');
await page.getByRole('button', { name: 'Sign in' }).click();
// assert
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
test('should show validation error for missing email', async ({ page }) => {
// arrange
await page.goto('/login');
// act
await page.getByLabel('Password').fill('secret');
await page.getByRole('button', { name: 'Sign in' }).click();
// assert
await expect(page.getByText('Email is required')).toBeVisible();
});
});
When categorization.enabled is true in the resolved config, invoke the test-case-categorizer skill before generating test blocks. Each test() receives a { tag: ['@<category>'] } option.
test.describe('Login Page', () => {
// --- Positive scenarios ---
test('should log in with valid credentials', { tag: ['@positive'] }, async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('validPass123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL('/dashboard');
});
test('should navigate to dashboard after login', { tag: ['@positive'] }, async ({ page }) => {
// ...
});
// --- Negative scenarios ---
test('should show error for invalid password', { tag: ['@negative'] }, async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('wrong');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Invalid credentials')).toBeVisible();
});
test('should show validation error for empty email', { tag: ['@negative'] }, async ({ page }) => {
await page.goto('/login');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Email is required')).toBeVisible();
});
// --- Edge scenarios ---
test('should handle 254-character email input', { tag: ['@edge'] }, async ({ page }) => {
const longEmail = 'a'.repeat(245) + '@test.com';
await page.goto('/login');
await page.getByLabel('Email').fill(longEmail);
await expect(page.getByLabel('Email')).toHaveValue(longEmail);
});
// --- Security scenarios ---
test('should sanitize XSS in email field', { tag: ['@security'] }, async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('<script>alert(1)</script>');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.locator('script')).toHaveCount(0);
await expect(page.getByText('Invalid email')).toBeVisible();
});
});
test() block must include a tag option with exactly one category tag (@positive, @negative, @edge, or @security).test.describe by category order: positive → negative → edge → security.// --- Positive scenarios ---) between category groups.test.describe blocks for categorization — use tags instead.npx claudepluginhub gagandeepp/software-agent-teams --plugin playwright-e2eGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.