CHAPTER 10 · FULL-STACK

Engineering & Quality

When agents write most of the code, one of your core jobs becomes building an automated safety net: mistakes surface within seconds, explicitly, instead of depending on you reading every line. The same net is also the agent's "eyes" — test results are how it knows whether it got things right.

⏱ ~50 minUse (type checking, Vitest, Playwright, CI) Aware (monorepos, observability tools)
By the end of this chapter you should be able to:
  • Say what each layer of the verification pyramid catches and what it costs.
  • Write a Vitest unit test and a Playwright end-to-end test.
  • Configure a CI pipeline that runs type checks, lint, tests and build on every PR.
  • Recognize typical ways agents "cheat" on tests.

Opening: you can't read all the code

Opening question

In one day, an agent submitted 30 changes to LinkNote touching 80 files. You carefully reviewed the 5 most important ones. Why should you believe the other 75 didn't break existing features?

Where the answer lies: you don't need to trust the agent; you need to trust the checks. Encode "what correct means" as machine-executable checks (types, rules, tests) that run automatically on every change. Your review focus then shifts from "are these 80 files right?" to "do the checks cover the important behavior?" and "are these 5 key design decisions right?".

The verification pyramid

Manual QA End-to-end tests (Playwright) Integration tests (API + real DB) Unit tests (Vitest) Static checks: types (tsc) + lint + format slow · costly · few fast · cheap · many click around the preview key user flows work SQL, auth, API contracts pure logic, edges typos, types, risky patterns
Lower layers are faster and cheaper and should be plentiful; higher layers are closer to real users but slower and more brittle, reserved for the most critical flows.

In an agent workflow, the bottom two layers become even more valuable: they answer within seconds, so the agent can loop "change → check → fix" on its own without you stepping in.

Static checks

Type checking

As stressed in Chapters 01 and 04: tsc --noEmit must report zero errors and be enforced in CI. Since TypeScript 7, even large projects type-check in seconds.

Lint and formatting

Two mainstream setups:

The rules worth enabling share a trait: they target code that "runs but hides a problem" — exactly what agents tend to write and humans tend to miss. Also, Knip finds unused files, exports and dependencies; agents leave plenty of that behind after repeated edits. Aware

Unit tests: Vitest Use

Vitest is the Vite ecosystem's test framework: Jest-compatible API, native TS and ESM, fast.

Good tests test behavior (input → output), not implementation details; their names read as requirements; they cover edges (nulls, case, empty lists). pnpm vitest starts watch mode and re-runs on changes.

For React components, Testing Library queries and interacts by "what the user can see" (by role, by text) rather than CSS classes. For Cloudflare Workers, a dedicated Vitest integration runs tests inside the workerd runtime with simulated bindings.

End-to-end tests: Playwright Use

Playwright drives a real browser, clicking, typing and asserting like a user. It covers what unit tests can't: navigation between pages, frontend-backend integration, elements made unclickable by styling, and so on.

Note the test locates elements by role and label (getByRole("button", { name: "Save" })). That makes tests sturdier and also checks the semantic HTML from Chapter 05 — if the "button" is a div, this test won't find it.

When Playwright fails it saves a trace (screenshots, DOM and network for every step), opened with pnpm playwright show-trace — the best tool for diagnosing E2E failures. Many coding agents can also "see" pages through Playwright (or a browser MCP tool) to self-check visually.

Testing strategy in the agent era

  1. Tests are the spec. For important features, have the agent write tests first (or write the list of test names yourself), confirm the tests express the behavior you want, then have it implement. That's test-driven development (TDD), and in agent workflows it's worth more than ever: tests turn a fuzzy requirement into executable acceptance criteria.
  2. Watch for "changing the test to make it pass". When an agent can't fix the code, it sometimes edits assertions, adds .skip, or sets the expected value to the current wrong output. When reviewing diffs, scrutinize changes to test files. You can state in AGENTS.md: "Never change existing test assertions unless I ask."
  3. Beware over-mocking. Tests that mock away the database and network often test only the mocks. For SQL and auth logic, integration tests against a real database (local Docker Postgres, a test D1, or an in-memory Postgres like PGlite) are worth more.
  4. Go easy on snapshots. Big snapshots get "updated with one keystroke" and stop verifying anything.

CI: turning checks into gates Use

Then enable branch protection in the GitHub repo settings: main only accepts merges via PR, and CI must pass. Now neither you nor the agent can merge code that fails the checks. E2E tests can run against the preview URL once the platform creates one.

Git habits

Monorepos Aware

When a project has several apps and shared code (a web frontend, a standalone API, a shared types/schema package), pnpm workspaces keep them in one repo and Turborepo orchestrates builds and tests by dependency order with caching.

Don't introduce it early. With one app, a plain single-package repo is enough. Migrate the first time two deployable units need to share code.

Observability: how do you know it broke in production? Aware

TechniquePurposeExamples
Structured logsRecord events with context (user ID, request ID) as JSON for searchPlatform logs (Vercel Logs, wrangler tail, Workers Logs)
Error trackingCollect frontend and backend exceptions, stacks, affected usersSentry and similar
TracingSee how long a request spends in each service, DB and LLM callOpenTelemetry standard + backends
Frontend performanceReal users' load speed and interaction latency (LCP, INP, CLS)Lighthouse, platform analytics
LLM observabilityRecord each model call's input, output, tokens, latency, costChapter 11
Tell the agent: write a "Definition of Done"

Put a Definition of Done like this in AGENTS.md; it applies to every task:

Vague
Precise
"Fix the search bug."
"Search crashes when a title is null. First write a failing test that reproduces it (null title, search by URL) and confirm it fails; then fix it; then run the full test suite."
Interview corner
  • How do you balance unit, integration and E2E tests? Follow the pyramid: lots of fast unit tests for logic branches; a few integration tests for contracts with databases and external services; very few E2E tests for core user flows (sign-up, checkout).
  • How do you test code that depends on time or randomness? Inject time and randomness as parameters, or use the framework's fake timers (vi.useFakeTimers()).
  • What should CI run? Install (frozen lockfile) → type check → lint → tests → build; protect main; run E2E against previews.

Self-check

1. After fixing a bug, the agent reports "all tests pass". The diff contains one test change: expect(result).toBe(3) became expect(result).toBe(2). What do you do?

First decide which value matches the requirement. If 3 is the correct business result, the agent "changed the test to fit the wrong implementation" — revert that change and require a fix to the implementation. If the requirement genuinely changed, the edit is legitimate but should be called out explicitly in the summary. Either way, "tests pass" alone isn't enough.

2. A team has only E2E tests, no unit tests. CI takes 25 minutes and often fails from network flakiness. How does that hurt an agent workflow?

The agent's feedback loop becomes very long (25 minutes per round), so it can't iterate quickly; flaky tests teach agents (and humans) to ignore failures or retry until they "happen to pass". Push most logic verification down into second-level unit tests and static checks, keep E2E for a handful of core flows, and fix the flaky ones.