GGua REFERENCE
UNITY / TESTING

Testing UI

Gua.Testing.Unity starts Unity Editor Play Mode or a desktop Mono Player and connects the shared IGuaContext to its bridge.

1. Add test packages

Gua.Testing.Unity is published on NuGet. These commands resolve the latest compatible stable packages; commit the versions written to the project and keep the three Gua packages on the same version line.

Add the Unity test hostpowershell
dotnet add package Gua.Core
dotnet add package Gua.Testing
dotnet add package Gua.Testing.Unity

Align the game integration and related Gua package versions. See version management for recording and pinning the result.

What each package adds

CodeMeaning
dotnet add package Gua.CoreAdds shared Gua types and the native runtime boundary.
dotnet add package Gua.TestingAdds the same semantic locators, actions, waits, assertions, and diagnostics used with Godot.
dotnet add package Gua.Testing.UnityAdds Unity Editor/Player build, startup, bridge connection, and teardown.

2. Start a Player or Editor

Build and test a rendered Playercsharp
using Gua.Core;
using Gua.Testing;
using Gua.Testing.Unity;

var player = UnityPlayerBuilder.Build(@"game/Assets/Scenes/Title.unity");
using var host = UnitySceneTestHost.LoadRenderedPlayer(
    player, UnitySceneTestHostOptions.StrictIsolation);
GuaAssertions.GetByRole(
    host.Context, "button", "Start Game"
).ToBeVisible();

await GuaAssertions.GetById(
    host.Context, "start"
).ClickAsync();

await GuaAssertions.WaitForTextAsync(
    host.Context, "loading", "Loading..."
);

var screenshot = host.CaptureScreenshot();

Read the Unity test in execution order

CodeMeaning
using Gua.Core;Imports shared runtime types such as reset policies.
using Gua.Testing;Imports semantic locators, actions, waits, and assertions.
using Gua.Testing.Unity;Imports the Unity Player builder and scene host.
UnityPlayerBuilder.Build("...Title.unity")Runs Unity Editor in batch mode and builds a temporary Mono Player for Auto or the selected platform.
LoadRenderedPlayer(player, StrictIsolation)Starts a rendered Player, connects to its bridge, and checks isolation at startup and teardown.
GetByRole(..., "button", "Start Game")Finds the button semantically across UI Toolkit, uGUI, or TMP.
.ToBeVisible()Asserts visibility in the current semantic snapshot.
GetById(..., "start").ClickAsync()Clicks the stable ID and waits for Unity listener completion.
WaitForTextAsync(..., "loading", "Loading...")Refreshes snapshots until the game publishes the expected result.
host.CaptureScreenshot()Requests a PNG after a later rendered frame.
using var hostCloses the bridge and child process when the scope ends.
Entry pointUse
LoadPlayerExisting -batchmode -nographics Player for fast semantic tests
LoadRenderedPlayerRendered existing Player for screenshots and visual tests
LoadEditorOpen a scene in Editor Play Mode
BuildAndLoadPlayerBuild and start the selected desktop Mono Player

3. Use the same semantic API as Godot

host.Context is the engine-neutral IGuaContext. Tests use the same locators and waits instead of branching on Unity UI technology.

Input, focus, and observed statecsharp
var name = GuaAssertions.Query(host.Context)
    .ByRole("textbox").Within("LoginForm");

await name.SetValueAsync("alice");
await name.FocusAsync();
await GuaAssertions.PressKeyAsync(host.Context, "Enter");

await GuaAssertions.GetById(host.Context, "RememberMe")
    .SetCheckedAsync(true);
await name.ByValue("alice").ResolveAsync();

What each semantic operation does

CodeMeaning
Query(host.Context).ByRole("textbox")Queries Unity UI through the shared textbox role.
.Within("LoginForm")Scopes the locator; each action resolves a current strict single match.
SetValueAsync("alice")Sets a UI Toolkit, uGUI, or TMP text input through one action.
FocusAsync()Uses Unity focus so the next global key gesture has a target.
PressKeyAsync(..., "Enter")Sends key-down and key-up to the focused textbox.
SetCheckedAsync(true)Checks a UI Toolkit or uGUI/TMP toggle.
ByValue("alice").ResolveAsync()Waits for a later published frame to expose the value.

Four rules for reliable Unity tests

Choose the host for the purpose

Use headless for semantic tests and a rendered Player or Editor for screenshots.

Wait for state

Observe nodes, text, and values instead of guessing Unity frame timing with sleeps.

Isolate each test

Use available ports and strict reset to detect collisions and leaked work.

Keep failure evidence

Retain the UI tree, history, Unity log, process data, and optional PNG.

Visual comparison and recording

Gua.Testing.Visual and Gua.Testing.Recording are engine-neutral packages, so both work with the host.Context supplied by Gua.Testing.Unity. Use LoadRenderedPlayer or Editor Play Mode when Visual needs a PNG. Recording operates on semantic actions and also works with a headless Player.

Use shared packages with a Unity hostcsharp
using Gua.Testing.Recording;
using Gua.Testing.Visual;

var comparison = await GuaVisualAssertions.ExpectScreenshotAsync(
    host.Context, "title-screen", new() {
        BaselineDirectory = "baselines",
        ArtifactDirectory = "artifacts/gua",
    });

var recorder = new GuaRecorder(host.Context);
await recorder.ClickAsync(new(Id: "start"));

What the Visual and Recording calls do

CodeMeaning
using Gua.Testing.Visual;Imports engine-neutral PNG baseline comparison.
ExpectScreenshotAsync(host.Context, "title-screen", ...)Captures Unity and compares it with the reviewed title-screen baseline.
BaselineDirectory = "baselines"Selects the version-controlled expected images.
ArtifactDirectory = "artifacts/gua"Stores expected, actual, diff, and manifest on mismatch.
new GuaRecorder(host.Context)Records semantic operations sent to the same Unity context.
recorder.ClickAsync(new(Id: "start"))Performs and records a request-correlated click step.

Parallel isolation

UseAvailableBridgePort defaults to true. The host selects a loopback port and passes it as GUA_BRIDGE_PORT, avoiding a fixed collision on port 8765. It closes the listener before Unity binds, so a small selection-to-bind race remains under extreme concurrency.

Explicit optionscsharp
var options = new UnitySceneTestHostOptions {
    UnityExecutablePath = @"C:/Program Files/Unity/Hub/Editor/6000.5.3f1/Editor/Unity.exe",
    ProjectPath = @"C:/src/game",
    UseAvailableBridgePort = true,
    TeardownResetPolicy = GuaResetPolicy.Strict,
    CaptureDiagnosticsBeforeTeardown = true,
};

What each Unity host option means

CodeMeaning
UnityExecutablePathSelects Unity.exe explicitly before environment and Hub discovery.
ProjectPathPoints to the Unity root containing Assets and ProjectSettings.
UseAvailableBridgePort = trueSelects a loopback port and passes it as GUA_BRIDGE_PORT.
TeardownResetPolicy = StrictReports unconsumed requests and events instead of deleting them.
CaptureDiagnosticsBeforeTeardown = trueCaptures evidence before terminating Unity when teardown fails.

Diagnostics

CreateDiagnosticsSession() adds the single -logFile Unity log, PID, bridge URL, and log path to shared artifacts. stdout and stderr are not redirected separately. Strict teardown reports leaked requests or events instead of deleting them silently.

Capture Unity evidence on assertion failurecsharp
using var diagnostics = host.CreateDiagnosticsSession(
    TestContext.CurrentContext.Test.FullName,
    outputDirectory: "artifacts/gua",
    captureScreenshot: true);

using var assertionScope = GuaAssertionScope.Use(
    new GuaAssertionOptions {
        DiagnosticsSession = diagnostics,
    });

GuaAssertions.GetByRole(
    host.Context, "button", "Start Game"
).ToBeVisible();

What the diagnostics setup does

CodeMeaning
host.CreateDiagnosticsSession(...)Adds Unity process logs and metadata to shared Gua diagnostics.
TestContext...Test.FullNameUses the full NUnit test name as the artifact identity.
outputDirectory: "artifacts/gua"Selects the evidence directory.
captureScreenshot: trueAdds an on-demand PNG; use a rendered host.
GuaAssertionScope.Use(...)Associates failures in the scope with this diagnostics session.
.ToBeVisible()Keeps the assertion as the primary error while collecting evidence.

SceneTimeout currently serves mainly as the default screenshot timeout; it does not wait for scene transitions automatically.

Wait for actionable UI

Gua.Testing locators resolve a current strict single match when used. Actions wait for visible/enabled state and the advertised action, then correlated host completion. Multiple matches fail immediately. Resolution, enqueue, and completion share one timeout budget; rejected enqueue and host failure are not automatically retried.

Click, then wait for the next screencsharp
using Gua.Testing;

var play = GuaAssertions.Query(host.Context).ByRole("button", "Play");
await play.ClickAsync(timeout: TimeSpan.FromSeconds(5));
await GuaAssertions.Query(host.Context).ById("game-screen").ResolveAsync();

ResolveAsync waits for the next node to exist; add the state assertion your test requires. Locator API and timeout rules.

Compare semantic state without pixels

Add Gua.Testing.Snapshots to compare UI IDs, hierarchy, text, values, and state against reviewed JSON baselines. It works with host.Context, including a headless host. Add Gua.Testing.Visual separately when rendered pixels matter. Installation, masks, and baseline update example.

Add semantic comparisonpowershell
dotnet add package Gua.Testing.Snapshots

Save test evidence with Gua.Testing Trace

Trace belongs to Gua.Testing; no extra Trace package is needed. Enable it for a test scope, complete it with the test's actual outcome, and create an offline HTML report at a new output path.

Record and inspect a testcsharp
using Gua.Testing;

await using var trace = new GuaTraceSession(new() {
    OutputDirectory = "artifacts/traces",
    SavePolicy = GuaTraceSavePolicy.Always,
});
using var scope = GuaAssertionScope.Use(new() { Trace = trace });
try {
    await GuaAssertions.Query(host.Context).ById("play").ClickAsync();
    await trace.CompleteAsync(GuaTraceOutcome.Passed);
} catch {
    await trace.CompleteAsync(GuaTraceOutcome.Failed);
    throw;
}
var report = GuaTraceReport.WriteHtml(trace.ArtifactPath, "artifacts/report.html");
// Check report.Succeeded and trace.Status separately from the test result.

Trace records evidence; it does not replay the flow or change its result. Capture UI/World or Observe values explicitly when required, and handle sensitive data before recording. Trace capture APIs · Open a saved Trace in Inspector.

Support when selecting a host

Unity 6000.5.3f1 desktop Mono Players are implemented and verified on Windows, Linux, Intel macOS, and Apple Silicon macOS. Editor Play Mode is verified on Windows, Linux, and Apple M4; native Intel macOS Editor is implemented but unverified. See Unity APIs and limits.