mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-29 00:24:42 +01:00
Compare commits
87 Commits
leilzh/ent
...
dev/zhaope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a23e84657 | ||
|
|
8ad79551b5 | ||
|
|
856cbaa293 | ||
|
|
4067be17b2 | ||
|
|
b4020c6720 | ||
|
|
f87c1bc448 | ||
|
|
f49daa24e0 | ||
|
|
c1c9720992 | ||
|
|
750bb429c5 | ||
|
|
7f5aaf26dc | ||
|
|
af8d5402c8 | ||
|
|
77852f2137 | ||
|
|
b902d3adf0 | ||
|
|
82ff0615d4 | ||
|
|
5081d6d31e | ||
|
|
abec5eb96a | ||
|
|
71ec39ff89 | ||
|
|
017b9e6339 | ||
|
|
c8b1a925b3 | ||
|
|
e0e3772cdd | ||
|
|
6b8798fb94 | ||
|
|
836cfbf698 | ||
|
|
91d504511f | ||
|
|
3d3682672c | ||
|
|
b9474e9f60 | ||
|
|
c5b29188b8 | ||
|
|
e59b6d3051 | ||
|
|
6625289a10 | ||
|
|
2f0dae347f | ||
|
|
f3ddba8aa5 | ||
|
|
4744d23857 | ||
|
|
c42f8b0a1b | ||
|
|
70da33783d | ||
|
|
5fa416b962 | ||
|
|
aa7d89c8de | ||
|
|
fd7cccefa2 | ||
|
|
ae2bb61d26 | ||
|
|
52b4150e3b | ||
|
|
6dc0211867 | ||
|
|
fc4d3a96c3 | ||
|
|
a347d740ca | ||
|
|
e8359931ac | ||
|
|
1cb215d1a6 | ||
|
|
0f6ff59d2e | ||
|
|
25a8c95049 | ||
|
|
c60c79af67 | ||
|
|
dd5279b9b8 | ||
|
|
78921eb7d3 | ||
|
|
9173ac27df | ||
|
|
ce57d818a7 | ||
|
|
595231f64b | ||
|
|
7894c1e5ad | ||
|
|
d2d71bf797 | ||
|
|
3eb5ca294a | ||
|
|
0277938f18 | ||
|
|
dd7b6f3ea9 | ||
|
|
0187d1abcd | ||
|
|
7bd2b50126 | ||
|
|
b127611462 | ||
|
|
622770134e | ||
|
|
ceca607142 | ||
|
|
77a5ef7d32 | ||
|
|
75f2b0927c | ||
|
|
f54ab6ebd5 | ||
|
|
dd5997ab06 | ||
|
|
6bdbb6b552 | ||
|
|
84dd551d84 | ||
|
|
83bb82322d | ||
|
|
c4f8f09fab | ||
|
|
9c95835384 | ||
|
|
1867ac8f02 | ||
|
|
127e079efe | ||
|
|
03540e307c | ||
|
|
a10578f7d3 | ||
|
|
9c08c957e5 | ||
|
|
c73b88f575 | ||
|
|
fc1c4abd0a | ||
|
|
22efeb3f63 | ||
|
|
63c4089441 | ||
|
|
fd206ecdee | ||
|
|
d243b58715 | ||
|
|
abf4626843 | ||
|
|
3874a3b893 | ||
|
|
8687b310db | ||
|
|
c7ed8ee0c3 | ||
|
|
8c21f794af | ||
|
|
18befd7149 |
@@ -50,6 +50,9 @@ parameters:
|
||||
- name: runTests
|
||||
type: boolean
|
||||
default: true
|
||||
- name: buildTests
|
||||
type: boolean
|
||||
default: true
|
||||
- name: useVSPreview
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -110,7 +113,7 @@ jobs:
|
||||
JobOutputArtifactName: build-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }}
|
||||
NUGET_RESTORE_MSBUILD_ARGS: /p:Platform=$(BuildPlatform) # Required for nuget to work due to self contained
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
${{ if eq(parameters.runTests, true) }}:
|
||||
${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
|
||||
MSBuildMainBuildTargets: Build;Test
|
||||
${{ else }}:
|
||||
MSBuildMainBuildTargets: Build
|
||||
@@ -514,7 +517,7 @@ jobs:
|
||||
displayName: Stage GPO files
|
||||
|
||||
# Running the tests may result in future jobs consuming artifacts out of this build
|
||||
- ${{ if eq(parameters.runTests, true) }}:
|
||||
- ${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Stage entire build output
|
||||
inputs:
|
||||
|
||||
@@ -11,6 +11,9 @@ parameters:
|
||||
- name: useLatestWebView2
|
||||
type: boolean
|
||||
default: false
|
||||
- name: isUIAutomationPipeline
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
- job: Test${{ parameters.platform }}${{ parameters.configuration }}
|
||||
@@ -101,8 +104,16 @@ jobs:
|
||||
vsTestVersion: 'toolsInstaller'
|
||||
uiTests: true
|
||||
rerunFailedTests: true
|
||||
testAssemblyVer2: |
|
||||
**\UITests-FancyZones.dll
|
||||
**\UITests-FancyZonesEditor.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
${{ if eq(parameters.isUIAutomationPipeline, true) }}:
|
||||
testAssemblyVer2: |
|
||||
**\*UITest*.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
${{ else }}:
|
||||
testAssemblyVer2: |
|
||||
**\UITests-FancyZones.dll
|
||||
**\UITests-FancyZonesEditor.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
env:
|
||||
platform: '$(BuildPlatform)'
|
||||
|
||||
60
.pipelines/v2/templates/pipeline-ui-tests-automation.yml
Normal file
60
.pipelines/v2/templates/pipeline-ui-tests-automation.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
variables:
|
||||
- name: runCodesignValidationInjectionBG
|
||||
value: false
|
||||
- name: EnablePipelineCache
|
||||
value: true
|
||||
- ${{ if eq(parameters.enableMsBuildCaching, true) }}:
|
||||
- name: EnablePipelineCache
|
||||
value: true
|
||||
|
||||
parameters:
|
||||
- name: buildPlatforms
|
||||
type: object
|
||||
default:
|
||||
- x64
|
||||
- arm64
|
||||
- name: enableMsBuildCaching
|
||||
type: boolean
|
||||
default: false
|
||||
- name: useVSPreview
|
||||
type: boolean
|
||||
default: false
|
||||
- name: useLatestWebView2
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
stages:
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
- stage: Build_${{ platform }}
|
||||
displayName: Build ${{ platform }}
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: job-build-project.yml
|
||||
parameters:
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ platform }}
|
||||
buildConfigurations: [Release]
|
||||
enablePackageCaching: true
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
runTests: false
|
||||
buildTests: true
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
dependsOn:
|
||||
- Build_${{platform}}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
isUIAutomationPipeline: true
|
||||
@@ -91,5 +91,4 @@ if ($totalFailures -gt 0) {
|
||||
}
|
||||
|
||||
Write-Host -ForegroundColor Green "All " $referencedFileVersionsPerDll.keys.Count " libraries are mentioned with the same version across the dependencies.`r`n"
|
||||
exit 0
|
||||
|
||||
exit 0
|
||||
@@ -7,6 +7,7 @@ using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Linq;
|
||||
using ABI.Windows.Foundation;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
@@ -199,6 +200,42 @@ namespace Microsoft.PowerToys.UITest
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates holding a key, clicking and dragging a UI element to the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="key">The keyboard key to press and hold during the drag operation.</param>
|
||||
/// <param name="targetX">The target X-coordinate to drag the element to.</param>
|
||||
/// <param name="targetY">The target Y-coordinate to drag the element to.</param>
|
||||
public void KeyDownAndDrag(Key key, int targetX, int targetY)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
KeyboardHelper.PressKey(key);
|
||||
|
||||
actions.MoveToElement(windowsElement)
|
||||
.ClickAndHold()
|
||||
.Perform();
|
||||
|
||||
int dx = targetX - windowElement.Rect.X;
|
||||
int dy = targetY - windowElement.Rect.Y;
|
||||
|
||||
int stepCount = 10;
|
||||
int stepX = dx / stepCount;
|
||||
int stepY = dy / stepCount;
|
||||
|
||||
for (int i = 0; i < stepCount; i++)
|
||||
{
|
||||
var stepAction = new Actions(driver);
|
||||
stepAction.MoveByOffset(stepX, stepY).Perform();
|
||||
}
|
||||
|
||||
var releaseAction = new Actions(driver);
|
||||
releaseAction.Release().Perform();
|
||||
|
||||
KeyboardHelper.ReleaseKey(key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attribute value of the UI element.
|
||||
/// </summary>
|
||||
@@ -218,7 +255,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(By by, int timeoutMS = 3000)
|
||||
public T Find<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -226,7 +263,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
// leverage findAll to filter out mismatched elements
|
||||
var collection = this.FindAll<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
@@ -239,7 +276,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
public T Find<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
@@ -252,7 +289,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
public Element Find(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -264,7 +301,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
public Element Find(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
@@ -276,7 +313,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -308,7 +345,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
@@ -321,7 +358,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -333,7 +370,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
@@ -360,5 +397,15 @@ namespace Microsoft.PowerToys.UITest
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save UI Element to a PNG file.
|
||||
/// </summary>
|
||||
/// <param name="path">the full path</param>
|
||||
internal void SaveToPngFile(string path)
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method SaveToFile with parameter: path = {path}");
|
||||
this.windowsElement.GetScreenshot().SaveAsFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using OpenQA.Selenium;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -35,8 +33,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
// select all text and delete it
|
||||
windowElement.SendKeys(Keys.Control + "a");
|
||||
windowElement.SendKeys(Keys.Delete);
|
||||
windowElement.SendKeys(OpenQA.Selenium.Keys.Control + "a");
|
||||
windowElement.SendKeys(OpenQA.Selenium.Keys.Delete);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<ReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
var items = findElementsFunc();
|
||||
var items = FindElementsWithRetry(findElementsFunc, timeoutMS);
|
||||
var res = items.Select(item =>
|
||||
{
|
||||
var element = item as WindowsElement;
|
||||
@@ -43,6 +43,27 @@ namespace Microsoft.PowerToys.UITest
|
||||
return new ReadOnlyCollection<T>(res);
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<TW> FindElementsWithRetry<TW>(Func<ReadOnlyCollection<TW>> findElementsFunc, int timeoutMS)
|
||||
{
|
||||
int retryIntervalMS = 500;
|
||||
timeoutMS = 1;
|
||||
int elapsedTime = 0;
|
||||
|
||||
while (elapsedTime < timeoutMS)
|
||||
{
|
||||
var items = findElementsFunc();
|
||||
if (items.Count > 0)
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
Task.Delay(retryIntervalMS).Wait();
|
||||
elapsedTime += retryIntervalMS;
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<TW>(new List<TW>());
|
||||
}
|
||||
|
||||
public static T NewElement<T>(WindowsElement? element, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
@@ -50,11 +71,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
Assert.IsNotNull(element, $"New Element {typeof(T).Name} error: element is null.");
|
||||
|
||||
T newElement = new T();
|
||||
if (timeoutMS > 0)
|
||||
{
|
||||
// Only set timeout if it is positive value
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
}
|
||||
|
||||
newElement.SetSession(driver);
|
||||
newElement.SetWindowsElement(element);
|
||||
|
||||
333
src/common/UITestAutomation/KeyboardHelper.cs
Normal file
333
src/common/UITestAutomation/KeyboardHelper.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
// 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.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents keyboard keys.
|
||||
/// </summary>
|
||||
public enum Key
|
||||
{
|
||||
Ctrl,
|
||||
Alt,
|
||||
Shift,
|
||||
Tab,
|
||||
Esc,
|
||||
Enter,
|
||||
Win,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Num0,
|
||||
Num1,
|
||||
Num2,
|
||||
Num3,
|
||||
Num4,
|
||||
Num5,
|
||||
Num6,
|
||||
Num7,
|
||||
Num8,
|
||||
Num9,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
Space,
|
||||
Backspace,
|
||||
Delete,
|
||||
Insert,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Other,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for simulating keyboard input.
|
||||
/// </summary>
|
||||
internal static class KeyboardHelper
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
||||
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
|
||||
#pragma warning restore SA1300 // Element should begin with upper-case letter
|
||||
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
private const byte VK_LWIN = 0x5B;
|
||||
private const uint KEYEVENTF_KEYDOWN = 0x0000;
|
||||
private const uint KEYEVENTF_KEYUP = 0x0002;
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
|
||||
/// <summary>
|
||||
/// Sends a combination of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys to send.</param>
|
||||
public static void SendKeys(params Key[] keys)
|
||||
{
|
||||
string keysToSend = string.Join(string.Empty, keys.Select(TranslateKey));
|
||||
SendWinKeyCombination(keysToSend);
|
||||
}
|
||||
|
||||
public static void PressKey(Key key)
|
||||
{
|
||||
PressVirtualKey(TranslateKeyHex(key));
|
||||
}
|
||||
|
||||
public static void ReleaseKey(Key key)
|
||||
{
|
||||
ReleaseVirtualKey(TranslateKeyHex(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates a key to its corresponding SendKeys representation.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to translate.</param>
|
||||
/// <returns>The SendKeys representation of the key.</returns>
|
||||
private static string TranslateKey(Key key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Key.Ctrl:
|
||||
return "^";
|
||||
case Key.Alt:
|
||||
return "%";
|
||||
case Key.Shift:
|
||||
return "+";
|
||||
case Key.Tab:
|
||||
return "{TAB}";
|
||||
case Key.Esc:
|
||||
return "{ESC}";
|
||||
case Key.Enter:
|
||||
return "{ENTER}";
|
||||
case Key.Win:
|
||||
return "{WIN}";
|
||||
case Key.Space:
|
||||
return " ";
|
||||
case Key.Backspace:
|
||||
return "{BACKSPACE}";
|
||||
case Key.Delete:
|
||||
return "{DELETE}";
|
||||
case Key.Insert:
|
||||
return "{INSERT}";
|
||||
case Key.Home:
|
||||
return "{HOME}";
|
||||
case Key.End:
|
||||
return "{END}";
|
||||
case Key.PageUp:
|
||||
return "{PGUP}";
|
||||
case Key.PageDown:
|
||||
return "{PGDN}";
|
||||
case Key.Up:
|
||||
return "{UP}";
|
||||
case Key.Down:
|
||||
return "{DOWN}";
|
||||
case Key.Left:
|
||||
return "{LEFT}";
|
||||
case Key.Right:
|
||||
return "{RIGHT}";
|
||||
case Key.F1:
|
||||
return "{F1}";
|
||||
case Key.F2:
|
||||
return "{F2}";
|
||||
case Key.F3:
|
||||
return "{F3}";
|
||||
case Key.F4:
|
||||
return "{F4}";
|
||||
case Key.F5:
|
||||
return "{F5}";
|
||||
case Key.F6:
|
||||
return "{F6}";
|
||||
case Key.F7:
|
||||
return "{F7}";
|
||||
case Key.F8:
|
||||
return "{F8}";
|
||||
case Key.F9:
|
||||
return "{F9}";
|
||||
case Key.F10:
|
||||
return "{F10}";
|
||||
case Key.F11:
|
||||
return "{F11}";
|
||||
case Key.F12:
|
||||
return "{F12}";
|
||||
case Key.A:
|
||||
return "a";
|
||||
case Key.B:
|
||||
return "b";
|
||||
case Key.C:
|
||||
return "c";
|
||||
case Key.D:
|
||||
return "d";
|
||||
case Key.E:
|
||||
return "e";
|
||||
case Key.F:
|
||||
return "f";
|
||||
case Key.G:
|
||||
return "g";
|
||||
case Key.H:
|
||||
return "h";
|
||||
case Key.I:
|
||||
return "i";
|
||||
case Key.J:
|
||||
return "j";
|
||||
case Key.K:
|
||||
return "k";
|
||||
case Key.L:
|
||||
return "l";
|
||||
case Key.M:
|
||||
return "m";
|
||||
case Key.N:
|
||||
return "n";
|
||||
case Key.O:
|
||||
return "o";
|
||||
case Key.P:
|
||||
return "p";
|
||||
case Key.Q:
|
||||
return "q";
|
||||
case Key.R:
|
||||
return "r";
|
||||
case Key.S:
|
||||
return "s";
|
||||
case Key.T:
|
||||
return "t";
|
||||
case Key.U:
|
||||
return "u";
|
||||
case Key.V:
|
||||
return "v";
|
||||
case Key.W:
|
||||
return "w";
|
||||
case Key.X:
|
||||
return "x";
|
||||
case Key.Y:
|
||||
return "y";
|
||||
case Key.Z:
|
||||
return "z";
|
||||
case Key.Num0:
|
||||
return "0";
|
||||
case Key.Num1:
|
||||
return "1";
|
||||
case Key.Num2:
|
||||
return "2";
|
||||
case Key.Num3:
|
||||
return "3";
|
||||
case Key.Num4:
|
||||
return "4";
|
||||
case Key.Num5:
|
||||
return "5";
|
||||
case Key.Num6:
|
||||
return "6";
|
||||
case Key.Num7:
|
||||
return "7";
|
||||
case Key.Num8:
|
||||
return "8";
|
||||
case Key.Num9:
|
||||
return "9";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// map the virtual key codes to the corresponding keys.
|
||||
/// </summary>
|
||||
private static byte TranslateKeyHex(Key key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Key.Win:
|
||||
return 0x5B; // Windows Key - 0x5B in hex
|
||||
case Key.Ctrl:
|
||||
return 0x11; // Ctrl Key - 0x11 in hex
|
||||
case Key.Alt:
|
||||
return 0x12; // Alt Key - 0x12 in hex
|
||||
case Key.Shift:
|
||||
return 0x10; // Shift Key - 0x10 in hex
|
||||
default:
|
||||
throw new ArgumentException($"Key {key} is not supported, Please add your key at TranslateKeyHex for translation to hex.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a combination of keys, including the Windows key, to the system.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys to send.</param>
|
||||
private static void SendWinKeyCombination(string keys)
|
||||
{
|
||||
bool winKeyDown = false;
|
||||
|
||||
if (keys.Contains("{WIN}"))
|
||||
{
|
||||
keybd_event(VK_LWIN, 0, KEYEVENTF_KEYDOWN, UIntPtr.Zero);
|
||||
winKeyDown = true;
|
||||
keys = keys.Replace("{WIN}", string.Empty); // Remove {WIN} from the string
|
||||
}
|
||||
|
||||
System.Windows.Forms.SendKeys.SendWait(keys);
|
||||
|
||||
// Release Windows key
|
||||
if (winKeyDown)
|
||||
{
|
||||
keybd_event(VK_LWIN, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just press the key.(no release)
|
||||
/// </summary>
|
||||
private static void PressVirtualKey(byte key)
|
||||
{
|
||||
keybd_event(key, 0, KEYEVENTF_KEYDOWN, UIntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release only the button (if pressed first)
|
||||
/// </summary>
|
||||
private static void ReleaseVirtualKey(byte key)
|
||||
{
|
||||
keybd_event(key, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,47 @@ namespace Microsoft.PowerToys.UITest
|
||||
Hosts,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the window size for the UI test.
|
||||
/// </summary>
|
||||
public enum WindowSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Unspecified window size, won't make any size change
|
||||
/// </summary>
|
||||
UnSpecified,
|
||||
|
||||
/// <summary>
|
||||
/// Small window size, 640 * 480
|
||||
/// </summary>
|
||||
Small,
|
||||
|
||||
/// <summary>
|
||||
/// Small window size, 480 * 640
|
||||
/// </summary>
|
||||
Small_Vertical,
|
||||
|
||||
/// <summary>
|
||||
/// Medium window size, 1024 * 768
|
||||
/// </summary>
|
||||
Medium,
|
||||
|
||||
/// <summary>
|
||||
/// Medium window size, 768 * 1024
|
||||
/// </summary>
|
||||
Medium_Vertical,
|
||||
|
||||
/// <summary>
|
||||
/// Large window size, 1920 * 1080
|
||||
/// </summary>
|
||||
Large,
|
||||
|
||||
/// <summary>
|
||||
/// Large window size, 1080 * 1920
|
||||
/// </summary>
|
||||
Large_Vertical,
|
||||
}
|
||||
|
||||
internal class ModuleConfigData
|
||||
{
|
||||
private Dictionary<PowerToysModule, string> ModulePath { get; }
|
||||
|
||||
49
src/common/UITestAutomation/MouseHelper.cs
Normal file
49
src/common/UITestAutomation/MouseHelper.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
internal static class MouseHelper
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool GetCursorPos(out POINT lpPoint);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetCursorPos(int x, int y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the mouse cursor as a tuple.
|
||||
/// </summary>
|
||||
/// <returns>A tuple containing the X and Y coordinates of the cursor.</returns>
|
||||
public static Tuple<int, int> GetMousePosition()
|
||||
{
|
||||
GetCursorPos(out POINT point);
|
||||
return Tuple.Create(point.X, point.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the mouse cursor to the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The new x-coordinate of the cursor.</param>
|
||||
/// <param name="y">The new y-coordinate of the cursor.</param
|
||||
public static void MoveMouseTo(int x, int y)
|
||||
{
|
||||
SetCursorPos(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
src/common/UITestAutomation/ScreenCapture.cs
Normal file
133
src/common/UITestAutomation/ScreenCapture.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for capturing the screen with the mouse cursor.
|
||||
/// </summary>
|
||||
internal static class ScreenCapture
|
||||
{
|
||||
[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 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);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a point with X and Y coordinates.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the cursor.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CURSORINFO
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the structure.
|
||||
/// </summary>
|
||||
public int CbSize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cursor state.
|
||||
/// </summary>
|
||||
public int Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handle to the cursor.
|
||||
/// </summary>
|
||||
public IntPtr HCursor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the screen position of the cursor.
|
||||
/// </summary>
|
||||
public POINT PTScreenPos;
|
||||
}
|
||||
|
||||
private const int CURSORSHOWING = 0x00000001;
|
||||
private const int DESKTOPHORZRES = 118;
|
||||
private const int DESKTOPVERTRES = 117;
|
||||
private const int DINORMAL = 0x0003;
|
||||
|
||||
/// <summary>
|
||||
/// Captures the screen with the mouse cursor and saves it to the specified file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path to save the captured image.</param>
|
||||
private static void CaptureScreenWithMouse(string filePath)
|
||||
{
|
||||
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))
|
||||
{
|
||||
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
|
||||
{
|
||||
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
|
||||
|
||||
CURSORINFO cursorInfo;
|
||||
cursorInfo.CbSize = Marshal.SizeOf<CURSORINFO>();
|
||||
if (GetCursorInfo(out cursorInfo) && cursorInfo.Flags == CURSORSHOWING)
|
||||
{
|
||||
using (System.Drawing.Graphics gIcon = System.Drawing.Graphics.FromImage(bitmap))
|
||||
{
|
||||
IntPtr hdcDest = gIcon.GetHdc();
|
||||
DrawIconEx(hdcDest, cursorInfo.PTScreenPos.X, cursorInfo.PTScreenPos.Y, cursorInfo.HCursor, 0, 0, 0, IntPtr.Zero, DINORMAL);
|
||||
gIcon.ReleaseHdc(hdcDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.Save(filePath, ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a screenshot and saves it to the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to save the screenshot.</param>
|
||||
private static void CaptureScreenshot(string directory)
|
||||
{
|
||||
string filePath = Path.Combine(directory, $"screenshot_{DateTime.Now:yyyyMMdd_HHmmssfff}.png");
|
||||
CaptureScreenWithMouse(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timer callback method to capture a screenshot.
|
||||
/// </summary>
|
||||
/// <param name="state">The state object passed to the callback method.</param>
|
||||
public static void TimerCallback(object? state)
|
||||
{
|
||||
string directory = (string)state!;
|
||||
CaptureScreenshot(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
@@ -17,27 +17,81 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public class Session
|
||||
{
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
public WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
private WindowsDriver<WindowsElement> WindowsDriver { get; set; }
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
private const string AdministratorPrefix = "Administrator: ";
|
||||
|
||||
public Session(WindowsDriver<WindowsElement> root, WindowsDriver<WindowsElement> windowsDriver)
|
||||
private List<IntPtr> windowHandlers = new List<IntPtr>();
|
||||
|
||||
private Window? MainWindow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Main Window Handler
|
||||
/// </summary>
|
||||
public IntPtr MainWindowHandler { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RunAsAdmin flag.
|
||||
/// If true, the session is running as admin.
|
||||
/// If false, the session is not running as admin.
|
||||
/// If null, no information is available.
|
||||
/// </summary>
|
||||
public bool? IsElevated { get; private set; }
|
||||
|
||||
public Session(WindowsDriver<WindowsElement> root, WindowsDriver<WindowsElement> windowsDriver, PowerToysModule scope, WindowSize size)
|
||||
{
|
||||
this.MainWindowHandler = IntPtr.Zero;
|
||||
this.Root = root;
|
||||
this.WindowsDriver = windowsDriver;
|
||||
|
||||
if (size != WindowSize.UnSpecified)
|
||||
{
|
||||
// Attach to the scope & reset MainWindowHandler
|
||||
this.Attach(scope, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by selector.
|
||||
/// Cleans up the Session Exe.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
/*
|
||||
foreach (var windowHandle in this.windowHandlers)
|
||||
{
|
||||
if (windowHandle == IntPtr.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var process = Process.GetProcessById((int)windowHandle);
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
windowHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(By by, int timeoutMS = 3000)
|
||||
public T Find<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -45,7 +99,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
// leverage findAll to filter out mismatched elements
|
||||
var collection = this.FindAll<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
@@ -55,9 +109,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
public T Find<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
@@ -67,9 +121,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
public Element Find(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -78,21 +132,117 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
public Element Find(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Has only one Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(by, timeoutMS).Count == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.HasOne<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.HasOne<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.HasOne<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.HasOne<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.HasOne<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.HasOne<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has one or more Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(by, timeoutMS).Count >= 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Has<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Has<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Has<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Has<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Has<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Has<Element>(name, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -122,9 +272,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
@@ -135,9 +285,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -147,55 +297,210 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard Action key.
|
||||
/// Sets the main window size.
|
||||
/// </summary>
|
||||
/// <param name="key1">The Keys1 to click.</param>
|
||||
/// <param name="key2">The Keys2 to click.</param>
|
||||
/// <param name="key3">The Keys3 to click.</param>
|
||||
/// <param name="key4">The Keys4 to click.</param>
|
||||
public void KeyboardAction(string key1, string key2 = "", string key3 = "", string key4 = "")
|
||||
/// <param name="size">WindowSize enum</param>
|
||||
public void SetMainWindowSize(WindowSize size)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
if (size == WindowSize.UnSpecified)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key2))
|
||||
{
|
||||
actions.SendKeys(key1);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(key3))
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(key4))
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3);
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
actions.Release();
|
||||
actions.Build().Perform();
|
||||
int width = 0, height = 0;
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case WindowSize.Small:
|
||||
width = 640;
|
||||
height = 480;
|
||||
break;
|
||||
case WindowSize.Small_Vertical:
|
||||
width = 480;
|
||||
height = 640;
|
||||
break;
|
||||
case WindowSize.Medium:
|
||||
width = 1024;
|
||||
height = 768;
|
||||
break;
|
||||
case WindowSize.Medium_Vertical:
|
||||
width = 768;
|
||||
height = 1024;
|
||||
break;
|
||||
case WindowSize.Large:
|
||||
width = 1920;
|
||||
height = 1080;
|
||||
break;
|
||||
case WindowSize.Large_Vertical:
|
||||
width = 1080;
|
||||
height = 1920;
|
||||
break;
|
||||
}
|
||||
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
this.SetMainWindowSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the main window size based on Width and Height.
|
||||
/// </summary>
|
||||
/// <param name="width">the width in pixel</param>
|
||||
/// <param name="height">the height in pixel</param>
|
||||
public void SetMainWindowSize(int width, int height)
|
||||
{
|
||||
if (this.MainWindowHandler == IntPtr.Zero
|
||||
|| width <= 0
|
||||
|| height <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApiHelper.SetWindowPos(this.MainWindowHandler, IntPtr.Zero, 0, 0, width, height, ApiHelper.SetWindowPosNoMove | ApiHelper.SetWindowPosNoZorder | ApiHelper.SetWindowPosShowWindow);
|
||||
|
||||
// Wait for 1000ms after resize
|
||||
Task.Delay(1000).Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the main window.
|
||||
/// </summary>
|
||||
public void CloseMainWindow()
|
||||
{
|
||||
if (MainWindow != null)
|
||||
{
|
||||
MainWindow.Close();
|
||||
MainWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the color of the pixel at the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate on the screen.</param>
|
||||
/// <param name="y">The Y coordinate on the screen.</param>
|
||||
/// <returns>The color of the pixel at the specified coordinates.</returns>
|
||||
public Color GetPixelColor(int x, int y)
|
||||
{
|
||||
IntPtr hdc = ApiHelper.GetDC(IntPtr.Zero);
|
||||
uint pixel = ApiHelper.GetPixel(hdc, x, y);
|
||||
_ = ApiHelper.ReleaseDC(IntPtr.Zero, hdc);
|
||||
|
||||
int r = (int)(pixel & 0x000000FF);
|
||||
int g = (int)((pixel & 0x0000FF00) >> 8);
|
||||
int b = (int)((pixel & 0x00FF0000) >> 16);
|
||||
|
||||
return Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the display.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A tuple containing the width and height of the display.
|
||||
/// </returns
|
||||
public Tuple<int, int> GetDisplaySize()
|
||||
{
|
||||
IntPtr hdc = ApiHelper.GetDC(IntPtr.Zero);
|
||||
int screenWidth = ApiHelper.GetDeviceCaps(hdc, ApiHelper.DESKTOPHORZRES);
|
||||
int screenHeight = ApiHelper.GetDeviceCaps(hdc, ApiHelper.DESKTOPVERTRES);
|
||||
_ = ApiHelper.ReleaseDC(IntPtr.Zero, hdc);
|
||||
|
||||
return Tuple.Create(screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a combination of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys to send.</param>
|
||||
public void SendKeys(params Key[] keys)
|
||||
{
|
||||
PerformAction(() =>
|
||||
{
|
||||
KeyboardHelper.SendKeys(keys);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// release the key (after the hold key and drag is completed.)
|
||||
/// </summary>
|
||||
/// <param name="key">The key release.</param>
|
||||
public void PressKey(Key key)
|
||||
{
|
||||
PerformAction(() =>
|
||||
{
|
||||
KeyboardHelper.PressKey(key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// press and hold the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to press and hold .</param>
|
||||
public void ReleaseKey(Key key)
|
||||
{
|
||||
PerformAction(() =>
|
||||
{
|
||||
KeyboardHelper.ReleaseKey(key);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a sequence of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">An array of keys to send.</param>
|
||||
public void SendKeySequence(params Key[] keys)
|
||||
{
|
||||
PerformAction(() =>
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
KeyboardHelper.SendKeys(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the mouse cursor as a tuple.
|
||||
/// </summary>
|
||||
/// <returns>A tuple containing the X and Y coordinates of the cursor.</returns>
|
||||
public Tuple<int, int> GetMousePosition()
|
||||
{
|
||||
return MouseHelper.GetMousePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the mouse cursor to the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The new x-coordinate of the cursor.</param>
|
||||
/// <param name="y">The new y-coordinate of the cursor.</param
|
||||
public void MoveMouseTo(int x, int y)
|
||||
{
|
||||
PerformAction(() =>
|
||||
{
|
||||
MouseHelper.MoveMouseTo(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches to an existing PowerToys module.
|
||||
/// </summary>
|
||||
/// <param name="module">The PowerToys module to attach to.</param>
|
||||
/// <param name="size">The window size to set. Default is no change to window size</param>
|
||||
/// <returns>The attached session.</returns>
|
||||
public Session Attach(PowerToysModule module)
|
||||
public Session Attach(PowerToysModule module, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
string windowName = ModuleConfigData.Instance.GetModuleWindowName(module);
|
||||
return this.Attach(windowName);
|
||||
return this.Attach(windowName, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,26 +508,44 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// The session should be attached when a new app is started.
|
||||
/// </summary>
|
||||
/// <param name="windowName">The window name to attach to.</param>
|
||||
/// <param name="size">The window size to set. Default is no change to window size</param>
|
||||
/// <returns>The attached session.</returns>
|
||||
public Session Attach(string windowName)
|
||||
public Session Attach(string windowName, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
this.IsElevated = null;
|
||||
this.MainWindowHandler = IntPtr.Zero;
|
||||
|
||||
if (this.Root != null)
|
||||
{
|
||||
var window = this.Root.FindElementByName(windowName);
|
||||
Assert.IsNotNull(window, $"Failed to attach. Window '{windowName}' not found");
|
||||
// search window handler by window title (admin and non-admin titles)
|
||||
var matchingWindows = ApiHelper.FindDesktopWindowHandler([windowName, AdministratorPrefix + windowName]);
|
||||
if (matchingWindows.Count == 0 || matchingWindows[0].HWnd == IntPtr.Zero)
|
||||
{
|
||||
Assert.Fail($"Failed to attach. Window '{windowName}' not found");
|
||||
}
|
||||
|
||||
// pick one from matching windows
|
||||
this.MainWindowHandler = matchingWindows[0].HWnd;
|
||||
this.IsElevated = matchingWindows[0].Title.StartsWith(AdministratorPrefix);
|
||||
|
||||
ApiHelper.SetForegroundWindow(this.MainWindowHandler);
|
||||
|
||||
var hexWindowHandle = this.MainWindowHandler.ToInt64().ToString("x");
|
||||
|
||||
var windowHandle = new nint(int.Parse(window.GetAttribute("NativeWindowHandle")));
|
||||
SetForegroundWindow(windowHandle);
|
||||
var hexWindowHandle = windowHandle.ToString("x");
|
||||
var appCapabilities = new AppiumOptions();
|
||||
|
||||
appCapabilities.AddAdditionalCapability("appTopLevelWindow", hexWindowHandle);
|
||||
appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC");
|
||||
this.WindowsDriver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), appCapabilities);
|
||||
Assert.IsNotNull(this.WindowsDriver, "Attach WindowsDriver is null");
|
||||
|
||||
// Set implicit timeout to make element search retry every 500 ms
|
||||
this.WindowsDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
|
||||
this.windowHandlers.Add(this.MainWindowHandler);
|
||||
|
||||
if (size != WindowSize.UnSpecified)
|
||||
{
|
||||
this.SetMainWindowSize(size);
|
||||
}
|
||||
|
||||
// Set MainWindow
|
||||
MainWindow = Find<Window>(matchingWindows[0].Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -232,6 +555,77 @@ namespace Microsoft.PowerToys.UITest
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class ApiHelper
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
public const uint SetWindowPosNoMove = 0x0002;
|
||||
public const uint SetWindowPosNoZorder = 0x0004;
|
||||
public const uint SetWindowPosShowWindow = 0x0040;
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||
|
||||
// Delegate for the EnumWindows callback function
|
||||
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
||||
|
||||
// P/Invoke declaration for EnumWindows
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
||||
|
||||
// P/Invoke declaration for GetWindowTextLength
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern int GetWindowTextLength(IntPtr hWnd);
|
||||
|
||||
// P/Invoke declaration for GetWindowText
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern uint GetPixel(IntPtr hdc, int x, int y);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
public const int DESKTOPHORZRES = 118;
|
||||
public const int DESKTOPVERTRES = 117;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
public static List<(IntPtr HWnd, string Title)> FindDesktopWindowHandler(string[] matchingWindowsTitles)
|
||||
{
|
||||
var windows = new List<(IntPtr HWnd, string Title)>();
|
||||
|
||||
_ = EnumWindows(
|
||||
(hWnd, lParam) =>
|
||||
{
|
||||
int length = GetWindowTextLength(hWnd);
|
||||
if (length > 0)
|
||||
{
|
||||
var builder = new StringBuilder(length + 1);
|
||||
_ = GetWindowText(hWnd, builder, builder.Capacity);
|
||||
|
||||
var title = builder.ToString();
|
||||
if (matchingWindowsTitles.Contains(title))
|
||||
{
|
||||
windows.Add((hWnd, title));
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Continue enumeration
|
||||
},
|
||||
IntPtr.Zero);
|
||||
|
||||
return windows;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
@@ -254,5 +648,26 @@ namespace Microsoft.PowerToys.UITest
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 500 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 500 ms</param>
|
||||
protected void PerformAction(Action action, int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
Task.Delay(msPreAction).Wait();
|
||||
}
|
||||
|
||||
action();
|
||||
|
||||
if (msPostAction > 0)
|
||||
{
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,14 +40,12 @@ namespace Microsoft.PowerToys.UITest
|
||||
Verb = "runas",
|
||||
};
|
||||
|
||||
this.ExitExe(winAppDriverProcessInfo.FileName);
|
||||
this.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
this.Root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Root.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +54,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
public SessionHelper Init()
|
||||
{
|
||||
this.StartExe(locationPath + this.sessionPath);
|
||||
// Exit setting exe to fix CommandPalette error, remove after fixing the issue
|
||||
this.ExitExe(this.locationPath + ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings));
|
||||
|
||||
this.ExitExe(this.locationPath + this.sessionPath);
|
||||
this.StartExe(this.locationPath + this.sessionPath);
|
||||
|
||||
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
|
||||
|
||||
@@ -81,31 +83,15 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
Console.WriteLine($"appPath: {appPath}");
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a exe.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the application executable.</param>
|
||||
public void ExitExe(string path)
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void ExitExe(string appPath)
|
||||
{
|
||||
// Exit Exe
|
||||
string exeName = Path.GetFileNameWithoutExtension(path);
|
||||
string exeName = Path.GetFileNameWithoutExtension(appPath);
|
||||
|
||||
// PowerToys.FancyZonesEditor
|
||||
Process[] processes = Process.GetProcessesByName(exeName);
|
||||
foreach (Process process in processes)
|
||||
{
|
||||
@@ -121,6 +107,21 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
opts.AddAdditionalCapability("ms:waitForAppLaunch", "5");
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit now exe.
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -6,10 +6,15 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using Windows.Devices.Display.Core;
|
||||
using Windows.Foundation.Metadata;
|
||||
using static Microsoft.PowerToys.UITest.UITestBase.NativeMethods;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -17,19 +22,36 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Base class that should be inherited by all Test Classes.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class UITestBase
|
||||
public class UITestBase : IDisposable
|
||||
{
|
||||
public Session Session { get; set; }
|
||||
public required TestContext TestContext { get; set; }
|
||||
|
||||
private readonly SessionHelper sessionHelper;
|
||||
public required Session Session { get; set; }
|
||||
|
||||
private readonly bool isInPipeline;
|
||||
private readonly PowerToysModule scope;
|
||||
private readonly WindowSize size;
|
||||
private SessionHelper? sessionHelper;
|
||||
private System.Threading.Timer? screenshotTimer;
|
||||
private string? screenshotDirectory;
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
|
||||
// private System.Threading.Timer? screenshotTimer;
|
||||
// private string? screenshotDirectory;
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
this.isInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
|
||||
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
|
||||
if (isInPipeline)
|
||||
{
|
||||
NativeMethods.ChangeDispalyResolution();
|
||||
NativeMethods.GetMonitorInfo();
|
||||
|
||||
// Escape Popups before starting
|
||||
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
||||
}
|
||||
|
||||
this.scope = scope;
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,6 +60,18 @@ namespace Microsoft.PowerToys.UITest
|
||||
[TestInitialize]
|
||||
public void TestInit()
|
||||
{
|
||||
if (isInPipeline)
|
||||
{
|
||||
screenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(screenshotDirectory);
|
||||
|
||||
// Take screenshot every 1 second
|
||||
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, screenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
|
||||
}
|
||||
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), scope, size);
|
||||
|
||||
if (this.scope == PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
// close Debug warning dialog if any
|
||||
@@ -50,12 +84,32 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UnInitializes the test.
|
||||
/// Cleanups the test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void TestClean()
|
||||
public void TestCleanup()
|
||||
{
|
||||
this.sessionHelper.Cleanup();
|
||||
if (isInPipeline)
|
||||
{
|
||||
screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
Dispose();
|
||||
if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed
|
||||
or UnitTestOutcome.Error
|
||||
or UnitTestOutcome.Unknown)
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
AddScreenShotsToTestResultsDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
this.Session.Cleanup();
|
||||
this.sessionHelper!.Cleanup();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
screenshotTimer?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,22 +118,22 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(By by, int timeoutMS = 3000)
|
||||
protected T Find<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(string name, int timeoutMS = 3000)
|
||||
protected T Find<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS);
|
||||
@@ -89,33 +143,129 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Session.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(By by, int timeoutMS = 3000)
|
||||
protected Element Find(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.Find(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(string name, int timeoutMS = 3000)
|
||||
protected Element Find(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.Find(name, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has only one Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(by, timeoutMS).Count == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.HasOne<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.HasOne<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.HasOne<T>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.HasOne<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.HasOne<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.HasOne<Element>(name, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS).Count >= 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.Has<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Has<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Has<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if has one or more element, otherwise false.</returns>
|
||||
public bool Has(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.Has<Element>(name, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS);
|
||||
@@ -127,9 +277,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS);
|
||||
@@ -140,9 +290,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Session.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -152,20 +302,105 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures the last screenshot when the test fails.
|
||||
/// </summary>
|
||||
protected void CaptureLastScreenshot()
|
||||
{
|
||||
// Implement your screenshot capture logic here
|
||||
// For example, save a screenshot to a file and return the file path
|
||||
string screenshotPath = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "last_screenshot.png");
|
||||
|
||||
this.Session.Root.GetScreenshot().SaveAsFile(screenshotPath, ScreenshotImageFormat.Png);
|
||||
|
||||
// Save screenshot to screenshotPath & upload to test attachment
|
||||
this.TestContext.AddResultFile(screenshotPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the color of the pixel at the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate on the screen.</param>
|
||||
/// <param name="y">The Y coordinate on the screen.</param>
|
||||
/// <returns>The color of the pixel at the specified coordinates.</returns>
|
||||
public Color GetPixelColor(int x, int y)
|
||||
{
|
||||
return this.Session.GetPixelColor(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the display.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A tuple containing the width and height of the display.
|
||||
/// </returns
|
||||
public Tuple<int, int> GetDisplaySize()
|
||||
{
|
||||
return this.Session.GetDisplaySize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a combination of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys to send.</param>
|
||||
public void SendKeys(params Key[] keys)
|
||||
{
|
||||
this.Session.SendKeys(keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a sequence of keys.
|
||||
/// </summary>
|
||||
/// <param name="keys">An array of keys to send.</param>
|
||||
public void SendKeySequence(params Key[] keys)
|
||||
{
|
||||
this.Session.SendKeySequence(keys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the mouse cursor as a tuple.
|
||||
/// </summary>
|
||||
/// <returns>A tuple containing the X and Y coordinates of the cursor.</returns>
|
||||
public Tuple<int, int> GetMousePosition()
|
||||
{
|
||||
return this.Session.GetMousePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the mouse cursor to the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The new x-coordinate of the cursor.</param>
|
||||
/// <param name="y">The new y-coordinate of the cursor.</param
|
||||
public void MoveMouseTo(int x, int y)
|
||||
{
|
||||
this.Session.MoveMouseTo(x, y);
|
||||
}
|
||||
|
||||
protected void AddScreenShotsToTestResultsDirectory()
|
||||
{
|
||||
if (screenshotDirectory != null)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(screenshotDirectory))
|
||||
{
|
||||
this.TestContext.AddResultFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart scope exe.
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
this.sessionHelper.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.sessionHelper!.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,8 +409,165 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void ExitScopeExe()
|
||||
{
|
||||
this.sessionHelper.ExitScopeExe();
|
||||
this.sessionHelper!.ExitScopeExe();
|
||||
return;
|
||||
}
|
||||
|
||||
public class NativeMethods
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct DISPLAY_DEVICE
|
||||
{
|
||||
public int cb;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string DeviceName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string DeviceString;
|
||||
public int StateFlags;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string DeviceID;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string DeviceKey;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int EnumDisplaySettings(IntPtr deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
|
||||
private static extern bool EnumDisplayDevices(IntPtr lpDevice, int iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, int dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int ChangeDisplaySettings(ref DEVMODE devMode, int flags);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
|
||||
private static extern int ChangeDisplaySettingsEx(IntPtr lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, uint dwflags, IntPtr lParam);
|
||||
|
||||
private const int DM_PELSWIDTH = 0x80000;
|
||||
private const int DM_PELSHEIGHT = 0x100000;
|
||||
|
||||
public const int ENUM_CURRENT_SETTINGS = -1;
|
||||
public const int CDS_TEST = 0x00000002;
|
||||
public const int CDS_UPDATEREGISTRY = 0x01;
|
||||
public const int DISP_CHANGE_SUCCESSFUL = 0;
|
||||
public const int DISP_CHANGE_RESTART = 1;
|
||||
public const int DISP_CHANGE_FAILED = -1;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DEVMODE
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string DmDeviceName;
|
||||
public short DmSpecVersion;
|
||||
public short DmDriverVersion;
|
||||
public short DmSize;
|
||||
public short DmDriverExtra;
|
||||
public int DmFields;
|
||||
public int DmPositionX;
|
||||
public int DmPositionY;
|
||||
public int DmDisplayOrientation;
|
||||
public int DmDisplayFixedOutput;
|
||||
public short DmColor;
|
||||
public short DmDuplex;
|
||||
public short DmYResolution;
|
||||
public short DmTTOption;
|
||||
public short DmCollate;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string DmFormName;
|
||||
public short DmLogPixels;
|
||||
public int DmBitsPerPel;
|
||||
public int DmPelsWidth;
|
||||
public int DmPelsHeight;
|
||||
public int DmDisplayFlags;
|
||||
public int DmDisplayFrequency;
|
||||
public int DmICMMethod;
|
||||
public int DmICMIntent;
|
||||
public int DmMediaType;
|
||||
public int DmDitherType;
|
||||
public int DmReserved1;
|
||||
public int DmReserved2;
|
||||
public int DmPanningWidth;
|
||||
public int DmPanningHeight;
|
||||
}
|
||||
|
||||
public static void GetMonitorInfo()
|
||||
{
|
||||
int deviceIndex = 0;
|
||||
DISPLAY_DEVICE d = default(DISPLAY_DEVICE);
|
||||
d.cb = Marshal.SizeOf(d);
|
||||
|
||||
Console.WriteLine("monitor list :");
|
||||
while (EnumDisplayDevices(IntPtr.Zero, deviceIndex, ref d, 0))
|
||||
{
|
||||
Console.WriteLine($"monitor {deviceIndex + 1}:");
|
||||
Console.WriteLine($" name: {d.DeviceName}");
|
||||
Console.WriteLine($" string: {d.DeviceString}");
|
||||
Console.WriteLine($" ID: {d.DeviceID}");
|
||||
Console.WriteLine($" key: {d.DeviceKey}");
|
||||
Console.WriteLine();
|
||||
|
||||
DEVMODE dm = default(DEVMODE);
|
||||
dm.DmSize = (short)Marshal.SizeOf<DEVMODE>();
|
||||
int modeNum = 0;
|
||||
while (EnumDisplaySettings(d.DeviceName, modeNum, ref dm) > 0)
|
||||
{
|
||||
Console.WriteLine($" mode {modeNum}: {dm.DmPelsWidth}x{dm.DmPelsHeight} @ {dm.DmDisplayFrequency}Hz");
|
||||
modeNum++;
|
||||
}
|
||||
|
||||
deviceIndex++;
|
||||
d.cb = Marshal.SizeOf(d); // Reset the size for the next device
|
||||
}
|
||||
}
|
||||
|
||||
public static void ChangeDispalyResolution()
|
||||
{
|
||||
Screen screen = Screen.PrimaryScreen!;
|
||||
if (screen.Bounds.Width == 1920 && screen.Bounds.Height == 1080)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DEVMODE devMode = default(DEVMODE);
|
||||
devMode.DmDeviceName = new string(new char[32]);
|
||||
devMode.DmFormName = new string(new char[32]);
|
||||
devMode.DmSize = (short)Marshal.SizeOf<DEVMODE>();
|
||||
|
||||
int modeNum = 0;
|
||||
while (EnumDisplaySettings(IntPtr.Zero, modeNum, ref devMode) > 0)
|
||||
{
|
||||
Console.WriteLine($"Mode {modeNum}: {devMode.DmPelsWidth}x{devMode.DmPelsHeight} @ {devMode.DmDisplayFrequency}Hz");
|
||||
modeNum++;
|
||||
}
|
||||
|
||||
devMode.DmPelsWidth = 1920;
|
||||
devMode.DmPelsHeight = 1080;
|
||||
|
||||
int result = NativeMethods.ChangeDisplaySettings(ref devMode, NativeMethods.CDS_TEST);
|
||||
|
||||
if (result == DISP_CHANGE_SUCCESSFUL)
|
||||
{
|
||||
result = ChangeDisplaySettings(ref devMode, CDS_UPDATEREGISTRY);
|
||||
if (result == DISP_CHANGE_SUCCESSFUL)
|
||||
{
|
||||
Console.WriteLine($"Changing display resolution to {devMode.DmPelsWidth}x{devMode.DmPelsHeight}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Failed to change display resolution. Error code: {result}");
|
||||
}
|
||||
}
|
||||
else if (result == DISP_CHANGE_RESTART)
|
||||
{
|
||||
Console.WriteLine($"Changing display resolution to {devMode.DmPelsWidth}x{devMode.DmPelsHeight} requires a restart");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Failed to change display resolution. Error code: {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
150
src/common/UITestAutomation/VisualAssert.cs
Normal file
150
src/common/UITestAutomation/VisualAssert.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
public static class VisualAssert
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts current visual state of the element is equal with base line image.
|
||||
/// To use this VisualAssert, you need to set Window Theme to Light-Mode to avoid Theme color difference in baseline image.
|
||||
/// Such limiation could be removed either Auto-generate baseline image for both Light & Dark mode
|
||||
/// </summary>
|
||||
/// <param name="testContext">TestContext object</param>
|
||||
/// <param name="element">Element object</param>
|
||||
/// <param name="scenarioSubname">additional scenario name if two or more scenarios in one test</param>
|
||||
[RequiresUnreferencedCode("This method uses reflection which may not be compatible with trimming.")]
|
||||
public static void AreEqual(TestContext? testContext, Element element, string scenarioSubname = "")
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
Assert.Fail("Element object is null or invalid");
|
||||
}
|
||||
|
||||
var stackTrace = new StackTrace();
|
||||
var callerFrame = stackTrace.GetFrame(1);
|
||||
var callerMethod = callerFrame?.GetMethod();
|
||||
|
||||
var callerName = callerMethod?.Name;
|
||||
var callerClassName = callerMethod?.DeclaringType?.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(callerName) || string.IsNullOrEmpty(callerClassName))
|
||||
{
|
||||
Assert.Fail("Unable to determine the caller method and class name.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(scenarioSubname))
|
||||
{
|
||||
scenarioSubname = string.Join("_", callerClassName, callerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim());
|
||||
}
|
||||
|
||||
var baselineImageResourceName = callerMethod!.DeclaringType!.Assembly.GetManifestResourceNames().Where(name => name.Contains(scenarioSubname)).FirstOrDefault();
|
||||
|
||||
var tempTestImagePath = GetTempFilePath(scenarioSubname, "test", ".png");
|
||||
|
||||
element.SaveToPngFile(tempTestImagePath);
|
||||
|
||||
if (string.IsNullOrEmpty(baselineImageResourceName)
|
||||
|| !Path.GetFileNameWithoutExtension(baselineImageResourceName).EndsWith(scenarioSubname))
|
||||
{
|
||||
Assert.Fail($"Baseline image for scenario {scenarioSubname} can not be found, test image saved in file://{tempTestImagePath.Replace('\\', '/')}");
|
||||
}
|
||||
|
||||
var tempBaselineImagePath = GetTempFilePath(scenarioSubname, "baseline", Path.GetExtension(baselineImageResourceName));
|
||||
|
||||
bool isSame = false;
|
||||
|
||||
using (var stream = callerMethod!.DeclaringType!.Assembly.GetManifestResourceStream(baselineImageResourceName))
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
Assert.Fail($"Resource stream '{baselineImageResourceName}' is null.");
|
||||
}
|
||||
|
||||
using (var baselineImage = new Bitmap(stream))
|
||||
{
|
||||
using (var testImage = new Bitmap(tempTestImagePath))
|
||||
{
|
||||
isSame = VisualAssert.AreEqual(baselineImage, testImage);
|
||||
|
||||
if (!isSame)
|
||||
{
|
||||
// Copy baseline image to temp folder as well
|
||||
baselineImage.Save(tempBaselineImagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSame)
|
||||
{
|
||||
if (testContext != null)
|
||||
{
|
||||
testContext.AddResultFile(tempBaselineImagePath);
|
||||
testContext.AddResultFile(tempTestImagePath);
|
||||
}
|
||||
|
||||
Assert.Fail($"Fail to validate visual result for scenario {scenarioSubname}, baseline image can be found file://{tempBaselineImagePath.Replace('\\', '/')}, and test image can be found file://{tempTestImagePath.Replace('\\', '/')}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get temp file path
|
||||
/// </summary>
|
||||
/// <param name="scenario">scenario name</param>
|
||||
/// <param name="imageType">baseline or test image</param>
|
||||
/// <param name="extension">image file extension</param>
|
||||
/// <returns>full temp file path</returns>
|
||||
private static string GetTempFilePath(string scenario, string imageType, string extension)
|
||||
{
|
||||
var tempFileFullName = $"{scenario}_{imageType}{extension}";
|
||||
|
||||
// Remove invalid filename character if any
|
||||
Path.GetInvalidFileNameChars().ToList().ForEach(c => tempFileFullName = tempFileFullName.Replace(c, '-'));
|
||||
|
||||
return Path.Combine(Path.GetTempPath(), tempFileFullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if two images are equal bit-by-bit
|
||||
/// </summary>
|
||||
/// <param name="baselineImage">baseline image</param>
|
||||
/// <param name="testImage">test image</param>
|
||||
/// <returns>true if are equal,otherwise false</returns>
|
||||
private static bool AreEqual(Bitmap baselineImage, Bitmap testImage)
|
||||
{
|
||||
if (baselineImage.Width != testImage.Width || baselineImage.Height != testImage.Height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// WinAppDriver sometimes adds a border to the screenshot (around 2 pix width), and it is not always consistent.
|
||||
// So we exclude the border when comparing the images, and usually it is the edge of the windows, won't affect the comparison.
|
||||
int excludeBorderWidth = 5, excludeBorderHeight = 5;
|
||||
|
||||
for (int x = excludeBorderWidth; x < baselineImage.Width - excludeBorderWidth; x++)
|
||||
{
|
||||
for (int y = excludeBorderHeight; y < baselineImage.Height - excludeBorderHeight; y++)
|
||||
{
|
||||
if (!VisualHelper.PixIsSame(baselineImage.GetPixel(x, y), testImage.GetPixel(x, y)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
170
src/common/UITestAutomation/VisualHelper.cs
Normal file
170
src/common/UITestAutomation/VisualHelper.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
internal static class VisualHelper
|
||||
{
|
||||
#pragma warning disable SA1307
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct IMMERSIVE_COLOR_PREFERENCE
|
||||
{
|
||||
public uint dwColorSetIndex;
|
||||
public uint crStartColor;
|
||||
public uint crAccentColor;
|
||||
}
|
||||
#pragma warning restore SA1307
|
||||
|
||||
[DllImport("uxtheme.dll", EntryPoint = "#120")]
|
||||
private static extern IntPtr GetUserColorPreference(ref IMMERSIVE_COLOR_PREFERENCE pcpPreference, bool fForceReload);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system accent color.
|
||||
/// </summary>
|
||||
/// <returns>The system accent color as a Color object.</returns>
|
||||
private static Color GetSystemAccentColor()
|
||||
{
|
||||
IMMERSIVE_COLOR_PREFERENCE colorPreference = default(IMMERSIVE_COLOR_PREFERENCE);
|
||||
GetUserColorPreference(ref colorPreference, true);
|
||||
return ToColor(colorPreference.crStartColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a color value to a Color object.
|
||||
/// </summary>
|
||||
/// <param name="c">The color value.</param>
|
||||
/// <returns>The Color object.</returns>
|
||||
private static Color ToColor(uint c)
|
||||
{
|
||||
int r = (int)(c & 0xFF) % 256;
|
||||
int g = (int)((c >> 8) & 0xFF) % 256;
|
||||
int b = (int)(c >> 16) % 256;
|
||||
return Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets HSL values from a Color object.
|
||||
/// </summary>
|
||||
/// <param name="color">The Color object.</param>
|
||||
/// <returns>A tuple containing the HSL values.</returns>
|
||||
private static (double H, double S, double L) GetHSL(Color color)
|
||||
{
|
||||
double rNorm = color.R / 255.0;
|
||||
double gNorm = color.G / 255.0;
|
||||
double bNorm = color.B / 255.0;
|
||||
|
||||
double max = Math.Max(rNorm, Math.Max(gNorm, bNorm));
|
||||
double min = Math.Min(rNorm, Math.Min(gNorm, bNorm));
|
||||
double h = 0, s = 0, l = (max + min) / 2;
|
||||
|
||||
if (max != min)
|
||||
{
|
||||
double delta = max - min;
|
||||
s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
||||
|
||||
if (max == rNorm)
|
||||
{
|
||||
h = ((gNorm - bNorm) / delta) + (gNorm < bNorm ? 6 : 0);
|
||||
}
|
||||
else if (max == gNorm)
|
||||
{
|
||||
h = ((bNorm - rNorm) / delta) + 2;
|
||||
}
|
||||
else if (max == bNorm)
|
||||
{
|
||||
h = ((rNorm - gNorm) / delta) + 4;
|
||||
}
|
||||
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return (h * 360, s * 100, l * 100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes a specific color in an image transparent.
|
||||
/// </summary>
|
||||
/// <param name="imagePath">The path to the image file.</param>
|
||||
/// <param name="outputPath">The path to save the output image file.</param>
|
||||
/// <param name="targetColor">The target color to make transparent.</param>
|
||||
/// <param name="fuzz">The fuzz factor for color comparison, default is 2.</param>
|
||||
private static void MakeColorTransparent(string imagePath, string outputPath, Color targetColor, int fuzz = 2)
|
||||
{
|
||||
var hsl = GetHSL(targetColor);
|
||||
|
||||
// Assert.IsNotNull(null, $"Target Color - H: {hsl.H}, S: {hsl.S}, L: {hsl.L}");
|
||||
using (Bitmap originalBitmap = new Bitmap(imagePath))
|
||||
{
|
||||
using (Bitmap bitmap = new Bitmap(originalBitmap))
|
||||
{
|
||||
for (int y = 0; y < bitmap.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < bitmap.Width; x++)
|
||||
{
|
||||
Color pixelColor = bitmap.GetPixel(x, y);
|
||||
if (HueIsSame(pixelColor, targetColor, fuzz))
|
||||
{
|
||||
bitmap.SetPixel(x, y, Color.Transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.Save(outputPath, ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erases the user preference color from an image. Will overwrite this image.
|
||||
/// </summary>
|
||||
/// <param name="imagePath">The path to the image file.</param>
|
||||
/// <param name="fuzz">The fuzz factor for color comparison, default is 2.</param>
|
||||
public static void EraseUserPreferenceColor(string imagePath, int fuzz = 2)
|
||||
{
|
||||
Color systemColor = GetSystemAccentColor();
|
||||
string tempPath = Path.GetTempFileName();
|
||||
MakeColorTransparent(imagePath, tempPath, systemColor, fuzz);
|
||||
File.Delete(imagePath);
|
||||
File.Move(tempPath, imagePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two pixels with a fuzz factor
|
||||
/// </summary>
|
||||
/// <param name="c1">base color</param>
|
||||
/// <param name="c2">test color</param>
|
||||
/// <param name="fuzz">fuzz factor, default is 10</param>
|
||||
/// <returns>true if same, otherwise is false</returns>
|
||||
public static bool PixIsSame(Color c1, Color c2, int fuzz = 10)
|
||||
{
|
||||
return Math.Abs(c1.A - c2.A) <= fuzz && Math.Abs(c1.R - c2.R) <= fuzz && Math.Abs(c1.G - c2.G) <= fuzz && Math.Abs(c1.B - c2.B) <= fuzz;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the hue of two colors with a fuzz factor.
|
||||
/// </summary>
|
||||
/// <param name="c1">The first color.</param>
|
||||
/// <param name="c2">The second color.</param>
|
||||
/// <param name="fuzz">The fuzz factor, default is 2.</param>
|
||||
/// <returns>True if the hues are the same, otherwise false.</returns>
|
||||
public static bool HueIsSame(Color c1, Color c2, int fuzz = 2)
|
||||
{
|
||||
var h1 = GetHSL(c1).H;
|
||||
var h2 = GetHSL(c2).H;
|
||||
return Math.Abs(h1 - h2) <= fuzz;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
@@ -13,7 +13,7 @@ namespace Hosts.UITests
|
||||
public class HostModuleTests : UITestBase
|
||||
{
|
||||
public HostModuleTests()
|
||||
: base(PowerToysModule.Hosts)
|
||||
: base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -31,14 +31,16 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
|
||||
public void TestEmptyView()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
Assert.IsTrue(this.HasOne<HyperlinkButton>("Add an entry"), "'Add an entry' button should be visible in the empty view");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "EmptyView");
|
||||
|
||||
// Click 'Add an entry' from empty-view for adding Host override rule
|
||||
this.Find<HyperlinkButton>("Add an entry").Click();
|
||||
@@ -46,8 +48,10 @@ namespace Hosts.UITests
|
||||
this.AddEntry("192.168.0.1", "localhost", false, false);
|
||||
|
||||
// Should have one row now and not more empty view
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
|
||||
Assert.IsFalse(this.Has<HyperlinkButton>("Add an entry"), "'Add an entry' button should be invisible if not empty view");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "NonEmptyView");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -58,17 +62,19 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.AddEntryButtonShouldWork")]
|
||||
public void TestAddingEntry()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0, "Should have no row after removing all");
|
||||
Assert.IsFalse(this.Has<Button>("Delete"), "Should have no row after removing all");
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +88,7 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.CanNotAddMoreThenNighHosts")]
|
||||
public void TestTooManyHosts()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
@@ -116,18 +122,46 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.ErrorMessgeShowupIfNotRunAsAdmin")]
|
||||
public void TestErrorMessageWithNonAdminPermission()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
if (this.Session.IsElevated == false)
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(
|
||||
this.FindAll<TextBlock>("The hosts file cannot be saved because the program isn't running as administrator.").Count == 1,
|
||||
"Should display host-file saving error if not run as administrator");
|
||||
Assert.IsTrue(
|
||||
this.FindAll<TextBlock>("The hosts file cannot be saved because the program isn't running as administrator.").Count == 1,
|
||||
"Should display host-file saving error if not run as administrator");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test No Error-message in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating error message should be shown if not run as admin.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("Hosts.Basic.NoErrorMessgeShowupIfRunAsAdmin")]
|
||||
public void TestNoErrorMessageWithNonAdminPermission()
|
||||
{
|
||||
if (this.Session.IsElevated == true)
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsFalse(
|
||||
this.FindAll<TextBlock>("The hosts file cannot be saved because the program isn't running as administrator.").Count > 0,
|
||||
"Should display host-file saving error if not run as administrator");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +178,7 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.FiltersControlShouldWork")]
|
||||
public void TestFilterControl()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
@@ -212,7 +246,7 @@ namespace Hosts.UITests
|
||||
|
||||
// Close-filter-panel
|
||||
this.Find<Button>("Filters").Click();
|
||||
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed afer click Filter Button");
|
||||
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed after clicking Filter Button");
|
||||
}
|
||||
|
||||
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Baseline\HostModuleTests_TestAddingEntry.png" />
|
||||
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_EmptyView.png" />
|
||||
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_NonEmptyView.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
|
||||
@@ -12,6 +12,11 @@ namespace Hosts.UITests
|
||||
[TestClass]
|
||||
public class HostsSettingTests : UITestBase
|
||||
{
|
||||
public HostsSettingTests()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Medium)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Warning Dialog at startup
|
||||
/// <list type="bullet">
|
||||
@@ -29,7 +34,7 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Settings.ShowWarningDialogIfRunAsAdmin")]
|
||||
public void TestWarningDialog()
|
||||
{
|
||||
this.LaunchFromSetting(showWarning: true);
|
||||
@@ -54,7 +59,7 @@ namespace Hosts.UITests
|
||||
// wait for 500 ms to make sure Hosts File Editor is launched
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
this.Session.Attach(PowerToysModule.Hosts);
|
||||
this.Session.Attach(PowerToysModule.Hosts, WindowSize.Small_Vertical);
|
||||
|
||||
// Should show warning dialog
|
||||
Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
|
||||
@@ -68,7 +73,7 @@ namespace Hosts.UITests
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed after click Accept button in Warning Dialog");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
this.Session.CloseMainWindow();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
@@ -82,7 +87,7 @@ namespace Hosts.UITests
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
this.Session.CloseMainWindow();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using ModernWpf.Controls;
|
||||
using OpenQA.Selenium;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
using NavigationViewItem = Microsoft.PowerToys.UITest.NavigationViewItem;
|
||||
using ToggleSwitch = Microsoft.PowerToys.UITest.ToggleSwitch;
|
||||
|
||||
namespace Microsoft.FancyZones.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class LayoutApplyHotKeyTests : UITestBase
|
||||
{
|
||||
public LayoutApplyHotKeyTests()
|
||||
: base(PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly EditorParameters.ParamsWrapper Parameters = new EditorParameters.ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<EditorParameters.NativeMonitorDataWrapper>
|
||||
{
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 2,
|
||||
RowsPercentage = new List<int> { 5000, 5000 },
|
||||
ColumnsPercentage = new List<int> { 5000, 5000 },
|
||||
CellChildMap = new int[][] { [0, 1], [2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 26,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid-9",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 3,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2333, 3333, 4334 },
|
||||
ColumnsPercentage = new List<int> { 2333, 3333, 4334 },
|
||||
CellChildMap = new int[][] { [0, 1, 2], [3, 4, 5], [6, 7, 8] },
|
||||
SensitivityRadius = 20,
|
||||
Spacing = 3,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Canvas custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1040,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper>
|
||||
{
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 500,
|
||||
Height = 250,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 500,
|
||||
Y = 0,
|
||||
Width = 1420,
|
||||
Height = 500,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 250,
|
||||
Width = 1920,
|
||||
Height = 500,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutHotkeys.LayoutHotkeysWrapper LayoutHotkeysList = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper>
|
||||
{
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
Key = 0,
|
||||
LayoutId = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
},
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
Key = 1,
|
||||
LayoutId = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}",
|
||||
},
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
Key = 2,
|
||||
LayoutId = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutTemplates.TemplateLayoutsListWrapper TemplateLayoutsList = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(TemplateLayoutsList));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayoutsList));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = true,
|
||||
Spacing = 5,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Vertical.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
ZoneCount = 0,
|
||||
ShowSpacing = false,
|
||||
Spacing = 0,
|
||||
SensitivityRadius = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(layoutHotkeys.Serialize(LayoutHotkeysList));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestApplyHotKey()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
|
||||
// this.Find<ToggleSwitch>("Enable quick layout switch").Toggle(true);
|
||||
}
|
||||
|
||||
private void OpenFancyZonesPanel(bool launchAsAdmin = false)
|
||||
{
|
||||
// Goto FancyZones Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Windowing & Layouts").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||
|
||||
// launch Hosts File Editor
|
||||
// this.Find<Button>("Launch layout editor").Click();
|
||||
|
||||
// Task.Delay(500).Wait();
|
||||
|
||||
// this.Session.Attach(PowerToysModule.FancyZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,5 +27,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
<ProjectReference Include="..\editor\FancyZonesEditor\FancyZonesEditor.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
<ProjectReference Include="..\UITests-FancyZonesEditor\UITests-FancyZonesEditor.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -19,7 +19,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class ApplyLayoutTests : UITestBase
|
||||
{
|
||||
public ApplyLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
@@ -195,12 +196,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplyCustomLayout()
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class CopyLayoutTests : UITestBase
|
||||
{
|
||||
public CopyLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -172,12 +173,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyTemplate_FromEditLayoutWindow()
|
||||
{
|
||||
|
||||
@@ -16,13 +16,15 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class CreateLayoutTests : UITestBase
|
||||
{
|
||||
public CreateLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
|
||||
// prepare test editor parameters with 2 monitors before launching the editor
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
EditorParameters.ParamsWrapper parameters = new EditorParameters.ParamsWrapper
|
||||
@@ -132,12 +134,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateWithDefaultName()
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class CustomLayoutsTests : UITestBase
|
||||
{
|
||||
public CustomLayoutsTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(Layouts));
|
||||
|
||||
@@ -208,12 +209,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Name_Initialize()
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class DefaultLayoutsTest : UITestBase
|
||||
{
|
||||
public DefaultLayoutsTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
var defaultLayouts = new DefaultLayouts();
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(Layouts));
|
||||
|
||||
@@ -237,12 +238,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class DeleteLayoutTests : UITestBase
|
||||
{
|
||||
public DeleteLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
@@ -215,19 +216,13 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Click();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DeleteNotAppliedLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
@@ -245,7 +240,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[0].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
@@ -270,7 +266,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is not removed
|
||||
Assert.IsNotNull(Session.Find<Element>(deletedLayout));
|
||||
@@ -287,7 +283,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
@@ -304,7 +300,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the default layout is reset to the "default" default
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid]).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
@@ -323,7 +319,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the hotkey is available
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class EditLayoutTests : UITestBase
|
||||
{
|
||||
public EditLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -209,12 +210,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void OpenEditMode()
|
||||
{
|
||||
|
||||
@@ -25,13 +25,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class FirstLunchTest : UITestBase
|
||||
{
|
||||
public FirstLunchTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -141,12 +142,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstLaunch()
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class LayoutHotkeysTests : UITestBase
|
||||
{
|
||||
public LayoutHotkeysTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -206,12 +207,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class TestCaseFirstLaunch : UITestBase
|
||||
{
|
||||
public TestCaseFirstLaunch()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class RunFancyZonesEditorTest : UITestBase
|
||||
{
|
||||
public RunFancyZonesEditorTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -73,13 +73,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
};
|
||||
|
||||
public TemplateLayoutsTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -191,12 +192,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZoneNumber_Cancel()
|
||||
{
|
||||
|
||||
@@ -23,16 +23,10 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class UIInitializeTest : UITestBase
|
||||
{
|
||||
public UIInitializeTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EditorParams_VerifySelectedMonitor()
|
||||
{
|
||||
@@ -737,6 +731,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
|
||||
private void InitFileData()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user