API Test Skill
Test Next.js Route Handlers with real HTTP requests via Playwright's request fixture — verifying
status codes, response shape, authentication, validation, and error handling against a running dev server.
This file holds the rules. Full copy-paste test suites live in the reference:
- references/examples.md — worked route-handler + test pairs (POST, GET with
query params, PATCH, DELETE, headers), reusable test utilities (auth/base-URL/assertion helpers), and
the extra patterns referenced below (health check, parameterized validation, rate limiting, ActionResponse).
First Step: Read Project Context
Before writing tests, check CLAUDE.md for the conventions that shape every assertion:
- Auth method (Clerk, NextAuth, JWT, API key) and how to pass a test credential.
- Response envelope (e.g.
{ data }, or the ActionResponse { success, data | error } shape).
- Error model (
ServiceError codes and how they map to HTTP status).
Prerequisites
- Dev server running (
npm run dev) — these are real requests, not mocks.
- Playwright installed; tests live in
tests/e2e/api/, named [endpoint-name].spec.ts.
- A test credential available (usually an env var like
TEST_SESSION_TOKEN).
Workflow
- Analyze the endpoint — supported methods, request body schema, query params, auth requirement,
expected response shape, and the error cases it can return.
- Write the spec — group tests with
test.describe per endpoint/method; use Playwright's request
fixture (request.get/post/put/patch/delete). Start from a worked suite in
references/examples.md and adapt the paths, payloads, and auth.
- Run and iterate —
npm run test:e2e -- tests/e2e/api/. Read failures, fix, re-run.
What Every Suite Should Cover
Treat this as the checklist when deciding which tests to write (see examples for the code):
- Authentication —
401 (or 403) when unauthenticated; the happy status when authenticated.
- Happy path per method — GET
200 (+ correct body shape), POST 201 (+ returns an id),
PUT/PATCH 200 (+ updated fields), DELETE 204.
- Validation —
400 with error details for missing/invalid fields. Parameterize a table of bad
payloads rather than writing one test each.
- Not found —
404 for a non-existent id.
- Conflict —
409 for a duplicate, when the endpoint enforces uniqueness.
- Server errors — degrade gracefully: an error status (
>= 400) with an error body, never a crash.
- Query params — pagination and filters are honoured (assert the returned set respects them).
- Headers —
content-type: application/json; cache-control/CORS where the endpoint sets them.
- Rate limiting —
429 once the limit is exceeded, if implemented.
Authentication
Don't guess the scheme — read CLAUDE.md, then attach the matching credential to headers:
| Provider |
Header |
| Clerk |
Cookie: __session=<token> |
| NextAuth |
Cookie: next-auth.session-token=<token> |
| JWT |
Authorization: Bearer <token> |
| API key |
X-API-Key: <token> |
Read the token from an env var (never hardcode it) and centralize it in a small auth helper so every
spec shares one source — see Test Utilities in references/examples.md.
Response Shape Assertions
Assert the shape, not just the status. If the project uses the ActionResponse envelope:
- Success →
{ success: true, data, message? } — assert with toMatchObject({ success: true, data: ... }).
- Failure →
{ success: false, error }, optionally fieldErrors keyed by field name.
ServiceError → HTTP — the typed code maps to a status (e.g. notFound → 404); assert both the
status and body.error.
Concrete assertions for each are in references/examples.md.
Test File Organization
tests/e2e/api/
├── auth/ # login.spec.ts, logout.spec.ts
├── <domain>/ # create-*.spec.ts, get-*.spec.ts, update-*.spec.ts
├── health.spec.ts
└── fixtures/ # shared test data
Best Practices
- Isolate — each test stands alone; create what it needs in
beforeEach, delete it in afterEach.
- Verify the body shape, not only the status code.
- Cover error paths, not just the happy path — and assert that error messages are user-friendly.
- Use env vars for base URL and tokens; never hardcode.
- Test edge cases — empty arrays, nulls, oversized/special-character input.
Questions to Ask
- Which HTTP methods, and what is the request-body schema for each?
- What auth is required, and how do I obtain a test credential?
- What are the possible error responses and their status codes?
- Are there rate limits, quotas, or notable headers (cache, CORS)?
- Any side effects (created/deleted records) that need cleanup?
1---2name: api-test3description: Test Next.js Route Handlers and API endpoints using Playwright for real HTTP requests. Use when testing API endpoints, route handlers, or backend integrations.4---56# API Test Skill78Test Next.js Route Handlers with **real HTTP requests** via Playwright's `request` fixture — verifying9status codes, response shape, authentication, validation, and error handling against a running dev server.1011This file holds the rules. Full copy-paste test suites live in the reference:1213> - [references/examples.md](./references/examples.md) — worked route-handler + test pairs (POST, GET with14> query params, PATCH, DELETE, headers), reusable test utilities (auth/base-URL/assertion helpers), and15> the extra patterns referenced below (health check, parameterized validation, rate limiting, ActionResponse).1617## First Step: Read Project Context1819Before writing tests, check **`CLAUDE.md`** for the conventions that shape every assertion:2021- **Auth method** (Clerk, NextAuth, JWT, API key) and how to pass a test credential.22- **Response envelope** (e.g. `{ data }`, or the `ActionResponse` `{ success, data | error }` shape).23- **Error model** (`ServiceError` codes and how they map to HTTP status).2425## Prerequisites2627- Dev server running (`npm run dev`) — these are real requests, not mocks.28- Playwright installed; tests live in `tests/e2e/api/`, named `[endpoint-name].spec.ts`.29- A test credential available (usually an env var like `TEST_SESSION_TOKEN`).3031## Workflow32331. **Analyze the endpoint** — supported methods, request body schema, query params, auth requirement,34 expected response shape, and the error cases it can return.352. **Write the spec** — group tests with `test.describe` per endpoint/method; use Playwright's `request`36 fixture (`request.get/post/put/patch/delete`). Start from a worked suite in37 [references/examples.md](./references/examples.md) and adapt the paths, payloads, and auth.383. **Run and iterate** — `npm run test:e2e -- tests/e2e/api/`. Read failures, fix, re-run.3940## What Every Suite Should Cover4142Treat this as the checklist when deciding which tests to write (see examples for the code):4344- **Authentication** — `401` (or `403`) when unauthenticated; the happy status when authenticated.45- **Happy path per method** — GET `200` (+ correct body shape), POST `201` (+ returns an `id`),46 PUT/PATCH `200` (+ updated fields), DELETE `204`.47- **Validation** — `400` with error details for missing/invalid fields. Parameterize a table of bad48 payloads rather than writing one test each.49- **Not found** — `404` for a non-existent id.50- **Conflict** — `409` for a duplicate, when the endpoint enforces uniqueness.51- **Server errors** — degrade gracefully: an error status (`>= 400`) with an error body, never a crash.52- **Query params** — pagination and filters are honoured (assert the returned set respects them).53- **Headers** — `content-type: application/json`; `cache-control`/CORS where the endpoint sets them.54- **Rate limiting** — `429` once the limit is exceeded, if implemented.5556## Authentication5758Don't guess the scheme — read `CLAUDE.md`, then attach the matching credential to `headers`:5960| Provider | Header |61| -------- | ----------------------------------------------- |62| Clerk | `Cookie: __session=<token>` |63| NextAuth | `Cookie: next-auth.session-token=<token>` |64| JWT | `Authorization: Bearer <token>` |65| API key | `X-API-Key: <token>` |6667Read the token from an env var (never hardcode it) and centralize it in a small auth helper so every68spec shares one source — see *Test Utilities* in [references/examples.md](./references/examples.md).6970## Response Shape Assertions7172Assert the **shape**, not just the status. If the project uses the `ActionResponse` envelope:7374- **Success** → `{ success: true, data, message? }` — assert with `toMatchObject({ success: true, data: ... })`.75- **Failure** → `{ success: false, error }`, optionally `fieldErrors` keyed by field name.76- **`ServiceError` → HTTP** — the typed code maps to a status (e.g. `notFound → 404`); assert both the77 status and `body.error`.7879Concrete assertions for each are in [references/examples.md](./references/examples.md#actionresponse-pattern).8081## Test File Organization8283```84tests/e2e/api/85├── auth/ # login.spec.ts, logout.spec.ts86├── <domain>/ # create-*.spec.ts, get-*.spec.ts, update-*.spec.ts87├── health.spec.ts88└── fixtures/ # shared test data89```9091## Best Practices9293- **Isolate** — each test stands alone; create what it needs in `beforeEach`, delete it in `afterEach`.94- **Verify the body shape**, not only the status code.95- **Cover error paths**, not just the happy path — and assert that error messages are user-friendly.96- **Use env vars** for base URL and tokens; never hardcode.97- **Test edge cases** — empty arrays, nulls, oversized/special-character input.9899## Questions to Ask100101- Which HTTP methods, and what is the request-body schema for each?102- What auth is required, and how do I obtain a test credential?103- What are the possible error responses and their status codes?104- Are there rate limits, quotas, or notable headers (cache, CORS)?105- Any side effects (created/deleted records) that need cleanup?