
E2e Testing
- 15 installs
- 213 repo stars
- Updated August 4, 2026
- yonatangross/orchestkit
Helps with testing & qa tasks.
About
e2e-testing is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted coding.
- e2e-testing
- Testing & QA
- AI-coding skill
E2e Testing by the numbers
- 15 all-time installs (skills.sh)
- Ranked #1,486 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yonatangross/orchestkit --skill e2e-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| repo stars | ★ 213 |
| Last updated | August 4, 2026 |
| Repository | yonatangross/orchestkit ↗ |
What it does
Helps with testing & qa tasks.
Files
E2E Testing with Playwright 1.58+
Validate critical user journeys end-to-end with AI-assisted test generation.
Quick Reference - Semantic Locators
// PREFERRED: Role-based locators (most resilient)
await page.getByRole('button', { name: 'Add to cart' }).click();
await page.getByRole('link', { name: 'Checkout' }).click();
// GOOD: Label-based for form controls
await page.getByLabel('Email').fill('test@example.com');
// ACCEPTABLE: Test IDs for stable anchors
await page.getByTestId('checkout-button').click();
// AVOID: CSS selectors and XPath (fragile)
// await page.click('[data-testid="add-to-cart"]');Locator Priority: getByRole() > getByLabel() > getByPlaceholder() > getByTestId()
Basic Test
import { test, expect } from '@playwright/test';
test('user can complete checkout', async ({ page }) => {
await page.goto('/products');
await page.getByRole('button', { name: 'Add to cart' }).click();
await page.getByRole('link', { name: 'Checkout' }).click();
await page.getByLabel('Email').fill('test@example.com');
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible();
});AI Agents (1.58+)
Initialize AI Agents
# Initialize agents for your preferred AI tool
npx playwright init-agents --loop=claude # For Claude Code
npx playwright init-agents --loop=vscode # For VS Code (requires v1.105+)
npx playwright init-agents --loop=opencode # For OpenCodeGenerated Structure
Running init-agents creates the following:
| Directory/File | Purpose |
|---|---|
.github/ | Agent definitions and configuration |
specs/ | Test plans in Markdown format |
tests/seed.spec.ts | Seed file for AI agents to reference |
Working with AI Agents
After initialization, agents can:
- Read test plans from
specs/and generate tests - Use
seed.spec.tsas a template for consistent patterns - Auto-repair failing tests by analyzing failures
Breaking Changes (1.58)
The following features have been removed in Playwright 1.58:
| Removed | Migration |
|---|---|
_react selector | Use getByRole() or getByTestId() |
_vue selector | Use getByRole() or getByTestId() |
:light selector suffix | Use standard CSS selectors without :light |
devtools launch option | Use args: ['--auto-open-devtools-for-tabs'] instead |
| macOS 13 WebKit support | Upgrade to macOS 14+ for WebKit testing |
Migration Examples
// Before (1.57 and earlier)
await page.locator('_react=MyComponent').click();
await page.locator('.card:light').click();
// After (1.58+)
await page.getByTestId('my-component').click();
await page.locator('.card').click();
// DevTools launch option
// Before
const browser = await chromium.launch({ devtools: true });
// After
const browser = await chromium.launch({
args: ['--auto-open-devtools-for-tabs']
});New Features (1.58+)
// Connect over CDP with local flag
const browser = await chromium.connectOverCDP({
endpointURL: 'http://localhost:9222',
isLocal: true // NEW: Optimizes for local connections
});
// Assert individual class names
await expect(page.locator('.card')).toContainClass('highlighted');
// Flaky test detection
export default defineConfig({
failOnFlakyTests: true,
});
// IndexedDB storage state
await page.context().storageState({
path: 'auth.json',
indexedDB: true // Include IndexedDB in storage state
});Timeline in Speedboard HTML Reports
HTML reports now include a timeline visualization showing:
- Test execution sequence
- Parallel test distribution
- Time spent in each test phase
- Performance bottlenecks
// Enable HTML reporter with timeline
export default defineConfig({
reporter: [['html', { open: 'never' }]],
});Anti-Patterns (FORBIDDEN)
// NEVER use CSS selectors for user interactions
await page.click('.submit-btn');
// NEVER use hardcoded waits
await page.waitForTimeout(2000);
// NEVER test implementation details
await page.click('[data-testid="btn-123"]');
// ALWAYS use semantic locators
await page.getByRole('button', { name: 'Submit' }).click();
// ALWAYS use Playwright's auto-wait
await expect(page.getByRole('alert')).toBeVisible();Key Decisions
| Decision | Recommendation |
|---|---|
| Locators | getByRole > getByLabel > getByTestId |
| Browser | Chromium (Chrome for Testing in 1.58+) |
| Execution | 5-30s per test |
| Retries | 2-3 in CI, 0 locally |
| Screenshots | On failure only |
Critical User Journeys to Test
1. Authentication: Signup, login, password reset 2. Core Transaction: Purchase, booking, submission 3. Data Operations: Create, update, delete 4. User Settings: Profile update, preferences
Detailed Documentation
| Resource | Description |
|---|---|
| references/playwright-1.57-api.md | Complete Playwright API reference |
| examples/test-patterns.md | User flows, page objects, visual tests |
| checklists/e2e-checklist.md | Test selection and review checklists |
| scripts/page-object-template.ts | Page object model template |
Related Skills
integration-testing- API-level testingwebapp-testing- Autonomous test agentsperformance-testing- Load testingllm-testing- Testing AI/LLM components
Capability Details
semantic-locators
Keywords: getByRole, getByLabel, getByText, semantic, locator Solves:
- Use accessibility-based locators
- Avoid brittle CSS/XPath selectors
- Write resilient element queries
visual-regression
Keywords: visual regression, screenshot, snapshot, visual diff Solves:
- Capture and compare visual snapshots
- Detect unintended UI changes
- Configure threshold tolerances
cross-browser-testing
Keywords: cross browser, chromium, firefox, webkit, browser matrix Solves:
- Run tests across multiple browsers
- Configure browser-specific settings
- Handle browser differences
ai-test-generation
Keywords: AI test, generate test, autonomous, test agent, planner, init-agents Solves:
- Generate tests from user journeys
- Use AI agents for test planning
- Create comprehensive test coverage
ai-test-healing
Keywords: test healing, self-heal, auto-fix, resilient test Solves:
- Automatically fix broken selectors
- Adapt tests to UI changes
- Reduce test maintenance
authentication-state
Keywords: auth state, storage state, login once, reuse session, indexedDB Solves:
- Persist authentication across tests
- Avoid repeated login flows
- Share auth state between tests
E2E Testing Checklist
Test Selection Checklist
Focus E2E tests on business-critical paths:
- [ ] Authentication: Signup, login, password reset, logout
- [ ] Core Transaction: Purchase, booking, submission, payment
- [ ] Data Operations: Create, update, delete critical entities
- [ ] User Settings: Profile update, preferences, notifications
- [ ] Error Recovery: Form validation, API errors, network issues
Locator Strategy Checklist
- [ ] Use
getByRole()as primary locator strategy - [ ] Use
getByLabel()for form inputs - [ ] Use
getByPlaceholder()when no label available - [ ] Use
getByTestId()only as last resort - [ ] AVOID CSS selectors for user interactions
- [ ] AVOID XPath locators
- [ ] AVOID
page.click('[data-testid=...]')- usegetByTestIdinstead
Test Implementation Checklist
For each test:
- [ ] Clear, descriptive test name
- [ ] Tests one user flow or scenario
- [ ] Uses semantic locators (getByRole, getByLabel)
- [ ] Waits for elements using Playwright's auto-wait
- [ ] No hardcoded
sleep()orwait()calls - [ ] Assertions use
expect()with appropriate matchers - [ ] Test can run in isolation (no dependencies on other tests)
Page Object Checklist
For each page object:
- [ ] Locators defined in constructor
- [ ] Methods for user actions (login, submit, navigate)
- [ ] Assertion methods (expectError, expectSuccess)
- [ ] No direct
page.click()calls - wrap in methods - [ ] TypeScript types for all methods
Configuration Checklist
- [ ] Set
baseURLin config - [ ] Configure browser(s) for testing
- [ ] Set up authentication state project
- [ ] Configure retries for CI (2-3 retries)
- [ ] Enable
failOnFlakyTestsin CI - [ ] Set appropriate timeouts
- [ ] Configure screenshot on failure
CI/CD Checklist
- [ ] Tests run in CI pipeline
- [ ] Artifacts (screenshots, traces) uploaded on failure
- [ ] Tests parallelized with sharding
- [ ] Auth state cached between runs
- [ ] Web server waits for ready signal
Visual Regression Checklist
- [ ] Screenshots stored in version control
- [ ] Different screenshots per browser/platform
- [ ] Mobile viewports tested
- [ ] Dark mode tested (if applicable)
- [ ] Threshold set for acceptable diff
Accessibility Checklist
- [ ] axe-core integrated for a11y testing
- [ ] Critical pages tested for violations
- [ ] Forms have proper labels
- [ ] Focus management tested
- [ ] Keyboard navigation tested
Review Checklist
Before PR:
- [ ] All tests pass locally
- [ ] Tests are deterministic (no flakes)
- [ ] Locators follow semantic strategy
- [ ] No hardcoded waits
- [ ] Test files organized logically
- [ ] Page objects used for complex pages
- [ ] CI configuration updated if needed
Anti-Patterns to Avoid
- [ ] Too many E2E tests (keep it focused)
- [ ] Testing non-critical paths
- [ ] Hard-coded waits (
await page.waitForTimeout()) - [ ] CSS/XPath selectors for interactions
- [ ] Tests that depend on each other
- [ ] Tests that modify global state
- [ ] Ignoring flaky test warnings
E2E Test Patterns
Complete User Flow Test
import { test, expect } from '@playwright/test';
test.describe('Checkout Flow', () => {
test('user can complete purchase', async ({ page }) => {
// Navigate to product
await page.goto('/products');
await page.getByRole('link', { name: 'Premium Widget' }).click();
// Add to cart
await page.getByRole('button', { name: 'Add to cart' }).click();
await expect(page.getByRole('alert')).toContainText('Added to cart');
// Go to checkout
await page.getByRole('link', { name: 'Cart' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
// Fill shipping info
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Full name').fill('Test User');
await page.getByLabel('Address').fill('123 Test St');
await page.getByLabel('City').fill('Test City');
await page.getByRole('combobox', { name: 'State' }).selectOption('CA');
await page.getByLabel('ZIP').fill('90210');
// Fill payment
await page.getByLabel('Card number').fill('4242424242424242');
await page.getByLabel('Expiry').fill('12/25');
await page.getByLabel('CVC').fill('123');
// Submit order
await page.getByRole('button', { name: 'Place order' }).click();
// Verify confirmation
await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible();
await expect(page.getByText(/order #/i)).toBeVisible();
});
});Page Object Model
// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
private readonly emailInput: Locator;
private readonly passwordInput: Locator;
private readonly submitButton: Locator;
private readonly errorMessage: Locator;
constructor(private 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 expectLoggedIn() {
await expect(this.page).toHaveURL('/dashboard');
}
}
// tests/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
test.describe('Login', () => {
test('successful login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await loginPage.expectLoggedIn();
});
test('invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'wrongpassword');
await loginPage.expectError('Invalid email or password');
});
});Authentication Fixture
// fixtures/auth.ts
import { test as base, Page } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
type AuthFixtures = {
authenticatedPage: Page;
adminPage: Page;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await use(page);
},
adminPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin@example.com', 'adminpass');
await use(page);
},
});
// tests/dashboard.spec.ts
import { test } from '../fixtures/auth';
test('user can view dashboard', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/dashboard');
// Already logged in
});
test('admin can access admin panel', async ({ adminPage }) => {
await adminPage.goto('/admin');
// Already logged in as admin
});Visual Regression Test
import { test, expect } from '@playwright/test';
test.describe('Visual Regression', () => {
test('homepage looks correct', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});
test('hero section visual', async ({ page }) => {
await page.goto('/');
const hero = page.locator('[data-testid="hero"]');
await expect(hero).toHaveScreenshot('hero.png');
});
test('responsive design - mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
await expect(page).toHaveScreenshot('homepage-mobile.png');
});
test('dark mode', async ({ page }) => {
await page.emulateMedia({ colorScheme: 'dark' });
await page.goto('/');
await expect(page).toHaveScreenshot('homepage-dark.png');
});
});API Mocking in E2E
import { test, expect } from '@playwright/test';
test('handles API error gracefully', async ({ page }) => {
// Mock API to return error
await page.route('/api/users', (route) => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server error' }),
});
});
await page.goto('/users');
await expect(page.getByText('Unable to load users')).toBeVisible();
await expect(page.getByRole('button', { name: 'Retry' })).toBeVisible();
});
test('shows loading state', async ({ page }) => {
// Delay API response
await page.route('/api/users', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
route.fulfill({
status: 200,
body: JSON.stringify([{ id: 1, name: 'User' }]),
});
});
await page.goto('/users');
await expect(page.getByTestId('loading-skeleton')).toBeVisible();
await expect(page.getByText('User')).toBeVisible({ timeout: 5000 });
});Multi-Tab Test
import { test, expect } from '@playwright/test';
test('multi-tab checkout flow', async ({ context }) => {
// Open two tabs
const page1 = await context.newPage();
const page2 = await context.newPage();
// Add item in first tab
await page1.goto('/products');
await page1.getByRole('button', { name: 'Add to cart' }).click();
// Verify cart updated in second tab
await page2.goto('/cart');
await expect(page2.getByRole('listitem')).toHaveCount(1);
});File Upload Test
import { test, expect } from '@playwright/test';
import path from 'path';
test('user can upload profile photo', async ({ page }) => {
await page.goto('/settings/profile');
// Upload file
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles(path.join(__dirname, 'fixtures/photo.jpg'));
// Verify preview
await expect(page.getByAltText('Profile preview')).toBeVisible();
// Save
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Profile updated');
});Accessibility Test
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.describe('Accessibility', () => {
test('homepage has no a11y violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test('login form is accessible', async ({ page }) => {
await page.goto('/login');
const results = await new AxeBuilder({ page })
.include('[data-testid="login-form"]')
.analyze();
expect(results.violations).toEqual([]);
});
});Playwright 1.58+ API Reference
Semantic Locators (2026 Best Practice)
Locator Priority
1. getByRole() - Matches how users/assistive tech see the page 2. getByLabel() - For form inputs with labels 3. getByPlaceholder() - For inputs with placeholders 4. getByText() - For text content 5. getByTestId() - When semantic locators aren't possible
Role-Based Locators
// Buttons
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('button', { name: /submit/i }).click(); // Regex
// Links
await page.getByRole('link', { name: 'Home' }).click();
// Headings
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Welcome');
// Form controls
await page.getByRole('textbox', { name: 'Email' }).fill('test@example.com');
await page.getByRole('checkbox', { name: 'Remember me' }).check();
await page.getByRole('combobox', { name: 'Country' }).selectOption('US');
// Lists
await expect(page.getByRole('list')).toContainText('Item 1');
await expect(page.getByRole('listitem')).toHaveCount(3);
// Navigation
await page.getByRole('navigation').getByRole('link', { name: 'About' }).click();Label-Based Locators
// Form inputs with labels
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('secret123');
await page.getByLabel('Remember me').check();
// Partial match
await page.getByLabel(/email/i).fill('test@example.com');Text and Placeholder
// Text content
await page.getByText('Welcome back').click();
await page.getByText(/welcome/i).isVisible();
// Placeholder
await page.getByPlaceholder('Enter email').fill('test@example.com');Test IDs (Fallback)
// When semantic locators aren't possible
await page.getByTestId('custom-widget').click();
// Configure test ID attribute
// playwright.config.ts
export default defineConfig({
use: {
testIdAttribute: 'data-test-id',
},
});Breaking Changes (1.58)
Removed Features
| Feature | Status | Migration |
|---|---|---|
_react selector | Removed | Use getByRole() or getByTestId() |
_vue selector | Removed | Use getByRole() or getByTestId() |
:light selector suffix | Removed | Use standard CSS selectors |
devtools launch option | Removed | Use args: ['--auto-open-devtools-for-tabs'] |
| macOS 13 WebKit | Removed | Upgrade to macOS 14+ |
Migration Examples
// React/Vue component selectors - Before
await page.locator('_react=MyComponent').click();
await page.locator('_vue=MyComponent').click();
// After - Use semantic locators or test IDs
await page.getByRole('button', { name: 'My Component' }).click();
await page.getByTestId('my-component').click();
// :light selector - Before
await page.locator('.card:light').click();
// After - Just use the selector directly
await page.locator('.card').click();
// DevTools option - Before
const browser = await chromium.launch({ devtools: true });
// After - Use args
const browser = await chromium.launch({
args: ['--auto-open-devtools-for-tabs']
});New Features (1.58+)
connectOverCDP with isLocal
// Optimized CDP connection for local debugging
const browser = await chromium.connectOverCDP({
endpointURL: 'http://localhost:9222',
isLocal: true // NEW: Optimizes for local connections
});
// Use for connecting to locally running Chrome instances
// Reduces latency and improves reliabilityTimeline in Speedboard HTML Reports
HTML reports now include an interactive timeline:
// playwright.config.ts
export default defineConfig({
reporter: [['html', { open: 'never' }]],
});
// The HTML report shows:
// - Test execution sequence
// - Parallel test distribution
// - Time spent in each test phase
// - Performance bottlenecksNew Assertions (1.57+)
// Assert individual class names (1.57+)
await expect(page.locator('.card')).toContainClass('highlighted');
await expect(page.locator('.card')).toContainClass(['active', 'visible']);
// Visibility
await expect(page.getByRole('button')).toBeVisible();
await expect(page.getByRole('button')).toBeHidden();
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByRole('button')).toBeDisabled();
// Text content
await expect(page.getByRole('heading')).toHaveText('Welcome');
await expect(page.getByRole('heading')).toContainText('Welcome');
// Attribute
await expect(page.getByRole('link')).toHaveAttribute('href', '/home');
// Count
await expect(page.getByRole('listitem')).toHaveCount(5);
// Screenshot
await expect(page).toHaveScreenshot('page.png');
await expect(page.locator('.hero')).toHaveScreenshot('hero.png');AI Agents (1.58+)
Initialize AI Agents
# Initialize agents for your preferred AI tool
npx playwright init-agents --loop=claude # For Claude Code
npx playwright init-agents --loop=vscode # For VS Code (requires v1.105+)
npx playwright init-agents --loop=opencode # For OpenCodeGenerated Structure
| Directory/File | Purpose |
|---|---|
.github/ | Agent definitions and configuration |
specs/ | Test plans in Markdown format |
tests/seed.spec.ts | Seed file for AI agents to reference |
Configuration
// playwright.config.ts
export default defineConfig({
use: {
aiAgents: {
enabled: true,
model: 'claude-3.5-sonnet', // or local Ollama
autoHeal: true, // Auto-repair on CI failures
}
}
});Authentication State
Storage State
// Save auth state
await page.context().storageState({ path: 'playwright/.auth/user.json' });
// Use saved state
const context = await browser.newContext({
storageState: 'playwright/.auth/user.json'
});IndexedDB Support (1.57+)
// Save storage state including IndexedDB
await page.context().storageState({
path: 'auth.json',
indexedDB: true // Include IndexedDB in storage state
});
// Restore with IndexedDB
const context = await browser.newContext({
storageState: 'auth.json' // Includes IndexedDB automatically
});Auth Setup Project
// playwright.config.ts
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'logged-in',
dependencies: ['setup'],
use: {
storageState: 'playwright/.auth/user.json',
},
},
],
});Flaky Test Detection (1.57+)
// playwright.config.ts
export default defineConfig({
// Fail CI if any flaky tests detected
failOnFlakyTests: true,
// Retry configuration
retries: process.env.CI ? 2 : 0,
// Web server with regex-based ready detection
webServer: {
command: 'npm run dev',
wait: /ready in \d+ms/, // Wait for this log pattern
},
});Visual Regression
test('visual regression', async ({ page }) => {
await page.goto('/');
// Full page screenshot
await expect(page).toHaveScreenshot('homepage.png');
// Element screenshot
await expect(page.locator('.hero')).toHaveScreenshot('hero.png');
// With options
await expect(page).toHaveScreenshot('page.png', {
maxDiffPixels: 100,
threshold: 0.2,
});
});Locator Descriptions (1.57+)
// Describe locators for trace viewer
const submitBtn = page.getByRole('button', { name: 'Submit' });
submitBtn.describe('Main form submit button');
// Shows in trace viewer for debuggingChrome for Testing (1.57+)
Playwright uses Chrome for Testing builds instead of Chromium:
# Install browsers (includes Chrome for Testing)
npx playwright install
# No code changes needed - better Chrome compatibilityExternal Links
Create page object: $ARGUMENTS
Page Object Context (Auto-Detected)
- Existing Page Objects: !
find tests/e2e/pages tests/__tests__ -name "*Page.ts" -o -name "*Page.tsx" 2>/dev/null | wc -l | tr -d ' ' || echo "0" - Test Directory: !
find . -type d \( -name "tests" -o -name "__tests__" -o -name "e2e" \) 2>/dev/null | head -1 || echo "tests/e2e" - Playwright Version: !
grep -r "@playwright/test" package.json 2>/dev/null | head -1 | grep -oE '@playwright/test[^"]*' || echo "Not detected" - Existing Patterns: !
grep -r "getByRole\|getByLabel" tests/e2e/pages 2>/dev/null | head -3 || echo "No existing patterns found"
Page Object Template
/**
* $ARGUMENTS Page Object
*
* Generated: !`date +%Y-%m-%d`
* Test Directory: !`find . -type d \( -name "tests" -o -name "e2e" \) 2>/dev/null | head -1 || echo "tests/e2e"`
*/
import { Page, Locator, expect } from '@playwright/test';
export class $ARGUMENTS {
// Locators
private readonly heading: Locator;
private readonly form: Locator;
constructor(private readonly page: Page) {
this.heading = page.getByRole('heading');
this.form = page.getByRole('form');
}
async goto() {
await this.page.goto('/$ARGUMENTS');
await this.waitForLoad();
}
async waitForLoad() {
await expect(this.heading).toBeVisible();
}
}Usage
1. Review detected patterns above 2. Save to: tests/e2e/pages/$ARGUMENTS.ts 3. Use in tests: const page = new $ARGUMENTS(page);
/**
* Page Object Template for Playwright
*
* Copy this template when creating new page objects.
* Replace placeholders with actual locators and methods.
*/
import { Page, Locator, expect } from '@playwright/test';
export class ExamplePage {
// ==========================================================================
// Locators
// ==========================================================================
private readonly heading: Locator;
private readonly form: Locator;
private readonly emailInput: Locator;
private readonly passwordInput: Locator;
private readonly submitButton: Locator;
private readonly errorAlert: Locator;
private readonly successAlert: Locator;
private readonly loadingSpinner: Locator;
// ==========================================================================
// Constructor
// ==========================================================================
constructor(private readonly page: Page) {
// Use semantic locators (getByRole, getByLabel) as primary strategy
this.heading = page.getByRole('heading', { name: 'Example Page' });
this.form = page.getByRole('form');
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Submit' });
this.errorAlert = page.getByRole('alert').filter({ hasText: 'Error' });
this.successAlert = page.getByRole('alert').filter({ hasText: 'Success' });
this.loadingSpinner = page.getByTestId('loading-spinner');
}
// ==========================================================================
// Navigation
// ==========================================================================
async goto() {
await this.page.goto('/example');
await this.waitForLoad();
}
async waitForLoad() {
await expect(this.heading).toBeVisible();
}
// ==========================================================================
// Actions
// ==========================================================================
async fillForm(data: { email: string; password: string }) {
await this.emailInput.fill(data.email);
await this.passwordInput.fill(data.password);
}
async submit() {
await this.submitButton.click();
}
async fillAndSubmit(data: { email: string; password: string }) {
await this.fillForm(data);
await this.submit();
}
// ==========================================================================
// Getters
// ==========================================================================
async getHeadingText(): Promise<string> {
return await this.heading.textContent() ?? '';
}
async getErrorMessage(): Promise<string> {
return await this.errorAlert.textContent() ?? '';
}
// ==========================================================================
// Assertions
// ==========================================================================
async expectLoaded() {
await expect(this.heading).toBeVisible();
await expect(this.form).toBeVisible();
}
async expectError(message: string) {
await expect(this.errorAlert).toBeVisible();
await expect(this.errorAlert).toContainText(message);
}
async expectSuccess(message?: string) {
await expect(this.successAlert).toBeVisible();
if (message) {
await expect(this.successAlert).toContainText(message);
}
}
async expectLoading() {
await expect(this.loadingSpinner).toBeVisible();
}
async expectNotLoading() {
await expect(this.loadingSpinner).not.toBeVisible();
}
async expectNavigatedTo(path: string) {
await expect(this.page).toHaveURL(new RegExp(path));
}
async expectFormEmpty() {
await expect(this.emailInput).toBeEmpty();
await expect(this.passwordInput).toBeEmpty();
}
// ==========================================================================
// Composite Actions
// ==========================================================================
async submitAndWaitForSuccess() {
await this.submit();
await this.expectSuccess();
}
async submitAndWaitForError(message: string) {
await this.submit();
await this.expectError(message);
}
}
// =============================================================================
// Usage Example
// =============================================================================
/*
import { test } from '@playwright/test';
import { ExamplePage } from '../pages/ExamplePage';
test('user can submit form', async ({ page }) => {
const examplePage = new ExamplePage(page);
await examplePage.goto();
await examplePage.fillAndSubmit({
email: 'test@example.com',
password: 'password123',
});
await examplePage.expectSuccess('Form submitted');
});
test('shows validation error', async ({ page }) => {
const examplePage = new ExamplePage(page);
await examplePage.goto();
await examplePage.fillAndSubmit({
email: 'invalid-email',
password: 'short',
});
await examplePage.expectError('Invalid email format');
});
*/