実際の利用場面
| 場面 | Recordingの価値 |
|---|---|
| ログイン→設定→保存の回帰flow | 複数操作を1つのversion管理可能なscenarioへまとめる |
| 不具合の再現手順を共有 | 座標説明ではなく安定したID・roleで再現する |
| 同じflowをGodot/Unityのfixtureで実行 | IGuaContextに対してengine非依存で再生する |
| passwordを含む操作 | 平文を保存せずsecretKeyだけを記録する |
導入
dotnet add package Gua.Testing.Recording操作を記録する
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 + Name | accessible nameで一意に特定できる | 同名がある場合はScopeを追加 |
CurrentFocus | 現在focus中のControlへkey入力 | press_keyだけで利用可能 |
| 座標fallback | Semantic targetが存在しないlegacyデータ | 既定で再生拒否。通常は使わない |
固定delayよりconditionを使う
recordingにconditionがあるstepは、既定のPreferConditionsで記録時delayの代わりに状態を待ちます。対応conditionはvisible、hidden、enabled、disabled、focused、unfocused、checked、unchecked、text、valueです。
GuaWaitConditions.Visible("login-dialog")
GuaWaitConditions.Enabled("submit")
GuaWaitConditions.Text("status", "Ready")
GuaWaitConditions.Value("volume", "0.8")秘密値を注入して再生する
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を使ったかどうかが含まれます。
var imported = GuaRecordingFile.ImportDiagnostics(context.GetDiagnosticsJson());
if (imported.UnpairedStepCount != 0)
throw new InvalidOperationException("Diagnostics contains unpaired operations.");
GuaRecordingFile.Save("recordings/imported.json", imported.Recording);