AI-generated tests carry a signature set of defects — sleep-based waits, tautological asserts, over-mocking, hidden order dependence — and research on LLM-authored tests confirms they flake measurably more than hand-written ones. This playbook is the review rubric that catches those defects at the PR, with mutation testing as the tiebreaker when reviewers disagree.
Tier: Core · Workflow area: Automation engineering
Treat this as a clinic you run on any suite with significant AI authorship: your own pb-08/pb-09 output, a teammate's agent-built PR, or an inherited repo full of generated specs nobody trusts. The rubric turns "this looks off" into named findings; the grep gates automate the cheap half; mutation testing settles the arguments.
When NOT to use:
Run the mechanical gates first — free findings before human minutes:
grep -rn "waitForTimeout\|sleep(\|time\.sleep\|cy\.wait([0-9]" tests/
grep -rn "expect(true)\|toBeTruthy()\s*$\|assert True" tests/
grep -rn "\.only(\|\.skip(" tests/
npx playwright test --repeat-each=3 --workers=4
The repeat run with parallel workers flushes both nondeterminism and order dependence before anyone reads a line.
Apply the anti-pattern rubric. Review each file against the named catalog — findings cite the pattern, not taste:
expect(result).toEqual(result) in disguise via shared helpers. Verdict: assert a consequence the code under test computed, not an input restated.Use an assistant as first-pass reviewer — with adversarial framing:
Review these test files against this anti-pattern catalog: [paste step-2
list]. For each finding: file:line, pattern name, why it can mask a
real failure, and a minimal rewrite. Then answer: which three tests in
this suite would still pass if the feature they test was completely
broken? Explain how.
That final question is the highest-yield prompt in test review — it forces the model to reason about detection power, and its answers are checkable.
Check the checkable claims. For each "would still pass if broken" claim, verify one by hand: stub the feature out or flip its logic and run the named test. Confirmed claims become PR findings with proof attached; unconfirmed ones are discarded — the human verdict is the finding, the model only drafted suspicion.
Settle disputes with mutation testing. When the author insists an assert is strong enough:
npx stryker run --mutate "src/checkout/**/*.ts"
mutmut run --paths-to-mutate src/checkout/ && mutmut results
Surviving mutants in the code a disputed test covers end the argument with data (the full loop is pb-09). Record the score in the PR.
Close the loop into prevention. Every accepted finding becomes a line in the pb-08 rules file or a new grep gate in CI — the clinic's output is a stricter generator next week, not just a cleaner PR today.
Tautological asserts are the pattern assessments probe hardest — practice spotting the mock-echo shape, where the expected value and the arranged value share a source, because coverage tools rate these tests as excellent while they detect nothing.
time.sleep, bare assert on truthy mocks, and fixture-scope leaks (scope="module" state mutated by tests); mutmut is the tiebreaker.--repeat-each into a test-quality CI job so the mechanical half of the clinic runs on every PR without a human; reserve human review for assertion strength.--repeat-each=3 --workers=4 (or pytest -p no:randomly off, randomization on) with zero flakes.AI didn't invent bad tests, it industrialized them — so industrialize the review: named anti-patterns, mechanical gates in CI, one adversarial prompt, and mutation testing as referee. The suite that clears this clinic is safe to grow at generation speed.