088
LVL 04 — SENIOR-IN-TRAININGSESSION 088DAY 88

E2E TESTING

🎫 PIXELCRAFT-074
Feature | 🟠 Hard | Priority: 🔴 Critical

Unit and integration tests pass — but the login form still breaks in Chrome because a CSS change hid the submit button. E2E tests simulate real users in real browsers. Register → login → upload → filter → export. If a user can't do it, the test fails.
CONCEPTS.UNLOCKED
🎭
Playwright
Browser automation from Microsoft. Controls Chromium, Firefox, and WebKit. Faster and more reliable than Selenium or Cypress. Auto-waits for elements. Runs headless in CI.
🔄
End-to-End Testing
Test the entire application as a user would. Real browser, real clicks, real network requests. The most expensive test — but the highest confidence that the full system works.
👤
User Flow Testing
Test complete journeys, not individual pages. Register → login → upload → edit → export. Each step depends on the previous. If any step breaks, the flow fails.
📸
Visual Regression Testing
Screenshot comparison catches UI changes. Take a screenshot after each test. Compare against the baseline. If pixels differ, flag the change for review — accidental or intentional?
🔧
Test Fixtures & Seeding
Start every test with a known state. Seed the database with test users and images before tests run. Clean up after. Tests must be independent — order should never matter.
🚀
CI Integration
Tests run automatically on every push. GitHub Actions runs Playwright in headless mode. If tests fail, the PR is blocked. No broken code reaches production.
HANDS-ON.TASKS
01
Set Up Playwright
npm init playwright@latest // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: './e2e', timeout: 30000, retries: 2, use: { baseURL: 'http://localhost:3000', screenshot: 'only-on-failure', trace: 'on-first-retry', }, webServer: { command: 'npm run dev', port: 3000, reuseExistingServer: !process.env.CI, }, projects: [ { name: 'chromium', use: { browserName: 'chromium' } }, { name: 'firefox', use: { browserName: 'firefox' } }, { name: 'webkit', use: { browserName: 'webkit' } }, ], });
02
Test Fixture: Seed & Cleanup
// e2e/fixtures.ts import { test as base } from '@playwright/test'; const TEST_USER = { email: 'e2e@test.com', password: 'TestPass123', name: 'E2E User', }; export const test = base.extend({ // Auto-register & login before test authenticatedPage: async ( { page }, use ) => { // Register (ignore if exists) await page.request.post( '/api/v1/auth/register', { data: TEST_USER }); // Login const res = await page.request.post( '/api/v1/auth/login', { data: { email: TEST_USER.email, password: TEST_USER.password, }}); const { token } = await res.json(); // Set auth cookie/header await page.addInitScript( (t) => { localStorage.setItem('token', t); }, token); await page.goto('/'); await use(page); }, });
Fixtures run before each test that uses them. authenticatedPage gives you a logged-in browser — no need to repeat login in every test.
03
E2E: Full Upload Flow
// e2e/upload-flow.spec.ts import { test } from './fixtures'; import { expect } from '@playwright/test'; import path from 'path'; test('upload → filter → export', async ({ authenticatedPage: page }) => { // Step 1: Upload image const fileInput = page.locator('input[type="file"]'); await fileInput.setInputFiles( path.join(__dirname, 'fixtures/test-image.jpg')); // Wait for upload to complete await expect( page.locator('.editor-canvas')) .toBeVisible({ timeout: 10000 }); // Step 2: Apply sepia filter await page.click( '[data-filter="sepia"]'); await expect( page.locator('[data-filter="sepia"]')) .toHaveClass(/active/); // Step 3: Export const [download] = await Promise.all([ page.waitForEvent('download'), page.click('button:has-text("Export")'), ]); // Step 4: Verify download const filename = download.suggestedFilename(); expect(filename).toMatch(/\.webp$/); const filePath = await download.path(); expect(filePath).toBeTruthy(); });
04
E2E: Registration & Login
// e2e/auth.spec.ts import { test, expect } from '@playwright/test'; test('register → login → see dashboard', async ({ page }) => { const unique = Date.now(); // Register await page.goto('/register'); await page.fill('[name="name"]', 'New User'); await page.fill('[name="email"]', `user${unique}@test.com`); await page.fill('[name="password"]', 'SecurePass1'); await page.click( 'button:has-text("Register")'); // Should redirect to dashboard await expect(page) .toHaveURL('/dashboard'); await expect( page.locator('h1')) .toContainText('Welcome'); }); test('shows validation errors', async ({ page }) => { await page.goto('/register'); await page.fill('[name="email"]', 'bad-email'); await page.fill('[name="password"]', '123'); await page.click( 'button:has-text("Register")'); await expect( page.locator('.error')) .toContainText('Invalid email'); });
05
Visual Regression & CI
// Visual regression test test('editor looks correct', async ({ authenticatedPage: page }) => { await page.goto('/editor'); await expect(page).toHaveScreenshot( 'editor-default.png', { maxDiffPixelRatio: 0.01 }); }); // .github/workflows/e2e.yml name: E2E Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - run: npx playwright install - run: npx playwright test - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/
06
Close the Ticket
git switch -c feature/PIXELCRAFT-074-e2e-tests git add e2e/ playwright.config.ts .github/ git commit -m "Add Playwright E2E tests + CI (PIXELCRAFT-074)" git push origin feature/PIXELCRAFT-074-e2e-tests # PR → Review → Merge → Close ticket ✅
CS.DEEP-DIVE

E2E tests answer the only question that matters: can the user do their job?

Unit tests verify code. Integration tests verify systems. E2E tests verify the product.

// Testing cost vs confidence:

Unit tests
  Cost: $  Speed: ⚡⚡⚡
  Confidence: "this function works"

Integration tests
  Cost: $$ Speed: ⚡⚡
  Confidence: "these systems connect"

E2E tests
  Cost: $$$ Speed: ⚡
  Confidence: "the user can do X"

// The tradeoff:
// E2E tests are slow and flaky.
// But they catch the bugs that
// unit tests never will:
// - CSS hiding buttons
// - Form not submitting
// - Auth redirect loop
// Test the critical paths. Not all.
"E2E Lab"
[A]Add Playwright's trace viewer: on failure, save a trace file. Open with npx playwright show-trace. You'll see every action, screenshot, network request, and console log — a full replay of the failure.
[B]Add accessibility testing: install @axe-core/playwright. After each page navigation, run an accessibility audit. Fail the test if any A/AA violations are found. Accessibility is testable.
[C]Research: why are E2E tests "flaky"? What causes intermittent failures? Explore strategies: retry logic, explicit waits vs implicit waits, test isolation, and deterministic test data. Write a "de-flaking guide."
REF.MATERIAL
ARTICLE
Microsoft
Official Playwright guide: locators, assertions, auto-waiting, fixtures, visual comparisons, and CI integration. The modern E2E framework.
PLAYWRIGHTOFFICIALESSENTIAL
VIDEO
Fireship
Quick Playwright overview: setup, writing tests, assertions, debugging, and why it's replacing Cypress as the E2E standard.
PLAYWRIGHTQUICK
ARTICLE
Microsoft
Writing reliable E2E tests: use locators not selectors, prefer user-facing attributes, handle async properly, and avoid flakiness.
PLAYWRIGHTBEST PRACTICES
ARTICLE
Martin Fowler
The original test pyramid concept: why you want many unit tests, some service tests, and few UI tests. The economics of testing.
TESTINGTHEORYESSENTIAL
VIDEO
LambdaTest
Comprehensive Playwright tutorial: setup, page objects, fixtures, parallel execution, CI pipelines, and visual regression testing.
PLAYWRIGHTTUTORIAL
// LEAVE EXCITED BECAUSE
Push code → CI runs Playwright → a real browser registers, logs in, uploads an image, applies a filter, and exports. If anything breaks, the PR is blocked. No broken features reach production.