using System.Collections.Generic; using Xunit; namespace Recordingtest.Player.Tests; public class UiaPathResolverTests { private sealed class FakeNode : IUiaTreeNode { public string ClassName { get; set; } = ""; public string? AutomationId { get; set; } public string? Name { get; set; } public ElementBounds Bounds { get; set; } public List ChildList { get; } = new(); public IEnumerable Children => ChildList; } [Fact] public void UiaPathParser_ParsesMultiSegment_WithClassAndId() { var segs = UiaPathParser.Parse( "MetroWindow[@AutomationId='root']/CommandPanel/TextBox[@AutomationId='CommandBox']/TextBox[@AutomationId='CB']"); Assert.Equal(4, segs.Count); Assert.Equal("MetroWindow", segs[0].ClassName); Assert.Equal("root", segs[0].AutomationId); Assert.Equal("CommandPanel", segs[1].ClassName); Assert.Null(segs[1].AutomationId); Assert.Equal("TextBox", segs[2].ClassName); Assert.Equal("CommandBox", segs[2].AutomationId); Assert.Equal("CB", segs[3].AutomationId); } [Fact] public void UiaPathParser_ParsesNameAttribute() { var seg = UiaPathParser.ParseSegment("Button[@Name='새 파일']"); Assert.Equal("Button", seg.ClassName); Assert.Equal("새 파일", seg.Name); Assert.Null(seg.AutomationId); } [Fact] public void UiaPathResolver_Descend_FindsNestedElement() { var leaf = new FakeNode { ClassName = "TextBox", AutomationId = "CB", Bounds = new ElementBounds(10, 20, 30, 40), }; var commandBox = new FakeNode { ClassName = "TextBox", AutomationId = "CommandBox", }; commandBox.ChildList.Add(leaf); var panel = new FakeNode { ClassName = "CommandPanel" }; panel.ChildList.Add(commandBox); var root = new FakeNode { ClassName = "MetroWindow", AutomationId = "root" }; root.ChildList.Add(panel); var found = UiaPathResolver.Resolve( root, "MetroWindow[@AutomationId='root']/CommandPanel/TextBox[@AutomationId='CommandBox']/TextBox[@AutomationId='CB']"); Assert.NotNull(found); Assert.Equal("CB", found!.AutomationId); Assert.Equal(10, found.Bounds.X); } [Fact] public void UiaPathResolver_LastSegmentWithoutId_UsesClassName() { var items = new FakeNode { ClassName = "ItemsControl", Bounds = new ElementBounds(0, 0, 1920, 1040), }; var root = new FakeNode { ClassName = "MetroWindow", AutomationId = "root" }; root.ChildList.Add(items); var found = UiaPathResolver.Resolve( root, "MetroWindow[@AutomationId='root']/ItemsControl"); Assert.NotNull(found); Assert.Equal("ItemsControl", found!.ClassName); Assert.Equal(1920, found.Bounds.Width); } [Fact] public void UiaPathResolver_NotFound_ReturnsNull() { var root = new FakeNode { ClassName = "MetroWindow", AutomationId = "root" }; var found = UiaPathResolver.Resolve( root, "MetroWindow[@AutomationId='root']/Missing"); Assert.Null(found); } [Fact] public void SmokeRegression_BoxV4CleanLike_ParsesAndResolves() { // Build a minimal fake tree resembling EG-BIM Modeler. var cb = new FakeNode { ClassName = "TextBox", AutomationId = "CB", Bounds = new ElementBounds(400, 1020, 200, 30) }; var commandBox = new FakeNode { ClassName = "TextBox", AutomationId = "CommandBox" }; commandBox.ChildList.Add(cb); var cmdPanel = new FakeNode { ClassName = "CommandPanel" }; cmdPanel.ChildList.Add(commandBox); var items = new FakeNode { ClassName = "ItemsControl", Bounds = new ElementBounds(0, 0, 1920, 1040) }; var root = new FakeNode { ClassName = "MetroWindow", AutomationId = "root" }; root.ChildList.Add(cmdPanel); root.ChildList.Add(items); var paths = new[] { "MetroWindow[@AutomationId='root']/ItemsControl", "MetroWindow[@AutomationId='root']/CommandPanel/TextBox[@AutomationId='CommandBox']/TextBox[@AutomationId='CB']", }; foreach (var p in paths) { var n = UiaPathResolver.Resolve(root, p); Assert.NotNull(n); } // The two paths should resolve to *different* nodes — proving the // resolver no longer collapses to "first descendant". var n1 = UiaPathResolver.Resolve(root, paths[0])!; var n2 = UiaPathResolver.Resolve(root, paths[1])!; Assert.NotSame(n1, n2); Assert.Equal("ItemsControl", n1.ClassName); Assert.Equal("CB", n2.AutomationId); } }