mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
UI Test Automation (#39777)
### Summary This pull request includes the following updates: 1. Improvements and stabilization of the UI automation framework 2. Setup of the UI automation pipeline 3. Add UI test cases for FancyZones 4. Add UI test cases for MouseUtils 5. Improvements of Hosts Editor UI tests --- ### Related Links - **Current Release checklist coverage**: https://github.com/microsoft/PowerToys/blob/feature/UITestAutomation/src/common/UITestAutomation/Doc/ui-automation-cover-list.md - **UI Automation pipeline**: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary --------- Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com> Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com> Co-authored-by: Jerry Xu <n.xu@outlook.com> Co-authored-by: Zhaopeng Wang <zhaopengwang@microsoft.com> Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com> Co-authored-by: Mengyuan <162882040+chenmy77@users.noreply.github.com> Co-authored-by: yaqingmi <miyaqing01@gmail.com> Co-authored-by: Clint Rutkas <clint@rutkas.com> Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com> Co-authored-by: Kai Tao <69313318+vanzue@users.noreply.github.com> Co-authored-by: zhaopeng wang <33367956+wang563681252@users.noreply.github.com> Co-authored-by: Laszlo Nemeth <57342539+donlaci@users.noreply.github.com> Co-authored-by: RokyZevon <12629919+RokyZevon@users.noreply.github.com> Co-authored-by: Yu Leng <42196638+moooyo@users.noreply.github.com> Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Davide Giacometti <25966642+davidegiacometti@users.noreply.github.com> Co-authored-by: Gordon Lam <73506701+yeelam-gordon@users.noreply.github.com> Co-authored-by: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Co-authored-by: Muhammad Danish <mdanishkhdev@gmail.com> Co-authored-by: Bennett Blodinger <benwa@users.noreply.github.com> Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> Co-authored-by: Ionuț Manța <ionut@janeasystems.com> Co-authored-by: Hao Liu <liuhaobupt@163.com> Co-authored-by: OlegHarchevkin <40352094+OlegKharchevkin@users.noreply.github.com> Co-authored-by: dcog989 <89043002+dcog989@users.noreply.github.com> Co-authored-by: PesBandi <127593627+PesBandi@users.noreply.github.com> Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Co-authored-by: vanzue <vanzue@outlook.com> Co-authored-by: Typpi <20943337+Nick2bad4u@users.noreply.github.com> Co-authored-by: Mike Griese <migrie@microsoft.com> Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com> Co-authored-by: Abhyudit <64366765+bitmap4@users.noreply.github.com> Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> Co-authored-by: Ved Nig <vednig12@outlook.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Aung Khaing Khant <aungkhaingkhant.dev@gmail.com> Co-authored-by: Aung Khaing Khant <aungkhaingkhant@advent-soft.com> Co-authored-by: Dustin L. Howett <duhowett@microsoft.com> Co-authored-by: leileizhang <leilzh@microsoft.com> Co-authored-by: Dustin L. Howett <dustin@howett.net> Co-authored-by: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Co-authored-by: Shawn Yuan <shuai.yuan.zju@gmail.com> Co-authored-by: cryolithic <cryolithic@gmail.com> Co-authored-by: Lemonyte <49930425+lemonyte@users.noreply.github.com> Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com> Co-authored-by: Corey Hayward <72159232+CoreyHayward@users.noreply.github.com> Co-authored-by: Jerry Xu <nxu@microsoft.com> Co-authored-by: Shawn Yuan <shuaiyuan@microsoft.com> Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com> Co-authored-by: Jeremy Sinclair <4016293+snickler@users.noreply.github.com>
This commit is contained in:
@@ -6,10 +6,16 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using Windows.Devices.Display.Core;
|
||||
using Windows.Foundation.Metadata;
|
||||
using static Microsoft.PowerToys.UITest.UITestBase.NativeMethods;
|
||||
using static Microsoft.PowerToys.UITest.WindowHelper;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -17,19 +23,37 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Base class that should be inherited by all Test Classes.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class UITestBase
|
||||
public class UITestBase : IDisposable
|
||||
{
|
||||
public Session Session { get; set; }
|
||||
public required TestContext TestContext { get; set; }
|
||||
|
||||
private readonly SessionHelper sessionHelper;
|
||||
public required Session Session { get; set; }
|
||||
|
||||
public bool IsInPipeline { get; }
|
||||
|
||||
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
|
||||
|
||||
private readonly PowerToysModule scope;
|
||||
private readonly WindowSize size;
|
||||
private SessionHelper? sessionHelper;
|
||||
private System.Threading.Timer? screenshotTimer;
|
||||
private string? screenshotDirectory;
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
|
||||
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
|
||||
if (IsInPipeline)
|
||||
{
|
||||
NativeMethods.ChangeDisplayResolution(1920, 1080);
|
||||
NativeMethods.GetMonitorInfo();
|
||||
|
||||
// Escape Popups before starting
|
||||
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
||||
}
|
||||
|
||||
this.scope = scope;
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,6 +62,22 @@ namespace Microsoft.PowerToys.UITest
|
||||
[TestInitialize]
|
||||
public void TestInit()
|
||||
{
|
||||
CloseOtherApplications();
|
||||
if (IsInPipeline)
|
||||
{
|
||||
screenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(screenshotDirectory);
|
||||
|
||||
// Take screenshot every 1 second
|
||||
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, screenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
|
||||
|
||||
// Escape Popups before starting
|
||||
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
||||
}
|
||||
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), scope, size);
|
||||
|
||||
if (this.scope == PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
// close Debug warning dialog if any
|
||||
@@ -50,12 +90,32 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UnInitializes the test.
|
||||
/// Cleanups the test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void TestClean()
|
||||
public void TestCleanup()
|
||||
{
|
||||
this.sessionHelper.Cleanup();
|
||||
if (IsInPipeline)
|
||||
{
|
||||
screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
Dispose();
|
||||
if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed
|
||||
or UnitTestOutcome.Error
|
||||
or UnitTestOutcome.Unknown)
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
AddScreenShotsToTestResultsDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
this.Session.Cleanup();
|
||||
this.sessionHelper!.Cleanup();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
screenshotTimer?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,47 +124,143 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(By by, int timeoutMS = 3000)
|
||||
protected T Find<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
return this.Session.Find<T>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// Shortcut for this.Session.Find<Element>(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>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(string name, int timeoutMS = 3000)
|
||||
protected T Find<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS);
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(By by, int timeoutMS = 3000)
|
||||
protected Element Find(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.Find(by, timeoutMS);
|
||||
return this.Session.Find(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(string name, int timeoutMS = 3000)
|
||||
protected Element Find(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.Find(name, timeoutMS);
|
||||
return this.Session.Find(name, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has only one Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(by, timeoutMS, global).Count == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.HasOne<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.HasOne<Element>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.HasOne<T>(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 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.HasOne<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.HasOne<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.HasOne<Element>(name, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS, global).Count >= 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.Has<Element>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<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 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Has<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.Has<Element>(name, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,12 +269,12 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS);
|
||||
return this.Session.FindAll<T>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -127,12 +283,12 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS);
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,11 +296,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Session.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>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.FindAll<Element>(by, timeoutMS);
|
||||
return this.Session.FindAll<Element>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,11 +308,137 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls the page
|
||||
/// </summary>
|
||||
/// <param name="scrollCount">The number of scroll attempts.</param>
|
||||
/// <param name="direction">The direction to scroll.</param>
|
||||
/// <param name="msPreAction">Pre-action delay in milliseconds.</param>
|
||||
/// <param name="msPostAction">Post-action delay in milliseconds.</param>
|
||||
public void Scroll(int scrollCount = 5, string direction = "Up", int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
MouseActionType mouseAction = direction == "Up" ? MouseActionType.ScrollUp : MouseActionType.ScrollDown;
|
||||
for (int i = 0; i < scrollCount; i++)
|
||||
{
|
||||
Session.PerformMouseAction(mouseAction, msPreAction, msPostAction); // Ensure settings are visible
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures the last screenshot when the test fails.
|
||||
/// </summary>
|
||||
protected void CaptureLastScreenshot()
|
||||
{
|
||||
// Implement your screenshot capture logic here
|
||||
// For example, save a screenshot to a file and return the file path
|
||||
string screenshotPath = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "last_screenshot.png");
|
||||
|
||||
this.Session.Root.GetScreenshot().SaveAsFile(screenshotPath, ScreenshotImageFormat.Png);
|
||||
|
||||
// Save screenshot to screenshotPath & upload to test attachment
|
||||
this.TestContext.AddResultFile(screenshotPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the color of the pixel at the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate on the screen.</param>
|
||||
/// <param name="y">The Y coordinate on the screen.</param>
|
||||
/// <returns>The color of the pixel at the specified coordinates.</returns>
|
||||
public Color GetPixelColor(int x, int y)
|
||||
{
|
||||
return WindowHelper.GetPixelColor(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the color of the pixel at the specified screen coordinates as a string.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate on the screen.</param>
|
||||
/// <param name="y">The Y coordinate on the screen.</param>
|
||||
/// <returns>The color of the pixel at the specified coordinates.</returns>
|
||||
public string GetPixelColorString(int x, int y)
|
||||
{
|
||||
return WindowHelper.GetPixelColorString(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the display.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A tuple containing the width and height of the display.
|
||||
/// </returns
|
||||
public Tuple<int, int> GetDisplaySize()
|
||||
{
|
||||
return WindowHelper.GetDisplaySize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a combination of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys to send.</param>
|
||||
public void SendKeys(params Key[] keys)
|
||||
{
|
||||
this.Session.SendKeys(keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a sequence of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">An array of keys to send.</param>
|
||||
public void SendKeySequence(params Key[] keys)
|
||||
{
|
||||
this.Session.SendKeySequence(keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the mouse cursor as a tuple.
|
||||
/// </summary>
|
||||
/// <returns>A tuple containing the X and Y coordinates of the cursor.</returns>
|
||||
public Tuple<int, int> GetMousePosition()
|
||||
{
|
||||
return this.Session.GetMousePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen center coordinates.
|
||||
/// </summary>
|
||||
/// <returns>(x, y)</returns>
|
||||
public (int CenterX, int CenterY) GetScreenCenter()
|
||||
{
|
||||
return WindowHelper.GetScreenCenter();
|
||||
}
|
||||
|
||||
public bool IsWindowOpen(string windowName)
|
||||
{
|
||||
return WindowHelper.IsWindowOpen(windowName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the mouse cursor to the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The new x-coordinate of the cursor.</param>
|
||||
/// <param name="y">The new y-coordinate of the cursor.</param
|
||||
public void MoveMouseTo(int x, int y)
|
||||
{
|
||||
this.Session.MoveMouseTo(x, y);
|
||||
}
|
||||
|
||||
protected void AddScreenShotsToTestResultsDirectory()
|
||||
{
|
||||
if (screenshotDirectory != null)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(screenshotDirectory))
|
||||
{
|
||||
this.TestContext.AddResultFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -164,8 +446,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
this.sessionHelper.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.sessionHelper!.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,8 +456,194 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void ExitScopeExe()
|
||||
{
|
||||
this.sessionHelper.ExitScopeExe();
|
||||
this.sessionHelper!.ExitScopeExe();
|
||||
return;
|
||||
}
|
||||
|
||||
private void CloseOtherApplications()
|
||||
{
|
||||
// Close other applications
|
||||
var processNamesToClose = new List<string>
|
||||
{
|
||||
"PowerToys",
|
||||
"PowerToys.Settings",
|
||||
"PowerToys.FancyZonesEditor",
|
||||
};
|
||||
foreach (var processName in processNamesToClose)
|
||||
{
|
||||
foreach (var process in Process.GetProcessesByName(processName))
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NativeMethods
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct DISPLAY_DEVICE
|
||||
{
|
||||
public int cb;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string DeviceName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string DeviceString;
|
||||
public int StateFlags;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string DeviceID;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string DeviceKey;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int EnumDisplaySettings(IntPtr deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
|
||||
private static extern bool EnumDisplayDevices(IntPtr lpDevice, int iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, int dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int ChangeDisplaySettings(ref DEVMODE devMode, int flags);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
|
||||
private static extern int ChangeDisplaySettingsEx(IntPtr lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, uint dwflags, IntPtr lParam);
|
||||
|
||||
private const int DM_PELSWIDTH = 0x80000;
|
||||
private const int DM_PELSHEIGHT = 0x100000;
|
||||
|
||||
public const int ENUM_CURRENT_SETTINGS = -1;
|
||||
public const int CDS_TEST = 0x00000002;
|
||||
public const int CDS_UPDATEREGISTRY = 0x01;
|
||||
public const int DISP_CHANGE_SUCCESSFUL = 0;
|
||||
public const int DISP_CHANGE_RESTART = 1;
|
||||
public const int DISP_CHANGE_FAILED = -1;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DEVMODE
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string DmDeviceName;
|
||||
public short DmSpecVersion;
|
||||
public short DmDriverVersion;
|
||||
public short DmSize;
|
||||
public short DmDriverExtra;
|
||||
public int DmFields;
|
||||
public int DmPositionX;
|
||||
public int DmPositionY;
|
||||
public int DmDisplayOrientation;
|
||||
public int DmDisplayFixedOutput;
|
||||
public short DmColor;
|
||||
public short DmDuplex;
|
||||
public short DmYResolution;
|
||||
public short DmTTOption;
|
||||
public short DmCollate;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string DmFormName;
|
||||
public short DmLogPixels;
|
||||
public int DmBitsPerPel;
|
||||
public int DmPelsWidth;
|
||||
public int DmPelsHeight;
|
||||
public int DmDisplayFlags;
|
||||
public int DmDisplayFrequency;
|
||||
public int DmICMMethod;
|
||||
public int DmICMIntent;
|
||||
public int DmMediaType;
|
||||
public int DmDitherType;
|
||||
public int DmReserved1;
|
||||
public int DmReserved2;
|
||||
public int DmPanningWidth;
|
||||
public int DmPanningHeight;
|
||||
}
|
||||
|
||||
public static void GetMonitorInfo()
|
||||
{
|
||||
int deviceIndex = 0;
|
||||
DISPLAY_DEVICE d = default(DISPLAY_DEVICE);
|
||||
d.cb = Marshal.SizeOf(d);
|
||||
|
||||
Console.WriteLine("monitor list :");
|
||||
while (EnumDisplayDevices(IntPtr.Zero, deviceIndex, ref d, 0))
|
||||
{
|
||||
Console.WriteLine($"monitor {deviceIndex + 1}:");
|
||||
Console.WriteLine($" name: {d.DeviceName}");
|
||||
Console.WriteLine($" string: {d.DeviceString}");
|
||||
Console.WriteLine($" ID: {d.DeviceID}");
|
||||
Console.WriteLine($" key: {d.DeviceKey}");
|
||||
Console.WriteLine();
|
||||
|
||||
DEVMODE dm = default(DEVMODE);
|
||||
dm.DmSize = (short)Marshal.SizeOf<DEVMODE>();
|
||||
int modeNum = 0;
|
||||
while (EnumDisplaySettings(d.DeviceName, modeNum, ref dm) > 0)
|
||||
{
|
||||
MonitorInfoData.Monitors.Add(new MonitorInfoData.MonitorInfoDataWrapper()
|
||||
{
|
||||
DeviceName = d.DeviceName,
|
||||
DeviceString = d.DeviceString,
|
||||
DeviceID = d.DeviceID,
|
||||
DeviceKey = d.DeviceKey,
|
||||
PelsWidth = dm.DmPelsWidth,
|
||||
PelsHeight = dm.DmPelsHeight,
|
||||
DisplayFrequency = dm.DmDisplayFrequency,
|
||||
});
|
||||
Console.WriteLine($" mode {modeNum}: {dm.DmPelsWidth}x{dm.DmPelsHeight} @ {dm.DmDisplayFrequency}Hz");
|
||||
modeNum++;
|
||||
}
|
||||
|
||||
deviceIndex++;
|
||||
d.cb = Marshal.SizeOf(d); // Reset the size for the next device
|
||||
}
|
||||
}
|
||||
|
||||
public static void ChangeDisplayResolution(int PelsWidth, int PelsHeight)
|
||||
{
|
||||
Screen screen = Screen.PrimaryScreen!;
|
||||
if (screen.Bounds.Width == PelsWidth && screen.Bounds.Height == PelsHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DEVMODE devMode = default(DEVMODE);
|
||||
devMode.DmDeviceName = new string(new char[32]);
|
||||
devMode.DmFormName = new string(new char[32]);
|
||||
devMode.DmSize = (short)Marshal.SizeOf<DEVMODE>();
|
||||
|
||||
int modeNum = 0;
|
||||
while (EnumDisplaySettings(IntPtr.Zero, modeNum, ref devMode) > 0)
|
||||
{
|
||||
Console.WriteLine($"Mode {modeNum}: {devMode.DmPelsWidth}x{devMode.DmPelsHeight} @ {devMode.DmDisplayFrequency}Hz");
|
||||
modeNum++;
|
||||
}
|
||||
|
||||
devMode.DmPelsWidth = PelsWidth;
|
||||
devMode.DmPelsHeight = PelsHeight;
|
||||
|
||||
int result = NativeMethods.ChangeDisplaySettings(ref devMode, NativeMethods.CDS_TEST);
|
||||
|
||||
if (result == DISP_CHANGE_SUCCESSFUL)
|
||||
{
|
||||
result = ChangeDisplaySettings(ref devMode, CDS_UPDATEREGISTRY);
|
||||
if (result == DISP_CHANGE_SUCCESSFUL)
|
||||
{
|
||||
Console.WriteLine($"Changing display resolution to {devMode.DmPelsWidth}x{devMode.DmPelsHeight}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Failed to change display resolution. Error code: {result}");
|
||||
}
|
||||
}
|
||||
else if (result == DISP_CHANGE_RESTART)
|
||||
{
|
||||
Console.WriteLine($"Changing display resolution to {devMode.DmPelsWidth}x{devMode.DmPelsHeight} requires a restart");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Failed to change display resolution. Error code: {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user