Techniques for Making AI Agents Your QA Team for Reliable Modernization
Agents can port a legacy screen in minutes, so the bottleneck moves to proving the new code behaves like the old. These are the techniques that turn agents into a QA team you can trust: layered gates, characterization te
Agents can port a legacy screen in minutes, so the bottleneck moves to proving the new code behaves like the old. These are the techniques that turn agents into a QA team you can trust: layered gates, characterization tests, maker-checker review and visual diffs.
AI agents can now port a legacy screen in minutes. That moves the bottleneck. The hard question in a legacy system migration is no longer "can we write the new code?" but "how do we know it behaves exactly like the old code?"
Manual QA can't keep up with agent-speed porting, and a passing build proves very little. The answer is to point agents at verification too, and to set them up so they find problems instead of approving them. Here are the techniques that make an agent QA team you can trust.
Why migration testing is different
Normal QA asks whether software meets a spec. Modernization QA asks whether the new system matches the old one, and the old one usually has no spec. Its real behavior is spread across templates, watchers, global events, server quirks and years of bug fixes nobody wrote down.
So the goal is parity: same outputs, same side effects, same edge cases, unless a difference is a deliberate, recorded decision. Every technique below serves that goal.
βProgram testing can be used to show the presence of bugs, but never to show their absence.β β Edsger W. Dijkstra, Notes on Structured Programming
Technique 1: Five layers, cheapest first
Don't send every change to the most expensive check. Stack the gates so cheap, deterministic checks catch the obvious problems before an agent or a person spends time on them:
- Static gates. Types, lint, build, and a banned-API list (no jQuery in the React tree, no direct DOM access, no old global stores).
- Characterization tests. Tests that record what the old code does today, written before the port.
- Behavior parity review. A separate agent compares old and new logic and reports differences.
- Visual and end-to-end diff. Screenshots, user flows and network calls, old against new.
- Human sign-off. People review a risk-ranked sample, not every line.
Nothing merges on the porter's word. Each layer is a gate, and a failure sends the unit back.
No single layer is close to perfect, and none needs to be. With modest catch rates for each layer, the model above lets through about 6% of behavior bugs, against 28% when you stop at tests. The layers only multiply like this when they look at different things, which is why each one has a distinct job.
Technique 2: Write characterization tests before the port
Have a test-writer agent read the old unit and write tests that pin down its current behavior: rendered output for known inputs, events fired, API calls made, validation messages shown, what happens on empty, error and slow responses. These tests run against the old code first and must pass there.
Then the same tests run against the new code. Because they were written from the original, not from the port, they can't inherit the porter's misunderstandings.
Lock the tests once they pass on the old code. If a porter edits a characterization test to make it pass, that is a finding, not a fix.
βTo me, legacy code is simply code without tests.β β Michael Feathers, Working Effectively with Legacy Code
Technique 3: Maker and checker never share a context
An agent reviewing its own work mostly agrees with itself. It already has a story for why every line is right. The reviewer has to be a separate agent with a fresh context that sees only the evidence: the original code, the new code and the test output.
Give the reviewer a narrow job and a strict output format. Ask for concrete findings, each with a file, a line and a failing scenario, and nothing else:
Compare src/legacy/orders/OrderList.js with src/app/orders/OrderList.tsx.
Report only behavior differences a user or API consumer could observe.
For each: file:line, the input or scenario, old result, new result.
Do not comment on style. Do not suggest refactors.
If you find none, say so.
Run a second pass on the findings themselves. A verifier agent tries to reproduce each one, and only confirmed findings go back to the porter. This keeps false alarms from wasting porter time and teaching the team to ignore the reviewer.
The same logic applies to choosing models for each layer. Use a different model family for the reviewer than for the code it checks, put cheap fast models on high-volume checks, and keep top-tier models for the judgment calls.
Technique 4: Diff what the user sees and what the server sees
Logic review misses things a user notices immediately: a shifted layout, a missing loading spinner, a date in the wrong format. An agent driving a browser can run the same flow on the old and new app, take screenshots at each step and compare them.
Record network traffic too. If the old screen made three API calls with certain parameters and the new one makes five, or drops a header, something changed. Network diffs catch problems that never show up on screen until production traffic hits them.
Technique 5: Spend human time where risk is highest
Agents make human review cheaper, not unnecessary. Have the orchestrator rank units by risk: payments, auth, permissions, data writes and anything with a history of incidents go first. People review those in full, and a sample of the rest.
In this model, risk ranking cuts senior review time by about two thirds while still reviewing every high-risk unit in full. Agents run the first four layers on every unit regardless.
Every deliberate difference, such as a bug you chose to fix instead of copying, goes into a decisions log with a reason. When parity tests fail later, the log tells the reviewer which differences are expected.
Technique 6: Measure the QA team itself
An agent QA team needs QA too. Seed a few known bugs into ported units, such as a dropped validation rule, a changed sort order or a missing error state, and check that the layers catch them. If a seeded bug slips through, you know which layer to strengthen before a real one does.
Track the reviewer's precision as well: the share of its findings that the verifier confirms. A reviewer that cries wolf gets ignored, and a reviewer that never finds anything is probably not looking hard enough. Both numbers belong on the project dashboard next to units migrated.
- Seeded-bug catch rate: which layer caught each planted defect, and which missed it.
- Reviewer precision: confirmed findings divided by all findings.
- Escaped defects: bugs found after merge, traced back to the layer that should have caught them.
What this looks like in MigrateX
Our MigrateX plugins build this in. Each framework plugin has a read-only parity-reviewer agent that checks one migrated piece against its original for behavior, markup, data and timing differences before the old code is removed. It can't edit code, so it can only report. The porter fixes, the reviewer checks again, and an engineer signs off.
Key takeaways
- Modernization QA is about parity with an undocumented system, not meeting a spec.
- Stack gates from cheap to expensive: static checks, characterization tests, parity review, visual and network diffs, human sign-off.
- Write tests from the old code before the port, and lock them.
- Keep the reviewer in a separate context, ask for concrete findings, and verify findings before acting on them.
- Spend human review on the riskiest units, and log every deliberate difference.
Related reading: how multi-agent AI speeds up application migration, 10 legacy modernization mistakes to avoid and software migration lessons from Shopify's AI rewrite.
Originally published on the CobuildX blog.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.



