GGua REFERENCE
GUIDE / RECORDING

Recording guide

Gua.Testing.Recording stores clicks, value changes, selections, and key input as semantic actions instead of screen coordinates, then replays them for regression testing and bug reproduction. It provides engine-independent, host-completion-aware journeys when multi-step flows would be unreliable as fixed-delay macros.

Concrete use cases

SituationValue
Login, configure, and save regression flowVersion a multi-step scenario as one document
Share a bug reproductionUse stable IDs and roles instead of screen coordinates
Run a flow against Godot and Unity fixturesReplay against the engine-independent IGuaContext
Enter a passwordStore a secretKey, never plaintext
Semantic targetID / role / focus→Enqueue actionassign requestId→Host completionsame requestId→recording.jsontarget, timing, revision→Replay + waitresolve every step
Figure 1: Recording waits for host completion instead of treating queue acceptance as success.

Install

Add the package to a scenario test projectpowershell
dotnet add package Gua.Testing.Recording

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

Record a flow

Record logincsharp
using Gua.Testing.Recording;

var recorder = new GuaRecorder(host.Context);
await recorder.ClickAsync(new GuaRecordingTarget(Id: "open-login"));

var emailTarget = new GuaRecordingTarget(
    Role: "textbox", Name: "Email", Scope: "login-dialog");
await recorder.SetValueAsync(
    emailTarget,
    "player@example.com",
    waitCondition: GuaWaitConditions.Visible("email"));

var passwordTarget = new GuaRecordingTarget(Id: "password");
await recorder.SetValueAsync(
    passwordTarget, password,
    sensitive: true, secretKey: "login-password");
await recorder.FocusAsync(passwordTarget);
await recorder.PressKeyAsync("Enter");

GuaRecordingFile.Save("recordings/login.json", recorder.Recording);

A step's waitCondition is evaluated before that step resolves its target and enqueues its action. Here the email step waits for the dialog's email node after the preceding click. SetValueAsync does not focus its target, so the password receives an explicit focus action before the untargeted PressKeyAsync sends Enter to the current focus. The recorder stores these actions, monotonic relative timing, revisions, and request IDs.

Choose stable targets

TargetUse whenRule
IdThe game exposes a stable IDPreferred; keep it stable through scene refactors
Role + NameAn accessible name is uniqueAdd Scope when names repeat
CurrentFocusSending a key to current focusValid only for press_key
Coordinate fallbackImporting legacy dataRejected by default

Prefer conditions to fixed delays

With the default PreferConditions mode, a step with a condition waits for semantic state instead of repeating its recorded delay. Conditions include visible, hidden, enabled, disabled, focused, unfocused, checked, unchecked, text, and value.

Conditionscsharp
GuaWaitConditions.Visible("login-dialog")
GuaWaitConditions.Enabled("submit")
GuaWaitConditions.Text("status", "Ready")
GuaWaitConditions.Value("volume", "0.8")

Replay with secret injection

Replay a saved flowcsharp
var recording = GuaRecordingFile.Load("recordings/login.json");

var result = await GuaReplayer.ReplayAsync(
    host.Context, recording, new GuaReplayOptions
    {
        TimingMode = GuaReplayTimingMode.PreferConditions,
        SecretResolver = key => key == "login-password" ? password : null,
        ActionTimeout = TimeSpan.FromSeconds(5),
        PollInterval = TimeSpan.FromMilliseconds(50),
    });

A sensitive set_value stores only its key. Replay fails if SecretResolver cannot supply the value. Keep the resolver from writing secrets to logs or artifacts.

Replay safety boundaries

  • Every semantic target is resolved against the current tree and must be unique.
  • Every action waits for completion with the same request ID without consuming unrelated events.
  • Coordinate fallback is rejected by default and requires an explicit caller-provided executor.
  • Schema version, timing, revisions, action arguments, and secret leakage are validated on load.

Import retained diagnostics

GuaRecordingFile.ImportDiagnostics pairs enqueued operations with observed completion events by request ID. Its metadata reports paired requests, unpaired steps, and whether a legacy payload required synthetic timing.

Convert diagnostics historycsharp
var imported = GuaRecordingFile.ImportDiagnostics(context.GetDiagnosticsJson());
if (imported.UnpairedStepCount != 0)
    throw new InvalidOperationException("Diagnostics contains unpaired operations.");
GuaRecordingFile.Save("recordings/imported.json", imported.Recording);

Replay a finite game-input segment

Ordinary ReplayAsync waits for each completion before the next step. For finite input offsets, explicitly use GuaReplayer.ReplayTimedSegmentAsync in Gua.Testing.Recording with an IGuaTimedSegmentHost. The supplied GuaRuntimeSegmentHost belongs to that same package and connects to a local Gua.Runtime with an initialized in-order game-input pump.

Hold, jump, and explicitly releasecsharp
using Gua.Runtime;
using Gua.Testing.Recording;

// runtime is local; its initialized adapter applies game input through a FIFO pump.
var segmentHost = new GuaRuntimeSegmentHost(runtime, adapterAppliesInOrder: true);
var segment = new GuaTimedSegment(
    SchemaVersion: 1, DurationMilliseconds: 300, MaxLatenessMilliseconds: 50,
    ExecutionTimeoutMilliseconds: 1000, CleanupTimeoutMilliseconds: 500,
    Inputs: [
        new(0, GuaGameInputKind.Keyboard, GuaGameInputOperation.Down,
            "KeyW", LeaseMilliseconds: 5000),
        new(100, GuaGameInputKind.Keyboard, GuaGameInputOperation.Press, "Space"),
        new(300, GuaGameInputKind.Keyboard, GuaGameInputOperation.Up, "KeyW"),
    ]);
var result = await GuaReplayer.ReplayTimedSegmentAsync(segmentHost, segment);
if (result.Outcome != GuaSegmentOutcome.Succeeded || !result.NeutralConfirmed)
    throw new InvalidOperationException("Timed input or cleanup failed.");

Offsets are relative to segment start. Set execution and cleanup budgets, close holds with explicit releases, and inspect both the outcome and neutral confirmation. The supplied host supports realtime FIFO only; it does not prove exact application times or same-tick application. Keep UI actions and condition waits outside the segment. Built-in MCP/Inspector replay stays sequential and does not offer Timed Segment execution. Plan save/load, import, and timing contract.

Attach a recording to Trace

GuaRecordingTrace.Attach explicitly attaches a validated recording to a Gua.Testing.GuaTraceSession step. AttachTimedResult adds value-free timing results. These are evidence attachments, not a guarantee of replayability; Trace does not replay them. Create a Trace · View saved evidence.