test(runtime): add deterministic world gate artifacts
This commit is contained in:
parent
db45b81f75
commit
354c2adc2e
11 changed files with 703 additions and 10 deletions
|
|
@ -149,7 +149,8 @@ public sealed record RetailUiProbeBindings(
|
|||
bool DumpOnStart,
|
||||
Action<string> Log,
|
||||
Func<InputAction, bool> PressInput,
|
||||
Func<InputAction, bool, bool> SetInputHeld);
|
||||
Func<InputAction, bool, bool> SetInputHeld,
|
||||
Testing.IRetailUiAutomationRuntime? Runtime = null);
|
||||
|
||||
public sealed record RetailUiCursorBindings(
|
||||
CursorFeedbackController Feedback,
|
||||
|
|
@ -266,7 +267,8 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
bindings.Chat.CommandBus(),
|
||||
ChatChannelKind.Say),
|
||||
bindings.Probe.PressInput,
|
||||
bindings.Probe.SetInputHeld);
|
||||
bindings.Probe.SetInputHeld,
|
||||
bindings.Probe.Runtime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@ using AcDream.UI.Abstractions.Input;
|
|||
|
||||
namespace AcDream.App.UI.Testing;
|
||||
|
||||
/// <summary>
|
||||
/// Narrow bridge from retained-UI scripts to render/world lifecycle
|
||||
/// diagnostics. Implementations run on the same update/render thread as the
|
||||
/// script; they must not mutate gameplay state or call GL from another thread.
|
||||
/// </summary>
|
||||
public interface IRetailUiAutomationRuntime
|
||||
{
|
||||
bool IsWorldReady { get; }
|
||||
bool IsWorldViewportVisible { get; }
|
||||
int PortalMaterializationCount { get; }
|
||||
bool TryWriteCheckpoint(string name, out string error);
|
||||
bool TryRequestScreenshot(string name, out string error);
|
||||
bool IsScreenshotComplete(string name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tiny one-thread script runner for <see cref="RetailUiAutomationProbe"/>.
|
||||
/// It is intended for local diagnostic launches, not for gameplay. Commands
|
||||
|
|
@ -20,6 +35,7 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
private readonly Action<string>? _submitCommand;
|
||||
private readonly Func<InputAction, bool>? _pressInput;
|
||||
private readonly Func<InputAction, bool, bool>? _setInputHeld;
|
||||
private readonly IRetailUiAutomationRuntime? _runtime;
|
||||
private readonly List<ScriptCommand> _commands = new();
|
||||
private readonly HashSet<InputAction> _heldInputs = new();
|
||||
private readonly bool _dumpOnStart;
|
||||
|
|
@ -39,13 +55,15 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
Action<string>? log = null,
|
||||
Action<string>? submitCommand = null,
|
||||
Func<InputAction, bool>? pressInput = null,
|
||||
Func<InputAction, bool, bool>? setInputHeld = null)
|
||||
Func<InputAction, bool, bool>? setInputHeld = null,
|
||||
IRetailUiAutomationRuntime? runtime = null)
|
||||
{
|
||||
_probe = probe ?? throw new ArgumentNullException(nameof(probe));
|
||||
_log = log ?? (_ => { });
|
||||
_submitCommand = submitCommand;
|
||||
_pressInput = pressInput;
|
||||
_setInputHeld = setInputHeld;
|
||||
_runtime = runtime;
|
||||
_dumpOnStart = dumpOnStart;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(scriptPath))
|
||||
|
|
@ -162,6 +180,8 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
"assert" => DoAssert(command),
|
||||
"command" => DoCommand(command),
|
||||
"input" => DoInput(command),
|
||||
"checkpoint" => DoCheckpoint(command),
|
||||
"screenshot" => DoScreenshot(command),
|
||||
_ => Stop(command, $"unknown command '{p[0]}'"),
|
||||
};
|
||||
}
|
||||
|
|
@ -236,7 +256,7 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
private bool DoWait(ScriptCommand command)
|
||||
{
|
||||
var p = command.Parts;
|
||||
if (p.Length < 2) return Stop(command, "usage: wait item <guid> | wait element <datId> | wait ms <milliseconds>");
|
||||
if (p.Length < 2) return Stop(command, "usage: wait item|element|ms|world-ready|world-visible|materialized ...");
|
||||
|
||||
string target = p[1].ToLowerInvariant();
|
||||
if (target == "item")
|
||||
|
|
@ -256,7 +276,30 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
if (target == "ms")
|
||||
return DoSleep(command);
|
||||
|
||||
return Stop(command, "usage: wait item <guid> | wait element <datId> | wait ms <milliseconds>");
|
||||
if (target == "world-ready")
|
||||
{
|
||||
if (_runtime is null) return Stop(command, "world lifecycle automation is unavailable");
|
||||
if (_runtime.IsWorldReady) return true;
|
||||
return WaitOrTimeout(command, TimeoutMs(p, 2, 60000), "world readiness");
|
||||
}
|
||||
|
||||
if (target == "world-visible")
|
||||
{
|
||||
if (_runtime is null) return Stop(command, "world lifecycle automation is unavailable");
|
||||
if (_runtime.IsWorldViewportVisible) return true;
|
||||
return WaitOrTimeout(command, TimeoutMs(p, 2, 60000), "normal world viewport");
|
||||
}
|
||||
|
||||
if (target == "materialized")
|
||||
{
|
||||
if (_runtime is null) return Stop(command, "world lifecycle automation is unavailable");
|
||||
if (p.Length < 3 || !TryParseInt(p[2], out int occurrence) || occurrence <= 0)
|
||||
return Stop(command, "usage: wait materialized <occurrence> [timeoutMs]");
|
||||
if (_runtime.PortalMaterializationCount >= occurrence) return true;
|
||||
return WaitOrTimeout(command, TimeoutMs(p, 3, 60000), $"portal materialization {occurrence}");
|
||||
}
|
||||
|
||||
return Stop(command, "usage: wait item|element|ms|world-ready|world-visible|materialized ...");
|
||||
}
|
||||
|
||||
private bool DoSleep(ScriptCommand command)
|
||||
|
|
@ -356,6 +399,31 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private bool DoCheckpoint(ScriptCommand command)
|
||||
{
|
||||
if (command.Parts.Length != 2)
|
||||
return Stop(command, "usage: checkpoint <name>");
|
||||
if (_runtime is null)
|
||||
return Stop(command, "world lifecycle automation is unavailable");
|
||||
return _runtime.TryWriteCheckpoint(command.Parts[1], out string error)
|
||||
|| Stop(command, error);
|
||||
}
|
||||
|
||||
private bool DoScreenshot(ScriptCommand command)
|
||||
{
|
||||
if (command.Parts.Length is < 2 or > 3)
|
||||
return Stop(command, "usage: screenshot <name> [timeoutMs]");
|
||||
if (_runtime is null)
|
||||
return Stop(command, "world lifecycle automation is unavailable");
|
||||
|
||||
string name = command.Parts[1];
|
||||
if (!_runtime.TryRequestScreenshot(name, out string error))
|
||||
return Stop(command, error);
|
||||
if (_runtime.IsScreenshotComplete(name))
|
||||
return true;
|
||||
return WaitOrTimeout(command, TimeoutMs(command.Parts, 2, 10000), $"screenshot '{name}'");
|
||||
}
|
||||
|
||||
private bool WaitOrTimeout(ScriptCommand command, int timeoutMs, string label)
|
||||
{
|
||||
if (!HasExceeded(timeoutMs)) return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue