Voltar para engenharia

ADR-006

Testing strategy — Vitest, Playwright and Lighthouse CI

Adopt a three-layer testing strategy — component tests with Vitest + Testing Library, E2E journeys with Playwright, and Lighthouse CI for performance and accessibility gates.

testingqualitycilighthouseplaywrightvitest

Status

Aceito

Publicado

27 de mai. de 2026

Context

#

Phase 1 retired the Pages Router and 9 legacy dependencies. With the architecture simplified to a single App Router tree, the project has zero component tests, zero E2E coverage and no automated Lighthouse gates. The existing CI pipeline has 7 steps — lint, typecheck, unit test (2 files / 7 assertions), build, and two bundle budget checks.

A portfolio is both a product (recruiter / hiring-manager experience) and a demonstration of engineering craft. Missing test coverage and no measurable accessibility or performance contracts undermine both purposes.

Decision

#

Add three quality layers in sequence:

### Layer 1 — Lighthouse CI (Phase 2a)

Run Lighthouse against the production build (`npm run build && npm run start`) in a dedicated CI job that gates on `quality` passing.

**Configuration file:** `.lighthouserc.js`

**URL audited:** `/v2` (home — heaviest bundle, WebGL background, largest reputational risk)

**Thresholds:**

| Category | Assertion | Min score | Rationale | |----------------|-----------|-----------|-----------| | Performance | warn | 0.55 | Measured baseline 0.60–0.63; WebGL shader compilation is the dominant cost (655 ms long task on 4× throttled CPU); see Performance baseline note below | | Accessibility | error | 0.95 | EcoVoz (a11y project) is the flagship case study | | SEO | error | 0.95 | `generateMetadata` + OG images are a portfolio signal | | Best Practices | error | 0.90 | Localhost in CI caps HTTPS score at ~0.96; 0.90 avoids false failures |

**Performance baseline note (measured 2026-05-27):**

Locally measured score: **0.60–0.63** (2 runs, Lighthouse 4× CPU throttle).

Root-cause analysis of the two main drivers:

1. **TBT 980–1180 ms** — `@react-three/fiber` (~1085–1310 ms JS execution) + WebGL context initialization (unattributable 655 ms long task). Even with Three.js fully deferred via `dynamic({ ssr: false })` at the shell level, this long task runs during the Lighthouse measurement window. It is inherent to having a WebGL background.

2. **LCP 4.5 s** (on throttled CPU) — The hero `<h1>` has `.jr-reveal` CSS class which sets `opacity: 0` as its initial state. Chrome's LCP algorithm does not count opacity-zero elements as candidates; the first *visible* element becomes LCP. On throttled hardware where JS blocking prevents paint for ~4 s, the small header brand link (`102×20 px`) becomes the LCP element at 4.5 s. In real production (unthrottled), LCP is estimated 1–2 s. This will be fixed in **Phase 3** when Framer Motion replaces `jr-reveal` with animations that start from `opacity: 1`.

The threshold of 0.55 catches genuine regressions (a new heavy synchronous import would push the score below 0.50) without flagging the baseline WebGL cost on every push. **Raise to 0.70 after Phase 3** once the opacity:0 LCP issue is resolved.

**Server ready detection:** `startServerReadyPattern: "Ready in"` — matches Next.js 15's `✓ Ready in Xms` output, stable across patch versions.

### Layer 2 — Component Tests with Vitest + Testing Library (Phase 2b)

Extend the existing Vitest setup with `@testing-library/react`, `@testing-library/user-event`, and `happy-dom`.

Target files: - `src/components/__tests__/Hero.test.tsx` - `src/components/__tests__/ProjectCard.test.tsx` - `src/components/__tests__/CommandPalette.test.tsx` - `src/features/v2/__tests__/shell-header-nav.test.tsx` - `src/i18n/__tests__/routing.test.ts` (expand existing coverage)

Target count: ≥ 40 component test assertions (from current 7).

### Layer 3 — E2E Journeys with Playwright (Phase 2c)

Install `@playwright/test` and `@axe-core/playwright`. Eight critical journey specs:

| Spec | What it guards | |------|---------------| | `navigation.spec.ts` | Home → Projetos → EcoVoz case study → back | | `locale-switching.spec.ts` | URL prefix changes, content updates | | `command-palette.spec.ts` | Ctrl+K opens, search navigates, Escape closes | | `theme-toggle.spec.ts` | Theme persists across routes via cookie | | `contact-links.spec.ts` | External links have correct href and target | | `accessibility.spec.ts` | axe-core on `/v2`, `/v2/projetos`, `/v2/projetos/ecovoz` | | `sitemap.spec.ts` | `/robots.txt` and `/sitemap.xml` return 200 | | `og-image.spec.ts` | `/api/og?title=test` returns 200 with image content-type |

Consequences

#
  • CI grows from 7 steps to 11 (+ Lighthouse job, + component test expansion, + Playwright job).
  • Lighthouse Performance is warn-only, intentionally — this is reviewed rather than blocking. If WebGL is later removed or lazy-loaded, upgrade to `error`.
  • The `numberOfRuns: 1` setting trades statistical reliability for speed. For a solo portfolio project this is acceptable; increase to 3 for a team project.
  • `temporary-public-storage` upload gives shareable report URLs per CI run without requiring a Lighthouse CI server.
  • Playwright adds ~5 minutes to CI; scope to chromium-only to keep wall time under 10 minutes total.

Alternatives Considered

#

**Single test layer (Vitest only):** Rejected — component tests cannot catch routing regressions, locale switching bugs or accessibility violations that only manifest in a real browser.

**Jest instead of Vitest:** Rejected — project already uses Vitest; switching adds churn with no material benefit for a Next.js 15 + React 19 codebase.

**Cypress instead of Playwright:** Rejected — Playwright has better Next.js 15 + App Router compatibility, first-class TypeScript, and built-in axe-core integration via `@axe-core/playwright`.