Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
infrastructure-ci-cd/performance.md
1# Performance & Parallelization23## Table of Contents451. [Parallel Execution](#parallel-execution)62. [Sharding](#sharding)73. [Test Optimization](#test-optimization)84. [Network Optimization](#network-optimization)95. [Isolation and Parallel Execution](#isolation-and-parallel-execution)106. [Resource Management](#resource-management)117. [Benchmarking](#benchmarking)1213## Parallel Execution1415### Configuration1617```typescript18// playwright.config.ts19export default defineConfig({20// Run test files in parallel21fullyParallel: true,2223// Number of worker processes24workers: process.env.CI ? 1 : undefined, // undefined = half CPU cores2526// Or explicit count27// workers: 4,28// workers: '50%', // Percentage of CPU cores29});30```3132### Serial Execution When Needed3334```typescript35// Entire file serial36test.describe.configure({ mode: "serial" });3738test.describe("Sequential Tests", () => {39test("first", async ({ page }) => {40// Runs first41});4243test("second", async ({ page }) => {44// Runs after first45});46});47```4849```typescript50// Single describe block serial51test.describe("Parallel Tests", () => {52test("a", async () => {}); // Parallel53test("b", async () => {}); // Parallel54});5556test.describe.serial("Serial Tests", () => {57test("c", async () => {}); // Serial58test("d", async () => {}); // Serial59});60```6162### Parallel Projects6364```typescript65// playwright.config.ts66export default defineConfig({67projects: [68{ name: "chromium", use: { ...devices["Desktop Chrome"] } },69{ name: "firefox", use: { ...devices["Desktop Firefox"] } },70{ name: "webkit", use: { ...devices["Desktop Safari"] } },71],72});73```7475```bash76# Run all projects in parallel77npx playwright test7879# Run specific project80npx playwright test --project=chromium81```8283## Sharding8485### Basic Sharding8687```bash88# Split tests across 4 machines89# Machine 1:90npx playwright test --shard=1/49192# Machine 2:93npx playwright test --shard=2/49495# Machine 3:96npx playwright test --shard=3/49798# Machine 4:99npx playwright test --shard=4/4100```101102### Sharding Strategy103104Tests are distributed evenly by file. For optimal sharding:105106- Keep test files similar in size107- Use `fullyParallel: true` for even distribution108- Balance slow tests across files109110### CI Sharding Pattern111112```yaml113# GitHub Actions114jobs:115test:116strategy:117matrix:118shard: [1, 2, 3, 4]119steps:120- run: npx playwright test --shard=${{ matrix.shard }}/4121```122123> **For comprehensive CI sharding** (blob reports, merging sharded results, full workflows), see [ci-cd.md](ci-cd.md#sharding).124125## Test Optimization126127### Reuse Authentication128129Avoid logging in for every test. Use setup projects with storage state to authenticate once and reuse the session.130131> **For authentication patterns** (storage state, multiple auth states, setup projects), see [fixtures-hooks.md](fixtures-hooks.md#authentication-patterns).132133### Reuse Page State (serial only — trade-off with isolation)134135Sharing a single page/context across tests with `beforeAll`/`afterAll` is **not recommended** for most suites: it breaks test isolation, causes state leak between tests, and makes failures harder to debug. Prefer a fresh `page` per test (Playwright default). Use shared page only when you explicitly need serial execution and accept no isolation.136137```typescript138// ⚠️ Serial only, no isolation: state from one test leaks into the next.139// Prefer test.describe.configure({ mode: 'serial' }) + fresh page per test, or beforeEach + page.goto().140test.describe.configure({ mode: "serial" });141test.describe("Dashboard", () => {142let page: Page;143144test.beforeAll(async ({ browser }) => {145const context = await browser.newContext({146storageState: ".auth/user.json",147});148page = await context.newPage();149await page.goto("/dashboard");150});151152test.afterAll(async () => {153await page?.close();154});155156test("shows stats", async () => {157await expect(page.getByTestId("stats")).toBeVisible();158});159160test("shows chart", async () => {161await expect(page.getByTestId("chart")).toBeVisible();162});163});164```165166### Lazy Navigation167168```typescript169// Bad: Navigate in every test170test("check header", async ({ page }) => {171await page.goto("/products");172await expect(page.getByRole("heading")).toBeVisible();173});174175test("check footer", async ({ page }) => {176await page.goto("/products");177await expect(page.getByRole("contentinfo")).toBeVisible();178});179180// Good: Share navigation181test.describe("Products Page", () => {182test.beforeEach(async ({ page }) => {183await page.goto("/products");184});185186test("check header", async ({ page }) => {187await expect(page.getByRole("heading")).toBeVisible();188});189190test("check footer", async ({ page }) => {191await expect(page.getByRole("contentinfo")).toBeVisible();192});193});194```195196### Skip Unnecessary Setup197198```typescript199// Use test.skip for conditional execution200test("admin feature", async ({ page }) => {201test.skip(!process.env.ADMIN_ENABLED, "Admin features disabled");202// ...203});204205// Use test.fixme for known broken tests206test.fixme("broken feature", async ({ page }) => {207// Skipped but tracked208});209```210211## Network Optimization212213### Mock APIs214215```typescript216test.beforeEach(async ({ page }) => {217// Mock slow/heavy endpoints218await page.route("**/api/analytics", (route) =>219route.fulfill({ json: { views: 1000 } }),220);221222await page.route("**/api/recommendations", (route) =>223route.fulfill({ json: [] }),224);225});226```227228### Block Unnecessary Resources229230```typescript231test.beforeEach(async ({ page }) => {232// Block analytics, ads, tracking233await page.route("**/*", (route) => {234const url = route.request().url();235if (236url.includes("google-analytics") ||237url.includes("facebook") ||238url.includes("hotjar")239) {240return route.abort();241}242return route.continue();243});244});245```246247### Block Resource Types248249```typescript250// Block images and fonts for faster tests251await page.route("**/*", (route) => {252const resourceType = route.request().resourceType();253if (["image", "font", "stylesheet"].includes(resourceType)) {254return route.abort();255}256return route.continue();257});258```259260### Cache API Responses261262```typescript263const apiCache = new Map<string, object>();264265test.beforeEach(async ({ page }) => {266await page.route("**/api/**", async (route) => {267const url = route.request().url();268269if (apiCache.has(url)) {270return route.fulfill({ json: apiCache.get(url) });271}272273const response = await route.fetch();274const json = await response.json();275apiCache.set(url, json);276return route.fulfill({ json });277});278});279```280281## Isolation and Parallel Execution282283### Default: one context per test284285Playwright gives each test its own browser context (and page). That gives isolation: no shared cookies, storage, or DOM between tests, so failures don’t carry over and you can run tests in any order or in parallel. Keep this default unless you have a clear reason to share state.286287### Avoiding state leak in parallel runs288289- **Do not** rely on shared mutable state (e.g. a single `page` or `context` in `beforeAll`) when tests can run in parallel. State from one test can leak into another and cause flaky, order-dependent failures.290- Use **fixtures** for setup/teardown and **`beforeEach`** for per-test navigation so each test gets a fresh page or a clean slate.291- For **backend or DB state** shared across tests, isolate per worker so parallel workers don’t collide. Use a worker-scoped fixture and `testInfo.workerIndex` (or `process.env.TEST_WORKER_INDEX`) to create unique data per worker (e.g. unique user or DB prefix). See [fixtures-hooks.md](../core/fixtures-hooks.md) for worker-scoped fixtures and [debugging.md](../debugging/debugging.md) for debugging flaky parallel runs.292293### Debugging flaky parallel runs294295If a test is flaky only with multiple workers:2962971. **Reproduce**: Run with default workers and `--repeat-each=10` (or `--repeat-each=100 --max-failures=1`).2982. **Confirm parallel-specific**: Run with `--workers=1`. If the failure disappears, the cause is likely shared state or non-isolated backend/DB data.2993. **Fix**: Remove shared page/context; use per-test fixtures and `beforeEach`; isolate test data per worker with `workerIndex` in a worker-scoped fixture.300301Workers are restarted after a test failure so subsequent tests in that worker get a clean environment; fixing isolation still prevents the initial flakiness.302303## Resource Management304305### Browser Contexts306307```typescript308// Recommended: One context per test (default) — full isolation309test("isolated test", async ({ page }) => {310// Fresh context automatically311});312313// Manual context for specific needs314test("multiple tabs", async ({ browser }) => {315const context = await browser.newContext();316const page1 = await context.newPage();317const page2 = await context.newPage();318319// Clean up320await context.close();321});322```323324### Memory Management325326```typescript327// playwright.config.ts328export default defineConfig({329// Limit concurrent workers330workers: 2,331332// Limit parallel tests per worker333use: {334// Lower memory usage335launchOptions: {336args: ["--disable-dev-shm-usage"],337},338},339});340```341342### Timeouts343344```typescript345// playwright.config.ts346export default defineConfig({347// Global test timeout348timeout: 30000,349350// Assertion timeout351expect: {352timeout: 5000,353},354355// Navigation timeout356use: {357navigationTimeout: 15000,358actionTimeout: 10000,359},360});361```362363## Benchmarking364365### Measure Test Duration366367```typescript368test("performance test", async ({ page }, testInfo) => {369const startTime = Date.now();370371await page.goto("/");372373const loadTime = Date.now() - startTime;374console.log(`Page load: ${loadTime}ms`);375376// Add to test report377testInfo.annotations.push({378type: "performance",379description: `Load time: ${loadTime}ms`,380});381});382```383384### Performance Metrics385386```typescript387test("collect metrics", async ({ page }) => {388await page.goto("/");389390const metrics = await page.evaluate(() => ({391// Navigation timing392loadTime:393performance.timing.loadEventEnd - performance.timing.navigationStart,394domContentLoaded:395performance.timing.domContentLoadedEventEnd -396performance.timing.navigationStart,397398// Performance entries399resources: performance.getEntriesByType("resource").length,400401// Memory (Chrome only)402// @ts-ignore403memory: performance.memory?.usedJSHeapSize,404}));405406console.log("Metrics:", metrics);407expect(metrics.loadTime).toBeLessThan(3000);408});409```410411### Lighthouse Integration412413```typescript414import { playAudit } from "playwright-lighthouse";415416test("lighthouse audit", async ({ page }) => {417await page.goto("/");418419const audit = await playAudit({420page,421thresholds: {422performance: 80,423accessibility: 90,424"best-practices": 80,425seo: 80,426},427port: 9222,428});429430expect(audit.lhr.categories.performance.score * 100).toBeGreaterThanOrEqual(43180,432);433});434```435436## Performance Checklist437438| Optimization | Impact |439| ------------------------------ | ---------- |440| Enable `fullyParallel` | High |441| Reuse authentication | High |442| Mock heavy APIs | High |443| Block tracking scripts | Medium |444| Use sharding in CI | High |445| Reduce workers if memory-bound | Medium |446| Cache API responses | Medium |447| Skip unnecessary tests | Low-Medium |448449## Related References450451- **CI/CD sharding**: See [ci-cd.md](ci-cd.md) for CI configuration452- **Test organization**: See [test-suite-structure.md](../core/test-suite-structure.md) for structuring tests453- **Fixtures for reuse**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for authentication patterns454