GGua REFERENCE日本語GitHub ↗
UNITY / TESTING

Testing UI

Gua.Testing.Unity starts Unity Editor Play Mode or a Windows 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 stable releases; 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

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 Windows x64 Mono Player.
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 a Windows x64 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").Get();

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

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

What each semantic operation does

CodeMeaning
Query(host.Context).ByRole("textbox")Queries Unity UI through the shared textbox role.
.Within("LoginForm").Get()Scopes the query and requires exactly one 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.
WaitForValueAsync("alice")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.