// 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. using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; using static Microsoft.PowerToys.UITest.WindowHelper; namespace Microsoft.PowerToys.UITest { /// /// Nested class for test initialization. /// 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 static WindowsDriver? root; private WindowsDriver? Driver { get; set; } 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 = "")] public SessionHelper(PowerToysModule scope) { this.scope = scope; this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope); this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); CheckWinAppDriverAndRoot(); } /// /// Initializes WinAppDriver And Root. /// 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(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities); } } /// /// Initializes the test environment. /// /// The PowerToys module to start. public SessionHelper Init() { 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."); return this; } /// /// Cleans up the test environment. /// public void Cleanup() { ExitScopeExe(); } /// /// Exit a exe. /// /// The path to the application executable. public void ExitExe(string appPath) { // Exit Exe string exeName = Path.GetFileNameWithoutExtension(appPath); Process[] processes = Process.GetProcessesByName(exeName); foreach (Process process in processes) { try { process.Kill(); process.WaitForExit(); // Optional: Wait for the process to exit } catch (Exception ex) { Assert.Fail($"Failed to terminate process {process.ProcessName} (ID: {process.Id}): {ex.Message}"); } } } /// /// Starts a new exe and takes control of it. /// /// The path to the application executable. public void StartExe(string appPath) { var opts = new AppiumOptions(); // if we want to start settings, we need to use the runner exe to open settings if (scope == PowerToysModule.PowerToysSettings) { TryLaunchPowerToysSettings(opts); } else { opts.AddAdditionalCapability("app", appPath); } this.Driver = NewWindowsDriver(opts); } private void TryLaunchPowerToysSettings(AppiumOptions opts) { CheckWinAppDriverAndRoot(); var runnerProcessInfo = new ProcessStartInfo { FileName = locationPath + this.runnerPath, Verb = "runas", Arguments = "--open-settings", }; this.ExitExe(runnerProcessInfo.FileName); this.runner = Process.Start(runnerProcessInfo); Thread.Sleep(5000); if (root != null) { const int maxRetries = 3; const int delayMs = 5000; var windowName = "PowerToys Settings"; for (int attempt = 1; attempt <= maxRetries; attempt++) { var settingsWindow = ApiHelper.FindDesktopWindowHandler( new[] { windowName, AdministratorPrefix + windowName }); if (settingsWindow.Count > 0) { var hexHwnd = settingsWindow[0].HWnd.ToString("x"); opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd); return; } if (attempt < maxRetries) { Thread.Sleep(delayMs); } else { throw new TimeoutException("Failed to find PowerToys Settings window after multiple attempts."); } } } } /// /// Starts a new exe and takes control of it. /// /// The path to the application executable. private WindowsDriver 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(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), info); return res; } catch (Exception) { if (DateTime.Now - startTime > timeout) { throw; } Task.Delay(retryInterval).Wait(); CheckWinAppDriverAndRoot(); } } } /// /// Exit now exe. /// public void ExitScopeExe() { ExitExe(sessionPath); try { if (this.scope == PowerToysModule.PowerToysSettings) { runner?.Kill(); runner?.WaitForExit(); // Optional: Wait for the process to exit } } catch (Exception ex) { // Handle exceptions if needed Debug.WriteLine($"Exception during Cleanup: {ex.Message}"); } } /// /// Restarts now exe and takes control of it. /// public void RestartScopeExe() { ExitScopeExe(); StartExe(locationPath + sessionPath); } public WindowsDriver GetRoot() { return SessionHelper.root!; } public WindowsDriver 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); } } }