Skip to content

Commit

Permalink
Start to convert manual test-case to automation
Browse files Browse the repository at this point in the history
  • Loading branch information
jamrobot committed Feb 26, 2025
1 parent aeeefa3 commit fd64f5d
Show file tree
Hide file tree
Showing 14 changed files with 568 additions and 71 deletions.
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;
}
}
}
113 changes: 96 additions & 17 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 @@ -187,16 +228,54 @@ public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
this.driver,
timeoutMS);

return foundElements ?? new ReadOnlyCollection<T>(new List<T>());
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 reepresents 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 pepresents 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 rpresents 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

1 comment on commit fd64f5d

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@check-spelling-bot Report

🔴 Please review

See the 📜action log or 📝 job summary for details.

Unrecognized words (3)

pepresents
reepresents
rpresents

These words are not needed and should be removed ahk AMPROPERTY AMPROPSETID Breadcrumb CDEF comdef ddf devenum DEVMON DEVSOURCE DGR DIIRFLAG dshow DVH DVHD DVSD DVSL EData ERole fdw FILEINFOSIG Filtergraph Filterx HCERTSTORE IKs iljxck IYUV KSPROPERTY lcb ldx lld LONGLONG LTRB majortype makecab MEDIASUBTYPE mediatype mfplat mic mjpg Msimg msiquery ORAW outpin overlaywindow PAUDIO PINDIR Pnp ppmt previouscamera PROPBAG propvarutil reencoded REFGUID REGFILTER REGFILTERPINS REGPINTYPES regsvr shmem sizeread stl strsafe strutil subquery SYNCMFT TMPVAR vcdl vdi vid VIDCAP VIDEOINFOHEADER vih webcam wistd WVC

To accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the [email protected]:microsoft/PowerToys.git repository
on the dev/nxu/AddHostSaveErrorUITest branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/v0.0.24/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/13543041244/attempts/1'
If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Please sign in to comment.