DEV Community

My AI QA agent said "all features working." The canvas was blank. Here's what it was actually seeing.

Back when I first delegated QA to an AI agent, it signed off on a tool with "all features working, pass." I opened the tool myself. The canvas was blank. The AI wasn't lying. In the environment it was looking at, the tool genuinely appeared to work.

I run visual QA across a large fleet of web tools using Claude + Chrome MCP, and this class of false positive traced back to exactly two causes.

Cause 1: requestAnimationFrame stops in hidden tabs

Chrome MCP typically operates in hidden (background) tabs. Browsers throttle requestAnimationFrame aggressively in hidden tabs to save power. QA a canvas or animation feature in that environment and here's what happens: the JS executes, no errors fire, event handlers respond - and the render finishes at zero frames. The AI looks at a screenshot plus clean JS results and concludes "animation started, no errors, pass."

Code executing and pixels rendering are different events, and a hidden tab erases the distinction.

Reproduction test before publishing (July 10, 2026)

I didn't want this to be a "back in my day" post, so I re-ran the measurement right before publishing. Opened a tab via Chrome MCP, ran an rAF counter:

Measurement Result
document.visibilityState hidden
rAF fires in 3.37s 0
setInterval(100ms) fires, same window 4 (expected: 33)
setTimeout(3000ms) actual delay 3373ms

rAF wasn't throttled. It was stopped - zero frames. And as a bonus finding: setInterval was decimated to roughly 1/8 of its expected rate, and even setTimeout drifted. So it's not just rAF-based animation; timer-driven logic in general cannot be trusted in a hidden tab.

Fixes

  • Open tabs with active: true so they're visible
  • Trigger the interaction in a visible tab, wait a few seconds, then screenshot and confirm the rendered output actually exists
  • If you must evaluate in a hidden tab, the report has to say "dynamic rendering not visually observed." Never silently pass it

Cause 2: confusing "healthy JS state" with "the feature works"

The second trap shows up in AI QA reports as pass rationale like this:

onclick wiring confirmed. Zero JS errors. Library loaded. โ†’ PASS

All of that describes code-path health, not feature behavior. Wiring can be correct while nothing renders (Cause 1). Errors can be absent while the downloaded file is empty.

My fix: grep the template for dynamic features first, then require a behavior check for every hit.

Found in code Required behavior check
<canvas> Interact in a visible tab โ†’ screenshot after a delay shows rendered content
<input type="file"> Upload a dummy file โ†’ preview src is non-empty
download attr / toBlob Press the button โ†’ observe Blob creation
mousedown / touchstart Fire the event โ†’ state/transform actually changed
requestAnimationFrame Transform values differ after time passes

QA reports must include a feature-coverage table, and "exists in code but behavior unverified" can never be graded as pass. That's the whole rule. That alone cut the false positives dramatically.

Bonus: screenshot-vs-DOM contradiction checks

A side benefit of visual QA: cross-checking what the code claims against what the pixels show. "The class is text-secondary (cyan) but it renders gray" - cascade and override bugs get caught with evidence attached. Humans tend to stall at "the color feels off somehow."

Limits and caveats

Hidden-tab throttling is a browser power-saving behavior, so Chrome could change it. My operating principle isn't "hidden tabs are bad" - it's "dynamic rendering must be verified in a visible state."

Behavior checks cost time and tokens. Running them on every feature of every tool is too heavy; I only require them for tools where the grep finds dynamic features.

Verified: April-June 2026 in production, re-reproduced July 10, 2026. Environment: Claude + Chrome MCP / Windows 11.

Comments

No comments yet. Start the discussion.