Testing: Jest, Playwright, Cypress, k6, Lighthouse
A bug found in a unit test costs a minute to fix. The same bug in production — hours of incident, client compensation, loss of trust. This is not abstract thesis: on an e-commerce project a bug in discount calculation passed manual testing, made it to production and processed 37 orders at zero price in 4 hours. An auto-test for edge cases of the calculation would sit in CI and catch it on the first push.
Jest and Unit Testing: Where It Works and Where It Doesn't
Jest is the standard for JavaScript/TypeScript. But unit tests make sense only where there's isolated logic: data transformation functions, validators, business rules, utilities.
Testing React components via Jest + Testing Library correctly for behavioral tests: "button appears after loading data", "form shows error on empty email". Snapshot tests (toMatchSnapshot) are a trap: they break on any markup change and become noise that developers update without looking.
Code coverage is a bad quality metric. 80% coverage can be achieved with tests that check nothing. Coverage shows code executed, not that it works correctly.
Vitest as alternative to Jest for Vite projects: 10–20x faster due to native ES modules without Babel transformation. For monorepos with thousands of tests the speed difference is noticeable.
Playwright: E2E Testing That Works
Cypress was the standard for E2E for a long time, but Playwright surpassed it on key metrics: native multi-tab, multi-origin, iframe support; parallel execution at test level (not just files); WebKit, Firefox, Chromium out of the box; no iframe for application — tests work in real browser.
Playwright codegen records actions and generates tests — good starting point, but generated code needs refactoring. Locators by text content are fragile: getByRole('button', { name: 'Checkout' }) is more stable than locator('.btn-primary').
Page Object Model is standard for E2E tests organization. Each page is a separate class with methods instead of direct locators in tests. When button moved from header to sidebar — change in one place, not search through all tests.
Typical problem — flaky tests. Test sometimes fails, sometimes passes. Reasons: race condition between request and render, animations without waiting for completion, dependence on external API. Solution: page.waitForResponse() instead of page.waitForTimeout(), mocking external API via page.route().
// Bad
await page.click('#submit');
await page.waitForTimeout(2000);
await expect(page.locator('.success')).toBeVisible();
// Good
await page.click('#submit');
await page.waitForResponse(resp =>
resp.url().includes('/api/orders') && resp.status() === 201
);
await expect(page.getByRole('alert', { name: /order created/i })).toBeVisible();
Load Testing with k6
k6 is an instrument for load testing with JavaScript API. Scenarios are written as code, versioned in git, run in CI.
Three main load scenarios:
Spike test — sudden load increase: 0 → 1000 users in 30 seconds. Imitates ad campaign launch or news top placement. Shows how system behaves under sudden load and whether autoscaling responds in time.
Soak test — stable load for 2–4 hours. Reveals memory leaks, connection pool exhaustion, performance degradation over time.
Stress test — load above expected (150–200% of peak). Shows failure point and graceful degradation.
Threshold values in k6:
thresholds: {
http_req_duration: ['p95<500', 'p99<1000'],
http_req_failed: ['rate<0.01'],
}
p95 < 500ms means: 95% of requests respond faster than 500ms. If threshold fails — k6 exits with error code, CI pipeline falls.
Lighthouse and Core Web Vitals
Google uses Core Web Vitals in ranking. Lighthouse CLI in CI pipeline: on every deployment check that LCP < 2.5s, CLS < 0.1, INP < 200ms.
Real problems Lighthouse finds:
- Hero image without
width/heightattributes: CLS 0.35 on load (browser shifts content when image loads) - 2.1MB JavaScript bundle blocks parsing synchronously: INP 450ms
- Fonts without
font-display: swap: invisible text until font loads (FOIT) - Unoptimized hero image 4MB: LCP 8.2s
Lighthouse CI (lhci) saves metrics history and sends PR comment with degradation.
Testing Pyramid in Project
| Level | Tool | Amount | Speed |
|---|---|---|---|
| Unit | Vitest/Jest | Many | <5 min |
| Integration | Vitest + supertest | Moderate | 5–15 min |
| E2E | Playwright | Few (happy path) | 10–30 min |
| Load | k6 | On schedule | 30–60 min |
| Performance | Lighthouse CI | On every deploy | 5 min |
Work Process
Audit of current coverage, determining critical user flows for E2E, setting up CI pipeline with parallel test execution. Playwright tests run on sharded workers (4 shards = 4x speed). Load tests are separate stage, don't block feature deployments, run before releases.
Timeline
Full test pipeline setup (Jest + Playwright + k6 + Lighthouse CI) from scratch: 2–4 weeks. E2E test coverage of existing project (20–30 scenarios): 3–6 weeks. Load testing with report and recommendations: 1–2 weeks.







