Frontend Testing Strategy: 3 Tests You Need, 4 You Don't

2026-04-16 · Nico Brandt

You installed Vitest and Playwright. You have 200 components and zero strategy. The testing pyramid says write 70% unit tests, the testing trophy says focus on integration, and every article you find compares tools instead of telling you which tests to actually write.

This is the decision framework — not another tool comparison. Three tests that earn their keep, four that don’t, and the two-tool stack that covers everything in between.

The Two-Tool Stack (and Why You Don’t Need Anything Else)

Vitest handles unit and integration tests. It’s 2–4x faster than Jest, has native ESM support, and runs in milliseconds. Playwright handles E2E — real Chromium, Firefox, and WebKit with built-in auto-waiting and trace viewer. Two tools. No Jest, no Cypress, no separate component testing framework.

The missing piece is MSW — Mock Service Worker. It intercepts HTTP requests at the network boundary, so your components run through real fetch calls while you control what the API returns. No more mocking axios at the function level. Your test exercises the actual code path. MSW is the difference between “this component renders” and “this component works.”

Map it to Kent C. Dodds’ Testing Trophy: static analysis (TypeScript and ESLint — already running), unit tests (Vitest), integration tests (Vitest + MSW), E2E tests (Playwright). Four layers, two tools, one runner you probably already have in your Vite config.

One shift worth noting: the classic 70/20/10 testing pyramid ratio is outdated. AI-generated code passes unit tests but breaks at integration boundaries — teams are shifting toward 40% unit, 35% integration, 25% E2E. Weight your effort where the bugs actually hide.

That covers the stack. But knowing you need Vitest and Playwright doesn’t answer the harder question: given a specific thing you need to test, which one do you reach for?

The Decision Framework: Which Tool, Which Test

Here’s the tree. No hedging.

Pure logic with no DOM and no API calls → Vitest unit test. Date formatting, price calculation, validation functions, state reducers. Import the function, call it, assert the output. These run in under 10ms and catch logic bugs before they touch a component.

Component rendering with props → Vitest unit test with Testing Library (or Vue Test Utils, or Svelte Testing Library). Does the button disable when loading={true}? Does the error message appear when error is truthy? No network, no routing — just props in, DOM out.

Component that fetches data or talks to an API → Vitest integration test with MSW. This is your highest-value test. MSW intercepts at the service worker level — your component fires a real fetch, MSW returns your controlled response. You verify the component handles it correctly: loading states, error states, rendered data. You’re testing the actual integration, not a hand-waved mock.

Multi-step flow across pages → Playwright E2E test. Login → dashboard → settings. Checkout flow. Onboarding wizard. If the failure only surfaces when a user navigates between real pages, that’s Playwright territory.

Anything that depends on real browser layout → Playwright. CSS-dependent behavior, responsive breakpoints, scroll interactions, viewport-specific rendering.

Quick reference:

Scenario Tool Test Type
Utility function Vitest Unit
Component with props Vitest + Testing Library Unit
Component + API call Vitest + MSW Integration
Form submission Vitest + MSW Integration
Login → dashboard flow Playwright E2E
Checkout across pages Playwright E2E
Responsive layout check Playwright E2E
Accessibility audit Vitest Browser Mode + axe-core Integration

Three test types, two tools — that’s the frontend testing strategy. But there’s a second half to this equation, and it’s the part every other guide skips.

Tests to Stop Writing

More tests ≠ better testing. These four cost CI minutes and maintenance hours while providing near-zero confidence that your app actually works.

1. Snapshot tests on dynamic components. They break on every change. Developers learn to auto-update them without reading the diff. If your snapshot is 200 lines of serialized HTML, nobody is reviewing that. It’s a rubber stamp, not a test.

2. Unit tests on framework bindings. Testing that React calls useEffect or Vue triggers onMounted is testing the framework, not your code. React’s team already tested React. Test the logic inside the lifecycle hook — the fetch call, the calculation, the state transition — not the hook itself.

3. E2E tests for simple CRUD. If your form POSTs to an API and renders a success message, a Vitest + MSW integration test catches the same bugs in 50ms instead of 5 seconds. Reserve Playwright for flows that span multiple pages or depend on real browser behavior.

4. Tests that mock everything. If you mock the API, mock the router, mock the store, and mock the DOM, you’re testing your mocks. You’ve proven your fake world is internally consistent. Congratulations. Production won’t care.

The filter I use: confidence per minute. Does this test catch a bug that would actually reach production, and is the CI runtime worth it? If not, delete it. A lean test suite that runs in 30 seconds beats a sprawling one that runs for 10 minutes and catches the same bugs. Your future code reviewer will appreciate the difference.

Speaking of overlapping tools — Vitest has Browser Mode that runs in real browsers, and Playwright also runs in real browsers. When do you use which?

Vitest Browser Mode vs Playwright: The Overlap Nobody Explains

Both run in real browsers. Both can render components. The confusion is legitimate — and in 2026, with Vitest Browser Mode now stable, the overlap is bigger than ever.

Vitest Browser Mode is for component-level tests that need real DOM behavior. CSS calculations, focus management, intersection observers, accessibility checks with axe-core. Think of it as a unit or integration test that happens to run in a real browser instead of jsdom. Fast. Isolated. One component at a time.

Playwright is for user-level flows that span pages, involve navigation, require network conditions, or test the app as a deployed whole. It’s the “user sits down and does a thing” test.

Rule of thumb: if you’re importing a component and rendering it → Vitest Browser Mode. If you’re visiting a URL and clicking through pages → Playwright.

Playwright has experimental component testing too. But Vitest Browser Mode is the more mature path for component-in-browser testing, and it keeps your runner unified — one vitest command runs unit, integration, and browser mode tests together.

That settles what to test, what to skip, and which tool handles each job. One question left: how does this actually run in your project?

Project Structure and CI Pipeline

Convention that makes the boundary obvious:

src/
  components/
    Button.tsx
    Button.test.ts        ← Vitest (unit/integration)
  utils/
    format.ts
    format.test.ts        ← Vitest (unit)
e2e/
  checkout.spec.ts        ← Playwright
  login.spec.ts           ← Playwright

Colocate *.test.ts next to source for Vitest. Top-level e2e/ directory for Playwright *.spec.ts files. The naming convention makes it instantly clear which runner owns which file — no ambiguity, no configuration gymnastics.

CI pipeline runs at two speeds. Vitest on every push — it finishes in seconds and gives you immediate feedback on logic and integration bugs. Playwright on PR merge to main — it takes minutes but catches flow-level regressions before deploy. Cache node_modules and Playwright browser binaries between runs, and you’ll cut CI time by half.

That’s roughly fifteen lines of GitHub Actions YAML. The official Vitest and Playwright docs have complete CI examples — no need to reinvent them here. The point is the two-speed pattern: fast feedback on every commit, thorough validation on merge.

The Bottom Line

You asked which tests go where. Three rules: pure logic → Vitest. Components with APIs → Vitest + MSW. Critical user flows → Playwright. Everything else gets the confidence-per-minute filter, and the four test types that fail it — snapshots, framework binding tests, CRUD E2E, mock-everything suites — get deleted.

The real advantage of a strong frontend testing strategy isn’t writing more tests. It’s writing fewer tests that catch more bugs. Measure confidence per minute of CI runtime. Delete what doesn’t earn its keep.

Your test suite should feel like a tight code review — every assertion has a reason to exist, and nothing is there just for coverage numbers. That’s the strategy. Go configure.