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

What they cover

Visual regression tests catch unintended UI changes by comparing screenshots of components or pages against a stored baseline. A pixel diff that exceeds a threshold fails the test, surfacing broken layouts, colour shifts, or missing elements that functional assertions would miss.

How they work in Vitest v4

Vitest v4 introduced a browser mode that runs tests inside real browsers instead of a simulated DOM. This makes screenshot-based assertions reliable and consistent.

  • Install @vitest/browser and vitest-browser-react, then configure a browser provider (playwright recommended).
  • Use the built-in toMatchScreenshot() matcher to capture and compare renders against a stored baseline.
  • Tests run against real browser engines — Chromium, Firefox, and WebKit — via the Playwright provider, without needing a separate E2E harness.
src/components/Badge.browser.test.tsx
import { render } from 'vitest-browser-react';
import { expect, test } from 'vitest';
import { Badge } from './Badge';

test('Badge renders correctly', async () => {
const screen = render(<Badge label="New" variant="success" />);
await expect(screen.baseElement).toMatchScreenshot();
});
note

Screenshot baselines are committed to the repository alongside the test files. Update them intentionally with the --update (-u) flag when a UI change is deliberate.

note

Playwright also supports screenshot assertions and is the better choice for full-page or flow-level visual checks within an E2E test. Vitest browser mode visual regression is best suited for isolated component snapshots during development.

See Vitest for browser mode configuration details.

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.

Resources