GGua 日本語リファレンスEnglishGitHub ↗
英語版が正式な参照です。内容に差異がある場合は、英語版を優先してください。
GUIDE / RECORDING

Recording実践

Gua.Testing.Recordingを使うと、click、値入力、選択、key操作などを座標ではなくSemantic actionとして保存し、回帰テストや不具合再現で繰り返し再生できます。固定delayだけのmacroでは不安定になる複数stepのユーザーフローを、host完了確認と状態wait付きで再現するために必要なgame engine非依存の機能です。

実際の利用場面

場面Recordingの価値
ログイン→設定→保存の回帰flow複数操作を1つのversion管理可能なscenarioへまとめる
不具合の再現手順を共有座標説明ではなく安定したID・roleで再現する
同じflowをGodot/Unityのfixtureで実行IGuaContextに対してengine非依存で再生する
passwordを含む操作平文を保存せずsecretKeyだけを記録する
Semantic targetID / role / focusEnqueue actionrequestIdを発行Host completion同じrequestIdrecording.jsontarget・timing・revisionReplay + wait各stepを再解決
図1: queueへの追加だけを成功扱いせず、host完了eventまで確認してからstepを記録・再生します。

導入

最新安定版をscenario test projectへ追加powershell
dotnet add package Gua.Testing.Recording

操作を記録する

ログインflowを記録csharp
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);

各stepのwaitConditionは、そのstepがtargetを解決してactionをenqueueする前に評価されます。この例では先行するclickの後、email stepがdialog内のemail nodeを待ちます。また、SetValueAsyncはtargetをfocusしないため、target未指定のPressKeyAsyncでEnterを現在focusへ送る前にpasswordを明示的にfocusします。GuaRecorderはこれらのaction、monotonicな相対時刻、操作前後のrevision、request IDを保存します。

targetの選び方

target適する場面注意
Idゲーム側に安定IDがある最優先。scene再構成でも変えない
Role + Nameaccessible nameで一意に特定できる同名がある場合はScopeを追加
CurrentFocus現在focus中のControlへkey入力press_keyだけで利用可能
座標fallbackSemantic targetが存在しないlegacyデータ既定で再生拒否。通常は使わない

固定delayよりconditionを使う

recordingにconditionがあるstepは、既定のPreferConditionsで記録時delayの代わりに状態を待ちます。対応conditionはvisible、hidden、enabled、disabled、focused、unfocused、checked、unchecked、text、valueです。

代表的なwait conditioncsharp
GuaWaitConditions.Visible("login-dialog")
GuaWaitConditions.Enabled("submit")
GuaWaitConditions.Text("status", "Ready")
GuaWaitConditions.Value("volume", "0.8")

秘密値を注入して再生する

保存済みflowを再生csharp
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),
    });

sensitiveなset_valueはrecording JSONへ値を書かず、secretKeyだけを保存します。再生時にSecretResolverが値を返さなければ失敗します。secretをlogやartifactへ出力しないresolverをtest側で用意してください。

再生時の安全境界

  • 各stepでSemantic targetを現在のUI Treeから解決し直します。一意でなければ失敗します。
  • 各actionは同じrequest IDのhost完了eventまで待ち、無関係なeventを消費しません。
  • 座標fallbackはFailOnCoordinateFallback = trueが既定です。許可する場合もcaller提供のCoordinateExecutorが必要です。
  • recording schema version、timing順序、revision、secret leak、action引数はload時に検証されます。

retained diagnosticsから取り込む

既にruntime diagnosticsへoperation/event historyが残っている場合、GuaRecordingFile.ImportDiagnosticsでrequest IDが一致するenqueueとcompletionを対応付けられます。結果にはpaired request数、unpaired step数、古いpayloadのsynthetic timingを使ったかどうかが含まれます。

diagnostics historyをrecordingへ変換csharp
var imported = GuaRecordingFile.ImportDiagnostics(context.GetDiagnosticsJson());

if (imported.UnpairedStepCount != 0)
    throw new InvalidOperationException("Diagnostics contains unpaired operations.");

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