From 995bbdc62d92bb7357676242d076492e10a1395a Mon Sep 17 00:00:00 2001 From: Gleb Khmyznikov Date: Wed, 10 Dec 2025 10:04:04 -0800 Subject: [PATCH] Fix fancy zones UI tests #42249 (#44181) - [ ] Closes: #42249 Contribution to https://github.com/microsoft/PowerToys/issues/40701 --- .github/actions/spell-check/allow/code.txt | 4 + .../UITestAutomation/ScreenRecording.cs | 399 ++++++++++++++++++ src/common/UITestAutomation/SessionHelper.cs | 122 ++++-- .../UITestAutomation/SettingsConfigHelper.cs | 7 +- src/common/UITestAutomation/UITestBase.cs | 99 ++++- .../MouseUtils.UITests/FindMyMouseTests.cs | 2 + .../Settings/SettingsWindow.xaml.cs | 1 + .../FancyZones.UITests/DragWindowTests.cs | 286 +++++++------ .../LayoutApplyHotKeyTests.cs | 10 +- .../FancyZones.UITests/OneZoneSwitchTests.cs | 68 ++- .../peek/Peek.UITests/PeekFilePreviewTests.cs | 4 +- .../SettingsXAML/Views/FancyZonesPage.xaml | 10 +- 12 files changed, 807 insertions(+), 205 deletions(-) create mode 100644 src/common/UITestAutomation/ScreenRecording.cs diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index c655bb1b55..a7d02dcb21 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -335,3 +335,7 @@ azp feedbackhub needinfo reportbug + +#ffmpeg +crf +nostdin diff --git a/src/common/UITestAutomation/ScreenRecording.cs b/src/common/UITestAutomation/ScreenRecording.cs new file mode 100644 index 0000000000..57e844936d --- /dev/null +++ b/src/common/UITestAutomation/ScreenRecording.cs @@ -0,0 +1,399 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.PowerToys.UITest +{ + /// + /// Provides methods for recording the screen during UI tests. + /// Requires FFmpeg to be installed and available in PATH. + /// + internal class ScreenRecording : IDisposable + { + private readonly string outputDirectory; + private readonly string framesDirectory; + private readonly string outputFilePath; + private readonly List capturedFrames; + private readonly SemaphoreSlim recordingLock = new(1, 1); + private readonly Stopwatch recordingStopwatch = new(); + private readonly string? ffmpegPath; + private CancellationTokenSource? recordingCancellation; + private Task? recordingTask; + private bool isRecording; + private int frameCount; + + [DllImport("user32.dll")] + private static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("gdi32.dll")] + private static extern int GetDeviceCaps(IntPtr hdc, int nIndex); + + [DllImport("user32.dll")] + private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("user32.dll")] + private static extern bool GetCursorInfo(out ScreenCapture.CURSORINFO pci); + + [DllImport("user32.dll")] + private static extern bool DrawIconEx(IntPtr hdc, int x, int y, IntPtr hIcon, int cx, int cy, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); + + private const int CURSORSHOWING = 0x00000001; + private const int DESKTOPHORZRES = 118; + private const int DESKTOPVERTRES = 117; + private const int DINORMAL = 0x0003; + private const int TargetFps = 15; // 15 FPS for good balance of quality and size + + /// + /// Initializes a new instance of the class. + /// + /// Directory where the recording will be saved. + public ScreenRecording(string outputDirectory) + { + this.outputDirectory = outputDirectory; + string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + framesDirectory = Path.Combine(outputDirectory, $"frames_{timestamp}"); + outputFilePath = Path.Combine(outputDirectory, $"recording_{timestamp}.mp4"); + capturedFrames = new List(); + frameCount = 0; + + // Check if FFmpeg is available + ffmpegPath = FindFfmpeg(); + if (ffmpegPath == null) + { + Console.WriteLine("FFmpeg not found. Screen recording will be disabled."); + Console.WriteLine("To enable video recording, install FFmpeg: https://ffmpeg.org/download.html"); + } + } + + /// + /// Gets a value indicating whether screen recording is available (FFmpeg found). + /// + public bool IsAvailable => ffmpegPath != null; + + /// + /// Starts recording the screen. + /// + /// A task representing the asynchronous operation. + public async Task StartRecordingAsync() + { + await recordingLock.WaitAsync(); + try + { + if (isRecording || !IsAvailable) + { + return; + } + + // Create frames directory + Directory.CreateDirectory(framesDirectory); + + recordingCancellation = new CancellationTokenSource(); + isRecording = true; + recordingStopwatch.Start(); + + // Start the recording task + recordingTask = Task.Run(() => RecordFrames(recordingCancellation.Token)); + + Console.WriteLine($"Started screen recording at {TargetFps} FPS"); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to start recording: {ex.Message}"); + isRecording = false; + } + finally + { + recordingLock.Release(); + } + } + + /// + /// Stops recording and encodes video. + /// + /// A task representing the asynchronous operation. + public async Task StopRecordingAsync() + { + await recordingLock.WaitAsync(); + try + { + if (!isRecording || recordingCancellation == null) + { + return; + } + + // Signal cancellation + recordingCancellation.Cancel(); + + // Wait for recording task to complete + if (recordingTask != null) + { + await recordingTask; + } + + recordingStopwatch.Stop(); + isRecording = false; + + double duration = recordingStopwatch.Elapsed.TotalSeconds; + Console.WriteLine($"Recording stopped. Captured {capturedFrames.Count} frames in {duration:F2} seconds"); + + // Encode to video + await EncodeToVideoAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Error stopping recording: {ex.Message}"); + } + finally + { + Cleanup(); + recordingLock.Release(); + } + } + + /// + /// Records frames from the screen. + /// + private void RecordFrames(CancellationToken cancellationToken) + { + try + { + int frameInterval = 1000 / TargetFps; + var frameTimer = Stopwatch.StartNew(); + + while (!cancellationToken.IsCancellationRequested) + { + var frameStart = frameTimer.ElapsedMilliseconds; + + try + { + CaptureFrame(); + } + catch (Exception ex) + { + Console.WriteLine($"Error capturing frame: {ex.Message}"); + } + + // Sleep for remaining time to maintain target FPS + var frameTime = frameTimer.ElapsedMilliseconds - frameStart; + var sleepTime = Math.Max(0, frameInterval - (int)frameTime); + + if (sleepTime > 0) + { + Thread.Sleep(sleepTime); + } + } + } + catch (OperationCanceledException) + { + // Expected when stopping + } + catch (Exception ex) + { + Console.WriteLine($"Error during recording: {ex.Message}"); + } + } + + /// + /// Captures a single frame. + /// + private void CaptureFrame() + { + IntPtr hdc = GetDC(IntPtr.Zero); + int screenWidth = GetDeviceCaps(hdc, DESKTOPHORZRES); + int screenHeight = GetDeviceCaps(hdc, DESKTOPVERTRES); + ReleaseDC(IntPtr.Zero, hdc); + + Rectangle bounds = new Rectangle(0, 0, screenWidth, screenHeight); + using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb)) + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size); + + ScreenCapture.CURSORINFO cursorInfo; + cursorInfo.CbSize = Marshal.SizeOf(); + if (GetCursorInfo(out cursorInfo) && cursorInfo.Flags == CURSORSHOWING) + { + IntPtr hdcDest = g.GetHdc(); + DrawIconEx(hdcDest, cursorInfo.PTScreenPos.X, cursorInfo.PTScreenPos.Y, cursorInfo.HCursor, 0, 0, 0, IntPtr.Zero, DINORMAL); + g.ReleaseHdc(hdcDest); + } + } + + string framePath = Path.Combine(framesDirectory, $"frame_{frameCount:D6}.jpg"); + bitmap.Save(framePath, ImageFormat.Jpeg); + capturedFrames.Add(framePath); + frameCount++; + } + } + + /// + /// Encodes captured frames to video using ffmpeg. + /// + private async Task EncodeToVideoAsync() + { + if (capturedFrames.Count == 0) + { + Console.WriteLine("No frames captured"); + return; + } + + try + { + // Build ffmpeg command with proper non-interactive flags + string inputPattern = Path.Combine(framesDirectory, "frame_%06d.jpg"); + + // -y: overwrite without asking + // -nostdin: disable interaction + // -loglevel error: only show errors + // -stats: show encoding progress + string args = $"-y -nostdin -loglevel error -stats -framerate {TargetFps} -i \"{inputPattern}\" -c:v libx264 -pix_fmt yuv420p -crf 23 \"{outputFilePath}\""; + + Console.WriteLine($"Encoding {capturedFrames.Count} frames to video..."); + + var startInfo = new ProcessStartInfo + { + FileName = ffmpegPath!, + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, // Important: redirect stdin to prevent hanging + CreateNoWindow = true, + }; + + using var process = Process.Start(startInfo); + if (process != null) + { + // Close stdin immediately to ensure FFmpeg doesn't wait for input + process.StandardInput.Close(); + + // Read output streams asynchronously to prevent deadlock + var outputTask = process.StandardOutput.ReadToEndAsync(); + var errorTask = process.StandardError.ReadToEndAsync(); + + // Wait for process to exit + await process.WaitForExitAsync(); + + // Get the output + string stdout = await outputTask; + string stderr = await errorTask; + + if (process.ExitCode == 0 && File.Exists(outputFilePath)) + { + var fileInfo = new FileInfo(outputFilePath); + Console.WriteLine($"Video created: {outputFilePath} ({fileInfo.Length / 1024 / 1024:F1} MB)"); + } + else + { + Console.WriteLine($"FFmpeg encoding failed with exit code {process.ExitCode}"); + if (!string.IsNullOrWhiteSpace(stderr)) + { + Console.WriteLine($"FFmpeg error: {stderr}"); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error encoding video: {ex.Message}"); + } + } + + /// + /// Finds ffmpeg executable. + /// + private static string? FindFfmpeg() + { + // Check if ffmpeg is in PATH + var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty(); + + foreach (var dir in pathDirs) + { + var ffmpegPath = Path.Combine(dir, "ffmpeg.exe"); + if (File.Exists(ffmpegPath)) + { + return ffmpegPath; + } + } + + // Check common installation locations + var commonPaths = new[] + { + @"C:\.tools\ffmpeg\bin\ffmpeg.exe", + @"C:\ffmpeg\bin\ffmpeg.exe", + @"C:\Program Files\ffmpeg\bin\ffmpeg.exe", + @"C:\Program Files (x86)\ffmpeg\bin\ffmpeg.exe", + @$"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Microsoft\WinGet\Links\ffmpeg.exe", + }; + + foreach (var path in commonPaths) + { + if (File.Exists(path)) + { + return path; + } + } + + return null; + } + + /// + /// Gets the path to the recorded video file. + /// + public string OutputFilePath => outputFilePath; + + /// + /// Gets the directory containing recordings. + /// + public string OutputDirectory => outputDirectory; + + /// + /// Cleans up resources. + /// + private void Cleanup() + { + recordingCancellation?.Dispose(); + recordingCancellation = null; + recordingTask = null; + + // Clean up frames directory if it exists + try + { + if (Directory.Exists(framesDirectory)) + { + Directory.Delete(framesDirectory, true); + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to cleanup frames directory: {ex.Message}"); + } + } + + /// + /// Disposes resources. + /// + public void Dispose() + { + if (isRecording) + { + StopRecordingAsync().GetAwaiter().GetResult(); + } + + Cleanup(); + recordingLock.Dispose(); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/common/UITestAutomation/SessionHelper.cs b/src/common/UITestAutomation/SessionHelper.cs index 0ca3eb3ddd..fef220a647 100644 --- a/src/common/UITestAutomation/SessionHelper.cs +++ b/src/common/UITestAutomation/SessionHelper.cs @@ -130,9 +130,13 @@ namespace Microsoft.PowerToys.UITest /// /// The path to the application executable. /// Optional command line arguments to pass to the application. - public void StartExe(string appPath, string[]? args = null) + public void StartExe(string appPath, string[]? args = null, string? enableModules = null) { var opts = new AppiumOptions(); + if (!string.IsNullOrEmpty(enableModules)) + { + opts.AddAdditionalCapability("enableModules", enableModules); + } if (scope == PowerToysModule.PowerToysSettings) { @@ -169,27 +173,66 @@ namespace Microsoft.PowerToys.UITest private void TryLaunchPowerToysSettings(AppiumOptions opts) { - try + if (opts.ToCapabilities().HasCapability("enableModules")) { - var runnerProcessInfo = new ProcessStartInfo + var modulesString = (string)opts.ToCapabilities().GetCapability("enableModules"); + var modulesArray = modulesString.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + SettingsConfigHelper.ConfigureGlobalModuleSettings(modulesArray); + } + else + { + SettingsConfigHelper.ConfigureGlobalModuleSettings(); + } + + const int maxTries = 3; + const int delayMs = 5000; + const int maxRetries = 3; + + for (int tryCount = 1; tryCount <= maxTries; tryCount++) + { + try { - FileName = locationPath + runnerPath, - Verb = "runas", - Arguments = "--open-settings", - }; + var runnerProcessInfo = new ProcessStartInfo + { + FileName = locationPath + runnerPath, + Verb = "runas", + Arguments = "--open-settings", + }; - ExitExe(runnerProcessInfo.FileName); - runner = Process.Start(runnerProcessInfo); + ExitExe(runnerProcessInfo.FileName); - WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5); + // Verify process was killed + string exeName = Path.GetFileNameWithoutExtension(runnerProcessInfo.FileName); + var remainingProcesses = Process.GetProcessesByName(exeName); - // Exit CmdPal UI before launching new process if use installer for test - ExitExeByName("Microsoft.CmdPal.UI"); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex); + runner = Process.Start(runnerProcessInfo); + + if (WaitForWindowAndSetCapability(opts, "PowerToys Settings", delayMs, maxRetries)) + { + // Exit CmdPal UI before launching new process if use installer for test + ExitExeByName("Microsoft.CmdPal.UI"); + return; + } + + // Window not found, kill all PowerToys processes and retry + if (tryCount < maxTries) + { + KillPowerToysProcesses(); + } + } + catch (Exception ex) + { + if (tryCount == maxTries) + { + throw new InvalidOperationException($"Failed to launch PowerToys Settings after {maxTries} attempts: {ex.Message}", ex); + } + + // Kill processes and retry + KillPowerToysProcesses(); + } } + + throw new InvalidOperationException($"Failed to launch PowerToys Settings: Window not found after {maxTries} attempts."); } private void TryLaunchCommandPalette(AppiumOptions opts) @@ -211,7 +254,10 @@ namespace Microsoft.PowerToys.UITest var process = Process.Start(processStartInfo); process?.WaitForExit(); - WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10); + if (!WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10)) + { + throw new TimeoutException("Failed to find Command Palette window after multiple attempts."); + } } catch (Exception ex) { @@ -219,7 +265,7 @@ namespace Microsoft.PowerToys.UITest } } - private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries) + private bool WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries) { for (int attempt = 1; attempt <= maxRetries; attempt++) { @@ -230,18 +276,16 @@ namespace Microsoft.PowerToys.UITest { var hexHwnd = window[0].HWnd.ToString("x"); opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd); - return; + return true; } if (attempt < maxRetries) { Thread.Sleep(delayMs); } - else - { - throw new TimeoutException($"Failed to find {windowName} window after multiple attempts."); - } } + + return false; } /// @@ -292,17 +336,17 @@ namespace Microsoft.PowerToys.UITest catch (Exception ex) { // Handle exceptions if needed - Debug.WriteLine($"Exception during Cleanup: {ex.Message}"); + Console.WriteLine($"Exception during Cleanup: {ex.Message}"); } } /// /// Restarts now exe and takes control of it. /// - public void RestartScopeExe() + public void RestartScopeExe(string? enableModules = null) { ExitScopeExe(); - StartExe(locationPath + sessionPath, this.commandLineArgs); + StartExe(locationPath + sessionPath, commandLineArgs, enableModules); } public WindowsDriver GetRoot() @@ -327,5 +371,31 @@ namespace Microsoft.PowerToys.UITest this.ExitExe(winAppDriverProcessInfo.FileName); SessionHelper.appDriver = Process.Start(winAppDriverProcessInfo); } + + private void KillPowerToysProcesses() + { + var powerToysProcessNames = new[] { "PowerToys", "Microsoft.CmdPal.UI" }; + + foreach (var processName in powerToysProcessNames) + { + try + { + var processes = Process.GetProcessesByName(processName); + + foreach (var process in processes) + { + process.Kill(); + process.WaitForExit(); + } + + // Verify processes are actually gone + var remainingProcesses = Process.GetProcessesByName(processName); + } + catch (Exception ex) + { + Console.WriteLine($"[KillPowerToysProcesses] Failed to kill process {processName}: {ex.Message}"); + } + } + } } } diff --git a/src/common/UITestAutomation/SettingsConfigHelper.cs b/src/common/UITestAutomation/SettingsConfigHelper.cs index 0a01891dc4..81e5e3c180 100644 --- a/src/common/UITestAutomation/SettingsConfigHelper.cs +++ b/src/common/UITestAutomation/SettingsConfigHelper.cs @@ -26,14 +26,13 @@ namespace Microsoft.PowerToys.UITest /// /// Configures global PowerToys settings to enable only specified modules and disable all others. /// - /// Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled. - /// Thrown when modulesToEnable is null. + /// Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled. If null or empty, all modules will be disabled. /// Thrown when settings file operations fail. [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")] [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")] - public static void ConfigureGlobalModuleSettings(params string[] modulesToEnable) + public static void ConfigureGlobalModuleSettings(params string[]? modulesToEnable) { - ArgumentNullException.ThrowIfNull(modulesToEnable); + modulesToEnable ??= Array.Empty(); try { diff --git a/src/common/UITestAutomation/UITestBase.cs b/src/common/UITestAutomation/UITestBase.cs index 1c72be05f4..877f384104 100644 --- a/src/common/UITestAutomation/UITestBase.cs +++ b/src/common/UITestAutomation/UITestBase.cs @@ -29,6 +29,8 @@ namespace Microsoft.PowerToys.UITest public string? ScreenshotDirectory { get; set; } + public string? RecordingDirectory { get; set; } + public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List() }; private readonly PowerToysModule scope; @@ -36,6 +38,7 @@ namespace Microsoft.PowerToys.UITest private readonly string[]? commandLineArgs; private SessionHelper? sessionHelper; private System.Threading.Timer? screenshotTimer; + private ScreenRecording? screenRecording; public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null) { @@ -65,12 +68,35 @@ namespace Microsoft.PowerToys.UITest CloseOtherApplications(); if (IsInPipeline) { - ScreenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString()); + string baseDirectory = this.TestContext.TestResultsDirectory ?? string.Empty; + ScreenshotDirectory = Path.Combine(baseDirectory, "UITestScreenshots_" + Guid.NewGuid().ToString()); Directory.CreateDirectory(ScreenshotDirectory); + RecordingDirectory = Path.Combine(baseDirectory, "UITestRecordings_" + Guid.NewGuid().ToString()); + Directory.CreateDirectory(RecordingDirectory); + // Take screenshot every 1 second screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, ScreenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000)); + // Start screen recording (requires FFmpeg) + try + { + screenRecording = new ScreenRecording(RecordingDirectory); + if (screenRecording.IsAvailable) + { + _ = screenRecording.StartRecordingAsync(); + } + else + { + screenRecording = null; + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to start screen recording: {ex.Message}"); + screenRecording = null; + } + // Escape Popups before starting System.Windows.Forms.SendKeys.SendWait("{ESC}"); } @@ -88,15 +114,36 @@ namespace Microsoft.PowerToys.UITest if (IsInPipeline) { screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite); - Dispose(); + + // Stop screen recording + if (screenRecording != null) + { + try + { + screenRecording.StopRecordingAsync().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to stop screen recording: {ex.Message}"); + } + } + if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed or UnitTestOutcome.Error or UnitTestOutcome.Unknown) { Task.Delay(1000).Wait(); AddScreenShotsToTestResultsDirectory(); + AddRecordingsToTestResultsDirectory(); AddLogFilesToTestResultsDirectory(); } + else + { + // Clean up recording if test passed + CleanupRecordingDirectory(); + } + + Dispose(); } this.Session.Cleanup(); @@ -106,6 +153,7 @@ namespace Microsoft.PowerToys.UITest public void Dispose() { screenshotTimer?.Dispose(); + screenRecording?.Dispose(); GC.SuppressFinalize(this); } @@ -600,6 +648,47 @@ namespace Microsoft.PowerToys.UITest } } + /// + /// Adds screen recordings to test results directory when test fails. + /// + protected void AddRecordingsToTestResultsDirectory() + { + if (RecordingDirectory != null && Directory.Exists(RecordingDirectory)) + { + // Add video files (MP4) + var videoFiles = Directory.GetFiles(RecordingDirectory, "*.mp4"); + foreach (string file in videoFiles) + { + this.TestContext.AddResultFile(file); + var fileInfo = new FileInfo(file); + Console.WriteLine($"Added video recording: {Path.GetFileName(file)} ({fileInfo.Length / 1024 / 1024:F1} MB)"); + } + + if (videoFiles.Length == 0) + { + Console.WriteLine("No video recording available (FFmpeg not found). Screenshots are still captured."); + } + } + } + + /// + /// Cleans up recording directory when test passes. + /// + private void CleanupRecordingDirectory() + { + if (RecordingDirectory != null && Directory.Exists(RecordingDirectory)) + { + try + { + Directory.Delete(RecordingDirectory, true); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to cleanup recording directory: {ex.Message}"); + } + } + } + /// /// Copies PowerToys log files to test results directory when test fails. /// Renames files to include the directory structure after \PowerToys. @@ -689,11 +778,11 @@ namespace Microsoft.PowerToys.UITest /// /// Restart scope exe. /// - public void RestartScopeExe() + public Session RestartScopeExe(string? enableModules = null) { - this.sessionHelper!.RestartScopeExe(); + this.sessionHelper!.RestartScopeExe(enableModules); this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size); - return; + return Session; } /// diff --git a/src/modules/MouseUtils/MouseUtils.UITests/FindMyMouseTests.cs b/src/modules/MouseUtils/MouseUtils.UITests/FindMyMouseTests.cs index 7cad62decb..5f857aa391 100644 --- a/src/modules/MouseUtils/MouseUtils.UITests/FindMyMouseTests.cs +++ b/src/modules/MouseUtils/MouseUtils.UITests/FindMyMouseTests.cs @@ -617,6 +617,8 @@ namespace MouseUtils.UITests private void LaunchFromSetting(bool reload = false, bool launchAsAdmin = false) { + Session = RestartScopeExe("FindMyMouse,MouseHighlighter,MouseJump,MousePointerCrosshairs,CursorWrap"); + // this.Session.Attach(PowerToysModule.PowerToysSettings); this.Session.SetMainWindowSize(WindowSize.Large); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs index 855a3e2e6c..5b12f78542 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/SettingsWindow.xaml.cs @@ -105,6 +105,7 @@ public sealed partial class SettingsWindow : WindowEx, "Extensions" => typeof(ExtensionsPage), _ => null, }; + if (pageType is not null) { NavFrame.Navigate(pageType); diff --git a/src/modules/fancyzones/FancyZones.UITests/DragWindowTests.cs b/src/modules/fancyzones/FancyZones.UITests/DragWindowTests.cs index 82e05707e7..117e128734 100644 --- a/src/modules/fancyzones/FancyZones.UITests/DragWindowTests.cs +++ b/src/modules/fancyzones/FancyZones.UITests/DragWindowTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using FancyZonesEditor.Models; @@ -49,19 +50,16 @@ namespace UITests_FancyZones [TestInitialize] public void TestInitialize() { - // ClearOpenWindows + Session.KillAllProcessesByName("PowerToys"); ClearOpenWindows(); - // kill all processes related to FancyZones Editor to ensure a clean state - Session.KillAllProcessesByName("PowerToys.FancyZonesEditor"); - AppZoneHistory.DeleteFile(); - this.RestartScopeExe(); FancyZonesEditorHelper.Files.Restore(); - - // Set a custom layout with 1 subzones and clear app zone history SetupCustomLayouts(); + RestartScopeExe("Hosts"); + Thread.Sleep(2000); + // Get the current mouse button setting nonPrimaryMouseButton = SystemInformation.MouseButtonsSwapped ? "Left" : "Right"; @@ -72,99 +70,6 @@ namespace UITests_FancyZones LaunchFancyZones(); } - /// - /// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings - /// - /// - /// Verifies that holding Shift while dragging shows all zones as expected. - /// - /// - /// - [TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")] - [TestCategory("FancyZones_Dragging #1")] - public void TestShowZonesOnShiftDuringDrag() - { - string testCaseName = nameof(TestShowZonesOnShiftDuringDrag); - Pane dragElement = Find(By.Name("Non Client Input Sink Window")); // element to drag - var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); - - var (initialColor, withShiftColor) = RunDragInteractions( - preAction: () => - { - dragElement.DragAndHold(offSet.Dx, offSet.Dy); - }, - postAction: () => - { - Session.PressKey(Key.Shift); - Task.Delay(500).Wait(); - }, - releaseAction: () => - { - Session.ReleaseKey(Key.Shift); - Task.Delay(5000).Wait(); // Optional: Wait for a moment to ensure window switch - }, - testCaseName: testCaseName); - - string zoneColorWithoutShift = GetOutWindowPixelColor(30); - - Assert.AreNotEqual(initialColor, withShiftColor, $"[{testCaseName}] Zone display failed."); - Assert.IsTrue( - withShiftColor == inactivateColor || withShiftColor == highlightColor, - $"[{testCaseName}] Zone display failed: withShiftColor was {withShiftColor}, expected {inactivateColor} or {highlightColor}."); - Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] Zone display failed."); - - Assert.AreEqual(zoneColorWithoutShift, initialColor, $"[{testCaseName}] Zone deactivated failed."); - dragElement.ReleaseDrag(); - - Clean(); - } - - /// - /// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings - /// - /// - /// Verifies that dragging activates zones as expected. - /// - /// - /// - [TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")] - [TestCategory("FancyZones_Dragging #2")] - public void TestShowZonesOnDragDuringShift() - { - string testCaseName = nameof(TestShowZonesOnDragDuringShift); - - var dragElement = Find(By.Name("Non Client Input Sink Window")); - var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); - - var (initialColor, withDragColor) = RunDragInteractions( - preAction: () => - { - dragElement.Drag(offSet.Dx, offSet.Dy); - Session.PressKey(Key.Shift); - }, - postAction: () => - { - dragElement.DragAndHold(0, 0); - Task.Delay(5000).Wait(); - }, - releaseAction: () => - { - dragElement.ReleaseDrag(); - Session.ReleaseKey(Key.Shift); - }, - testCaseName: testCaseName); - - Assert.AreNotEqual(initialColor, withDragColor, $"[{testCaseName}] Zone color did not change; zone activation failed."); - Assert.AreEqual(highlightColor, withDragColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed."); - - // double check by app-zone-history.json - string appZoneHistoryJson = AppZoneHistory.GetData(); - string? zoneNumber = ZoneSwitchHelper.GetZoneIndexSetByAppName(powertoysWindowName, appZoneHistoryJson); - Assert.IsNull(zoneNumber, $"[{testCaseName}] AppZoneHistory layout was unexpectedly set."); - - Clean(); - } - /// /// Test toggling zones using a non-primary mouse click during window dragging. /// @@ -178,14 +83,19 @@ namespace UITests_FancyZones public void TestToggleZonesWithNonPrimaryMouseClick() { string testCaseName = nameof(TestToggleZonesWithNonPrimaryMouseClick); - var dragElement = Find(By.Name("Non Client Input Sink Window")); - var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + + var windowRect = Session.GetMainWindowRect(); + int startX = windowRect.Left + 70; + int startY = windowRect.Top + 25; + int endX = startX + 300; + int endY = startY + 300; var (initialColor, withMouseColor) = RunDragInteractions( preAction: () => { - // activate zone - dragElement.DragAndHold(offSet.Dx, offSet.Dy); + Session.MoveMouseTo(startX, startY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.MoveMouseTo(endX, endY); }, postAction: () => { @@ -195,7 +105,7 @@ namespace UITests_FancyZones }, releaseAction: () => { - dragElement.ReleaseDrag(); + Session.PerformMouseAction(MouseActionType.LeftUp); }, testCaseName: testCaseName); @@ -204,8 +114,6 @@ namespace UITests_FancyZones // check the zone color is activated Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed."); - - Clean(); } /// @@ -221,32 +129,35 @@ namespace UITests_FancyZones public void TestShowZonesWhenShiftAndMouseOff() { string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOff); - Pane dragElement = Find(By.Name("Non Client Input Sink Window")); - var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + + var windowRect = Session.GetMainWindowRect(); + int startX = windowRect.Left + 70; + int startY = windowRect.Top + 25; + int endX = startX + 300; + int endY = startY + 300; var (initialColor, withShiftColor) = RunDragInteractions( preAction: () => { - // activate zone - dragElement.DragAndHold(offSet.Dx, offSet.Dy); + Session.MoveMouseTo(startX, startY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.MoveMouseTo(endX, endY); }, postAction: () => { // press Shift Key to deactivate zones Session.PressKey(Key.Shift); - Task.Delay(500).Wait(); + Task.Delay(1000).Wait(); }, releaseAction: () => { - dragElement.ReleaseDrag(); + Session.PerformMouseAction(MouseActionType.LeftUp); Session.ReleaseKey(Key.Shift); }, testCaseName: testCaseName); Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed."); Assert.AreNotEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone deactivation failed."); - - Clean(); } /// @@ -263,12 +174,17 @@ namespace UITests_FancyZones { string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOn); - var dragElement = Find(By.Name("Non Client Input Sink Window")); - var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + var windowRect = Session.GetMainWindowRect(); + int startX = windowRect.Left + 70; + int startY = windowRect.Top + 25; + int endX = startX + 300; + int endY = startY + 300; var (initialColor, withShiftColor) = RunDragInteractions( preAction: () => { - dragElement.DragAndHold(offSet.Dx, offSet.Dy); + Session.MoveMouseTo(startX, startY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.MoveMouseTo(endX, endY); }, postAction: () => { @@ -279,7 +195,7 @@ namespace UITests_FancyZones }, testCaseName: testCaseName); - Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] show zone failed."); + Assert.AreEqual(highlightColor, withShiftColor, $"[{testCaseName}] show zone failed."); Session.PerformMouseAction( nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick); @@ -288,9 +204,7 @@ namespace UITests_FancyZones Assert.AreEqual(initialColor, zoneColorWithMouse, $"[{nameof(TestShowZonesWhenShiftAndMouseOff)}] Zone deactivate failed."); Session.ReleaseKey(Key.Shift); - dragElement.ReleaseDrag(); - - Clean(); + Session.PerformMouseAction(MouseActionType.LeftUp); } /// @@ -307,8 +221,6 @@ namespace UITests_FancyZones { var pixel = GetPixelWhenMakeDraggedWindow(); Assert.AreNotEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOn)}] Window transparency failed."); - - Clean(); } /// @@ -325,14 +237,103 @@ namespace UITests_FancyZones { var pixel = GetPixelWhenMakeDraggedWindow(); Assert.AreEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOff)}] Window without transparency failed."); - - Clean(); } - private void Clean() + /// + /// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings + /// + /// + /// Verifies that holding Shift while dragging shows all zones as expected. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")] + [TestCategory("FancyZones_Dragging #1")] + public void TestShowZonesOnShiftDuringDrag() { - // clean app zone history file - AppZoneHistory.DeleteFile(); + string testCaseName = nameof(TestShowZonesOnShiftDuringDrag); + + var windowRect = Session.GetMainWindowRect(); + int startX = windowRect.Left + 70; + int startY = windowRect.Top + 25; + int endX = startX + 300; + int endY = startY + 300; + + var (initialColor, withShiftColor) = RunDragInteractions( + preAction: () => + { + Session.MoveMouseTo(startX, startY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.MoveMouseTo(endX, endY); + }, + postAction: () => + { + Session.PressKey(Key.Shift); + Task.Delay(500).Wait(); + }, + releaseAction: () => + { + Session.ReleaseKey(Key.Shift); + Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure window switch + }, + testCaseName: testCaseName); + + string zoneColorWithoutShift = GetOutWindowPixelColor(30); + + Assert.AreNotEqual(initialColor, withShiftColor, $"[{testCaseName}] Zone color did not change; zone activation failed."); + Assert.AreEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed."); + + Session.PerformMouseAction(MouseActionType.LeftUp); + } + + /// + /// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings + /// + /// + /// Verifies that dragging activates zones as expected. + /// + /// + /// + [TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")] + [TestCategory("FancyZones_Dragging #2")] + public void TestShowZonesOnDragDuringShift() + { + string testCaseName = nameof(TestShowZonesOnDragDuringShift); + + var windowRect = Session.GetMainWindowRect(); + int startX = windowRect.Left + 70; + int startY = windowRect.Top + 25; + int endX = startX + 300; + int endY = startY + 300; + + var (initialColor, withDragColor) = RunDragInteractions( + preAction: () => + { + Session.PressKey(Key.Shift); + Task.Delay(100).Wait(); + }, + postAction: () => + { + Session.MoveMouseTo(startX, startY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.MoveMouseTo(endX, endY); + Task.Delay(1000).Wait(); + }, + releaseAction: () => + { + Session.PerformMouseAction(MouseActionType.LeftUp); + Session.ReleaseKey(Key.Shift); + Task.Delay(100).Wait(); + }, + testCaseName: testCaseName); + + Assert.AreNotEqual(initialColor, withDragColor, $"[{testCaseName}] Zone color did not change; zone activation failed."); + Assert.AreEqual(highlightColor, withDragColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed."); + + // double check by app-zone-history.json + string appZoneHistoryJson = AppZoneHistory.GetData(); + string? zoneNumber = ZoneSwitchHelper.GetZoneIndexSetByAppName(powertoysWindowName, appZoneHistoryJson); + Assert.IsNull(zoneNumber, $"[{testCaseName}] AppZoneHistory layout was unexpectedly set."); } // Helper method to ensure the desktop has no open windows by clicking the "Show Desktop" button @@ -352,7 +353,7 @@ namespace UITests_FancyZones desktopButtonName = "Show Desktop"; } - this.Find(By.Name(desktopButtonName), 5000, true).Click(false, 500, 2000); + this.Find(By.Name(desktopButtonName), 5000, true).Click(false, 500, 1000); } // Setup custom layout with 1 subzones @@ -382,6 +383,11 @@ namespace UITests_FancyZones this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible ZoneBehaviourSettings(TestContext.TestName); + // Go back and forth to make sure settings applied + this.Find("Workspaces").Click(); + Task.Delay(200).Wait(); + this.Find("FancyZones").Click(); + this.Find(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000); this.Session.Attach(PowerToysModule.FancyZone); @@ -435,22 +441,26 @@ namespace UITests_FancyZones // Get the mouse color of the pixel when make dragged window private (string PixelInWindow, string TransPixel) GetPixelWhenMakeDraggedWindow() { - var dragElement = Find(By.Name("Non Client Input Sink Window")); + var windowRect = Session.GetMainWindowRect(); + int startX = windowRect.Left + 70; + int startY = windowRect.Top + 25; + int endX = startX + 100; + int endY = startY + 100; - // maximize the window to make sure get pixel color more accurate - dragElement.DoubleClick(); + Session.MoveMouseTo(startX, startY); - var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY); + // Session.PerformMouseAction(MouseActionType.LeftDoubleClick); Session.PressKey(Key.Shift); - dragElement.DragAndHold(offSet.Dx, offSet.Dy); - Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.MoveMouseTo(endX, endY); + Tuple pos = GetMousePosition(); string pixelInWindow = this.GetPixelColorString(pos.Item1, pos.Item2); Session.ReleaseKey(Key.Shift); - Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position + Task.Delay(1000).Wait(); string transPixel = this.GetPixelColorString(pos.Item1, pos.Item2); - dragElement.ReleaseDrag(); + Session.PerformMouseAction(MouseActionType.LeftUp); return (pixelInWindow, transPixel); } diff --git a/src/modules/fancyzones/FancyZones.UITests/LayoutApplyHotKeyTests.cs b/src/modules/fancyzones/FancyZones.UITests/LayoutApplyHotKeyTests.cs index d1712d3e2d..a145dde718 100644 --- a/src/modules/fancyzones/FancyZones.UITests/LayoutApplyHotKeyTests.cs +++ b/src/modules/fancyzones/FancyZones.UITests/LayoutApplyHotKeyTests.cs @@ -271,7 +271,7 @@ namespace UITests_FancyZones }; FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper)); - this.RestartScopeExe(); + RestartScopeExe("Hosts"); } [TestMethod("FancyZones.Settings.TestApplyHotKey")] @@ -598,10 +598,12 @@ namespace UITests_FancyZones this.TryReaction(); int tries = 24; Pull(tries, "down"); // Pull the setting page up to make sure the setting is visible - this.Find("Enable quick layout switch").Toggle(flag); + this.Find("FancyZonesQuickLayoutSwitch").Toggle(flag); - tries = 24; - Pull(tries, "up"); + // Go back and forth to make sure settings applied + this.Find("Workspaces").Click(); + Task.Delay(200).Wait(); + this.Find("FancyZones").Click(); } private void TryReaction() diff --git a/src/modules/fancyzones/FancyZones.UITests/OneZoneSwitchTests.cs b/src/modules/fancyzones/FancyZones.UITests/OneZoneSwitchTests.cs index 70d6935702..68989a4054 100644 --- a/src/modules/fancyzones/FancyZones.UITests/OneZoneSwitchTests.cs +++ b/src/modules/fancyzones/FancyZones.UITests/OneZoneSwitchTests.cs @@ -34,7 +34,7 @@ namespace UITests_FancyZones Session.KillAllProcessesByName("PowerToys.FancyZonesEditor"); AppZoneHistory.DeleteFile(); - this.RestartScopeExe(); + RestartScopeExe("Hosts"); FancyZonesEditorHelper.Files.Restore(); // Set a custom layout with 1 subzones and clear app zone history @@ -137,7 +137,7 @@ namespace UITests_FancyZones Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle(); - Assert.AreNotEqual(preWindow, activeWindowTitle); + Assert.AreEqual(postWindow, activeWindowTitle); Clean(); // close the windows } @@ -151,9 +151,23 @@ namespace UITests_FancyZones var rect = Session.GetMainWindowRect(); var (targetX, targetY) = ZoneSwitchHelper.GetScreenMargins(rect, 4); - var offSet = ZoneSwitchHelper.GetOffset(hostsView, targetX, targetY); - DragWithShift(hostsView, offSet); + // Snap first window (Hosts) to left zone using shift+drag with direct mouse movement + var hostsRect = hostsView.Rect ?? throw new InvalidOperationException("Failed to get hosts window rect"); + int hostsStartX = hostsRect.Left + 70; + int hostsStartY = hostsRect.Top + 25; + + // For a 2-column layout, left zone is at approximately 1/4 of screen width + int hostsEndX = rect.Left + (3 * (rect.Right - rect.Left) / 4); + int hostsEndY = rect.Top + ((rect.Bottom - rect.Top) / 2); + + Session.MoveMouseTo(hostsStartX, hostsStartY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.PressKey(Key.Shift); + Session.MoveMouseTo(hostsEndX, hostsEndY); + Session.PerformMouseAction(MouseActionType.LeftUp); + Session.ReleaseKey(Key.Shift); + Task.Delay(500).Wait(); // Wait for snap to complete string preWindow = ZoneSwitchHelper.GetActiveWindowTitle(); @@ -163,11 +177,26 @@ namespace UITests_FancyZones Pane settingsView = Find(By.Name("Non Client Input Sink Window")); settingsView.DoubleClick(); // maximize the window - DragWithShift(settingsView, offSet); + var windowRect = Session.GetMainWindowRect(); + var settingsRect = settingsView.Rect ?? throw new InvalidOperationException("Failed to get settings window rect"); + int settingsStartX = settingsRect.Left + 70; + int settingsStartY = settingsRect.Top + 25; + + // For a 2-column layout, right zone is at approximately 3/4 of screen width + int settingsEndX = windowRect.Left + (3 * (windowRect.Right - windowRect.Left) / 4); + int settingsEndY = windowRect.Top + ((windowRect.Bottom - windowRect.Top) / 2); + + Session.MoveMouseTo(settingsStartX, settingsStartY); + Session.PerformMouseAction(MouseActionType.LeftDown); + Session.PressKey(Key.Shift); + Session.MoveMouseTo(settingsEndX, settingsEndY); + Session.PerformMouseAction(MouseActionType.LeftUp); + Session.ReleaseKey(Key.Shift); + Task.Delay(500).Wait(); // Wait for snap to complete string appZoneHistoryJson = AppZoneHistory.GetData(); - string? zoneIndexOfFileWindow = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Hosts.exe", appZoneHistoryJson); // explorer.exe + string? zoneIndexOfFileWindow = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Hosts.exe", appZoneHistoryJson); string? zoneIndexOfPowertoys = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Settings.exe", appZoneHistoryJson); // check the AppZoneHistory layout is set and in the same zone @@ -176,16 +205,6 @@ namespace UITests_FancyZones return (preWindow, powertoysWindowName); } - private void DragWithShift(Pane settingsView, (int Dx, int Dy) offSet) - { - Session.PressKey(Key.Shift); - settingsView.DragAndHold(offSet.Dx, offSet.Dy); - Task.Delay(1000).Wait(); // Wait for drag to start (optional) - settingsView.ReleaseDrag(); - Task.Delay(1000).Wait(); // Wait after drag (optional) - Session.ReleaseKey(Key.Shift); - } - private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper { CustomLayouts = new List @@ -253,11 +272,14 @@ namespace UITests_FancyZones this.Scroll(9, "Down"); // Pull the setting page up to make sure the setting is visible bool switchWindowEnable = TestContext.TestName == "TestSwitchShortCutDisable" ? false : true; - this.Find("Switch between windows in the current zone").Toggle(switchWindowEnable); + this.Find("FancyZonesWindowSwitchingToggle").Toggle(switchWindowEnable); - Task.Delay(500).Wait(); // Wait for the setting to be applied - this.Scroll(9, "Up"); // Pull the setting page down to make sure the setting is visible - this.Find