Software Engineering · Chapter 19 of 31
Testing tools: two races that are over
Why one runner won on architecture rather than features, why migration is smaller than teams assume, and the mobile tools that are actually worth the setup.
Two long-running tool debates in web testing resolved between 2024 and 2026, and in both cases the winner won on architecture rather than on feature lists — which is what makes them worth understanding rather than just memorising.
End-to-end: why the out-of-process design won
The download ratio is now roughly eleven to one in Playwright's favour, and reported satisfaction is around 91% against 72%. But the download count is the symptom. The cause is architectural:
Cypress runs your test code inside the browser's own event loop. That is an elegant design with three unavoidable consequences: multi-tab is hard, multi-origin is hard, and true parallelism requires external infrastructure.
Playwright drives the browser out-of-process over a debugging protocol. Multi-tab, multi-origin, multiple browser contexts and parallel workers all fall out of that choice for free.
Auto-waiting on actionability rather than arbitrary retry: before clicking, it checks that the element is visible, stable, enabled and actually receiving events. That single behaviour removes most of the flakiness people blame on end-to-end testing as a category.
Genuinely cross-browser — Chromium, Firefox and WebKit — plus Python, Java and .NET bindings, which is why quality-engineering organisations adopted it rather than just frontend teams.
And the commercial point that decided many migrations: parallelism and the trace viewer are free. In the competing tool, those were the paid product. Open-sourcing your competitor's revenue model is a very effective strategy.
Cypress is still genuinely better at interactive time-travel debugging and component-testing ergonomics for a single team that already knows it. But “we're on Cypress” is now a migration conversation rather than a defence.
Unit testing: the config-drift argument
Vitest now runs at roughly twice Jest's weekly downloads. Again the structural reasons matter more than the speed claims:
- Native ES modules and TypeScript with no configuration. The older runner's module support has been experimental for years and its transform layer is a persistent tax.
- It reuses your build configuration, so your test environment matches your build environment by construction. The older runner requires a parallel universe of module mappings and transforms that drifts from your real build — and that drift is the actual failure mode: tests that pass against a module resolution your production bundle does not use.
- Framework defaults moved. Angular replaced its long-standing runner with Vitest as the default in late 2025; several meta-frameworks ship or recommend it.
- Published speed multipliers — five times faster cold start, twenty-plus times on watch re-runs — are all secondary and none are reproducible. The direction is real; the multipliers are marketing.
- Jest still wins for legacy CommonJS codebases, very large monorepos with deep investment in its ecosystem, and React Native, whose bundler-shaped preset is built around it.
The migration is far smaller than teams assume
React Testing Library is runner-agnostic. The library, the user-event package and the DOM matchers all work identically under either runner, because both expose the same describe/it/expect shape and a DOM environment.
So the migration is mechanical: the mock and spy factory functions rename, and snapshot files are compatible.
“Should we move?” is therefore a much smaller question than the discussion it usually generates — and knowing that is worth more in an interview than a strong opinion about either tool.
And here is the point that outranks every tool comparison in this chapter: Testing Library's core doctrine — query by role, label or visible text, never by implementation detail — is what makes tests survive a refactor.
A team that migrated to the faster runner while querying everything by test-id has bought speed and kept brittleness. The runner was never the problem.
Mobile testing
| Tool | Scope | What it is actually for |
|---|---|---|
| XCUITest | iOS only | Apple-native, Xcode-integrated, free, and the most stable option on iOS. No cross-platform story at all |
| Espresso | Android only | Google-native and fast, because it synchronises with the UI thread rather than polling |
| Maestro | iOS, Android, RN, Flutter, web | YAML flows. Fastest setup and lowest flakiness of the cross-platform options — and cheap enough to write that you can throw suites away |
| Detox | React Native | Grey-box: synchronises with the JavaScript runtime instead of polling the UI, which is why it is less flaky than generic automation on RN. Small and shrinking relative to Maestro |
| Appium | Everything | Most setup, most maintenance, most coverage. Still the default when you must automate hybrid or WebView-heavy apps |
The practical shape: platform-native tools for the critical happy paths where flakiness is unacceptable; a cross-platform flow tool for smoke and regression, because the flow files are cheap to write and cheap to discard; the React-Native-specific tool only if you are React-Native-only and already invested; and generic automation when you have no other option.
On device farms: pricing ranges from tens of dollars a month for manual access to roughly seventeen cents per device-minute or a few hundred a month unmetered, depending on the provider. Use them for OS-version and manufacturer-fragmentation coverage, not for your main continuous-integration loop — the per-minute economics punish anyone running a full suite on real devices per commit. Emulators for every commit, real devices on a schedule and before release, is the shape that survives contact with a finance review.
Check yourself