1. Install the required NuGet packages
External Godot tests require all three packages: Gua.Core, Gua.Testing, and Gua.Testing.Godot. Run these commands in the test project directory. Add --version 0.12.0 when you want to pin the current release line.
dotnet add package Gua.Core --version 0.12.0
dotnet add package Gua.Testing --version 0.12.0
dotnet add package Gua.Testing.Godot --version 0.12.0
# When using NUnit
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package NUnit
dotnet add package NUnit3TestAdapterIf Godot is not on PATH, set its executable explicitly.
$env:GODOT_EXECUTABLE = "C:\path\to\Godot_v4.7-stable_mono_win64_console.exe"
dotnet test YourGame.Tests.csproj2. Launch and operate a scene semantically
using Gua.Testing;
using Gua.Testing.Godot;
using var host = GodotSceneTestHost.Load(
"res://Main.tscn",
new GodotSceneTestHostOptions {
ProjectPath = projectPath,
UseAvailableBridgePort = true,
StartupResetPolicy = GuaResetPolicy.Strict,
TeardownResetPolicy = GuaResetPolicy.Strict,
});
GuaAssertions.GetByRole(
host.Context, "button", "Start Game"
).ToBeVisible();
await GuaAssertions.GetById(
host.Context, "start"
).ClickAsync();
await GuaAssertions.WaitForTextAsync(
host.Context, "loading", "Loading..."
);3. Operate forms
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");Four rules for reliable tests
Set UseAvailableBridgePort = true to avoid collisions in parallel runs.
Detect leaked requests and events at startup and teardown.
Wait for nodes, values, or stable snapshots instead of sleeping for a fixed time.
Capture the UI tree, logs, stdout/stderr, and optional PNG evidence.
1. Reserve a port per test
If every test uses 8765, only one Godot process can bind it during a parallel run. Let the host reserve a loopback port and pass it to the child as GUA_BRIDGE_PORT.
using var host = GodotSceneTestHost.Load(
"res://Main.tscn",
new GodotSceneTestHostOptions
{
ProjectPath = projectPath,
UseAvailableBridgePort = true,
});
// The reserved URL is assigned to the child and RemoteContext.2. Reset strictly before and after the test
Do not carry nodes, requests, or events from one test into the next. A strict teardown reports unconsumed work instead of silently deleting it.
using var host = GodotSceneTestHost.Load(
"res://Main.tscn",
new GodotSceneTestHostOptions
{
ProjectPath = projectPath,
UseAvailableBridgePort = true,
StartupResetPolicy = GuaResetPolicy.Strict,
TeardownResetPolicy = GuaResetPolicy.Strict,
CaptureDiagnosticsBeforeTeardown = true,
CleanupAfterLeakReport = true,
});3. Wait for observed state, not elapsed time
Task.Delay(1000) wastes time on a fast machine and may still be too short on a slow one. Wait directly for the state that should follow the action.
await GuaAssertions.GetById(host.Context, "start")
.ClickAsync();
// Re-fetch snapshots until the node is visible.
await GuaAssertions.WaitForVisibleAsync(
host.Context,
"loading",
timeout: TimeSpan.FromSeconds(3),
pollInterval: TimeSpan.FromMilliseconds(20));
// Optionally require three unchanged rendered frames.
await GuaAssertions.WaitForStableSnapshotAsync(
host.Context,
stableFrames: 3);4. Capture diagnostics when an assertion fails
Write the final UI tree, operation history, logs, and Godot stdout/stderr to artifacts/gua. In CI, upload that directory as a test artifact.
using var diagnostics = host.CreateDiagnosticsSession(
TestContext.CurrentContext.Test.FullName,
outputDirectory: Path.Combine(
TestContext.CurrentContext.WorkDirectory,
"artifacts", "gua"));
using var assertionScope = GuaAssertionScope.Use(
new GuaAssertionOptions
{
DiagnosticsSession = diagnostics,
});
// A failure preserves the primary exception and writes diagnostics.
GuaAssertions.GetByRole(
host.Context, "button", "Start Game"
).ToBeVisible();With GitHub Actions, pass artifacts/gua/** to actions/upload-artifact after the test step so evidence remains available from a failed run.
Run tests with GitHub Actions
The public link1345/gua-tester action installs Godot on a Windows runner, links the latest released Gua add-on, sets GODOT_EXECUTABLE, and runs dotnet test.
name: Godot Gua UI tests
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Run Gua UI tests
uses: link1345/gua-tester@v1.2
with:
project-path: game
test-project: tests/GuaTester.Tests.csproj
godot-version: "4.7"
godot-status: stable