Compare commits

..

2 Commits

Author SHA1 Message Date
Yu Leng (from Dev Box)
0a400c35e7 fix bug 2025-05-09 15:08:03 +08:00
Yu Leng (from Dev Box)
8d2185721e Init 2025-05-09 14:59:30 +08:00
163 changed files with 880 additions and 8824 deletions

View File

@@ -271,7 +271,6 @@ mengyuanchen
# DllName
testhost
Testably
#Tools
OIP

View File

@@ -315,7 +315,6 @@ debugbreak
declatory
decryptor
Dedup
Deeplink
DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR
DEFAULTFLAGS
@@ -327,7 +326,6 @@ DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
DEFPUSHBUTTON
DEFT
deinitialization
DELA
DELETEDKEYIMAGE
@@ -348,7 +346,6 @@ devmgmt
DEVMODE
DEVMODEW
devpal
DFX
DIALOGEX
digicert
dimm

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.PowerToys.Telemetry" version="2.0.3" />
<package id="Microsoft.PowerToys.Telemetry" version="2.0.2" />
</packages>

View File

@@ -3,5 +3,5 @@
"notificationAliases": ["powertoys@microsoft.com"],
"instanceUrl": "https://microsoft.visualstudio.com",
"projectName": "OS",
"areaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\PowerToys"
"areaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\DIVE\\PowerToys"
}

View File

@@ -112,12 +112,10 @@ extends:
# Prepare the localizations and telemetry config before the release build
- template: .pipelines/v2/templates/steps-fetch-and-prepare-localizations.yml@self
- pwsh: |-
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
& nuget.exe restore -configFile .pipelines/release-nuget.config -PackagesDirectory . .pipelines/packages.config
Move-Item -Force -Verbose "Microsoft.PowerToys.Telemetry.*\build\include\TraceLoggingDefines.h" "src\common\Telemetry\TraceLoggingDefines.h"
Move-Item -Force -Verbose "Microsoft.PowerToys.Telemetry.*\build\include\TelemetryBase.cs" "src\common\Telemetry\TelemetryBase.cs"
- script: |
call nuget.exe restore -configFile .pipelines/release-nuget.config -PackagesDirectory . .pipelines/packages.config || exit /b 1
move /Y "Microsoft.PowerToys.Telemetry.2.0.2\build\include\TraceLoggingDefines.h" "src\common\Telemetry\TraceLoggingDefines.h" || exit /b 1
move /Y "Microsoft.PowerToys.Telemetry.2.0.2\build\include\TelemetryBase.cs" "src\common\Telemetry\TelemetryBase.cs" || exit /b 1
displayName: Emplace telemetry files
- stage: Build_SDK

View File

@@ -50,9 +50,6 @@ parameters:
- name: runTests
type: boolean
default: true
- name: buildTests
type: boolean
default: true
- name: useVSPreview
type: boolean
default: false
@@ -113,7 +110,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 or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
${{ if eq(parameters.runTests, true) }}:
MSBuildMainBuildTargets: Build;Test
${{ else }}:
MSBuildMainBuildTargets: Build
@@ -522,7 +519,7 @@ jobs:
displayName: Stage GPO files
# Running the tests may result in future jobs consuming artifacts out of this build
- ${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
- ${{ if eq(parameters.runTests, true) }}:
- task: CopyFiles@2
displayName: Stage entire build output
inputs:

View File

@@ -11,36 +11,24 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: isUIAutomationPipeline
type: boolean
default: false
jobs:
- job: Test${{ parameters.platform }}${{ parameters.configuration }}
displayName: Test ${{ parameters.platform }} ${{ parameters.configuration }}
timeoutInMinutes: 300
variables:
${{ if or(eq(parameters.platform, 'x64Win10'), eq(parameters.platform, 'x64Win11')) }}:
BuildPlatform: x64
${{ else }}:
BuildPlatform: ${{ parameters.platform }}
TestPlatform: ${{ parameters.platform }}
BuildPlatform: ${{ parameters.platform }}
BuildConfiguration: ${{ parameters.configuration }}
SrcPath: $(Build.Repository.LocalPath)
TestArtifactsName: build-${{ variables.BuildPlatform }}-${{ parameters.configuration }}${{ parameters.inputArtifactStem }}
TestArtifactsName: build-${{ parameters.platform }}-${{ parameters.configuration }}${{ parameters.inputArtifactStem }}
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
${{ if ne(parameters.platform, 'ARM64') }}:
name: SHINE-INT-Testing-x64
${{ if and(eq(parameters.isUIAutomationPipeline, true), eq(parameters.platform, 'x64Win11')) }}:
demands: ImageOverride -equals SHINE-W11-Testing
${{ else }}:
name: SHINE-INT-Testing-arm64
${{ else }}:
${{ if ne(parameters.platform, 'ARM64') }}:
name: SHINE-OSS-Testing-x64
${{ if and(eq(parameters.isUIAutomationPipeline, true), eq(parameters.platform, 'x64Win11')) }}:
demands: ImageOverride -equals SHINE-W11-Testing
${{ else }}:
name: SHINE-OSS-Testing-arm64
steps:
@@ -113,16 +101,8 @@ jobs:
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
${{ if eq(parameters.isUIAutomationPipeline, true) }}:
testAssemblyVer2: |
**\*UITest*.dll
!**\obj\**
!**\ref\**
${{ else }}:
testAssemblyVer2: |
**\UITests-FancyZones.dll
**\UITests-FancyZonesEditor.dll
!**\obj\**
!**\ref\**
env:
platform: '$(TestPlatform)'
testAssemblyVer2: |
**\UITests-FancyZones.dll
**\UITests-FancyZonesEditor.dll
!**\obj\**
!**\ref\**

View File

@@ -1,87 +0,0 @@
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 }}
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win10
displayName: Test x64Win10
dependsOn:
- Build_${{platform}}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
isUIAutomationPipeline: true
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win11
displayName: Test x64Win11
dependsOn:
- Build_${{platform}}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
isUIAutomationPipeline: true
- ${{ if ne(platform, 'x64') }}:
- 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

View File

@@ -91,4 +91,5 @@ 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

View File

@@ -31,22 +31,22 @@
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.5" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.4" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.5" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.5" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.15.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.5" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.4" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.5" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="9.0.4" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -72,28 +72,28 @@
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.5" />
<PackageVersion Include="System.CodeDom" Version="9.0.4" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.5" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.5" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.5" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.4" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.4" />
<PackageVersion Include="System.Data.OleDb" Version="9.0.4" />
<!-- Package System.Data.SqlClient added to force it as a dependency of Microsoft.Windows.Compatibility to the latest version available at this time. -->
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" />
<!-- Package System.Diagnostics.EventLog added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.5" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="9.0.4" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.5" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.5" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="9.0.4" />
<PackageVersion Include="System.Drawing.Common" Version="9.0.4" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.5" />
<PackageVersion Include="System.Management" Version="9.0.4" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.5" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.5" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.5" />
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.4" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="9.0.4" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.4" />
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />

View File

@@ -1453,22 +1453,22 @@ SOFTWARE.
- Mages 3.0.0
- Markdig.Signed 0.34.0
- MessagePack 3.1.3
- Microsoft.Bcl.AsyncInterfaces 9.0.5
- Microsoft.Bcl.AsyncInterfaces 9.0.4
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
- Microsoft.Data.Sqlite 9.0.5
- Microsoft.Data.Sqlite 9.0.4
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
- Microsoft.DotNet.ILCompiler (A)
- Microsoft.Extensions.DependencyInjection 9.0.5
- Microsoft.Extensions.Hosting 9.0.5
- Microsoft.Extensions.Hosting.WindowsServices 9.0.5
- Microsoft.Extensions.Logging 9.0.5
- Microsoft.Extensions.Logging.Abstractions 9.0.5
- Microsoft.Extensions.DependencyInjection 9.0.4
- Microsoft.Extensions.Hosting 9.0.4
- Microsoft.Extensions.Hosting.WindowsServices 9.0.4
- Microsoft.Extensions.Logging 9.0.4
- Microsoft.Extensions.Logging.Abstractions 9.0.4
- Microsoft.NET.ILLink.Tasks (A)
- Microsoft.SemanticKernel 1.15.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2903.40
- Microsoft.Win32.SystemEvents 9.0.5
- Microsoft.Windows.Compatibility 9.0.5
- Microsoft.Win32.SystemEvents 9.0.4
- Microsoft.Windows.Compatibility 9.0.4
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
@@ -1487,25 +1487,25 @@ SOFTWARE.
- SharpCompress 0.37.2
- StreamJsonRpc 2.21.69
- StyleCop.Analyzers 1.2.0-beta.556
- System.CodeDom 9.0.5
- System.CodeDom 9.0.4
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 9.0.5
- System.Configuration.ConfigurationManager 9.0.5
- System.Data.OleDb 9.0.5
- System.ComponentModel.Composition 9.0.4
- System.Configuration.ConfigurationManager 9.0.4
- System.Data.OleDb 9.0.4
- System.Data.SqlClient 4.8.6
- System.Diagnostics.EventLog 9.0.5
- System.Diagnostics.PerformanceCounter 9.0.5
- System.Drawing.Common 9.0.5
- System.Diagnostics.EventLog 9.0.4
- System.Diagnostics.PerformanceCounter 9.0.4
- System.Drawing.Common 9.0.4
- System.IO.Abstractions 22.0.13
- System.IO.Abstractions.TestingHelpers 22.0.13
- System.Management 9.0.5
- System.Management 9.0.4
- System.Net.Http 4.3.4
- System.Private.Uri 4.3.2
- System.Reactive 6.0.1
- System.Runtime.Caching 9.0.5
- System.ServiceProcess.ServiceController 9.0.5
- System.Text.Encoding.CodePages 9.0.5
- System.Text.Json 9.0.5
- System.Runtime.Caching 9.0.4
- System.ServiceProcess.ServiceController 9.0.4
- System.Text.Encoding.CodePages 9.0.4
- System.Text.Json 9.0.4
- System.Text.RegularExpressions 4.3.1
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0

View File

@@ -710,10 +710,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "sr
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseUtils.UITests", "src\modules\MouseUtils\MouseUtils.UITests\MouseUtils.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesEditorUITest", "src\modules\Workspaces\WorkspacesEditorUITest\WorkspacesEditorUITest.csproj", "{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2586,22 +2582,6 @@ Global
{64B88F02-CD88-4ED8-9624-989A800230F9}.Release|ARM64.Build.0 = Release|ARM64
{64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.ActiveCfg = Release|x64
{64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.Build.0 = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.Build.0 = Debug|ARM64
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|x64.ActiveCfg = Debug|x64
@@ -2755,7 +2735,7 @@ Global
{25C91A4E-BA4E-467A-85CD-8B62545BF674} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
{212AD910-8488-4036-BE20-326931B75FB2} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{7AC943C9-52E8-44CF-9083-744D8049667B} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{7AC943C9-52E8-44CF-9083-744D8049667B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{54A93AF7-60C7-4F6C-99D2-FBB1F75F853A} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{92C39820-9F84-4529-BC7D-22AAE514D63B} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{515554D1-D004-4F7F-A107-2211FC0F6B2C} = {7AC943C9-52E8-44CF-9083-744D8049667B}
@@ -2886,8 +2866,6 @@ Global
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{5702B3CC-8575-48D5-83D8-15BB42269CD3} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{64B88F02-CD88-4ED8-9624-989A800230F9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
EndGlobalSection

View File

@@ -21,74 +21,67 @@
- Create a new project and add the following references to the project file. Change the OutputPath to your own module's path.
```
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<ProjectGuid>{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}</ProjectGuid>
<RootNamespace>PowerToys.Hosts.UITests</RootNamespace>
<AssemblyName>PowerToys.Hosts.UITests</AssemblyName>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<!-- This is a UI test, so don't run as part of MSBuild -->
<RunVSTest>false</RunVSTest>
</PropertyGroup>
<PropertyGroup>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
</Project>
<PropertyGroup>
<OutputType>Library</OutputType>
<!-- This is a UI test, so don't run as part of MSBuild -->
<RunVSTest>false</RunVSTest>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\KeyboardManagerUITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
<Folder Include="Properties\" />
</ItemGroup>
```
- Inherit your test class from UITestBase.
>Set Scope: The default scope starts from the PowerToys settings UI. If you want to start from your own module, set the constructor as shown below:
>Specify Scope:
```
[TestClass]
public class HostModuleTests : UITestBase
{
public HostModuleTests()
: base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
{
}
}
[TestClass]
public class RunFancyZonesTest : UITestBase
{
public RunFancyZonesTest()
: base(PowerToysModule.FancyZone)
{
}
}
```
- Then you can start performing the UI operations.
- Then you can start using session to perform the UI operations.
**Example**
```
[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
[TestCategory("Hosts File Editor #4")]
public void TestEmptyView()
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UITests_KeyboardManager
{
this.CloseWarningDialog();
this.RemoveAllEntries();
[TestClass]
public class RunKeyboardManagerUITests : UITestBase
{
[TestMethod]
public void OpenKeyboardManagerEditor()
{
// Open KeyboardManagerEditor
this.Session.Find<Button>(By.Name("Remap a key")).Click();
this.Session.Attach("Remap keys");
// 'Add an entry' button (only show-up when list is empty) should be visible
Assert.IsTrue(this.HasOne<HyperlinkButton>("Add an entry"), "'Add an entry' button should be visible in the empty view");
// Maximize window
var window = Session.Find<Window>(By.Name("Remap keys")).Maximize();
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "EmptyView");
// Add Key Remapping
this.Session.Find<Button>(By.Name("Add key remapping")).Click();
window.Close();
// Click 'Add an entry' from empty-view for adding Host override rule
this.Find<HyperlinkButton>("Add an entry").Click();
this.AddEntry("192.168.0.1", "localhost", false, false);
// Should have one row now and not more 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");
// Back to Settings
this.Session.Attach(PowerToysModule.PowerToysSettings);
}
}
}
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +0,0 @@
// 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.
namespace Microsoft.PowerToys.UITest
{
public class CheckBox : Element
{
private static readonly string ExpectedControlType = "ControlType.CheckBox";
/// <summary>
/// Initializes a new instance of the <see cref="CheckBox"/> class.
/// </summary>
public CheckBox()
{
this.TargetControlType = CheckBox.ExpectedControlType;
}
/// <summary>
/// Select the item of the ComboBox.
/// </summary>
/// <param name="value">The text to select from the list view.</param>
public void Select(string value)
{
this.Find<NavigationViewItem>(value).Click();
}
}
}

View File

@@ -1,28 +0,0 @@
// 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.
namespace Microsoft.PowerToys.UITest
{
public class ComboBox : Element
{
private static readonly string ExpectedControlType = "ControlType.ComboBox";
/// <summary>
/// Initializes a new instance of the <see cref="ComboBox"/> class.
/// </summary>
public ComboBox()
{
this.TargetControlType = ComboBox.ExpectedControlType;
}
/// <summary>
/// Select the item of the ComboBox.
/// </summary>
/// <param name="value">The text to select from the list view.</param>
public void Select(string value)
{
this.Find<NavigationViewItem>(value).Click();
}
}
}

View File

@@ -1,74 +0,0 @@
// 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.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.PowerToys.UITest
{
public class Custom : Element
{
private static readonly string ExpectedControlType = "ControlType.Custom";
/// <summary>
/// Initializes a new instance of the <see cref="Custom"/> class.
/// </summary>
public Custom()
{
this.TargetControlType = Custom.ExpectedControlType;
}
/// <summary>
/// Sends a combination of keys.
/// </summary>
/// <param name="keys">The keys to send.</param>
public void SendKeys(params Key[] keys)
{
PerformAction((actions, windowElement) =>
{
KeyboardHelper.SendKeys(keys);
});
}
/// <summary>
/// Drag element move offset.
/// </summary>
/// <param name="offsetX">The offsetX to move.</param>
/// <param name="offsetY">The offsetY to move.</param>
public void Drag(int offsetX, int offsetY)
{
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY).Release();
actions.Build().Perform();
});
}
/// <summary>
/// Drag element move to other element.
/// </summary>
/// <param name="element">Move to this element.</param>
public void Drag(Element element)
{
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement).ClickAndHold();
Assert.IsNotNull(element.WindowsElement, "element is null");
int dx = (element.WindowsElement.Rect.X - windowElement.Rect.X) / 10;
int dy = (element.WindowsElement.Rect.Y - windowElement.Rect.Y) / 10;
for (int i = 0; i < 10; i++)
{
actions.MoveByOffset(dx, dy);
}
actions.Release();
actions.Build().Perform();
});
}
}
}

View File

@@ -7,7 +7,6 @@ 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;
@@ -26,20 +25,8 @@ namespace Microsoft.PowerToys.UITest
{
private WindowsElement? windowsElement;
protected internal WindowsElement? WindowsElement
{
get => windowsElement;
set => windowsElement = value;
}
private WindowsDriver<WindowsElement>? driver;
protected internal WindowsDriver<WindowsElement>? Driver
{
get => driver;
set => driver = value;
}
protected string? TargetControlType { get; set; }
internal bool IsMatchingTarget()
@@ -125,10 +112,9 @@ namespace Microsoft.PowerToys.UITest
/// Click the UI element.
/// </summary>
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
public virtual void Click(bool rightClick = false, int msPreAction = 500, int msPostAction = 500)
public virtual void Click(bool rightClick = false)
{
PerformAction(
(actions, windowElement) =>
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement);
@@ -145,9 +131,7 @@ namespace Microsoft.PowerToys.UITest
}
actions.Build().Perform();
},
msPreAction,
msPostAction);
});
}
/// <summary>
@@ -168,20 +152,51 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Release action
/// Drag element move offset.
/// </summary>
public void ReleaseAction()
/// <param name="offsetX">The offsetX to move.</param>
/// <param name="offsetY">The offsetY to move.</param>
public void Drag(int offsetX, int offsetY)
{
var releaseAction = new Actions(driver);
releaseAction.Release().Perform();
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY).Release();
actions.Build().Perform();
});
}
/// <summary>
/// Release key
/// Drag element move to other element.
/// </summary>
public void ReleaseKey(Key key)
/// <param name="element">Move to this element.</param>
public void Drag(Element element)
{
KeyboardHelper.ReleaseKey(key);
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement).ClickAndHold();
Assert.IsNotNull(element.windowsElement, "element is null");
int dx = (element.windowsElement.Rect.X - windowElement.Rect.X) / 10;
int dy = (element.windowsElement.Rect.Y - windowElement.Rect.Y) / 10;
for (int i = 0; i < 10; i++)
{
actions.MoveByOffset(dx, dy);
}
actions.Release();
actions.Build().Perform();
});
}
/// <summary>
/// Send Key of the element.
/// </summary>
/// <param name="key">The Key to Send.</param>
public void SendKeys(string key)
{
PerformAction((actions, windowElement) =>
{
windowElement.SendKeys(key);
});
}
/// <summary>
@@ -203,7 +218,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 = 5000)
public T Find<T>(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
@@ -211,7 +226,7 @@ namespace Microsoft.PowerToys.UITest
// leverage findAll to filter out mismatched elements
var collection = this.FindAll<T>(by, timeoutMS);
Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}");
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
return collection[0];
}
@@ -224,7 +239,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 = 5000)
public T Find<T>(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.Find<T>(By.Name(name), timeoutMS);
@@ -237,7 +252,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 = 5000)
public Element Find(By by, int timeoutMS = 3000)
{
return this.Find<Element>(by, timeoutMS);
}
@@ -249,7 +264,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 = 5000)
public Element Find(string name, int timeoutMS = 3000)
{
return this.Find<Element>(By.Name(name), timeoutMS);
}
@@ -261,7 +276,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 = 5000)
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
@@ -293,7 +308,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 = 5000)
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.FindAll<T>(By.Name(name), timeoutMS);
@@ -306,7 +321,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 = 5000)
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
{
return this.FindAll<Element>(by, timeoutMS);
}
@@ -318,23 +333,11 @@ 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 = 5000)
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
{
return this.FindAll<Element>(By.Name(name), timeoutMS);
}
/// <summary>
/// Send Key of the element.
/// </summary>
/// <param name="key">The Key to Send.</param>
protected void SendKeys(string key)
{
PerformAction((actions, windowElement) =>
{
windowElement.SendKeys(key);
});
}
/// <summary>
/// Simulates a manual operation on the element.
/// </summary>
@@ -357,15 +360,5 @@ 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);
}
}
}

View File

@@ -1,25 +0,0 @@
// 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.Text;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.UITest
{
public class Group : Element
{
private static readonly string ExpectedControlType = "ControlType.Group";
/// <summary>
/// Initializes a new instance of the <see cref="Group"/> class.
/// </summary>
public Group()
{
this.TargetControlType = Group.ExpectedControlType;
}
}
}

View File

@@ -24,12 +24,9 @@ namespace Microsoft.PowerToys.UITest
/// Click the ListItem element.
/// </summary>
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
/// <param name="msPreAction">Pre action delay in milliseconds. Default value is 500</param>
/// <param name="msPostAction">Post action delay in milliseconds. Default value is 500</param>
public override void Click(bool rightClick = false, int msPreAction = 500, int msPostAction = 500)
public override void Click(bool rightClick = false)
{
PerformAction(
(actions, windowElement) =>
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement, 10, 10);
@@ -43,36 +40,7 @@ namespace Microsoft.PowerToys.UITest
}
actions.Build().Perform();
},
msPreAction,
msPostAction);
}
/// <summary>
/// Click the ListItem element.
/// </summary>
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
/// <param name="msPreAction">Pre action delay in milliseconds. Default value is 500</param>
/// <param name="msPostAction">Post action delay in milliseconds. Default value is 500</param>
public void ClickCenter(bool rightClick = false, int msPreAction = 500, int msPostAction = 500)
{
PerformAction(
(actions, windowElement) =>
{
actions.MoveToElement(windowElement);
if (rightClick)
{
actions.ContextClick();
}
else
{
actions.Click();
}
actions.Build().Perform();
},
msPreAction,
msPostAction);
});
}
/// <summary>

View File

@@ -1,67 +0,0 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Interactions;
namespace Microsoft.PowerToys.UITest
{
public class Pane : Element
{
private static readonly string ExpectedControlType = "ControlType.Pane";
/// <summary>
/// Initializes a new instance of the <see cref="Pane"/> class.
/// </summary>
public Pane()
{
this.TargetControlType = Pane.ExpectedControlType;
}
/// <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)
{
HoldShiftToDrag(key, targetX, targetY);
ReleaseAction();
ReleaseKey(key);
}
/// <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 HoldShiftToDrag(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();
}
});
}
}
}

View File

@@ -1,131 +0,0 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
namespace Microsoft.PowerToys.UITest
{
public class Slider : Element
{
private static readonly string ExpectedControlType = "ControlType.Slider";
/// <summary>
/// Initializes a new instance of the <see cref="Slider"/> class.
/// </summary>
public Slider()
{
this.TargetControlType = Slider.ExpectedControlType;
}
/// <summary>
/// Gets the value of a Slider (WindowsElement)
/// </summary>
/// <returns>The integer value of the slider</returns>
public int GetValue()
{
return this.Text == string.Empty ? 0 : int.Parse(this.Text);
}
/// <summary>
/// Sends a combination of keys.
/// </summary>
/// <param name="keys">The keys to send.</param>
public void SendKeys(params Key[] keys)
{
PerformAction((actions, windowElement) =>
{
KeyboardHelper.SendKeys(keys);
});
}
/// <summary>
/// Sets the value of a Slider (WindowsElement) to the specified integer value.
/// Throws an exception if the value is out of the slider's valid range.
/// </summary>
/// <param name="targetValue">The target integer value to set</param>
public void SetValue(int targetValue)
{
// Read range and current value
int min = int.Parse(this.GetAttribute("RangeValue.Minimum"));
int max = int.Parse(this.GetAttribute("RangeValue.Maximum"));
int current = int.Parse(this.Text);
// Use Assert to check if the target value is within the valid range
Assert.IsTrue(
targetValue >= min && targetValue <= max,
$"Target value {targetValue} is out of range (min: {min}, max: {max}).");
// Compute difference
int diff = targetValue - current;
if (diff == 0)
{
return;
}
string key = diff > 0 ? OpenQA.Selenium.Keys.Right : OpenQA.Selenium.Keys.Left;
int steps = Math.Abs(diff);
for (int i = 0; i < steps; i++)
{
this.SendKeys(key);
// Thread.Sleep(2);
}
// Final check
int finalValue = int.Parse(this.Text);
Assert.AreEqual(
targetValue, finalValue, $"Slider value mismatch: expected {targetValue}, but got {finalValue}.");
}
/// <summary>
/// Sets the value of a Slider (WindowsElement) to the specified integer value.
/// Throws an exception if the value is out of the slider's valid range.
/// </summary>
/// <param name="targetValue">The target integer value to set</param>
public void QuickSetValue(int targetValue)
{
// Read range and current value
int min = int.Parse(this.GetAttribute("RangeValue.Minimum"));
int max = int.Parse(this.GetAttribute("RangeValue.Maximum"));
int current = int.Parse(this.Text);
// Use Assert to check if the target value is within the valid range
Assert.IsTrue(
targetValue >= min && targetValue <= max,
$"Target value {targetValue} is out of range (min: {min}, max: {max}).");
// Compute difference
int diff = targetValue - current;
if (diff == 0)
{
return;
}
string key = diff > 0 ? OpenQA.Selenium.Keys.Right : OpenQA.Selenium.Keys.Left;
int steps = Math.Abs(diff);
int maxKeysPerSend = 50;
int fullChunks = steps / maxKeysPerSend;
int remainder = steps % maxKeysPerSend;
for (int i = 0; i < fullChunks; i++)
{
SendKeys(new string(key[0], maxKeysPerSend));
Thread.Sleep(2);
}
if (remainder > 0)
{
SendKeys(new string(key[0], remainder));
Thread.Sleep(2);
}
// Final check
int finalValue = int.Parse(this.Text);
Assert.AreEqual(
targetValue, finalValue, $"Slider value mismatch: expected {targetValue}, but got {finalValue}.");
}
}
}

View File

@@ -1,67 +0,0 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Interactions;
namespace Microsoft.PowerToys.UITest
{
public class Tab : Element
{
private static readonly string ExpectedControlType = "ControlType.Tab";
/// <summary>
/// Initializes a new instance of the <see cref="Tab"/> class.
/// </summary>
public Tab()
{
this.TargetControlType = Tab.ExpectedControlType;
}
/// <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)
{
HoldShiftToDrag(key, targetX, targetY);
ReleaseAction();
ReleaseKey(key);
}
/// <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 HoldShiftToDrag(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();
}
});
}
}
}

View File

@@ -2,6 +2,8 @@
// 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>
@@ -33,10 +35,9 @@ namespace Microsoft.PowerToys.UITest
PerformAction((actions, windowElement) =>
{
// select all text and delete it
windowElement.SendKeys(OpenQA.Selenium.Keys.Control + "a");
windowElement.SendKeys(OpenQA.Selenium.Keys.Delete);
windowElement.SendKeys(Keys.Control + "a");
windowElement.SendKeys(Keys.Delete);
});
Task.Delay(500).Wait();
}
PerformAction((actions, windowElement) =>

View File

@@ -1,36 +0,0 @@
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
namespace Microsoft.PowerToys.UITest
{
public class Thumb : Element
{
private static readonly string ExpectedControlType = "ControlType.Thumb";
/// <summary>
/// Initializes a new instance of the <see cref="Thumb"/> class.
/// </summary>
public Thumb()
{
this.TargetControlType = Thumb.ExpectedControlType;
}
/// <summary>
/// Drag element move offset.
/// </summary>
/// <param name="offsetX">The offsetX to move.</param>
/// <param name="offsetY">The offsetY to move.</param>
public void Drag(int offsetX, int offsetY)
{
PerformAction((actions, windowElement) =>
{
actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY).Release();
actions.Build().Perform();
});
}
}
}

View File

@@ -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 = FindElementsWithRetry(findElementsFunc, timeoutMS);
var items = findElementsFunc();
var res = items.Select(item =>
{
var element = item as WindowsElement;
@@ -43,27 +43,6 @@ 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()
{
@@ -71,6 +50,11 @@ 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);

View File

@@ -1,475 +0,0 @@
// 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,
LCtrl,
RCtrl,
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));
}
public static void SendKey(Key key)
{
PressVirtualKey(TranslateKeyHex(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.LCtrl:
return "^";
case Key.RCtrl:
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
case Key.LCtrl:
return 0xA2; // Left Ctrl Key - 0xA2 in hex
case Key.RCtrl:
return 0xA3; // Right Ctrl Key - 0xA3 in hex
case Key.A:
return 0x41; // A Key - 0x41 in hex
case Key.B:
return 0x42; // B Key - 0x42 in hex
case Key.C:
return 0x43; // C Key - 0x43 in hex
case Key.D:
return 0x44; // D Key - 0x44 in hex
case Key.E:
return 0x45; // E Key - 0x45 in hex
case Key.F:
return 0x46; // F Key - 0x46 in hex
case Key.G:
return 0x47; // G Key - 0x47 in hex
case Key.H:
return 0x48; // H Key - 0x48 in hex
case Key.I:
return 0x49; // I Key - 0x49 in hex
case Key.J:
return 0x4A; // J Key - 0x4A in hex
case Key.K:
return 0x4B; // K Key - 0x4B in hex
case Key.L:
return 0x4C; // L Key - 0x4C in hex
case Key.M:
return 0x4D; // M Key - 0x4D in hex
case Key.N:
return 0x4E; // N Key - 0x4E in hex
case Key.O:
return 0x4F; // O Key - 0x4F in hex
case Key.P:
return 0x50; // P Key - 0x50 in hex
case Key.Q:
return 0x51; // Q Key - 0x51 in hex
case Key.R:
return 0x52; // R Key - 0x52 in hex
case Key.S:
return 0x53; // S Key - 0x53 in hex
case Key.T:
return 0x54; // T Key - 0x54 in hex
case Key.U:
return 0x55; // U Key - 0x55 in hex
case Key.V:
return 0x56; // V Key - 0x56 in hex
case Key.W:
return 0x57; // W Key - 0x57 in hex
case Key.X:
return 0x58; // X Key - 0x58 in hex
case Key.Y:
return 0x59; // Y Key - 0x59 in hex
case Key.Z:
return 0x5A; // Z Key - 0x5A in hex
case Key.Num0:
return 0x30; // 0 Key - 0x30 in hex
case Key.Num1:
return 0x31; // 1 Key - 0x31 in hex
case Key.Num2:
return 0x32; // 2 Key - 0x32 in hex
case Key.Num3:
return 0x33; // 3 Key - 0x33 in hex
case Key.Num4:
return 0x34; // 4 Key - 0x34 in hex
case Key.Num5:
return 0x35; // 5 Key - 0x35 in hex
case Key.Num6:
return 0x36; // 6 Key - 0x36 in hex
case Key.Num7:
return 0x37; // 7 Key - 0x37 in hex
case Key.Num8:
return 0x38; // 8 Key - 0x38 in hex
case Key.Num9:
return 0x39; // 9 Key - 0x39 in hex
case Key.F1:
return 0x70; // F1 Key - 0x70 in hex
case Key.F2:
return 0x71; // F2 Key - 0x71 in hex
case Key.F3:
return 0x72; // F3 Key - 0x72 in hex
case Key.F4:
return 0x73; // F4 Key - 0x73 in hex
case Key.F5:
return 0x74; // F5 Key - 0x74 in hex
case Key.F6:
return 0x75; // F6 Key - 0x75 in hex
case Key.F7:
return 0x76; // F7 Key - 0x76 in hex
case Key.F8:
return 0x77; // F8 Key - 0x77 in hex
case Key.F9:
return 0x78; // F9 Key - 0x78 in hex
case Key.F10:
return 0x79; // F10 Key - 0x79 in hex
case Key.F11:
return 0x7A; // F11 Key - 0x7A in hex
case Key.F12:
return 0x7B; // F12 Key - 0x7B in hex
case Key.Up:
return 0x26; // Up Arrow Key - 0x26 in hex
case Key.Down:
return 0x28; // Down Arrow Key - 0x28 in hex
case Key.Left:
return 0x25; // Left Arrow Key - 0x25 in hex
case Key.Right:
return 0x27; // Right Arrow Key - 0x27 in hex
case Key.Home:
return 0x24; // Home Key - 0x24 in hex
case Key.End:
return 0x23; // End Key - 0x23 in hex
case Key.PageUp:
return 0x21; // Page Up Key - 0x21 in hex
case Key.PageDown:
return 0x22; // Page Down Key - 0x22 in hex
case Key.Space:
return 0x20; // Space Key - 0x20 in hex
case Key.Enter:
return 0x0D; // Enter Key - 0x0D in hex
case Key.Backspace:
return 0x08; // Backspace Key - 0x08 in hex
case Key.Tab:
return 0x09; // Tab Key - 0x09 in hex
case Key.Esc:
return 0x1B; // Escape Key - 0x1B in hex
case Key.Insert:
return 0x2D; // Insert Key - 0x2D in hex
case Key.Delete:
return 0x2E; // Delete Key - 0x2E 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);
}
}
}

View File

@@ -29,49 +29,6 @@ namespace Microsoft.PowerToys.UITest
PowerToysSettings,
FancyZone,
Hosts,
Runner,
Workspaces,
}
/// <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
@@ -95,8 +52,6 @@ namespace Microsoft.PowerToys.UITest
[PowerToysModule.PowerToysSettings] = "PowerToys Settings",
[PowerToysModule.FancyZone] = "FancyZones Layout",
[PowerToysModule.Hosts] = "Hosts File Editor",
[PowerToysModule.Runner] = "PowerToys",
[PowerToysModule.Workspaces] = "Workspaces Editor",
};
// Exe start path for the module if it exists.
@@ -105,8 +60,6 @@ namespace Microsoft.PowerToys.UITest
[PowerToysModule.PowerToysSettings] = @"\..\..\..\WinUI3Apps\PowerToys.Settings.exe",
[PowerToysModule.FancyZone] = @"\..\..\..\PowerToys.FancyZonesEditor.exe",
[PowerToysModule.Hosts] = @"\..\..\..\WinUI3Apps\PowerToys.Hosts.exe",
[PowerToysModule.Runner] = @"\..\..\..\PowerToys.exe",
[PowerToysModule.Workspaces] = @"\..\..\..\PowerToys.WorkspacesEditor.exe",
};
}

View File

@@ -1,37 +0,0 @@
// 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;
namespace Microsoft.PowerToys.UITest
{
public class MonitorInfoData
{
public MonitorInfoData()
{
}
public struct MonitorInfoDataWrapper
{
public string DeviceName { get; set; }
public string DeviceString { get; set; }
public string DeviceID { get; set; }
public string DeviceKey { get; set; }
public int PelsWidth { get; set; }
public int PelsHeight { get; set; }
public int DisplayFrequency { get; set; }
}
public struct ParamsWrapper
{
public List<MonitorInfoDataWrapper> Monitors { get; set; }
}
}
}

View File

@@ -1,217 +0,0 @@
// 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
{
public enum MouseActionType
{
LeftClick,
RightClick,
MiddleClick,
LeftDoubleClick,
RightDoubleClick,
LeftDown,
LeftUp,
RightDown,
RightUp,
MiddleDown,
MiddleUp,
ScrollUp,
ScrollDown,
}
internal static class MouseHelper
{
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[Flags]
internal enum MouseEvent
{
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
Wheel = 0x0800,
}
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll")]
public static extern bool SetCursorPos(int x, int y);
[DllImport("user32.dll")]
#pragma warning disable SA1300 // Element should begin with upper-case letter
private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);
#pragma warning restore SA1300 // Element should begin with upper-case letter
        /// <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);
}
/// <summary>
/// The delay in milliseconds between mouse down and up events to simulate a click.
/// </summary>
private const int ClickDelay = 100;
/// <summary>
/// The amount of scroll units to simulate a single mouse wheel tick.
/// </summary>
private const int ScrollAmount = 120;
/// <summary>
/// Simulates a left mouse click (press and release).
/// </summary>
public static void LeftClick()
{
LeftDown();
Thread.Sleep(ClickDelay);
LeftUp();
}
/// <summary>
/// Simulates a right mouse click (press and release).
/// </summary>
public static void RightClick()
{
RightDown();
Thread.Sleep(ClickDelay);
RightUp();
}
/// <summary>
/// Simulates a middle mouse click (press and release).
/// </summary>
public static void MiddleClick()
{
MiddleDown();
Thread.Sleep(ClickDelay);
MiddleUp();
}
/// <summary>
/// Simulates a left mouse double-click.
/// </summary>
public static void LeftDoubleClick()
{
LeftClick();
Thread.Sleep(ClickDelay);
LeftClick();
}
/// <summary>
/// Simulates a right mouse double-click.
/// </summary>
public static void RightDoubleClick()
{
RightClick();
Thread.Sleep(ClickDelay);
RightClick();
}
/// <summary>
/// Simulates pressing the left mouse button down.
/// </summary>
public static void LeftDown()
{
mouse_event((uint)MouseEvent.LeftDown, 0, 0, 0, UIntPtr.Zero);
}
/// <summary>
/// Simulates pressing the right mouse button down.
/// </summary>
public static void RightDown()
{
mouse_event((uint)MouseEvent.RightDown, 0, 0, 0, UIntPtr.Zero);
}
/// <summary>
/// Simulates pressing the middle mouse button down.
/// </summary>
public static void MiddleDown()
{
mouse_event((uint)MouseEvent.MiddleDown, 0, 0, 0, UIntPtr.Zero);
}
/// <summary>
/// Simulates releasing the left mouse button.
/// </summary>
public static void LeftUp()
{
mouse_event((uint)MouseEvent.LeftUp, 0, 0, 0, UIntPtr.Zero);
}
/// <summary>
/// Simulates releasing the right mouse button.
/// </summary>
public static void RightUp()
{
mouse_event((uint)MouseEvent.RightUp, 0, 0, 0, UIntPtr.Zero);
}
/// <summary>
/// Simulates releasing the middle mouse button.
/// </summary>
public static void MiddleUp()
{
mouse_event((uint)MouseEvent.MiddleUp, 0, 0, 0, UIntPtr.Zero);
}
/// <summary>
/// Simulates a mouse scroll wheel action by a specified amount.
/// Positive values scroll up, negative values scroll down.
/// </summary>
/// <param name="amount">The scroll amount. Typically 120 or -120 per tick.</param>
public static void ScrollWheel(int amount)
{
mouse_event((uint)MouseEvent.Wheel, 0, 0, (uint)amount, UIntPtr.Zero);
}
/// <summary>
/// Simulates scrolling the mouse wheel up by one tick.
/// </summary>
public static void ScrollUp()
{
ScrollWheel(ScrollAmount);
}
/// <summary>
/// Simulates scrolling the mouse wheel down by one tick.
/// </summary>
public static void ScrollDown()
{
ScrollWheel(-ScrollAmount);
}
}
}

View File

@@ -1,133 +0,0 @@
// 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);
}
}
}

View File

@@ -3,14 +3,12 @@
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Interactions;
using static Microsoft.PowerToys.UITest.WindowHelper;
namespace Microsoft.PowerToys.UITest
{
@@ -19,70 +17,35 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
public class Session
{
public WindowsDriver<WindowsElement> Root { get; set; }
private WindowsDriver<WindowsElement> Root { get; set; }
private WindowsDriver<WindowsElement> WindowsDriver { get; set; }
private List<IntPtr> windowHandlers = new List<IntPtr>();
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(nint hWnd);
private Window? MainWindow { get; set; }
/// <summary>
/// Gets Main Window Handler
/// </summary>
public IntPtr MainWindowHandler { get; private set; }
/// <summary>
/// Gets Init Scope
/// </summary>
public PowerToysModule InitScope { 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)
public Session(WindowsDriver<WindowsElement> root, WindowsDriver<WindowsElement> windowsDriver)
{
this.MainWindowHandler = IntPtr.Zero;
this.Root = root;
this.WindowsDriver = windowsDriver;
this.InitScope = scope;
if (size != WindowSize.UnSpecified)
{
// Attach to the scope & reset MainWindowHandler
this.Attach(scope, size);
}
}
/// <summary>
/// Cleans up the Session Exe.
/// </summary>
public void Cleanup()
{
windowHandlers.Clear();
}
/// <summary>
/// Finds an Element or its derived class by selector.
/// Finds an element 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>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
public T Find<T>(By by, int timeoutMS = 5000, bool global = false)
public T Find<T>(By by, int timeoutMS = 3000)
where T : Element, new()
{
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
// leverage findAll to filter out mismatched elements
var collection = this.FindAll<T>(by, timeoutMS, global);
var collection = this.FindAll<T>(by, timeoutMS);
Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}");
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
return collection[0];
}
@@ -92,159 +55,62 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
public T Find<T>(string name, int timeoutMS = 5000, bool global = false)
public T Find<T>(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.Find<T>(By.Name(name), timeoutMS, global);
return this.Find<T>(By.Name(name), timeoutMS);
}
/// <summary>
/// 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
public Element Find(By by, int timeoutMS = 5000, bool global = false)
public Element Find(By by, int timeoutMS = 3000)
{
return this.Find<Element>(by, timeoutMS, global);
return this.Find<Element>(by, timeoutMS);
}
/// <summary>
/// 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
public Element Find(string name, int timeoutMS = 5000, bool global = false)
public Element Find(string name, int timeoutMS = 3000)
{
return this.Find<Element>(By.Name(name), timeoutMS, global);
return this.Find<Element>(By.Name(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, bool global = false)
where T : Element, new()
{
return this.FindAll<T>(by, timeoutMS, global).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, bool global = false)
{
return this.HasOne<Element>(by, timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.HasOne<T>(By.Name(name), timeoutMS, global);
}
/// <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, bool global = false)
{
return this.HasOne<Element>(By.Name(name), timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.FindAll<T>(by, timeoutMS, global).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, bool global = false)
{
return this.Has<Element>(by, timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.Has<T>(By.Name(name), timeoutMS, global);
}
/// <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, bool global = false)
{
return this.Has<Element>(name, timeoutMS, global);
}
/// <summary>
/// Finds all Element or its derived class by selector.
/// Finds all elements 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000, bool global = false)
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
where T : Element, new()
{
var driver = global ? this.Root : this.WindowsDriver;
Assert.IsNotNull(driver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
var foundElements = FindHelper.FindAll<T, WindowsElement>(
() =>
{
if (by.GetIsAccessibilityId())
{
var elements = driver.FindElementsByAccessibilityId(by.GetAccessibilityId());
var elements = this.WindowsDriver.FindElementsByAccessibilityId(by.GetAccessibilityId());
return elements;
}
else
{
var elements = driver.FindElements(by.ToSeleniumBy());
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
return elements;
}
},
driver,
this.WindowsDriver,
timeoutMS);
return foundElements ?? new ReadOnlyCollection<T>([]);
@@ -256,12 +122,12 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000, bool global = false)
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.FindAll<T>(By.Name(name), timeoutMS, global);
return this.FindAll<T>(By.Name(name), timeoutMS);
}
/// <summary>
@@ -269,11 +135,11 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000, bool global = false)
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
{
return this.FindAll<Element>(by, timeoutMS, global);
return this.FindAll<Element>(by, timeoutMS);
}
/// <summary>
@@ -281,187 +147,55 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000, bool global = false)
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
{
return this.FindAll<Element>(By.Name(name), timeoutMS, global);
return this.FindAll<Element>(By.Name(name), timeoutMS);
}
/// <summary>
/// Close the main window.
/// Keyboard Action key.
/// </summary>
public void CloseMainWindow()
/// <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 = "")
{
if (MainWindow != null)
PerformAction((actions, windowElement) =>
{
MainWindow.Close();
MainWindow = null;
}
}
/// <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>
/// press and hold the specified key.
/// </summary>
/// <param name="key">The key to press and release .</param>
public void SendKey(Key key, int msPreAction = 500, int msPostAction = 500)
{
PerformAction(
() =>
{
KeyboardHelper.SendKey(key);
},
msPreAction,
msPostAction);
}
/// <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)
if (string.IsNullOrEmpty(key2))
{
KeyboardHelper.SendKeys(key);
actions.SendKeys(key1);
}
});
}
        /// <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, int msPreAction = 500, int msPostAction = 500)
{
PerformAction(
() =>
{
MouseHelper.MoveMouseTo(x, y);
},
msPreAction,
msPostAction);
}
/// <summary>
/// Performs a mouse action based on the specified action type.
/// </summary>
/// <param name="action">The mouse action to perform.</param>
/// <param name="msPreAction">Pre-action delay in milliseconds.</param>
/// <param name="msPostAction">Post-action delay in milliseconds.</param>
public void PerformMouseAction(MouseActionType action, int msPreAction = 500, int msPostAction = 500)
{
PerformAction(
() =>
{
switch (action)
else if (string.IsNullOrEmpty(key3))
{
case MouseActionType.LeftClick:
MouseHelper.LeftClick();
break;
case MouseActionType.RightClick:
MouseHelper.RightClick();
break;
case MouseActionType.MiddleClick:
MouseHelper.MiddleClick();
break;
case MouseActionType.LeftDoubleClick:
MouseHelper.LeftDoubleClick();
break;
case MouseActionType.RightDoubleClick:
MouseHelper.RightDoubleClick();
break;
case MouseActionType.LeftDown:
MouseHelper.LeftDown();
break;
case MouseActionType.LeftUp:
MouseHelper.LeftUp();
break;
case MouseActionType.RightDown:
MouseHelper.RightDown();
break;
case MouseActionType.RightUp:
MouseHelper.RightUp();
break;
case MouseActionType.MiddleDown:
MouseHelper.MiddleDown();
break;
case MouseActionType.MiddleUp:
MouseHelper.MiddleUp();
break;
case MouseActionType.ScrollUp:
MouseHelper.ScrollUp();
break;
case MouseActionType.ScrollDown:
MouseHelper.ScrollDown();
break;
default:
throw new ArgumentException("Unsupported mouse action.", nameof(action));
throw new ArgumentException("Unsupported mouse action.", nameof(action));
actions.SendKeys(key1).SendKeys(key2);
}
},
msPreAction,
msPostAction);
else if (string.IsNullOrEmpty(key4))
{
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3);
}
else
{
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4);
}
actions.Release();
actions.Build().Perform();
});
}
/// <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, WindowSize size = WindowSize.UnSpecified)
public Session Attach(PowerToysModule module)
{
string windowName = ModuleConfigData.Instance.GetModuleWindowName(module);
return this.Attach(windowName, size);
return this.Attach(windowName);
}
/// <summary>
@@ -469,44 +203,26 @@ 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, WindowSize size = WindowSize.UnSpecified)
public Session Attach(string windowName)
{
this.IsElevated = null;
this.MainWindowHandler = IntPtr.Zero;
if (this.Root != null)
{
// search window handler by window title (admin and non-admin titles)
var matchingWindows = WindowHelper.ApiHelper.FindDesktopWindowHandler([windowName, WindowHelper.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(WindowHelper.AdministratorPrefix);
ApiHelper.SetForegroundWindow(this.MainWindowHandler);
var hexWindowHandle = this.MainWindowHandler.ToInt64().ToString("x");
var window = this.Root.FindElementByName(windowName);
Assert.IsNotNull(window, $"Failed to attach. Window '{windowName}' not found");
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");
this.windowHandlers.Add(this.MainWindowHandler);
if (size != WindowSize.UnSpecified)
{
WindowHelper.SetWindowSize(this.MainWindowHandler, size);
}
// Set MainWindow
MainWindow = Find<Window>(matchingWindows[0].Title);
// Set implicit timeout to make element search retry every 500 ms
this.WindowsDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
}
else
{
@@ -516,61 +232,6 @@ namespace Microsoft.PowerToys.UITest
return this;
}
/// <summary>
/// Sets the main window size.
/// </summary>
/// <param name="size">WindowSize enum</param>
public void SetMainWindowSize(WindowSize size)
{
if (this.MainWindowHandler == IntPtr.Zero)
{
// Attach to the scope & reset MainWindowHandler
this.Attach(this.InitScope);
}
WindowHelper.SetWindowSize(this.MainWindowHandler, size);
}
/// <summary>
/// Gets the main window center coordinates.
/// </summary>
/// <returns>(x, y)</returns>
public (int CenterX, int CenterY) GetMainWindowCenter()
{
return WindowHelper.GetWindowCenter(this.MainWindowHandler);
}
public void StartExe(string executablePath, string arguments = "", int msPreAction = 0, int msPostAction = 2000)
{
PerformAction(
() =>
{
StartExeInternal(executablePath, arguments);
},
msPreAction,
msPostAction);
}
private void StartExeInternal(string executablePath, string arguments = "")
{
var processInfo = new ProcessStartInfo
{
FileName = executablePath,
Arguments = arguments,
UseShellExecute = true,
};
Process.Start(processInfo);
}
public void KillAllProcessesByName(string processName)
{
foreach (var process in Process.GetProcessesByName(processName))
{
process.Kill();
process.WaitForExit();
}
}
/// <summary>
/// Simulates a manual operation on the element.
/// </summary>
@@ -593,26 +254,5 @@ 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();
}
}
}
}

View File

@@ -20,8 +20,6 @@ namespace Microsoft.PowerToys.UITest
// Default session path is PowerToys settings dashboard
private readonly string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
private readonly string runnerPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.Runner);
private string? locationPath;
private WindowsDriver<WindowsElement> Root { get; set; }
@@ -29,14 +27,10 @@ namespace Microsoft.PowerToys.UITest
private WindowsDriver<WindowsElement>? Driver { get; set; }
private Process? appDriver;
private Process? runner;
private PowerToysModule scope;
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
public SessionHelper(PowerToysModule scope)
{
this.scope = scope;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
@@ -46,24 +40,14 @@ namespace Microsoft.PowerToys.UITest
Verb = "runas",
};
this.ExitExe(winAppDriverProcessInfo.FileName);
this.appDriver = Process.Start(winAppDriverProcessInfo);
var runnerProcessInfo = new ProcessStartInfo
{
FileName = locationPath + this.runnerPath,
Verb = "runas",
};
if (scope == PowerToysModule.PowerToysSettings)
{
this.ExitExe(runnerProcessInfo.FileName);
this.runner = Process.Start(runnerProcessInfo);
}
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>
@@ -72,11 +56,7 @@ namespace Microsoft.PowerToys.UITest
/// <param name="scope">The PowerToys module to start.</param>
public SessionHelper Init()
{
// 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);
this.StartExe(locationPath + this.sessionPath);
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
@@ -93,11 +73,6 @@ namespace Microsoft.PowerToys.UITest
{
appDriver?.Kill();
appDriver?.WaitForExit(); // Optional: Wait for the process to exit
if (this.scope == PowerToysModule.PowerToysSettings)
{
runner?.Kill();
runner?.WaitForExit(); // Optional: Wait for the process to exit
}
}
catch (Exception ex)
{
@@ -107,14 +82,30 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Exit a exe.
/// Starts a new exe and takes control of it.
/// </summary>
/// <param name="appPath">The path to the application executable.</param>
public void ExitExe(string appPath)
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)
{
// Exit Exe
string exeName = Path.GetFileNameWithoutExtension(appPath);
string exeName = Path.GetFileNameWithoutExtension(path);
// PowerToys.FancyZonesEditor
Process[] processes = Process.GetProcessesByName(exeName);
foreach (Process process in processes)
{
@@ -130,39 +121,6 @@ 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);
// Create driver with retry
var timeout = TimeSpan.FromMinutes(2);
var retryInterval = TimeSpan.FromSeconds(5);
DateTime startTime = DateTime.Now;
while (true)
        {
try
{
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
break;
}
            catch (Exception)
            {
                if (DateTime.Now - startTime > timeout)
                {
                    throw;
                }
                Task.Delay(retryInterval).Wait();
            }
        }
    }
/// <summary>
/// Exit now exe.
/// </summary>

View File

@@ -8,9 +8,6 @@
<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>

View File

@@ -6,16 +6,10 @@ 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;
using static Microsoft.PowerToys.UITest.WindowHelper;
namespace Microsoft.PowerToys.UITest
{
@@ -23,38 +17,19 @@ namespace Microsoft.PowerToys.UITest
/// Base class that should be inherited by all Test Classes.
/// </summary>
[TestClass]
public class UITestBase : IDisposable
public class UITestBase
{
public required TestContext TestContext { get; set; }
public Session Session { get; set; }
public required Session Session { get; set; }
private readonly SessionHelper sessionHelper;
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
private readonly bool isInPipeline;
private readonly PowerToysModule scope;
private readonly WindowSize size;
private SessionHelper? sessionHelper;
private System.Threading.Timer? screenshotTimer;
private string? screenshotDirectory;
// private System.Threading.Timer? screenshotTimer;
// private string? screenshotDirectory;
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified)
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
{
this.isInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
if (isInPipeline)
{
NativeMethods.ChangeDisplayResolution(1920, 1080);
NativeMethods.GetMonitorInfo();
// Escape Popups before starting
System.Windows.Forms.SendKeys.SendWait("{ESC}");
}
this.scope = scope;
this.size = size;
this.sessionHelper = new SessionHelper(scope).Init();
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
}
/// <summary>
@@ -63,21 +38,6 @@ 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));
// Escape Popups before starting
System.Windows.Forms.SendKeys.SendWait("{ESC}");
}
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
@@ -90,32 +50,12 @@ namespace Microsoft.PowerToys.UITest
}
/// <summary>
/// Cleanups the test.
/// UnInitializes the test.
/// </summary>
[TestCleanup]
public void TestCleanup()
public void TestClean()
{
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);
this.sessionHelper.Cleanup();
}
/// <summary>
@@ -124,143 +64,47 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
protected T Find<T>(By by, int timeoutMS = 5000, bool global = false)
protected T Find<T>(By by, int timeoutMS = 3000)
where T : Element, new()
{
return this.Session.Find<T>(by, timeoutMS, global);
return this.Session.Find<T>(by, timeoutMS);
}
/// <summary>
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
/// Shortcut for this.Session.Find<Element>(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>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
protected T Find<T>(string name, int timeoutMS = 5000, bool global = false)
protected T Find<T>(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.Session.Find<T>(By.Name(name), timeoutMS, global);
return this.Session.Find<T>(By.Name(name), timeoutMS);
}
/// <summary>
/// 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
protected Element Find(By by, int timeoutMS = 5000, bool global = false)
protected Element Find(By by, int timeoutMS = 3000)
{
return this.Session.Find(by, timeoutMS, global);
return this.Session.Find(by, timeoutMS);
}
/// <summary>
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
/// Shortcut for this.Session.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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>The found element.</returns>
protected Element Find(string name, int timeoutMS = 5000, bool global = false)
protected Element Find(string name, int timeoutMS = 3000)
{
return this.Session.Find(name, timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.FindAll<T>(by, timeoutMS, global).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, bool global = false)
{
return this.Session.HasOne<Element>(by, timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.Session.HasOne<T>(By.Name(name), timeoutMS, global);
}
/// <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, bool global = false)
{
return this.Session.HasOne<Element>(name, timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.Session.FindAll<T>(by, timeoutMS, global).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, bool global = false)
{
return this.Session.Has<Element>(by, timeoutMS, global);
}
/// <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, bool global = false)
where T : Element, new()
{
return this.Session.Has<T>(By.Name(name), timeoutMS, global);
}
/// <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, bool global = false)
{
return this.Session.Has<Element>(name, timeoutMS, global);
return this.Session.Find(name, timeoutMS);
}
/// <summary>
@@ -269,12 +113,12 @@ namespace Microsoft.PowerToys.UITest
/// </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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000, bool global = false)
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
where T : Element, new()
{
return this.Session.FindAll<T>(by, timeoutMS, global);
return this.Session.FindAll<T>(by, timeoutMS);
}
/// <summary>
@@ -283,12 +127,12 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000, bool global = false)
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
where T : Element, new()
{
return this.Session.FindAll<T>(By.Name(name), timeoutMS, global);
return this.Session.FindAll<T>(By.Name(name), timeoutMS);
}
/// <summary>
@@ -296,11 +140,11 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000, bool global = false)
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
{
return this.Session.FindAll<Element>(by, timeoutMS, global);
return this.Session.FindAll<Element>(by, timeoutMS);
}
/// <summary>
@@ -308,121 +152,11 @@ 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 5000).</param>
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
/// <returns>A read-only collection of the found elements.</returns>
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000, bool global = false)
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
{
return this.Session.FindAll<Element>(By.Name(name), timeoutMS, global);
}
/// <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 WindowHelper.GetPixelColor(x, y);
}
/// <summary>
/// Retrieves the color of the pixel at the specified screen coordinates as a string.
/// </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 string GetPixelColorString(int x, int y)
{
return WindowHelper.GetPixelColorString(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 WindowHelper.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>
/// Gets the screen center coordinates.
/// </summary>
/// <returns>(x, y)</returns>
public (int CenterX, int CenterY) GetScreenCenter()
{
return WindowHelper.GetScreenCenter();
}
public bool IsWindowOpen(string windowName)
{
return WindowHelper.IsWindowOpen(windowName);
}
/// <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);
}
}
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
}
/// <summary>
@@ -430,8 +164,8 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
public void RestartScopeExe()
{
this.sessionHelper!.RestartScopeExe();
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
this.sessionHelper.RestartScopeExe();
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
return;
}
@@ -440,175 +174,8 @@ 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)
{
MonitorInfoData.Monitors.Add(new MonitorInfoData.MonitorInfoDataWrapper()
{
DeviceName = d.DeviceName,
DeviceString = d.DeviceString,
DeviceID = d.DeviceID,
DeviceKey = d.DeviceKey,
PelsWidth = dm.DmPelsWidth,
PelsHeight = dm.DmPelsHeight,
DisplayFrequency = dm.DmDisplayFrequency,
});
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 ChangeDisplayResolution(int PelsWidth, int PelsHeight)
{
Screen screen = Screen.PrimaryScreen!;
if (screen.Bounds.Width == PelsWidth && screen.Bounds.Height == PelsHeight)
{
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 = PelsWidth;
devMode.DmPelsHeight = PelsHeight;
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}");
}
}
}
}
}

View File

@@ -1,160 +0,0 @@
// 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 = "")
{
var pipelinePlatform = Environment.GetEnvironmentVariable("platform");
// Perform visual validation only in the pipeline
if (string.IsNullOrEmpty(pipelinePlatform))
{
Console.WriteLine("Skip visual validation in the local run.");
return;
}
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, pipelinePlatform);
}
else
{
scenarioSubname = string.Join("_", callerClassName, callerName, scenarioSubname.Trim(), pipelinePlatform);
}
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))
{
testContext?.AddResultFile(tempTestImagePath);
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;
}
}
}

View File

@@ -1,170 +0,0 @@
// 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;
}
}
}

View File

@@ -1,297 +0,0 @@
// 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 WindowHelper
{
internal const string AdministratorPrefix = "Administrator: ";
/// <summary>
/// Sets the main window size.
/// </summary>
/// <param name="size">WindowSize enum</param>
public static void SetWindowSize(IntPtr windowHandler, WindowSize size)
{
if (size == WindowSize.UnSpecified)
{
return;
}
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)
{
WindowHelper.SetMainWindowSize(windowHandler, width, height);
}
}
/// <summary>
/// Gets the main window center coordinates.
/// </summary>
/// <returns>(x, y)</returns>
public static (int CenterX, int CenterY) GetWindowCenter(IntPtr windowHandler)
{
if (windowHandler == IntPtr.Zero)
{
return (0, 0);
}
else
{
var rect = ApiHelper.GetWindowCenter(windowHandler);
return (rect.CenterX, rect.CenterY);
}
}
/// <summary>
/// Gets the screen center coordinates.
/// </summary>
/// <returns>(x, y)</returns>
public static (int CenterX, int CenterY) GetScreenCenter()
{
return ApiHelper.GetScreenCenter();
}
/// <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 static void SetMainWindowSize(IntPtr windowHandler, int width, int height)
{
if (windowHandler == IntPtr.Zero
|| width <= 0
|| height <= 0)
{
return;
}
ApiHelper.SetWindowPos(windowHandler, IntPtr.Zero, 0, 0, width, height, ApiHelper.SetWindowPosNoZorder | ApiHelper.SetWindowPosShowWindow);
// Wait for 1000ms after resize
Task.Delay(1000).Wait();
}
/// <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 static 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>
/// Retrieves the color of the pixel at the specified screen coordinates as a string.
/// </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 static string GetPixelColorString(int x, int y)
{
Color color = WindowHelper.GetPixelColor(x, y);
return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
}
/// <summary>
/// Gets the size of the display.
/// </summary>
/// <returns>
/// A tuple containing the width and height of the display.
/// </returns
public static 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);
}
public static bool IsWindowOpen(string windowName)
{
var matchingWindows = ApiHelper.FindDesktopWindowHandler([windowName, AdministratorPrefix + windowName]);
return matchingWindows.Count > 0;
}
internal 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);
// Define the Win32 RECT structure
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // X coordinate of the left edge of the window
public int Top; // Y coordinate of the top edge of the window
public int Right; // X coordinate of the right edge of the window
public int Bottom; // Y coordinate of the bottom edge of the window
}
// Import GetWindowRect API to retrieve window's screen coordinates
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
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>
/// Get the center point coordinates of a specified window (in screen coordinates)
/// </summary>
/// <param name="hWnd">The window handle</param>
/// <returns>The center point (x, y)</returns>
public static (int CenterX, int CenterY) GetWindowCenter(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero)
{
throw new ArgumentException("Invalid window handle");
}
if (GetWindowRect(hWnd, out RECT rect))
{
int width = rect.Right - rect.Left;
int height = rect.Bottom - rect.Top;
int centerX = rect.Left + (width / 2);
int centerY = rect.Top + (height / 2);
return (centerX, centerY);
}
else
{
throw new InvalidOperationException("Failed to retrieve window coordinates");
}
}
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
public enum SystemMetric
{
ScreenWidth = 0, // Width of the primary screen in pixels (SM_CXSCREEN)
ScreenHeight = 1, // Height of the primary screen in pixels (SM_CYSCREEN)
VirtualScreenWidth = 78, // Width of the virtual screen that includes all monitors (SM_CXVIRTUALSCREEN)
VirtualScreenHeight = 79, // Height of the virtual screen that includes all monitors (SM_CYVIRTUALSCREEN)
MonitorCount = 80, // Number of display monitors (SM_CMONITORS, available on Windows XP+)
}
public static (int CenterX, int CenterY) GetScreenCenter()
{
int width = GetSystemMetrics((int)SystemMetric.ScreenWidth);
int height = GetSystemMetrics((int)SystemMetric.ScreenHeight);
return (width / 2, height / 2);
}
}
}
}

View File

@@ -17,7 +17,7 @@
"org": "microsoft",
"project": "OS",
"AssignedTo": "leilzh@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "leilzh@microsoft.com",

View File

@@ -17,7 +17,7 @@
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
@@ -58,7 +58,7 @@
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
@@ -99,7 +99,7 @@
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
@@ -140,7 +140,7 @@
"org": "microsoft",
"project": "OS",
"AssignedTo": "mengyuanchen@microsoft.com",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\SALT",
"AreaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys",
"IterationPath": "OS\\Future"
},
"jobNotificationEmail": "mengyuanchen@microsoft.com",
@@ -158,19 +158,19 @@
// the DLL and PDB files
// you will need to add any other files required
// (globs are supported)
"Castle.Core.dll",
"CommunityToolkit.Mvvm.dll",
"Hosts.FuzzTests.dll",
"Hosts.FuzzTests.pdb",
"Microsoft.Windows.SDK.NET.dll",
"WinRT.Runtime.dll",
"Moq.dll",
"testhost.dll",
"Castle.Core.dll",
"System.IO.Abstractions.dll",
"CommunityToolkit.Mvvm.dll",
"System.IO.Abstractions.TestingHelpers.dll",
"TestableIO.System.IO.Abstractions.dll",
"TestableIO.System.IO.Abstractions.TestingHelpers.dll",
"TestableIO.System.IO.Abstractions.Wrappers.dll",
"Testably.Abstractions.FileSystem.Interface.dll",
"WinRT.Runtime.dll"
"TestableIO.System.IO.Abstractions.Wrappers.dll"
],
"SdlWorkItemId": 49911822
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -13,7 +13,7 @@ namespace Hosts.UITests
public class HostModuleTests : UITestBase
{
public HostModuleTests()
: base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
: base(PowerToysModule.Hosts)
{
}
@@ -31,17 +31,14 @@ namespace Hosts.UITests
/// </item>
/// </list>
/// </summary>
[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
[TestCategory("Hosts File Editor #4")]
[TestMethod]
public void TestEmptyView()
{
this.CloseWarningDialog();
this.RemoveAllEntries();
// 'Add an entry' button (only show-up when list is empty) should be visible
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");
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 1, "'Add an entry' button should be visible in the empty view");
// Click 'Add an entry' from empty-view for adding Host override rule
this.Find<HyperlinkButton>("Add an entry").Click();
@@ -49,10 +46,8 @@ 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.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");
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");
}
/// <summary>
@@ -63,20 +58,17 @@ namespace Hosts.UITests
/// </item>
/// </list>
/// </summary>
[TestMethod("Hosts.Basic.AddEntryButtonShouldWork")]
[TestCategory("Hosts File Editor #4")]
[TestMethod]
public void TestAddingEntry()
{
this.CloseWarningDialog();
this.RemoveAllEntries();
Assert.IsFalse(this.Has<Button>("Delete"), "Should have no row after removing all");
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0, "Should have no row after removing all");
this.AddEntry("192.168.0.1", "localhost", true);
Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"));
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
}
/// <summary>
@@ -90,8 +82,7 @@ namespace Hosts.UITests
/// </item>
/// </list>
/// </summary>
[TestMethod("Hosts.Basic.CanNotAddMoreThenNighHosts")]
[TestCategory("Hosts File Editor #5")]
[TestMethod]
public void TestTooManyHosts()
{
this.CloseWarningDialog();
@@ -125,48 +116,18 @@ namespace Hosts.UITests
/// </item>
/// </list>
/// </summary>
[TestMethod("Hosts.Basic.ErrorMessgeShowupIfNotRunAsAdmin")]
[TestCategory("Hosts File Editor #8")]
[TestMethod]
public void TestErrorMessageWithNonAdminPermission()
{
if (this.Session.IsElevated == false)
{
this.CloseWarningDialog();
this.RemoveAllEntries();
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");
}
}
/// <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")]
[TestCategory("Hosts File Editor #8")]
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");
}
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>
@@ -183,8 +144,7 @@ namespace Hosts.UITests
/// </item>
/// </list>
/// </summary>
[TestMethod("Hosts.Basic.FiltersControlShouldWork")]
[TestCategory("Hosts File Editor #6")]
[TestMethod]
public void TestFilterControl()
{
this.CloseWarningDialog();
@@ -252,7 +212,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 after clicking Filter Button");
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed afer click Filter Button");
}
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)

View File

@@ -17,17 +17,6 @@
<PropertyGroup>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\Hosts.UITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Baseline\HostModuleTests_TestAddingEntry_arm64.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_EmptyView_arm64.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_NonEmptyView_arm64.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestAddingEntry_x64Win10.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_EmptyView_x64Win10.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_NonEmptyView_x64Win10.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestAddingEntry_x64Win11.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_EmptyView_x64Win11.png" />
<EmbeddedResource Include="Baseline\HostModuleTests_TestEmptyView_NonEmptyView_x64Win11.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" />

View File

@@ -12,11 +12,6 @@ namespace Hosts.UITests
[TestClass]
public class HostsSettingTests : UITestBase
{
public HostsSettingTests()
: base(PowerToysModule.PowerToysSettings, WindowSize.Medium)
{
}
/// <summary>
/// Test Warning Dialog at startup
/// <list type="bullet">
@@ -34,9 +29,7 @@ namespace Hosts.UITests
/// </item>
/// </list>
/// </summary>
[TestMethod("Hosts.Settings.ShowWarningDialogIfRunAsAdmin")]
[TestCategory("Hosts File Editor #1")]
[TestCategory("Hosts File Editor #9")]
[TestMethod]
public void TestWarningDialog()
{
this.LaunchFromSetting(showWarning: true);
@@ -61,7 +54,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, WindowSize.Small_Vertical);
this.Session.Attach(PowerToysModule.Hosts);
// Should show warning dialog
Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
@@ -75,7 +68,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.CloseMainWindow();
this.Session.Find<Window>("Hosts File Editor").Close();
// Restore back to PowerToysSettings Session
this.Session.Attach(PowerToysModule.PowerToysSettings);
@@ -89,7 +82,7 @@ namespace Hosts.UITests
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed");
// Close Hosts File Editor window
this.Session.CloseMainWindow();
this.Session.Find<Window>("Hosts File Editor").Close();
// Restore back to PowerToysSettings Session
this.Session.Attach(PowerToysModule.PowerToysSettings);
@@ -128,7 +121,7 @@ namespace Hosts.UITests
// launch Hosts File Editor
this.Find<Button>("Launch Hosts File Editor").Click();
Task.Delay(2000).Wait();
Task.Delay(500).Wait();
this.Session.Attach(PowerToysModule.Hosts);
}

View File

@@ -1,640 +0,0 @@
// 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.Globalization;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Devices.Printers;
namespace MouseUtils.UITests
{
[TestClass]
public class FindMyMouseTests : UITestBase
{
/// <summary>
/// Test Warning Dialog at startup
/// <list type="bullet">
/// <item>
/// <description>Validating Warning-Dialog will be shown if 'Show a warning at startup' toggle is On.</description>
/// </item>
/// <item>
/// <description>Validating Warning-Dialog will NOT be shown if 'Show a warning at startup' toggle is Off.</description>
/// </item>
/// <item>
/// <description>Validating click 'Quit' button in Warning-Dialog, the Hosts File Editor window would be closed.</description>
/// </item>
/// <item>
/// <description>Validating click 'Accept' button in Warning-Dialog, the Hosts File Editor window would NOT be closed.</description>
/// </item>
/// </list>
/// </summary>
[TestMethod("MouseUtils.FindMyMouse.EnableFindMyMouse")]
[TestCategory("Mouse Utils #1")]
[TestCategory("Mouse Utils #2")]
[TestCategory("Mouse Utils #3")]
[TestCategory("Mouse Utils #4")]
public void TestEnableFindMyMouse()
{
LaunchFromSetting();
var settings = new FindMyMouseSettings();
settings.OverlayOpacity = "100";
settings.Radius = "50";
settings.InitialZoom = "1";
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
if (excludedApps != null)
{
excludedApps.Click();
excludedApps.Click();
}
else
{
Assert.Fail("Activation method group not found.");
}
}
else
{
Assert.Fail("Find My Mouse group not found.");
}
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press Left Ctrl twice and verify the overlay appears.
VerifySpotlightSettings(ref settings);
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press any other key and verify the overlay disappears.
Session.SendKeys(Key.A);
VerifySpotlightDisappears(ref settings);
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press Left Ctrl twice and verify the overlay appears.
VerifySpotlightSettings(ref settings);
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press a mouse button and verify the overlay disappears.
Task.Delay(1000).Wait();
Session.PerformMouseAction(MouseActionType.LeftClick, 500, 1000);
VerifySpotlightDisappears(ref settings);
}
[TestMethod("MouseUtils.FindMyMouse.FindMyMouseDifferentSettings")]
[TestCategory("Mouse Utils #10")]
[TestCategory("Mouse Utils #11")]
[TestCategory("Mouse Utils #12")]
public void TestFindMyMouseDifferentSettings()
{
LaunchFromSetting();
var settings = new FindMyMouseSettings();
settings.OverlayOpacity = "100";
settings.Radius = "80";
settings.InitialZoom = "1";
settings.AnimationDuration = "0";
settings.BackgroundColor = "FF0000";
settings.SpotlightColor = "0000FF";
var foundCustom = this.Find<Custom>("Find My Mouse");
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
if (excludedApps != null)
{
excludedApps.Click();
excludedApps.Click();
}
else
{
Assert.Fail("Excluded apps group not found.");
}
}
else
{
Assert.Fail("Find My Mouse group not found.");
}
// [Test Case]Test the different settings and verify they apply, Background color
// [Test Case]Test the different settings and verify they apply, Spotlight color
// [Test Case]Test the different settings and verify they apply, Spotlight radius
VerifySpotlightSettings(ref settings);
Session.SendKeys(Key.A);
VerifySpotlightDisappears(ref settings);
}
[TestMethod("MouseUtils.FindMyMouse.DisableFindMyMouse")]
[TestCategory("Mouse Utils #5")]
[TestCategory("Mouse Utils #6")]
public void TestDisableFindMyMouse()
{
LaunchFromSetting();
var settings = new FindMyMouseSettings();
settings.OverlayOpacity = "100";
settings.Radius = "50";
settings.InitialZoom = "1";
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom);
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
if (excludedApps != null)
{
excludedApps.Click();
excludedApps.Click();
}
else
{
Assert.Fail("Activation method group not found.");
}
}
else
{
Assert.Fail("Find My Mouse group not found.");
}
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press Left Ctrl twice and verify the overlay appears.
// VerifySpotlightSettings(ref settings);
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
Task.Delay(1000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightDisappears(ref settings);
// [Test Case] Press Left Ctrl twice and verify the overlay appears
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
Task.Delay(2000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
Session.PerformMouseAction(MouseActionType.LeftClick);
}
[TestMethod("MouseUtils.FindMyMouse.DisableFindMyMouse3")]
[TestCategory("Mouse Utils #6")]
public void TestDisableFindMyMouse3()
{
LaunchFromSetting();
var settings = new FindMyMouseSettings();
settings.OverlayOpacity = "100";
settings.Radius = "50";
settings.InitialZoom = "1";
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
Assert.IsNotNull(foundCustom);
if (CheckAnimationEnable(ref foundCustom))
{
foundCustom = this.Find<Custom>("Find My Mouse");
}
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom);
SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
if (excludedApps != null)
{
excludedApps.Click();
excludedApps.Click();
}
else
{
Assert.Fail("Activation method group not found.");
}
}
else
{
Assert.Fail("Find My Mouse group not found.");
}
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press Left Ctrl twice and verify the overlay appears.
// VerifySpotlightSettings(ref settings);
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
Task.Delay(1000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightDisappears(ref settings);
// [Test Case] Press Left Ctrl twice and verify the overlay appears
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
Task.Delay(2000).Wait();
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
Session.PerformMouseAction(MouseActionType.LeftClick);
}
[TestMethod("MouseUtils.FindMyMouse.DisableFindMyMouse2")]
[TestCategory("Mouse Utils #5")]
public void TestDisableFindMyMouse2()
{
LaunchFromSetting();
var settings = new FindMyMouseSettings();
settings.OverlayOpacity = "100";
settings.Radius = "50";
settings.InitialZoom = "1";
settings.AnimationDuration = "0";
settings.BackgroundColor = "000000";
settings.SpotlightColor = "FFFFFF";
var foundCustom = this.Find<Custom>("Find My Mouse");
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
// foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
SetFindMyMouseActivationMethod(ref foundCustom, "Press Left Control twice");
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
// SetFindMyMouseAppearanceBehavior(ref foundCustom, ref settings);
var excludedApps = foundCustom.Find<TextBlock>("Excluded apps");
if (excludedApps != null)
{
excludedApps.Click();
excludedApps.Click();
}
else
{
Assert.Fail("Activation method group not found.");
}
}
else
{
Assert.Fail("Find My Mouse group not found.");
}
// [Test Case]Enable FindMyMouse. Then, without moving your mouse: Press Left Ctrl twice and verify the overlay appears.
// VerifySpotlightSettings(ref settings);
// [Test Case] Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice
foundCustom.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
Task.Delay(2000).Wait();
Session.SendKey(Key.LCtrl, 0, 0);
Task.Delay(100).Wait();
Session.SendKey(Key.LCtrl, 0, 0);
VerifySpotlightDisappears(ref settings);
}
private void VerifySpotlightDisappears(ref FindMyMouseSettings settings)
{
Task.Delay(2000).Wait();
var location = Session.GetMousePosition();
int radius = int.Parse(settings.Radius, CultureInfo.InvariantCulture);
var colorSpotlight = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreNotEqual("#" + settings.SpotlightColor, colorSpotlight);
var colorBackground = this.GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
Assert.AreNotEqual("#" + settings.BackgroundColor, colorBackground);
var colorBackground2 = this.GetPixelColorString(location.Item1 + radius + 100, location.Item2 + radius + 100);
Assert.AreNotEqual("#" + settings.BackgroundColor, colorBackground2);
}
private void VerifySpotlightAppears(ref FindMyMouseSettings settings)
{
Task.Delay(2000).Wait();
var location = Session.GetMousePosition();
int radius = int.Parse(settings.Radius, CultureInfo.InvariantCulture);
var colorSpotlight = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreEqual("#" + settings.SpotlightColor, colorSpotlight);
var colorSpotlight2 = this.GetPixelColorString(location.Item1 + radius - 1, location.Item2);
// Session.MoveMouseTo(location.Item1 + radius - 10, location.Item2);
Assert.AreEqual("#" + settings.SpotlightColor, colorSpotlight2);
Task.Delay(100).Wait();
var colorBackground = this.GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
Assert.AreEqual("#" + settings.BackgroundColor, colorBackground);
var colorBackground2 = this.GetPixelColorString(location.Item1 + radius + 100, location.Item2 + radius + 100);
Assert.AreEqual("#" + settings.BackgroundColor, colorBackground2);
}
private void ActivateSpotlight(ref FindMyMouseSettings settings)
{
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1 - 200, xy.Item2 - 100);
Task.Delay(1000).Wait();
Session.PerformMouseAction(MouseActionType.LeftClick);
Task.Delay(1000).Wait();
if (settings.SelectedActivationMethod == FindMyMouseSettings.ActivationMethod.PressLeftControlTwice)
{
Session.SendKey(Key.LCtrl, 0, 0);
Task.Delay(200).Wait();
Session.SendKey(Key.LCtrl, 0, 0);
}
else if (settings.SelectedActivationMethod == FindMyMouseSettings.ActivationMethod.PressRightControlTwice)
{
Session.SendKey(Key.RCtrl, 0, 0);
Task.Delay(200).Wait();
Session.SendKey(Key.RCtrl, 0, 0);
}
else if (settings.SelectedActivationMethod == FindMyMouseSettings.ActivationMethod.ShakeMouse)
{
// Simulate shake mouse;
}
else if (settings.SelectedActivationMethod == FindMyMouseSettings.ActivationMethod.CustomShortcut)
{
// Simulate custom shortcut
}
}
private void VerifySpotlightSettings(ref FindMyMouseSettings settings, bool equal = true)
{
ActivateSpotlight(ref settings);
VerifySpotlightAppears(ref settings);
}
private void SetFindMyMouseActivationMethod(ref Custom? foundCustom, string method)
{
Assert.IsNotNull(foundCustom);
var groupActivation = foundCustom.Find<TextBlock>("Activation method");
if (groupActivation != null)
{
groupActivation.Click();
string findMyMouseComboBoxKey = "Activation method";
var foundElements = foundCustom.FindAll<ComboBox>(findMyMouseComboBoxKey);
if (foundElements.Count != 0)
{
var myMouseComboBox = foundCustom.Find<ComboBox>(findMyMouseComboBoxKey);
Assert.IsNotNull(myMouseComboBox);
myMouseComboBox.Click();
var selectedItem = myMouseComboBox.Find<NavigationViewItem>(method);
Assert.IsNotNull(selectedItem);
selectedItem.Click();
}
else
{
Assert.IsTrue(false, "ComboBox is not found in the setting page.");
}
}
else
{
Assert.Fail("Activation method group not found.");
}
}
private void SetFindMyMouseAppearanceBehavior(ref Custom foundCustom, ref FindMyMouseSettings settings)
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
if (foundCustom.FindAll<Slider>("Overlay opacity (%)").Count == 0)
{
groupAppearanceBehavior.Click();
}
// Set the BackGround color
var backgroundColor = foundCustom.Find<Group>("Background color");
Assert.IsNotNull(backgroundColor);
var button = backgroundColor.Find<Button>(By.XPath(".//Button"));
Assert.IsNotNull(button);
button.Click();
var popupWindow = this.Find<Window>("Popup");
Assert.IsNotNull(popupWindow);
Task.Delay(1000).Wait();
var colorModelComboBox = this.Find<ComboBox>("Color model");
Assert.IsNotNull(colorModelComboBox);
colorModelComboBox.Click();
var selectedItem = colorModelComboBox.Find<NavigationViewItem>("RGB");
selectedItem.Click();
Task.Delay(500).Wait();
var rgbHexEdit = this.Find<TextBox>("RGB hex");
Assert.IsNotNull(rgbHexEdit);
Task.Delay(500).Wait();
int retry = 5;
while (retry > 0)
{
Task.Delay(500).Wait();
rgbHexEdit.SetText(settings.BackgroundColor);
Task.Delay(500).Wait();
string rgbHex = rgbHexEdit.Text;
bool isValid = rgbHex.StartsWith('#') && rgbHex.Length == 7 && rgbHex.Substring(1) == settings.BackgroundColor;
Task.Delay(500).Wait();
if (isValid)
{
break;
}
retry--;
}
button.Click();
// Set the Spotlight color
var spotlightColor = foundCustom.Find<Group>("Spotlight color");
Assert.IsNotNull(spotlightColor);
var spotlightColorButton = spotlightColor.Find<Button>(By.XPath(".//Button"));
Assert.IsNotNull(spotlightColorButton);
spotlightColorButton.Click();
var spotlightColorPopupWindow = Session.Find<Window>("Popup");
Assert.IsNotNull(spotlightColorPopupWindow);
var spotlightColorModelComboBox = this.Find<ComboBox>("Color model");
Assert.IsNotNull(spotlightColorModelComboBox);
spotlightColorModelComboBox.Click();
var selectedItem2 = spotlightColorModelComboBox.Find<NavigationViewItem>("RGB");
Assert.IsNotNull(selectedItem2);
selectedItem2.Click();
Task.Delay(500).Wait();
var rgbHexEdit2 = this.Find<TextBox>("RGB hex");
Assert.IsNotNull(rgbHexEdit2);
Task.Delay(500).Wait();
retry = 5;
while (retry > 0)
{
Task.Delay(500).Wait();
rgbHexEdit2.SetText(settings.SpotlightColor);
Task.Delay(500).Wait();
string rgbHex = rgbHexEdit2.Text;
bool isValid = rgbHex.StartsWith('#') && rgbHex.Length == 7 && rgbHex.Substring(1) == settings.SpotlightColor;
Task.Delay(500).Wait();
if (isValid)
{
break;
}
retry--;
}
Task.Delay(500).Wait();
spotlightColorButton.Click(false, 500, 1500);
// Set the overlay opacity to overlayOpacity%
var overlayOpacitySlider = foundCustom.Find<Slider>("Overlay opacity (%)");
Assert.IsNotNull(overlayOpacitySlider);
Assert.IsNotNull(settings.OverlayOpacity);
int overlayOpacityValue = int.Parse(settings.OverlayOpacity, CultureInfo.InvariantCulture);
overlayOpacitySlider.QuickSetValue(overlayOpacityValue);
Assert.AreEqual(settings.OverlayOpacity, overlayOpacitySlider.Text);
Task.Delay(1000).Wait();
// Set the Fade Initial zoom to 0
var spotlightInitialZoomSlider = foundCustom.Find<Slider>("Spotlight initial zoom");
Assert.IsNotNull(spotlightInitialZoomSlider);
Task.Delay(1000).Wait();
spotlightInitialZoomSlider.QuickSetValue(int.Parse(settings.InitialZoom, CultureInfo.InvariantCulture));
Assert.AreEqual(settings.InitialZoom, spotlightInitialZoomSlider.Text);
Task.Delay(1000).Wait();
//// Change the edit value
var spotlightRadiusEdit = foundCustom.Find<TextBox>("Spotlight radius (px) Minimum5");
Assert.IsNotNull(spotlightRadiusEdit);
Task.Delay(1000).Wait();
spotlightRadiusEdit.SetText(settings.Radius);
Assert.AreEqual(settings.Radius, spotlightRadiusEdit.Text);
Task.Delay(1000).Wait();
// Set the duration to 0 ms
var spotlightAnimationDuration = foundCustom.Find<TextBox>("Animation duration (ms) Minimum0");
Assert.IsNotNull(spotlightAnimationDuration);
Task.Delay(1000).Wait();
spotlightAnimationDuration.SetText(settings.AnimationDuration);
Assert.AreEqual(settings.AnimationDuration, spotlightAnimationDuration.Text);
Task.Delay(1000).Wait();
// groupAppearanceBehavior.Click();
}
else
{
Assert.Fail("Appearance & behavior group not found.");
}
}
private bool CheckAnimationEnable(ref Custom? foundCustom)
{
Assert.IsNotNull(foundCustom, "Find My Mouse group not found.");
var foundElements = foundCustom.FindAll<TextBlock>("Animations are disabled in your system settings.");
// Assert.IsNull(animationDisabledWarning);
if (foundElements.Count != 0)
{
var openSettingsLink = foundCustom.Find<Element>("Open settings");
Assert.IsNotNull(openSettingsLink);
openSettingsLink.Click(false, 500, 3000);
string settingsWindow = "Settings";
this.Session.Attach(settingsWindow);
var animationEffects = this.Find<ToggleSwitch>("Animation effects");
Assert.IsNotNull(animationEffects);
animationEffects.Toggle(true);
Task.Delay(2000).Wait();
Session.SendKeys(Key.Alt, Key.F4);
this.Session.Attach(PowerToysModule.PowerToysSettings);
this.LaunchFromSetting(reload: true);
}
else
{
return false;
}
return true;
}
private void LaunchFromSetting(bool reload = false, bool launchAsAdmin = false)
{
// this.Session.Attach(PowerToysModule.PowerToysSettings);
this.Session.SetMainWindowSize(WindowSize.Large);
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities", 10000).Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").Click();
}
if (reload)
{
this.Find<NavigationViewItem>("Keyboard Manager").Click();
}
Task.Delay(1000).Wait();
this.Find<NavigationViewItem>("Mouse utilities").Click();
}
}
}

View File

@@ -1,497 +0,0 @@
// 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.Globalization;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Devices.Printers;
namespace MouseUtils.UITests
{
[TestClass]
public class MouseHighlighterTests : UITestBase
{
[TestMethod("MouseUtils.MouseHighlighter.EnableMouseHighlighter")]
[TestCategory("Mouse Utils #17")]
[TestCategory("Mouse Utils #18")]
[TestCategory("Mouse Utils #19")]
[TestCategory("Mouse Utils #20")]
[TestCategory("Mouse Utils #21")]
public void TestEnableMouseHighlighter()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
}
else
{
Assert.Fail("Find My Mouse custom not found.");
}
var settings = new MouseHighlighterSettings();
settings.PrimaryButtonHighlightColor = "FFFF0000";
settings.SecondaryButtonHighlightColor = "FF00FF00";
settings.AlwaysHighlightColor = "004cFF71";
settings.Radius = "50";
settings.FadeDelay = "0";
settings.FadeDuration = "90";
var foundCustom = this.Find<Custom>("Mouse Highlighter");
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
// Change the shortcut key for MouseHighlighter
// [TestCase]Change activation shortcut and test it
var activationShortcutButton = foundCustom.Find<Button>("Activation shortcut");
Assert.IsNotNull(activationShortcutButton);
activationShortcutButton.Click();
Task.Delay(500).Wait();
var activationShortcutWindow = Session.Find<Window>("Activation shortcut");
Assert.IsNotNull(activationShortcutWindow);
// Invalid shortcut key
Session.SendKeySequence(Key.H);
// IOUtil.SimulateKeyPress(0x41);
var invalidShortcutText = activationShortcutWindow.Find<TextBlock>("Invalid shortcut");
Assert.IsNotNull(invalidShortcutText);
// IOUtil.SimulateShortcut(0x5B, 0x10, 0x45);
Session.SendKeys(Key.Win, Key.Shift, Key.H);
// Assert.IsNull(activationShortcutWindow.Find<TextBlock>("Invalid shortcut"));
var saveButton = activationShortcutWindow.Find<Button>("Save");
Assert.IsNotNull(saveButton);
saveButton.Click(false, 500, 1000);
SetMouseHighlighterAppearanceBehavior(ref foundCustom, ref settings);
var xy0 = Session.GetMousePosition();
Session.MoveMouseTo(xy0.Item1 - 100, xy0.Item2);
Session.PerformMouseAction(MouseActionType.LeftClick);
// Check the mouse highlighter is enabled
Session.SendKeys(Key.Win, Key.Shift, Key.H);
// IOUtil.SimulateShortcut(0x5B, 0x10, 0x45);
Task.Delay(1000).Wait();
// MouseSimulator.LeftClick();
// [Test Case] Press the activation shortcut and press left and right click somewhere, verifying the highlights are applied.
// [Test Case] Press the activation shortcut again and verify no highlights appear when the mouse buttons are clicked.
VerifyMouseHighlighterAppears(ref settings, "leftClick");
VerifyMouseHighlighterAppears(ref settings, "rightClick");
// Disable mouse highlighter
Session.SendKeys(Key.Win, Key.Shift, Key.H);
Task.Delay(1000).Wait();
VerifyMouseHighlighterNotAppears(ref settings, "leftClick");
VerifyMouseHighlighterNotAppears(ref settings, "rightClick");
// [Test Case] Disable Mouse Highlighter and verify that the module is not activated when you press the activation shortcut.
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1 - 100, xy.Item2);
Session.SendKeys(Key.Win, Key.Shift, Key.H);
Task.Delay(1000).Wait();
VerifyMouseHighlighterNotAppears(ref settings, "leftClick");
VerifyMouseHighlighterNotAppears(ref settings, "rightClick");
// [Test Case] With left mouse button pressed, drag the mouse and verify the hightlight is dragged with the pointer.
// [Test Case] With right mouse button pressed, drag the mouse and verify the hightlight is dragged with the pointer.
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1 - 100, xy.Item2);
Session.SendKeys(Key.Win, Key.Shift, Key.H);
Task.Delay(1000).Wait();
VerifyMouseHighlighterDrag(ref settings, "leftClick");
VerifyMouseHighlighterDrag(ref settings, "rightClick");
}
else
{
Assert.Fail("Mouse Highlighter Custom not found.");
}
Task.Delay(500).Wait();
}
[TestMethod("MouseUtils.MouseHighlighter.MouseHighlighterDifferentSettings")]
[TestCategory("Mouse Utils #22")]
[TestCategory("Mouse Utils #23")]
[TestCategory("Mouse Utils #24")]
public void TestMouseHighlighterDifferentSettings()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
}
else
{
Assert.Fail("Find My Mouse custom not found.");
}
var settings = new MouseHighlighterSettings();
settings.PrimaryButtonHighlightColor = "FF000000";
settings.SecondaryButtonHighlightColor = "FFFFFFFF";
settings.AlwaysHighlightColor = "004cFF71";
settings.Radius = "70";
settings.FadeDelay = "0";
settings.FadeDuration = "90";
var foundCustom = this.Find<Custom>("Mouse Highlighter");
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(false);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
foundCustom.Find<ToggleSwitch>("Enable Mouse Highlighter").Toggle(true);
// Change the shortcut key for MouseHighlighter
// [TestCase] Test the different settings and verify they apply - Change activation shortcut and test it
// [Test Case] Test the different settings and verify they apply - Left button highlight color
// [Test Case] Test the different settings and verify they apply - Right button highlight color
// [Test Case] Test the different settings and verify they apply - Radius
var activationShortcutButton = foundCustom.Find<Button>("Activation shortcut");
Assert.IsNotNull(activationShortcutButton);
activationShortcutButton.Click();
Task.Delay(500).Wait();
var activationShortcutWindow = Session.Find<Window>("Activation shortcut");
Assert.IsNotNull(activationShortcutWindow);
// Invalid shortcut key
Session.SendKeySequence(Key.H);
// IOUtil.SimulateKeyPress(0x41);
var invalidShortcutText = activationShortcutWindow.Find<TextBlock>("Invalid shortcut");
Assert.IsNotNull(invalidShortcutText);
// IOUtil.SimulateShortcut(0x5B, 0x10, 0x45);
Session.SendKeys(Key.Win, Key.Shift, Key.O);
// Assert.IsNull(activationShortcutWindow.Find<TextBlock>("Invalid shortcut"));
var saveButton = activationShortcutWindow.Find<Button>("Save");
Assert.IsNotNull(saveButton);
saveButton.Click(false, 500, 1000);
SetMouseHighlighterAppearanceBehavior(ref foundCustom, ref settings);
var xy0 = Session.GetMousePosition();
Session.MoveMouseTo(xy0.Item1 - 100, xy0.Item2);
Session.PerformMouseAction(MouseActionType.LeftClick);
// Check the mouse highlighter is enabled
Session.SendKeys(Key.Win, Key.Shift, Key.O);
Task.Delay(1000).Wait();
VerifyMouseHighlighterAppears(ref settings, "leftClick");
VerifyMouseHighlighterAppears(ref settings, "rightClick");
}
else
{
Assert.Fail("Mouse Highlighter Custom not found.");
}
Task.Delay(500).Wait();
}
private void VerifyMouseHighlighterDrag(ref MouseHighlighterSettings settings, string action = "leftClick")
{
Task.Delay(500).Wait();
string expectedColor = string.Empty;
if (action == "leftClick")
{
IOUtil.SimulateMouseDown(true);
expectedColor = settings.PrimaryButtonHighlightColor.Substring(2);
}
else if (action == "rightClick")
{
IOUtil.SimulateMouseDown(false);
expectedColor = settings.SecondaryButtonHighlightColor.Substring(2);
}
else
{
Assert.Fail("Invalid action specified.");
}
expectedColor = "#" + expectedColor;
var location = Session.GetMousePosition();
int radius = int.Parse(settings.Radius, CultureInfo.InvariantCulture);
var colorLeftClick = GetPixelColorString(location.Item1, location.Item2);
Assert.AreEqual(expectedColor, colorLeftClick);
var colorLeftClick2 = GetPixelColorString(location.Item1 + radius - 1, location.Item2);
Assert.AreEqual(expectedColor, colorLeftClick2);
var colorBackground = GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
Assert.AreNotEqual(expectedColor, colorBackground);
// Drag the mouse
// Session.MoveMouseTo(location.Item1 - 400, location.Item2);
for (int i = 0; i < 500; i++)
{
IOUtil.MoveMouseBy(-1, 0);
Task.Delay(10).Wait();
}
Task.Delay(2000).Wait();
location = Session.GetMousePosition();
colorLeftClick = GetPixelColorString(location.Item1, location.Item2);
Assert.AreEqual(expectedColor, colorLeftClick);
colorLeftClick2 = GetPixelColorString(location.Item1 + radius - 1, location.Item2);
Assert.AreEqual(expectedColor, colorLeftClick2);
colorBackground = GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
Assert.AreNotEqual(expectedColor, colorBackground);
if (action == "leftClick")
{
IOUtil.SimulateMouseUp(true);
}
else if (action == "rightClick")
{
IOUtil.SimulateMouseUp(false);
}
int duration = int.Parse(settings.FadeDuration, CultureInfo.InvariantCulture);
Task.Delay(duration + 100).Wait();
colorLeftClick = GetPixelColorString(location.Item1, location.Item2);
Assert.AreNotEqual("#" + settings.PrimaryButtonHighlightColor, colorLeftClick);
}
private void VerifyMouseHighlighterNotAppears(ref MouseHighlighterSettings settings, string action = "leftClick")
{
Task.Delay(500).Wait();
string expectedColor = string.Empty;
if (action == "leftClick")
{
// MouseSimulator.LeftDown();
Session.PerformMouseAction(MouseActionType.LeftDown);
expectedColor = settings.PrimaryButtonHighlightColor.Substring(2);
}
else if (action == "rightClick")
{
// MouseSimulator.RightDown();
Session.PerformMouseAction(MouseActionType.RightDown);
expectedColor = settings.SecondaryButtonHighlightColor.Substring(2);
}
else
{
Assert.Fail("Invalid action specified.");
}
expectedColor = "#" + expectedColor;
var location = Session.GetMousePosition();
int radius = int.Parse(settings.Radius, CultureInfo.InvariantCulture);
var colorLeftClick = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreNotEqual(expectedColor, colorLeftClick);
var colorLeftClick2 = this.GetPixelColorString(location.Item1 + radius - 1, location.Item2);
Assert.AreNotEqual(expectedColor, colorLeftClick2);
if (action == "leftClick")
{
Session.PerformMouseAction(MouseActionType.LeftUp);
}
else if (action == "rightClick")
{
Session.PerformMouseAction(MouseActionType.RightUp);
}
}
private void VerifyMouseHighlighterAppears(ref MouseHighlighterSettings settings, string action = "leftClick")
{
Task.Delay(500).Wait();
string expectedColor = string.Empty;
if (action == "leftClick")
{
// MouseSimulator.LeftDown();
Session.PerformMouseAction(MouseActionType.LeftDown);
expectedColor = settings.PrimaryButtonHighlightColor.Substring(2);
}
else if (action == "rightClick")
{
// MouseSimulator.RightDown();
Session.PerformMouseAction(MouseActionType.RightDown);
expectedColor = settings.SecondaryButtonHighlightColor.Substring(2);
}
else
{
Assert.Fail("Invalid action specified.");
}
expectedColor = "#" + expectedColor;
var location = Session.GetMousePosition();
int radius = int.Parse(settings.Radius, CultureInfo.InvariantCulture);
var colorLeftClick = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreEqual(expectedColor, colorLeftClick);
var colorLeftClick2 = this.GetPixelColorString(location.Item1 + radius - 1, location.Item2);
Assert.AreEqual(expectedColor, colorLeftClick2);
Task.Delay(500).Wait();
var colorBackground = this.GetPixelColorString(location.Item1 + radius + 50, location.Item2 + radius + 50);
Assert.AreNotEqual(expectedColor, colorBackground);
if (action == "leftClick")
{
// MouseSimulator.LeftUp();
Session.PerformMouseAction(MouseActionType.LeftUp);
}
else if (action == "rightClick")
{
// MouseSimulator.RightUp();
Session.PerformMouseAction(MouseActionType.RightUp);
}
int duration = int.Parse(settings.FadeDuration, CultureInfo.InvariantCulture);
Task.Delay(duration + 100).Wait();
colorLeftClick = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreNotEqual("#" + settings.PrimaryButtonHighlightColor, colorLeftClick);
}
private void SetColor(ref Custom foundCustom, string colorName = "Primary button highlight color", string colorValue = "000000", string opacity = "0")
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
if (groupAppearanceBehavior != null)
{
if (foundCustom.FindAll<TextBox>("Fade duration (ms) Minimum0").Count == 0)
{
groupAppearanceBehavior.Click();
}
// Set primary button highlight color
var primaryButtonHighlightColor = foundCustom.Find<Group>(colorName);
Assert.IsNotNull(primaryButtonHighlightColor);
var button = primaryButtonHighlightColor.Find<Button>(By.XPath(".//Button"));
Assert.IsNotNull(button);
button.Click(false, 500, 700);
var popupWindow = Session.Find<Window>("Popup");
Assert.IsNotNull(popupWindow);
var colorModelComboBox = this.Find<ComboBox>("Color model");
Assert.IsNotNull(colorModelComboBox);
colorModelComboBox.Click(false, 500, 700);
var selectedItem = colorModelComboBox.Find<NavigationViewItem>("RGB");
selectedItem.Click();
var rgbHexEdit = this.Find<TextBox>("RGB hex");
Assert.IsNotNull(rgbHexEdit);
Task.Delay(500).Wait();
rgbHexEdit.SetText(colorValue);
int retry = 5;
while (retry > 0)
{
Task.Delay(500).Wait();
rgbHexEdit.SetText(colorValue);
Task.Delay(500).Wait();
string rgbHex = rgbHexEdit.Text;
bool isValid = rgbHex.StartsWith('#') && rgbHex.Length == 9 && rgbHex.Substring(1) == colorValue;
Task.Delay(500).Wait();
if (isValid)
{
break;
}
retry--;
}
Task.Delay(500).Wait();
button.Click();
}
}
private void SetMouseHighlighterAppearanceBehavior(ref Custom foundCustom, ref MouseHighlighterSettings settings)
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
if (foundCustom.FindAll<TextBox>(settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.FadeDurationEdit)).Count == 0)
{
groupAppearanceBehavior.Click();
}
// Set primary button highlight color
SetColor(ref foundCustom, settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.PrimaryButtonHighlightColorGroup), settings.PrimaryButtonHighlightColor);
// Set secondary button highlight color
SetColor(ref foundCustom, settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.SecondaryButtonHighlightColorGroup), settings.SecondaryButtonHighlightColor);
// Set the duration to duration ms
var fadeDurationEdit = foundCustom.Find<TextBox>(settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.FadeDurationEdit));
Assert.IsNotNull(fadeDurationEdit);
fadeDurationEdit.SetText(settings.FadeDuration);
Assert.AreEqual(settings.FadeDuration, fadeDurationEdit.Text);
// Set Fade delay(ms)
var fadeDelayEdit = foundCustom.Find<TextBox>(settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.FadeDelayEdit));
Assert.IsNotNull(fadeDelayEdit);
fadeDelayEdit.SetText(settings.FadeDelay);
Assert.AreEqual(settings.FadeDelay, fadeDelayEdit.Text);
// Set the fade radius (px)
var fadeRadiusEdit = foundCustom.Find<TextBox>(settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.RadiusEdit));
Assert.IsNotNull(fadeRadiusEdit);
fadeRadiusEdit.SetText(settings.Radius);
Assert.AreEqual(settings.Radius, fadeRadiusEdit.Text);
// Set always highlight color
SetColor(ref foundCustom, settings.GetElementName(MouseHighlighterSettings.SettingsUIElements.AlwaysHighlightColorGroup), settings.AlwaysHighlightColor);
}
else
{
Assert.Fail("Appearance & behavior group not found.");
}
}
private void LaunchFromSetting(bool showWarning = false, bool launchAsAdmin = false)
{
this.Session.SetMainWindowSize(WindowSize.Large);
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").Click();
}
this.Find<NavigationViewItem>("Mouse utilities").Click();
}
}
}

View File

@@ -1,238 +0,0 @@
// 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.Threading.Tasks;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MouseUtils.UITests
{
[TestClass]
public class MouseJumpTests : UITestBase
{
[TestMethod("MouseUtils.MouseJump.EnableMouseJump")]
[TestCategory("Mouse Utils #39")]
[TestCategory("Mouse Utils #40")]
[TestCategory("Mouse Utils #41")]
[TestCategory("Mouse Utils #45")]
public void TestEnableMouseJump()
{
LaunchFromSetting(true);
}
[TestMethod("MouseUtils.MouseJump.EnableMouseJump2")]
[TestCategory("Mouse Utils #39")]
[TestCategory("Mouse Utils #41")]
[TestCategory("Mouse Utils #45")]
public void TestEnableMouseJump2()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
}
else
{
Assert.Fail("Find My Mouse custom not found.");
}
for (int i = 0; i < 10; i++)
{
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
var foundCustom = this.Find<Custom>("Mouse Jump");
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(true);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
// Change the shortcut key for MouseHighlighter
// [TestCase]Change activation shortcut and test it
var activationShortcutButton = foundCustom.Find<Button>("Activation shortcut");
Assert.IsNotNull(activationShortcutButton);
activationShortcutButton.Click(false, 500, 1000);
var activationShortcutWindow = Session.Find<Window>("Activation shortcut");
Assert.IsNotNull(activationShortcutWindow);
// Invalid shortcut key
Session.SendKeySequence(Key.H);
// IOUtil.SimulateKeyPress(0x41);
var invalidShortcutText = activationShortcutWindow.Find<TextBlock>("Invalid shortcut");
Assert.IsNotNull(invalidShortcutText);
// IOUtil.SimulateShortcut(0x5B, 0x10, 0x45);
Session.SendKeys(Key.Win, Key.Shift, Key.Z);
// Assert.IsNull(activationShortcutWindow.Find<TextBlock>("Invalid shortcut"));
var saveButton = activationShortcutWindow.Find<Button>("Save");
Assert.IsNotNull(saveButton);
saveButton.Click(false, 500, 1500);
var screenCenter = this.GetScreenCenter();
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY, 500, 1000);
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY - 300, 500, 1000);
// [TestCase] Enable Mouse Jump. Then - Press the activation shortcut and verify the screens preview appears.
// [TestCase] Enable Mouse Jump. Then - Click around the screen preview and ensure that mouse cursor jumped to clicked location.
Session.SendKeys(Key.Win, Key.Shift, Key.Z);
VerifyWindowAppears();
Task.Delay(1000).Wait();
// [TestCase] Enable Mouse Jump. Then - Disable Mouse Jump and verify that the module is not activated when you press the activation shortcut.
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(false);
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY - 300, 500, 1000);
Session.SendKeys(Key.Win, Key.Shift, Key.Z);
Task.Delay(500).Wait();
VerifyWindowNotAppears();
}
else
{
Assert.Fail("Mouse Highlighter Custom not found.");
}
Task.Delay(500).Wait();
}
[TestMethod("MouseUtils.MouseJump.EnableMouseJump3")]
[TestCategory("Mouse Utils #40")]
public void TestEnableMouseJump3()
{
LaunchFromSetting();
var foundCustom0 = this.Find<Custom>("Find My Mouse");
if (foundCustom0 != null)
{
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(true);
foundCustom0.Find<ToggleSwitch>("Enable Find My Mouse").Toggle(false);
}
else
{
Assert.Fail("Find My Mouse custom not found.");
}
for (int i = 0; i < 10; i++)
{
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
var foundCustom = this.Find<Custom>("Mouse Jump");
if (foundCustom != null)
{
foundCustom.Find<ToggleSwitch>("Enable Mouse Jump").Toggle(true);
var xy = Session.GetMousePosition();
Session.MoveMouseTo(xy.Item1, xy.Item2 - 100);
// Change the shortcut key for MouseHighlighter
// [TestCase]Change activation shortcut and test it
var activationShortcutButton = foundCustom.Find<Button>("Activation shortcut");
Assert.IsNotNull(activationShortcutButton);
activationShortcutButton.Click(false, 500, 1000);
var activationShortcutWindow = Session.Find<Window>("Activation shortcut");
Assert.IsNotNull(activationShortcutWindow);
// Invalid shortcut key
Session.SendKeySequence(Key.H);
// IOUtil.SimulateKeyPress(0x41);
var invalidShortcutText = activationShortcutWindow.Find<TextBlock>("Invalid shortcut");
Assert.IsNotNull(invalidShortcutText);
// IOUtil.SimulateShortcut(0x5B, 0x10, 0x45);
Session.SendKeys(Key.Win, Key.Shift, Key.J);
// Assert.IsNull(activationShortcutWindow.Find<TextBlock>("Invalid shortcut"));
var saveButton = activationShortcutWindow.Find<Button>("Save");
Assert.IsNotNull(saveButton);
saveButton.Click(false, 500, 1500);
var screenCenter = this.GetScreenCenter();
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY, 500, 1000);
Session.MoveMouseTo(screenCenter.CenterX, screenCenter.CenterY - 300, 500, 1000);
// [TestCase] Enable Mouse Jump. Then - Change activation shortcut and verify that new shortcut triggers Mouse Jump.
Session.SendKeys(Key.Win, Key.Shift, Key.J);
VerifyWindowAppears();
}
else
{
Assert.Fail("Mouse Highlighter Custom not found.");
}
Task.Delay(500).Wait();
}
private void VerifyWindowAppears()
{
string windowName = "MouseJump";
Session.Attach(windowName);
var center = this.Session.GetMainWindowCenter();
Session.MoveMouseTo(center.CenterX, center.CenterY);
Session.PerformMouseAction(MouseActionType.LeftClick, 1000, 1000);
var screenCenter = this.GetScreenCenter();
// Get Mouse position
var xy = Session.GetMousePosition();
double distance = CalculateDistance(xy.Item1, xy.Item2, screenCenter.CenterX, screenCenter.CenterY);
Assert.IsTrue(distance <= 10, "Mouse Jump window should be opened and mouse should be moved to the center of the screen.");
}
private void VerifyWindowNotAppears()
{
string windowName = "MouseJump";
bool open = this.IsWindowOpen(windowName);
Assert.IsFalse(open, "Mouse Jump window should not be opened.");
}
/// <summary>
/// Calculate the Euclidean distance between two 2D points
/// </summary>
/// <param name="x1">X coordinate of first point</param>
/// <param name="y1">Y coordinate of first point</param>
/// <param name="x2">X coordinate of second point</param>
/// <param name="y2">Y coordinate of second point</param>
/// <returns>Distance (double)</returns>
public double CalculateDistance(int x1, int y1, int x2, int y2)
{
int dx = x2 - x1;
int dy = y2 - y1;
return Math.Sqrt((dx * dx) + (dy * dy));
}
private void LaunchFromSetting(bool firstTime = false, bool launchAsAdmin = false)
{
Session.SetMainWindowSize(WindowSize.Large);
Task.Delay(1000).Wait();
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").ClickCenter();
Task.Delay(2000).Wait();
}
// Click on the Mouse utilities
// Task.Delay(2000).Wait();
if (firstTime)
{
return;
}
else
{
this.Find<NavigationViewItem>("Mouse utilities").Click();
}
}
}
}

View File

@@ -1,404 +0,0 @@
// 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.Globalization;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Devices.Printers;
namespace MouseUtils.UITests
{
[TestClass]
public class MousePointerCrosshairsTests : UITestBase
{
[TestMethod("MouseUtils.MousePointerCrosshairs.EnableMousePointerCrosshairs")]
[TestCategory("Mouse Utils #29")]
[TestCategory("Mouse Utils #30")]
[TestCategory("Mouse Utils #31")]
public void TestEnableMousePointerCrosshairs()
{
LaunchFromSetting();
var settings = new MousePointerCrosshairsSettings();
settings.CrosshairsColor = "FF0000";
settings.CrosshairsBorderColor = "FF0000";
settings.Opacity = "100";
settings.CenterRadius = "0";
settings.Thickness = "20";
settings.BorderSize = "0";
settings.IsFixLength = false;
settings.FixedLength = "1";
var foundCustom = FindMouseUtilElement(MouseUtilsSettings.MouseUtils.FindMyMouse);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.FindMyMouse, false);
foundCustom = FindMouseUtilElement(MouseUtilsSettings.MouseUtils.MouseHighlighter);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MouseHighlighter, true);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MouseHighlighter, false);
for (int i = 0; i < 10; i++)
{
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
foundCustom = FindMouseUtilElement(MouseUtilsSettings.MouseUtils.MousePointerCrosshairs);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MousePointerCrosshairs, false);
Task.Delay(500).Wait();
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MousePointerCrosshairs, true);
Assert.IsNotNull(foundCustom);
// [Test Case] Change activation shortcut and test it.
var activationShortcutButton = foundCustom.Find<Button>("Activation shortcut");
Assert.IsNotNull(activationShortcutButton);
activationShortcutButton.Click(false, 500, 1000);
var activationShortcutWindow = Session.Find<Window>("Activation shortcut");
Assert.IsNotNull(activationShortcutWindow);
// Invalid shortcut key
Session.SendKeySequence(Key.H);
var invalidShortcutText = activationShortcutWindow.Find<TextBlock>("Invalid shortcut");
Assert.IsNotNull(invalidShortcutText);
Session.SendKeys(Key.Win, Key.Alt, Key.A);
var saveButton = activationShortcutWindow.Find<Button>("Save");
Assert.IsNotNull(saveButton);
saveButton.Click(false, 500, 1000);
SetMousePointerCrosshairsAppearanceBehavior(ref foundCustom, ref settings);
Task.Delay(500).Wait();
// [Test Case] Press the activation shortcut and verify the crosshairs appear, and that they follow the mouse around.
var xy0 = Session.GetMousePosition();
Session.MoveMouseTo(xy0.Item1 - 100, xy0.Item2);
IOUtil.MouseClick();
Task.Delay(500).Wait();
Session.SendKeys(Key.Win, Key.Alt, Key.A);
Task.Delay(1000).Wait();
xy0 = Session.GetMousePosition();
VerifyMousePointerCrosshairsAppears(ref settings);
Task.Delay(500).Wait();
for (int i = 0; i < 100; i++)
{
IOUtil.MoveMouseBy(-1, 0);
Task.Delay(10).Wait();
}
VerifyMousePointerCrosshairsAppears(ref settings);
// [Test Case] Press the activation shortcut again and verify the crosshairs disappear.
Session.SendKeys(Key.Win, Key.Alt, Key.A);
Task.Delay(1000).Wait();
VerifyMousePointerCrosshairsNotAppears(ref settings);
Task.Delay(500).Wait();
// [Test Case] Disable Mouse Pointer Crosshairs and verify that the module is not activated when you press the activation shortcut.
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MousePointerCrosshairs, false);
xy0 = Session.GetMousePosition();
Session.MoveMouseTo(xy0.Item1 - 100, xy0.Item2);
Session.PerformMouseAction(MouseActionType.LeftClick);
Session.SendKeys(Key.Win, Key.Alt, Key.A);
Task.Delay(1000).Wait();
VerifyMousePointerCrosshairsNotAppears(ref settings);
}
[TestMethod("MouseUtils.MousePointerCrosshairs.MousePointerCrosshairsDifferentSettings")]
[TestCategory("Mouse Utils #32")]
[TestCategory("Mouse Utils #33")]
public void TestMousePointerCrosshairsDifferentSettings()
{
LaunchFromSetting();
var settings = new MousePointerCrosshairsSettings();
settings.CrosshairsColor = "00FF00";
settings.CrosshairsBorderColor = "00FF00";
settings.Opacity = "100";
settings.CenterRadius = "0";
settings.Thickness = "20";
settings.BorderSize = "0";
settings.IsFixLength = false;
settings.FixedLength = "1";
var foundCustom = FindMouseUtilElement(MouseUtilsSettings.MouseUtils.FindMyMouse);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.FindMyMouse, false);
foundCustom = FindMouseUtilElement(MouseUtilsSettings.MouseUtils.MouseHighlighter);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MouseHighlighter, true);
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MouseHighlighter, false);
for (int i = 0; i < 10; i++)
{
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
foundCustom = FindMouseUtilElement(MouseUtilsSettings.MouseUtils.MousePointerCrosshairs);
// this.FindGroup("Enable Mouse Pointer Crosshairs");
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MousePointerCrosshairs, false);
Task.Delay(500).Wait();
MouseUtilsSettings.SetMouseUtilEnabled(foundCustom, MouseUtilsSettings.MouseUtils.MousePointerCrosshairs, true);
Assert.IsNotNull(foundCustom);
// [Test Case] Change activation shortcut and test it.
var activationShortcutButton = foundCustom.Find<Button>("Activation shortcut");
Assert.IsNotNull(activationShortcutButton);
activationShortcutButton.Click(false, 500, 1000);
var activationShortcutWindow = Session.Find<Window>("Activation shortcut");
Assert.IsNotNull(activationShortcutWindow);
// Invalid shortcut key
Session.SendKeySequence(Key.H);
var invalidShortcutText = activationShortcutWindow.Find<TextBlock>("Invalid shortcut");
Assert.IsNotNull(invalidShortcutText);
Session.SendKeys(Key.Win, Key.Alt, Key.P);
var saveButton = activationShortcutWindow.Find<Button>("Save");
Assert.IsNotNull(saveButton);
saveButton.Click(false, 500, 1000);
SetMousePointerCrosshairsAppearanceBehavior(ref foundCustom, ref settings);
Task.Delay(500).Wait();
// [Test Case] Test the different settings and verify they apply - Change activation shortcut and test it.
// [Test Case] Test the different settings and verify they apply - Crosshairs color.
var xy0 = Session.GetMousePosition();
Session.MoveMouseTo(xy0.Item1 - 100, xy0.Item2);
IOUtil.MouseClick();
Task.Delay(500).Wait();
Session.SendKeys(Key.Win, Key.Alt, Key.P);
Task.Delay(1000).Wait();
xy0 = Session.GetMousePosition();
VerifyMousePointerCrosshairsAppears(ref settings);
Task.Delay(500).Wait();
for (int i = 0; i < 100; i++)
{
IOUtil.MoveMouseBy(-1, 0);
Task.Delay(10).Wait();
}
VerifyMousePointerCrosshairsAppears(ref settings);
// Press the activation shortcut again and verify the crosshairs disappear.
Session.SendKeys(Key.Win, Key.Alt, Key.P);
Task.Delay(1000).Wait();
VerifyMousePointerCrosshairsNotAppears(ref settings);
}
private void VerifyMousePointerCrosshairsNotAppears(ref MousePointerCrosshairsSettings settings)
{
Task.Delay(500).Wait();
string expectedColor = string.Empty;
expectedColor = "#" + settings.CrosshairsColor;
var location = Session.GetMousePosition();
int radius = int.Parse(settings.CenterRadius, CultureInfo.InvariantCulture);
var color = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreNotEqual(expectedColor, color);
}
private void VerifyMousePointerCrosshairsAppears(ref MousePointerCrosshairsSettings settings)
{
Task.Delay(1000).Wait();
string expectedColor = string.Empty;
expectedColor = "#" + settings.CrosshairsColor;
var location = Session.GetMousePosition();
int radius = int.Parse(settings.CenterRadius, CultureInfo.InvariantCulture);
var color = this.GetPixelColorString(location.Item1, location.Item2);
Assert.AreEqual(expectedColor, color, "Center color check failed");
var colorX = this.GetPixelColorString(location.Item1 + 50, location.Item2);
Assert.AreEqual(expectedColor, colorX, "Center x + 50 color check failed");
colorX = this.GetPixelColorString(location.Item1 - 50, location.Item2);
Assert.AreEqual(expectedColor, colorX, "Center x - 50 color check failed");
var colorY = this.GetPixelColorString(location.Item1, location.Item2 + 50);
Assert.AreEqual(expectedColor, colorY, "Center y + 50 color check failed");
colorY = this.GetPixelColorString(location.Item1, location.Item2 - 50);
Assert.AreEqual(expectedColor, colorY, "Center y + 50 color check failed");
}
private void SetColor(ref Custom foundCustom, string colorName, string colorValue = "000000")
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
if (groupAppearanceBehavior != null)
{
// Set primary button highlight color
var primaryButtonHighlightColor = foundCustom.Find<Group>(colorName);
Assert.IsNotNull(primaryButtonHighlightColor);
var button = primaryButtonHighlightColor.Find<Button>(By.XPath(".//Button"));
Assert.IsNotNull(button);
button.Click(false);
var popupWindow = Session.Find<Window>("Popup");
Assert.IsNotNull(popupWindow);
var colorModelComboBox = this.Find<ComboBox>("Color model");
Assert.IsNotNull(colorModelComboBox);
colorModelComboBox.Click();
var selectedItem = colorModelComboBox.Find<NavigationViewItem>("RGB");
selectedItem.Click();
var rgbHexEdit = this.Find<TextBox>("RGB hex");
Assert.IsNotNull(rgbHexEdit);
rgbHexEdit.SetText(colorValue);
button.Click();
}
}
private void SetMousePointerCrosshairsAppearanceBehavior(ref Custom foundCustom, ref MousePointerCrosshairsSettings settings)
{
Assert.IsNotNull(foundCustom);
var groupAppearanceBehavior = foundCustom.Find<TextBlock>("Appearance & behavior");
if (groupAppearanceBehavior != null)
{
// groupAppearanceBehavior.Click();
if (foundCustom.FindAll<TextBox>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.ThicknessEdit)).Count == 0)
{
groupAppearanceBehavior.Click();
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
Session.PerformMouseAction(MouseActionType.ScrollDown);
}
// Set the crosshairs color
SetColor(ref foundCustom, settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.CrosshairsColorGroup), settings.CrosshairsColor);
// Set the crosshairs border color
SetColor(ref foundCustom, settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.CrosshairsBorderColorGroup), settings.CrosshairsBorderColor);
// Set the duration to duration ms
var opacitySlider = foundCustom.Find<Slider>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.OpacitySlider));
Assert.IsNotNull(opacitySlider);
Assert.IsNotNull(settings.Opacity);
int opacityValue = int.Parse(settings.Opacity, CultureInfo.InvariantCulture);
opacitySlider.QuickSetValue(opacityValue);
Assert.AreEqual(settings.Opacity, opacitySlider.Text);
Task.Delay(500).Wait();
// Set the center radius (px)
var centerRadiusEdit = foundCustom.Find<TextBox>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.CenterRadiusEdit));
Assert.IsNotNull(centerRadiusEdit);
centerRadiusEdit.SetText(settings.CenterRadius);
Assert.AreEqual(settings.CenterRadius, centerRadiusEdit.Text);
// Set the thickness (px)
var thicknessEdit = foundCustom.Find<TextBox>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.ThicknessEdit));
Assert.IsNotNull(thicknessEdit);
thicknessEdit.SetText(settings.Thickness);
Assert.AreEqual(settings.Thickness, thicknessEdit.Text);
// Set the border size (px)
var borderSizeEdit = foundCustom.Find<TextBox>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.BorderSizeEdit));
Assert.IsNotNull(borderSizeEdit);
borderSizeEdit.SetText(settings.BorderSize);
Assert.AreEqual(settings.BorderSize, borderSizeEdit.Text);
// Set the fixed length (px)
var isFixedLength = foundCustom.Find<ToggleSwitch>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.IsFixLengthToggle));
Assert.IsNotNull(isFixedLength);
isFixedLength.Toggle(settings.IsFixLength);
Assert.AreEqual(settings.IsFixLength, isFixedLength.IsOn);
if (settings.IsFixLength)
{
var fixedLengthEdit = foundCustom.Find<TextBox>(settings.GetElementName(MousePointerCrosshairsSettings.SettingsUIElements.FixedLengthEdit));
Assert.IsNotNull(fixedLengthEdit);
fixedLengthEdit.SetText(settings.FixedLength);
Assert.AreEqual(settings.FixedLength, fixedLengthEdit.Text);
}
}
else
{
Assert.Fail("Appearance & behavior group not found.");
}
}
private bool FindGroup(string groupName)
{
try
{
var foundElements = this.FindAll<Element>(groupName);
foreach (var element in foundElements)
{
string className = element.ClassName;
string name = element.Name;
string text = element.Text;
string helptext = element.HelpText;
string controlType = element.ControlType;
}
if (foundElements.Count == 0)
{
return false;
}
}
catch (Exception ex)
{
// Validate if group is not found by checking exception.Message
return ex.Message.Contains("No element found");
}
return true;
}
public Custom? FindMouseUtilElement(MouseUtilsSettings.MouseUtils element)
{
var elementName = MouseUtilsSettings.GetMouseUtilUIName(element);
var foundCustom = this.Find<Custom>(elementName);
for (int i = 0; i < 20; i++)
{
if (foundCustom != null)
{
break;
}
Session.PerformMouseAction(MouseActionType.ScrollDown);
foundCustom = this.Find<Custom>(elementName);
}
return foundCustom;
}
private void LaunchFromSetting(bool showWarning = false, bool launchAsAdmin = false)
{
Session.SetMainWindowSize(WindowSize.Large);
// Goto Hosts File Editor setting page
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
{
// Expand Advanced list-group if needed
this.Find<NavigationViewItem>("Input / Output").Click();
}
this.Find<NavigationViewItem>("Mouse utilities").Click();
}
}
}

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<ProjectGuid>{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}</ProjectGuid>
<RootNamespace>PowerToys.MouseUtils.UITests</RootNamespace>
<AssemblyName>PowerToys.MouseUtils.UITests</AssemblyName>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<!-- This is a UI test, so don't run as part of MSBuild -->
<RunVSTest>false</RunVSTest>
</PropertyGroup>
<PropertyGroup>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\MouseUtils.UITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,64 +0,0 @@
## [Mouse Utils](tests-checklist-template-mouse-utils-section.md)
Find My Mouse:
* Enable FindMyMouse. Then, without moving your mouse:
- [x] Press Left Ctrl twice and verify the overlay appears.
- [x] Press any other key and verify the overlay disappears.
- [x] Press Left Ctrl twice and verify the overlay appears.
- [x] Press a mouse button and verify the overlay disappears.
* Disable FindMyMouse. Verify the overlay no longer appears when you press Left Ctrl twice.
* Enable FindMyMouse. Then, without moving your mouse:
- [x] Press Left Ctrl twice and verify the overlay appears.
* Enable the "Do not activate on game mode" option. Start playing a game that uses CG native full screen.
- [ ] Verify the overlay no longer appears when you press Left Ctrl twice.
* Disable the "Do not activate on game mode" option. Start playing the same game.
- [ ] Verify the overlay appears when you press Left Ctrl twice. (though it'll likely minimize the game)
* Test the different settings and verify they apply:
- [ ] Overlay opacity
- [x] Background color
- [x] Spotlight color
- [x] Spotlight radius
- [ ] Spotlight initial zoom (1x vs 9x will show the difference)
- [ ] Animation duration
- [ ] Change activation method to shake and activate by shaking your mouse pointer
- [ ] Excluded apps
Mouse Highlighter:
* Enable Mouse Highlighter. Then:
- [x] Press the activation shortcut and press left and right click somewhere, verifying the hightlights are applied.
- [x] With left mouse button pressed, drag the mouse and verify the hightlight is dragged with the pointer.
- [x] With right mouse button pressed, drag the mouse and verify the hightlight is dragged with the pointer.
- [x] Press the activation shortcut again and verify no highlights appear when the mouse buttons are clicked.
- [x] Disable Mouse Highlighter and verify that the module is not activated when you press the activation shortcut.
* Test the different settings and verify they apply:
- [x] Change activation shortcut and test it
- [x] Left button highlight color
- [x] Right button highlight color
- [ ] Opacity
- [ ] Radius
- [ ] Fade delay
- [ ] Fade duration
Mouse Pointer Crosshairs:
* Enable Mouse Pointer Crosshairs. Then:
- [x] Press the activation shortcut and verify the crosshairs appear, and that they follow the mouse around.
- [x] Press the activation shortcut again and verify the crosshairs disappear.
- [x] Disable Mouse Pointer Crosshairs and verify that the module is not activated when you press the activation shortcut.
* Test the different settings and verify they apply:
- [x] Change activation shortcut and test it
- [x] Crosshairs color
- [ ] Crosshairs opacity
- [ ] Crosshairs center radius
- [ ] Crosshairs thickness
- [ ] Crosshairs border color
- [ ] Crosshairs border size
Mouse Jump:
* Enable Mouse Jump. Then:
- [x] Press the activation shortcut and verify the screens preview appears.
- [x] Change activation shortcut and verify that new shorctut triggers Mouse Jump.
- [x] Click around the screen preview and ensure that mouse cursor jumped to clicked location.
- [ ] Reorder screens in Display settings and confirm that Mouse Jump reflects the change and still works correctly.
- [ ] Change scaling of screens and confirm that Mouse Jump still works correctly.
- [ ] Unplug additional monitors and confirm that Mouse Jump still works correctly.
- [x] Disable Mouse Jump and verify that the module is not activated when you press the activation shortcut.

View File

@@ -1,58 +0,0 @@
// 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.Text;
using System.Threading.Tasks;
namespace MouseUtils.UITests
{
public class FindMyMouseSettings
{
// Appearance settings
public string OverlayOpacity { get; set; }
public string Radius { get; set; }
public string InitialZoom { get; set; }
public string AnimationDuration { get; set; }
// Color settings
public string BackgroundColor { get; set; }
public string SpotlightColor { get; set; }
// Activation method settings
public enum ActivationMethod
{
PressLeftControlTwice,
PressRightControlTwice,
ShakeMouse,
CustomShortcut,
}
public ActivationMethod SelectedActivationMethod { get; set; }
// Optional constructor to initialize properties
public FindMyMouseSettings(
string overlayOpacity = "",
string radius = "",
string initialZoom = "",
string animationDuration = "",
string backgroundColor = "",
string spotlightColor = "")
{
OverlayOpacity = overlayOpacity;
Radius = radius;
InitialZoom = initialZoom;
AnimationDuration = animationDuration;
BackgroundColor = backgroundColor;
SpotlightColor = spotlightColor;
SelectedActivationMethod = ActivationMethod.PressLeftControlTwice; // Default value
}
}
}

View File

@@ -1,285 +0,0 @@
// 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 MouseUtils.UITests
{
public class IOUtil
{
private readonly UIntPtr ignoreKeyEventFlag = 0x5555;
[DllImport("user32.dll")]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[StructLayout(LayoutKind.Sequential)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")]
internal struct INPUT
{
internal INPUTTYPE type;
internal InputUnion data;
internal static int Size
{
get { return Marshal.SizeOf<INPUT>(); }
}
}
[StructLayout(LayoutKind.Explicit)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")]
internal struct InputUnion
{
[FieldOffset(0)]
internal MOUSEINPUT mi;
[FieldOffset(0)]
internal KEYBDINPUT ki;
[FieldOffset(0)]
internal HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")]
internal struct MOUSEINPUT
{
internal int dx;
internal int dy;
internal int mouseData;
internal uint dwFlags;
internal uint time;
internal UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")]
internal struct KEYBDINPUT
{
internal short wVk;
internal short wScan;
internal uint dwFlags;
internal int time;
internal UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")]
internal struct HARDWAREINPUT
{
internal int uMsg;
internal short wParamL;
internal short wParamH;
}
internal enum INPUTTYPE : uint
{
INPUT_MOUSE = 0,
INPUT_KEYBOARD = 1,
INPUT_HARDWARE = 2,
}
[Flags]
internal enum KeyEventF
{
KeyDown = 0x0000,
ExtendedKey = 0x0001,
KeyUp = 0x0002,
Unicode = 0x0004,
Scancode = 0x0008,
}
[Flags]
internal enum MouseEventF : uint
{
MOVE = 0x0001,
LEFTDOWN = 0x0002,
LEFTUP = 0x0004,
RIGHTDOWN = 0x0008,
RIGHTUP = 0x0010,
ABSOLUTE = 0x8000,
MIDDLEDOWN = 0x0020,
MIDDLEUP = 0x0040,
}
public static void SimulateMouseDown(bool leftButton = true)
{
SendMouseInput(leftButton ? MouseEventF.LEFTDOWN : MouseEventF.RIGHTDOWN);
}
public static void SimulateMouseUp(bool leftButton = true)
{
SendMouseInput(leftButton ? MouseEventF.LEFTUP : MouseEventF.RIGHTUP);
}
public static void MouseClick(bool leftButton = true)
{
SendMouseInput(leftButton ? MouseEventF.LEFTDOWN : MouseEventF.RIGHTDOWN);
SendMouseInput(leftButton ? MouseEventF.LEFTUP : MouseEventF.RIGHTUP);
}
private static void SendMouseInput(MouseEventF mouseFlags)
{
var input = new INPUT
{
type = INPUTTYPE.INPUT_MOUSE,
data = new InputUnion
{
mi = new MOUSEINPUT
{
dx = 0,
dy = 0,
mouseData = 0,
dwFlags = (uint)mouseFlags,
time = 0,
dwExtraInfo = UIntPtr.Zero,
},
},
};
INPUT[] inputs = [input];
_ = SendInput(1, inputs, INPUT.Size);
}
[DllImport("user32.dll")]
private static extern IntPtr GetMessageExtraInfo();
public static void MoveMouseBy(int dx, int dy)
{
var input = new INPUT
{
type = INPUTTYPE.INPUT_MOUSE,
data = new InputUnion
{
mi = new MOUSEINPUT
{
dx = dx,
dy = dy,
mouseData = 0,
dwFlags = (uint)MouseEventF.MOVE,
time = 0,
dwExtraInfo = (nuint)GetMessageExtraInfo(),
},
},
};
INPUT[] inputs = [input];
_ = SendInput(1, inputs, INPUT.Size);
}
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
public static void MoveMouseTo(int x, int y)
{
int screenWidth = GetSystemMetrics(0);
int screenHeight = GetSystemMetrics(1);
int normalizedX = (int)(x * 65535 / screenWidth);
int normalizedY = (int)(y * 65535 / screenHeight);
var input = new INPUT
{
type = INPUTTYPE.INPUT_MOUSE,
data = new InputUnion
{
mi = new MOUSEINPUT
{
dx = normalizedX,
dy = normalizedY,
mouseData = 0,
dwFlags = (uint)(MouseEventF.MOVE | MouseEventF.ABSOLUTE),
time = 0,
dwExtraInfo = UIntPtr.Zero,
},
},
};
INPUT[] inputs = [input];
_ = SendInput(1, inputs, INPUT.Size);
}
private void SendSingleKeyboardInput(short keyCode, uint keyStatus)
{
var inputShift = new INPUT
{
type = INPUTTYPE.INPUT_KEYBOARD,
data = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = keyCode,
dwFlags = keyStatus,
// Any keyevent with the extraInfo set to this value will be ignored by the keyboard hook and sent to the system instead.
dwExtraInfo = ignoreKeyEventFlag,
},
},
};
INPUT[] inputs = [inputShift];
_ = SendInput(1, inputs, INPUT.Size);
}
public static void SimulateKeyDown(ushort keyCode)
{
SendKey(keyCode, false);
}
public static void SimulateKeyUp(ushort keyCode)
{
SendKey(keyCode, true);
}
public static void SimulateKeyPress(ushort keyCode)
{
SendKey(keyCode, false);
SendKey(keyCode, true);
}
public static void SimulateShortcut(params ushort[] keyCodes)
{
foreach (var key in keyCodes)
{
SimulateKeyDown(key);
}
for (int i = keyCodes.Length - 1; i >= 0; i--)
{
SimulateKeyUp(keyCodes[i]);
}
}
public static void SendKey(ushort keyCode, bool keyUp)
{
var inputShift = new INPUT
{
type = INPUTTYPE.INPUT_KEYBOARD,
data = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = (short)keyCode,
dwFlags = (uint)(keyUp ? KeyEventF.KeyUp : 0),
dwExtraInfo = (uint)IntPtr.Zero,
},
},
};
INPUT[] inputs = [inputShift];
_ = SendInput(1, inputs, INPUT.Size);
}
}
}

View File

@@ -1,74 +0,0 @@
// 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.Text;
using System.Threading.Tasks;
namespace MouseUtils.UITests
{
public class MouseHighlighterSettings
{
// Appearance settings
public string Radius { get; set; }
public string FadeDelay { get; set; }
public string FadeDuration { get; set; }
// Color settings
public string PrimaryButtonHighlightColor { get; set; }
public string SecondaryButtonHighlightColor { get; set; }
public string AlwaysHighlightColor { get; set; }
// Settings UI Elements
public enum SettingsUIElements
{
PrimaryButtonHighlightColorGroup,
SecondaryButtonHighlightColorGroup,
AlwaysHighlightColorGroup,
RadiusEdit,
FadeDelayEdit,
FadeDurationEdit,
}
private Dictionary<SettingsUIElements, string> ElementNameMap { get; }
// Optional constructor to initialize properties
public MouseHighlighterSettings(
string radius = "",
string fadeDelay = "",
string fadeDuration = "",
string primaryButtonHighlightColor = "",
string secondaryButtonHighlightColor = "",
string alwaysHighlightColor = "")
{
this.Radius = radius;
this.FadeDelay = fadeDelay;
this.FadeDuration = fadeDuration;
this.PrimaryButtonHighlightColor = primaryButtonHighlightColor;
this.SecondaryButtonHighlightColor = secondaryButtonHighlightColor;
this.AlwaysHighlightColor = alwaysHighlightColor;
ElementNameMap = new Dictionary<SettingsUIElements, string>
{
[SettingsUIElements.PrimaryButtonHighlightColorGroup] = @"Primary button highlight color",
[SettingsUIElements.SecondaryButtonHighlightColorGroup] = @"Secondary button highlight color",
[SettingsUIElements.AlwaysHighlightColorGroup] = @"Always highlight color",
[SettingsUIElements.RadiusEdit] = @"Radius (px) Minimum5",
[SettingsUIElements.FadeDelayEdit] = @"Fade delay (ms) Minimum0",
[SettingsUIElements.FadeDurationEdit] = @"Fade duration (ms) Minimum0",
};
}
public string GetElementName(SettingsUIElements element)
{
return ElementNameMap[element];
}
}
}

View File

@@ -1,86 +0,0 @@
// 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.Text;
using System.Threading.Tasks;
using Windows.UI.Core.AnimationMetrics;
namespace MouseUtils.UITests
{
public class MousePointerCrosshairsSettings
{
// Appearance settings
public string Opacity { get; set; }
public string CenterRadius { get; set; }
public string Thickness { get; set; }
public string BorderSize { get; set; }
public bool IsFixLength { get; set; }
public string FixedLength { get; set; }
// Color settings
public string CrosshairsColor { get; set; }
public string CrosshairsBorderColor { get; set; }
// Settings UI Elements
public enum SettingsUIElements
{
CrosshairsColorGroup,
CrosshairsBorderColorGroup,
OpacitySlider,
CenterRadiusEdit,
ThicknessEdit,
BorderSizeEdit,
FixedLengthEdit,
IsFixLengthToggle,
}
private Dictionary<SettingsUIElements, string> ElementNameMap { get; }
// Optional constructor to initialize properties
public MousePointerCrosshairsSettings(
string opacity = "",
string centerRadius = "",
string thickness = "",
string borderSize = "",
bool isFixLength = false,
string fixedLength = "",
string crosshairsColor = "",
string crosshairsBorderColor = "")
{
this.Opacity = opacity;
this.CenterRadius = centerRadius;
this.Thickness = thickness;
this.BorderSize = borderSize;
this.IsFixLength = isFixLength;
this.FixedLength = fixedLength;
this.CrosshairsColor = crosshairsColor;
this.CrosshairsBorderColor = crosshairsBorderColor;
ElementNameMap = new Dictionary<SettingsUIElements, string>
{
[SettingsUIElements.CrosshairsColorGroup] = @"Crosshairs color",
[SettingsUIElements.CrosshairsBorderColorGroup] = @"Crosshairs border color",
[SettingsUIElements.OpacitySlider] = @"Crosshairs opacity (%)",
[SettingsUIElements.CenterRadiusEdit] = @"Crosshairs center radius (px) Minimum0 Maximum500",
[SettingsUIElements.ThicknessEdit] = @"Crosshairs thickness (px) Minimum1 Maximum50",
[SettingsUIElements.BorderSizeEdit] = @"Crosshairs border size (px) Minimum0 Maximum50",
[SettingsUIElements.FixedLengthEdit] = @"Crosshairs fixed length (px) Minimum1",
[SettingsUIElements.IsFixLengthToggle] = @"Fix crosshairs length",
};
}
public string GetElementName(SettingsUIElements element)
{
return ElementNameMap[element];
}
}
}

View File

@@ -1,59 +0,0 @@
// 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 Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium.Appium.Windows;
namespace MouseUtils.UITests
{
public class MouseUtilsSettings
{
// Mouse Utils Modules
public enum MouseUtils
{
MouseHighlighter,
FindMyMouse,
MousePointerCrosshairs,
MouseJump,
}
private static readonly Dictionary<MouseUtils, string> MouseUtilUINameMap = new()
{
[MouseUtils.MouseHighlighter] = @"Mouse Highlighter",
[MouseUtils.FindMyMouse] = @"Find My Mouse",
[MouseUtils.MousePointerCrosshairs] = @"Mouse Pointer Crosshairs",
[MouseUtils.MouseJump] = @"Mouse Jump",
};
private static readonly Dictionary<MouseUtils, string> MouseUtilUIToggleMap = new()
{
[MouseUtils.MouseHighlighter] = @"Enable Mouse Highlighter",
[MouseUtils.FindMyMouse] = @"Enable Find My Mouse",
[MouseUtils.MousePointerCrosshairs] = @"Enable Mouse Pointer Crosshairs",
[MouseUtils.MouseJump] = @"Enable Mouse Jump",
};
public static string GetMouseUtilUIName(MouseUtils element)
{
return MouseUtilUINameMap[element];
}
public static void SetMouseUtilEnabled(Custom? custom, MouseUtils element, bool isEnable = true)
{
if (custom != null)
{
string toggleName = MouseUtilUIToggleMap[element];
var toggle = custom.Find<ToggleSwitch>(toggleName);
toggle.Toggle(isEnable);
}
else
{
Assert.Fail(element + " custom not found.");
}
}
}
}

View File

@@ -1,24 +0,0 @@
// 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 Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace WorkspacesEditorUITest;
[TestClass]
public class WorkspacesEditorTests : UITestBase
{
public WorkspacesEditorTests()
: base(PowerToysModule.Workspaces, WindowSize.Medium)
{
}
[TestMethod("WorkspacesEditor.Items.Present")]
[TestCategory("Workspaces UI")]
public void TestItemsPresents()
{
Assert.IsTrue(this.Has<Button>("Create Workspace"), "Should have create workspace button");
}
}

View File

@@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<RunVSTest>false</RunVSTest>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<AssemblyName>PowerToys.Workspaces.UITests</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\Workspaces.UITests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Appium.WebDriver" />
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
</ItemGroup>
</Project>

View File

@@ -217,9 +217,10 @@ public:
CmdPal::m_enabled.store(true);
std::wstring packageName = L"Microsoft.CommandPalette";
std::wstring launchPath = L"x-cmdpal://background";
std::wstring launchPath = L"shell:AppsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App";
#ifdef IS_DEV_BRANDING
packageName = L"Microsoft.CommandPalette.Dev";
launchPath = L"shell:AppsFolder\\Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App";
#endif
if (!package::GetRegisteredPackage(packageName, false).has_value())
@@ -268,7 +269,7 @@ public:
if (!firstEnableCall)
{
Logger::trace("Not first attempt, try to launch");
LaunchApp(launchPath, L"", false /*no elevated*/, false /*error pop up*/);
LaunchApp(launchPath, L"RunFromPT", false /*no elevated*/, false /*error pop up*/);
}
else
{
@@ -296,7 +297,7 @@ public:
int retry = 0;
do
{
auto launch_result = LaunchApp(path, L"", false, retry < max_retry);
auto launch_result = LaunchApp(path, L"RunFromPT", false, retry < max_retry);
if (launch_result)
{
Logger::info(L"CmdPal launched successfully after {} retries.", retry);

View File

@@ -3,7 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.2.0" />
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.1.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0-preview.24508.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
@@ -12,6 +12,6 @@
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250401001" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
<PackageVersion Include="System.Text.Json" Version="9.0.3" />
</ItemGroup>
</Project>

View File

@@ -128,7 +128,7 @@ public partial class CommandBarViewModel : ObservableObject,
// this comes in when the primary button is tapped
public void InvokePrimaryCommand()
{
PerformCommand(PrimaryCommand);
PerformCommand(SecondaryCommand);
}
// this comes in when the secondary button is tapped

View File

@@ -66,10 +66,8 @@ public sealed class CommandProviderWrapper
DisplayName = provider.DisplayName;
Icon = new(provider.Icon);
Icon.InitializeProperties();
// Note: explicitly not InitializeProperties()ing the settings here. If
// we do that, then we'd regress GH #38321
Settings = new(provider.Settings, this, _taskScheduler);
Settings.InitializeProperties();
Logger.LogDebug($"Initialized command provider {ProviderId}");
}
@@ -153,11 +151,9 @@ public sealed class CommandProviderWrapper
Icon = new(model.Icon);
Icon.InitializeProperties();
// Note: explicitly not InitializeProperties()ing the settings here. If
// we do that, then we'd regress GH #38321
Settings = new(model.Settings, this, _taskScheduler);
Settings.InitializeProperties();
// We do need to explicitly initialize commands though
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
@@ -198,6 +194,21 @@ public sealed class CommandProviderWrapper
}
}
/* This is a View/ExtensionHost piece
* public void AllowSetForeground(bool allow)
{
if (!IsExtension)
{
return;
}
var iextn = extensionWrapper?.GetExtensionObject();
unsafe
{
PInvoke.CoAllowSetForegroundWindow(iextn);
}
}*/
public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid;
public override int GetHashCode() => _commandProvider.GetHashCode();

View File

@@ -2,25 +2,18 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
public partial class CommandSettingsViewModel(ICommandSettings _unsafeSettings, CommandProviderWrapper provider, TaskScheduler mainThread)
{
private readonly ExtensionObject<ICommandSettings> _model = new(_unsafeSettings);
public ContentPageViewModel? SettingsPage { get; private set; }
public bool Initialized { get; private set; }
public bool HasSettings =>
_model.Unsafe != null && // We have a settings model AND
(!Initialized || SettingsPage != null); // we weren't initialized, OR we were, and we do have a settings page
private void UnsafeInitializeProperties()
public void InitializeProperties()
{
var model = _model.Unsafe;
if (model == null)
@@ -34,27 +27,4 @@ public partial class CommandSettingsViewModel(ICommandSettings? _unsafeSettings,
SettingsPage.InitializeProperties();
}
}
public void SafeInitializeProperties()
{
try
{
UnsafeInitializeProperties();
}
catch (Exception ex)
{
Logger.LogError($"Failed to load settings page", ex: ex);
}
Initialized = true;
}
public void DoOnUiThread(Action action)
{
Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.None,
mainThread);
}
}

View File

@@ -6,7 +6,6 @@ using System.Text.Json;
using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Templating;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
@@ -29,67 +28,43 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
public AdaptiveCardParseResult? Card { get; private set; }
private static string Serialize(string? s) =>
JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
private static bool TryBuildCard(
string templateJson,
string dataJson,
out AdaptiveCardParseResult? card,
out Exception? error)
{
card = null;
error = null;
try
{
var template = new AdaptiveCardTemplate(templateJson);
var cardJson = template.Expand(dataJson);
card = AdaptiveCard.FromJsonString(cardJson);
return true;
}
catch (Exception ex)
{
Logger.LogError("Error building card from template: {Message}", ex.Message);
error = ex;
return false;
}
}
public override void InitializeProperties()
{
var model = _formModel.Unsafe;
if (model is null)
if (model == null)
{
return;
}
TemplateJson = model.TemplateJson;
StateJson = model.StateJson;
DataJson = model.DataJson;
if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError))
try
{
Card = builtCard;
UpdateProperty(nameof(Card));
return;
TemplateJson = model.TemplateJson;
StateJson = model.StateJson;
DataJson = model.DataJson;
AdaptiveCardTemplate template = new(TemplateJson);
var cardJson = template.Expand(DataJson);
Card = AdaptiveCard.FromJsonString(cardJson);
}
var errorPayload = $$"""
{
"error_message": {{Serialize(renderingError!.Message)}},
"error_stack": {{Serialize(renderingError.StackTrace)}},
"inner_exception": {{Serialize(renderingError.InnerException?.Message)}},
"template_json": {{Serialize(TemplateJson)}},
"data_json": {{Serialize(DataJson)}}
}
""";
if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _))
catch (Exception e)
{
Card = errorCard;
UpdateProperty(nameof(Card));
return;
// If we fail to parse the card JSON, then display _our own card_
// with the exception
AdaptiveCardTemplate template = new(ErrorCardJson);
var serializeString = (string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
// todo: we could probably stick Card.Errors in there too
var dataJson = $$"""
{
"error_message": {{serializeString(e.Message)}},
"error_stack": {{serializeString(e.StackTrace)}},
"inner_exception": {{serializeString(e.InnerException?.Message)}},
"template_json": {{serializeString(TemplateJson)}},
"data_json": {{serializeString(DataJson)}}
}
""";
var cardJson = template.Expand(dataJson);
Card = AdaptiveCard.FromJsonString(cardJson);
}
UpdateProperty(nameof(Card));

View File

@@ -61,8 +61,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
private Task? _initializeItemsTask;
private CancellationTokenSource? _cancellationTokenSource;
private ListItemViewModel? _lastSelectedItem;
public override bool IsInitialized
{
get => base.IsInitialized; protected set
@@ -332,11 +330,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
[RelayCommand]
private void UpdateSelectedItem(ListItemViewModel? item)
{
if (_lastSelectedItem != null)
{
_lastSelectedItem.PropertyChanged -= SelectedItemPropertyChanged;
}
if (item != null)
{
SetSelectedItem(item);
@@ -374,43 +367,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
TextToSuggest = item.TextToSuggest;
});
_lastSelectedItem = item;
_lastSelectedItem.PropertyChanged += SelectedItemPropertyChanged;
}
private void SelectedItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var item = _lastSelectedItem;
if (item == null)
{
return;
}
// already on the UI thread here
switch (e.PropertyName)
{
case nameof(item.Command):
case nameof(item.SecondaryCommand):
case nameof(item.AllCommands):
case nameof(item.Name):
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(item));
break;
case nameof(item.Details):
if (ShowDetails && item.HasDetails)
{
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(item.Details));
}
else
{
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
}
break;
case nameof(item.TextToSuggest):
TextToSuggest = item.TextToSuggest;
break;
}
}
private void ClearSelectedItem()

View File

@@ -18,8 +18,6 @@ public partial class ProviderSettingsViewModel(
IServiceProvider _serviceProvider) : ObservableObject
{
private readonly SettingsModel _settings = _serviceProvider.GetService<SettingsModel>()!;
private readonly Lock _initializeSettingsLock = new();
private Task? _initializeSettingsTask;
public string DisplayName => _provider.DisplayName;
@@ -36,9 +34,6 @@ public partial class ProviderSettingsViewModel(
public IconInfoViewModel Icon => _provider.Icon;
[ObservableProperty]
public partial bool LoadingSettings { get; set; } = _provider.Settings?.HasSettings ?? false;
public bool IsEnabled
{
get => _providerSettings.IsEnabled;
@@ -61,60 +56,15 @@ public partial class ProviderSettingsViewModel(
}
}
/// <summary>
/// Gets a value indicating whether returns true if we have a settings page
/// that's initialized, or we are still working on initializing that
/// settings page. If we don't have a settings object, or that settings
/// object doesn't have a settings page, then we'll return false.
/// </summary>
public bool HasSettings
private void Provider_CommandsChanged(CommandProviderWrapper sender, CommandPalette.Extensions.IItemsChangedEventArgs args)
{
get
{
if (_provider.Settings == null)
{
return false;
}
if (_provider.Settings.Initialized)
{
return _provider.Settings.HasSettings;
}
// settings still need to be loaded.
return LoadingSettings;
}
OnPropertyChanged(nameof(ExtensionSubtext));
OnPropertyChanged(nameof(TopLevelCommands));
}
/// <summary>
/// Gets will return the settings page, if we have one, and have initialized it.
/// If we haven't initialized it, this will kick off a thread to start
/// initializing it.
/// </summary>
public ContentPageViewModel? SettingsPage
{
get
{
if (_provider.Settings == null)
{
return null;
}
public bool HasSettings => _provider.Settings != null && _provider.Settings.SettingsPage != null;
if (_provider.Settings.Initialized)
{
LoadingSettings = false;
return _provider.Settings.SettingsPage;
}
// Don't load the settings if we're already working on it
lock (_initializeSettingsLock)
{
_initializeSettingsTask ??= Task.Run(InitializeSettingsPage);
}
return null;
}
}
public ContentPageViewModel? SettingsPage => HasSettings ? _provider?.Settings?.SettingsPage : null;
[field: AllowNull]
public List<TopLevelViewModel> TopLevelCommands
@@ -140,30 +90,4 @@ public partial class ProviderSettingsViewModel(
}
private void Save() => SettingsModel.SaveSettings(_settings);
private void InitializeSettingsPage()
{
if (_provider.Settings == null)
{
return;
}
_provider.Settings.SafeInitializeProperties();
_provider.Settings.DoOnUiThread(() =>
{
// Changing these properties will try to update XAML, and that has
// to be handled on the UI thread, so we need to raise them on the
// UI thread
LoadingSettings = false;
OnPropertyChanged(nameof(HasSettings));
OnPropertyChanged(nameof(LoadingSettings));
OnPropertyChanged(nameof(SettingsPage));
});
}
private void Provider_CommandsChanged(CommandProviderWrapper sender, CommandPalette.Extensions.IItemsChangedEventArgs args)
{
OnPropertyChanged(nameof(ExtensionSubtext));
OnPropertyChanged(nameof(TopLevelCommands));
}
}

View File

@@ -40,7 +40,7 @@ public partial class SettingsModel : ObservableObject
public bool ShowSystemTrayIcon { get; set; } = true;
public bool IgnoreShortcutWhenFullscreen { get; set; }
public bool IgnoreShortcutWhenFullscreen { get; set; } = true;
public Dictionary<string, ProviderSettings> ProviderSettings { get; set; } = [];

View File

@@ -45,9 +45,6 @@ public partial class TopLevelCommandManager : ObservableObject,
public async Task<bool> LoadBuiltinsAsync()
{
var s = new Stopwatch();
s.Start();
_builtInCommands.Clear();
// Load built-In commands first. These are all in-proc, and
@@ -67,10 +64,6 @@ public partial class TopLevelCommandManager : ObservableObject,
}
}
s.Stop();
Logger.LogDebug($"Loading built-ins took {s.ElapsedMilliseconds}ms");
return true;
}

View File

@@ -73,12 +73,26 @@ public partial class App : Application
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
AppWindow = new MainWindow();
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
((MainWindow)AppWindow).HandleLaunch(activatedEventArgs);
var cmdArgs = Environment.GetCommandLineArgs();
var runFromPT = false;
foreach (var arg in cmdArgs)
{
if (arg == "RunFromPT")
{
runFromPT = true;
break;
}
}
if (!runFromPT)
{
AppWindow.Activate();
}
}
/// <summary>

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
@@ -12,9 +13,4 @@ namespace Microsoft.CmdPal.UI.Events;
public class BeginInvoke : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public BeginInvoke()
{
EventName = "CmdPal_BeginInvoke";
}
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
@@ -12,9 +13,4 @@ namespace Microsoft.CmdPal.UI.Events;
public class ColdLaunch : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public ColdLaunch()
{
EventName = "CmdPal_ColdLaunch";
}
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
@@ -16,8 +17,6 @@ public class OpenPage : EventBase, IEvent
public OpenPage(int pageDepth)
{
PageDepth = pageDepth;
EventName = "CmdPal_OpenPage";
}
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.CommandPalette.Extensions;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
@@ -12,9 +13,4 @@ namespace Microsoft.CmdPal.UI.Events;
public class ReactivateInstance : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
public ReactivateInstance()
{
EventName = "CmdPal_ReactivateInstance";
}
}

View File

@@ -19,8 +19,6 @@ using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Graphics;
using Windows.UI;
@@ -242,7 +240,7 @@ public sealed partial class MainWindow : WindowEx,
unsafe
{
BOOL value = false;
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, (void*)&value, (uint)sizeof(BOOL));
}
PInvoke.SetForegroundWindow(hwnd);
@@ -322,7 +320,7 @@ public sealed partial class MainWindow : WindowEx,
unsafe
{
BOOL value = true;
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, (void*)&value, (uint)sizeof(BOOL));
}
}
@@ -425,40 +423,6 @@ public sealed partial class MainWindow : WindowEx,
}
}
public void HandleLaunch(AppActivationArguments? activatedEventArgs)
{
if (activatedEventArgs == null)
{
Summon(string.Empty);
return;
}
if (activatedEventArgs.Kind == Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Protocol)
{
if (activatedEventArgs.Data is IProtocolActivatedEventArgs protocolArgs)
{
if (protocolArgs.Uri.ToString() is string uri)
{
// was the URI "x-cmdpal://background" ?
if (uri.StartsWith("x-cmdpal://background", StringComparison.OrdinalIgnoreCase))
{
// we're running, we don't want to activate our window. bail
return;
}
else if (uri.StartsWith("x-cmdpal://settings", StringComparison.OrdinalIgnoreCase))
{
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
return;
}
}
return;
}
}
Activate();
}
public void Summon(string commandId) =>
// The actual showing and hiding of the window will be done by the

View File

@@ -39,7 +39,6 @@ WM_RBUTTONUP
WM_LBUTTONUP
WM_LBUTTONDBLCLK
MessageBox
DwmGetWindowAttribute
DwmSetWindowAttribute
DWM_CLOAKED_APP
DWM_CLOAKED_APP

View File

@@ -70,13 +70,6 @@
DisplayName="ms-resource:StartupTaskNameDev" />
</uap5:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="x-cmdpal">
<uap:Logo>Assets\StoreLogo.png</uap:Logo>
<uap:DisplayName>Command Palette Dev URI scheme</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>

View File

@@ -70,14 +70,6 @@
DisplayName="ms-resource:StartupTaskName" />
</uap5:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="x-cmdpal">
<uap:Logo>Assets\StoreLogo.png</uap:Logo>
<uap:DisplayName>Command Palette URI scheme</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>

View File

@@ -418,20 +418,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
{
_ = DispatcherQueue.TryEnqueue(() =>
{
OpenSettings();
if (_settingsWindow == null)
{
_settingsWindow = new SettingsWindow();
}
_settingsWindow.Activate();
});
}
public void OpenSettings()
{
if (_settingsWindow == null)
{
_settingsWindow = new SettingsWindow();
}
_settingsWindow.Activate();
}
public void Receive(ShowDetailsMessage message)
{
// TERRIBLE HACK TODO GH #245

View File

@@ -2,14 +2,10 @@
// 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.Runtime.InteropServices;
using ManagedCommon;
using Microsoft.CmdPal.UI.Events;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Windows.AppLifecycle;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.UI;
@@ -34,33 +30,7 @@ internal sealed class Program
return 0;
}
try
{
Logger.InitializeLogger("\\CmdPal\\Logs\\");
}
catch (COMException e)
{
// This is unexpected. For the sake of debugging:
// pop a message box
PInvoke.MessageBox(
(HWND)IntPtr.Zero,
$"Failed to initialize the logger. COMException: \r{e.Message}",
"Command Palette",
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
return 0;
}
catch (Exception e2)
{
// This is unexpected. For the sake of debugging:
// pop a message box
PInvoke.MessageBox(
(HWND)IntPtr.Zero,
$"Failed to initialize the logger. Unknown Exception: \r{e2.Message}",
"Command Palette",
MESSAGEBOX_STYLE.MB_OK | MESSAGEBOX_STYLE.MB_ICONERROR);
return 0;
}
Logger.InitializeLogger("\\CmdPal\\Logs\\");
Logger.LogDebug($"Starting at {DateTime.UtcNow}");
PowerToysTelemetry.Log.WriteEvent(new CmdPalProcessStarted());
@@ -109,9 +79,7 @@ internal sealed class Program
if (thisApp.AppWindow is not null and
MainWindow mainWindow)
{
mainWindow.HandleLaunch(args);
// mainWindow.Summon(string.Empty);
mainWindow.Summon(string.Empty);
}
}
}

View File

@@ -2,8 +2,7 @@
"profiles": {
"Microsoft.CmdPal.UI (Package)": {
"commandName": "MsixPackage",
"nativeDebugging": false,
"doNotLaunchApp": false
"nativeDebugging": false
},
"Microsoft.CmdPal.UI (Unpackaged)": {
"commandName": "Project"

View File

@@ -113,24 +113,7 @@
Visibility="{x:Bind ViewModel.HasSettings}" />
<Frame x:Name="SettingsFrame" Visibility="{x:Bind ViewModel.HasSettings}">
<controls:SwitchPresenter
HorizontalAlignment="Stretch"
TargetType="x:Boolean"
Value="{x:Bind ViewModel.LoadingSettings, Mode=OneWay}">
<controls:Case Value="True">
<ProgressRing
Width="36"
Height="36"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsIndeterminate="True" />
</controls:Case>
<controls:Case Value="False">
<cmdpalUI:ContentPage ViewModel="{x:Bind ViewModel.SettingsPage, Mode=OneWay}" />
</controls:Case>
</controls:SwitchPresenter>
<cmdpalUI:ContentPage ViewModel="{x:Bind ViewModel.SettingsPage, Mode=OneWay}" />
</Frame>
<TextBlock

View File

@@ -2,10 +2,8 @@
// 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.Diagnostics;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -35,9 +33,8 @@ internal sealed partial class AppCommand : InvokableCommand
{
appManager.ActivateApplication(aumid, /*queryArguments*/ string.Empty, noFlags, out var unusedPid);
}
catch (System.Exception ex)
catch (System.Exception)
{
Logger.LogError(ex.Message);
}
}).ConfigureAwait(false);
}
@@ -49,14 +46,7 @@ internal sealed partial class AppCommand : InvokableCommand
// const ActivateOptions noFlags = ActivateOptions.None;
await Task.Run(() =>
{
try
{
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
}
catch (System.Exception ex)
{
Logger.LogError(ex.Message);
}
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
});
}

View File

@@ -5,7 +5,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -39,9 +38,9 @@ internal sealed partial class OpenInConsoleCommand : InvokableCommand
Process.Start(processStartInfo);
}
catch (Exception ex)
catch (Exception)
{
Logger.LogError(ex.Message);
// Log.Exception($"Failed to open {Name} in console, {e.Message}", e, GetType());
}
});
}

View File

@@ -1,11 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.Apps</RootNamespace>
<Nullable>enable</Nullable>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,7 +1,6 @@
GetPhysicallyInstalledSystemMemory
GlobalMemoryStatusEx
GetSystemInfo
CoCreateInstance
SetForegroundWindow
IsIconic
RegisterHotKey
@@ -13,7 +12,6 @@ SetFocus
SetActiveWindow
MonitorFromWindow
GetMonitorInfo
SHCreateStreamOnFileEx
CoAllowSetForegroundWindow
SHCreateStreamOnFileEx
SHLoadIndirectString
SHLoadIndirectString
IStream

View File

@@ -4,7 +4,6 @@
using System;
using System.IO;
using ManagedCommon;
using Windows.Foundation.Metadata;
using Package = Windows.ApplicationModel.Package;

View File

@@ -6,10 +6,9 @@ using System;
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
@@ -67,11 +66,18 @@ public partial class UWP
const uint noAttribute = 0x80;
var access = (uint)STGM.READ;
var hResult = PInvoke.SHCreateStreamOnFileEx(path, access, noAttribute, false, null, out IStream stream);
var hResult = Native.SHCreateStreamOnFileEx(path, access, noAttribute, false, IntPtr.Zero, out var streamPtr);
// S_OK
if (hResult == 0)
{
// create IStream from streamPtr
var stream = Marshal.GetTypedObjectForIUnknown(streamPtr, typeof(IStream)) as IStream;
if (stream == null)
{
return;
}
Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => new UWPApplication(appInManifest, this)).Where(a =>
{
var valid =
@@ -133,9 +139,8 @@ public partial class UWP
u = new UWP(p);
u.InitializeAppInfo(p.InstalledLocation);
}
catch (Exception ex)
catch (Exception )
{
Logger.LogError(ex.Message);
return Array.Empty<UWPApplication>();
}
@@ -164,9 +169,8 @@ public partial class UWP
var path = p.InstalledLocation;
return !f && !string.IsNullOrEmpty(path);
}
catch (Exception ex)
catch (Exception )
{
Logger.LogError(ex.Message);
return false;
}
});

View File

@@ -8,14 +8,12 @@ using System.IO.Abstractions;
using System.Linq;
using System.Text;
using System.Xml;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Microsoft.CommandPalette.Extensions.Toolkit;
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
using PackageVersion = Microsoft.CmdPal.Ext.Apps.Programs.UWP.PackageVersion;
using Theme = Microsoft.CmdPal.Ext.Apps.Utils.Theme;
namespace Microsoft.CmdPal.Ext.Apps.Programs;
@@ -156,9 +154,8 @@ public class UWPApplication : IProgram
return true;
}
}
catch (Exception ex)
catch (Exception)
{
Logger.LogError(ex.Message);
}
}
}
@@ -200,9 +197,10 @@ public class UWPApplication : IProgram
parsedFallback = prefix + "///" + key;
}
var outBuffer = new StringBuilder(128);
var outBuffer = new char[1024];
var source = $"@{{{packageFullName}? {parsed}}}";
var capacity = (uint)outBuffer.Capacity;
var capacity = (uint)outBuffer.Length;
var hResult = SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
if (hResult != HRESULT.S_OK)
{

View File

@@ -15,7 +15,6 @@ using System.Security;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Input;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Commands;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.Utils;
@@ -240,12 +239,10 @@ public class Win32Program : IProgram
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
}
@@ -320,13 +317,11 @@ public class Win32Program : IProgram
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
}
@@ -379,17 +374,15 @@ public class Win32Program : IProgram
return program;
}
catch (System.IO.FileLoadException e)
catch (System.IO.FileLoadException)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
// Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
// Error caused likely due to trying to get the description of the program
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
}
@@ -409,17 +402,14 @@ public class Win32Program : IProgram
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
catch (FileNotFoundException e)
catch (FileNotFoundException)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
return InvalidProgram;
}
}
@@ -525,19 +515,16 @@ public class Win32Program : IProgram
{
files.AddRange(Directory.EnumerateFiles(currentDirectory, $"*.{suffix}", SearchOption.TopDirectoryOnly));
}
catch (DirectoryNotFoundException e)
catch (DirectoryNotFoundException)
{
Logger.LogError(e.Message);
}
}
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Logger.LogError(e.Message);
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
}
try
@@ -561,11 +548,9 @@ public class Win32Program : IProgram
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Logger.LogError(e.Message);
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
}
}
while (folderQueue.Count > 0);
@@ -697,7 +682,6 @@ public class Win32Program : IProgram
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
Logger.LogError(e.Message);
return string.Empty;
}
}
@@ -785,9 +769,8 @@ public class Win32Program : IProgram
icoPath = ExpandEnvironmentVariables(redirectionPath);
return true;
}
catch (IOException e)
catch (IOException)
{
Logger.LogError(e.Message);
}
icoPath = null;
@@ -856,9 +839,8 @@ public class Win32Program : IProgram
return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true));
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
return Array.Empty<Win32Program>();
}
}

View File

@@ -7,7 +7,6 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using ManagedCommon;
namespace Microsoft.CmdPal.Ext.Apps.Storage;
@@ -38,9 +37,8 @@ public class ListRepository<T> : IRepository<T>, IEnumerable<T>
_items = new ConcurrentDictionary<int, T>(list.ToDictionary(i => i.GetHashCode()));
#pragma warning restore CS8602 // Dereference of a possibly null reference.
}
catch (ArgumentException ex)
catch (ArgumentException)
{
Logger.LogInfo(ex.Message);
}
}

View File

@@ -4,8 +4,8 @@
using System;
using System.Linq;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Storage;
using Microsoft.CmdPal.Ext.Apps.Utils;
using Windows.ApplicationModel;
@@ -93,9 +93,8 @@ internal sealed partial class PackageRepository : ListRepository<UWPApplication>
// InitializeAppInfo will throw if there is no AppxManifest.xml for the package.
// Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app.
// eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'."
catch (System.IO.FileNotFoundException ex)
catch (System.IO.FileNotFoundException)
{
Logger.LogError(ex.Message);
}
}

View File

@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ManagedCommon;
namespace Microsoft.CmdPal.Ext.Apps.Storage;
@@ -48,9 +47,8 @@ internal sealed partial class Win32ProgramFileSystemWatchers : IDisposable
{
Directory.GetFiles(path);
}
catch (Exception e)
catch (Exception)
{
Logger.LogError(e.Message);
invalidPaths.Add(path);
}
}

Some files were not shown because too many files have changed in this diff Show More