Skip to content

Commit ce16a2b

Browse files
Add Custom Command Function to AppService Pipe
Allows for access to VisualTree within UI Test Uses System.Test.Json serialization to return UI object info between host/harness Added intial basic GridSplitter tests to validate (though unsure why there's such a large variance in delta from drag... test is reporting moved the right number of requested pixels, so not sure if issue with delta vs. completed in GridSplitter itself)
1 parent 6b835cf commit ce16a2b

14 files changed

+498
-11
lines changed

UITests/UITests.App/App.AppService.xaml.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.Generic;
67
using System.Threading.Tasks;
78
using Microsoft.Toolkit.Mvvm.Messaging;
89
using UITests.App.Pages;
@@ -65,6 +66,44 @@ private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppS
6566

6667
await args.Request.SendResponseAsync(pageResponse ? OkResult : BadResult);
6768

69+
break;
70+
case "Custom":
71+
if (!TryGetValueAndLog(message, "Id", out var id) || !_customCommands.ContainsKey(id))
72+
{
73+
await args.Request.SendResponseAsync(BadResult);
74+
break;
75+
}
76+
77+
Log.Comment("Received request for custom command: {0}", id);
78+
79+
try
80+
{
81+
ValueSet response = await _customCommands[id].Invoke(message);
82+
83+
if (response != null)
84+
{
85+
response.Add("Status", "OK");
86+
}
87+
else
88+
{
89+
await args.Request.SendResponseAsync(BadResult);
90+
break;
91+
}
92+
93+
await args.Request.SendResponseAsync(response);
94+
}
95+
catch (Exception e)
96+
{
97+
ValueSet errmsg = new() { { "Status", "BAD" }, { "Exception", e.Message }, { "StackTrace", e.StackTrace } };
98+
if (e.InnerException != null)
99+
{
100+
errmsg.Add("InnerException", e.InnerException.Message);
101+
errmsg.Add("InnerExceptionStackTrace", e.InnerException.StackTrace);
102+
}
103+
104+
await args.Request.SendResponseAsync(errmsg);
105+
}
106+
68107
break;
69108
default:
70109
break;
@@ -118,5 +157,12 @@ private static bool TryGetValueAndLog(ValueSet message, string key, out string v
118157

119158
return true;
120159
}
160+
161+
private Dictionary<string, Func<ValueSet, Task<ValueSet>>> _customCommands = new();
162+
163+
internal void RegisterCustomCommand(string id, Func<ValueSet, Task<ValueSet>> customCommandFunction)
164+
{
165+
_customCommands.Add(id, customCommandFunction);
166+
}
121167
}
122-
}
168+
}

UITests/UITests.App/App.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
using System.Collections.Generic;
77
using System.Linq;
88
using System.Reflection;
9+
using UITests.App.Commands;
910
using UITests.App.Pages;
1011
using Windows.ApplicationModel;
1112
using Windows.ApplicationModel.Activation;
13+
using Windows.System;
1214
using Windows.UI.Xaml;
1315
using Windows.UI.Xaml.Controls;
1416
using Windows.UI.Xaml.Navigation;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Text.Json;
9+
using System.Text.Json.Serialization;
10+
using System.Threading.Tasks;
11+
using Microsoft.Toolkit;
12+
using Microsoft.Toolkit.Uwp;
13+
using Microsoft.Toolkit.Uwp.UI;
14+
using UITests.App.Pages;
15+
using Windows.Foundation.Collections;
16+
using Windows.System;
17+
using Windows.UI.Xaml;
18+
using Windows.UI.Xaml.Controls;
19+
20+
namespace UITests.App.Commands
21+
{
22+
public static class VisualTreeHelperCommands
23+
{
24+
private static DispatcherQueue Queue { get; set; }
25+
26+
private static JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions(JsonSerializerDefaults.General)
27+
{
28+
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
29+
};
30+
31+
public static void Initialize(DispatcherQueue uiThread)
32+
{
33+
Queue = uiThread;
34+
35+
(App.Current as App).RegisterCustomCommand("VisualTreeHelper.FindElementProperty", FindElementProperty);
36+
}
37+
38+
public static async Task<ValueSet> FindElementProperty(ValueSet arguments)
39+
{
40+
ValueSet results = new ValueSet();
41+
42+
if (Queue == null)
43+
{
44+
Log.Error("VisualTreeHelper - Missing UI DispatcherQueue");
45+
return null;
46+
}
47+
48+
await Queue.EnqueueAsync(() =>
49+
{
50+
// Dispatch?
51+
var content = Window.Current.Content as Frame;
52+
53+
if (content == null)
54+
{
55+
Log.Error("VisualTreeHelper.FindElementProperty - Window has no content.");
56+
return;
57+
}
58+
59+
if (arguments.TryGetValue("ElementName", out object value) && value is string name &&
60+
arguments.TryGetValue("Property", out object value2) && value2 is string propertyName)
61+
{
62+
Log.Comment("VisualTreeHelper.FindElementProperty('{0}', '{1}')", name, propertyName);
63+
64+
// 1. Find Element in Visual Tree
65+
var element = content.FindDescendant(name);
66+
67+
try
68+
{
69+
Log.Comment("VisualTreeHelper.FindElementProperty - Found Element? {0}", element != null);
70+
71+
var typeinfo = element.GetType().GetTypeInfo();
72+
73+
Log.Comment("Element Type: {0}", typeinfo.FullName);
74+
75+
var prop = element.GetType().GetTypeInfo().GetProperty(propertyName);
76+
77+
if (prop == null)
78+
{
79+
Log.Error("VisualTreeHelper.FindElementProperty - Couldn't find Property named {0} on type {1}", propertyName, typeinfo.FullName);
80+
return;
81+
}
82+
83+
// 2. Get the property using reflection
84+
var propValue = prop.GetValue(element);
85+
86+
// 3. Serialize and return the result
87+
results.Add("Result", JsonSerializer.Serialize(propValue, SerializerOptions));
88+
}
89+
catch (Exception e)
90+
{
91+
Log.Error("Error {0}", e.Message);
92+
Log.Error("StackTrace:\n{0}", e.StackTrace);
93+
}
94+
}
95+
});
96+
97+
if (results.Count > 0)
98+
{
99+
return results;
100+
}
101+
102+
return null; // Failure
103+
}
104+
}
105+
}

UITests/UITests.App/MainTestHost.xaml.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Threading.Tasks;
88
using Microsoft.Toolkit.Mvvm.Messaging;
99
using Microsoft.Toolkit.Uwp;
10+
using UITests.App.Commands;
1011
using UITests.App.Pages;
1112
using Windows.System;
1213
using Windows.UI.Xaml;
@@ -33,6 +34,9 @@ public MainTestHost()
3334
WeakReferenceMessenger.Default.Register<RequestPageMessage>(this);
3435

3536
_queue = DispatcherQueue.GetForCurrentThread();
37+
38+
// Initialize Custom Commands for AppService
39+
VisualTreeHelperCommands.Initialize(_queue);
3640
}
3741

3842
public void Receive(RequestPageMessage message)

UITests/UITests.App/Properties/Default.rd.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
the application package. The asterisks are not wildcards.
2222
-->
2323
<Assembly Name="*Application*" Dynamic="Required All" />
24+
<Namespace Name="Windows.UI.Xaml.Controls" Dynamic="Required All" />
25+
<Namespace Name="Microsoft.UI.Xaml.Controls" Dynamic="Required All" />
2426
<!-- Add your application specific runtime directives here. -->
2527
</Application>
2628
</Directives>

UITests/UITests.App/UITests.App.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
<Compile Include="App.AppService.xaml.cs">
133133
<DependentUpon>App.xaml</DependentUpon>
134134
</Compile>
135+
<Compile Include="Commands\VisualTreeHelperCommands.cs" />
135136
<Compile Include="HomePage.xaml.cs">
136137
<DependentUpon>HomePage.xaml</DependentUpon>
137138
</Compile>
@@ -178,6 +179,9 @@
178179
<PackageReference Include="MUXAppTestHelpers">
179180
<Version>0.0.4</Version>
180181
</PackageReference>
182+
<PackageReference Include="System.Text.Json">
183+
<Version>5.0.2</Version>
184+
</PackageReference>
181185
</ItemGroup>
182186
<ItemGroup>
183187
<None Include="UITests.App.pfx" />
@@ -248,6 +252,7 @@
248252
<Name>Microsoft.Toolkit</Name>
249253
</ProjectReference>
250254
</ItemGroup>
255+
<ItemGroup />
251256
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
252257
<VisualStudioVersion>14.0</VisualStudioVersion>
253258
</PropertyGroup>

UITests/UITests.Tests.MSTest/UITests.Tests.MSTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.5.1" />
3131
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
3232
<PackageReference Include="Microsoft.Windows.Apps.Test" Version="1.0.181205002" />
33+
<PackageReference Include="System.Text.Json" Version="5.0.2" />
3334
</ItemGroup>
3435

3536
<Import Project="..\UITests.Tests.Shared\UITests.Tests.Shared.projitems" Label="Shared" />
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.Windows.Apps.Test.Foundation;
6+
using Microsoft.Windows.Apps.Test.Foundation.Controls;
7+
using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
8+
using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Infra;
9+
using System.Linq;
10+
using System.Threading.Tasks;
11+
using System.Collections.Generic;
12+
using System.Dynamic;
13+
using System;
14+
15+
#if USING_TAEF
16+
using WEX.Logging.Interop;
17+
using WEX.TestExecution;
18+
using WEX.TestExecution.Markup;
19+
#else
20+
using Microsoft.VisualStudio.TestTools.UnitTesting;
21+
#endif
22+
23+
namespace UITests.Tests
24+
{
25+
26+
[TestClass]
27+
public class GridSplitterTest : UITestBase
28+
{
29+
[ClassInitialize]
30+
[TestProperty("RunAs", "User")]
31+
[TestProperty("Classification", "ScenarioTestSuite")]
32+
[TestProperty("Platform", "Any")]
33+
public static void ClassInitialize(TestContext testContext)
34+
{
35+
TestEnvironment.Initialize(testContext, WinUICsUWPSampleApp);
36+
}
37+
38+
[TestMethod]
39+
[TestPage("GridSplitterTestPage")]
40+
public async Task TestGridSplitterDragHorizontalAsync()
41+
{
42+
var amount = 50;
43+
var tolerance = 10;
44+
45+
var grid = FindElement.ByName("GridSplitterRoot");
46+
var gridSplitter = FindElement.ById("GridSplitterHorizontal");
47+
var box = FindElement.ByName("TopLeftBox");
48+
49+
Verify.IsNotNull(grid, "Can't find GridSplitterRoot");
50+
Verify.IsNotNull(gridSplitter, "Can't find Horizontal GridSplitter");
51+
Verify.IsNotNull(box, "Can't find box");
52+
53+
var width = box.BoundingRectangle.Width;
54+
55+
ColumnDefinition columnDefinitionStart = (await VisualTreeHelper.FindElementPropertyAsync<List<ColumnDefinition>>("GridSplitterRoot", "ColumnDefinitions"))?.FirstOrDefault();
56+
57+
Verify.IsNotNull(columnDefinitionStart, "Couldn't retrieve Column Definition");
58+
59+
// Drag to the Left
60+
InputHelper.DragDistance(gridSplitter, amount, Direction.West, 1000);
61+
62+
Wait.ForMilliseconds(1050);
63+
Wait.ForIdle();
64+
65+
ColumnDefinition columnDefinitionEnd = (await VisualTreeHelper.FindElementPropertyAsync<List<ColumnDefinition>>("GridSplitterRoot", "ColumnDefinitions"))?.FirstOrDefault();
66+
67+
Wait.ForIdle();
68+
69+
Verify.IsTrue(Math.Abs(columnDefinitionStart.ActualWidth - amount - columnDefinitionEnd.ActualWidth) <= tolerance, $"ColumnDefinition not in range expected {columnDefinitionStart.ActualWidth - amount} was {columnDefinitionEnd.ActualWidth}");
70+
71+
Verify.IsTrue(Math.Abs(width - amount - box.BoundingRectangle.Width) <= tolerance, $"Bounding box not in range expected {width - amount} was {box.BoundingRectangle.Width}.");
72+
}
73+
74+
[TestMethod]
75+
[TestPage("GridSplitterTestPage")]
76+
public async Task TestGridSplitterDragHorizontalPastMinimumAsync()
77+
{
78+
var amount = 150;
79+
80+
var gridSplitter = FindElement.ById("GridSplitterHorizontal");
81+
82+
Verify.IsNotNull(gridSplitter, "Can't find Horizontal GridSplitter");
83+
84+
// Drag to the Left
85+
InputHelper.DragDistance(gridSplitter, amount, Direction.West, 1000);
86+
87+
Wait.ForMilliseconds(1050);
88+
Wait.ForIdle();
89+
90+
ColumnDefinition columnDefinitionEnd = (await VisualTreeHelper.FindElementPropertyAsync<List<ColumnDefinition>>("GridSplitterRoot", "ColumnDefinitions"))?.FirstOrDefault();
91+
92+
Wait.ForIdle();
93+
94+
Verify.AreEqual(columnDefinitionEnd.MinWidth, columnDefinitionEnd.ActualWidth, "Column was not the minimum size expected.");
95+
}
96+
97+
private class ColumnDefinition
98+
{
99+
public GridLength Width { get; set; }
100+
101+
public double ActualWidth { get; set; }
102+
103+
public double MinWidth { get; set; }
104+
105+
public double MaxWidth { get; set; }
106+
}
107+
108+
private class GridLength
109+
{
110+
public int GridUnitType { get; set; } // 0 Auto, 1 Pixel, 2 Star
111+
112+
public double Value { get; set; }
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)