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