Implement player PoC (#7)
This commit is contained in:
38
tests/Recordingtest.Player.Tests/FakePlayerHost.cs
Normal file
38
tests/Recordingtest.Player.Tests/FakePlayerHost.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Recordingtest.Player.Tests;
|
||||
|
||||
internal sealed class FakePlayerHost : IPlayerHost
|
||||
{
|
||||
public Func<string, ResolvedElement?> ResolveImpl { get; set; } =
|
||||
_ => new ResolvedElement(new ElementBounds(100, 200, 50, 40), null);
|
||||
public Func<string, bool> WaitForImpl { get; set; } = _ => true;
|
||||
|
||||
public List<ScreenPoint> Clicks { get; } = new();
|
||||
public List<string> Types { get; } = new();
|
||||
public List<(ScreenPoint From, ScreenPoint To)> Drags { get; } = new();
|
||||
public List<string> Hotkeys { get; } = new();
|
||||
public List<(int AfterStep, string SaveAs)> Checkpoints { get; } = new();
|
||||
public List<(int StepIndex, string Reason)> Failures { get; } = new();
|
||||
public List<string> Resolved { get; } = new();
|
||||
public List<string> WaitedFor { get; } = new();
|
||||
|
||||
public ResolvedElement? ResolveElement(string uiaPath, TimeSpan timeout)
|
||||
{
|
||||
Resolved.Add(uiaPath);
|
||||
return ResolveImpl(uiaPath);
|
||||
}
|
||||
|
||||
public bool WaitFor(string waitForHint, TimeSpan timeout)
|
||||
{
|
||||
WaitedFor.Add(waitForHint);
|
||||
return WaitForImpl(waitForHint);
|
||||
}
|
||||
|
||||
public void Click(ScreenPoint point) => Clicks.Add(point);
|
||||
public void Type(string text) => Types.Add(text);
|
||||
public void Drag(ScreenPoint from, ScreenPoint to) => Drags.Add((from, to));
|
||||
public void Hotkey(string keys) => Hotkeys.Add(keys);
|
||||
public void CaptureCheckpoint(int afterStep, string saveAs) =>
|
||||
Checkpoints.Add((afterStep, saveAs));
|
||||
public void CaptureFailureArtifacts(int stepIndex, string reason) =>
|
||||
Failures.Add((stepIndex, reason));
|
||||
}
|
||||
163
tests/Recordingtest.Player.Tests/PlayerEngineTests.cs
Normal file
163
tests/Recordingtest.Player.Tests/PlayerEngineTests.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Recordingtest.Player.Model;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.Player.Tests;
|
||||
|
||||
public class PlayerEngineTests
|
||||
{
|
||||
[Fact]
|
||||
public void Player_EmptyScenario_ExitsZero()
|
||||
{
|
||||
var engine = new PlayerEngine();
|
||||
var host = new FakePlayerHost();
|
||||
var scenario = new Scenario { Name = "empty" };
|
||||
|
||||
engine.Run(scenario, host);
|
||||
|
||||
Assert.Empty(host.Clicks);
|
||||
Assert.Empty(host.Failures);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Player_ClickStep_InvokesHostClickAtExpectedScreenPoint()
|
||||
{
|
||||
var engine = new PlayerEngine();
|
||||
var host = new FakePlayerHost
|
||||
{
|
||||
ResolveImpl = _ => new ResolvedElement(
|
||||
new ElementBounds(100, 200, 50, 40), null),
|
||||
};
|
||||
var scenario = new Scenario
|
||||
{
|
||||
Steps =
|
||||
{
|
||||
new Step
|
||||
{
|
||||
Kind = StepKind.Click,
|
||||
Target = new Target
|
||||
{
|
||||
UiaPath = "Window/Button[@AutomationId='ok']",
|
||||
Offset = new[] { 0.5, 0.25 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
engine.Run(scenario, host);
|
||||
|
||||
// 100 + 50*0.5 = 125 ; 200 + 40*0.25 = 210
|
||||
Assert.Single(host.Clicks);
|
||||
Assert.Equal(new ScreenPoint(125, 210), host.Clicks[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Player_ResolveFailure_CapturesArtifacts()
|
||||
{
|
||||
var engine = new PlayerEngine();
|
||||
var host = new FakePlayerHost { ResolveImpl = _ => null };
|
||||
var scenario = new Scenario
|
||||
{
|
||||
Steps =
|
||||
{
|
||||
new Step
|
||||
{
|
||||
Kind = StepKind.Click,
|
||||
Target = new Target { UiaPath = "Bogus" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => engine.Run(scenario, host));
|
||||
|
||||
Assert.Single(host.Failures);
|
||||
Assert.Equal(0, host.Failures[0].StepIndex);
|
||||
Assert.False(string.IsNullOrEmpty(host.Failures[0].Reason));
|
||||
Assert.Contains("Bogus", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Player_CheckpointStep_InvokesCapture()
|
||||
{
|
||||
var engine = new PlayerEngine();
|
||||
var host = new FakePlayerHost();
|
||||
var scenario = new Scenario
|
||||
{
|
||||
Steps =
|
||||
{
|
||||
new Step
|
||||
{
|
||||
Kind = StepKind.Checkpoint,
|
||||
AfterStep = 3,
|
||||
SaveAs = "out/cp.hmeg",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
engine.Run(scenario, host);
|
||||
|
||||
Assert.Single(host.Checkpoints);
|
||||
Assert.Equal(3, host.Checkpoints[0].AfterStep);
|
||||
Assert.Equal("out/cp.hmeg", host.Checkpoints[0].SaveAs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Player_NoFixedSleep()
|
||||
{
|
||||
var path = LocateEngineSource();
|
||||
var src = File.ReadAllText(path);
|
||||
Assert.DoesNotMatch(new Regex(@"Thread\.Sleep\("), src);
|
||||
Assert.DoesNotMatch(new Regex(@"Task\.Delay\(TimeSpan\.FromSeconds"), src);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Player_ScenarioLoader_ParsesSampleYaml()
|
||||
{
|
||||
const string yaml = """
|
||||
name: sample
|
||||
description: tiny
|
||||
sut:
|
||||
exe: "EG-BIM Modeler/EG-BIM Modeler.exe"
|
||||
startup_timeout_ms: 12000
|
||||
steps:
|
||||
- kind: click
|
||||
target:
|
||||
uia_path: "Window/Button[@AutomationId='ok']"
|
||||
offset: [0.5, 0.5]
|
||||
- kind: type
|
||||
value: "hello"
|
||||
- kind: checkpoint
|
||||
after_step: 1
|
||||
save_as: "out/cp1.hmeg"
|
||||
checkpoints:
|
||||
- after_step: 1
|
||||
save_as: "out/cp1.hmeg"
|
||||
baselines:
|
||||
- path: "baselines/cp1.approved.hmeg"
|
||||
""";
|
||||
var s = ScenarioLoader.LoadFromString(yaml);
|
||||
|
||||
Assert.Equal("sample", s.Name);
|
||||
Assert.Equal(12000, s.Sut.StartupTimeoutMs);
|
||||
Assert.Equal(3, s.Steps.Count);
|
||||
Assert.Equal(StepKind.Click, s.Steps[0].Kind);
|
||||
Assert.Equal("Window/Button[@AutomationId='ok']", s.Steps[0].Target!.UiaPath);
|
||||
Assert.Equal(0.5, s.Steps[0].Target!.Offset[0]);
|
||||
Assert.Equal(StepKind.Type, s.Steps[1].Kind);
|
||||
Assert.Equal("hello", s.Steps[1].Value);
|
||||
Assert.Equal(StepKind.Checkpoint, s.Steps[2].Kind);
|
||||
Assert.Equal(1, s.Steps[2].AfterStep);
|
||||
Assert.Single(s.Checkpoints);
|
||||
Assert.Single(s.Baselines);
|
||||
}
|
||||
|
||||
private static string LocateEngineSource([CallerFilePath] string here = "")
|
||||
{
|
||||
// here = .../tests/Recordingtest.Player.Tests/PlayerEngineTests.cs
|
||||
var dir = Path.GetDirectoryName(here)!;
|
||||
var repo = Path.GetFullPath(Path.Combine(dir, "..", ".."));
|
||||
return Path.Combine(repo, "src", "Recordingtest.Player", "PlayerEngine.cs");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Recordingtest.Player.Tests</RootNamespace>
|
||||
<AssemblyName>Recordingtest.Player.Tests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Recordingtest.Player\Recordingtest.Player.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user