Skip to main content

Testing Strategy

Overviewโ€‹

Aliz projects use three layers of testing, each with a distinct tool and purpose:

LayerToolWhat it covers
Unit & integrationVitestIsolated logic, components, hooks
Visual regressionVitest v4 browser modeUnintended UI changes caught by screenshot comparison
End-to-endPlaywrightFull user flows in real browsers

Each layer has a different scope and speed trade-off. Run unit tests constantly during development, visual regression tests on PRs, and E2E tests at the integration stage or in CI.

tip

You don't need all three layers on every project. Start with unit tests. Add E2E tests for critical user flows. Introduce visual regression tests when UI stability becomes a priority.

Unit tests with Vitestโ€‹

What they coverโ€‹

Unit tests verify isolated pieces of logic โ€” pure functions, custom hooks, and individual components โ€” without a real browser or network. They are fast, deterministic, and the first line of defence against regressions.

How to write themโ€‹

Vitest provides a Jest-compatible API, so the patterns are familiar:

  • Use describe to group related tests.
  • Use it (or test) for individual assertions.
  • Use expect matchers for assertions.
  • Pair with React Testing Library for component tests โ€” render, query, and interact with components the way a user would.
src/utils/format.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency } from './format';

describe('formatCurrency', () => {
it('formats a positive amount with the correct symbol', () => {
expect(formatCurrency(1234.5, 'USD')).toBe('$1,234.50');
});

it('returns an empty string for invalid input', () => {
expect(formatCurrency(NaN, 'USD')).toBe('');
});
});
note

Vitest shares your vite.config.ts, so TypeScript, JSX, and path aliases work out of the box โ€” no separate Babel or Jest config needed.

See Vitest for setup details and configuration options.

Visual regression tests with Vitest v4โ€‹

Visual regression tests catch unintended UI changes by comparing screenshots of components against a stored baseline โ€” surfacing broken layouts, colour shifts, or missing elements that functional assertions would miss. Vitest v4 added a built-in toMatchScreenshot() matcher to its browser mode, which runs tests in real browsers via the Playwright provider.

The main operational caveat is screenshot consistency: baselines differ across operating systems and browser builds, so generate them in CI (or Docker), not on developer machines.

See the Visual Regression Testing with Vitest recipe for setup pointers, baseline workflow, and stability tips.

E2E tests with Playwrightโ€‹

What they coverโ€‹

End-to-end tests exercise complete user flows โ€” login, form submission, navigation, checkout โ€” in a real browser against a running application. They validate that all layers of the stack (UI, API, auth) work together correctly.

What Playwright bringsโ€‹

  • Cross-browser โ€” a single test suite runs against Chromium, Firefox, and WebKit.
  • Auto-wait โ€” locators wait for elements to be visible and actionable before interacting, eliminating manual sleeps and reducing flakiness.
  • Trace viewer โ€” captures a full timeline (DOM snapshots, network, console) for post-mortem debugging of failed CI runs.
  • Codegen โ€” records user interactions and generates test code, making it fast to bootstrap new test scenarios.
  • Parallel execution โ€” tests run in isolated browser contexts by default and can be sharded across CI machines.
e2e/login.spec.ts
import { test, expect } from '@playwright/test';

test('user can log in and reach the dashboard', async ({ page }) => {
await page.goto('/login');

await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('secret');
await page.getByRole('button', { name: 'Sign in' }).click();

await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

See Playwright for installation, configuration, and CI setup.

Code coverageโ€‹

Generating coverage with Vitestโ€‹

Vitest has built-in coverage support via the @vitest/coverage-v8 (default) or @vitest/coverage-istanbul provider. Generate an LCOV report โ€” the format accepted by GitHub's native coverage feature โ€” with a single flag:

npx vitest --coverage --coverage.reporter=lcov

Or configure it persistently in vite.config.ts:

vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
},
},
});

The LCOV file is written to coverage/lcov.info by default.

Uploading coverage to GitHub (public preview)โ€‹

GitHub supports native code coverage in pull requests as a public preview. Once uploaded, GitHub annotates changed lines in the PR diff view with coverage indicators and shows a coverage delta (whether this PR improves or worsens overall coverage).

Add an upload step after your test run in your GitHub Actions workflow:

.github/workflows/coverage.yml
name: Test & Coverage
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npx vitest --coverage --coverage.reporter=lcov
- uses: actions/upload-coverage@v1
with:
files: coverage/lcov.info
note

The actions/upload-coverage action is part of a public preview. Its API may change before general availability. Check the GitHub documentation for the latest syntax.

GitHub also supports Cobertura XML and JaCoCo XML in addition to LCOV, covering most JavaScript, Java, Python, Go, and .NET frameworks.

Enforcement via repository rulesetsโ€‹

Coverage thresholds can be enforced through repository rulesets. You can block a PR merge if:

  • Overall coverage drops below a configured threshold (e.g., 80%)
  • The coverage delta for this PR is negative

This turns coverage from an informational signal into an actual merge gate with no additional tooling.

See the GitHub PR Code Coverage blog post for a full walkthrough of the feature and how it compares to third-party tools like Codecov and Coveralls.

Resourcesโ€‹