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.
- 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
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
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:
- Biome: one Rust tool handling both formatting and lint, extremely fast, works with zero config. A good default for new projects.
- ESLint (typescript-eslint) + Prettier: the richest ecosystem, especially rules that use type information (e.g.
no-floating-promisescatching missing awaits,no-misused-promises).
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
- 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.
- 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." - 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.
- 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
- Small commits: have the agent commit after every verifiable step. When something breaks, you can roll back precisely to the last good state.
- One feature, one branch / PR: the PR description says what changed, why, and how to verify. You can require the agent to fill in a template.
- Parallel work: when running several agents on different tasks, give each task its own directory with
git worktreeso they don't interfere. Aware
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
| Technique | Purpose | Examples |
|---|---|---|
| Structured logs | Record events with context (user ID, request ID) as JSON for search | Platform logs (Vercel Logs, wrangler tail, Workers Logs) |
| Error tracking | Collect frontend and backend exceptions, stacks, affected users | Sentry and similar |
| Tracing | See how long a request spends in each service, DB and LLM call | OpenTelemetry standard + backends |
| Frontend performance | Real users' load speed and interaction latency (LCP, INP, CLS) | Lighthouse, platform analytics |
| LLM observability | Record each model call's input, output, tokens, latency, cost | Chapter 11 |
Put a Definition of Done like this in AGENTS.md; it applies to every task:
- 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.