GGua REFERENCE日本語GitHub ↗
GODOT / TESTING

Testing UI

Launch a Godot process and connect to its live semantic UI tree. Locate controls by role, text, and ID, then wait for observable results instead of clicking coordinates.

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.

PowerShellpowershell
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 NUnit3TestAdapter

If Godot is not on PATH, set its executable explicitly.

PowerShellpowershell
$env:GODOT_EXECUTABLE = "C:\path\to\Godot_v4.7-stable_mono_win64_console.exe"
dotnet test YourGame.Tests.csproj

2. Launch and operate a scene semantically

TitleScreenTests.cscsharp
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

Correlated asynchronous actionscsharp
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

Use an available port

Set UseAvailableBridgePort = true to avoid collisions in parallel runs.

Reset strictly

Detect leaked requests and events at startup and teardown.

Wait for state

Wait for nodes, values, or stable snapshots instead of sleeping for a fixed time.

Keep failure evidence

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.

Launch on an available portcsharp
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.

Strict isolationcsharp
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.

State-based synchronizationcsharp
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.

Enable failure diagnosticscsharp
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.

.github/workflows/godot-gdscript.ymlyaml
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