Skip to content

Modern Front-End Testing Tools and Techniques

Which front-end testing tools actually earn their place, which techniques belong at each layer, and how to combine them without turning CI into a tax on shipping.

Share
Abstract visualisation of layered test suites running in parallel

Most advice about front-end testing is really advice about tool selection, which is the least interesting part of the problem. The tools are good now. Playwright, Cypress, Vitest and Jest are all mature, well-documented, and actively maintained, and you can build a perfectly respectable suite on any of them.

What actually goes wrong is more structural. A team writes hundreds of tests, CI takes twenty-five minutes, everyone starts merging on a green tick they no longer read carefully, and bugs reach users anyway because the suite was testing the wrong layer the whole time. That failure has nothing to do with which runner was installed.

So this is less a tools roundup than an argument about where testing effort actually pays off, with tool recommendations tied to specific jobs rather than presented as a leaderboard.

The bugs that actually reach users

It is worth being precise about what escapes into production, because it is rarely what unit tests are looking for.

Logic errors in isolated functions are the easiest class of bug to catch and the least common cause of a support ticket. They are also, for obvious reasons, the class most teams over-invest in, because they are cheap and pleasant to test.

The bugs that reach real people are mostly environmental. A tap target that is fine at 1440px and unusable at 375px. A modal that traps focus correctly in Chrome and does not in iOS Safari. A layout that reflows the instant a mobile keyboard opens. A form that works perfectly until the network is slow enough for someone to double-tap submit.

None of those is logic failures, and none of them will be caught by a suite that runs in jsdom on a developer's machine. If your tests pass and users still hit bugs, the missing coverage is rarely in unit tests. It is at the end-to-end, visual or mobile-viewport level.

That is the single most useful lens for the rest of this, so keep it in mind when weighing where to spend the effort.

End-to-end: Playwright, and when Cypress is still the better call

Playwright has become the sensible default for end-to-end testing on new projects, and the reasons are architectural rather than fashionable.

It drives the browser out-of-process rather than executing inside it, which gives it genuine multi-tab, multi-origin, and multi-context support. Anything involving an OAuth redirect, a payment iframe or a second browser tab tends to be awkward on an in-browser architecture and straightforward here. It also ships real WebKit coverage, which matters more than teams generally admit. If a meaningful share of your users are on iOS, WebKit testing is the difference between knowing your app works there and assuming it.

The practical advantage is built-in, free parallel execution across workers. On a suite of a few hundred tests, that is often the difference between a twenty-five-minute CI run and one under ten, and CI duration is not a vanity metric. Past roughly fifteen minutes, people stop waiting for results before context-switching, and the feedback loop the suite exists to provide quietly stops working.

Reach for Playwright when you need cross-browser or mobile-viewport coverage, and when CI speed is a real constraint.

Cypress is not obsolete, and the migration pressure it gets is mostly unwarranted. Its time-travel debugging and interactive runner remain the best developer experience in the category, and its component testing story is more settled than Playwright's, where component testing has been experimental for a long stretch. If your app is single-origin, your team leans on visual debugging while authoring tests, and component tests are a large part of your suite, Cypress is a completely defensible choice.

Reach for Cypress when debugging experience and component testing maturity matter more than multi-browser coverage or raw CI throughput.

The honest summary: this is not a case where one tool is better. It is a case where the two optimise for different bottlenecks, and you should know which bottleneck is yours before switching.

Unit and component: Vitest, Jest, and the migration question

Vitest is the strongest option for new projects, particularly on Vite, where it shares the same transform pipeline and configuration rather than maintaining a parallel one. Native ESM support and a watch mode that re-runs only tests affected by a given change make the inner loop meaningfully faster, and the speed difference is largest where it matters: the incremental run you perform fifty times an afternoon.

Jest remains reliable, thoroughly documented and surrounded by a mature ecosystem. It ships assertions, mocking, snapshots, and coverage out of the box, and a working Jest suite isn't a problem to solve.

On migration, the reasonable test is whether the wait is changing behaviour. If your unit suite is slow enough that people have stopped running it locally and rely on CI to tell them, that is a real cost and worth fixing. If it runs in a few seconds and nobody thinks about it, rewriting hundreds of test files to chase a newer runner is among the lowest-value work on any front-end backlog.

Worth noting separately: whichever runner you use, the assertions matter more than the runner. Testing Library's core discipline, querying by accessible role and label rather than by test id or class name, is the thing that stops component tests from breaking on every refactor while failing to catch actual regressions. That habit outlasts any tool choice.

The rest of the field

Puppeteer is still a sensible, lightweight choice for genuinely Chrome-specific automation: screenshot generation, PDF rendering, scraping, and scripted flows where cross-browser coverage is not the point. For cross-browser testing, Playwright has effectively absorbed the niche.

Selenium is the right call in narrower circumstances than it once was. Still, those circumstances are real: large inherited suites, teams needing language bindings beyond the JavaScript and Python ecosystems, and organisations with existing Grid infrastructure that is not worth dismantling. For a new project with a free choice, the developer experience gap is large enough that it is hard to recommend.

Tools compared

Tool Best for Browsers Languages Parallel execution
Playwright End-to-end, cross-browser Chromium, Firefox, WebKit JS, TS, Python, Java, C# Built in, free
Cypress End-to-end, component, debugging Chromium, Firefox, WebKit (limited) JS, TS Paid dashboard for full parallelism
Vitest Unit and component Node, jsdom, browser mode JS, TS Built in
Jest Unit and component Node, jsdom JS, TS Built in
Puppeteer Chrome automation, screenshots Chromium, Chrome JS, TS Manual setup
Selenium Legacy and polyglot suites All major browsers Many Via Grid

Techniques, and which layer each one belongs to

Knowing which technique answers which question is worth more than any tool decision.

Unit testing

Unit tests check individual functions and components in isolation. They are fast, cheap and precise, and they should carry your pure logic: formatters, reducers, validation, state machines, anything where the input and output are data rather than pixels.

The failure mode here is over-extension. Once a unit test needs three layers of mocks to run, it stops testing your code and starts testing your mocking configuration. That is the signal to move the case up a level rather than to build a more elaborate harness.

Integration testing

Integration tests verify that pieces work together, which is where most genuine defects live. In front-end terms, this usually means rendering a real component tree with a real router and store, and stubbing only the network boundary.

Mock Service Worker makes this pleasant because it intercepts at the network level rather than replacing your fetch client. The same handlers then serve your unit tests, your end-to-end tests and local development, which removes an entire category of "the mock and the real API have drifted" bugs.

Visual regression testing

Visual regression testing compares rendered screenshots across versions to catch CSS changes that looked fine locally but broke something three components away. Playwright's built-in screenshot comparison is now good enough to try before adding a separate service, though Percy and BackstopJS remain solid if you want a hosted review workflow.

One point deserves emphasis, and it connects directly to how modern layouts are built. If your components use container queries, they respond to the width of their container rather than the width of the window, so resizing the viewport in a test no longer exercises their responsive behaviour. A card can render correctly in a 1440px window and break at 280px container width, and no amount of viewport-based testing will surface it. Test components at multiple container widths, in isolation, or that whole class of regression ships silently.

Accessibility testing

Accessibility is the technique most often deferred and most expensive to retrofit. Automated tooling will not catch everything, but it reliably catches the cheap and common failures: missing labels, insufficient contrast, unlabelled controls, incorrect heading order.

The important decision is placement. Run axe-core inside the end-to-end suite you already have rather than as a separate audit somebody runs quarterly, because a check that lives in its own ritual eventually stops happening. Both Playwright and Cypress integrate with it directly.

Keyboard traps and focus management still need a human, and a five-minute pass through a new flow using only the keyboard catches more than any scanner.

Performance testing

Lighthouse and WebPageTest remain the standards, and Lighthouse CI can hold a budget in your pipeline so regressions fail a build rather than being discovered in a quarterly review.

The critical part is the device profile. A feature can be functionally perfect and still too slow to be worth using on a mid-range Android phone on a congested connection, which is a far more representative environment than a developer machine on office fibre. Test on throttled CPU and network by default, not as an occasional exercise.

Building a stack that stays fast

Most teams end up combining tools by layer rather than standardising on one:

  • Unit and component: Vitest for new projects, Jest for established codebases
  • Integration: the same runner, with MSW at the network boundary
  • End-to-end: Playwright for cross-browser and CI speed, Cypress where debugging and component testing dominate
  • Visual regression: Playwright's built-in comparison, or Percy or BackstopJS for a hosted review flow
  • Accessibility: axe-core, inside the end-to-end suite
  • Performance: Lighthouse CI with an enforced budget

Two habits keep that from degrading.

Split the pipeline by speed. Run unit and integration tests on every push, the full cross-browser and visual suite on pull requests, and the exhaustive matrix nightly. A suite that runs in full on every commit will eventually be cut down, and it will be cut down carelessly under deadline pressure rather than deliberately.

Treat flaky tests as failures, not noise. A suite with a handful of tests that fail intermittently trains the whole team to re-run rather than investigate, and once that reflex exists, the suite has lost its authority regardless of coverage. Quarantine flaky tests immediately and fix or delete them, because the cost of an unreliable signal is higher than the cost of no signal at all.

What good looks like

Coverage percentage is a weak proxy for anything. The useful measures are behavioural.

A failing suite should reliably mean something is broken, and a passing suite should mean it is safe to deploy. If either of those is untrue, the number attached to your coverage report is irrelevant.

People should still be running tests locally. If they have stopped, the suite is too slow, and that is an engineering problem, not a discipline problem.

And bugs that reach production should be teaching you something. Every escaped defect tells you which layer your suite is missing, and adding a regression test at the right level is worth more than any amount of speculative coverage added upfront.

The goal was never to use the newest tool. It is to ship front-end code that works for the people using it, catch the failures before they do, and keep the feedback loop fast enough that testing feels like help rather than a tax on shipping.

Front-end testing FAQ

What is front-end testing?
Front-end testing verifies the presentation, usability and behaviour of a web application's interface. It spans unit tests on individual components, integration and end-to-end tests across full user flows, and visual, accessibility and performance checks on the rendered result.

Which front-end testing tool should I use?
For new projects, Playwright for end-to-end and Vitest for unit and component testing is the strongest default pairing. Existing Cypress and Jest suites remain perfectly good and are rarely worth migrating on their own.

Is Playwright better than Cypress?
They optimise for different things. Playwright wins on cross-browser coverage, multi-origin support and free parallel execution. Cypress wins on interactive debugging and component testing maturity. Which is better depends on whether your bottleneck is CI speed or developer feedback.

Should I migrate from Jest to Vitest?
Only if you are already on Vite, or if the suite is slow enough that people have stopped running it locally. A stable Jest setup is not a problem that needs solving.

How much testing is enough?
Enough that a green build is trustworthy and a red one is informative. If tests pass and users still hit bugs, the gap is usually at the end-to-end, visual or mobile-viewport level rather than in unit coverage.