Skip to content

Commit

Permalink
add DeleteLayouTest and change cleanup to base class and change FindB…
Browse files Browse the repository at this point in the history
…yAccessibilityId to By.AccessibilityId
  • Loading branch information
Zhaopeng Wang committed Mar 5, 2025
1 parent ddd3042 commit 7a511ac
Show file tree
Hide file tree
Showing 12 changed files with 533 additions and 187 deletions.
36 changes: 33 additions & 3 deletions src/common/UITestAutomation/Element/By.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,40 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
public class By
{
private readonly OpenQA.Selenium.By by;
private readonly OpenQA.Selenium.By? by;
private readonly bool isAccessibilityId;
private readonly string? accessibilityId;

private By(OpenQA.Selenium.By by)
{
isAccessibilityId = false;
this.by = by;
}

private By(string accessibilityId)
{
isAccessibilityId = true;
this.accessibilityId = accessibilityId;
}

public override string ToString()
{
// override ToString to return detailed debugging content provided by OpenQA.Selenium.By
return this.by.ToString();
return this.GetAccessibilityId();
}

public bool GetIsAccessibilityId() => this.isAccessibilityId;

public string GetAccessibilityId()
{
if (this.isAccessibilityId)
{
return this.accessibilityId!;
}
else
{
return this.by!.ToString();
}
}

/// <summary>
Expand All @@ -45,6 +68,13 @@ public override string ToString()
/// <returns>A By object.</returns>
public static By Id(string id) => new By(OpenQA.Selenium.By.Id(id));

/// <summary>
/// Creates a By object using the ID attribute.
/// </summary>
/// <param name="accessibilityId">The ID attribute to search for.</param>
/// <returns>A By object.</returns>
public static By AccessibilityId(string accessibilityId) => new By(accessibilityId);

/// <summary>
/// Creates a By object using the XPath expression.
/// </summary>
Expand Down Expand Up @@ -77,6 +107,6 @@ public override string ToString()
/// Converts the By object to an OpenQA.Selenium.By object.
/// </summary>
/// <returns>An OpenQA.Selenium.By object.</returns>
internal OpenQA.Selenium.By ToSeleniumBy() => by;
internal OpenQA.Selenium.By ToSeleniumBy() => by!;
}
}
61 changes: 15 additions & 46 deletions src/common/UITestAutomation/Element/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using ABI.Windows.Foundation;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
Expand Down Expand Up @@ -192,7 +193,10 @@ public void Drag(Element element)
/// <param name="key">The Key to Send.</param>
public void SendKeys(string key)
{
windowsElement?.SendKeys(key);
PerformAction((actions, windowElement) =>
{
windowElement.SendKeys(key);
});
}

/// <summary>
Expand Down Expand Up @@ -241,26 +245,6 @@ public T Find<T>(string name, int timeoutMS = 3000)
return this.Find<T>(By.Name(name), timeoutMS);
}

/// <summary>
/// Shortcut for this.FindAllByAccessibilityId<T>(accessibilityId, timeoutMS)
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="accessibilityId">The accessibilityId of the element.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
public T FindByAccessibilityId<T>(string accessibilityId, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: accessibilityId = {accessibilityId}, timeoutMS = {timeoutMS}");

// leverage findAll to filter out mismatched elements
var collection = this.FindAllByAccessibilityId<T>(accessibilityId, timeoutMS);

Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {accessibilityId}");

return collection[0];
}

/// <summary>
/// Finds an element by the selector.
/// Shortcut for this.Find<Element>(by, timeoutMS)
Expand Down Expand Up @@ -299,31 +283,16 @@ public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
var foundElements = FindHelper.FindAll<T, AppiumWebElement>(
() =>
{
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
return elements;
},
this.driver,
timeoutMS);

return foundElements ?? new ReadOnlyCollection<T>([]);
}

/// <summary>
/// Finds all elements by accessibilityId.
/// </summary>
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
/// <param name="accessibilityId">The accessibilityId 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> FindAllByAccessibilityId<T>(string accessibilityId, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: accessibilityId = {accessibilityId}, timeoutMS = {timeoutMS}");
var foundElements = FindHelper.FindAll<T, AppiumWebElement>(
() =>
{
var elements = this.windowsElement.FindElementsByAccessibilityId(accessibilityId);
return elements;
if (by.GetIsAccessibilityId())
{
var elements = this.windowsElement.FindElementsByAccessibilityId(by.GetAccessibilityId());
return elements;
}
else
{
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
return elements;
}
},
this.driver,
timeoutMS);
Expand Down
112 changes: 67 additions & 45 deletions src/common/UITestAutomation/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Interactions;

namespace Microsoft.PowerToys.UITest
{
Expand Down Expand Up @@ -62,26 +63,6 @@ public T Find<T>(string name, int timeoutMS = 3000)
return this.Find<T>(By.Name(name), timeoutMS);
}

/// <summary>
/// Shortcut for this.FindAllByAccessibilityId<T>(accessibilityId, timeoutMS)
/// </summary>
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
/// <param name="accessibilityId">The accessibilityId of the element.</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
public T FindByAccessibilityId<T>(string accessibilityId, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: accessibilityId = {accessibilityId}, timeoutMS = {timeoutMS}");

// leverage findAll to filter out mismatched elements
var collection = this.FindAllByAccessibilityId<T>(accessibilityId, timeoutMS);

Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {accessibilityId}");

return collection[0];
}

/// <summary>
/// Shortcut for this.Find<Element>(by, timeoutMS)
/// </summary>
Expand Down Expand Up @@ -118,31 +99,16 @@ public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
var foundElements = FindHelper.FindAll<T, WindowsElement>(
() =>
{
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
return elements;
},
this.WindowsDriver,
timeoutMS);

return foundElements ?? new ReadOnlyCollection<T>([]);
}

/// <summary>
/// Finds all elements by accessibilityId.
/// </summary>
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
/// <param name="accessibilityId">The accessibilityId 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> FindAllByAccessibilityId<T>(string accessibilityId, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: accessibilityId = {accessibilityId}, timeoutMS = {timeoutMS}");
var foundElements = FindHelper.FindAll<T, WindowsElement>(
() =>
{
var elements = this.WindowsDriver.FindElementsByAccessibilityId(accessibilityId);
return elements;
if (by.GetIsAccessibilityId())
{
var elements = this.WindowsDriver.FindElementsByAccessibilityId(by.GetAccessibilityId());
return elements;
}
else
{
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
return elements;
}
},
this.WindowsDriver,
timeoutMS);
Expand Down Expand Up @@ -188,6 +154,39 @@ public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
return this.FindAll<Element>(By.Name(name), timeoutMS);
}

/// <summary>
/// Keyboard Action key.
/// </summary>
/// <param name="key1">The Keys1 to click.</param>
/// <param name="key2">The Keys2 to click.</param>
/// <param name="key3">The Keys3 to click.</param>
/// <param name="key4">The Keys4 to click.</param>
public void KeyboardAction(string key1, string key2 = "", string key3 = "", string key4 = "")
{
PerformAction((actions, windowElement) =>
{
if (string.IsNullOrEmpty(key2))
{
actions.SendKeys(key1);
}
else if (string.IsNullOrEmpty(key3))
{
actions.SendKeys(key1).SendKeys(key2);
}
else if (string.IsNullOrEmpty(key4))
{
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3);
}
else
{
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4);
}

actions.Release();
actions.Build().Perform();
});
}

/// <summary>
/// Attaches to an existing PowerToys module.
/// </summary>
Expand Down Expand Up @@ -232,5 +231,28 @@ public Session Attach(string windowName)

return this;
}

/// <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 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, WindowsDriver<WindowsElement>> action, int msPreAction = 500, int msPostAction = 500)
{
if (msPreAction > 0)
{
Task.Delay(msPreAction).Wait();
}

var windowsDriver = this.WindowsDriver;
Actions actions = new Actions(this.WindowsDriver);
action(actions, windowsDriver);

if (msPostAction > 0)
{
Task.Delay(msPostAction).Wait();
}
}
}
}
3 changes: 2 additions & 1 deletion src/common/UITestAutomation/UITestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ public void TestInit()
/// <summary>
/// UnInitializes the test.
/// </summary>
[TestCleanup]
public void TestClean()
{
this.sessionHelper.Cleanup();
// this.sessionHelper.Cleanup();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,21 @@ public DefaultLayoutsTest()
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(parameters));
}

[TestCleanup]
public void TestCleanup()
{
this.TestClean();
}

[TestMethod]
public void ClickMonitor()
{
Assert.IsNotNull(Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 1"));
Assert.IsNotNull(Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 2"));
Assert.IsNotNull(Session.Find<Element>("Monitor 1"));
Assert.IsNotNull(Session.Find<Element>("Monitor 2"));

// verify that the monitor 1 is selected initially
Assert.IsTrue(Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 1").Selected);
Assert.IsFalse(Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 2").Selected);
Assert.IsTrue(Session.Find<Element>("Monitor 1").Selected);
Assert.IsFalse(Session.Find<Element>("Monitor 2").Selected);

Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 2").Click();
Session.Find<Element>("Monitor 2").Click();

// verify that the monitor 2 is selected after click
Assert.IsFalse(Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 1").Selected);
Assert.IsTrue(Session.FindByAccessibilityId<Element>("Monitors").Find<Element>("Monitor 2").Selected);
Assert.IsFalse(Session.Find<Element>("Monitor 1").Selected);
Assert.IsTrue(Session.Find<Element>("Monitor 2").Selected);
}
}
}
Loading

1 comment on commit 7a511ac

@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 (2)

Initializet
Tes

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/zhaopengwang/test/37733-ui-test-fancyzones-editor 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/13674000549/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.