mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02: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:
@@ -15,39 +15,58 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
internal class SessionHelper
|
||||
public class SessionHelper
|
||||
{
|
||||
// Default session path is PowerToys settings dashboard
|
||||
private readonly string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
|
||||
|
||||
private readonly string runnerPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.Runner);
|
||||
|
||||
private string? locationPath;
|
||||
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
private static WindowsDriver<WindowsElement>? root;
|
||||
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
private Process? appDriver;
|
||||
private static Process? appDriver;
|
||||
private Process? runner;
|
||||
|
||||
private PowerToysModule scope;
|
||||
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public SessionHelper(PowerToysModule scope)
|
||||
{
|
||||
this.scope = scope;
|
||||
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
|
||||
this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
var winAppDriverProcessInfo = new ProcessStartInfo
|
||||
CheckWinAppDriverAndRoot();
|
||||
|
||||
var runnerProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
FileName = locationPath + this.runnerPath,
|
||||
Verb = "runas",
|
||||
};
|
||||
|
||||
this.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
if (scope == PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
this.ExitExe(runnerProcessInfo.FileName);
|
||||
this.runner = Process.Start(runnerProcessInfo);
|
||||
}
|
||||
}
|
||||
|
||||
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 WinAppDriver And Root.
|
||||
/// </summary>
|
||||
public void CheckWinAppDriverAndRoot()
|
||||
{
|
||||
if (SessionHelper.root == null || SessionHelper.appDriver?.SessionId == null || SessionHelper.appDriver == null || SessionHelper.appDriver.HasExited)
|
||||
{
|
||||
this.StartWindowsAppDriverApp();
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
SessionHelper.root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +75,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
public SessionHelper Init()
|
||||
{
|
||||
this.StartExe(locationPath + this.sessionPath);
|
||||
this.ExitExe(this.locationPath + this.sessionPath);
|
||||
this.StartExe(this.locationPath + this.sessionPath);
|
||||
|
||||
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
|
||||
|
||||
@@ -71,8 +91,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
ExitScopeExe();
|
||||
try
|
||||
{
|
||||
appDriver?.Kill();
|
||||
appDriver?.WaitForExit(); // Optional: Wait for the process to exit
|
||||
if (this.scope == PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
runner?.Kill();
|
||||
runner?.WaitForExit(); // Optional: Wait for the process to exit
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -81,31 +104,15 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
Console.WriteLine($"appPath: {appPath}");
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a exe.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the application executable.</param>
|
||||
public void ExitExe(string path)
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void ExitExe(string appPath)
|
||||
{
|
||||
// Exit Exe
|
||||
string exeName = Path.GetFileNameWithoutExtension(path);
|
||||
string exeName = Path.GetFileNameWithoutExtension(appPath);
|
||||
|
||||
// PowerToys.FancyZonesEditor
|
||||
Process[] processes = Process.GetProcessesByName(exeName);
|
||||
foreach (Process process in processes)
|
||||
{
|
||||
@@ -121,6 +128,48 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 = NewWindowsDriver(opts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="info">The path to the application executable.</param>
|
||||
private WindowsDriver<WindowsElement> NewWindowsDriver(AppiumOptions info)
|
||||
{
|
||||
// Create driver with retry
|
||||
var timeout = TimeSpan.FromMinutes(2);
|
||||
var retryInterval = TimeSpan.FromSeconds(5);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), info);
|
||||
return res;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (DateTime.Now - startTime > timeout)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Task.Delay(retryInterval).Wait();
|
||||
CheckWinAppDriverAndRoot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit now exe.
|
||||
/// </summary>
|
||||
@@ -134,16 +183,31 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
ExitExe(sessionPath);
|
||||
ExitScopeExe();
|
||||
StartExe(locationPath + sessionPath);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
public WindowsDriver<WindowsElement> GetRoot()
|
||||
{
|
||||
return SessionHelper.root!;
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetDriver()
|
||||
{
|
||||
Assert.IsNotNull(this.Driver, $"Failed to get driver. Driver is null.");
|
||||
return this.Driver;
|
||||
}
|
||||
|
||||
private void StartWindowsAppDriverApp()
|
||||
{
|
||||
var winAppDriverProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
Verb = "runas",
|
||||
};
|
||||
|
||||
this.ExitExe(winAppDriverProcessInfo.FileName);
|
||||
SessionHelper.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user