Visual regression
screenshot_compare captures the current state of a widget (or the first visible top-level window) and compares it pixel-by-pixel against a baseline image you provide. On mismatch you get back a diff image with changed pixels highlighted in red.
How comparison works
Two knobs control strictness:
| Param | Default | Meaning |
|---|---|---|
threshold | 0.02 | Maximum fraction of pixels allowed to differ. 0.02 = the images match if ≥ 98% of pixels are equal. |
channel_tolerance | 10 | Per-channel slack (0–255). A pixel counts as different only if R, G, or B differs by more than this. Absorbs antialiasing noise. |
Both images are normalized to ARGB32 before comparison. A size mismatch is always a failure (match: false, similarity: 0.0, with a reason string) - no scaling is attempted.
Response fields: match, similarity (0.0–1.0), diff_pixels, total_pixels, and - only on mismatch - diff_image (base64 PNG, your current capture with differing pixels overlaid in red).
Creating baselines
Capture the widget in a known-good state and save it:
await client.save_baseline("baselines/main_window.png") # whole window
await client.save_baseline("baselines/chart.png", path="MainWindow/chartView")Commit baselines to your repository next to the tests. Regenerating them is a deliberate, reviewable act - a baseline change in a diff shows you exactly what the UI change was.
Comparing in tests
compare_screenshot_file reads a baseline from disk, sends it for comparison, and can write the diff image on failure:
async def test_chart_renders(vericue_client):
result = await vericue_client.compare_screenshot_file(
"baselines/chart.png",
path="MainWindow/chartView",
threshold=0.02,
diff_output="artifacts/chart_diff.png", # written only on mismatch
)
assert result["match"], (
f"Similarity {result['similarity']:.4f}, "
f"{result['diff_pixels']}/{result['total_pixels']} pixels differ - "
"see artifacts/chart_diff.png"
)Upload the diff_output directory as a CI artifact so failures are diagnosable without re-running locally.
Errors
2016 invalid_baseline- thebaselineparam wasn't a decodable image.2007 screenshot_failed- no visible top-level window to capture.2001 object_not_found-pathdidn't resolve.
Making it reliable in CI
Pixel comparison is exact by nature; the environment must be reproducible:
- Pin the platform. Fonts, DPI scaling, and style engines differ across OSes and even distro versions. Baselines taken on a developer's Ubuntu desktop will not match a Windows CI runner. Keep per-platform baseline directories (
baselines/linux/,baselines/windows/) if you test on more than one. - Use the offscreen platform in CI:
QT_QPA_PLATFORM=offscreenrenders deterministically without a display server and is what the veriCue pytest fixtures use by default. - Fix the window size before capturing - a size mismatch is an automatic failure. Set an explicit
resize()in the app under test or compare a specific widget bypathinstead of the whole window. - Freeze animation state. Compare after animations settle (
wait_for_propertyon the relevant state, or disable animations in a test build). - Prefer widget-level baselines over whole-window ones: they are smaller, fail less often for unrelated reasons (clock labels, status bars), and localize failures to the component that changed.
- Loosen
channel_tolerance(e.g.15–20) before looseningthreshold- it absorbs rendering noise without letting real layout changes through.
Updating baselines
When a UI change is intentional, regenerate and commit:
# tools/update_baselines.py
async with VeriCueClient() as client:
await client.connect("127.0.0.1", 4242)
await client.save_baseline("baselines/linux/chart.png",
path="MainWindow/chartView")Review the image diff in the pull request like any other code change.

