This document covers unit/integration tests (Jest) and end-to-end tests (Playwright).
| Layer | Tools |
|---|---|
| Unit / component | Jest 30.x, Testing Library, jsdom |
| Coverage | V8 provider, HTML / LCOV / JUnit reports |
| End-to-end | Playwright (e2e/, pnpm test:e2e) |
| CI | GitHub Actions (quality.yml, test.yml, ci.yml) |
pnpm testpnpm test:watchpnpm test:coveragepnpm test path/to/test-file.test.tsx
pnpm test --testNamePattern="Button"
pnpm test -- --runInBand components/recruitment/table.test.tsxColocate unit/component tests next to source with .test.ts / .test.tsx:
components/
ui/
button.tsx
button.test.tsx
app/
page.tsx
page.test.tsx
lib/
utils.ts
utils.test.ts
Playwright specs live under e2e/:
e2e/
recruitment-flow.spec.ts
email-center.spec.ts
email-prelaunch.spec.ts
visual-smoke.spec.ts
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button } from "./button";
describe("Button", () => {
it("renders a button with text", () => {
render(<Button>Click me</Button>);
expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument();
});
it("handles click events", async () => {
const handleClick = jest.fn();
const user = userEvent.setup();
render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByRole("button"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});import { cn } from "./utils";
describe("cn utility function", () => {
it("merges class names correctly", () => {
const result = cn("class1", "class2");
expect(result).toBe("class1 class2");
});
});pnpm test:e2e runs scripts/run-playwright-e2e.mjs, which boots a temporary Next.js server and executes Playwright against Chromium.
- Local dependencies installed (
pnpm install) - Database available and migrated when the suite exercises real DB paths
- Optional
.env.localvalues are loaded by the runner; missing secrets fall back to safe local defaults for Playwright
pnpm test:e2eCurrent suites cover:
- Recruitment registration → persistence → admin grading / pass
- Administrator interview evaluation final approval
- Email center admin surfaces and webhook auth
- Visual smoke checks for desktop / mobile layout overflow
See docs/RELEASE_CHECKLIST.md for the release-oriented e2e acceptance list.
After pnpm test:coverage, reports are written to coverage/:
- HTML:
coverage/index.html - LCOV:
coverage/lcov.info - JUnit XML:
coverage/junit.xml - Clover XML:
coverage/clover.xml
Open the HTML report:
# Windows
start coverage/index.html
# macOS
open coverage/index.html
# Linux
xdg-open coverage/index.htmljest.config.ts includes:
- Test environment: jsdom for React component tests
- Setup file:
jest.setup.ts - Path aliases: mirrors
@/fromtsconfig.json - Coverage collection: primarily
app/,components/, andlib/ - Reporters: console + JUnit for CI
jest.setup.ts mocks common Next.js modules such as:
next/imagenext/navigation(useRouter,usePathname,useSearchParams)
Tests run through GitHub Actions on:
- Push / PR to
masterordevelopvia.github/workflows/ci.yml - Version tags via
.github/workflows/release.yml - Manual debugging via
workflow_dispatchon reusable workflows
Typical pipeline:
- Install dependencies
- Lint and typecheck
- Run Jest with coverage
- Upload coverage / test artifacts when configured
- Build the Next.js application
- Run Playwright e2e when the workflow includes that job
Details live in CI_CD.md.
- Sign up at codecov.io
- Add the repository
- Add
CODECOV_TOKENto GitHub repository secrets
// ❌
expect(component.state.count).toBe(1);
// ✅
expect(screen.getByText("Count: 1")).toBeInTheDocument();getByRolegetByLabelTextgetByPlaceholderTextgetByTextgetByTestIdonly when no better query exists
Prefer tests that include empty states, unauthorized access, invalid input, and external dependency failure over happy-path-only snapshots.
Use Playwright for critical user journeys. Keep pure logic and component edge cases in Jest.
- Use
test.only()during local debugging - Prefer
pnpm test:watchfor changed files - Use
--runInBandwhen investigating flaky shared-state suites
- Confirm path aliases match between
jest.config.tsandtsconfig.json - Mock Next.js-only modules in
jest.setup.tswhen needed
- Confirm the file matches
collectCoverageFrom - Confirm it is not ignored by
coveragePathIgnorePatterns
- Confirm port
3101is free or setPLAYWRIGHT_PORT - Confirm
SESSION_SECRETand database connectivity for DB-backed suites - Prefer
LINK_USE_MOCK=truefor isolated local e2e unless intentionally testing real Link