diff --git a/UITests/UITests.App/App.AppService.xaml.cs b/UITests/UITests.App/App.AppService.xaml.cs
new file mode 100644
index 00000000000..9a1cf41b08a
--- /dev/null
+++ b/UITests/UITests.App/App.AppService.xaml.cs
@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using Microsoft.Toolkit.Mvvm.Messaging;
+using UITests.App.Pages;
+using Windows.ApplicationModel.Activation;
+using Windows.ApplicationModel.AppService;
+using Windows.ApplicationModel.Background;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace UITests.App
+{
+ ///
+ /// This file contains part of the app related to the AppService for communication between this test host and the test harness processes.
+ ///
+ public sealed partial class App
+ {
+ private AppServiceConnection _appServiceConnection;
+ private BackgroundTaskDeferral _appServiceDeferral;
+
+ protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
+ {
+ base.OnBackgroundActivated(args);
+ IBackgroundTaskInstance taskInstance = args.TaskInstance;
+ AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
+ _appServiceDeferral = taskInstance.GetDeferral();
+ taskInstance.Canceled += OnAppServicesCanceled;
+ _appServiceConnection = appService.AppServiceConnection;
+ _appServiceConnection.RequestReceived += OnAppServiceRequestReceived;
+ _appServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
+ }
+
+ private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
+ {
+ AppServiceDeferral messageDeferral = args.GetDeferral();
+ ValueSet message = args.Request.Message;
+ string cmd = message["Command"] as string;
+
+ try
+ {
+ // Return the data to the caller.
+ if (cmd == "Start")
+ {
+ var pageName = message["Page"] as string;
+
+ Log.Comment("Received request for Page: {0}", pageName);
+
+ ValueSet returnMessage = new ValueSet();
+
+ // We await the OpenPage method to ensure the navigation has finished.
+ if (await WeakReferenceMessenger.Default.Send(new RequestPageMessage(pageName)))
+ {
+ returnMessage.Add("Status", "OK");
+ }
+ else
+ {
+ returnMessage.Add("Status", "BAD");
+ }
+
+ await args.Request.SendResponseAsync(returnMessage);
+ }
+ }
+ catch (Exception e)
+ {
+ // Your exception handling code here.
+ Log.Error("Exception processing request: {0}", e.Message);
+ }
+ finally
+ {
+ // Complete the deferral so that the platform knows that we're done responding to the app service call.
+ // Note: for error handling: this must be called even if SendResponseAsync() throws an exception.
+ messageDeferral.Complete();
+ }
+ }
+
+ private void OnAppServicesCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
+ {
+ _appServiceDeferral.Complete();
+ }
+
+ private void AppServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
+ {
+ _appServiceDeferral.Complete();
+ }
+
+ public async void SendLogMessage(string level, string msg)
+ {
+ var message = new ValueSet();
+ message.Add("Command", "Log");
+ message.Add("Level", level);
+ message.Add("Message", msg);
+
+ await _appServiceConnection.SendMessageAsync(message);
+
+ // TODO: do we care if we have a problem here?
+ }
+ }
+}
diff --git a/UITests/UITests.App/App.xaml b/UITests/UITests.App/App.xaml
index 74e9cd2df91..1b9cedbffb7 100644
--- a/UITests/UITests.App/App.xaml
+++ b/UITests/UITests.App/App.xaml
@@ -1,7 +1,4 @@
-
-
-
+
diff --git a/UITests/UITests.App/App.xaml.cs b/UITests/UITests.App/App.xaml.cs
index 23f5035b6d5..16e9e174077 100644
--- a/UITests/UITests.App/App.xaml.cs
+++ b/UITests/UITests.App/App.xaml.cs
@@ -3,6 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using UITests.App.Pages;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
@@ -11,12 +15,22 @@
namespace UITests.App
{
+ ///
+ /// Application class for hosting UI pages to test.
+ ///
public sealed partial class App
{
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
+ this.UnhandledException += this.App_UnhandledException;
+ }
+
+ private void App_UnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
+ {
+ // TODO: Also Log to a file?
+ Log.Error("Unhandled Exception: " + e.Message);
}
///
@@ -53,7 +67,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
- rootFrame.Navigate(typeof(MainPage), e.Arguments);
+ rootFrame.Navigate(typeof(MainTestHost), e.Arguments);
}
// Ensure the current window is active
@@ -68,6 +82,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
/// Details about the navigation failure
private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
+ Log.Error("Failed to load root page: " + e.SourcePageType.FullName);
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
diff --git a/UITests/UITests.App/MainPage.xaml b/UITests/UITests.App/MainPage.xaml
deleted file mode 100644
index d38db07e3e3..00000000000
--- a/UITests/UITests.App/MainPage.xaml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/UITests/UITests.App/MainPage.xaml.cs b/UITests/UITests.App/MainPage.xaml.cs
deleted file mode 100644
index b44c1291f46..00000000000
--- a/UITests/UITests.App/MainPage.xaml.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace UITests.App
-{
- public sealed partial class MainPage
- {
- public MainPage()
- {
- InitializeComponent();
- }
-
- private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
- {
- textBlock.Text = "Clicked";
- }
- }
-}
diff --git a/UITests/UITests.App/MainTestHost.xaml b/UITests/UITests.App/MainTestHost.xaml
new file mode 100644
index 00000000000..0780a771361
--- /dev/null
+++ b/UITests/UITests.App/MainTestHost.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/UITests/UITests.App/MainTestHost.xaml.cs b/UITests/UITests.App/MainTestHost.xaml.cs
new file mode 100644
index 00000000000..ded965f655b
--- /dev/null
+++ b/UITests/UITests.App/MainTestHost.xaml.cs
@@ -0,0 +1,118 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.Toolkit.Mvvm.Messaging;
+using Microsoft.Toolkit.Uwp.Extensions;
+using UITests.App.Pages;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media.Animation;
+
+namespace UITests.App
+{
+ ///
+ /// MainPage hosting all other test pages.
+ ///
+ public sealed partial class MainTestHost : IRecipient
+ {
+ private DispatcherQueue _queue;
+
+ private Assembly _executingAssembly = Assembly.GetExecutingAssembly();
+
+ private TaskCompletionSource _loadingStateTask;
+
+ public MainTestHost()
+ {
+ InitializeComponent();
+
+ WeakReferenceMessenger.Default.Register(this);
+
+ _queue = DispatcherQueue.GetForCurrentThread();
+ }
+
+ public void Receive(RequestPageMessage message)
+ {
+ // Reply with task back to so it can be properly awaited link:App.AppService.xaml.cs#L56
+ message.Reply(OpenPage(message.PageName));
+ }
+
+ private async Task OpenPage(string pageName)
+ {
+ try
+ {
+ Log.Comment("Trying to Load Page: " + pageName);
+
+ _loadingStateTask = new TaskCompletionSource();
+
+ // Ensure we're on the UI thread as we'll be called from the AppService now.
+ _ = _queue.EnqueueAsync(() =>
+ {
+ // Navigate without extra animations
+ navigationFrame.Navigate(FindPageType(pageName), new SuppressNavigationTransitionInfo());
+ });
+
+ // Wait for load to complete
+ await _loadingStateTask.Task;
+ }
+ catch (Exception e)
+ {
+ Log.Error("Exception Loading Page {0}: {1} ", pageName, e.Message);
+ return false;
+ }
+
+ return true;
+ }
+
+ private Type FindPageType(string pageName)
+ {
+ try
+ {
+ return _executingAssembly.GetType("UITests.App.Pages." + pageName);
+ }
+ catch (Exception e)
+ {
+ Log.Error("Exception Finding Page {0}: {1} ", pageName, e.Message);
+ }
+
+ return null;
+ }
+
+ private void NavigationFrame_Navigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
+ {
+ Log.Comment("Navigated to Page {0}", e.SourcePageType.FullName);
+ if (e.Content is Page page)
+ {
+ if (page.IsLoaded)
+ {
+ Log.Comment("Loaded Page {0}", e.SourcePageType.FullName);
+ _loadingStateTask.SetResult(true);
+ }
+ else
+ {
+ page.Loaded += this.Page_Loaded;
+ }
+ }
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ var page = sender as Page;
+
+ page.Loaded -= Page_Loaded;
+
+ Log.Comment("Loaded Page (E) {0}", page.GetType().FullName);
+ _loadingStateTask.SetResult(true);
+ }
+
+ private void NavigationFrame_NavigationFailed(object sender, Windows.UI.Xaml.Navigation.NavigationFailedEventArgs e)
+ {
+ Log.Error("Failed to navigate to page {0}", e.SourcePageType.FullName);
+ _loadingStateTask.SetResult(false);
+ }
+ }
+}
diff --git a/UITests/UITests.App/Package.appxmanifest b/UITests/UITests.App/Package.appxmanifest
index a17d7b8e653..d9fad29b889 100644
--- a/UITests/UITests.App/Package.appxmanifest
+++ b/UITests/UITests.App/Package.appxmanifest
@@ -40,6 +40,11 @@
+
+
+
+
+
diff --git a/UITests/UITests.App/RequestPageMessage.cs b/UITests/UITests.App/RequestPageMessage.cs
new file mode 100644
index 00000000000..cddd704a842
--- /dev/null
+++ b/UITests/UITests.App/RequestPageMessage.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Toolkit.Mvvm.Messaging.Messages;
+
+namespace UITests.App
+{
+ public sealed class RequestPageMessage : AsyncRequestMessage
+ {
+ public RequestPageMessage(string name)
+ {
+ PageName = name;
+ }
+
+ public string PageName { get; }
+ }
+}
diff --git a/UITests/UITests.App/TestInterop.cs b/UITests/UITests.App/TestInterop.cs
new file mode 100644
index 00000000000..956f34f9e35
--- /dev/null
+++ b/UITests/UITests.App/TestInterop.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Windows.UI.Xaml;
+
+namespace UITests.App.Pages
+{
+ // TAEF has a different terms for the same concepts as compared with MSTest.
+ // In order to allow both to use the same test files, we'll define these helper classes
+ // to translate TAEF into MSTest.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Shim helpers")]
+ public static class Log
+ {
+ public static void Comment(string format, params object[] args)
+ {
+ LogMessage("Comment", format, args);
+ }
+
+ public static void Warning(string format, params object[] args)
+ {
+ LogMessage("Warning", "[Warning] " + format, args);
+ }
+
+ public static void Error(string format, params object[] args)
+ {
+ LogMessage("Error", "[Error] " + format, args);
+ }
+
+ private static void LogMessage(string level, string format, object[] args)
+ {
+ // string.Format() complains if we pass it something with braces, even if we have no arguments.
+ // To account for that, we'll escape braces if we have no arguments.
+ if (args.Length == 0)
+ {
+ format = format.Replace("{", "{{").Replace("}", "}}");
+ }
+
+ // Send back to Test Harness via AppService
+ // TODO: Make this a cleaner connection/pattern
+ ((App)Application.Current).SendLogMessage(level, string.Format(format, args));
+ }
+ }
+}
\ No newline at end of file
diff --git a/UITests/UITests.App/UITests.App.csproj b/UITests/UITests.App/UITests.App.csproj
index 48fc8de5936..bcb7be34ecc 100644
--- a/UITests/UITests.App/UITests.App.csproj
+++ b/UITests/UITests.App/UITests.App.csproj
@@ -27,6 +27,7 @@
SHA256trueTrue
+ true
@@ -130,9 +131,14 @@
App.xaml
-
- MainPage.xaml
+
+ App.xaml
+
+
+ MainTestHost.xaml
+
+
@@ -155,7 +161,7 @@
MSBuild:CompileDesigner
-
+ MSBuild:CompileDesigner
@@ -252,4 +258,5 @@
-->
+
diff --git a/UITests/UITests.Tests.MSTest/MSTestInterop.cs b/UITests/UITests.Tests.MSTest/MSTestInterop.cs
index dc1740ba15e..83db7967202 100644
--- a/UITests/UITests.Tests.MSTest/MSTestInterop.cs
+++ b/UITests/UITests.Tests.MSTest/MSTestInterop.cs
@@ -11,6 +11,7 @@ namespace UITests.Tests
// TAEF has a different terms for the same concepts as compared with MSTest.
// In order to allow both to use the same test files, we'll define these helper classes
// to translate TAEF into MSTest.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Shim helpers")]
public static class Log
{
public static void Comment(string format, params object[] args)
@@ -20,12 +21,12 @@ public static void Comment(string format, params object[] args)
public static void Warning(string format, params object[] args)
{
- LogMessage(format, args);
+ LogMessage("[Warning] " + format, args);
}
public static void Error(string format, params object[] args)
{
- LogMessage(format, args);
+ LogMessage("[Error] " + format, args);
}
private static void LogMessage(string format, object[] args)
@@ -41,6 +42,7 @@ private static void LogMessage(string format, object[] args)
}
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Shim helpers.")]
public static class LogController
{
public static void InitializeLogging()
@@ -49,6 +51,7 @@ public static void InitializeLogging()
}
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Shim helpers.")]
public static class Verify
{
// TODO: implement
diff --git a/UITests/UITests.Tests.MSTest/UITests.Tests.MSTest.csproj b/UITests/UITests.Tests.MSTest/UITests.Tests.MSTest.csproj
index 67a02acf40f..18a830ffd80 100644
--- a/UITests/UITests.Tests.MSTest/UITests.Tests.MSTest.csproj
+++ b/UITests/UITests.Tests.MSTest/UITests.Tests.MSTest.csproj
@@ -13,9 +13,15 @@
x86Exefalse
+
+ true
+
+
@@ -27,5 +33,5 @@
-
+
\ No newline at end of file
diff --git a/UITests/UITests.Tests.Shared/Controls/TextBoxMaskTest.cs b/UITests/UITests.Tests.Shared/Controls/TextBoxMaskTest.cs
new file mode 100644
index 00000000000..afb4c5d749a
--- /dev/null
+++ b/UITests/UITests.Tests.Shared/Controls/TextBoxMaskTest.cs
@@ -0,0 +1,56 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Windows.Apps.Test.Foundation.Controls;
+using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
+using Windows.UI.Xaml.Tests.MUXControls.InteractionTests.Infra;
+
+#if USING_TAEF
+using WEX.Logging.Interop;
+using WEX.TestExecution;
+using WEX.TestExecution.Markup;
+#else
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+#endif
+
+namespace UITests.Tests
+{
+ [TestClass]
+ public class TextBoxMaskTest : UITestBase
+ {
+ [ClassInitialize]
+ [TestProperty("RunAs", "User")]
+ [TestProperty("Classification", "ScenarioTestSuite")]
+ [TestProperty("Platform", "Any")]
+ public static void ClassInitialize(TestContext testContext)
+ {
+ TestEnvironment.Initialize(testContext, WinUICsUWPSampleApp);
+ }
+
+ [ClassCleanup]
+ public static void ClassCleanup()
+ {
+ TestEnvironment.AssemblyCleanupWorker(WinUICsUWPSampleApp);
+ }
+
+ [TestMethod]
+ [TestPage("TextBoxMaskTestPage")]
+ public void TestTextBoxMaskBinding_Property()
+ {
+ var initialValue = FindElement.ById("InitialValueTextBlock").GetText();
+ var textBox = FindElement.ById("TextBox");
+
+ Verify.AreEqual(initialValue, textBox.GetText());
+
+ var changeButton = FindElement.ById