E2E Testing Overview
End-to-end tests drive a real browser to cover flows that cross components, routes, and HTTP.
E2E vs Unit Tests
| Feature | Unit Tests | E2E Tests |
|---|---|---|
| Scope | Individual units | Full user journeys |
| Speed | Fast | Slower |
| Browser | Headless (Karma) | Real browser |
| Network | Mocked | Real or mocked |
| Confidence | Low (isolated) | High (integration) |
| Cost | Cheap | Expensive |
E2E Test Frameworks
| Framework | Status |
|---|---|
| Playwright | Recommended — modern, fast, reliable |
| Cypress | Popular — great developer experience |
| Protractor | Deprecated — do not use for new projects |
Playwright Setup
npm install @playwright/test --save-dev
npx playwright install
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
use: {
baseURL: 'http://localhost:4200',
headless: true,
},
});
Basic E2E Test
// e2e/app.spec.ts
import { test, expect } from '@playwright/test';
test('should display the home page', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toHaveText('Welcome');
});
test('should navigate to about page', async ({ page }) => {
await page.goto('/');
await page.click('a[href="/about"]');
await expect(page.locator('h1')).toHaveText('About');
});
E2E Testing Patterns
E2E tests should use stable selectors and cover critical user flows.
Using data-testid
Stable data-testid attributes make selectors resilient to styling and copy changes:
<!-- Component template -->
<button data-testid="submit-button">Submit</button>
<input data-testid="email-input" type="email">
// E2E test
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.click('[data-testid="submit-button"]');
Testing a Complete Flow
test('user can create a todo', async ({ page }) => {
await page.goto('/todos');
// Fill the form
await page.fill('[data-testid="todo-input"]', 'Learn E2E testing');
await page.click('[data-testid="add-todo-button"]');
// Verify the todo appears
await expect(
page.locator('[data-testid="todo-item"]')
).toContainText('Learn E2E testing');
// Mark as done
await page.click('[data-testid="todo-checkbox"]');
await expect(
page.locator('[data-testid="todo-item"]')
).toHaveClass(/completed/);
});
Running E2E in CI
# Run e2e tests
npx playwright test
# Run with UI mode (debugging)
px playwright test --ui
# Run in headed mode
npx playwright test --headed
# Generate HTML report
npx playwright show-report
E2E Best Practices
- Use data-testid — Resilient to CSS and text changes
- Test critical flows — Login, checkout, form submission
- Mock external APIs — Use Playwright's route interception
- Keep tests independent — Each test sets up its own state
- Run in CI — Catch regressions before they reach production
Quiz
1. What is the difference between unit tests and e2e tests?
2. Which e2e framework is recommended for Angular?
3. Why use data-testid attributes in e2e tests?
Flashcards
Question
Unit vs e2e tests?
Click to reveal answer
Answer
Unit: isolated units. E2E: full user journeys in real browser.
Question
Recommended e2e framework?
Click to reveal answer
Answer
Playwright — modern, fast, reliable. Protractor is deprecated.
Question
Why use data-testid?
Click to reveal answer
Answer
Stable selectors resilient to styling and copy changes.
Revision Notes
Key Takeaways
- 1. E2E tests cover full user journeys in a real browser
- 2. Playwright is the recommended framework
- 3. data-testid provides stable selectors
- 4. Run e2e in CI for regression detection
Interview Tips
- • Explain the difference between unit and e2e tests
- • Be ready to describe Playwright setup
- • Mention data-testid for resilient selectors
Cheat Sheet
E2E Testing Cheat Sheet
Framework: Playwright (recommended)
Setup:
npm install @playwright/test --save-dev
npx playwright install
Basic test:
test('should work', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toHaveText('Welcome');
});
Best practices:
- Use data-testid for stable selectors
- Test critical user flows
- Mock external APIs
- Run in CI