Playwright + TypeScript test automation for the WCC platform. The suite is type-safe end to end: requests are built from Faker factories, responses are validated at runtime with Zod, and API calls go through a small client/service layer.
- Node.js ≥ 18 (developed on v23)
- npm
# 1. Install dependencies
npm install
# 2. Install Playwright browsers (needed for the admin/UI project)
npx playwright install
# 3. Configure environment (see below)
# Copy tests/.env.example to tests/.env and fill in the valuesTests read configuration from tests/.env (copy tests/.env.example):
# Target API
API_HOST=https://your-api-host
API_KEY=your-x-api-key
# Admin/UI base URL (optional — defaults to http://localhost:3000)
ADMIN_BASE_URL=
# Role credentials (used by the per-role fixtures and the admin setup project)
ADMIN_EMAIL=
ADMIN_PASSWORD=
LEADER_EMAIL=
LEADER_PASSWORD=
MENTOR_EMAIL=
MENTOR_PASSWORD=
MENTORSHIP_ADMIN_EMAIL=
MENTORSHIP_ADMIN_PASSWORD=
⚠️ Never commit real credentials..envholds secrets only — all values come fromprocess.env.
# All tests (both projects)
npm test
# API tests only
npm run test:api
# Admin (UI) tests only
npm run test:admin
# Type-check without running tests
npm run typecheck
# Open the last HTML report
npm run reportFor ad-hoc runs (single file, specific reporter), call Playwright directly:
# A single file
npx playwright test tests/api/tests/auth/auth.flow.spec.ts --project=api
# Live console output
npx playwright test --project=api --reporter=lineThree Playwright projects are defined in playwright.config.ts: setup (logs each role in and saves its storageState), api (headless API flows), and admin (Desktop Safari UI; depends on setup). Admin tests authenticate by loading a role's saved session — e.g. test.use({ storageState: USERS.mentor.storageState }).
Code style is enforced by ESLint (with typescript-eslint and eslint-plugin-playwright) and Prettier. The two are kept conflict-free via eslint-config-prettier.
# Lint
npm run lint # report problems
npm run lint:fix # auto-fix what ESLint can
# Format
npm run format # check formatting (no writes)
npm run format:fix # rewrite files to match PrettierConfiguration lives in eslint.config.mjs, .prettierrc.json, and .prettierignore.
A Husky pre-commit hook runs lint-staged so every commit is automatically linted and formatted — only the files you staged are touched:
*.{ts,mjs,js}→eslint --fixthenprettier --write*.{json,md,yml,yaml}→prettier --write
The fixes are re-staged automatically. If ESLint reports a non-auto-fixable error, the commit is aborted so it can be resolved first.
The hook installs itself on npm install via the prepare script, so no extra setup is needed after cloning. The hook definition is in .husky/pre-commit and the lint-staged config is the lint-staged block in package.json.
helpers/ Test support code (outside tests/)
apifactory/
api.service.ts APIService aggregator (.authentication, .mentor, .member)
api.helper.ts TypedAPIResponse<T> + ensureSuccess(response)
clients/ Transport layer — one method per endpoint, returns raw APIResponse
services/ Business layer — builds payloads, optional ensureSuccess, returns TypedAPIResponse<T>
datafactory/
constants/paths.data.ts Endpoint path enums (AuthEndpoints, PlatformEndpoints, CmsEndpoints)
constants/roles.data.ts USERS — per-role config (email, password, storageState) + Role type
schemas/ Zod response schemas
mentor.factory.ts Faker payload factories
fixtures/
common.fixtures.ts API fixtures (contexts + APIService per role)
pom.fixture.ts UI fixtures (page objects: basePage, loginPage)
index.ts Merged test (API + POM) — import from 'helpers/fixtures'
tests/
.env Env vars: API_HOST, API_KEY, ADMIN_BASE_URL, role creds (not committed)
api/
TEST_PLAN.md Flow + test-case catalogue
tests/ API specs ({area}/[name].flow.spec.ts)
admin/
.auth/ Saved per-role login sessions (not committed)
setup.ts Setup project — logs each role in, saves storageState
pages/ Admin page objects
tests/ Admin specs
playwright.config.ts Projects: setup, api, admin (admin depends on setup)
tsconfig.json paths: helpers/* and tests/* → bare imports from repo root
eslint.config.mjs ESLint flat config (typescript-eslint + playwright + prettier)
.prettierrc.json / .prettierignore Prettier formatting rules and ignore list
.husky/pre-commit Pre-commit hook — runs lint-staged
All imports use bare specifiers resolved via tsconfig paths (e.g. helpers/fixtures, tests/admin/pages/login.page) — no relative ../../ paths in specs.
A two-layer design, aggregated by APIService and exposed through role-scoped fixtures.
- Clients (
helpers/apifactory/clients/) — transport only. One method per endpoint, returns the rawAPIResponse. No assertions, no parsing. - Services (
helpers/apifactory/services/) — business layer. Each method:- builds its request payload in the body (a Faker factory for pure test data, or assembled from discrete params like
login(email, password)); - takes a trailing
ensureSuccess = falseflag — whentrueit callsensureSuccess(response)to throw on a non-ok response; - always returns
TypedAPIResponse<T>, so callers get a typed.json().
- builds its request payload in the body (a Faker factory for pure test data, or assembled from discrete params like
- Fixtures (
helpers/fixtures/common.fixtures.ts) —authApi(X-API-KEY only) plusadminApi/leaderApi/mentorApi/mentorshipAdminApi, andapiForRole(role)for permission-matrix tests. Tokens are fetched once per role per worker and cached. The mergedtest(API + UI page objects) is re-exported fromhelpers/fixtures.
import { test } from "helpers/fixtures";
import { expect } from "@playwright/test";
import { loginResponseSchema } from "helpers/datafactory/schemas/auth.schema";
test("login returns a valid session", async ({ authApi }) => {
// ensureSuccess: true → asserts the response is ok before returning
const response = await authApi.authentication.login(email, password, true);
// Runtime schema validation; throws (fails the test) on shape drift
loginResponseSchema.parse(await response.json());
});For negative/permission tests, omit ensureSuccess and assert the status on the returned response:
const response = await adminApi.mentor.accept(mentorId); // no ensureSuccess
expect(response.status()).toBe(409);