Skip to content

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:

ParamDefaultMeaning
threshold0.02Maximum fraction of pixels allowed to differ. 0.02 = the images match if ≥ 98% of pixels are equal.
channel_tolerance10Per-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:

python
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:

python
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 - the baseline param wasn't a decodable image.
  • 2007 screenshot_failed - no visible top-level window to capture.
  • 2001 object_not_found - path didn'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=offscreen renders 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 by path instead of the whole window.
  • Freeze animation state. Compare after animations settle (wait_for_property on 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 loosening threshold - it absorbs rendering noise without letting real layout changes through.

Updating baselines

When a UI change is intentional, regenerate and commit:

python
# 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.

Released under a commercial licence. Privacy · Terms