2025-02-28 03:08:08 -08:00
// 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.
2025-03-14 17:04:23 +08:00
using System ;
2025-02-28 03:08:08 -08:00
using System.Diagnostics ;
using System.Diagnostics.CodeAnalysis ;
using System.Reflection ;
using Microsoft.VisualStudio.TestTools.UnitTesting ;
using OpenQA.Selenium.Appium ;
using OpenQA.Selenium.Appium.Windows ;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
/// Nested class for test initialization.
/// </summary>
2025-06-17 17:56:48 +08:00
public class SessionHelper
2025-02-28 03:08:08 -08:00
{
// Default session path is PowerToys settings dashboard
private readonly string sessionPath = ModuleConfigData . Instance . GetModulePath ( PowerToysModule . PowerToysSettings ) ;
2025-06-17 17:56:48 +08:00
private readonly string runnerPath = ModuleConfigData . Instance . GetModulePath ( PowerToysModule . Runner ) ;
2025-03-14 17:04:23 +08:00
private string? locationPath ;
2025-06-17 17:56:48 +08:00
private static WindowsDriver < WindowsElement > ? root ;
2025-02-28 03:08:08 -08:00
private WindowsDriver < WindowsElement > ? Driver { get ; set ; }
2025-06-17 17:56:48 +08:00
private static Process ? appDriver ;
private Process ? runner ;
private PowerToysModule scope ;
2025-02-28 03:08:08 -08:00
2025-03-14 17:04:23 +08:00
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
2025-02-28 03:08:08 -08:00
public SessionHelper ( PowerToysModule scope )
{
2025-06-17 17:56:48 +08:00
this . scope = scope ;
2025-02-28 03:08:08 -08:00
this . sessionPath = ModuleConfigData . Instance . GetModulePath ( scope ) ;
2025-03-14 17:04:23 +08:00
this . locationPath = Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ;
2025-02-28 03:08:08 -08:00
2025-06-17 17:56:48 +08:00
CheckWinAppDriverAndRoot ( ) ;
var runnerProcessInfo = new ProcessStartInfo
2025-02-28 03:08:08 -08:00
{
2025-06-17 17:56:48 +08:00
FileName = locationPath + this . runnerPath ,
2025-02-28 03:08:08 -08:00
Verb = "runas" ,
} ;
2025-06-17 17:56:48 +08:00
if ( scope = = PowerToysModule . PowerToysSettings )
{
this . ExitExe ( runnerProcessInfo . FileName ) ;
this . runner = Process . Start ( runnerProcessInfo ) ;
}
}
2025-02-28 03:08:08 -08:00
2025-06-17 17:56:48 +08:00
/// <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 ) ;
}
2025-02-28 03:08:08 -08:00
}
/// <summary>
/// Initializes the test environment.
/// </summary>
/// <param name="scope">The PowerToys module to start.</param>
public SessionHelper Init ( )
{
2025-06-17 17:56:48 +08:00
this . ExitExe ( this . locationPath + this . sessionPath ) ;
this . StartExe ( this . locationPath + this . sessionPath ) ;
2025-02-28 03:08:08 -08:00
Assert . IsNotNull ( this . Driver , $"Failed to initialize the test environment. Driver is null." ) ;
return this ;
}
/// <summary>
/// Cleans up the test environment.
/// </summary>
public void Cleanup ( )
{
2025-03-14 17:04:23 +08:00
ExitScopeExe ( ) ;
2025-02-28 03:08:08 -08:00
try
{
2025-06-17 17:56:48 +08:00
if ( this . scope = = PowerToysModule . PowerToysSettings )
{
runner ? . Kill ( ) ;
runner ? . WaitForExit ( ) ; // Optional: Wait for the process to exit
}
2025-02-28 03:08:08 -08:00
}
catch ( Exception ex )
{
// Handle exceptions if needed
Debug . WriteLine ( $"Exception during Cleanup: {ex.Message}" ) ;
}
}
2025-06-17 17:56:48 +08:00
/// <summary>
/// Exit a exe.
/// </summary>
/// <param name="appPath">The path to the application executable.</param>
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}" ) ;
}
}
}
2025-02-28 03:08:08 -08:00
/// <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 ) ;
2025-06-17 17:56:48 +08:00
this . Driver = NewWindowsDriver ( opts ) ;
2025-03-14 17:04:23 +08:00
}
/// <summary>
2025-06-17 17:56:48 +08:00
/// Starts a new exe and takes control of it.
2025-03-14 17:04:23 +08:00
/// </summary>
2025-06-17 17:56:48 +08:00
/// <param name="info">The path to the application executable.</param>
private WindowsDriver < WindowsElement > NewWindowsDriver ( AppiumOptions info )
2025-03-14 17:04:23 +08:00
{
2025-06-17 17:56:48 +08:00
// Create driver with retry
var timeout = TimeSpan . FromMinutes ( 2 ) ;
var retryInterval = TimeSpan . FromSeconds ( 5 ) ;
DateTime startTime = DateTime . Now ;
2025-03-14 17:04:23 +08:00
2025-06-17 17:56:48 +08:00
while ( true )
2025-03-14 17:04:23 +08:00
{
try
{
2025-06-17 17:56:48 +08:00
var res = new WindowsDriver < WindowsElement > ( new Uri ( ModuleConfigData . Instance . GetWindowsApplicationDriverUrl ( ) ) , info ) ;
return res ;
2025-03-14 17:04:23 +08:00
}
2025-06-17 17:56:48 +08:00
catch ( Exception )
2025-03-14 17:04:23 +08:00
{
2025-06-17 17:56:48 +08:00
if ( DateTime . Now - startTime > timeout )
{
throw ;
}
Task . Delay ( retryInterval ) . Wait ( ) ;
CheckWinAppDriverAndRoot ( ) ;
2025-03-14 17:04:23 +08:00
}
}
}
/// <summary>
/// Exit now exe.
/// </summary>
public void ExitScopeExe ( )
{
ExitExe ( sessionPath ) ;
}
/// <summary>
/// Restarts now exe and takes control of it.
/// </summary>
public void RestartScopeExe ( )
{
2025-06-17 17:56:48 +08:00
ExitScopeExe ( ) ;
2025-03-14 17:04:23 +08:00
StartExe ( locationPath + sessionPath ) ;
2025-02-28 03:08:08 -08:00
}
2025-06-17 17:56:48 +08:00
public WindowsDriver < WindowsElement > GetRoot ( )
{
return SessionHelper . root ! ;
}
2025-02-28 03:08:08 -08:00
public WindowsDriver < WindowsElement > GetDriver ( )
{
Assert . IsNotNull ( this . Driver , $"Failed to get driver. Driver is null." ) ;
return this . Driver ;
}
2025-06-17 17:56:48 +08:00
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 ) ;
}
2025-02-28 03:08:08 -08:00
}
}