Skip to main content

Overview

Been uses a comprehensive testing strategy with two complementary testing frameworks:
  • Vitest for unit and component tests
  • Playwright for end-to-end (E2E) tests

Unit & Component Testing with Vitest

Running Tests

Run all unit tests with coverage:
pnpm test
This command runs tests in headless browser mode using Playwright as the browser provider and generates coverage reports.

Test Configuration

Vitest is configured in vite.config.ts:
test: {
  browser: {
    enabled: true,
    headless: true,
    provider: playwright(),
    screenshotFailures: false,
    instances: [{ browser: "chromium" }],
  },
  clearMocks: true,
  coverage: {
    exclude: ["./index.tsx", "**/*.spec.{ts,tsx}"],
    provider: "istanbul",
    reportsDirectory: "../coverage",
  },
  exclude: ["**/__e2e-test__/**"],
  outputFile: "../reports/test.xml",
  reporters: ["default", "junit"],
  setupFiles: "./vitest-setup.ts",
}

Key Features

  • Browser mode: Tests run in a real Chromium browser using Playwright
  • Coverage: Istanbul-based code coverage with reports in coverage/
  • Setup file: vitest-setup.ts imports @testing-library/jest-dom matchers
  • JUnit reports: Test results exported to reports/test.xml for CI/CD

Writing Unit Tests

Tests use Vitest with React Testing Library. Here’s an example of a custom hook test:
import { renderHook } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { useLocalStorage } from "./use-local-storage";

describe("useLocalStorage", () => {
  it("should initialise", () => {
    const { result } = renderHook(() => useLocalStorage("TEST", "hello"));
    const [data, setData] = result.current;

    expect(data).toBe("hello");
    expect(setData).toBeDefined();
  });
});

Writing Component Tests

Example component test with snapshot testing:
import { render } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { MenuItem } from './menu-item';

describe('menuItem', () => {
  it('should render', () => {
    const result = render(
      <MenuItem
        country={{
          iso3166: 'GB',
          name: 'United Kingdom',
          region: 'Europe',
        }}
      />,
    );

    expect(result.asFragment()).toMatchSnapshot();
  });
});

Test File Location

  • Unit tests: Place .spec.ts or .spec.tsx files next to the source files they test
  • E2E tests: Place in src/__e2e-test__/ directory (excluded from unit tests)

Available Matchers

The project includes @testing-library/jest-dom which provides additional matchers:
  • toBeInTheDocument()
  • toHaveClass()
  • toBeVisible()
  • toHaveTextContent()
  • And many more

End-to-End Testing with Playwright

Running E2E Tests

Run all E2E tests:
pnpm e2e
This command:
  1. Starts the preview server (builds the app first if needed)
  2. Runs all tests in src/__e2e-test__/
  3. Captures traces for debugging

Playwright Configuration

Configured in playwright.config.ts:
import { defineConfig } from "@playwright/test";

const URL_SERVE = "http://127.0.0.1:5173";
const URL_PREVIEW = "http://127.0.0.1:4173";
const URL = process.env["CI"] ? URL_PREVIEW : URL_SERVE;

export default defineConfig({
  testDir: "./src/__e2e-test__",
  use: {
    baseURL: URL,
    trace: "on",
  },
  webServer: {
    command: "pnpm run preview",
    reuseExistingServer: !process.env["CI"],
    timeout: 10 * 1000,
    url: process.env["CI"]
      ? "http://127.0.0.1:4173"
      : "http://127.0.0.1:5173",
  },
});

Key Features

  • Automatic server: Playwright starts and stops the web server automatically
  • Trace collection: Full traces captured for debugging failed tests
  • CI-ready: Uses production build in CI environments
  • Server reuse: Reuses existing dev server in local development

Writing E2E Tests

Example E2E test:
import { expect, test } from "@playwright/test";

test.describe("base", () => {
  test("loads the page", async ({ page }) => {
    await page.goto("/");
    await page.waitForLoadState("load");

    expect(await page.title()).toBe("Been");
  });
});

E2E Best Practices

  1. Use data-testid attributes: For reliable element selection
  2. Wait for states: Use waitForLoadState(), waitForSelector(), etc.
  3. Test user flows: Focus on complete user journeys, not implementation details
  4. Keep tests independent: Each test should run in isolation

Coverage Reports

After running pnpm test, coverage reports are generated in the coverage/ directory:
  • coverage/index.html: Interactive HTML report
  • Various formats for CI integration

Excluded from Coverage

  • src/index.tsx (application entry point)
  • All test files (*.spec.ts, *.spec.tsx)

Continuous Integration

The test setup is optimized for CI/CD:
  • JUnit reports: reports/test.xml for CI dashboards
  • Environment detection: Uses production build and preview server in CI
  • No server reuse in CI: Ensures clean test environment

Testing Tips

Running Specific Tests

Vitest supports filtering:
# Run tests matching pattern
pnpm test -- useLocalStorage

# Run in watch mode (local development)
pnpm test -- --watch
Playwright supports filtering:
# Run specific test file
pnpm e2e -- base.spec.ts

# Run tests with specific title
pnpm e2e -- --grep "loads the page"

Debugging Tests

Vitest: Use browser DevTools when running with browser.headless: false Playwright: View traces after test runs:
px playwright show-trace trace.zip

Test Driven Development (TDD)

For TDD workflows, run tests in watch mode:
pnpm test -- --watch
Tests will re-run automatically when files change.

Build docs developers (and LLMs) love