Comprehensive end-to-end testing methodologies and best practices covering architecture, test design, data management, flakiness prevention, and cross-browser strategies.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Comprehensive end-to-end testing methodologies and best practices covering architecture, test design, data management, flakiness prevention, and cross-browser strategies.
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.
// helpers/test-data.tsexportasyncfunctioncreateUserViaAPI(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 = awaitcreateUserViaAPI({
email: 'test@example.com',
password: 'password123',
});
// Test: Update profile via UIawait page.goto('/profile');
await page.getByLabel('Name').fill('Updated Name');
await page.getByRole('button', { name: 'Save' }).click();
// Assertionawaitexpect(page.getByText('Updated Name')).toBeVisible();
});
Handling Test Flakiness
1. Explicit Waits Over Implicit Waits
// ❌ BAD: Hardcoded waitawait page.waitForTimeout(5000);
// ✅ GOOD: Wait for specific conditionawait page.waitForSelector('[data-testid="results"]');
await page.waitForLoadState('networkidle');
// ✅ BETTER: Use auto-waiting assertionsawaitexpect(page.getByTestId('results')).toBeVisible();
2. Retry-able Assertions
// ✅ Automatically retries until condition is met (or timeout)awaitexpect(page.getByRole('alert')).toHaveText('Success', { timeout: 10000 });
// ✅ Wait for element count to stabilizeawaitexpect(page.getByRole('listitem')).toHaveCount(5);
// ✅ Wait for element to be in the right stateawaitexpect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
3. Stabilizing Network Requests
// Wait for specific API call to completetest('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;
awaitexpect(page.getByRole('heading')).toContainText('Users');
});
4. Handling Race Conditions
// ❌ BAD: Assumes element exists immediatelyawait page.click('button');
await page.fill('input', 'text');
// ✅ GOOD: Wait for element before interactionawait page.waitForSelector('button');
await page.click('button');
await page.waitForSelector('input');
await page.fill('input', 'text');
// ✅ BETTER: Use built-in auto-waitingawait page.getByRole('button').click();
await page.getByRole('textbox').fill('text');
// Tag tests by prioritytest('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
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.