diff --git a/src/common/UITestAutomation/Element/Button.cs b/src/common/UITestAutomation/Element/Button.cs
index b5915031be92..8225971b4e32 100644
--- a/src/common/UITestAutomation/Element/Button.cs
+++ b/src/common/UITestAutomation/Element/Button.cs
@@ -9,5 +9,14 @@ namespace Microsoft.PowerToys.UITest
///
public class Button : Element
{
+ private static readonly string ExpectedControlType = "ControlType.Button";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Button()
+ {
+ this.TargetControlType = Button.ExpectedControlType;
+ }
}
}
diff --git a/src/common/UITestAutomation/Element/Element.cs b/src/common/UITestAutomation/Element/Element.cs
index 7d78ac5f9c4e..59c799e401dc 100644
--- a/src/common/UITestAutomation/Element/Element.cs
+++ b/src/common/UITestAutomation/Element/Element.cs
@@ -22,6 +22,14 @@ public class Element
private WindowsDriver? driver;
+ protected string? TargetControlType { get; set; }
+
+ internal bool IsMatchingTarget()
+ {
+ var ct = this.ControlType;
+ return string.IsNullOrEmpty(this.TargetControlType) || this.TargetControlType == this.ControlType;
+ }
+
internal void SetWindowsElement(WindowsElement windowsElement) => this.windowsElement = windowsElement;
internal void SetSession(WindowsDriver driver) => this.driver = driver;
@@ -91,7 +99,7 @@ public string ControlType
/// Click the UI element.
///
/// If true, performs a right-click; otherwise, performs a left-click. Default value is false
- public void Click(bool rightClick = false)
+ public virtual void Click(bool rightClick = false)
{
PerformAction((actions, windowElement) =>
{
@@ -116,7 +124,7 @@ public void Click(bool rightClick = false)
///
/// Double Click the UI element.
///
- public void DoubleClick()
+ public virtual void DoubleClick()
{
PerformAction((actions, windowElement) =>
{
@@ -139,7 +147,6 @@ public string GetAttribute(string attributeName)
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method GetAttribute with parameter: attributeName = {attributeName}");
var attributeValue = this.windowsElement.GetAttribute(attributeName);
- Assert.IsNotNull(attributeValue, $"Attribute '{attributeName}' is null.");
return attributeValue;
}
@@ -154,17 +161,51 @@ public T Find(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
- var foundElement = FindHelper.Find(
- () =>
- {
- var element = this.windowsElement.FindElement(by.ToSeleniumBy());
- Assert.IsNotNull(element, $"Element not found using selector: {by}");
- return element;
- },
- this.driver,
- timeoutMS);
- return foundElement;
+ // leverage findAll to filter out mismatched elements
+ var collection = this.FindAll(by, timeoutMS);
+
+ Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
+
+ return collection[0];
+ }
+
+ ///
+ /// Finds an element by the selector.
+ /// Shortcut for this.Find(By.Name(name), timeoutMS)
+ ///
+ /// The class type of the element to find.
+ /// The name for finding the element.
+ /// The timeout in milliseconds.
+ /// The found element.
+ public T Find(string name, int timeoutMS = 3000)
+ where T : Element, new()
+ {
+ return this.Find(By.Name(name), timeoutMS);
+ }
+
+ ///
+ /// Finds an element by the selector.
+ /// Shortcut for this.Find(by, timeoutMS)
+ ///
+ /// The selector to use for finding the element.
+ /// The timeout in milliseconds.
+ /// The found element.
+ public Element Find(By by, int timeoutMS = 3000)
+ {
+ return this.Find(by, timeoutMS);
+ }
+
+ ///
+ /// Finds an element by the selector.
+ /// Shortcut for this.Find(By.Name(name), timeoutMS)
+ ///
+ /// The name for finding the element.
+ /// The timeout in milliseconds.
+ /// The found element.
+ public Element Find(string name, int timeoutMS = 3000)
+ {
+ return this.Find(By.Name(name), timeoutMS);
}
///
@@ -187,16 +228,54 @@ public ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
this.driver,
timeoutMS);
- return foundElements ?? new ReadOnlyCollection(new List());
+ return foundElements ?? new ReadOnlyCollection([]);
+ }
+
+ ///
+ /// Finds all elements by the selector.
+ /// Shortcut for this.FindAll(By.Name(name), timeoutMS)
+ ///
+ /// The class type of the elements to find.
+ /// The name for finding the element.
+ /// The timeout in milliseconds.
+ /// A read-only collection of the found elements.
+ public ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
+ where T : Element, new()
+ {
+ return this.FindAll(By.Name(name), timeoutMS);
+ }
+
+ ///
+ /// Finds all elements by the selector.
+ /// Shortcut for this.FindAll(by, timeoutMS)
+ ///
+ /// The selector to use for finding the elements.
+ /// The timeout in milliseconds.
+ /// A read-only collection of the found elements.
+ public ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
+ {
+ return this.FindAll(by, timeoutMS);
+ }
+
+ ///
+ /// Finds all elements by the selector.
+ /// Shortcut for this.FindAll(By.Name(name), timeoutMS)
+ ///
+ /// The name for finding the element.
+ /// The timeout in milliseconds.
+ /// A read-only collection of the found elements.
+ public ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
+ {
+ return this.FindAll(By.Name(name), timeoutMS);
}
///
/// 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 100 ms
- /// The number of milliseconds to wait after the action. Default value is 100 ms
- protected void PerformAction(Action action, int msPreAction = 100, int msPostAction = 100)
+ /// 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)
{
diff --git a/src/common/UITestAutomation/Element/HyperlinkButton.cs b/src/common/UITestAutomation/Element/HyperlinkButton.cs
new file mode 100644
index 000000000000..738414b1b508
--- /dev/null
+++ b/src/common/UITestAutomation/Element/HyperlinkButton.cs
@@ -0,0 +1,23 @@
+// 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 Microsoft.PowerToys.UITest
+{
+ ///
+ /// Represents a HyperLinkButton in the UI test environment.
+ /// HyperLinkButton reepresents a button control that functions as a hyperlink.
+ ///
+ public class HyperlinkButton : Button
+ {
+ private static readonly string ExpectedControlType = "ControlType.HyperLink";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HyperlinkButton()
+ {
+ this.TargetControlType = HyperlinkButton.ExpectedControlType;
+ }
+ }
+}
diff --git a/src/common/UITestAutomation/Element/NavigationViewItem.cs b/src/common/UITestAutomation/Element/NavigationViewItem.cs
new file mode 100644
index 000000000000..c23713de31a4
--- /dev/null
+++ b/src/common/UITestAutomation/Element/NavigationViewItem.cs
@@ -0,0 +1,59 @@
+// 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 Microsoft.PowerToys.UITest
+{
+ ///
+ /// Represents a NavigationViewItem in the UI test environment.
+ /// NavigationViewItem pepresents the container for an item in a NavigationView control.
+ ///
+ public class NavigationViewItem : Element
+ {
+ private static readonly string ExpectedControlType = "ControlType.ListItem";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NavigationViewItem()
+ {
+ this.TargetControlType = NavigationViewItem.ExpectedControlType;
+ }
+
+ ///
+ /// Click the ListItem element.
+ ///
+ /// If true, performs a right-click; otherwise, performs a left-click. Default value is false
+ public override void Click(bool rightClick = false)
+ {
+ PerformAction((actions, windowElement) =>
+ {
+ actions.MoveToElement(windowElement, 10, 10);
+
+ if (rightClick)
+ {
+ actions.ContextClick();
+ }
+ else
+ {
+ actions.Click();
+ }
+
+ actions.Build().Perform();
+ });
+ }
+
+ ///
+ /// Double Click the ListItem element.
+ ///
+ public override void DoubleClick()
+ {
+ PerformAction((actions, windowElement) =>
+ {
+ actions.MoveToElement(windowElement, 10, 10);
+ actions.DoubleClick();
+ actions.Build().Perform();
+ });
+ }
+ }
+}
diff --git a/src/common/UITestAutomation/Element/TextBlock.cs b/src/common/UITestAutomation/Element/TextBlock.cs
new file mode 100644
index 000000000000..883b104e2623
--- /dev/null
+++ b/src/common/UITestAutomation/Element/TextBlock.cs
@@ -0,0 +1,23 @@
+// 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 Microsoft.PowerToys.UITest
+{
+ ///
+ /// Represents a TextBlock in the UI test environment.
+ /// TextBlock provides a lightweight control for displaying small amounts of flow content.
+ ///
+ public class TextBlock : Element
+ {
+ private static readonly string ExpectedControlType = "ControlType.Text";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TextBlock()
+ {
+ this.TargetControlType = TextBlock.ExpectedControlType;
+ }
+ }
+}
diff --git a/src/common/UITestAutomation/Element/TextBox.cs b/src/common/UITestAutomation/Element/TextBox.cs
index e427f7c9d316..dc6d70fe38cc 100644
--- a/src/common/UITestAutomation/Element/TextBox.cs
+++ b/src/common/UITestAutomation/Element/TextBox.cs
@@ -7,10 +7,21 @@
namespace Microsoft.PowerToys.UITest
{
///
- /// Represents a textbox in the UI test environment.
+ /// Represents a TextBox in the UI test environment.
+ /// TextBox rpresents a control that can be used to display and edit plain text (single or multi-line).
///
public class TextBox : Element
{
+ private static readonly string ExpectedControlType = "ControlType.Edit";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TextBox()
+ {
+ this.TargetControlType = TextBox.ExpectedControlType;
+ }
+
///
/// Sets the text of the textbox.
///
diff --git a/src/common/UITestAutomation/Element/Window.cs b/src/common/UITestAutomation/Element/Window.cs
index eceb1fcf4793..5fc5fc6e266c 100644
--- a/src/common/UITestAutomation/Element/Window.cs
+++ b/src/common/UITestAutomation/Element/Window.cs
@@ -18,7 +18,7 @@ public Window Maximize(bool byClickButton = true)
{
if (byClickButton)
{
- Find
internal static class FindHelper
{
- public static T Find(Func findElementFunc, WindowsDriver? driver, int timeoutMS)
- where T : Element, new()
- {
- var item = findElementFunc() as WindowsElement;
- return NewElement(item, driver, timeoutMS);
- }
-
public static ReadOnlyCollection? FindAll(Func> findElementsFunc, WindowsDriver? driver, int timeoutMS)
where T : Element, new()
{
@@ -32,7 +25,7 @@ public static T Find(Func findElementFunc, WindowsDriver(element, driver, timeoutMS);
- }).ToList();
+ }).Where(item => item.IsMatchingTarget()).ToList();
return new ReadOnlyCollection(res);
}
diff --git a/src/common/UITestAutomation/Session.cs b/src/common/UITestAutomation/Session.cs
index 7071f6d2e236..ef0a6fff3fad 100644
--- a/src/common/UITestAutomation/Session.cs
+++ b/src/common/UITestAutomation/Session.cs
@@ -4,6 +4,7 @@
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
+using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
@@ -39,17 +40,48 @@ public T Find(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
- var foundElement = FindHelper.Find(
- () =>
- {
- var element = this.WindowsDriver.FindElement(by.ToSeleniumBy());
- Assert.IsNotNull(element, $"Element not found using selector: {by}");
- return element;
- },
- this.WindowsDriver,
- timeoutMS);
- return foundElement;
+ // leverage findAll to filter out mismatched elements
+ var collection = this.FindAll(by, timeoutMS);
+
+ Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
+
+ return collection[0];
+ }
+
+ ///
+ /// Shortcut for this.Find(By.Name(name), timeoutMS)
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// The name of the element.
+ /// The timeout in milliseconds (default is 3000).
+ /// The found element.
+ public T Find(string name, int timeoutMS = 3000)
+ where T : Element, new()
+ {
+ return this.Find(By.Name(name), timeoutMS);
+ }
+
+ ///
+ /// Shortcut for this.Find(by, timeoutMS)
+ ///
+ /// The selector to find the element.
+ /// The timeout in milliseconds (default is 3000).
+ /// The found element.
+ public Element Find(By by, int timeoutMS = 3000)
+ {
+ return this.Find(by, timeoutMS);
+ }
+
+ ///
+ /// Shortcut for this.Find(By.Name(name), timeoutMS)
+ ///
+ /// The name of the element.
+ /// The timeout in milliseconds (default is 3000).
+ /// The found element.
+ public Element Find(string name, int timeoutMS = 3000)
+ {
+ return this.Find(By.Name(name), timeoutMS);
}
///
@@ -72,7 +104,45 @@ public ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
this.WindowsDriver,
timeoutMS);
- return foundElements ?? new ReadOnlyCollection(new List());
+ return foundElements ?? new ReadOnlyCollection([]);
+ }
+
+ ///
+ /// Finds all elements by selector.
+ /// Shortcut for this.FindAll(By.Name(name), timeoutMS)
+ ///
+ /// The class of the elements, should be Element or its derived class.
+ /// The name to find the elements.
+ /// The timeout in milliseconds (default is 3000).
+ /// A read-only collection of the found elements.
+ public ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
+ where T : Element, new()
+ {
+ return this.FindAll(By.Name(name), timeoutMS);
+ }
+
+ ///
+ /// Finds all elements by selector.
+ /// Shortcut for this.FindAll(by, timeoutMS)
+ ///
+ /// The selector to find the elements.
+ /// The timeout in milliseconds (default is 3000).
+ /// A read-only collection of the found elements.
+ public ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
+ {
+ return this.FindAll(by, timeoutMS);
+ }
+
+ ///
+ /// Finds all elements by selector.
+ /// Shortcut for this.FindAll(By.Name(name), timeoutMS)
+ ///
+ /// The name to find the elements.
+ /// The timeout in milliseconds (default is 3000).
+ /// A read-only collection of the found elements.
+ public ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
+ {
+ return this.FindAll(By.Name(name), timeoutMS);
}
///
diff --git a/src/common/UITestAutomation/SessionHelper.cs b/src/common/UITestAutomation/SessionHelper.cs
index ec2e97caec76..7bb1f6e7a6fd 100644
--- a/src/common/UITestAutomation/SessionHelper.cs
+++ b/src/common/UITestAutomation/SessionHelper.cs
@@ -25,21 +25,16 @@ internal class SessionHelper
private Process? appDriver;
- public SessionHelper(PowerToysModule scope, bool runAsAdmin)
+ public SessionHelper(PowerToysModule scope)
{
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
var winAppDriverProcessInfo = new ProcessStartInfo
{
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
+ Verb = "runas",
};
- if (runAsAdmin)
- {
- winAppDriverProcessInfo.UseShellExecute = true;
- winAppDriverProcessInfo.Verb = "runas";
- }
-
this.appDriver = Process.Start(winAppDriverProcessInfo);
var desktopCapabilities = new AppiumOptions();
diff --git a/src/common/UITestAutomation/UITestBase.cs b/src/common/UITestAutomation/UITestBase.cs
index 2fbb6cefe827..1d6502ac5447 100644
--- a/src/common/UITestAutomation/UITestBase.cs
+++ b/src/common/UITestAutomation/UITestBase.cs
@@ -15,15 +15,19 @@ namespace Microsoft.PowerToys.UITest
///
/// Base class that should be inherited by all Test Classes.
///
+ [TestClass]
public class UITestBase
{
public Session Session { get; set; }
private readonly SessionHelper sessionHelper;
- public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, bool runAsAdmin = false)
+ private readonly PowerToysModule scope;
+
+ public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
{
- this.sessionHelper = new SessionHelper(scope, runAsAdmin).Init();
+ this.scope = scope;
+ this.sessionHelper = new SessionHelper(scope).Init();
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
}
@@ -32,6 +36,23 @@ public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, boo
this.sessionHelper.Cleanup();
}
+ ///
+ /// Initializes the test.
+ ///
+ [TestInitialize]
+ public void TestInit()
+ {
+ if (this.scope == PowerToysModule.PowerToysSettings)
+ {
+ // close Debug warning dialog if any
+ // Such debug warning dialog seems only appear in PowerToys Settings
+ if (this.FindAll("DEBUG").Count > 0)
+ {
+ this.Find("DEBUG").Find("Close").Click();
+ }
+ }
+ }
+
///
/// Finds an element by selector.
/// Shortcut for this.Session.Find(by, timeoutMS)
@@ -46,6 +67,41 @@ protected T Find(By by, int timeoutMS = 3000)
return this.Session.Find(by, timeoutMS);
}
+ ///
+ /// Shortcut for this.Session.Find(By.Name(name), timeoutMS)
+ ///
+ /// The class of the element, should be Element or its derived class.
+ /// The name of the element.
+ /// The timeout in milliseconds (default is 3000).
+ /// The found element.
+ protected T Find(string name, int timeoutMS = 3000)
+ where T : Element, new()
+ {
+ return this.Session.Find(By.Name(name), timeoutMS);
+ }
+
+ ///
+ /// Shortcut for this.Session.Find(by, timeoutMS)
+ ///
+ /// The selector to find the element.
+ /// The timeout in milliseconds (default is 3000).
+ /// The found element.
+ protected Element Find(By by, int timeoutMS = 3000)
+ {
+ return this.Session.Find(by, timeoutMS);
+ }
+
+ ///
+ /// Shortcut for this.Session.Find(By.Name(name), timeoutMS)
+ ///
+ /// The name of the element.
+ /// The timeout in milliseconds (default is 3000).
+ /// The found element.
+ protected Element Find(string name, int timeoutMS = 3000)
+ {
+ return this.Session.Find(name, timeoutMS);
+ }
+
///
/// Finds all elements by selector.
/// Shortcut for this.Session.FindAll(by, timeoutMS)
@@ -59,5 +115,43 @@ protected ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
{
return this.Session.FindAll(by, timeoutMS);
}
+
+ ///
+ /// Finds all elements by selector.
+ /// Shortcut for this.Session.FindAll(By.Name(name), timeoutMS)
+ ///
+ /// The class of the elements, should be Element or its derived class.
+ /// The name of the elements.
+ /// The timeout in milliseconds (default is 3000).
+ /// A read-only collection of the found elements.
+ protected ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
+ where T : Element, new()
+ {
+ return this.Session.FindAll(By.Name(name), timeoutMS);
+ }
+
+ ///
+ /// Finds all elements by selector.
+ /// Shortcut for this.Session.FindAll(by, timeoutMS)
+ ///
+ /// The selector to find the elements.
+ /// The timeout in milliseconds (default is 3000).
+ /// A read-only collection of the found elements.
+ protected ReadOnlyCollection FindAll(By by, int timeoutMS = 3000)
+ {
+ return this.Session.FindAll(by, timeoutMS);
+ }
+
+ ///
+ /// Finds all elements by selector.
+ /// Shortcut for this.Session.FindAll(By.Name(name), timeoutMS)
+ ///
+ /// The name of the elements.
+ /// The timeout in milliseconds (default is 3000).
+ /// A read-only collection of the found elements.
+ protected ReadOnlyCollection FindAll(string name, int timeoutMS = 3000)
+ {
+ return this.Session.FindAll(By.Name(name), timeoutMS);
+ }
}
}
diff --git a/src/modules/Hosts/Hosts.UITests/HostModuleTests.cs b/src/modules/Hosts/Hosts.UITests/HostModuleTests.cs
index 77b494c1673d..58fba007fa97 100644
--- a/src/modules/Hosts/Hosts.UITests/HostModuleTests.cs
+++ b/src/modules/Hosts/Hosts.UITests/HostModuleTests.cs
@@ -28,16 +28,16 @@ public void TestEmptyView()
this.RemoveAllEntries();
// 'Add an entry' button (only show-up when list is empty) should be visible
- Assert.IsTrue(this.FindAll(By.Name("Add an entry")).Count == 1, "'Add an entry' button should be visible in the empty view");
+ Assert.IsTrue(this.FindAll("Add an entry").Count == 1, "'Add an entry' button should be visible in the empty view");
// Click 'Add an entry' from empty-view for adding Host override rule
- this.Find(By.Name("Add an entry")).Click();
+ this.Find("Add an entry").Click();
this.AddEntry("192.168.0.1", "localhost", false, false);
// Should have one row now and not more empty view
- Assert.IsTrue(this.FindAll(By.Name("Delete")).Count == 1, "Should have one row now");
- Assert.IsTrue(this.FindAll(By.Name("Add an entry")).Count == 0, "'Add an entry' button should be invisible if not empty view");
+ Assert.IsTrue(this.FindAll("Delete").Count == 1, "Should have one row now");
+ Assert.IsTrue(this.FindAll("Add an entry").Count == 0, "'Add an entry' button should be invisible if not empty view");
}
///
@@ -49,11 +49,42 @@ public void TestAddingEntry()
this.CloseWarningDialog();
this.RemoveAllEntries();
- Assert.IsTrue(this.FindAll(By.Name("Delete")).Count == 0, "Should have no row after removing all");
+ Assert.IsTrue(this.FindAll("Delete").Count == 0, "Should have no row after removing all");
this.AddEntry("192.168.0.1", "localhost", true);
- Assert.IsTrue(this.FindAll(By.Name("Delete")).Count == 1, "Should have one row now");
+ Assert.IsTrue(this.FindAll("Delete").Count == 1, "Should have one row now");
+ }
+
+ ///
+ /// Test when adding more than 9 entries
+ ///
+ [TestMethod]
+ public void TestTooManyHosts()
+ {
+ this.CloseWarningDialog();
+
+ // only at most 9 hosts allowed in one entry
+ string validHosts = string.Join(" ", "host_1", "host_2", "host_3", "host_4", "host_5", "host_6", "host_7", "host_8", "host_9");
+
+ // should not allow to add more than 9 hosts in one entry, hosts are separated by space
+ string inValidHosts = validHosts + " more_host";
+
+ this.Find("New entry").Click();
+
+ Assert.IsFalse(this.Find("Add").Enabled, "Add button should be Disabled by default");
+
+ this.Find("Address").SetText("127.0.0.1");
+
+ this.Find("Hosts").SetText(validHosts);
+
+ Assert.IsTrue(this.Find("Add").Enabled, "Add button should be Enabled with validHosts");
+
+ this.Find("Hosts").SetText(inValidHosts);
+
+ Assert.IsFalse(this.Find("Add").Enabled, "Add button should be Enabled with validHosts");
+
+ this.Find("Cancel").Click();
}
///
@@ -69,8 +100,7 @@ public void TestErrorMessageWithNonAdminPermission()
this.AddEntry("192.168.0.1", "localhost", true);
Assert.IsTrue(
- this.FindAll(By.TagName("StatusBar")).Count == 1 &&
- this.Find(By.TagName("StatusBar")).FindAll(By.Name("The hosts file cannot be saved because the program isn't running as administrator.")).Count == 1,
+ this.FindAll("The hosts file cannot be saved because the program isn't running as administrator.").Count == 1,
"Should display host-file saving error if not run as administrator");
}
@@ -79,21 +109,21 @@ private void AddEntry(string ip, string host, bool active = true, bool clickAddE
if (clickAddEntryButton)
{
// Click 'Add an entry' for adding Host override rule
- this.Find(By.Name("New entry")).Click();
+ this.Find("New entry").Click();
}
// Adding a new host override localhost -> 192.168.0.1
- Assert.IsFalse(this.Find(By.Name("Add")).Enabled, "Add button should be Disabled by default");
+ Assert.IsFalse(this.Find("Add").Enabled, "Add button should be Disabled by default");
- Assert.IsTrue(this.Find(By.Name("Address")).SetText(ip, false).Text == ip);
- Assert.IsTrue(this.Find(By.Name("Hosts")).SetText(host, false).Text == host);
+ Assert.IsTrue(this.Find("Address").SetText(ip).Text == ip);
+ Assert.IsTrue(this.Find("Hosts").SetText(host).Text == host);
- this.Find(By.Name("Active")).Toggle(active);
+ this.Find("Active").Toggle(active);
- Assert.IsTrue(this.Find(By.Name("Add")).Enabled, "Add button should be Enabled after providing valid inputs");
+ Assert.IsTrue(this.Find("Add").Enabled, "Add button should be Enabled after providing valid inputs");
// Add the entry
- this.Find(By.Name("Add")).Click();
+ this.Find("Add").Click();
// 0.5 second delay after adding an entry
Task.Delay(500).Wait();
@@ -102,25 +132,25 @@ private void AddEntry(string ip, string host, bool active = true, bool clickAddE
private void CloseWarningDialog()
{
// Find 'Accept' button which come in 'Warning' dialog
- if (this.FindAll(By.Name("Warning")).Count > 0 &&
- this.FindAll(By.Name("Accept")).Count > 0)
+ if (this.FindAll("Warning").Count > 0 &&
+ this.FindAll("Accept").Count > 0)
{
// Hide Warning dialog if any
- this.Find(By.Name("Accept")).Click();
+ this.Find("Accept").Click();
}
}
private void RemoveAllEntries()
{
// Delete all existing host-override rules
- foreach (var deleteBtn in this.FindAll(By.Name("Delete")))
+ foreach (var deleteBtn in this.FindAll("Delete"))
{
deleteBtn.Click();
- this.Find(By.Name("Yes")).Click();
+ this.Find("Yes").Click();
}
// Should have no row left, and no more delete button
- Assert.IsTrue(this.FindAll(By.Name("Delete")).Count == 0);
+ Assert.IsTrue(this.FindAll("Delete").Count == 0);
}
}
}
diff --git a/src/modules/Hosts/Hosts.UITests/HostsSettingTests.cs b/src/modules/Hosts/Hosts.UITests/HostsSettingTests.cs
new file mode 100644
index 000000000000..735b5f8c8e05
--- /dev/null
+++ b/src/modules/Hosts/Hosts.UITests/HostsSettingTests.cs
@@ -0,0 +1,85 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.PowerToys.UITest;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Hosts.UITests
+{
+ [TestClass]
+ public class HostsSettingTests : UITestBase
+ {
+ [TestMethod]
+ public void TestShowWarningDialog()
+ {
+ this.GotoHostsFileEditor();
+
+ this.Find("Enable Hosts File Editor").Toggle(true);
+ this.Find("Launch as administrator").Toggle(false);
+ this.Find("Show a warning at startup").Toggle(true);
+
+ this.Find("Launch Hosts File Editor").Click();
+
+ // wait for 500 ms to make sure Hosts File Editor is launched
+ Task.Delay(500).Wait();
+
+ this.Session.Attach(PowerToysModule.Hosts);
+
+ // Should show warning dialog
+ Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
+
+ // Quit Hosts File Editor
+ this.Find("Quit").Click();
+
+ try
+ {
+ // Wait for 500 ms to make sure Hosts File Editor is closed
+ Task.Delay(500).Wait();
+
+ this.Session.FindAll("Hosts File Editor");
+ Assert.IsTrue(false, "Hosts File Editor should be closed");
+ }
+ catch (Exception ex)
+ {
+ // Hosts File Editor should be closed
+ Assert.IsTrue(ex.Message.Contains("Currently selected window has been closed"), "Hosts File Editor should be closed");
+ }
+ }
+
+ [TestMethod]
+ public void TestNotShowWarningDialog()
+ {
+ this.GotoHostsFileEditor();
+
+ this.Find("Enable Hosts File Editor").Toggle(true);
+ this.Find("Launch as administrator").Toggle(false);
+ this.Find("Show a warning at startup").Toggle(false);
+
+ this.Find("Launch Hosts File Editor").Click();
+
+ Task.Delay(500).Wait();
+
+ this.Session.Attach(PowerToysModule.Hosts);
+
+ // Should NOT show warning dialog
+ Assert.IsTrue(this.FindAll("Warning").Count == 0, "Should not show warning dialog");
+
+ this.Session.Find("Hosts File Editor").Close();
+ }
+
+ private void GotoHostsFileEditor()
+ {
+ // Goto Hosts File Editor setting page
+ if (this.FindAll("Hosts File Editor").Count == 0)
+ {
+ // Expand Advanced list-group if needed
+ this.Find("Advanced").Click();
+ }
+
+ this.Find("Hosts File Editor").Click();
+ }
+ }
+}
diff --git a/src/modules/Hosts/Hosts.UITests/Release-Test-Checklist.md b/src/modules/Hosts/Hosts.UITests/Release-Test-Checklist.md
new file mode 100644
index 000000000000..8444809b64af
--- /dev/null
+++ b/src/modules/Hosts/Hosts.UITests/Release-Test-Checklist.md
@@ -0,0 +1,26 @@
+## This is for tracking UI-Tests migration progress for Hosts File Editor Module
+Refer to [release check list] (https://github.com/microsoft/PowerToys/blob/releaseChecklist/doc/releases/tests-checklist-template.md#hosts-file-editor) for all manual tests.
+
+### Existing Manual Test-cases run by previous PowerToys owner
+For existing manual test-cases, we will convert them to UI-Tests and run them in CI and Release pipeline
+
+ * Launch Host File Editor:
+ - [x] Verify the application exits if "Quit" is clicked on the initial warning. (**HostsSettingTests.TestShowWarningDialog**)
+ - [x] Launch Host File Editor again and click "Accept". The module should not close. (**HostModuleTests.TestEmptyView**)
+ - [ ] Launch Host File Editor again and click "Accept". The module should not close. Open the hosts file (`%WinDir%\System32\Drivers\Etc`) in a text editor that auto-refreshes so you can see the changes applied by the editor in real time. (VSCode is an editor like this, for example)
+ - [ ] Enable and disable lines and verify they are applied to the file.
+ - [ ] Add a new entry and verify it's applied.
+ - [ ] Add manually an entry with more than 9 hosts in hosts file (Windows limitation) and verify it is split correctly on loading and the info bar is shown.
+ - [ ] Try to filter for lines and verify you can find them.
+ - [ ] Click the "Open hosts file" button and verify it opens in your default editor. (likely Notepad)
+ * Test the different settings and verify they are applied:
+ - [ ] Launch as Administrator.
+ - [x] Show a warning at startup. (**HostsSettingTests.TestShowWarningDialog**)
+ - [ ] Additional lines position.
+
+### Additional UI-Tests cases
+ - [x] Add manually an entry with more than 9 hosts and Add button should be disabled. (**HostModuleTests.TestTooManyHosts**)
+ - [x] Add manually an entry with less or equal 9 hosts and Add button should be enabled. (**HostModuleTests.TestTooManyHosts**)
+ - [x] Should show empty view if no entries. (**HostModuleTests.TestEmptyView**)
+ - [x] Add a new entry with valid or invalid input (**HostModuleTests.TestAddHost**)
+ - [x] Show save host file error if not run as Administrator. (**HostModuleTests.TestErrorMessageWithNonAdminPermission**)
\ No newline at end of file