Merancang E2E testing strategy: best practices, test isolation, mengurangi flaky tests, dan balance antara coverage dan maintenance

Setelah di episode 8 kita mempelajari API testing, pada episode ini kita kembali ke E2E testing dengan sudut pandang strategi — bagaimana merancang E2E suite yang efektif tanpa terjebak maintenance burden.
// Setiap test harus independent
test('user can login', async ({ page }) => {
// Setup: buat data yang dibutuhkan
await page.goto('/register');
// Register user baru...
// Test: login
await page.goto('/login');
await page.fill('#email', 'newuser@test.com');
await page.fill('#password', 'password123');
await page.click('#login-button');
// Assert
await expect(page).toHaveURL('/dashboard');
// Cleanup: hapus data (atau pakai database reset)
});// fixtures/testData.ts
export const testUsers = {
valid: { name: 'Test User', email: 'test@test.com', password: 'password123' },
invalid: { name: '', email: 'not-email', password: '1' },
admin: { name: 'Admin', email: 'admin@test.com', password: 'admin123' },
};
// Gunakan di tests
test('login', async ({ page }) => {
await loginPage.login(testUsers.valid.email, testUsers.valid.password);
});Flaky Test Causes & Fixes:
├── Timing issues → Gunakan Playwright auto-wait, bukan sleep
├── Network issues → Mock API responses
├── State leakage → Test isolation, database reset
├── Random data → Use fixtures, seed data
├── Browser issues → Stable selectors (getByRole)
└── Parallel conflicts → Separate test data per workerE2E Focus Areas (Critical Paths):
├── Authentication: login, register, logout
├── Core business: checkout, payment, booking
├── Data integrity: CRUD operations
├── Error handling: 404, 500, validation errors
└── Cross-browser: Chrome, Firefox, Safari (critical flows)// Page-level: test individual page functionality
test('products page loads', async ({ page }) => {
await page.goto('/products');
await expect(page.locator('.product')).toHaveCount(10);
});
// Flow-level: test complete user journey
test('complete purchase flow', async ({ page }) => {
// Browse → Add to cart → Checkout → Payment → Confirmation
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
await page.getByRole('link', { name: 'Cart' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
await page.fill('#card-number', '4242424242424242');
await page.getByRole('button', { name: 'Pay' }).click();
await expect(page.getByText('Order Confirmed')).toBeVisible();
});Tip
E2E tests harus fokus pada critical paths — jangan coba cover semua dengan E2E. Gunakan unit & integration tests untuk detail.
test.describe('Critical Path: Purchase', () => {
test('complete purchase flow', async ({ page }) => {
// Login
await page.goto('/login');
await page.fill('#email', 'user@test.com');
await page.fill('#password', 'password123');
await page.click('#login-button');
// Browse & add to cart
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
// Checkout
await page.getByRole('link', { name: 'Cart' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
// Payment
await page.fill('#card-number', '4242424242424242');
await page.getByRole('button', { name: 'Pay' }).click();
// Confirmation
await expect(page.getByText('Order Confirmed')).toBeVisible();
});
});Di episode 10 selanjutnya kita akan membahas test data & fixtures — factories, data management, dan strategies untuk test data yang reliable. Sampai jumpa di episode 10!