home / skills / eyadsibai / ltk / e2e-testing
This skill helps you design and maintain robust end-to-end tests with Playwright or Cypress, improving reliability and debugging flaky journeys.
npx playbooks add skill eyadsibai/ltk --skill e2e-testingReview the files below or copy the command above to add this skill to your agents.
---
name: e2e-testing
description: Use when implementing end-to-end tests, using Playwright or Cypress, testing user journeys, debugging flaky tests, or asking about "E2E testing", "Playwright", "Cypress", "browser testing", "visual regression", "test automation"
version: 1.0.0
---
# 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
```typescript
// 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
```typescript
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
```typescript
// 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
```typescript
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
```typescript
test('homepage looks correct', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixels: 100,
});
});
```
## Accessibility Testing
```typescript
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
1. **Use Data Attributes**: `data-testid` for stable selectors
2. **Test User Behavior**: Click, type, see - not implementation
3. **Keep Tests Independent**: Each test runs in isolation
4. **Clean Up Test Data**: Create and destroy per test
5. **Use Page Objects**: Encapsulate page logic
6. **Optimize for Speed**: Mock when possible, parallel execution
## Bad vs Good Selectors
```typescript
// 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('[email protected]');
```
## Debugging
```bash
# Headed mode
npx playwright test --headed
# Debug mode (step through)
npx playwright test --debug
# Trace viewer
npx playwright show-trace trace.zip
```
This skill helps teams design and maintain reliable end-to-end test suites using Playwright or Cypress. It focuses on testing critical user journeys, reducing flakiness, and improving test performance through practical patterns and configuration. The guidance covers selectors, page objects, waiting strategies, network mocking, visual regression, and accessibility checks.
I inspect your test goals and recommend where E2E fits versus unit or integration tests, then provide concrete examples and configuration snippets for Playwright and Cypress. Guidance includes creating stable selectors (data attributes/roles), encapsulating page logic with Page Objects, using conditional waits instead of fixed timeouts, and mocking network responses to speed and stabilize tests. I also explain running headed/debug modes, traces, and visual regression checks.
Should I test everything with E2E?
No. Reserve E2E for critical user journeys and complex interactions. Use unit tests for business logic and integration tests for API contracts.
How do I reduce flaky tests?
Replace fixed timeouts with explicit waits for visibility, URL changes, or network responses. Use stable selectors and isolate tests with clean test data and mocks.