Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Hosts] Converting manual release-check-list tests to UI-Test Automation and Adding more UI-Test-cases #37657

Merged
merged 16 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/common/UITestAutomation/Element/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
116 changes: 97 additions & 19 deletions src/common/UITestAutomation/Element/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public class Element

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;
Expand Down Expand Up @@ -91,7 +99,7 @@ public string ControlType
/// 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) =>
{
Expand All @@ -116,7 +124,7 @@ public void Click(bool rightClick = false)
/// <summary>
/// Double Click the UI element.
/// </summary>
public void DoubleClick()
public virtual void DoubleClick()
{
PerformAction((actions, windowElement) =>
{
Expand All @@ -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;
}

Expand All @@ -154,17 +161,51 @@ public T Find<T>(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<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>
Expand All @@ -174,30 +215,67 @@ public T Find<T>(By by, int timeoutMS = 3000)
/// <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}");
var foundElements = FindHelper.FindAll<T, AppiumWebElement>(
() =>
{
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)
{
Expand Down
23 changes: 23 additions & 0 deletions src/common/UITestAutomation/Element/HyperlinkButton.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 59 additions & 0 deletions src/common/UITestAutomation/Element/NavigationViewItem.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions src/common/UITestAutomation/Element/TextBlock.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
13 changes: 12 additions & 1 deletion src/common/UITestAutomation/Element/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@
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>
Expand Down
8 changes: 4 additions & 4 deletions src/common/UITestAutomation/Element/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public Window Maximize(bool byClickButton = true)
{
if (byClickButton)
{
Find<Button>(By.Name("Maximize")).Click();
Find<Button>("Maximize").Click();
}
else
{
Expand All @@ -37,7 +37,7 @@ public Window Restore(bool byClickButton = true)
{
if (byClickButton)
{
Find<Button>(By.Name("Restore")).Click();
Find<Button>("Restore").Click();
}
else
{
Expand All @@ -56,7 +56,7 @@ public Window Minimize(bool byClickButton = true)
{
if (byClickButton)
{
Find<Button>(By.Name("Minimize")).Click();
Find<Button>("Minimize").Click();
}
else
{
Expand All @@ -74,7 +74,7 @@ public void Close(bool byClickButton = true)
{
if (byClickButton)
{
Find<Button>(By.Name("Close")).Click();
Find<Button>("Close").Click();
}
else
{
Expand Down
9 changes: 1 addition & 8 deletions src/common/UITestAutomation/FindHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -32,7 +25,7 @@ public static T Find<T, TW>(Func<TW> findElementFunc, WindowsDriver<WindowsEleme
{
var element = item as WindowsElement;
return NewElement<T>(element, driver, timeoutMS);
}).ToList();
}).Where(item => item.IsMatchingTarget()).ToList();

return new ReadOnlyCollection<T>(res);
}
Expand Down
Loading
Loading