When it adds value
| Use visual comparison for | Prefer semantic assertions for |
|---|---|
| Clipping, overlap, and layout shifts | Control existence and visibility |
| Wrong textures, fonts, themes, or overlays | Expected text and values |
| Resolution-dependent layout breakage | State transitions after actions |
Install
dotnet add package Gua.Testing.VisualCreate a baseline deliberately
$env:GUA_UPDATE_BASELINES = "1"
dotnet test tests/Game.Visual.Tests/Game.Visual.Tests.csproj
Remove-Item Env:GUA_UPDATE_BASELINESReview the generated PNG and commit it only when it is correct. Never set this variable in an ordinary pull-request job.
Keep baselines in source control
The comparison below reads from NUnit's test output directory. Add this rule to the test project so committed PNGs under the project-level baselines directory are copied into that output on clean checkouts and CI builds.
<ItemGroup>
<None Update="baselines/**/*.png"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>When creating or updating a baseline, review the generated output PNG and copy the approved file back to the project's baselines directory before committing it.
Compare in normal test runs
using Gua.Testing;
using Gua.Testing.Visual;
// The game publishes this only after transitions and animations are deterministic.
await GuaAssertions.WaitForVisibleAsync(host.Context, "visual-ready");
var baselineVariant = Environment.GetEnvironmentVariable("GUA_VISUAL_VARIANT")
?? "windows-unity-mono-1920x1080";
var result = await GuaVisualAssertions.ExpectScreenshotAsync(
host.Context, "title-screen", new ScreenshotOptions
{
BaselineDirectory = Path.Combine(TestContext.CurrentContext.TestDirectory, "baselines"),
ArtifactDirectory = Path.Combine(TestContext.CurrentContext.WorkDirectory, "artifacts", "gua"),
BaselineVariant = baselineVariant,
PixelThreshold = 0.02f,
MaxDifferentPixelRatio = 0.001,
WaitForStableSnapshot = true,
StableFrames = 3,
});WaitForStableSnapshot checks that the semantic UI snapshot stopped changing; it does not inspect pixels or prove that sprite, shader, caret, or transition animations stopped. First wait for an explicit application-ready condition and make animation state deterministic, as above. PixelThreshold tolerates small per-channel differences; MaxDifferentPixelRatio limits how many pixels may differ. Tune them only from known stable renderer noise.
Failure evidence
| Reason | Files |
|---|---|
pixel_difference | expected.png, actual.png, diff.png, comparison.json |
baseline_missing | actual.png, comparison.json |
dimension_mismatch | expected.png, actual.png, comparison.json |
Set ArtifactDirectory to place visual artifacts beside ordinary Gua diagnostics. Dimension mismatches fail without implicit resizing. Gua.Testing.Visual writes data only and never generates HTML; use the visual-report action to build the static viewer.
Variants, masks, and stability
| Option | Good use | Avoid |
|---|---|---|
BaselineVariant | Name legitimate OS, renderer, and resolution differences | Creating a variant for every failure |
Masks | Exclude a clock or truly random avatar | Masking important UI |
WaitForStableSnapshot | Confirm semantic state remains unchanged after an application-ready condition | Assuming it freezes or detects pixel animation |
Masks = new[]
{
new GuaMaskRectangle(X: 1710, Y: 24, Width: 180, Height: 48), // clock only
};CI policy
- name: Run visual tests
id: visual-tests
shell: pwsh
env:
GUA_VISUAL_VARIANT: windows-unity-mono-1920x1080
run: dotnet test tests/Game.Visual.Tests/Game.Visual.Tests.csproj --configuration Release
- name: Upload visual report
if: failure()
uses: link1345/gua-tester/visual-report@v2.2
with:
artifact-path: artifacts/gua
test-outcome: failure
upload-target: workflowKeep resolution, DPI, fonts, locale, theme, engine version, renderer, and GPU/driver policy deterministic. Separate baseline updates from ordinary CI and require review of the PNG diff.