Step 1: Set up test data factories
Tests need data. Instead of constructing test objects inline in every test file, build factories that produce realistic test data on demand.
A factory is a function that creates a valid instance of a domain entity with sensible defaults. A member factory produces a member with a name, email, role, and membership status. An artifact factory produces an artifact with a name, category, era, and description. You override specific fields when the test needs a particular value.
Install Faker.js for generating realistic names, dates, and text:
npm install -D @faker-js/faker
Direct Claude to create factories for your core entities: members, artifacts, and sessions. Each factory should produce valid, realistic data -- not "Test User 1" with "test@test.com" but a name and email that look like real data.
The factories serve two purposes. They make tests easier to write. And they encode your understanding of what valid data looks like -- if every test uses the same factory, there's one definition of "a valid member" and "a valid artifact." That's a form of documentation.
Step 2: Write integration tests for auth
The auth system is the highest-risk part of the application. Integration tests verify that the server enforces access control correctly.
Direct Claude to write integration tests that:
- Request a protected route without authentication -- expect a redirect
- Request a protected route with a valid member session -- expect content
- Request a protected route with an expired token -- expect a redirect
- Request a member-only API endpoint without authentication -- expect 401 or 403
These tests exercise the server-side enforcement, not the UI. If someone can construct an HTTP request that bypasses auth, the integration test catches it -- even though the UI correctly hides the link.
Step 3: Write E2E tests
E2E tests verify complete user journeys through the full application stack. Direct Claude to write Playwright tests for three critical paths:
- Tourist browses collection: Navigate to the collection page, search for "compass," verify results appear, open an artifact detail view, close it.
- Member logs in: Navigate to login, authenticate, access member-exclusive content, log out.
- Keyboard-only navigation: Complete the same flows using keyboard only -- this reuses the axe-core integration from Unit 5.
Each test exercises a real user journey end to end. If the test passes, a real person following the same steps would succeed.
Step 4: Cross-model review
You have a test suite. But is it testing the right things?
Use a second AI model to review your test suite. Ask it: "Review these tests. What critical behaviors are not covered? What failure modes would pass all these tests but still break in production?"
Evaluate what the second model suggests. Some suggestions will be useful -- gaps you didn't think of. Some will be convergent noise -- both models agreeing that a certain pattern is correct when it isn't. The student is the judge. Cross-model review supplements your judgment. It doesn't replace it.
If your context window is getting full after several rounds of work -- Claude contradicting constraints it followed earlier, generating code inconsistent with established patterns -- consolidate what's been decided into a clean starting context for a new session. The degradation is a context problem, not a capability problem.
Step 5: Examine coverage
Run the coverage report:
npx vitest --coverage
Look at the numbers. A high percentage means those lines of code were executed during testing. It does not mean those lines were tested well.
AI treats coverage as a target to maximize. If you ask "increase coverage to 90%," it will generate tests that execute uncovered lines without meaningfully asserting anything about them. A test that calls a function and doesn't check the return value "covers" the function while verifying nothing.
Direct AI to focus on critical-path coverage instead: auth flows, collection search, accessibility checks, server-side enforcement. 80% coverage on the paths that matter is worth more than 95% coverage padded with empty assertions.
Now verify the tests catch real failures. Intentionally break something: comment out the auth middleware on one protected route. Run the tests. At least one should fail. If they all pass, the test suite is checking that pages render, not that auth is enforced.
Check: Run the full test suite. All tests pass. Then intentionally break server-side auth enforcement on one route. Run the tests again. At least one test should fail.