/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/common/UITestAutomation/Session.cs
658 строк
27 KB
Josh Soref
Updates for check-spelling v0.0.25 (#40386)
09 июл 2025, 01:16
Не верифицирован
09 июл 2025, 01:16
bf16e10
Код
Авторство
О чём код?
// 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.Collections.ObjectModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; using OpenQA.Selenium.Interactions; using static Microsoft.PowerToys.UITest.WindowHelper; namespace Microsoft.PowerToys.UITest { /// <summary> /// Provides interfaces for interacting with UI elements. /// </summary> public class Session { public WindowsDriver<WindowsElement> Root { get; set; } private WindowsDriver<WindowsElement> WindowsDriver { get; set; } private List<IntPtr> windowHandlers = new List<IntPtr>(); private Window? MainWindow { get; set; } /// <summary> /// Gets Main Window Handler /// </summary> public IntPtr MainWindowHandler { get; private set; } /// <summary> /// Gets Init Scope /// </summary> public PowerToysModule InitScope { get; private set; } /// <summary> /// Gets the RunAsAdmin flag. /// If true, the session is running as admin. /// If false, the session is not running as admin. /// If null, no information is available. /// </summary> public bool? IsElevated { get; private set; } public Session(WindowsDriver<WindowsElement> pRoot, WindowsDriver<WindowsElement> pDriver, PowerToysModule scope, WindowSize size) { this.MainWindowHandler = IntPtr.Zero; this.Root = pRoot; this.WindowsDriver = pDriver; this.InitScope = scope; if (size != WindowSize.UnSpecified) { // Attach to the scope & reset MainWindowHandler this.Attach(scope, size); } } /// <summary> /// Cleans up the Session Exe. /// </summary> public void Cleanup() { windowHandlers.Clear(); } /// <summary> /// Finds an 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 selector to find the element.</param> /// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param> /// <returns>The found element.</returns> public T Find<T>(By by, int timeoutMS = 5000, bool global = false) where T : Element, new() { Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}"); // leverage findAll to filter out mismatched elements var collection = this.FindAll<T>(by, timeoutMS, global); Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}"); return collection[0]; } /// <summary> /// Shortcut for this.Find<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>The found element.</returns> public T Find<T>(string name, int timeoutMS = 5000, bool global = false) where T : Element, new() { return this.Find<T>(By.Name(name), timeoutMS, global); } /// <summary> /// Shortcut for this.Find<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>The found element.</returns> public Element Find(By by, int timeoutMS = 5000, bool global = false) { return this.Find<Element>(by, timeoutMS, global); } /// <summary> /// Shortcut for this.Find<Element>(By.Name(name), timeoutMS) /// </summary> /// <param name="name">The name of the element.</param> /// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param> /// <returns>The found element.</returns> public Element Find(string name, int timeoutMS = 5000, bool global = false) { return this.Find<Element>(By.Name(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.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.HasOne<Element>(by, timeoutMS, global); } /// <summary> /// Shortcut for this.HasOne<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 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.HasOne<T>(By.Name(name), timeoutMS, global); } /// <summary> /// Shortcut for this.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.HasOne<Element>(By.Name(name), timeoutMS, global); } /// <summary> /// Has one or more 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 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.FindAll<T>(by, timeoutMS, global).Count >= 1; } /// <summary> /// Shortcut for this.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.Has<Element>(by, timeoutMS, global); } /// <summary> /// Shortcut for this.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.Has<T>(By.Name(name), timeoutMS, global); } /// <summary> /// Shortcut for this.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.Has<Element>(name, timeoutMS, global); } /// <summary> /// Finds all Element or its derived class by selector. /// </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 5000).</param> /// <returns>A read-only collection of the found elements.</returns> public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000, bool global = false) where T : Element, new() { var driver = global ? this.Root : this.WindowsDriver; Assert.IsNotNull(driver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}"); var foundElements = FindHelper.FindAll<T, WindowsElement>( () => { if (by.GetIsAccessibilityId()) { var elements = driver.FindElementsByAccessibilityId(by.GetAccessibilityId()); return elements; } else { var elements = driver.FindElements(by.ToSeleniumBy()); return elements; } }, driver, timeoutMS); return foundElements ?? new ReadOnlyCollection<T>([]); } /// <summary> /// Finds all elements by selector. /// Shortcut for this.FindAll<T>(By.Name(name), timeoutMS) /// </summary> /// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam> /// <param name="name">The name to find the elements.</param> /// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param> /// <returns>A read-only collection of the found elements.</returns> public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000, bool global = false) where T : Element, new() { return this.FindAll<T>(By.Name(name), timeoutMS, global); } /// <summary> /// Finds all elements by selector. /// Shortcut for this.FindAll<Element>(by, timeoutMS) /// </summary> /// <param name="by">The selector to find the elements.</param> /// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param> /// <returns>A read-only collection of the found elements.</returns> public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000, bool global = false) { return this.FindAll<Element>(by, timeoutMS, global); } /// <summary> /// Finds all elements by selector. /// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS) /// </summary> /// <param name="name">The name to find the elements.</param> /// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param> /// <returns>A read-only collection of the found elements.</returns> public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000, bool global = false) { return this.FindAll<Element>(By.Name(name), timeoutMS, global); } /// <summary> /// Close the main window. /// </summary> public void CloseMainWindow() { if (MainWindow != null) { MainWindow.Close(); MainWindow = null; } } /// <summary> /// Sends a combination of keys. /// </summary> /// <param name="keys">The keys to send.</param> public void SendKeys(params Key[] keys) { PerformAction(() => { KeyboardHelper.SendKeys(keys); }); } /// <summary> /// release the key (after the hold key and drag is completed.) /// </summary> /// <param name="key">The key release.</param> public void PressKey(Key key) { PerformAction(() => { KeyboardHelper.PressKey(key); }); } /// <summary> /// press and hold the specified key. /// </summary> /// <param name="key">The key to press and hold .</param> public void ReleaseKey(Key key) { PerformAction(() => { KeyboardHelper.ReleaseKey(key); }); } /// <summary> /// press and hold the specified key. /// </summary> /// <param name="key">The key to press and release .</param> public void SendKey(Key key, int msPreAction = 500, int msPostAction = 500) { PerformAction( () => { KeyboardHelper.SendKey(key); }, msPreAction, msPostAction); } /// <summary> /// Sends a sequence of keys. /// </summary> /// <param name="keys">An array of keys to send.</param> public void SendKeySequence(params Key[] keys) { PerformAction(() => { foreach (var key in keys) { KeyboardHelper.SendKeys(key); } }); } /// <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 MouseHelper.GetMousePosition(); } /// <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, int msPreAction = 500, int msPostAction = 500) { PerformAction( () => { MouseHelper.MoveMouseTo(x, y); }, msPreAction, msPostAction); } /// <summary> /// Performs a mouse action based on the specified action type. /// </summary> /// <param name="action">The mouse action to perform.</param> /// <param name="msPreAction">Pre-action delay in milliseconds.</param> /// <param name="msPostAction">Post-action delay in milliseconds.</param> public void PerformMouseAction(MouseActionType action, int msPreAction = 500, int msPostAction = 500) { PerformAction( () => { switch (action) { case MouseActionType.LeftClick: MouseHelper.LeftClick(); break; case MouseActionType.RightClick: MouseHelper.RightClick(); break; case MouseActionType.MiddleClick: MouseHelper.MiddleClick(); break; case MouseActionType.LeftDoubleClick: MouseHelper.LeftDoubleClick(); break; case MouseActionType.RightDoubleClick: MouseHelper.RightDoubleClick(); break; case MouseActionType.LeftDown: MouseHelper.LeftDown(); break; case MouseActionType.LeftUp: MouseHelper.LeftUp(); break; case MouseActionType.RightDown: MouseHelper.RightDown(); break; case MouseActionType.RightUp: MouseHelper.RightUp(); break; case MouseActionType.MiddleDown: MouseHelper.MiddleDown(); break; case MouseActionType.MiddleUp: MouseHelper.MiddleUp(); break; case MouseActionType.ScrollUp: MouseHelper.ScrollUp(); break; case MouseActionType.ScrollDown: MouseHelper.ScrollDown(); break; default: throw new ArgumentException("Unsupported mouse action.", nameof(action)); } }, msPreAction, msPostAction); } /// <summary> /// Attaches to an existing PowerToys module. /// </summary> /// <param name="module">The PowerToys module to attach to.</param> /// <param name="size">The window size to set. Default is no change to window size</param> /// <returns>The attached session.</returns> public Session Attach(PowerToysModule module, WindowSize size = WindowSize.UnSpecified) { string windowName = ModuleConfigData.Instance.GetModuleWindowName(module); return this.Attach(windowName, size); } /// <summary> /// Attaches to an existing exe by string window name. /// The session should be attached when a new app is started. /// </summary> /// <param name="windowName">The window name to attach to.</param> /// <param name="size">The window size to set. Default is no change to window size</param> /// <returns>The attached session.</returns> public Session Attach(string windowName, WindowSize size = WindowSize.UnSpecified) { this.IsElevated = null; this.MainWindowHandler = IntPtr.Zero; if (this.Root != null) { // search window handler by window title (admin and non-admin titles) var timeout = TimeSpan.FromMinutes(2); var retryInterval = TimeSpan.FromSeconds(5); DateTime startTime = DateTime.Now; List<(IntPtr HWnd, string Title)>? matchingWindows = null; while (DateTime.Now - startTime < timeout) { matchingWindows = WindowHelper.ApiHelper.FindDesktopWindowHandler( new[] { windowName, WindowHelper.AdministratorPrefix + windowName }); if (matchingWindows.Count > 0 && matchingWindows[0].HWnd != IntPtr.Zero) { break; } Task.Delay(retryInterval).Wait(); } if (matchingWindows == null || matchingWindows.Count == 0 || matchingWindows[0].HWnd == IntPtr.Zero) { Assert.Fail($"Failed to attach. Window '{windowName}' not found after {timeout.TotalSeconds} seconds."); } // pick one from matching windows this.MainWindowHandler = matchingWindows[0].HWnd; this.IsElevated = matchingWindows[0].Title.StartsWith(WindowHelper.AdministratorPrefix); ApiHelper.SetForegroundWindow(this.MainWindowHandler); var hexWindowHandle = this.MainWindowHandler.ToInt64().ToString("x"); var appCapabilities = new AppiumOptions(); appCapabilities.AddAdditionalCapability("appTopLevelWindow", hexWindowHandle); appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC"); this.WindowsDriver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), appCapabilities); this.windowHandlers.Add(this.MainWindowHandler); if (size != WindowSize.UnSpecified) { WindowHelper.SetWindowSize(this.MainWindowHandler, size); } // Set MainWindow MainWindow = Find<Window>(matchingWindows[0].Title); } else { Assert.IsNotNull(this.Root, $"Failed to attach to the window '{windowName}'. Root driver is null"); } Task.Delay(3000).Wait(); return this; } /// <summary> /// Sets the main window size. /// </summary> /// <param name="size">WindowSize enum</param> public void SetMainWindowSize(WindowSize size) { if (this.MainWindowHandler == IntPtr.Zero) { // Attach to the scope & reset MainWindowHandler this.Attach(this.InitScope); } WindowHelper.SetWindowSize(this.MainWindowHandler, size); } /// <summary> /// Gets the main window center coordinates. /// </summary> /// <returns>(x, y)</returns> public (int CenterX, int CenterY) GetMainWindowCenter() { return WindowHelper.GetWindowCenter(this.MainWindowHandler); } /// <summary> /// Gets the main window center coordinates. /// </summary> /// <returns>(int Left, int Top, int Right, int Bottom)</returns> public (int Left, int Top, int Right, int Bottom) GetMainWindowRect() { return WindowHelper.GetWindowRect(this.MainWindowHandler); } /// <summary> /// Launches the specified executable with optional arguments and simulates a delay before and after execution. /// </summary> /// <param name="executablePath">The full path to the executable to launch.</param> /// <param name="arguments">Optional command-line arguments to pass to the executable.</param> /// <param name="msPreAction">The number of milliseconds to wait before launching the executable. Default is 0 ms.</param> /// <param name="msPostAction">The number of milliseconds to wait after launching the executable. Default is 2000 ms.</param> public void StartExe(string executablePath, string arguments = "", int msPreAction = 0, int msPostAction = 2000) { PerformAction( () => { StartExeInternal(executablePath, arguments); }, msPreAction, msPostAction); } private void StartExeInternal(string executablePath, string arguments = "") { var processInfo = new ProcessStartInfo { FileName = executablePath, Arguments = arguments, UseShellExecute = true, }; Process.Start(processInfo); } /// <summary> /// Terminates all running processes that match the specified process name. /// Waits for each process to exit after sending the kill signal. /// </summary> /// <param name="processName">The name of the process to terminate (without extension, e.g., "notepad").</param> public void KillAllProcessesByName(string processName) { foreach (var process in Process.GetProcessesByName(processName)) { process.Kill(); process.WaitForExit(); } } /// <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(); } } /// <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 action, int msPreAction = 500, int msPostAction = 500) { if (msPreAction > 0) { Task.Delay(msPreAction).Wait(); } action(); if (msPostAction > 0) { Task.Delay(msPostAction).Wait(); } } } }