The first time I asked Claude Code to write tests for a feature it had just implemented, the tests passed. All of them. On the first try. I felt great about it for about ten minutes, until I realized the tests were describing the code, not the requirements. The code had a bug I had specified against, and the tests happily locked the bug in.
That is context pollution. It is the fundamental problem with letting one model both implement and verify. The New Stack put it best: letting the model that wrote the code write the tests is asking a student to grade their own homework.
The fix is architectural, not attitudinal. Split the loop across three isolated contexts, each running at a different point in the development lifecycle. Here is what those three layers look like when you build them out.
Layer 1: TDD by subagent (dev time)
Red-Green-Refactor works with Claude Code, but not the way most teams first try it. The naive setup is one Claude Code session doing all three phases. That is the setup that produces homework-grading.
The working setup uses three subagents, each blind to the others' work.
Skills (orchestrator)
├── tdd-test-writer (RED) — sees feature description, NOT implementation
├── tdd-implementer (GREEN) — sees test file, NOT the writer's reasoning
└── tdd-refactorer (REFACTOR) — sees passing code, must keep tests green
The critical bit is that each subagent runs in its own context. The test writer cannot peek at how the implementer plans to solve the problem. The implementer cannot see the notes the test writer wrote about "edge cases we should probably cover."
A minimal skill definition for the writer:
# .claude/agents/tdd-test-writer.md
You are a test writer. Your ONLY job is to write failing tests.
Rules:
- You CANNOT see or reference any implementation files
- Write tests based ONLY on the feature description
- Tests must cover happy path + edge cases (nulls, empty, negative, overflow)
- Run tests and confirm they FAIL before finishing
And the implementer, which is deliberately narrow:
# .claude/agents/tdd-implementer.md
You are an implementer. Your ONLY job is to make tests pass.
Rules:
- Write the MINIMUM code to make tests pass
- Do NOT refactor or optimize (that's the refactorer's job)
- Do NOT modify test files
- Run tests and confirm they PASS before finishing
Why this works: the "design intent" that a normal Claude Code session leaks between phases now cannot leak. The test author writes what the feature should do; the implementer writes what makes those tests pass; the refactorer cleans up without breaking anything. It is exactly the discipline TDD was invented to enforce, made mechanical by the fact that context boundaries are hard walls now.
The other nice property: you can CLAUDE.md this policy at the repo level and forget about it.
## Testing policy
- All new features go through the TDD subagent chain
- No test code and implementation code in the same Claude session
- Skip TDD only for one-line typo fixes and docs
Layer 2: Quinn, the Playwright MCP QA agent (PR time)
The second layer runs at pull-request time and does the thing no unit test can do: click around the UI like a person who has never seen the code.
Alexop's Quinn pattern is the reference here. Quinn is an AI QA engineer defined by a prompt: a 12-year-veteran QA persona, instructed to test only through the UI, never look at source, always exercise the mobile viewport (375x667), and file findings with reproduction steps.
The mechanism that makes Quinn possible in 2026 is Playwright MCP. The server ships dozens of browser-automation tools exposed over MCP, and the important detail is that it hands the agent an accessibility tree as structured JSON rather than a screenshot. No vision model needed. Claude Code reads the tree, decides what to click, tells Playwright to click it, reads the resulting tree, and so on. Deterministic, cheap, and stable across page redesigns.
A working .github/workflows/ai-qa.yml:
name: AI QA (Quinn)
on:
pull_request:
types: [labeled]
jobs:
qa:
if: github.event.label.name == 'qa-review'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm install && pnpm build
- name: Start dev server
run: pnpm dev &
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--mcp-config '{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--headless", "--no-sandbox"]
}
}
}'
prompt: |
You are Quinn, a senior QA engineer with 12 years of experience.
Test the application at http://localhost:3000.
DO NOT look at source code. Test only through the UI.
Test on desktop (1280x720) and mobile (375x667).
Try edge cases: empty inputs, huge inputs, negatives, unicode names.
Report each bug with a screenshot and a reproduction sequence.
Two flags in that config carry more weight than they look: --headless --no-sandbox on the MCP server side makes it run under GitHub Actions' container without display. Claude Code itself runs non-interactively under claude-code-action@v1, so the action handles the "exit when done" part for you.
Quinn is not a replacement for scripted E2E tests. Scripted E2Es catch known-bad regressions. Quinn catches the class of bug where "nobody thought to write a test for that particular sequence." Two different tools, two different failure modes.
The trigger being types: [labeled] matters. Quinn burns real tokens, and running her on every push to every PR gets expensive. Making her opt-in via a qa-review label means the team pays for her when the PR is close to ready, not while it is still churning.
Layer 3: claude-code-action on the CI gate (merge time)
The third layer is the boring one, which is why it works. Once a PR passes Quinn, one more Claude Code invocation runs on the CI gate, looking at the diff against the existing test suite.
# .github/workflows/claude-test.yml
name: Claude Code Test Gate
on:
pull_request:
types: [opened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: pnpm install
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Analyze the changes in this PR. Then:
1. Run the existing test suite: pnpm test
2. Identify code paths in this diff not covered by any test
3. Propose specific test cases for the gaps (do not write them yet)
4. Post the analysis as a PR comment
This is the "did we miss anything" pass. It does not write tests (that would push us back into homework-grading), it flags gaps. A human reads the comment, decides which gaps matter, and either accepts them or opens follow-up tickets.
Anthropic ships claude-code-action@v1 as the official integration, and the setup is one CLI call away — /install-github-app from Claude Code walks you through installation, permissions, and mentioning @claude on issues or PRs to get responses.
Between the three layers, the diff of who runs which tests when looks like this:
| Layer | When | Who tests | What they use |
|---|---|---|---|
| 1. TDD subagents | Dev time (per commit) | Claude Code, subagent per phase | Feature description + failing test |
| 2. Quinn (Playwright MCP) | PR time (label-triggered) | Claude Code as QA persona | Browser via MCP, no source access |
| 3. claude-code-action | Merge time (every push) | Claude Code as reviewer | Diff + existing suite + coverage report |
Notice what each layer has that the others don't. Layer 1 has the requirements but not the implementation. Layer 2 has the running app but not the source. Layer 3 has the diff but is forbidden from writing tests. Each layer's blindness is what makes it useful.
Why selector reliability improves under this shape
Small side-benefit worth mentioning. When Quinn writes down what she clicked (for the reproduction steps she files with bugs), she writes it in Playwright MCP's accessibility-tree vocabulary. That means:
// The kind of selector Quinn produces
await page.getByRole('button', { name: 'Log in' }).click();
// Not the kind you get from asking Claude to "look at the DOM"
await page.click('.btn-primary.mt-4.px-6');
The role-plus-text form survives redesigns, works with screen readers, and doesn't break the first time someone renames a Tailwind class. When you promote Quinn's reproduction steps into permanent E2E tests (which you should, whenever the bug is worth guarding against), you get selector stability for free.
What each layer is doing that the others can't
The reason context pollution is a whole category of AI-testing bug is that most teams try to solve testing with more AI in one place. More prompts, better instructions, longer CLAUDE.md rules about "please check your own work." None of that helps. The problem is information leakage between phases, and stronger prompts inside one leaky pipeline still leak.
Three isolated contexts, three different data diets:
- The test writer eats requirements, produces tests.
- Quinn eats the running app, produces bug reports.
- The CI reviewer eats the diff, produces gap analysis.
Roll any two of those together and you get a subtle regression somewhere. Keep them separate and each does its narrow job well.
The Anthropic tooling to make this work — subagents, Playwright MCP, claude-code-action — all landed in 2025-2026. The pattern was possible before, in the way most patterns are "possible" before the tooling; now it is a config-file exercise, not a research project.
If your team already has one Claude Code session doing all three jobs, split them next sprint. The tests will start finding things the single-session setup silently endorsed.
The full Claude Code testing playbook (subagent memory design, when to use skills vs commands vs subagents, how to structure a repo so the three layers stay independent, and what to do when Quinn files a false positive) is written up in Claude Code Mastery. Chapter 10 covers test automation end-to-end, including the TDD-subagent split this article is a distillation of.
References
- The New Stack. "Claude Code and the Art of Test-Driven Development." 2025.
- alexop.dev. "Building an AI QA Engineer with Claude Code and Playwright MCP." 2025.
- alexop.dev. "Forcing Claude Code to TDD: An Agentic Red-Green-Refactor Loop." 2025.
- Anthropic. "Claude Code GitHub Actions." Official docs, 2026.
- Microsoft Playwright team. "Playwright MCP Server." github.com/microsoft/playwright-mcp, 2026.
Top comments (0)