e2e-testing
SKILL.md
E2E Testing Patterns
Build reliable, fast, and maintainable end-to-end test suites with Playwright and Cypress.
What to Test with E2E
Good for:
- Critical user journeys (login, checkout, signup)
- Complex interactions (drag-and-drop, multi-step forms)
- Cross-browser compatibility
- Real API integration
Not for:
- Unit-level logic (use unit tests)
- API contracts (use integration tests)
- Edge cases (too slow)
Playwright Configuration
// playwright.config.ts
export default defineConfig({
testDir: './e2e',
timeout: 30000,
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
],
});
Page Object Model
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly loginButton: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.loginButton = page.getByRole('button', { name: 'Login' });
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.page.getByLabel('Password').fill(password);
await this.loginButton.click();
}
}
Waiting Strategies
// Bad: Fixed timeouts
await page.waitForTimeout(3000); // Flaky!
// Good: Wait for conditions
await expect(page.getByText('Welcome')).toBeVisible();
await page.waitForURL('/dashboard');
// Wait for API response
const responsePromise = page.waitForResponse(
r => r.url().includes('/api/users') && r.status() === 200
);
await page.click('button');
await responsePromise;
Network Mocking
test('displays error when API fails', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server Error' }),
});
});
await page.goto('/users');
await expect(page.getByText('Failed to load')).toBeVisible();
});
Visual Regression
test('homepage looks correct', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixels: 100,
});
});
Accessibility Testing
import AxeBuilder from '@axe-core/playwright';
test('no accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Best Practices
- Use Data Attributes:
data-testidfor stable selectors - Test User Behavior: Click, type, see - not implementation
- Keep Tests Independent: Each test runs in isolation
- Clean Up Test Data: Create and destroy per test
- Use Page Objects: Encapsulate page logic
- Optimize for Speed: Mock when possible, parallel execution
Bad vs Good Selectors
// Bad
cy.get('.btn.btn-primary.submit-button').click();
cy.get('div > form > div:nth-child(2) > input').type('text');
// Good
cy.getByRole('button', { name: 'Submit' }).click();
cy.get('[data-testid="email-input"]').type('user@example.com');
Debugging
# Headed mode
npx playwright test --headed
# Debug mode (step through)
npx playwright test --debug
# Trace viewer
npx playwright show-trace trace.zip
Weekly Installs
32
Repository
eyadsibai/ltkFirst Seen
Jan 28, 2026
Security Audits
Installed on
gemini-cli26
opencode24
github-copilot23
codex23
antigravity21
claude-code21