mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
[Hosts] Converting manual release-check-list tests to UI-Test Automation and Adding more UI-Test-cases (#37657)
* Add more UI-Test, refactor UITestAutomation * Convert manual test-case to automation UI-Tests: Validating Empty-view is shown if no entries in the list. Validating Empty-view is NOT shown if 1 or more entries in the list. Validating Add-an-entry HyperlinkButton in Empty-view works correctly. Validating Adding-entry Button works correctly. Validating the Add button should be Disabled if more than 9 hosts in one entry. Validating the Add button should be Enabled if less or equal 9 hosts in one entry. Validating error message should be shown if not run as admin. Validating Warning-Dialog will be shown if 'Show a warning at startup' toggle is On. Validating Warning-Dialog will NOT be shown if 'Show a warning at startup' toggle is Off. Validating click 'Quit' button in Warning-Dialog, the Hosts File Editor window would be closed. Validating click 'Accept' button in Warning-Dialog, the Hosts File Editor window would NOT be closed. --------- Co-authored-by: Jerry Xu <nxu@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -9,5 +9,14 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public class Button : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.Button";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Button"/> class.
|
||||
/// </summary>
|
||||
public Button()
|
||||
{
|
||||
this.TargetControlType = Button.ExpectedControlType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,14 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
private WindowsDriver<WindowsElement>? 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<WindowsElement> driver) => this.driver = driver;
|
||||
@@ -91,7 +99,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Click the UI element.
|
||||
/// </summary>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
|
||||
public void Click(bool rightClick = false)
|
||||
public virtual void Click(bool rightClick = false)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
@@ -116,7 +124,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Double Click the UI element.
|
||||
/// </summary>
|
||||
public void DoubleClick()
|
||||
public virtual void DoubleClick()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
@@ -139,7 +147,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
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 @@ namespace Microsoft.PowerToys.UITest
|
||||
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<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
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<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by the selector.
|
||||
/// Shortcut for this.Find<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class type of the element to find.</typeparam>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by the selector.
|
||||
/// Shortcut for this.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to use for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by the selector.
|
||||
/// Shortcut for this.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,7 +215,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T>? FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -182,22 +223,59 @@ namespace Microsoft.PowerToys.UITest
|
||||
() =>
|
||||
{
|
||||
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
|
||||
Assert.IsTrue(elements.Count > 0, $"Elements not found using selector: {by}");
|
||||
return elements;
|
||||
},
|
||||
this.driver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements;
|
||||
return foundElements ?? new ReadOnlyCollection<T>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by the selector.
|
||||
/// Shortcut for this.FindAll<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class type of the elements to find.</typeparam>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by the selector.
|
||||
/// Shortcut for this.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by the selector.
|
||||
/// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 100 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 100 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsElement> action, int msPreAction = 100, int msPostAction = 100)
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 500 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 500 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsElement> action, int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
|
||||
23
src/common/UITestAutomation/Element/HyperlinkButton.cs
Normal file
23
src/common/UITestAutomation/Element/HyperlinkButton.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a HyperLinkButton in the UI test environment.
|
||||
/// HyperLinkButton represents a button control that functions as a hyperlink.
|
||||
/// </summary>
|
||||
public class HyperlinkButton : Button
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.HyperLink";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HyperlinkButton"/> class.
|
||||
/// </summary>
|
||||
public HyperlinkButton()
|
||||
{
|
||||
this.TargetControlType = HyperlinkButton.ExpectedControlType;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/common/UITestAutomation/Element/NavigationViewItem.cs
Normal file
59
src/common/UITestAutomation/Element/NavigationViewItem.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a NavigationViewItem in the UI test environment.
|
||||
/// NavigationViewItem represents the container for an item in a NavigationView control.
|
||||
/// </summary>
|
||||
public class NavigationViewItem : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.ListItem";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NavigationViewItem"/> class.
|
||||
/// </summary>
|
||||
public NavigationViewItem()
|
||||
{
|
||||
this.TargetControlType = NavigationViewItem.ExpectedControlType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Click the ListItem element.
|
||||
/// </summary>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Double Click the ListItem element.
|
||||
/// </summary>
|
||||
public override void DoubleClick()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement, 10, 10);
|
||||
actions.DoubleClick();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/common/UITestAutomation/Element/TextBlock.cs
Normal file
23
src/common/UITestAutomation/Element/TextBlock.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TextBlock in the UI test environment.
|
||||
/// TextBlock provides a lightweight control for displaying small amounts of flow content.
|
||||
/// </summary>
|
||||
public class TextBlock : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.Text";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextBlock"/> class.
|
||||
/// </summary>
|
||||
public TextBlock()
|
||||
{
|
||||
this.TargetControlType = TextBlock.ExpectedControlType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,21 @@ using OpenQA.Selenium;
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a textbox in the UI test environment.
|
||||
/// Represents a TextBox in the UI test environment.
|
||||
/// TextBox represents a control that can be used to display and edit plain text (single or multi-line).
|
||||
/// </summary>
|
||||
public class TextBox : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.Edit";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextBox"/> class.
|
||||
/// </summary>
|
||||
public TextBox()
|
||||
{
|
||||
this.TargetControlType = TextBox.ExpectedControlType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text of the textbox.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>(By.Name("Maximize")).Click();
|
||||
Find<Button>("Maximize").Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -37,7 +37,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>(By.Name("Restore")).Click();
|
||||
Find<Button>("Restore").Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>(By.Name("Minimize")).Click();
|
||||
Find<Button>("Minimize").Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -74,7 +74,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>(By.Name("Close")).Click();
|
||||
Find<Button>("Close").Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -17,13 +17,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
internal static class FindHelper
|
||||
{
|
||||
public static T Find<T, TW>(Func<TW> findElementFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
var item = findElementFunc() as WindowsElement;
|
||||
return NewElement<T>(item, driver, timeoutMS);
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<ReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
@@ -32,7 +25,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
var element = item as WindowsElement;
|
||||
return NewElement<T>(element, driver, timeoutMS);
|
||||
}).ToList();
|
||||
}).Where(item => item.IsMatchingTarget()).ToList();
|
||||
|
||||
return new ReadOnlyCollection<T>(res);
|
||||
}
|
||||
|
||||
@@ -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 @@ namespace Microsoft.PowerToys.UITest
|
||||
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<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
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<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Find<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +104,45 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.WindowsDriver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements ?? new ReadOnlyCollection<T>(new List<T>());
|
||||
return foundElements ?? new ReadOnlyCollection<T>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.FindAll<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
101
src/common/UITestAutomation/SessionHelper.cs
Normal file
101
src/common/UITestAutomation/SessionHelper.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
internal class SessionHelper
|
||||
{
|
||||
// Default session path is PowerToys settings dashboard
|
||||
private readonly string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
|
||||
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
private Process? appDriver;
|
||||
|
||||
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",
|
||||
};
|
||||
|
||||
this.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
this.Root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Root.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test environment.
|
||||
/// </summary>
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public SessionHelper Init()
|
||||
{
|
||||
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
this.StartExe(path + 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the test environment.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
appDriver?.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle exceptions if needed
|
||||
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
|
||||
public WindowsDriver<WindowsElement> GetDriver()
|
||||
{
|
||||
Assert.IsNotNull(this.Driver, $"Failed to get driver. Driver is null.");
|
||||
return this.Driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,42 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Base class that should be inherited by all Test Classes.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class UITestBase
|
||||
{
|
||||
public Session Session { get; set; }
|
||||
|
||||
private readonly TestInit testInit = new TestInit();
|
||||
private readonly SessionHelper sessionHelper;
|
||||
|
||||
private readonly PowerToysModule scope;
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
this.testInit.SetScope(scope);
|
||||
this.testInit.Init();
|
||||
this.Session = new Session(this.testInit.GetRoot(), this.testInit.GetDriver());
|
||||
this.scope = scope;
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
}
|
||||
|
||||
~UITestBase()
|
||||
{
|
||||
this.testInit.Cleanup();
|
||||
this.sessionHelper.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test.
|
||||
/// </summary>
|
||||
[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<Button>("Close").Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,6 +67,41 @@ namespace Microsoft.PowerToys.UITest
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Session.Find(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Session.Find(name, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)
|
||||
@@ -62,93 +117,41 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
private sealed class TestInit
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
private static Process? appDriver;
|
||||
|
||||
// Default session path is PowerToys settings dashboard
|
||||
private static string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
|
||||
|
||||
public TestInit()
|
||||
{
|
||||
appDriver = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
Verb = "runas",
|
||||
});
|
||||
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
this.Root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Root.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test environment.
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public void Init()
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
{
|
||||
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
this.StartExe(path + 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.Session.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the test environment.
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
{
|
||||
try
|
||||
{
|
||||
appDriver?.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle exceptions if needed
|
||||
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets scope to the Test Class.
|
||||
/// </summary>
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
public void SetScope(PowerToysModule scope)
|
||||
{
|
||||
sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
|
||||
public WindowsDriver<WindowsElement> GetDriver()
|
||||
{
|
||||
Assert.IsNotNull(this.Driver, $"Failed to get driver. Driver is null.");
|
||||
return this.Driver;
|
||||
}
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -18,8 +18,18 @@ namespace Hosts.UITests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if Empty-view is shown when no entries are present.
|
||||
/// And 'Add an entry' button from Empty-view is functional.
|
||||
/// Test Empty-view in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Empty-view is shown if no entries in the list.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Empty-view is NOT shown if 1 or more entries in the list.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Add-an-entry HyperlinkButton in Empty-view works correctly.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestEmptyView()
|
||||
@@ -28,20 +38,25 @@ namespace Hosts.UITests
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("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<Button>(By.Name("Add an entry")).Click();
|
||||
this.Find<HyperlinkButton>("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<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if 'New entry' button is functional
|
||||
/// Test Adding-entry Button in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Adding-entry Button works correctly.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestAddingEntry()
|
||||
@@ -49,11 +64,155 @@ namespace Hosts.UITests
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0, "Should have no row after removing all");
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0, "Should have no row after removing all");
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Multiple-hosts validation logic
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating the Add button should be Disabled if more than 9 hosts in one entry.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating the Add button should be Enabled if less or equal 9 hosts in one entry.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[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<Button>("New entry").Click();
|
||||
|
||||
Assert.IsFalse(this.Find<Button>("Add").Enabled, "Add button should be Disabled by default");
|
||||
|
||||
this.Find<TextBox>("Address").SetText("127.0.0.1");
|
||||
|
||||
this.Find<TextBox>("Hosts").SetText(validHosts);
|
||||
Assert.IsTrue(this.Find<Button>("Add").Enabled, "Add button should be Enabled with validHosts");
|
||||
|
||||
this.Find<TextBox>("Hosts").SetText(inValidHosts);
|
||||
Assert.IsFalse(this.Find<Button>("Add").Enabled, "Add button should be Disabled with inValidHosts");
|
||||
|
||||
this.Find<Button>("Cancel").Click();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Error-message in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating error message should be shown if not run as admin.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestErrorMessageWithNonAdminPermission()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(
|
||||
this.FindAll<TextBlock>("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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Filter-panel function in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Address filter matching pattern: contains, endsWith, startsWith, exactly-match.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Hosts filter matching pattern: contains, endsWith, startsWith, exactly-match.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating click Filters Button to open filter-panel, and click Filter Button again to close filter-panel.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFilterControl()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
this.AddEntry("192.168.0." + i, "localhost_" + i, true);
|
||||
}
|
||||
|
||||
// Open-filter-panel
|
||||
this.Find<Button>("Filters").Click();
|
||||
Assert.IsTrue(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be opened afer click Filter Button");
|
||||
|
||||
var addressFilterCases = new KeyValuePair<string, int>[]
|
||||
{
|
||||
// contains text, expected matched more rows
|
||||
new("168.0", 10),
|
||||
|
||||
// ends with text, expected matched 1 row
|
||||
new("168.0.1", 1),
|
||||
|
||||
// starts with text, expected matched more rows
|
||||
new("192.168.", 10),
|
||||
|
||||
// full text, expected matched 1 row
|
||||
new("192.168.0.1", 1),
|
||||
|
||||
// no-matching text, expected matched no row
|
||||
new("127.0.0", 0),
|
||||
|
||||
// empty filter, should display all rows
|
||||
new(string.Empty, 10),
|
||||
};
|
||||
|
||||
foreach (var (addressFilter, expectedCount) in addressFilterCases)
|
||||
{
|
||||
this.Find<TextBox>("Address").SetText(addressFilter);
|
||||
Assert.IsTrue(this.Find("Entries").FindAll<Button>("Delete").Count == expectedCount);
|
||||
}
|
||||
|
||||
var hostFilterCases = new KeyValuePair<string, int>[]
|
||||
{
|
||||
// contains text, expected matched more rows
|
||||
new("host_", 10),
|
||||
|
||||
// ends with text, expected matched 1 row
|
||||
new("host_4", 1),
|
||||
|
||||
// starts with text, expected matched more rows
|
||||
new("localhost", 10),
|
||||
|
||||
// full text, expected matched 1 row
|
||||
new("localhost_5", 1),
|
||||
|
||||
// empty filter, should display all rows
|
||||
new(string.Empty, 10),
|
||||
};
|
||||
|
||||
foreach (var (hostFilterCase, expectedCount) in hostFilterCases)
|
||||
{
|
||||
this.Find<TextBox>("Hosts").SetText(hostFilterCase);
|
||||
Assert.IsTrue(this.Find("Entries").FindAll<Button>("Delete").Count == expectedCount);
|
||||
}
|
||||
|
||||
// Close-filter-panel
|
||||
this.Find<Button>("Filters").Click();
|
||||
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed afer click Filter Button");
|
||||
}
|
||||
|
||||
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)
|
||||
@@ -61,21 +220,21 @@ namespace Hosts.UITests
|
||||
if (clickAddEntryButton)
|
||||
{
|
||||
// Click 'Add an entry' for adding Host override rule
|
||||
this.Find<Button>(By.Name("New entry")).Click();
|
||||
this.Find<Button>("New entry").Click();
|
||||
}
|
||||
|
||||
// Adding a new host override localhost -> 192.168.0.1
|
||||
Assert.IsFalse(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Disabled by default");
|
||||
Assert.IsFalse(this.Find<Button>("Add").Enabled, "Add button should be Disabled by default");
|
||||
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Address")).SetText(ip, false).Text == ip);
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Hosts")).SetText(host, false).Text == host);
|
||||
Assert.IsTrue(this.Find<TextBox>("Address").SetText(ip).Text == ip);
|
||||
Assert.IsTrue(this.Find<TextBox>("Hosts").SetText(host).Text == host);
|
||||
|
||||
this.Find<ToggleSwitch>(By.Name("Active")).Toggle(active);
|
||||
this.Find<ToggleSwitch>("Active").Toggle(active);
|
||||
|
||||
Assert.IsTrue(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Enabled after providing valid inputs");
|
||||
Assert.IsTrue(this.Find<Button>("Add").Enabled, "Add button should be Enabled after providing valid inputs");
|
||||
|
||||
// Add the entry
|
||||
this.Find<Button>(By.Name("Add")).Click();
|
||||
this.Find<Button>("Add").Click();
|
||||
|
||||
// 0.5 second delay after adding an entry
|
||||
Task.Delay(500).Wait();
|
||||
@@ -84,25 +243,25 @@ namespace Hosts.UITests
|
||||
private void CloseWarningDialog()
|
||||
{
|
||||
// Find 'Accept' button which come in 'Warning' dialog
|
||||
if (this.FindAll<Window>(By.Name("Warning")).Count > 0 &&
|
||||
this.FindAll<Button>(By.Name("Accept")).Count > 0)
|
||||
if (this.FindAll("Warning").Count > 0 &&
|
||||
this.FindAll<Button>("Accept").Count > 0)
|
||||
{
|
||||
// Hide Warning dialog if any
|
||||
this.Find<Button>(By.Name("Accept")).Click();
|
||||
this.Find<Button>("Accept").Click();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllEntries()
|
||||
{
|
||||
// Delete all existing host-override rules
|
||||
foreach (var deleteBtn in this.FindAll<Button>(By.Name("Delete")))
|
||||
foreach (var deleteBtn in this.FindAll<Button>("Delete"))
|
||||
{
|
||||
deleteBtn.Click();
|
||||
this.Find<Button>(By.Name("Yes")).Click();
|
||||
this.Find<Button>("Yes").Click();
|
||||
}
|
||||
|
||||
// Should have no row left, and no more delete button
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0);
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
src/modules/Hosts/Hosts.UITests/HostsSettingTests.cs
Normal file
129
src/modules/Hosts/Hosts.UITests/HostsSettingTests.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Test Warning Dialog at startup
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Warning-Dialog will be shown if 'Show a warning at startup' toggle is On.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Warning-Dialog will NOT be shown if 'Show a warning at startup' toggle is Off.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating click 'Quit' button in Warning-Dialog, the Hosts File Editor window would be closed.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating click 'Accept' button in Warning-Dialog, the Hosts File Editor window would NOT be closed.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestWarningDialog()
|
||||
{
|
||||
this.LaunchFromSetting(showWarning: true);
|
||||
|
||||
// Validating Warning-Dialog will be shown if 'Show a warning at startup' toggle is on
|
||||
Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
|
||||
|
||||
// Quit Hosts File Editor
|
||||
this.Find<Button>("Quit").Click();
|
||||
|
||||
// Wait for 500 ms to make sure Hosts File Editor is closed
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Validating click 'Quit' button in Warning-Dialog, the Hosts File Editor window would be closed
|
||||
Assert.IsTrue(this.IsHostsFileEditorClosed(), "Hosts File Editor should be closed after click Quit button in Warning Dialog");
|
||||
|
||||
// Re-attaching to Setting Windows
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
|
||||
this.Find<Button>("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<Button>("Accept").Click();
|
||||
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Validating click 'Accept' button in Warning-Dialog, the Hosts File Editor window would NOT be closed
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed after click Accept button in Warning Dialog");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
|
||||
this.LaunchFromSetting(showWarning: false);
|
||||
|
||||
// Should NOT show warning dialog
|
||||
Assert.IsTrue(this.FindAll("Warning").Count == 0, "Should NOT show warning dialog");
|
||||
|
||||
// Host Editor Window should not be closed
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
|
||||
private bool IsHostsFileEditorClosed()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Session.FindAll<Window>("Hosts File Editor");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Validate if editor window closed by checking exception.Message
|
||||
return ex.Message.Contains("Currently selected window has been closed");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void LaunchFromSetting(bool showWarning = false, bool launchAsAdmin = false)
|
||||
{
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Hosts File Editor").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Advanced").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("Hosts File Editor").Click();
|
||||
|
||||
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
|
||||
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
|
||||
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
|
||||
|
||||
// launch Hosts File Editor
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
this.Session.Attach(PowerToysModule.Hosts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.TestWarningDialog**)
|
||||
- [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.
|
||||
- [x] Try to filter for lines and verify you can find them. (**HostModuleTests.TestFilterControl**)
|
||||
- [ ] 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.TestWarningDialog**)
|
||||
- [ ] 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**)
|
||||
Reference in New Issue
Block a user