[UI tests] Add full UI test coverage for Peek based on release checklist (#40734)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Add full UI test coverage for Peek based on release checklist ### Peek * Open different files to check that they're shown properly - [x] Image - [x] Text or dev file - [x] Markdown file - [ ] PDF - [x] Archive files (.zip, .tar, .rar) - [x] Any other not mentioned file (.exe for example) to verify the unsupported file view is shown * Pinning/unpinning - [x] Pin the window, switch between images of different size, verify the window stays at the same place and the same size. - [x] Pin the window, close and reopen Peek, verify the new window is opened at the same place and the same size as before. - [x] Unpin the window, switch to a different file, verify the window is moved to the default place. - [x] Unpin the window, close and reopen Peek, verify the new window is opened on the default place. * Open with a default program - [x] By clicking a button. - [x] By pressing enter. - [x] Switch between files in the folder using `LeftArrow` and `RightArrow`, verify you can switch between all files in the folder. - [x] Open multiple files, verify you can switch only between selected files. <img width="519" height="266" alt="image" src="https://github.com/user-attachments/assets/f27c555d-9939-476f-9ecc-50d598285aef" /> <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] **Closes:** #[40676](https://github.com/microsoft/PowerToys/issues/40676) - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
44
src/modules/peek/Peek.UITests/Peek.UITests.csproj
Normal file
@@ -0,0 +1,44 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>PowerToys.Peek.UITests</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\Peek.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_2_arm64.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_4_arm64.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_5_arm64.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_6_arm64.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_7_arm64.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_8_arm64.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_2_x64Win11.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_4_x64Win11.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_5_x64Win11.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_6_x64Win11.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_7_x64Win11.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_8_x64Win11.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_2_x64Win10.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_4_x64Win10.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_5_x64Win10.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_6_x64Win10.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_7_x64Win10.png" />
|
||||
<EmbeddedResource Include="Baseline\PeekFilePreviewTests_TestSingleFilePreview_8_x64Win10.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="TestAssets\**\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
865
src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs
Normal file
@@ -0,0 +1,865 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Peek.UITests;
|
||||
|
||||
[TestClass]
|
||||
public class PeekFilePreviewTests : UITestBase
|
||||
{
|
||||
// Timeout constants for better maintainability
|
||||
private const int ExplorerOpenTimeoutSeconds = 15;
|
||||
private const int PeekWindowTimeoutSeconds = 15;
|
||||
private const int ExplorerLoadDelayMs = 3000;
|
||||
private const int ExplorerCheckIntervalMs = 1000;
|
||||
private const int PeekCheckIntervalMs = 1000;
|
||||
private const int PeekInitializeDelayMs = 3000;
|
||||
private const int MaxRetryAttempts = 3;
|
||||
private const int RetryDelayMs = 3000;
|
||||
private const int PinActionDelayMs = 500;
|
||||
|
||||
public PeekFilePreviewTests()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Small_Vertical)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
Session.CloseMainWindow();
|
||||
SendKeys(Key.Win, Key.M);
|
||||
}
|
||||
|
||||
[TestMethod("Peek.FilePreview.Folder")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekFolderFilePreview()
|
||||
{
|
||||
string folderFullPath = Path.GetFullPath(@".\TestAssets");
|
||||
|
||||
var peekWindow = OpenPeekWindow(folderFullPath);
|
||||
|
||||
Assert.IsNotNull(peekWindow);
|
||||
|
||||
Assert.IsNotNull(peekWindow.Find<TextBlock>("File Type: File folder", 500), "Folder preview should be loaded successfully");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test JPEG image preview
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FilePreview.JPEGImage")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekJPEGImagePreview()
|
||||
{
|
||||
string imagePath = Path.GetFullPath(@".\TestAssets\2.jpg");
|
||||
TestSingleFilePreview(imagePath, "2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PDF document preview
|
||||
/// ToDo: need to open settings to enable PDF preview in Peek
|
||||
/// </summary>
|
||||
// [TestMethod("Peek.FilePreview.PDFDocument")]
|
||||
// [TestCategory("Preview files")]
|
||||
// public void PeekPDFDocumentPreview()
|
||||
// {
|
||||
// string pdfPath = Path.GetFullPath(@".\TestAssets\3.pdf");
|
||||
// TestSingleFilePreview(pdfPath, "3", 10000);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Test QOI image preview
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FilePreview.QOIImage")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekQOIImagePreview()
|
||||
{
|
||||
string qoiPath = Path.GetFullPath(@".\TestAssets\4.qoi");
|
||||
TestSingleFilePreview(qoiPath, "4");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test C++ source code preview
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FilePreview.CPPSourceCode")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekCPPSourceCodePreview()
|
||||
{
|
||||
string cppPath = Path.GetFullPath(@".\TestAssets\5.cpp");
|
||||
TestSingleFilePreview(cppPath, "5");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Markdown document preview
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FilePreview.MarkdownDocument")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekMarkdownDocumentPreview()
|
||||
{
|
||||
string markdownPath = Path.GetFullPath(@".\TestAssets\6.md");
|
||||
TestSingleFilePreview(markdownPath, "6");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test ZIP archive preview
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FilePreview.ZIPArchive")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekZIPArchivePreview()
|
||||
{
|
||||
string zipPath = Path.GetFullPath(@".\TestAssets\7.zip");
|
||||
TestSingleFilePreview(zipPath, "7");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test PNG image preview
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FilePreview.PNGImage")]
|
||||
[TestCategory("Preview files")]
|
||||
public void PeekPNGImagePreview()
|
||||
{
|
||||
string pngPath = Path.GetFullPath(@".\TestAssets\8.png");
|
||||
TestSingleFilePreview(pngPath, "8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test window pinning functionality - pin window and switch between different sized images
|
||||
/// Verify the window stays at the same place and the same size
|
||||
/// </summary>
|
||||
[TestMethod("Peek.WindowPinning.PinAndSwitchImages")]
|
||||
[TestCategory("Window Pinning")]
|
||||
public void TestPinWindowAndSwitchImages()
|
||||
{
|
||||
// Use two different image files with different size
|
||||
string firstImagePath = Path.GetFullPath(@".\TestAssets\8.png");
|
||||
string secondImagePath = Path.GetFullPath(@".\TestAssets\2.jpg"); // Different format/size
|
||||
|
||||
// Open first image
|
||||
var initialWindow = OpenPeekWindow(firstImagePath);
|
||||
|
||||
var originalBounds = GetWindowBounds(initialWindow);
|
||||
|
||||
// Move window to a custom position to test pin functionality
|
||||
NativeMethods.MoveWindow(initialWindow, originalBounds.X + 100, originalBounds.Y + 50);
|
||||
var movedBounds = GetWindowBounds(initialWindow);
|
||||
|
||||
// Pin the window
|
||||
PinWindow();
|
||||
|
||||
// Close current peek
|
||||
ClosePeekAndExplorer();
|
||||
|
||||
// Open second image with different size
|
||||
var secondWindow = OpenPeekWindow(secondImagePath);
|
||||
var finalBounds = GetWindowBounds(secondWindow);
|
||||
|
||||
// Verify window position and size remained the same as the moved position
|
||||
Assert.AreEqual(movedBounds.X, finalBounds.X, 5, "Window X position should remain the same when pinned");
|
||||
Assert.AreEqual(movedBounds.Y, finalBounds.Y, 5, "Window Y position should remain the same when pinned");
|
||||
Assert.AreEqual(movedBounds.Width, finalBounds.Width, 10, "Window width should remain the same when pinned");
|
||||
Assert.AreEqual(movedBounds.Height, finalBounds.Height, 10, "Window height should remain the same when pinned");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test window pinning persistence - pin window, close and reopen Peek
|
||||
/// Verify the new window is opened at the same place and the same size as before
|
||||
/// </summary>
|
||||
[TestMethod("Peek.WindowPinning.PinAndReopen")]
|
||||
[TestCategory("Window Pinning")]
|
||||
public void TestPinWindowAndReopen()
|
||||
{
|
||||
string imagePath = Path.GetFullPath(@".\TestAssets\8.png");
|
||||
|
||||
// Open image and pin window
|
||||
var initialWindow = OpenPeekWindow(imagePath);
|
||||
var originalBounds = GetWindowBounds(initialWindow);
|
||||
|
||||
// Move window to a custom position to test pin persistence
|
||||
NativeMethods.MoveWindow(initialWindow, originalBounds.X + 150, originalBounds.Y + 75);
|
||||
var movedBounds = GetWindowBounds(initialWindow);
|
||||
|
||||
// Pin the window
|
||||
PinWindow();
|
||||
|
||||
// Close peek
|
||||
ClosePeekAndExplorer();
|
||||
Thread.Sleep(1000); // Wait for window to close completely
|
||||
|
||||
// Reopen the same image
|
||||
var reopenedWindow = OpenPeekWindow(imagePath);
|
||||
var finalBounds = GetWindowBounds(reopenedWindow);
|
||||
|
||||
// Verify window position and size are restored to the moved position
|
||||
Assert.AreEqual(movedBounds.X, finalBounds.X, 5, "Window X position should be restored when pinned");
|
||||
Assert.AreEqual(movedBounds.Y, finalBounds.Y, 5, "Window Y position should be restored when pinned");
|
||||
Assert.AreEqual(movedBounds.Width, finalBounds.Width, 10, "Window width should be restored when pinned");
|
||||
Assert.AreEqual(movedBounds.Height, finalBounds.Height, 10, "Window height should be restored when pinned");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test window unpinning - unpin window and switch to different file
|
||||
/// Verify the window is moved to the default place
|
||||
/// </summary>
|
||||
[TestMethod("Peek.WindowPinning.UnpinAndSwitchFiles")]
|
||||
[TestCategory("Window Pinning")]
|
||||
public void TestUnpinWindowAndSwitchFiles()
|
||||
{
|
||||
string firstFilePath = Path.GetFullPath(@".\TestAssets\8.png");
|
||||
string secondFilePath = Path.GetFullPath(@".\TestAssets\2.jpg");
|
||||
|
||||
// Open first file and pin window
|
||||
var pinnedWindow = OpenPeekWindow(firstFilePath);
|
||||
var originalBounds = GetWindowBounds(pinnedWindow);
|
||||
|
||||
// Move window to a custom position
|
||||
NativeMethods.MoveWindow(pinnedWindow, originalBounds.X + 200, originalBounds.Y + 100);
|
||||
var movedBounds = GetWindowBounds(pinnedWindow);
|
||||
|
||||
// Calculate the center point of the moved window
|
||||
var movedCenter = Session.GetMainWindowCenter();
|
||||
|
||||
// Pin the window first
|
||||
PinWindow();
|
||||
|
||||
// Unpin the window
|
||||
UnpinWindow();
|
||||
|
||||
// Close current peek
|
||||
ClosePeekAndExplorer();
|
||||
|
||||
// Open different file (different size)
|
||||
var unpinnedWindow = OpenPeekWindow(secondFilePath);
|
||||
var unpinnedBounds = GetWindowBounds(unpinnedWindow);
|
||||
|
||||
// Calculate the center point of the unpinned window
|
||||
var unpinnedCenter = Session.GetMainWindowCenter();
|
||||
|
||||
// Verify window size is different (since it's a different file type)
|
||||
bool sizeChanged = Math.Abs(movedBounds.Width - unpinnedBounds.Width) > 10 ||
|
||||
Math.Abs(movedBounds.Height - unpinnedBounds.Height) > 10;
|
||||
|
||||
// Verify window center moved to default position (should be different from moved center)
|
||||
bool centerChanged = Math.Abs(movedCenter.CenterX - unpinnedCenter.CenterX) > 50 ||
|
||||
Math.Abs(movedCenter.CenterY - unpinnedCenter.CenterY) > 50;
|
||||
|
||||
Assert.IsTrue(sizeChanged, "Window size should be different for different file types");
|
||||
Assert.IsTrue(centerChanged, "Window center should move to default position when unpinned");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test unpinned window behavior - unpin window, close and reopen Peek
|
||||
/// Verify the new window is opened on the default place
|
||||
/// </summary>
|
||||
[TestMethod("Peek.WindowPinning.UnpinAndReopen")]
|
||||
[TestCategory("Window Pinning")]
|
||||
public void TestUnpinWindowAndReopen()
|
||||
{
|
||||
string imagePath = Path.GetFullPath(@".\TestAssets\8.png");
|
||||
|
||||
// Open image, pin it first, then unpin
|
||||
var initialWindow = OpenPeekWindow(imagePath);
|
||||
var originalBounds = GetWindowBounds(initialWindow);
|
||||
|
||||
// Move window to a custom position
|
||||
NativeMethods.MoveWindow(initialWindow, originalBounds.X + 250, originalBounds.Y + 125);
|
||||
var movedBounds = GetWindowBounds(initialWindow);
|
||||
|
||||
// Pin then unpin to ensure we test the unpinned state
|
||||
PinWindow();
|
||||
UnpinWindow();
|
||||
|
||||
// Close peek
|
||||
ClosePeekAndExplorer();
|
||||
|
||||
// Reopen the same image
|
||||
var reopenedWindow = OpenPeekWindow(imagePath);
|
||||
var reopenedBounds = GetWindowBounds(reopenedWindow);
|
||||
|
||||
// Verify window opened at default position (not the previous moved position)
|
||||
bool openedAtDefault = Math.Abs(movedBounds.X - reopenedBounds.X) > 50 ||
|
||||
Math.Abs(movedBounds.Y - reopenedBounds.Y) > 50;
|
||||
|
||||
Assert.IsTrue(openedAtDefault, "Unpinned window should open at default position, not previous moved position");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test opening file with default program by clicking a button
|
||||
/// </summary>
|
||||
[TestMethod("Peek.OpenWithDefaultProgram.ClickButton")]
|
||||
[TestCategory("Open with default program")]
|
||||
public void TestOpenWithDefaultProgramByButton()
|
||||
{
|
||||
string zipPath = Path.GetFullPath(@".\TestAssets\7.zip");
|
||||
|
||||
// Open zip file with Peek
|
||||
var peekWindow = OpenPeekWindow(zipPath);
|
||||
|
||||
// Find and click the "Open with default program" button
|
||||
var openButton = FindLaunchButton();
|
||||
Assert.IsNotNull(openButton, "Open with default program button should be found");
|
||||
|
||||
// Click the button to open with default program
|
||||
openButton.Click();
|
||||
|
||||
// Wait a moment for the default program to launch
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// Verify that the default program process has started (check for Explorer opening 7-zip)
|
||||
bool defaultProgramLaunched = CheckIfExplorerLaunched();
|
||||
Assert.IsTrue(defaultProgramLaunched, "Default program (Explorer/7-zip) should be launched after clicking the button");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test opening file with default program by pressing Enter key
|
||||
/// </summary>
|
||||
[TestMethod("Peek.OpenWithDefaultProgram.PressEnter")]
|
||||
[TestCategory("Open with default program")]
|
||||
public void TestOpenWithDefaultProgramByEnter()
|
||||
{
|
||||
string zipPath = Path.GetFullPath(@".\TestAssets\7.zip");
|
||||
|
||||
// Open zip file with Peek
|
||||
var peekWindow = OpenPeekWindow(zipPath);
|
||||
|
||||
// Press Enter key to open with default program
|
||||
SendKeys(Key.Enter);
|
||||
|
||||
// Wait a moment for the default program to launch
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// Verify that the default program process has started (check for Explorer opening 7-zip)
|
||||
bool defaultProgramLaunched = CheckIfExplorerLaunched();
|
||||
Assert.IsTrue(defaultProgramLaunched, "Default program (Explorer/7-zip) should be launched after pressing Enter");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test switching between files in a folder using Left and Right arrow keys
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FileNavigation.SwitchFilesWithArrowKeys")]
|
||||
[TestCategory("File Navigation")]
|
||||
public void TestSwitchFilesWithArrowKeys()
|
||||
{
|
||||
// Get all files in TestAssets folder, ordered alphabetically
|
||||
var testFiles = GetTestAssetFiles();
|
||||
|
||||
// Start with the first file in the TestAssets folder
|
||||
string firstFilePath = testFiles[0];
|
||||
var peekWindow = OpenPeekWindow(firstFilePath);
|
||||
|
||||
// Keep track of visited files to ensure we can navigate through all
|
||||
var visitedFiles = new List<string> { Path.GetFileNameWithoutExtension(firstFilePath) };
|
||||
|
||||
// Navigate forward through files using Right arrow
|
||||
for (int i = 1; i < testFiles.Count; i++)
|
||||
{
|
||||
// Press Right arrow to go to next file
|
||||
SendKeys(Key.Right);
|
||||
|
||||
// Wait for file to load
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// Try to determine current file from window title
|
||||
var currentWindow = peekWindow.Name;
|
||||
string expectedFileName = Path.GetFileNameWithoutExtension(testFiles[i]);
|
||||
if (!string.IsNullOrEmpty(currentWindow) && currentWindow.StartsWith(expectedFileName, StringComparison.Ordinal))
|
||||
{
|
||||
visitedFiles.Add(expectedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we navigated through the expected number of files
|
||||
Assert.AreEqual(testFiles.Count, visitedFiles.Count, $"Should have navigated through all {testFiles.Count} files, but only visited {visitedFiles.Count} files: {string.Join(", ", visitedFiles)}");
|
||||
|
||||
// Navigate backward using Left arrow to verify reverse navigation
|
||||
for (int i = testFiles.Count - 2; i >= 0; i--)
|
||||
{
|
||||
SendKeys(Key.Left);
|
||||
|
||||
// Wait for file to load
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// Try to determine current file from window title during backward navigation
|
||||
var currentWindow = peekWindow.Name;
|
||||
string expectedFileName = Path.GetFileNameWithoutExtension(testFiles[i]);
|
||||
if (!string.IsNullOrEmpty(currentWindow) && currentWindow.StartsWith(expectedFileName, StringComparison.Ordinal))
|
||||
{
|
||||
// Remove the last visited file (going backward)
|
||||
if (visitedFiles.Count > 1)
|
||||
{
|
||||
visitedFiles.RemoveAt(visitedFiles.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify backward navigation worked - should be back to the first file
|
||||
Assert.AreEqual(1, visitedFiles.Count, $"After backward navigation, should be back to first file only. Remaining files: {string.Join(", ", visitedFiles)}");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test switching between multiple selected files
|
||||
/// Select first 3 files in Explorer, open with Peek, verify you can switch only between selected files using arrow keys
|
||||
/// </summary>
|
||||
[TestMethod("Peek.FileNavigation.SwitchBetweenSelectedFiles")]
|
||||
[TestCategory("File Navigation")]
|
||||
public void TestSwitchBetweenSelectedFiles()
|
||||
{
|
||||
// Get first 3 files in TestAssets folder, ordered alphabetically
|
||||
var allFiles = GetTestAssetFiles();
|
||||
var selectedFiles = allFiles.Take(3).ToList();
|
||||
|
||||
// Open Explorer and select the first file
|
||||
Session.StartExe("explorer.exe", $"/select,\"{selectedFiles[0]}\"");
|
||||
|
||||
// Wait for Explorer to open and select the first file
|
||||
WaitForExplorerWindow(selectedFiles[0]);
|
||||
|
||||
// Give Explorer time to fully load
|
||||
Thread.Sleep(2000);
|
||||
|
||||
// Use Shift+Down to extend selection to include the next 2 files
|
||||
SendKeys(Key.Shift, Key.Down); // Extend to second file
|
||||
Thread.Sleep(300);
|
||||
SendKeys(Key.Shift, Key.Down); // Extend to third file
|
||||
Thread.Sleep(300);
|
||||
|
||||
// Now we should have the first 3 files selected, open Peek
|
||||
SendPeekHotkeyWithRetry();
|
||||
|
||||
// Find the peek window (should open with last selected file when multiple files are selected)
|
||||
var peekWindow = FindPeekWindow(selectedFiles[2]); // Third file (last selected)
|
||||
string lastFileName = Path.GetFileNameWithoutExtension(selectedFiles[2]);
|
||||
|
||||
// Keep track of visited files during navigation (starting from the last file)
|
||||
var visitedFiles = new List<string> { lastFileName };
|
||||
var expectedFileNames = selectedFiles.Select(f => Path.GetFileNameWithoutExtension(f)).ToList();
|
||||
|
||||
// Test navigation by pressing Left arrow multiple times to verify we only cycle through 3 selected files
|
||||
var windowTitles = new List<string> { peekWindow.Name };
|
||||
|
||||
// Press Left arrow 5 times (more than the 3 selected files) to see if we cycle through only the selected files
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
SendKeys(Key.Left);
|
||||
Thread.Sleep(2000); // Wait for file to load
|
||||
|
||||
var currentWindowTitle = peekWindow.Name;
|
||||
windowTitles.Add(currentWindowTitle);
|
||||
}
|
||||
|
||||
// Analyze the navigation pattern - we should see repetition indicating we're only cycling through 3 files
|
||||
var uniqueWindowsVisited = windowTitles.Distinct().Count();
|
||||
|
||||
// We should see at most 3 unique windows (the 3 selected files), even after 6 navigation steps
|
||||
Assert.IsTrue(uniqueWindowsVisited <= 3, $"Should only navigate through the 3 selected files, but found {uniqueWindowsVisited} unique windows. " + $"Window titles: {string.Join(" -> ", windowTitles)}");
|
||||
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
|
||||
private bool CheckIfExplorerLaunched()
|
||||
{
|
||||
var possibleTitles = new[]
|
||||
{
|
||||
"7.zip - File Explorer",
|
||||
"7 - File Explorer",
|
||||
"7",
|
||||
"7.zip",
|
||||
};
|
||||
|
||||
foreach (var title in possibleTitles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var explorerWindow = Find(title, 5000, true);
|
||||
if (explorerWindow != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Continue to next title
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OpenAndPeekFile(string fullPath)
|
||||
{
|
||||
Session.StartExe("explorer.exe", $"/select,\"{fullPath}\"");
|
||||
|
||||
// Wait for Explorer to open and become ready
|
||||
WaitForExplorerWindow(fullPath);
|
||||
|
||||
// Send Peek hotkey with retry mechanism
|
||||
SendPeekHotkeyWithRetry();
|
||||
}
|
||||
|
||||
private void WaitForExplorerWindow(string filePath)
|
||||
{
|
||||
WaitForCondition(
|
||||
condition: () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if Explorer window is open and responsive
|
||||
var explorerProcesses = Process.GetProcessesByName("explorer")
|
||||
.Where(p => p.MainWindowHandle != IntPtr.Zero)
|
||||
.ToList();
|
||||
|
||||
if (explorerProcesses.Count != 0)
|
||||
{
|
||||
// Give Explorer a moment to fully load the file selection
|
||||
Thread.Sleep(ExplorerLoadDelayMs);
|
||||
|
||||
// Verify the file is accessible
|
||||
return File.Exists(filePath) || Directory.Exists(filePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"WaitForExplorerWindow exception: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
timeoutSeconds: ExplorerOpenTimeoutSeconds,
|
||||
checkIntervalMs: ExplorerCheckIntervalMs,
|
||||
timeoutMessage: $"Explorer window did not open for file: {filePath}");
|
||||
}
|
||||
|
||||
private void SendPeekHotkeyWithRetry()
|
||||
{
|
||||
for (int attempt = 1; attempt <= MaxRetryAttempts; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send the Peek hotkey
|
||||
SendKeys(Key.LCtrl, Key.Space);
|
||||
|
||||
// Wait for Peek window to appear
|
||||
if (WaitForPeekWindow())
|
||||
{
|
||||
return; // Success
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"SendPeekHotkeyWithRetry attempt {attempt} failed: {ex.Message}");
|
||||
|
||||
if (attempt == MaxRetryAttempts)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to open Peek after {MaxRetryAttempts} attempts. Last error: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait before retry using Thread.Sleep
|
||||
Thread.Sleep(RetryDelayMs);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to open Peek after {MaxRetryAttempts} attempts");
|
||||
}
|
||||
|
||||
private bool WaitForPeekWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
WaitForCondition(
|
||||
condition: () =>
|
||||
{
|
||||
if (TryFindPeekWindow())
|
||||
{
|
||||
// Give Peek a moment to fully initialize using Thread.Sleep
|
||||
Thread.Sleep(PeekInitializeDelayMs);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
timeoutSeconds: PeekWindowTimeoutSeconds,
|
||||
checkIntervalMs: PeekCheckIntervalMs,
|
||||
timeoutMessage: "Peek window did not appear");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"WaitForPeekWindow failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool WaitForCondition(Func<bool> condition, int timeoutSeconds, int checkIntervalMs, string timeoutMessage)
|
||||
{
|
||||
var timeout = TimeSpan.FromSeconds(timeoutSeconds);
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
while (DateTime.Now - startTime < timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (condition())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log exception but continue waiting
|
||||
Debug.WriteLine($"WaitForCondition exception: {ex.Message}");
|
||||
}
|
||||
|
||||
// Use async delay to prevent blocking the thread
|
||||
Thread.Sleep(checkIntervalMs);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"{timeoutMessage} (timeout: {timeoutSeconds}s)");
|
||||
}
|
||||
|
||||
private bool TryFindPeekWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check for Peek process with timeout
|
||||
var peekProcesses = Process.GetProcessesByName("PowerToys.Peek.UI")
|
||||
.Where(p => p.MainWindowHandle != IntPtr.Zero);
|
||||
|
||||
var foundProcess = peekProcesses.Any();
|
||||
|
||||
if (foundProcess)
|
||||
{
|
||||
// Additional validation - check if window is responsive
|
||||
Thread.Sleep(100); // Small delay to ensure window is ready
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"TryFindPeekWindow exception: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Element OpenPeekWindow(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
SendKeys(Key.Enter);
|
||||
|
||||
// Open file with Peek
|
||||
OpenAndPeekFile(filePath);
|
||||
|
||||
// Find the Peek window using the common method with timeout
|
||||
var peekWindow = FindPeekWindow(filePath);
|
||||
|
||||
// Attach to the found window with error handling
|
||||
try
|
||||
{
|
||||
Session.Attach(peekWindow.Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to attach to window: {ex.Message}");
|
||||
}
|
||||
|
||||
return peekWindow;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"OpenPeekWindow failed for {filePath}: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test a single file preview with visual comparison
|
||||
/// </summary>
|
||||
/// <param name="filePath">Full path to the file to test</param>
|
||||
/// <param name="expectedFileName">Expected file name for visual comparison</param>
|
||||
private void TestSingleFilePreview(string filePath, string expectedFileName, int? delayMs = 5000)
|
||||
{
|
||||
Element? previewWindow = null;
|
||||
|
||||
try
|
||||
{
|
||||
Debug.WriteLine($"Testing file preview: {Path.GetFileName(filePath)}");
|
||||
|
||||
previewWindow = OpenPeekWindow(filePath);
|
||||
|
||||
if (delayMs.HasValue)
|
||||
{
|
||||
Thread.Sleep(delayMs.Value); // Allow time for the preview to load
|
||||
}
|
||||
|
||||
Assert.IsNotNull(previewWindow, $"Should open Peek window for {Path.GetFileName(filePath)}");
|
||||
|
||||
// Perform visual comparison
|
||||
VisualAssert.AreEqual(TestContext, previewWindow, expectedFileName);
|
||||
|
||||
Debug.WriteLine($"Successfully tested: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always cleanup in finally block
|
||||
ClosePeekAndExplorer();
|
||||
}
|
||||
}
|
||||
|
||||
private Rectangle GetWindowBounds(Element window)
|
||||
{
|
||||
if (window.Rect == null)
|
||||
{
|
||||
return Rectangle.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return window.Rect.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void PinWindow()
|
||||
{
|
||||
// Find pin button using AutomationId
|
||||
var pinButton = Find(By.AccessibilityId("PinButton"), 2000);
|
||||
Assert.IsNotNull(pinButton, "Pin button should be found");
|
||||
|
||||
pinButton.Click();
|
||||
Thread.Sleep(PinActionDelayMs); // Wait for pin action to complete
|
||||
}
|
||||
|
||||
private void UnpinWindow()
|
||||
{
|
||||
// Find pin button using AutomationId (same button, just toggle the state)
|
||||
var pinButton = Find(By.AccessibilityId("PinButton"), 2000);
|
||||
Assert.IsNotNull(pinButton, "Pin button should be found");
|
||||
|
||||
pinButton.Click();
|
||||
Thread.Sleep(PinActionDelayMs); // Wait for unpin action to complete
|
||||
}
|
||||
|
||||
private void ClosePeekAndExplorer()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Close Peek window
|
||||
Session.CloseMainWindow();
|
||||
Thread.Sleep(500);
|
||||
SendKeys(Key.Win, Key.M);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error closing Peek window: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all files in TestAssets folder, ordered alphabetically, excluding hidden files
|
||||
/// </summary>
|
||||
/// <returns>List of file paths in alphabetical order</returns>
|
||||
private List<string> GetTestAssetFiles()
|
||||
{
|
||||
string testAssetsPath = Path.GetFullPath(@".\TestAssets");
|
||||
return Directory.GetFiles(testAssetsPath, "*.*", SearchOption.TopDirectoryOnly)
|
||||
.Where(file => !Path.GetFileName(file).StartsWith('.'))
|
||||
.OrderBy(file => file)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find Peek window by trying both filename with and without extension
|
||||
/// </summary>
|
||||
/// <param name="filePath">Full path to the file</param>
|
||||
/// <param name="timeout">Timeout in milliseconds</param>
|
||||
/// <returns>The found Peek window element</returns>
|
||||
private Element FindPeekWindow(string filePath, int timeout = 5000)
|
||||
{
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
// Try both window title formats since Windows may show or hide file extensions
|
||||
string peekWindowTitleWithExt = $"{fileName} - Peek";
|
||||
string peekWindowTitleWithoutExt = $"{fileNameWithoutExt} - Peek";
|
||||
|
||||
Element? peekWindow = null;
|
||||
|
||||
try
|
||||
{
|
||||
// First try to find the window with extension
|
||||
peekWindow = Find(peekWindowTitleWithoutExt, timeout, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
// Then try without extension
|
||||
peekWindow = Find(peekWindowTitleWithExt, timeout, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If neither works, let it fail with a clear message
|
||||
Assert.Fail($"Could not find Peek window with title '{peekWindowTitleWithExt}' or '{peekWindowTitleWithoutExt}'");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsNotNull(peekWindow, $"Should find Peek window for file: {Path.GetFileName(filePath)}");
|
||||
|
||||
return peekWindow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to find the launch button with different AccessibilityIds depending on window size
|
||||
/// </summary>
|
||||
/// <returns>The launch button element</returns>
|
||||
private Element? FindLaunchButton()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to find button with ID for larger window first
|
||||
var button = Find(By.AccessibilityId("LaunchAppButton_Text"), 1000);
|
||||
if (button != null)
|
||||
{
|
||||
return button;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Try to find button with ID for smaller window
|
||||
var button = Find(By.AccessibilityId("LaunchAppButton"), 1000);
|
||||
if (button != null)
|
||||
{
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
BIN
src/modules/peek/Peek.UITests/TestAssets/2.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/modules/peek/Peek.UITests/TestAssets/4.qoi
Normal file
6
src/modules/peek/Peek.UITests/TestAssets/5.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, world!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
11
src/modules/peek/Peek.UITests/TestAssets/6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## 简单的 C++ 示例
|
||||
|
||||
这是一个最基础的 C++ 程序,它会输出 "Hello, world!":
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, world!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
BIN
src/modules/peek/Peek.UITests/TestAssets/7.zip
Normal file
BIN
src/modules/peek/Peek.UITests/TestAssets/8.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
@@ -0,0 +1,21 @@
|
||||
## Peek
|
||||
* Open different files to check that they're shown properly
|
||||
- [x] Image
|
||||
- [x] Text or dev file
|
||||
- [x] Markdown file
|
||||
- [x] PDF
|
||||
- [x] Archive files (.zip, .tar, .rar)
|
||||
- [x] Any other not mentioned file (.exe for example) to verify the unsupported file view is shown
|
||||
|
||||
* Pinning/unpinning
|
||||
- [x] Pin the window, switch between images of different size, verify the window stays at the same place and the same size.
|
||||
- [x] Pin the window, close and reopen Peek, verify the new window is opened at the same place and the same size as before.
|
||||
- [x] Unpin the window, switch to a different file, verify the window is moved to the default place.
|
||||
- [x] Unpin the window, close and reopen Peek, verify the new window is opened on the default place.
|
||||
|
||||
* Open with a default program
|
||||
- [x] By clicking a button.
|
||||
- [x] By pressing enter.
|
||||
|
||||
- [x] Switch between files in the folder using `LeftArrow` and `RightArrow`, verify you can switch between all files in the folder.
|
||||
- [x] Open multiple files, verify you can switch only between selected files.
|
||||