Skip to main content

Testing

Terra uses Vitest for unit tests and Playwright for E2E tests.

Test Stack

ToolPurpose
VitestUnit and integration tests
PlaywrightEnd-to-end browser tests
React Testing LibraryComponent testing

Running Tests

# Unit tests
pnpm --dir apps/terra test

# Unit tests in watch mode
pnpm --dir apps/terra test --watch

# E2E tests
pnpm --dir apps/terra test:e2e

# E2E tests with UI
pnpm --dir apps/terra test:e2e:ui

# Load certification gate (status, submission, optional upload)
pnpm --dir apps/terra test:load

# Type check
pnpm --dir apps/terra tsc --noEmit

Test Structure

apps/terra/
├── src/
│   ├── lib/
│   │   └── __tests__/
│   │       └── logic-engine.test.ts
│   └── components/
│       └── __tests__/
│           └── form-renderer.test.tsx
├── tests/
│   ├── status-lookup.spec.ts
│   └── form-submission.spec.ts
└── scripts/
    └── load-certification.mjs

Load Certification

pnpm test:load runs a pass/fail capacity gate for public critical paths:
  1. GET /status (public status lookup)
  2. GET /f/<slug> (public submission form)
  3. POST /api/drive-upload (optional when upload fixture is configured)
Default SLO gates:
ScenarioMax p95 latencyMax error rateMin throughput
Status lookup1200ms2%8 rps
Public submission1500ms2%6 rps
Drive upload2500ms5%2 rps
Common environment variables:
  • TERRA_LOAD_BASE_URL (required, e.g. http://127.0.0.1:3310)
  • TERRA_LOAD_FORM_PATH (default /f/e2e-test-form)
  • TERRA_LOAD_REQUESTS (default 120)
  • TERRA_LOAD_CONCURRENCY (default 12)
  • TERRA_LOAD_INCLUDE_UPLOAD=true
  • TERRA_LOAD_UPLOAD_PATH (include query params; use {{index}} in submissionId to avoid collisions)
  • Per-scenario SLO overrides: TERRA_LOAD_STATUS_MAX_P95_MS, TERRA_LOAD_SUBMISSION_MAX_ERROR_RATE, TERRA_LOAD_UPLOAD_MIN_RPS, etc.
If a required fixture is missing, the gate fails by default. For local exploratory runs only, set TERRA_LOAD_ALLOW_MISSING_FIXTURES=true.

Deterministic Time-Based Tests

If a test depends on “last 7 days” / “last 30 days” logic, freeze time and use explicit timestamps.
const FIXED_NOW = new Date("2026-02-22T12:00:00.000Z");

beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(FIXED_NOW);
});

afterEach(() => {
  vi.useRealTimers();
});
Why this matters:
  • Prevents flaky tests caused by local clock drift or timezone differences.
  • Makes date-window assertions stable in CI.
  • Keeps failures tied to logic regressions, not timing noise.

Writing Unit Tests

// src/lib/__tests__/logic-engine.test.ts
import { evaluateLogic } from "../logic-engine";

describe("evaluateLogic", () => {
  it("evaluates simple equality rule", () => {
    const rule = {
      type: "rule",
      fieldId: "status",
      operator: "eq",
      value: "active",
    };

    expect(evaluateLogic(rule, { status: "active" })).toBe(true);
    expect(evaluateLogic(rule, { status: "inactive" })).toBe(false);
  });
});

Writing E2E Tests

// tests/submission.spec.ts
import { test, expect } from "@playwright/test";

test("submits a form", async ({ page }) => {
  await page.goto("/f/test-form");

  await page.fill('[name="full-name"]', "Jane Doe");
  await page.fill('[name="email"]', "jane@example.com");
  await page.click('button[type="submit"]');

  await expect(page.locator("text=Thank you")).toBeVisible();
});