player: raw scenario replay without manual cleanup (#14)
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>
This commit is contained in:
@@ -35,4 +35,6 @@ internal sealed class FakePlayerHost : IPlayerHost
|
||||
Checkpoints.Add((afterStep, saveAs));
|
||||
public void CaptureFailureArtifacts(int stepIndex, string reason) =>
|
||||
Failures.Add((stepIndex, reason));
|
||||
public List<TimeSpan> Delays { get; } = new();
|
||||
public void Delay(TimeSpan duration) => Delays.Add(duration);
|
||||
}
|
||||
|
||||
@@ -154,25 +154,33 @@ baselines:
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlayerEngine_NullTarget_SkipsWithoutCalling()
|
||||
public void PlayerEngine_NullTarget_Fallback_Issue14()
|
||||
{
|
||||
// Issue #14: null-target fallbacks.
|
||||
// - Type → send keystrokes to current focus (no target required)
|
||||
// - Click w/ raw_coord → click at raw_coord screen-absolute
|
||||
// - Click w/o raw_coord → still skipped (no way to click safely)
|
||||
// - Drag → still skipped (no raw_coord pair handling yet)
|
||||
var engine = new PlayerEngine();
|
||||
var host = new FakePlayerHost();
|
||||
var scenario = new Scenario
|
||||
{
|
||||
Steps =
|
||||
{
|
||||
new Step { Kind = StepKind.Click, Target = null },
|
||||
new Step { Kind = StepKind.Drag, Target = null },
|
||||
new Step { Kind = StepKind.Click, Target = null }, // skip
|
||||
new Step { Kind = StepKind.Click, Target = null, RawCoord = new[] { 123, 456 } },
|
||||
new Step { Kind = StepKind.Drag, Target = null }, // skip
|
||||
new Step { Kind = StepKind.Type, Target = null, Value = "hello" },
|
||||
},
|
||||
};
|
||||
|
||||
engine.Run(scenario, host);
|
||||
|
||||
Assert.Empty(host.Clicks);
|
||||
Assert.Single(host.Clicks);
|
||||
Assert.Equal(new ScreenPoint(123, 456), host.Clicks[0]);
|
||||
Assert.Empty(host.Drags);
|
||||
Assert.Empty(host.Types);
|
||||
Assert.Single(host.Types);
|
||||
Assert.Equal("hello", host.Types[0]);
|
||||
Assert.Empty(host.Failures);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ public sealed class FakePlayerHost : IPlayerHost
|
||||
}
|
||||
public void CaptureCheckpoint(int afterStep, string saveAs) { }
|
||||
public void CaptureFailureArtifacts(int stepIndex, string reason) { }
|
||||
public void Delay(TimeSpan duration) { }
|
||||
}
|
||||
|
||||
public sealed class FakeHostFactory : IRunnerHostFactory
|
||||
|
||||
Reference in New Issue
Block a user