First time box-v6.yaml (raw recorder output, 676 lines) replays end-to-end and actually creates a Box in the SUT — no AI post-editing of target paths or offsets required. This is the counterpart to #13's recorder-side fixes: the player now absorbs the remaining record→replay gaps instead of demanding a hand-cleaned scenario. Changes (all in Recordingtest.Player): - PlayerEngine: null-target fallbacks - Type with null target → host.Type() against current focus - Click with null target + raw_coord → click at screen-absolute raw_coord - Other null targets still skipped - PlayerEngine: strip leading alt+tab hotkey steps (recording-startup noise that fights the player's own foreground switch) - PlayerEngine: preserve recorded inter-step timing, clamped 150ms–3s, routed through new IPlayerHost.Delay so the engine itself stays Sleep-free (keeps the existing "no fixed sleep" DoD test passing) - PlayerEngine: per-step console log for live debugging - UiaPlayerHost: BringSutToForeground() — SetForeground + Focus + 600ms settle, called from Program.cs before engine.Run - Step model: add RawCoord (int[]) and Ts (long?) fields, auto-mapped from YAML raw_coord / ts keys Tests updated: - PlayerEngine_NullTarget_SkipsWithoutCalling → _Fallback_Issue14 (verifies the new Click-with-raw_coord and Type behavior) - FakePlayerHost (both player.tests and runner.tests) implement Delay Live smoke: box-v6.yaml raw replay produced the expected Box geometry on the 2nd attempt; 1st attempt dropped the initial "BOX" keystrokes, tracked as a follow-up (foreground settle is still threshold-sensitive at 600ms). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
2.6 KiB
C#
88 lines
2.6 KiB
C#
using Recordingtest.DiffReporter;
|
|
using Recordingtest.Player;
|
|
using Recordingtest.Player.Model;
|
|
using Recordingtest.Runner;
|
|
|
|
namespace Recordingtest.Runner.Tests;
|
|
|
|
public sealed class FakePlayerHost : IPlayerHost
|
|
{
|
|
private readonly string _outDir;
|
|
private readonly string _resultContent;
|
|
private readonly bool _throwOnClick;
|
|
|
|
public FakePlayerHost(string outDir, string resultContent, bool throwOnClick = false)
|
|
{
|
|
_outDir = outDir;
|
|
_resultContent = resultContent;
|
|
_throwOnClick = throwOnClick;
|
|
}
|
|
|
|
public ResolvedElement? ResolveElement(string uiaPath, TimeSpan timeout)
|
|
=> new ResolvedElement(new ElementBounds(0, 0, 10, 10), null);
|
|
|
|
public bool WaitFor(string waitForHint, TimeSpan timeout) => true;
|
|
|
|
public void Click(ScreenPoint point)
|
|
{
|
|
if (_throwOnClick) throw new InvalidOperationException("fake click failure");
|
|
}
|
|
|
|
public void Type(string text) { }
|
|
public void Drag(ScreenPoint from, ScreenPoint to) { }
|
|
public void Hotkey(string keys)
|
|
{
|
|
// simulate save
|
|
Directory.CreateDirectory(_outDir);
|
|
File.WriteAllText(Path.Combine(_outDir, "result.json"), _resultContent);
|
|
}
|
|
public void CaptureCheckpoint(int afterStep, string saveAs) { }
|
|
public void CaptureFailureArtifacts(int stepIndex, string reason) { }
|
|
public void Delay(TimeSpan duration) { }
|
|
}
|
|
|
|
public sealed class FakeHostFactory : IRunnerHostFactory
|
|
{
|
|
private readonly string _resultContent;
|
|
private readonly bool _throwOnClick;
|
|
|
|
public FakeHostFactory(string resultContent, bool throwOnClick = false)
|
|
{
|
|
_resultContent = resultContent;
|
|
_throwOnClick = throwOnClick;
|
|
}
|
|
|
|
public IPlayerHost Create(Scenario scenario, string outDir)
|
|
=> new FakePlayerHost(outDir, _resultContent, _throwOnClick);
|
|
}
|
|
|
|
public sealed class SpyNormalizer : INormalizer
|
|
{
|
|
public List<string> Profiles { get; } = new();
|
|
public string Normalize(string input, string profile, string? sidecarPath)
|
|
{
|
|
Profiles.Add(profile);
|
|
return input;
|
|
}
|
|
}
|
|
|
|
public sealed class StubDiffer : IDiffer
|
|
{
|
|
private readonly bool _identical;
|
|
private readonly int _hunkCount;
|
|
|
|
public StubDiffer(bool identical, int hunkCount = 0)
|
|
{
|
|
_identical = identical;
|
|
_hunkCount = hunkCount;
|
|
}
|
|
|
|
public DiffResult Compare(string approvedPath, string receivedPath)
|
|
{
|
|
var hunks = new List<Hunk>();
|
|
for (int i = 0; i < _hunkCount; i++)
|
|
hunks.Add(new Hunk(i, "changed", "a", "b"));
|
|
return new DiffResult(Path.GetFileName(receivedPath), _identical, hunks, new DiffSummary(0, 0, _hunkCount));
|
|
}
|
|
}
|