Frontend testing & observability
Frontend quality is not one test type. A reliable frontend combines static checks, focused component tests, realistic browser flows, accessibility coverage, performance signals, and production telemetry that catches what lab tests miss.
Core details
| Layer | Catches | Does not catch well |
|---|---|---|
| Type/static checks | contracts, impossible states, import mistakes | real browser behavior |
| Unit tests | pure logic, reducers, formatters, validators | integration and focus behavior |
| Component tests | rendering states, events, accessibility names | full routing/network/device issues |
| E2E tests | critical user journeys in a browser | every edge case; they are slower/flakier |
| Visual tests | layout regressions and design drift | hidden accessibility or data bugs |
| A11y tests | missing names, invalid roles, contrast baseline | nuanced keyboard/screen reader flows |
| Performance tests | bundle/trace regressions | field-only device/network variance |
| RUM/observability | real user errors and latency | exact local reproduction by itself |
Testing pyramid for UI: keep pure logic heavily unit-tested, component states covered with focused tests, and E2E reserved for high-value journeys: auth, checkout, create/edit/delete, navigation, and permission boundaries.
Observability basics: capture client errors, route transitions, failed resource loads, hydration warnings, web vitals, long interactions, API failure rates, and release version. Logs without release/user-agent/route context are hard to use.
Error boundaries: design fallbacks by product boundary. A broken chart should not blank the whole dashboard; a broken authenticated shell may need a route-level recovery path.
Understanding
Frontend failures are often stateful and environmental. Browser extensions, locale, timezone, network retries, mobile CPU, old cached assets, and hydration order can expose bugs that unit tests never see.
The goal is not maximum test count. It is fast feedback for cheap mistakes and targeted confidence for expensive workflows. Every flaky E2E test should justify its cost by protecting a journey that matters.
Observability closes the loop. A release can pass tests and still regress low-end devices, a specific locale, or a cache transition. Field data tells you where to reproduce and which users were harmed.
Practical examples
Test matrix by feature:
| Feature | Minimum coverage |
|---|---|
| Form | validation unit tests, component error/focus tests, one submit E2E |
| Dialog/combobox | component keyboard tests, accessibility scan, screen-reader spot check |
| Route data | loader/action tests where possible, E2E for auth and stale state |
| Heavy dashboard | bundle budget, lab trace, RUM web vitals |
| Payment/security flow | E2E happy path, failure path, duplicate-submit/idempotency test |
Telemetry payload shape:
type ClientEvent = {
release: string;
route: string;
userAgent: string;
event: "error" | "web_vital" | "hydration_warning" | "api_failure";
severity: "info" | "warn" | "error";
};Include enough context to group and bisect, but avoid sensitive payloads.
Senior understanding
| Probe | Strong answer |
|---|---|
| “Why flaky E2E?” | Identify timing, network, test data, animation, and selector instability |
| “A11y automated enough?” | No; combine automated scans with keyboard and AT walkthroughs |
| “Performance in CI?” | Use stable budgets for bundles; traces only where reproducible |
| “Production error?” | group by release/route/browser, feature flag rollback, user impact |
Failure modes
- Testing implementation details instead of user-visible behavior.
- Using brittle selectors instead of roles, labels, and stable test IDs where appropriate.
- Ignoring hydration warnings because the page “looks fine.”
- Capturing client logs without release version or route.
- Running only desktop Chrome tests for mobile-heavy products.
- Treating visual snapshots as accessibility coverage.
Diagram
See also
Spotted something unclear or wrong on this page?