PramodDutta/qaskills/seed-skills/e2e-testing-patterns/SKILL.md
E2E Testing Patterns
Comprehensive end-to-end testing methodologies and best practices covering architecture, test design, data management, flakiness prevention, and cross-browser strategies.
- Source repository stars
- 211
- Declared platforms
- 3
- Static risk flags
- 1
- Last source update
- 2026-08-28
- Source checked
- 2026-08-28
Decision brief
What it does: where it fits
You are an expert QA architect specializing in end-to-end testing patterns and methodologies. When the user asks you to design, review, or improve E2E testing strategies, follow these detailed instructions.
Not for
- Testing every edge case in E2E -- Use unit tests for edge cases.
- Relying on hardcoded waits -- sleep(5000) is a code smell.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Declared | Source record | Install path and trigger |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Declared | Source record | Install path and trigger |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/e2e-testing-patterns"Inspect the Agent Skill "E2E Testing Patterns" from https://github.com/PramodDutta/qaskills/blob/fb3fbec70591bad971dd97c5d9add6eaa99bae18/seed-skills/e2e-testing-patterns/SKILL.md at commit fb3fbec70591bad971dd97c5d9add6eaa99bae18. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
3. API-Based Data Setup
Review the “3. API-Based Data Setup” section in the pinned source before continuing.
Review and apply the “3. API-Based Data Setup” source section. - 02
Core Principles
1. Test user journeys, not implementation -- E2E tests should mirror real user behavior. 2. Fast feedback over exhaustive coverage -- Critical paths first, edge cases later. 3. Flakiness is a bug -- Unreliable tests are worse than no tests. 4. Isolate test data -- Each test shou…
Test user journeys, not implementation -- E2E tests should mirror real user behavior.Fast feedback over exhaustive coverage -- Critical paths first, edge cases later.Flakiness is a bug -- Unreliable tests are worse than no tests. - 03
Testing Pyramid and E2E Tests
E2E tests should focus on: - Happy path user journeys (login → purchase → checkout) - Critical business flows (payment processing, data submission) - Cross-browser compatibility on core features - Integration between major system components
Happy path user journeys (login → purchase → checkout)Critical business flows (payment processing, data submission)Cross-browser compatibility on core features - 04
Test Architecture Patterns
Pros: - Clear separation of concerns - Easy to maintain selectors in one place - Reusable across multiple tests
Clear separation of concernsEasy to maintain selectors in one placeReusable across multiple tests - 05
1. Page Object Model (POM)
Pros: - Clear separation of concerns - Easy to maintain selectors in one place - Reusable across multiple tests
Clear separation of concernsEasy to maintain selectors in one placeReusable across multiple tests
Permission review
Static risk signals and limitations
Network access
The documentation includes network, browsing, or remote request actions.
const response = await request.post('/api/users', {Evidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 91/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 211 | Source | Repository attention, not individual Skill quality |
| Compatibility | 3 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- PramodDutta/qaskills
- Skill path
- seed-skills/e2e-testing-patterns/SKILL.md
- Commit
- fb3fbec70591bad971dd97c5d9add6eaa99bae18
- License
- MIT
- Collected
- 2026-08-28
- Default branch
- main
View the original SKILL.md
E2E Testing Patterns Skill
You are an expert QA architect specializing in end-to-end testing patterns and methodologies. When the user asks you to design, review, or improve E2E testing strategies, follow these detailed instructions.
Core Principles
- Test user journeys, not implementation -- E2E tests should mirror real user behavior.
- Fast feedback over exhaustive coverage -- Critical paths first, edge cases later.
- Flakiness is a bug -- Unreliable tests are worse than no tests.
- Isolate test data -- Each test should create and clean up its own data.
- Test at the right level -- Not everything needs an E2E test.
Testing Pyramid and E2E Tests
/\
/ \ E2E Tests (10-20%)
/____\ - Critical user journeys
/ \ - High-value scenarios
/ \ - Smoke tests
/__________\ Integration Tests (20-30%)
/ \
/ \ Unit Tests (50-70%)
/________________\
E2E tests should focus on:
- Happy path user journeys (login → purchase → checkout)
- Critical business flows (payment processing, data submission)
- Cross-browser compatibility on core features
- Integration between major system components
E2E tests should NOT test:
- Edge cases better covered by unit tests
- Every permutation of form validation
- Internal implementation details
- Third-party service internals
Test Architecture Patterns
1. Page Object Model (POM)
Structure:
pages/
base.page.ts # Shared base functionality
login.page.ts # Login page actions and selectors
dashboard.page.ts # Dashboard page actions
components/
header.component.ts # Reusable header component
modal.component.ts # Reusable modal component
Implementation:
// base.page.ts
export abstract class BasePage {
constructor(protected page: Page) {}
async navigate(path: string): Promise<void> {
await this.page.goto(path);
}
async waitForLoad(): Promise<void> {
await this.page.waitForLoadState('networkidle');
}
async takeScreenshot(name: string): Promise<void> {
await this.page.screenshot({ path: `screenshots/${name}.png` });
}
}
// login.page.ts
export class LoginPage extends BasePage {
private readonly emailInput = this.page.getByLabel('Email');
private readonly passwordInput = this.page.getByLabel('Password');
private readonly submitButton = this.page.getByRole('button', { name: 'Sign in' });
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();
await this.waitForLoad();
}
async expectError(message: string): Promise<void> {
await expect(this.page.getByRole('alert')).toContainText(message);
}
}
Pros:
- Clear separation of concerns
- Easy to maintain selectors in one place
- Reusable across multiple tests
Cons:
- Can become bloated if not organized well
- May encourage creating methods for every tiny action
2. Screenplay Pattern (Actor-Task Model)
Structure:
// actors/user.actor.ts
export class User {
constructor(private page: Page) {}
async attemptsTo(...tasks: Task[]): Promise<void> {
for (const task of tasks) {
await task.perform(this.page);
}
}
async shouldSee(...assertions: Assertion[]): Promise<void> {
for (const assertion of assertions) {
await assertion.verify(this.page);
}
}
}
// tasks/login.task.ts
export class Login implements Task {
constructor(
private email: string,
private password: string
) {}
async perform(page: Page): Promise<void> {
await page.getByLabel('Email').fill(this.email);
await page.getByLabel('Password').fill(this.password);
await page.getByRole('button', { name: 'Sign in' }).click();
}
}
// Usage
test('user can login and view dashboard', async ({ page }) => {
const user = new User(page);
await user.attemptsTo(
new NavigateTo('/login'),
new Login('[email protected]', 'password123')
);
await user.shouldSee(
new PageTitle('Dashboard'),
new Element('welcome-message').isVisible()
);
});
Pros:
- Highly readable, business-focused tests
- Great for complex user journeys
- Easy to compose tasks
Cons:
- More upfront setup
- Can be overkill for simple apps
3. Journey-Based Testing
Organize tests by complete user journeys rather than by pages:
describe('Purchase Journey', () => {
test('guest user can complete full purchase flow', async ({ page }) => {
// Journey: Browse → Add to Cart → Checkout → Payment → Confirmation
// Step 1: Browse products
await page.goto('/products');
await page.getByRole('link', { name: 'Laptops' }).click();
// Step 2: Add to cart
const product = page.getByTestId('product-123');
await product.getByRole('button', { name: 'Add to Cart' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
// Step 3: Checkout
await page.getByRole('button', { name: 'Checkout' }).click();
await fillCheckoutForm(page, guestUserData);
// Step 4: Payment
await fillPaymentForm(page, testPaymentData);
await page.getByRole('button', { name: 'Place Order' }).click();
// Step 5: Confirmation
await expect(page.getByRole('heading', { name: 'Order Confirmed' })).toBeVisible();
const orderNumber = await page.getByTestId('order-number').textContent();
expect(orderNumber).toMatch(/^ORD-\d{6}$/);
});
});
Pros:
- Tests mirror real user behavior
- Easy to understand business value
- Catches integration issues
Cons:
- Longer test execution time
- Harder to debug when failures occur mid-journey
Test Data Management Patterns
1. Test Data Factory Pattern
// factories/user.factory.ts
export class UserFactory {
private static counter = 0;
static createUser(overrides: Partial<User> = {}): User {
const id = ++this.counter;
return {
id: `user-${id}`,
email: `testuser${id}@example.com`,
name: `Test User ${id}`,
role: 'user',
...overrides,
};
}
static createAdmin(): User {
return this.createUser({ role: 'admin' });
}
}
// Usage in tests
test('admin can delete users', async ({ page }) => {
const admin = UserFactory.createAdmin();
await loginAs(page, admin);
// ... rest of test
});
2. Database Seeding Strategy
// fixtures/db-seed.fixture.ts
export async function seedDatabase(): Promise<SeedData> {
const users = await db.users.createMany([
{ email: '[email protected]', name: 'User 1' },
{ email: '[email protected]', name: 'User 2' },
]);
const products = await db.products.createMany([
{ name: 'Product A', price: 29.99 },
{ name: 'Product B', price: 49.99 },
]);
return { users, products };
}
export async function cleanDatabase(): Promise<void> {
await db.orders.deleteMany();
await db.products.deleteMany();
await db.users.deleteMany();
}
// Use in test setup
test.beforeEach(async () => {
await cleanDatabase();
await seedDatabase();
});
3. API-Based Data Setup
// helpers/test-data.ts
export async function createUserViaAPI(userData: CreateUserDto): Promise<User> {
const response = await request.post('/api/users', {
data: userData,
});
return response.json();
}
test('user can update profile', async ({ page }) => {
// Setup: Create user via API (faster than UI)
const user = await createUserViaAPI({
email: '[email protected]',
password: 'password123',
});
// Test: Update profile via UI
await page.goto('/profile');
await page.getByLabel('Name').fill('Updated Name');
await page.getByRole('button', { name: 'Save' }).click();
// Assertion
await expect(page.getByText('Updated Name')).toBeVisible();
});
Handling Test Flakiness
1. Explicit Waits Over Implicit Waits
// ❌ BAD: Hardcoded wait
await page.waitForTimeout(5000);
// ✅ GOOD: Wait for specific condition
await page.waitForSelector('[data-testid="results"]');
await page.waitForLoadState('networkidle');
// ✅ BETTER: Use auto-waiting assertions
await expect(page.getByTestId('results')).toBeVisible();
2. Retry-able Assertions
// ✅ Automatically retries until condition is met (or timeout)
await expect(page.getByRole('alert')).toHaveText('Success', { timeout: 10000 });
// ✅ Wait for element count to stabilize
await expect(page.getByRole('listitem')).toHaveCount(5);
// ✅ Wait for element to be in the right state
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
3. Stabilizing Network Requests
// Wait for specific API call to complete
test('should load user data', async ({ page }) => {
const responsePromise = page.waitForResponse(
(response) => response.url().includes('/api/users') && response.status() === 200
);
await page.goto('/users');
await responsePromise;
await expect(page.getByRole('heading')).toContainText('Users');
});
4. Handling Race Conditions
// ❌ BAD: Assumes element exists immediately
await page.click('button');
await page.fill('input', 'text');
// ✅ GOOD: Wait for element before interaction
await page.waitForSelector('button');
await page.click('button');
await page.waitForSelector('input');
await page.fill('input', 'text');
// ✅ BETTER: Use built-in auto-waiting
await page.getByRole('button').click();
await page.getByRole('textbox').fill('text');
Cross-Browser Testing Strategies
1. Browser Matrix Configuration
// playwright.config.ts
export default defineConfig({
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 13'] },
},
],
});
2. Browser-Specific Test Skipping
test('should support advanced CSS features', async ({ page, browserName }) => {
test.skip(browserName === 'webkit', 'Safari does not support this CSS feature yet');
await page.goto('/advanced-styles');
// ... test advanced CSS behavior
});
3. Visual Regression Across Browsers
test('homepage renders consistently across browsers', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixels: 100, // Allow minor rendering differences
});
});
Test Organization Patterns
1. Feature-Based Organization
tests/
e2e/
auth/
login.spec.ts
signup.spec.ts
password-reset.spec.ts
shopping/
browse-products.spec.ts
cart-operations.spec.ts
checkout.spec.ts
admin/
user-management.spec.ts
analytics.spec.ts
2. Smoke, Regression, and Full Suites
// Tag tests by priority
test('user can login @smoke', async ({ page }) => {
// Critical path
});
test('user can reset password @regression', async ({ page }) => {
// Less critical, run in nightly builds
});
test('admin can export analytics @full', async ({ page }) => {
// Run only in full test suite
});
// Run subsets
// npx playwright test --grep @smoke
// npx playwright test --grep @regression
3. Parallel vs Serial Execution
// Run tests in parallel (default)
test.describe.configure({ mode: 'parallel' });
// Run tests serially when they share state
test.describe.configure({ mode: 'serial' });
test.describe('User onboarding flow', () => {
test.describe.configure({ mode: 'serial' });
test('step 1: create account', async ({ page }) => {
// ...
});
test('step 2: verify email', async ({ page }) => {
// ...
});
test('step 3: complete profile', async ({ page }) => {
// ...
});
});
Authentication and Session Management
1. Reusable Authentication State
// auth.setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('admin123');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.context().storageState({ path: 'playwright/.auth/user.json' });
});
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
2. Role-Based Authentication Fixtures
// fixtures/auth.fixture.ts
export const test = base.extend<{
authenticatedPage: Page;
adminPage: Page;
}>({
authenticatedPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'playwright/.auth/user.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
adminPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'playwright/.auth/admin.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
});
// Usage
test('admin can access admin panel', async ({ adminPage }) => {
await adminPage.goto('/admin');
await expect(adminPage.getByRole('heading')).toHaveText('Admin Dashboard');
});
Performance Testing Patterns
1. Measure Page Load Metrics
test('homepage loads within 3 seconds', async ({ page }) => {
const startTime = Date.now();
await page.goto('/');
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(3000);
});
2. Lighthouse Integration
import { playAudit } from 'playwright-lighthouse';
test('homepage meets performance standards', async ({ page }) => {
await page.goto('/');
await playAudit({
page,
thresholds: {
performance: 90,
accessibility: 95,
'best-practices': 90,
seo: 90,
},
});
});
Best Practices
- Keep tests independent -- Each test should run in isolation.
- Use realistic test data -- Avoid "test" or "foo" in production-like tests.
- Prioritize stability over speed -- Flaky fast tests are useless.
- Test critical paths first -- 80/20 rule: cover 80% of usage with 20% of tests.
- Use Page Object Model -- Centralize selectors and actions.
- Avoid sleep/wait timers -- Use explicit waits for conditions.
- Clean up test data -- Don't pollute the database or state.
- Run tests in CI/CD -- Automate on every commit or PR.
- Monitor test flakiness -- Track and fix unreliable tests immediately.
- Use visual regression wisely -- Critical UI only, not everything.
Anti-Patterns to Avoid
- Testing every edge case in E2E -- Use unit tests for edge cases.
- Relying on hardcoded waits --
sleep(5000)is a code smell. - Sharing state between tests -- Tests must be isolated.
- Testing third-party code -- Trust external libraries, test integration only.
- Overly complex Page Objects -- Keep them focused and simple.
- Testing implementation details -- Test user-visible behavior.
- Ignoring flaky tests -- Fix or delete, never skip indefinitely.
- Too many E2E tests -- Balance with faster unit/integration tests.
- Not using test reporters -- Visibility into failures is critical.
- Committing with .only or .skip -- Clean up before committing.
Test Reporting and Debugging
1. Rich HTML Reports
// playwright.config.ts
export default defineConfig({
reporter: [
['html', { open: 'never', outputFolder: 'test-results/html' }],
['json', { outputFile: 'test-results/results.json' }],
['junit', { outputFile: 'test-results/junit.xml' }],
],
});
2. Trace Viewer for Debugging
// Enable tracing on failure
export default defineConfig({
use: {
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
// View trace:
// npx playwright show-trace trace.zip
3. Custom Test Annotations
test('critical payment flow', async ({ page }) => {
test.info().annotations.push({ type: 'priority', description: 'critical' });
test.info().annotations.push({ type: 'ticket', description: 'JIRA-1234' });
// ... test implementation
});
Continuous Improvement
- Review test failures weekly -- Identify patterns and fix root causes.
- Track test execution time -- Optimize slow tests or split them.
- Monitor flakiness rates -- Set thresholds (e.g., < 1% flaky).
- Update tests with product changes -- Keep tests in sync with features.
- Refactor Page Objects -- Keep them DRY and maintainable.
E2E testing is an investment in confidence. Done well, it catches critical bugs before production. Done poorly, it wastes time and erodes trust in automation.
Frequently asked questions
What to verify before installation and use
What does the E2E Testing Patterns source document cover?
You are an expert QA architect specializing in end-to-end testing patterns and methodologies. When the user asks you to design, review, or improve E2E testing strategies, follow these detailed instructions.
How do I install E2E Testing Patterns?
The source record exposes this install command: npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/e2e-testing-patterns". Inspect the command and pinned source before running it.
Which Agent platforms does the source record declare?
The pinned source record declares support for: codex, claude code, cursor.
Which permission-related actions were detected?
Static rules flagged network in the source; the page lists the matching lines and excerpts.
Alternatives
Compare before choosing
upex-galaxy/agentic-qa-boilerplate
test-automation
Plan, write, and review automated tests following KATA (Komponent Action Test Architecture) on Playwright + TypeScript, or explain existing automated tests in a sealed read-only mode. Use when writing E2E or API/integration tests, creating Page or Api components, designing ATCs, parameterizing test data, registering fixtures, reviewing test code for KATA compliance, or requesting break-down-tests / a plain-English test breakdown. The explain mode reads source and reports assertions without enter
upex-galaxy/agentic-qa-boilerplate
test-documentation
Analyze, prioritize, and document test cases in TMS (Jira/Xray), or repair an existing Story-ATS-ATP-ATR-TC cascade through a sealed explicit mode. Use for Test/ATP/ATR artifacts, ROI and automation verdicts, maintaining traceability, fix-traceability, or broken TMS links. The repair-traceability mode audits, plans, waits for explicit approval, applies, and verifies without launching the general documentation workflow. Do NOT use for writing test code (test-automation) or running suites (regress
alirezarezvani/claude-skills
chaos-engineering
Use when planning, running, or learning from chaos engineering experiments. Triggers on "chaos experiment", "fault injection", "gameday", "resilience test", "blast radius", "steady state", "abort criteria", "Chaos Toolkit", "Chaos Mesh", "Litmus", "Gremlin", "AWS FIS", or any deliberate failure-injection question. Ships experiment designer, blast-radius calculator, and postmortem generator (all stdlib Python), 4 references on chaos principles + experiment design + attack taxonomy + tooling lands
PramodDutta/qaskills
Angry User Simulator
Simulate aggressive user behavior patterns including rapid clicking, random navigation, form abuse, tab spamming, and unexpected interaction sequences to find UI resilience issues