diff --git a/src/common/UITestAutomation/Element/By.cs b/src/common/UITestAutomation/Element/By.cs index 5ccaf1eb06..b2d8ecf6c4 100644 --- a/src/common/UITestAutomation/Element/By.cs +++ b/src/common/UITestAutomation/Element/By.cs @@ -11,17 +11,40 @@ namespace Microsoft.PowerToys.UITest /// public class By { - private readonly OpenQA.Selenium.By by; + private readonly OpenQA.Selenium.By? by; + private readonly bool isAccessibilityId; + private readonly string? accessibilityId; private By(OpenQA.Selenium.By by) { + isAccessibilityId = false; this.by = by; } + private By(string accessibilityId) + { + isAccessibilityId = true; + this.accessibilityId = accessibilityId; + } + public override string ToString() { // override ToString to return detailed debugging content provided by OpenQA.Selenium.By - return this.by.ToString(); + return this.GetAccessibilityId(); + } + + public bool GetIsAccessibilityId() => this.isAccessibilityId; + + public string GetAccessibilityId() + { + if (this.isAccessibilityId) + { + return this.accessibilityId!; + } + else + { + return this.by!.ToString(); + } } /// @@ -31,6 +54,13 @@ namespace Microsoft.PowerToys.UITest /// A By object. public static By Name(string name) => new By(OpenQA.Selenium.By.Name(name)); + /// + /// Creates a By object using the className attribute. + /// + /// The className attribute to search for. + /// A By object. + public static By ClassName(string className) => new By(OpenQA.Selenium.By.ClassName(className)); + /// /// Creates a By object using the ID attribute. /// @@ -38,6 +68,13 @@ namespace Microsoft.PowerToys.UITest /// A By object. public static By Id(string id) => new By(OpenQA.Selenium.By.Id(id)); + /// + /// Creates a By object using the ID attribute. + /// + /// The ID attribute to search for. + /// A By object. + public static By AccessibilityId(string accessibilityId) => new By(accessibilityId); + /// /// Creates a By object using the XPath expression. /// @@ -70,6 +107,6 @@ namespace Microsoft.PowerToys.UITest /// Converts the By object to an OpenQA.Selenium.By object. /// /// An OpenQA.Selenium.By object. - internal OpenQA.Selenium.By ToSeleniumBy() => by; + internal OpenQA.Selenium.By ToSeleniumBy() => by!; } } diff --git a/src/common/UITestAutomation/Element/Element.cs b/src/common/UITestAutomation/Element/Element.cs index 59c799e401..7ca0cf53a5 100644 --- a/src/common/UITestAutomation/Element/Element.cs +++ b/src/common/UITestAutomation/Element/Element.cs @@ -3,8 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; +using System.Drawing; using System.Runtime.CompilerServices; +using System.Xml.Linq; +using ABI.Windows.Foundation; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; using OpenQA.Selenium.Interactions; @@ -63,6 +68,14 @@ namespace Microsoft.PowerToys.UITest get { return this.windowsElement?.Selected ?? false; } } + /// + /// Gets the Rect of the UI element. + /// + public Rectangle? Rect + { + get { return this.windowsElement?.Rect; } + } + /// /// Gets the AutomationID of the UI element. /// @@ -138,6 +151,54 @@ namespace Microsoft.PowerToys.UITest }); } + /// + /// Drag element move offset. + /// + /// The offsetX to move. + /// The offsetY to move. + public void Drag(int offsetX, int offsetY) + { + PerformAction((actions, windowElement) => + { + actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY).Release(); + actions.Build().Perform(); + }); + } + + /// + /// Drag element move to other element. + /// + /// Move to this element. + public void Drag(Element element) + { + PerformAction((actions, windowElement) => + { + actions.MoveToElement(windowElement).ClickAndHold(); + Assert.IsNotNull(element.windowsElement, "element is null"); + int dx = (element.windowsElement.Rect.X - windowElement.Rect.X) / 10; + int dy = (element.windowsElement.Rect.Y - windowElement.Rect.Y) / 10; + for (int i = 0; i < 10; i++) + { + actions.MoveByOffset(dx, dy); + } + + actions.Release(); + actions.Build().Perform(); + }); + } + + /// + /// Send Key of the element. + /// + /// The Key to Send. + public void SendKeys(string key) + { + PerformAction((actions, windowElement) => + { + windowElement.SendKeys(key); + }); + } + /// /// Gets the attribute value of the UI element. /// @@ -222,8 +283,16 @@ namespace Microsoft.PowerToys.UITest var foundElements = FindHelper.FindAll( () => { - var elements = this.windowsElement.FindElements(by.ToSeleniumBy()); - return elements; + if (by.GetIsAccessibilityId()) + { + var elements = this.windowsElement.FindElementsByAccessibilityId(by.GetAccessibilityId()); + return elements; + } + else + { + var elements = this.windowsElement.FindElements(by.ToSeleniumBy()); + return elements; + } }, this.driver, timeoutMS); diff --git a/src/common/UITestAutomation/FindHelper.cs b/src/common/UITestAutomation/FindHelper.cs index 5f25dfcb15..465f1206a1 100644 --- a/src/common/UITestAutomation/FindHelper.cs +++ b/src/common/UITestAutomation/FindHelper.cs @@ -17,6 +17,19 @@ namespace Microsoft.PowerToys.UITest /// internal static class FindHelper { + public static ReadOnlyCollection? FindAll(Func> findElementsFunc, WindowsDriver? driver, int timeoutMS) + where T : Element, new() + { + var items = findElementsFunc(); + var res = items.Select(item => + { + var element = item as WindowsElement; + return NewElement(element, driver, timeoutMS); + }).Where(item => item.IsMatchingTarget()).ToList(); + + return new ReadOnlyCollection(res); + } + public static ReadOnlyCollection? FindAll(Func> findElementsFunc, WindowsDriver? driver, int timeoutMS) where T : Element, new() { diff --git a/src/common/UITestAutomation/Session.cs b/src/common/UITestAutomation/Session.cs index ef0a6fff3f..9e5101fc75 100644 --- a/src/common/UITestAutomation/Session.cs +++ b/src/common/UITestAutomation/Session.cs @@ -8,6 +8,7 @@ using System.Xml.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Interactions; namespace Microsoft.PowerToys.UITest { @@ -98,8 +99,16 @@ namespace Microsoft.PowerToys.UITest var foundElements = FindHelper.FindAll( () => { - var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy()); - return elements; + if (by.GetIsAccessibilityId()) + { + var elements = this.WindowsDriver.FindElementsByAccessibilityId(by.GetAccessibilityId()); + return elements; + } + else + { + var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy()); + return elements; + } }, this.WindowsDriver, timeoutMS); @@ -145,6 +154,39 @@ namespace Microsoft.PowerToys.UITest return this.FindAll(By.Name(name), timeoutMS); } + /// + /// Keyboard Action key. + /// + /// The Keys1 to click. + /// The Keys2 to click. + /// The Keys3 to click. + /// The Keys4 to click. + public void KeyboardAction(string key1, string key2 = "", string key3 = "", string key4 = "") + { + PerformAction((actions, windowElement) => + { + if (string.IsNullOrEmpty(key2)) + { + actions.SendKeys(key1); + } + else if (string.IsNullOrEmpty(key3)) + { + actions.SendKeys(key1).SendKeys(key2); + } + else if (string.IsNullOrEmpty(key4)) + { + actions.SendKeys(key1).SendKeys(key2).SendKeys(key3); + } + else + { + actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4); + } + + actions.Release(); + actions.Build().Perform(); + }); + } + /// /// Attaches to an existing PowerToys module. /// @@ -189,5 +231,28 @@ namespace Microsoft.PowerToys.UITest return this; } + + /// + /// Simulates a manual operation on the element. + /// + /// The action to perform on the element. + /// The number of milliseconds to wait before the action. Default value is 500 ms + /// The number of milliseconds to wait after the action. Default value is 500 ms + protected void PerformAction(Action> action, int msPreAction = 500, int msPostAction = 500) + { + if (msPreAction > 0) + { + Task.Delay(msPreAction).Wait(); + } + + var windowsDriver = this.WindowsDriver; + Actions actions = new Actions(this.WindowsDriver); + action(actions, windowsDriver); + + if (msPostAction > 0) + { + Task.Delay(msPostAction).Wait(); + } + } } } diff --git a/src/common/UITestAutomation/SessionHelper.cs b/src/common/UITestAutomation/SessionHelper.cs index 7bb1f6e7a6..324f41e5e3 100644 --- a/src/common/UITestAutomation/SessionHelper.cs +++ b/src/common/UITestAutomation/SessionHelper.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation 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 System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -19,15 +20,19 @@ namespace Microsoft.PowerToys.UITest // Default session path is PowerToys settings dashboard private readonly string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings); + private string? locationPath; + private WindowsDriver Root { get; set; } private WindowsDriver? Driver { get; set; } private Process? appDriver; + [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "")] public SessionHelper(PowerToysModule scope) { this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope); + this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var winAppDriverProcessInfo = new ProcessStartInfo { @@ -49,17 +54,12 @@ namespace Microsoft.PowerToys.UITest /// Initializes the test environment. /// /// The PowerToys module to start. - [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "")] public SessionHelper Init() { - string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - this.StartExe(path + this.sessionPath); + this.StartExe(locationPath + this.sessionPath); Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null."); - // Set default timeout to 5 seconds - this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5); - return this; } @@ -68,9 +68,11 @@ namespace Microsoft.PowerToys.UITest /// public void Cleanup() { + ExitScopeExe(); try { appDriver?.Kill(); + appDriver?.WaitForExit(); // Optional: Wait for the process to exit } catch (Exception ex) { @@ -87,7 +89,53 @@ namespace Microsoft.PowerToys.UITest { var opts = new AppiumOptions(); opts.AddAdditionalCapability("app", appPath); + Console.WriteLine($"appPath: {appPath}"); this.Driver = new WindowsDriver(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts); + + // Set default timeout to 5 seconds + this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5); + } + + /// + /// Exit a exe. + /// + /// The path to the application executable. + public void ExitExe(string path) + { + // Exit Exe + string exeName = Path.GetFileNameWithoutExtension(path); + + // PowerToys.FancyZonesEditor + Process[] processes = Process.GetProcessesByName(exeName); + foreach (Process process in processes) + { + try + { + process.Kill(); + process.WaitForExit(); // Optional: Wait for the process to exit + } + catch (Exception ex) + { + Assert.Fail($"Failed to terminate process {process.ProcessName} (ID: {process.Id}): {ex.Message}"); + } + } + } + + /// + /// Exit now exe. + /// + public void ExitScopeExe() + { + ExitExe(sessionPath); + } + + /// + /// Restarts now exe and takes control of it. + /// + public void RestartScopeExe() + { + ExitExe(sessionPath); + StartExe(locationPath + sessionPath); } public WindowsDriver GetRoot() => this.Root; diff --git a/src/common/UITestAutomation/UITestBase.cs b/src/common/UITestAutomation/UITestBase.cs index 1d6502ac54..40de0dc991 100644 --- a/src/common/UITestAutomation/UITestBase.cs +++ b/src/common/UITestAutomation/UITestBase.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Xml.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; @@ -31,11 +32,6 @@ namespace Microsoft.PowerToys.UITest this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver()); } - ~UITestBase() - { - this.sessionHelper.Cleanup(); - } - /// /// Initializes the test. /// @@ -53,6 +49,15 @@ namespace Microsoft.PowerToys.UITest } } + /// + /// UnInitializes the test. + /// + [TestCleanup] + public void TestClean() + { + this.sessionHelper.Cleanup(); + } + /// /// Finds an element by selector. /// Shortcut for this.Session.Find(by, timeoutMS) @@ -153,5 +158,24 @@ namespace Microsoft.PowerToys.UITest { return this.Session.FindAll(By.Name(name), timeoutMS); } + + /// + /// Restart scope exe. + /// + public void RestartScopeExe() + { + this.sessionHelper.RestartScopeExe(); + this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver()); + return; + } + + /// + /// Restart scope exe. + /// + public void ExitScopeExe() + { + this.sessionHelper.ExitScopeExe(); + return; + } } } diff --git a/src/modules/fancyzones/FancyZonesEditorCommon/Data/CustomLayoutEnumExtension.cs b/src/modules/fancyzones/FancyZonesEditorCommon/Data/CustomLayoutEnumExtension.cs new file mode 100644 index 0000000000..4f158b3aca --- /dev/null +++ b/src/modules/fancyzones/FancyZonesEditorCommon/Data/CustomLayoutEnumExtension.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace FancyZonesEditorCommon.Data +{ + public enum CustomLayout + { + Canvas, + Grid, + } + + public static class CustomLayoutEnumExtension + { + private const string CanvasJsonTag = "canvas"; + private const string GridJsonTag = "grid"; + + public static string TypeToString(this CustomLayout value) + { + switch (value) + { + case CustomLayout.Canvas: + return CanvasJsonTag; + case CustomLayout.Grid: + return GridJsonTag; + } + + return CanvasJsonTag; + } + + public static CustomLayout TypeFromString(string value) + { + switch (value) + { + case CanvasJsonTag: + return CustomLayout.Canvas; + case GridJsonTag: + return CustomLayout.Grid; + } + + return CustomLayout.Canvas; + } + } +} diff --git a/src/modules/fancyzones/UITests-FancyZonesEditor/ApplyLayoutTests.cs b/src/modules/fancyzones/UITests-FancyZonesEditor/ApplyLayoutTests.cs new file mode 100644 index 0000000000..2ae581ebe1 --- /dev/null +++ b/src/modules/fancyzones/UITests-FancyZonesEditor/ApplyLayoutTests.cs @@ -0,0 +1,308 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using FancyZonesEditor.Models; +using FancyZonesEditorCommon.Data; +using Microsoft.FancyZonesEditor.UnitTests.Utils; +using Microsoft.PowerToys.UITest; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ModernWpf.Controls; +using OpenQA.Selenium; +using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper; + +namespace Microsoft.FancyZonesEditor.UITests +{ + [TestClass] + public class ApplyLayoutTests : UITestBase + { + public ApplyLayoutTests() + : base(PowerToysModule.FancyZone) + { + } + + private static readonly EditorParameters.ParamsWrapper Parameters = new EditorParameters.ParamsWrapper + { + ProcessId = 1, + SpanZonesAcrossMonitors = false, + Monitors = new List + { + new EditorParameters.NativeMonitorDataWrapper + { + Monitor = "monitor-1", + MonitorInstanceId = "instance-id-1", + MonitorSerialNumber = "serial-number-1", + MonitorNumber = 1, + VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}", + Dpi = 96, + LeftCoordinate = 0, + TopCoordinate = 0, + WorkAreaHeight = 1040, + WorkAreaWidth = 1920, + MonitorHeight = 1080, + MonitorWidth = 1920, + IsSelected = true, + }, + new EditorParameters.NativeMonitorDataWrapper + { + Monitor = "monitor-2", + MonitorInstanceId = "instance-id-2", + MonitorSerialNumber = "serial-number-2", + MonitorNumber = 2, + VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}", + Dpi = 96, + LeftCoordinate = 1920, + TopCoordinate = 0, + WorkAreaHeight = 1040, + WorkAreaWidth = 1920, + MonitorHeight = 1080, + MonitorWidth = 1920, + IsSelected = false, + }, + }, + }; + + private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper + { + CustomLayouts = new List + { + new CustomLayouts.CustomLayoutWrapper + { + Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}", + Type = CustomLayout.Canvas.TypeToString(), + Name = "Custom layout", + Info = new CustomLayouts().ToJsonElement(new CustomLayouts.CanvasInfoWrapper + { + RefHeight = 952, + RefWidth = 1500, + SensitivityRadius = 10, + Zones = new List { }, + }), + }, + }, + }; + + private static readonly LayoutTemplates.TemplateLayoutsListWrapper TemplateLayoutsList = new LayoutTemplates.TemplateLayoutsListWrapper + { + LayoutTemplates = new List + { + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Blank.TypeToString(), + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Focus.TypeToString(), + ZoneCount = 10, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Rows.TypeToString(), + ZoneCount = 2, + ShowSpacing = true, + Spacing = 10, + SensitivityRadius = 10, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Columns.TypeToString(), + ZoneCount = 2, + ShowSpacing = true, + Spacing = 20, + SensitivityRadius = 20, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.Grid.TypeToString(), + ZoneCount = 4, + ShowSpacing = false, + Spacing = 10, + SensitivityRadius = 30, + }, + new LayoutTemplates.TemplateLayoutWrapper + { + Type = LayoutType.PriorityGrid.TypeToString(), + ZoneCount = 3, + ShowSpacing = true, + Spacing = 1, + SensitivityRadius = 40, + }, + }, + }; + + [TestInitialize] + public void TestInitialize() + { + EditorParameters editorParameters = new EditorParameters(); + FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters)); + + LayoutTemplates layoutTemplates = new LayoutTemplates(); + FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(TemplateLayoutsList)); + + CustomLayouts customLayouts = new CustomLayouts(); + FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayoutsList)); + + DefaultLayouts defaultLayouts = new DefaultLayouts(); + DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper + { + DefaultLayouts = new List + { + new DefaultLayouts.DefaultLayoutWrapper + { + MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(), + Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper + { + Type = LayoutType.Focus.TypeToString(), + ZoneCount = 4, + ShowSpacing = true, + Spacing = 5, + SensitivityRadius = 20, + }, + }, + new DefaultLayouts.DefaultLayoutWrapper + { + MonitorConfiguration = MonitorConfigurationType.Vertical.TypeToString(), + Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper + { + Type = LayoutType.Custom.TypeToString(), + Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}", + ZoneCount = 0, + ShowSpacing = false, + Spacing = 0, + SensitivityRadius = 0, + }, + }, + }, + }; + FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper)); + + LayoutHotkeys layoutHotkeys = new LayoutHotkeys(); + LayoutHotkeys.LayoutHotkeysWrapper layoutHotkeysWrapper = new LayoutHotkeys.LayoutHotkeysWrapper + { + LayoutHotkeys = new List { }, + }; + FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(layoutHotkeysWrapper)); + + AppliedLayouts appliedLayouts = new AppliedLayouts(); + AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper + { + AppliedLayouts = new List { }, + }; + FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper)); + + this.RestartScopeExe(); + } + + [TestCleanup] + public void TestCleanup() + { + FancyZonesEditorHelper.Files.Restore(); + } + + [TestMethod] + public void ApplyCustomLayout() + { + var layout = CustomLayoutsList.CustomLayouts[0]; + Assert.IsFalse(Session.Find(layout.Name).Selected); + Session.Find(layout.Name).Click(); + + Assert.IsTrue(Session.Find(layout.Name).Selected); + + AppliedLayouts appliedLayouts = new AppliedLayouts(); + var data = appliedLayouts.Read(appliedLayouts.File); + Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count); + Assert.AreEqual(layout.Uuid, data.AppliedLayouts[0].AppliedLayout.Uuid); + Assert.AreEqual(Parameters.Monitors[0].MonitorNumber, data.AppliedLayouts[0].Device.MonitorNumber); + } + + [TestMethod] + public void ApplyTemplateLayout() + { + var layoutType = LayoutType.Columns; + var layout = TestConstants.TemplateLayoutNames[layoutType]; + Assert.IsFalse(Session.Find(layout).Selected); + Session.Find(layout).Click(); + + Assert.IsTrue(Session.Find(layout).Selected); + + AppliedLayouts appliedLayouts = new AppliedLayouts(); + var data = appliedLayouts.Read(appliedLayouts.File); + Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count); + Assert.AreEqual(layoutType.TypeToString(), data.AppliedLayouts[0].AppliedLayout.Type); + Assert.AreEqual(Parameters.Monitors[0].MonitorNumber, data.AppliedLayouts[0].Device.MonitorNumber); + } + + [TestMethod] + public void ApplyLayoutsOnEachMonitor() + { + // apply the layout on the first monitor + var firstLayoutType = LayoutType.Columns; + var firstLayoutName = TestConstants.TemplateLayoutNames[firstLayoutType]; + Session.Find(firstLayoutName).Click(); + Assert.IsTrue(Session.Find(firstLayoutName)!.Selected); + + // apply the layout on the second monitor + Session.Find(PowerToys.UITest.By.AccessibilityId("Monitors")).Find("Monitor 2").Click(); + var secondLayout = CustomLayoutsList.CustomLayouts[0]; + Session.Find(secondLayout.Name).Click(); + Assert.IsTrue(Session.Find(secondLayout.Name)!.Selected); + + // verify the layout on the first monitor wasn't changed + Session.Find(PowerToys.UITest.By.AccessibilityId("Monitors")).Find("Monitor 1").Click(); + Assert.IsTrue(Session.Find(firstLayoutName)!.Selected); + + // verify the file + var appliedLayouts = new AppliedLayouts(); + var data = appliedLayouts.Read(appliedLayouts.File); + Assert.AreEqual(Parameters.Monitors.Count, data.AppliedLayouts.Count); + Assert.AreEqual(firstLayoutType.TypeToString(), data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 1).AppliedLayout.Type); + Assert.AreEqual(secondLayout.Uuid, data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 2).AppliedLayout.Uuid); + } + + [TestMethod] + public void ApplyTemplateWithDifferentParametersOnEachMonitor() + { + var layoutType = LayoutType.Columns; + var layoutName = TestConstants.TemplateLayoutNames[layoutType]; + + // apply the layout on the first monitor, set parameters + Session.Find(layoutName).Click(); + Session.Find(layoutName).Find