mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 03:07:56 +01:00
- [ ] Closes: #42249 Contribution to https://github.com/microsoft/PowerToys/issues/40701
This commit is contained in:
4
.github/actions/spell-check/allow/code.txt
vendored
4
.github/actions/spell-check/allow/code.txt
vendored
@@ -335,3 +335,7 @@ azp
|
|||||||
feedbackhub
|
feedbackhub
|
||||||
needinfo
|
needinfo
|
||||||
reportbug
|
reportbug
|
||||||
|
|
||||||
|
#ffmpeg
|
||||||
|
crf
|
||||||
|
nostdin
|
||||||
|
|||||||
399
src/common/UITestAutomation/ScreenRecording.cs
Normal file
399
src/common/UITestAutomation/ScreenRecording.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods for recording the screen during UI tests.
|
||||||
|
/// Requires FFmpeg to be installed and available in PATH.
|
||||||
|
/// </summary>
|
||||||
|
internal class ScreenRecording : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string outputDirectory;
|
||||||
|
private readonly string framesDirectory;
|
||||||
|
private readonly string outputFilePath;
|
||||||
|
private readonly List<string> 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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ScreenRecording"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="outputDirectory">Directory where the recording will be saved.</param>
|
||||||
|
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<string>();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether screen recording is available (FFmpeg found).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAvailable => ffmpegPath != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts recording the screen.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops recording and encodes video.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Records frames from the screen.
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures a single frame.
|
||||||
|
/// </summary>
|
||||||
|
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<ScreenCapture.CURSORINFO>();
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes captured frames to video using ffmpeg.
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds ffmpeg executable.
|
||||||
|
/// </summary>
|
||||||
|
private static string? FindFfmpeg()
|
||||||
|
{
|
||||||
|
// Check if ffmpeg is in PATH
|
||||||
|
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty<string>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the recorded video file.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputFilePath => outputFilePath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the directory containing recordings.
|
||||||
|
/// </summary>
|
||||||
|
public string OutputDirectory => outputDirectory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans up resources.
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (isRecording)
|
||||||
|
{
|
||||||
|
StopRecordingAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
|
recordingLock.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -130,9 +130,13 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appPath">The path to the application executable.</param>
|
/// <param name="appPath">The path to the application executable.</param>
|
||||||
/// <param name="args">Optional command line arguments to pass to the application.</param>
|
/// <param name="args">Optional command line arguments to pass to the application.</param>
|
||||||
public void StartExe(string appPath, string[]? args = null)
|
public void StartExe(string appPath, string[]? args = null, string? enableModules = null)
|
||||||
{
|
{
|
||||||
var opts = new AppiumOptions();
|
var opts = new AppiumOptions();
|
||||||
|
if (!string.IsNullOrEmpty(enableModules))
|
||||||
|
{
|
||||||
|
opts.AddAdditionalCapability("enableModules", enableModules);
|
||||||
|
}
|
||||||
|
|
||||||
if (scope == PowerToysModule.PowerToysSettings)
|
if (scope == PowerToysModule.PowerToysSettings)
|
||||||
{
|
{
|
||||||
@@ -169,27 +173,66 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
|
|
||||||
private void TryLaunchPowerToysSettings(AppiumOptions opts)
|
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,
|
var runnerProcessInfo = new ProcessStartInfo
|
||||||
Verb = "runas",
|
{
|
||||||
Arguments = "--open-settings",
|
FileName = locationPath + runnerPath,
|
||||||
};
|
Verb = "runas",
|
||||||
|
Arguments = "--open-settings",
|
||||||
|
};
|
||||||
|
|
||||||
ExitExe(runnerProcessInfo.FileName);
|
ExitExe(runnerProcessInfo.FileName);
|
||||||
runner = Process.Start(runnerProcessInfo);
|
|
||||||
|
|
||||||
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
|
runner = Process.Start(runnerProcessInfo);
|
||||||
ExitExeByName("Microsoft.CmdPal.UI");
|
|
||||||
}
|
if (WaitForWindowAndSetCapability(opts, "PowerToys Settings", delayMs, maxRetries))
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
// Exit CmdPal UI before launching new process if use installer for test
|
||||||
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
|
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)
|
private void TryLaunchCommandPalette(AppiumOptions opts)
|
||||||
@@ -211,7 +254,10 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
var process = Process.Start(processStartInfo);
|
var process = Process.Start(processStartInfo);
|
||||||
process?.WaitForExit();
|
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)
|
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++)
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||||
{
|
{
|
||||||
@@ -230,18 +276,16 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
{
|
{
|
||||||
var hexHwnd = window[0].HWnd.ToString("x");
|
var hexHwnd = window[0].HWnd.ToString("x");
|
||||||
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
|
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attempt < maxRetries)
|
if (attempt < maxRetries)
|
||||||
{
|
{
|
||||||
Thread.Sleep(delayMs);
|
Thread.Sleep(delayMs);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -292,17 +336,17 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Handle exceptions if needed
|
// Handle exceptions if needed
|
||||||
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
|
Console.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restarts now exe and takes control of it.
|
/// Restarts now exe and takes control of it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RestartScopeExe()
|
public void RestartScopeExe(string? enableModules = null)
|
||||||
{
|
{
|
||||||
ExitScopeExe();
|
ExitScopeExe();
|
||||||
StartExe(locationPath + sessionPath, this.commandLineArgs);
|
StartExe(locationPath + sessionPath, commandLineArgs, enableModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WindowsDriver<WindowsElement> GetRoot()
|
public WindowsDriver<WindowsElement> GetRoot()
|
||||||
@@ -327,5 +371,31 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
this.ExitExe(winAppDriverProcessInfo.FileName);
|
this.ExitExe(winAppDriverProcessInfo.FileName);
|
||||||
SessionHelper.appDriver = Process.Start(winAppDriverProcessInfo);
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures global PowerToys settings to enable only specified modules and disable all others.
|
/// Configures global PowerToys settings to enable only specified modules and disable all others.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="modulesToEnable">Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled.</param>
|
/// <param name="modulesToEnable">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.</param>
|
||||||
/// <exception cref="ArgumentNullException">Thrown when modulesToEnable is null.</exception>
|
|
||||||
/// <exception cref="InvalidOperationException">Thrown when settings file operations fail.</exception>
|
/// <exception cref="InvalidOperationException">Thrown when settings file operations fail.</exception>
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
|
[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")]
|
[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<string>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
|
|
||||||
public string? ScreenshotDirectory { get; set; }
|
public string? ScreenshotDirectory { get; set; }
|
||||||
|
|
||||||
|
public string? RecordingDirectory { get; set; }
|
||||||
|
|
||||||
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
|
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
|
||||||
|
|
||||||
private readonly PowerToysModule scope;
|
private readonly PowerToysModule scope;
|
||||||
@@ -36,6 +38,7 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
private readonly string[]? commandLineArgs;
|
private readonly string[]? commandLineArgs;
|
||||||
private SessionHelper? sessionHelper;
|
private SessionHelper? sessionHelper;
|
||||||
private System.Threading.Timer? screenshotTimer;
|
private System.Threading.Timer? screenshotTimer;
|
||||||
|
private ScreenRecording? screenRecording;
|
||||||
|
|
||||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
|
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
|
||||||
{
|
{
|
||||||
@@ -65,12 +68,35 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
CloseOtherApplications();
|
CloseOtherApplications();
|
||||||
if (IsInPipeline)
|
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);
|
Directory.CreateDirectory(ScreenshotDirectory);
|
||||||
|
|
||||||
|
RecordingDirectory = Path.Combine(baseDirectory, "UITestRecordings_" + Guid.NewGuid().ToString());
|
||||||
|
Directory.CreateDirectory(RecordingDirectory);
|
||||||
|
|
||||||
// Take screenshot every 1 second
|
// Take screenshot every 1 second
|
||||||
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, ScreenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
|
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
|
// Escape Popups before starting
|
||||||
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
||||||
}
|
}
|
||||||
@@ -88,15 +114,36 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
if (IsInPipeline)
|
if (IsInPipeline)
|
||||||
{
|
{
|
||||||
screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
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
|
if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed
|
||||||
or UnitTestOutcome.Error
|
or UnitTestOutcome.Error
|
||||||
or UnitTestOutcome.Unknown)
|
or UnitTestOutcome.Unknown)
|
||||||
{
|
{
|
||||||
Task.Delay(1000).Wait();
|
Task.Delay(1000).Wait();
|
||||||
AddScreenShotsToTestResultsDirectory();
|
AddScreenShotsToTestResultsDirectory();
|
||||||
|
AddRecordingsToTestResultsDirectory();
|
||||||
AddLogFilesToTestResultsDirectory();
|
AddLogFilesToTestResultsDirectory();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clean up recording if test passed
|
||||||
|
CleanupRecordingDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Session.Cleanup();
|
this.Session.Cleanup();
|
||||||
@@ -106,6 +153,7 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
screenshotTimer?.Dispose();
|
screenshotTimer?.Dispose();
|
||||||
|
screenRecording?.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,6 +648,47 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds screen recordings to test results directory when test fails.
|
||||||
|
/// </summary>
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cleans up recording directory when test passes.
|
||||||
|
/// </summary>
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copies PowerToys log files to test results directory when test fails.
|
/// Copies PowerToys log files to test results directory when test fails.
|
||||||
/// Renames files to include the directory structure after \PowerToys.
|
/// Renames files to include the directory structure after \PowerToys.
|
||||||
@@ -689,11 +778,11 @@ namespace Microsoft.PowerToys.UITest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restart scope exe.
|
/// Restart scope exe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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);
|
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
|
||||||
return;
|
return Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -617,6 +617,8 @@ namespace MouseUtils.UITests
|
|||||||
|
|
||||||
private void LaunchFromSetting(bool reload = false, bool launchAsAdmin = false)
|
private void LaunchFromSetting(bool reload = false, bool launchAsAdmin = false)
|
||||||
{
|
{
|
||||||
|
Session = RestartScopeExe("FindMyMouse,MouseHighlighter,MouseJump,MousePointerCrosshairs,CursorWrap");
|
||||||
|
|
||||||
// this.Session.Attach(PowerToysModule.PowerToysSettings);
|
// this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ public sealed partial class SettingsWindow : WindowEx,
|
|||||||
"Extensions" => typeof(ExtensionsPage),
|
"Extensions" => typeof(ExtensionsPage),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pageType is not null)
|
if (pageType is not null)
|
||||||
{
|
{
|
||||||
NavFrame.Navigate(pageType);
|
NavFrame.Navigate(pageType);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using FancyZonesEditor.Models;
|
using FancyZonesEditor.Models;
|
||||||
@@ -49,19 +50,16 @@ namespace UITests_FancyZones
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
// ClearOpenWindows
|
Session.KillAllProcessesByName("PowerToys");
|
||||||
ClearOpenWindows();
|
ClearOpenWindows();
|
||||||
|
|
||||||
// kill all processes related to FancyZones Editor to ensure a clean state
|
|
||||||
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
|
|
||||||
|
|
||||||
AppZoneHistory.DeleteFile();
|
AppZoneHistory.DeleteFile();
|
||||||
this.RestartScopeExe();
|
|
||||||
FancyZonesEditorHelper.Files.Restore();
|
FancyZonesEditorHelper.Files.Restore();
|
||||||
|
|
||||||
// Set a custom layout with 1 subzones and clear app zone history
|
|
||||||
SetupCustomLayouts();
|
SetupCustomLayouts();
|
||||||
|
|
||||||
|
RestartScopeExe("Hosts");
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
// Get the current mouse button setting
|
// Get the current mouse button setting
|
||||||
nonPrimaryMouseButton = SystemInformation.MouseButtonsSwapped ? "Left" : "Right";
|
nonPrimaryMouseButton = SystemInformation.MouseButtonsSwapped ? "Left" : "Right";
|
||||||
|
|
||||||
@@ -72,99 +70,6 @@ namespace UITests_FancyZones
|
|||||||
LaunchFancyZones();
|
LaunchFancyZones();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings
|
|
||||||
/// <list type="bullet">
|
|
||||||
/// <item>
|
|
||||||
/// <description>Verifies that holding Shift while dragging shows all zones as expected.</description>
|
|
||||||
/// </item>
|
|
||||||
/// </list>
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")]
|
|
||||||
[TestCategory("FancyZones_Dragging #1")]
|
|
||||||
public void TestShowZonesOnShiftDuringDrag()
|
|
||||||
{
|
|
||||||
string testCaseName = nameof(TestShowZonesOnShiftDuringDrag);
|
|
||||||
Pane dragElement = Find<Pane>(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings
|
|
||||||
/// <list type="bullet">
|
|
||||||
/// <item>
|
|
||||||
/// <description>Verifies that dragging activates zones as expected.</description>
|
|
||||||
/// </item>
|
|
||||||
/// </list>
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")]
|
|
||||||
[TestCategory("FancyZones_Dragging #2")]
|
|
||||||
public void TestShowZonesOnDragDuringShift()
|
|
||||||
{
|
|
||||||
string testCaseName = nameof(TestShowZonesOnDragDuringShift);
|
|
||||||
|
|
||||||
var dragElement = Find<Pane>(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test toggling zones using a non-primary mouse click during window dragging.
|
/// Test toggling zones using a non-primary mouse click during window dragging.
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
@@ -178,14 +83,19 @@ namespace UITests_FancyZones
|
|||||||
public void TestToggleZonesWithNonPrimaryMouseClick()
|
public void TestToggleZonesWithNonPrimaryMouseClick()
|
||||||
{
|
{
|
||||||
string testCaseName = nameof(TestToggleZonesWithNonPrimaryMouseClick);
|
string testCaseName = nameof(TestToggleZonesWithNonPrimaryMouseClick);
|
||||||
var dragElement = Find<Pane>(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(
|
var (initialColor, withMouseColor) = RunDragInteractions(
|
||||||
preAction: () =>
|
preAction: () =>
|
||||||
{
|
{
|
||||||
// activate zone
|
Session.MoveMouseTo(startX, startY);
|
||||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
Session.PerformMouseAction(MouseActionType.LeftDown);
|
||||||
|
Session.MoveMouseTo(endX, endY);
|
||||||
},
|
},
|
||||||
postAction: () =>
|
postAction: () =>
|
||||||
{
|
{
|
||||||
@@ -195,7 +105,7 @@ namespace UITests_FancyZones
|
|||||||
},
|
},
|
||||||
releaseAction: () =>
|
releaseAction: () =>
|
||||||
{
|
{
|
||||||
dragElement.ReleaseDrag();
|
Session.PerformMouseAction(MouseActionType.LeftUp);
|
||||||
},
|
},
|
||||||
testCaseName: testCaseName);
|
testCaseName: testCaseName);
|
||||||
|
|
||||||
@@ -204,8 +114,6 @@ namespace UITests_FancyZones
|
|||||||
|
|
||||||
// check the zone color is activated
|
// check the zone color is activated
|
||||||
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
|
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
|
||||||
|
|
||||||
Clean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -221,32 +129,35 @@ namespace UITests_FancyZones
|
|||||||
public void TestShowZonesWhenShiftAndMouseOff()
|
public void TestShowZonesWhenShiftAndMouseOff()
|
||||||
{
|
{
|
||||||
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOff);
|
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOff);
|
||||||
Pane dragElement = Find<Pane>(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(
|
var (initialColor, withShiftColor) = RunDragInteractions(
|
||||||
preAction: () =>
|
preAction: () =>
|
||||||
{
|
{
|
||||||
// activate zone
|
Session.MoveMouseTo(startX, startY);
|
||||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
Session.PerformMouseAction(MouseActionType.LeftDown);
|
||||||
|
Session.MoveMouseTo(endX, endY);
|
||||||
},
|
},
|
||||||
postAction: () =>
|
postAction: () =>
|
||||||
{
|
{
|
||||||
// press Shift Key to deactivate zones
|
// press Shift Key to deactivate zones
|
||||||
Session.PressKey(Key.Shift);
|
Session.PressKey(Key.Shift);
|
||||||
Task.Delay(500).Wait();
|
Task.Delay(1000).Wait();
|
||||||
},
|
},
|
||||||
releaseAction: () =>
|
releaseAction: () =>
|
||||||
{
|
{
|
||||||
dragElement.ReleaseDrag();
|
Session.PerformMouseAction(MouseActionType.LeftUp);
|
||||||
Session.ReleaseKey(Key.Shift);
|
Session.ReleaseKey(Key.Shift);
|
||||||
},
|
},
|
||||||
testCaseName: testCaseName);
|
testCaseName: testCaseName);
|
||||||
|
|
||||||
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
|
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
|
||||||
Assert.AreNotEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone deactivation failed.");
|
Assert.AreNotEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone deactivation failed.");
|
||||||
|
|
||||||
Clean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -263,12 +174,17 @@ namespace UITests_FancyZones
|
|||||||
{
|
{
|
||||||
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOn);
|
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOn);
|
||||||
|
|
||||||
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
var windowRect = Session.GetMainWindowRect();
|
||||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
int startX = windowRect.Left + 70;
|
||||||
|
int startY = windowRect.Top + 25;
|
||||||
|
int endX = startX + 300;
|
||||||
|
int endY = startY + 300;
|
||||||
var (initialColor, withShiftColor) = RunDragInteractions(
|
var (initialColor, withShiftColor) = RunDragInteractions(
|
||||||
preAction: () =>
|
preAction: () =>
|
||||||
{
|
{
|
||||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
Session.MoveMouseTo(startX, startY);
|
||||||
|
Session.PerformMouseAction(MouseActionType.LeftDown);
|
||||||
|
Session.MoveMouseTo(endX, endY);
|
||||||
},
|
},
|
||||||
postAction: () =>
|
postAction: () =>
|
||||||
{
|
{
|
||||||
@@ -279,7 +195,7 @@ namespace UITests_FancyZones
|
|||||||
},
|
},
|
||||||
testCaseName: testCaseName);
|
testCaseName: testCaseName);
|
||||||
|
|
||||||
Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] show zone failed.");
|
Assert.AreEqual(highlightColor, withShiftColor, $"[{testCaseName}] show zone failed.");
|
||||||
|
|
||||||
Session.PerformMouseAction(
|
Session.PerformMouseAction(
|
||||||
nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick);
|
nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick);
|
||||||
@@ -288,9 +204,7 @@ namespace UITests_FancyZones
|
|||||||
Assert.AreEqual(initialColor, zoneColorWithMouse, $"[{nameof(TestShowZonesWhenShiftAndMouseOff)}] Zone deactivate failed.");
|
Assert.AreEqual(initialColor, zoneColorWithMouse, $"[{nameof(TestShowZonesWhenShiftAndMouseOff)}] Zone deactivate failed.");
|
||||||
|
|
||||||
Session.ReleaseKey(Key.Shift);
|
Session.ReleaseKey(Key.Shift);
|
||||||
dragElement.ReleaseDrag();
|
Session.PerformMouseAction(MouseActionType.LeftUp);
|
||||||
|
|
||||||
Clean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -307,8 +221,6 @@ namespace UITests_FancyZones
|
|||||||
{
|
{
|
||||||
var pixel = GetPixelWhenMakeDraggedWindow();
|
var pixel = GetPixelWhenMakeDraggedWindow();
|
||||||
Assert.AreNotEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOn)}] Window transparency failed.");
|
Assert.AreNotEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOn)}] Window transparency failed.");
|
||||||
|
|
||||||
Clean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -325,14 +237,103 @@ namespace UITests_FancyZones
|
|||||||
{
|
{
|
||||||
var pixel = GetPixelWhenMakeDraggedWindow();
|
var pixel = GetPixelWhenMakeDraggedWindow();
|
||||||
Assert.AreEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOff)}] Window without transparency failed.");
|
Assert.AreEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOff)}] Window without transparency failed.");
|
||||||
|
|
||||||
Clean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Clean()
|
/// <summary>
|
||||||
|
/// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// <description>Verifies that holding Shift while dragging shows all zones as expected.</description>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")]
|
||||||
|
[TestCategory("FancyZones_Dragging #1")]
|
||||||
|
public void TestShowZonesOnShiftDuringDrag()
|
||||||
{
|
{
|
||||||
// clean app zone history file
|
string testCaseName = nameof(TestShowZonesOnShiftDuringDrag);
|
||||||
AppZoneHistory.DeleteFile();
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>
|
||||||
|
/// <description>Verifies that dragging activates zones as expected.</description>
|
||||||
|
/// </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
[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
|
// 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";
|
desktopButtonName = "Show Desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Find<Microsoft.PowerToys.UITest.Button>(By.Name(desktopButtonName), 5000, true).Click(false, 500, 2000);
|
this.Find<Microsoft.PowerToys.UITest.Button>(By.Name(desktopButtonName), 5000, true).Click(false, 500, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup custom layout with 1 subzones
|
// 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
|
this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible
|
||||||
ZoneBehaviourSettings(TestContext.TestName);
|
ZoneBehaviourSettings(TestContext.TestName);
|
||||||
|
|
||||||
|
// Go back and forth to make sure settings applied
|
||||||
|
this.Find<NavigationViewItem>("Workspaces").Click();
|
||||||
|
Task.Delay(200).Wait();
|
||||||
|
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||||
|
|
||||||
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000);
|
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000);
|
||||||
this.Session.Attach(PowerToysModule.FancyZone);
|
this.Session.Attach(PowerToysModule.FancyZone);
|
||||||
|
|
||||||
@@ -435,22 +441,26 @@ namespace UITests_FancyZones
|
|||||||
// Get the mouse color of the pixel when make dragged window
|
// Get the mouse color of the pixel when make dragged window
|
||||||
private (string PixelInWindow, string TransPixel) GetPixelWhenMakeDraggedWindow()
|
private (string PixelInWindow, string TransPixel) GetPixelWhenMakeDraggedWindow()
|
||||||
{
|
{
|
||||||
var dragElement = Find<Pane>(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
|
Session.MoveMouseTo(startX, startY);
|
||||||
dragElement.DoubleClick();
|
|
||||||
|
|
||||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
// Session.PerformMouseAction(MouseActionType.LeftDoubleClick);
|
||||||
Session.PressKey(Key.Shift);
|
Session.PressKey(Key.Shift);
|
||||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
Session.PerformMouseAction(MouseActionType.LeftDown);
|
||||||
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position
|
Session.MoveMouseTo(endX, endY);
|
||||||
|
|
||||||
Tuple<int, int> pos = GetMousePosition();
|
Tuple<int, int> pos = GetMousePosition();
|
||||||
string pixelInWindow = this.GetPixelColorString(pos.Item1, pos.Item2);
|
string pixelInWindow = this.GetPixelColorString(pos.Item1, pos.Item2);
|
||||||
Session.ReleaseKey(Key.Shift);
|
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);
|
string transPixel = this.GetPixelColorString(pos.Item1, pos.Item2);
|
||||||
dragElement.ReleaseDrag();
|
|
||||||
|
|
||||||
|
Session.PerformMouseAction(MouseActionType.LeftUp);
|
||||||
return (pixelInWindow, transPixel);
|
return (pixelInWindow, transPixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ namespace UITests_FancyZones
|
|||||||
};
|
};
|
||||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||||
|
|
||||||
this.RestartScopeExe();
|
RestartScopeExe("Hosts");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod("FancyZones.Settings.TestApplyHotKey")]
|
[TestMethod("FancyZones.Settings.TestApplyHotKey")]
|
||||||
@@ -598,10 +598,12 @@ namespace UITests_FancyZones
|
|||||||
this.TryReaction();
|
this.TryReaction();
|
||||||
int tries = 24;
|
int tries = 24;
|
||||||
Pull(tries, "down"); // Pull the setting page up to make sure the setting is visible
|
Pull(tries, "down"); // Pull the setting page up to make sure the setting is visible
|
||||||
this.Find<ToggleSwitch>("Enable quick layout switch").Toggle(flag);
|
this.Find<ToggleSwitch>("FancyZonesQuickLayoutSwitch").Toggle(flag);
|
||||||
|
|
||||||
tries = 24;
|
// Go back and forth to make sure settings applied
|
||||||
Pull(tries, "up");
|
this.Find<NavigationViewItem>("Workspaces").Click();
|
||||||
|
Task.Delay(200).Wait();
|
||||||
|
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryReaction()
|
private void TryReaction()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace UITests_FancyZones
|
|||||||
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
|
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
|
||||||
AppZoneHistory.DeleteFile();
|
AppZoneHistory.DeleteFile();
|
||||||
|
|
||||||
this.RestartScopeExe();
|
RestartScopeExe("Hosts");
|
||||||
FancyZonesEditorHelper.Files.Restore();
|
FancyZonesEditorHelper.Files.Restore();
|
||||||
|
|
||||||
// Set a custom layout with 1 subzones and clear app zone history
|
// 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
|
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||||
|
|
||||||
activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||||
Assert.AreNotEqual(preWindow, activeWindowTitle);
|
Assert.AreEqual(postWindow, activeWindowTitle);
|
||||||
|
|
||||||
Clean(); // close the windows
|
Clean(); // close the windows
|
||||||
}
|
}
|
||||||
@@ -151,9 +151,23 @@ namespace UITests_FancyZones
|
|||||||
|
|
||||||
var rect = Session.GetMainWindowRect();
|
var rect = Session.GetMainWindowRect();
|
||||||
var (targetX, targetY) = ZoneSwitchHelper.GetScreenMargins(rect, 4);
|
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();
|
string preWindow = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||||
|
|
||||||
@@ -163,11 +177,26 @@ namespace UITests_FancyZones
|
|||||||
Pane settingsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
Pane settingsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||||
settingsView.DoubleClick(); // maximize the 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 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);
|
string? zoneIndexOfPowertoys = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Settings.exe", appZoneHistoryJson);
|
||||||
|
|
||||||
// check the AppZoneHistory layout is set and in the same zone
|
// check the AppZoneHistory layout is set and in the same zone
|
||||||
@@ -176,16 +205,6 @@ namespace UITests_FancyZones
|
|||||||
return (preWindow, powertoysWindowName);
|
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
|
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
|
||||||
{
|
{
|
||||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||||
@@ -253,11 +272,14 @@ namespace UITests_FancyZones
|
|||||||
this.Scroll(9, "Down"); // Pull the setting page up to make sure the setting is visible
|
this.Scroll(9, "Down"); // Pull the setting page up to make sure the setting is visible
|
||||||
bool switchWindowEnable = TestContext.TestName == "TestSwitchShortCutDisable" ? false : true;
|
bool switchWindowEnable = TestContext.TestName == "TestSwitchShortCutDisable" ? false : true;
|
||||||
|
|
||||||
this.Find<ToggleSwitch>("Switch between windows in the current zone").Toggle(switchWindowEnable);
|
this.Find<ToggleSwitch>("FancyZonesWindowSwitchingToggle").Toggle(switchWindowEnable);
|
||||||
|
|
||||||
Task.Delay(500).Wait(); // Wait for the setting to be applied
|
// Go back and forth to make sure settings applied
|
||||||
this.Scroll(9, "Up"); // Pull the setting page down to make sure the setting is visible
|
this.Find<NavigationViewItem>("Workspaces").Click();
|
||||||
this.Find<Button>("Launch layout editor").Click(false, 500, 5000);
|
Task.Delay(200).Wait();
|
||||||
|
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||||
|
|
||||||
|
this.Find<Button>("Open layout editor").Click(false, 500, 5000);
|
||||||
this.Session.Attach(PowerToysModule.FancyZone);
|
this.Session.Attach(PowerToysModule.FancyZone);
|
||||||
|
|
||||||
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
|
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
|
||||||
@@ -273,7 +295,7 @@ namespace UITests_FancyZones
|
|||||||
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
|
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
|
||||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||||
SetupCustomLayouts();
|
SetupCustomLayouts();
|
||||||
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
|
this.Find<Microsoft.PowerToys.UITest.Button>("Open layout editor").Click(false, 5000, 5000);
|
||||||
this.Session.Attach(PowerToysModule.FancyZone);
|
this.Session.Attach(PowerToysModule.FancyZone);
|
||||||
|
|
||||||
// customLayoutData = FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.GetData();
|
// customLayoutData = FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.GetData();
|
||||||
@@ -301,11 +323,11 @@ namespace UITests_FancyZones
|
|||||||
Task.Delay(1000).Wait();
|
Task.Delay(1000).Wait();
|
||||||
|
|
||||||
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
|
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
|
||||||
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
|
this.Find<ToggleSwitch>("Open as administrator").Toggle(launchAsAdmin);
|
||||||
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
|
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
|
||||||
|
|
||||||
// launch Hosts File Editor
|
// launch Hosts File Editor
|
||||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
this.Find<Button>("Open Hosts File Editor").Click();
|
||||||
|
|
||||||
Task.Delay(5000).Wait();
|
Task.Delay(5000).Wait();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,9 +124,6 @@ public class PeekFilePreviewTests : UITestBase
|
|||||||
settings["properties"] = properties;
|
settings["properties"] = properties;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disable all modules except Peek in global settings
|
|
||||||
SettingsConfigHelper.ConfigureGlobalModuleSettings("Peek");
|
|
||||||
|
|
||||||
Debug.WriteLine("Successfully updated all settings - Peek shortcut configured and all modules except Peek disabled");
|
Debug.WriteLine("Successfully updated all settings - Peek shortcut configured and all modules except Peek disabled");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -138,6 +135,7 @@ public class PeekFilePreviewTests : UITestBase
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
|
RestartScopeExe("Peek");
|
||||||
Session.CloseMainWindow();
|
Session.CloseMainWindow();
|
||||||
SendKeys(Key.Win, Key.M);
|
SendKeys(Key.Win, Key.M);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,7 +192,10 @@
|
|||||||
x:Uid="FancyZones_WindowSwitching_GroupSettings"
|
x:Uid="FancyZones_WindowSwitching_GroupSettings"
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
IsExpanded="True">
|
IsExpanded="True">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.WindowSwitching, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="FancyZonesWindowSwitchingToggle"
|
||||||
|
IsOn="{x:Bind ViewModel.WindowSwitching, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsExpander.Items>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
<!-- HACK: For some weird reason, a Shortcut Control is not working correctly if it's the first item in the expander, so we add an invisible card as the first one. -->
|
<!-- HACK: For some weird reason, a Shortcut Control is not working correctly if it's the first item in the expander, so we add an invisible card as the first one. -->
|
||||||
<tkcontrols:SettingsCard Name="FancyZonesWindowSwitchingPlaceholder" Visibility="Collapsed" />
|
<tkcontrols:SettingsCard Name="FancyZonesWindowSwitchingPlaceholder" Visibility="Collapsed" />
|
||||||
@@ -259,7 +262,10 @@
|
|||||||
Name="FancyZonesQuickLayoutSwitch"
|
Name="FancyZonesQuickLayoutSwitch"
|
||||||
x:Uid="FancyZones_QuickLayoutSwitch"
|
x:Uid="FancyZones_QuickLayoutSwitch"
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.QuickLayoutSwitch, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="FancyZonesQuickLayoutSwitch"
|
||||||
|
IsOn="{x:Bind ViewModel.QuickLayoutSwitch, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsExpander.Items>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsCard
|
||||||
Name="FancyZonesFlashZonesOnQuickSwitch"
|
Name="FancyZonesFlashZonesOnQuickSwitch"
|
||||||
|
|||||||
Reference in New Issue
Block a user