UI Test Automation (#39777)
### Summary This pull request includes the following updates: 1. Improvements and stabilization of the UI automation framework 2. Setup of the UI automation pipeline 3. Add UI test cases for FancyZones 4. Add UI test cases for MouseUtils 5. Improvements of Hosts Editor UI tests --- ### Related Links - **Current Release checklist coverage**: https://github.com/microsoft/PowerToys/blob/feature/UITestAutomation/src/common/UITestAutomation/Doc/ui-automation-cover-list.md - **UI Automation pipeline**: https://microsoft.visualstudio.com/Dart/_build?definitionId=161438&_a=summary --------- Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com> Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com> Co-authored-by: Jerry Xu <n.xu@outlook.com> Co-authored-by: Zhaopeng Wang <zhaopengwang@microsoft.com> Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com> Co-authored-by: Mengyuan <162882040+chenmy77@users.noreply.github.com> Co-authored-by: yaqingmi <miyaqing01@gmail.com> Co-authored-by: Clint Rutkas <clint@rutkas.com> Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com> Co-authored-by: Kai Tao <69313318+vanzue@users.noreply.github.com> Co-authored-by: zhaopeng wang <33367956+wang563681252@users.noreply.github.com> Co-authored-by: Laszlo Nemeth <57342539+donlaci@users.noreply.github.com> Co-authored-by: RokyZevon <12629919+RokyZevon@users.noreply.github.com> Co-authored-by: Yu Leng <42196638+moooyo@users.noreply.github.com> Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Davide Giacometti <25966642+davidegiacometti@users.noreply.github.com> Co-authored-by: Gordon Lam <73506701+yeelam-gordon@users.noreply.github.com> Co-authored-by: ruslanlap <106077551+ruslanlap@users.noreply.github.com> Co-authored-by: Muhammad Danish <mdanishkhdev@gmail.com> Co-authored-by: Bennett Blodinger <benwa@users.noreply.github.com> Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> Co-authored-by: Ionuț Manța <ionut@janeasystems.com> Co-authored-by: Hao Liu <liuhaobupt@163.com> Co-authored-by: OlegHarchevkin <40352094+OlegKharchevkin@users.noreply.github.com> Co-authored-by: dcog989 <89043002+dcog989@users.noreply.github.com> Co-authored-by: PesBandi <127593627+PesBandi@users.noreply.github.com> Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Co-authored-by: vanzue <vanzue@outlook.com> Co-authored-by: Typpi <20943337+Nick2bad4u@users.noreply.github.com> Co-authored-by: Mike Griese <migrie@microsoft.com> Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com> Co-authored-by: Abhyudit <64366765+bitmap4@users.noreply.github.com> Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> Co-authored-by: Ved Nig <vednig12@outlook.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Aung Khaing Khant <aungkhaingkhant.dev@gmail.com> Co-authored-by: Aung Khaing Khant <aungkhaingkhant@advent-soft.com> Co-authored-by: Dustin L. Howett <duhowett@microsoft.com> Co-authored-by: leileizhang <leilzh@microsoft.com> Co-authored-by: Dustin L. Howett <dustin@howett.net> Co-authored-by: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Co-authored-by: Shawn Yuan <shuai.yuan.zju@gmail.com> Co-authored-by: cryolithic <cryolithic@gmail.com> Co-authored-by: Lemonyte <49930425+lemonyte@users.noreply.github.com> Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com> Co-authored-by: Corey Hayward <72159232+CoreyHayward@users.noreply.github.com> Co-authored-by: Jerry Xu <nxu@microsoft.com> Co-authored-by: Shawn Yuan <shuaiyuan@microsoft.com> Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com> Co-authored-by: Jeremy Sinclair <4016293+snickler@users.noreply.github.com>
14
.github/actions/spell-check/expect.txt
vendored
@@ -192,6 +192,7 @@ CImage
|
||||
cla
|
||||
CLASSDC
|
||||
CLASSNOTAVAILABLE
|
||||
cleanmgr
|
||||
clickable
|
||||
clickonce
|
||||
CLIENTEDGE
|
||||
@@ -276,6 +277,7 @@ currentculture
|
||||
CURRENTDIR
|
||||
CURSORINFO
|
||||
cursorpos
|
||||
CURSORSHOWING
|
||||
customaction
|
||||
CUSTOMACTIONTEST
|
||||
CUSTOMFORMATPLACEHOLDER
|
||||
@@ -343,7 +345,9 @@ DESELECTOTHERS
|
||||
DESIGNINFO
|
||||
DESKTOPABSOLUTEEDITING
|
||||
DESKTOPABSOLUTEPARSING
|
||||
DESKTOPHORZRES
|
||||
desktopshorcutinstalled
|
||||
DESKTOPVERTRES
|
||||
devblogs
|
||||
devdocs
|
||||
devmgmt
|
||||
@@ -354,6 +358,7 @@ DFX
|
||||
DIALOGEX
|
||||
digicert
|
||||
dimm
|
||||
DINORMAL
|
||||
DISABLEASACTIONKEY
|
||||
DISABLENOSCROLL
|
||||
diskmgmt
|
||||
@@ -397,6 +402,7 @@ DVASPECTINFO
|
||||
DVD
|
||||
dvr
|
||||
DVTARGETDEVICE
|
||||
dwflags
|
||||
dwl
|
||||
dwm
|
||||
dwmapi
|
||||
@@ -485,6 +491,7 @@ FANCYZONESDRAWLAYOUTTEST
|
||||
FANCYZONESEDITOR
|
||||
FARPROC
|
||||
fff
|
||||
FFFF
|
||||
FILEEXPLORER
|
||||
FILEFLAGS
|
||||
FILEFLAGSMASK
|
||||
@@ -603,6 +610,7 @@ helptext
|
||||
HGFE
|
||||
hglobal
|
||||
hhk
|
||||
HHmmssfff
|
||||
hhx
|
||||
Hiber
|
||||
Hiberboot
|
||||
@@ -611,6 +619,7 @@ hicon
|
||||
HIDEREADONLY
|
||||
HIDEWINDOW
|
||||
Hif
|
||||
hightlight
|
||||
HIMAGELIST
|
||||
himl
|
||||
hinst
|
||||
@@ -745,6 +754,7 @@ isocpp
|
||||
iss
|
||||
issecret
|
||||
ISSEPARATOR
|
||||
istep
|
||||
ith
|
||||
ITHUMBNAIL
|
||||
IUI
|
||||
@@ -1275,6 +1285,7 @@ prvpane
|
||||
psapi
|
||||
pscid
|
||||
PSECURITY
|
||||
psexec
|
||||
psfgao
|
||||
psfi
|
||||
PSMODULEPATH
|
||||
@@ -1532,6 +1543,7 @@ SLGP
|
||||
sln
|
||||
SMALLICON
|
||||
smartphone
|
||||
smileys
|
||||
SMTO
|
||||
SNAPPROCESS
|
||||
snk
|
||||
@@ -1876,6 +1888,7 @@ WINDOWPOSCHANGING
|
||||
WINDOWSBUILDNUMBER
|
||||
windowssearch
|
||||
windowssettings
|
||||
windowsterminal
|
||||
WINDOWSTYLES
|
||||
WINDOWSTYLESICON
|
||||
winerror
|
||||
@@ -1985,6 +1998,7 @@ Zoneszonabletester
|
||||
Zoomin
|
||||
zoomit
|
||||
ZOOMITX
|
||||
Zorder
|
||||
ZXk
|
||||
ZXNs
|
||||
zzz
|
||||
|
@@ -53,6 +53,9 @@ parameters:
|
||||
- name: runTests
|
||||
type: boolean
|
||||
default: true
|
||||
- name: buildTests
|
||||
type: boolean
|
||||
default: true
|
||||
- name: useVSPreview
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -110,7 +113,7 @@ jobs:
|
||||
JobOutputArtifactName: build-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }}
|
||||
NUGET_RESTORE_MSBUILD_ARGS: /p:Platform=$(BuildPlatform) # Required for nuget to work due to self contained
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
${{ if eq(parameters.runTests, true) }}:
|
||||
${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
|
||||
MSBuildMainBuildTargets: Build;Test
|
||||
${{ else }}:
|
||||
MSBuildMainBuildTargets: Build
|
||||
@@ -528,7 +531,7 @@ jobs:
|
||||
displayName: Stage GPO files
|
||||
|
||||
# Running the tests may result in future jobs consuming artifacts out of this build
|
||||
- ${{ if eq(parameters.runTests, true) }}:
|
||||
- ${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
|
||||
- task: CopyFiles@2
|
||||
displayName: Stage entire build output
|
||||
inputs:
|
||||
|
@@ -15,20 +15,29 @@ parameters:
|
||||
jobs:
|
||||
- job: Test${{ parameters.platform }}${{ parameters.configuration }}
|
||||
displayName: Test ${{ parameters.platform }} ${{ parameters.configuration }}
|
||||
timeoutInMinutes: 300
|
||||
variables:
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
${{ if or(eq(parameters.platform, 'x64Win10'), eq(parameters.platform, 'x64Win11')) }}:
|
||||
BuildPlatform: x64
|
||||
${{ else }}:
|
||||
BuildPlatform: ${{ parameters.platform }}
|
||||
TestPlatform: ${{ parameters.platform }}
|
||||
BuildConfiguration: ${{ parameters.configuration }}
|
||||
SrcPath: $(Build.Repository.LocalPath)
|
||||
TestArtifactsName: build-${{ parameters.platform }}-${{ parameters.configuration }}${{ parameters.inputArtifactStem }}
|
||||
TestArtifactsName: build-${{ variables.BuildPlatform }}-${{ 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 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 eq(parameters.platform, 'x64Win11') }}:
|
||||
demands: ImageOverride -equals SHINE-W11-Testing
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-Testing-arm64
|
||||
steps:
|
||||
@@ -101,8 +110,13 @@ jobs:
|
||||
vsTestVersion: 'toolsInstaller'
|
||||
uiTests: true
|
||||
rerunFailedTests: true
|
||||
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
|
||||
testAssemblyVer2: |
|
||||
**\UITests-FancyZones.dll
|
||||
**\UITests-FancyZonesEditor.dll
|
||||
**\*UITest*.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
!**\ref\**
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
|
||||
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
|
@@ -60,16 +60,4 @@ stages:
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
- ${{ if and(eq(parameters.runTests, true), not(and(eq(platform, 'arm64'), eq(variables['System.PullRequest.IsFork'], true)))) }}:
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
dependsOn:
|
||||
- Build_${{platform}}
|
||||
jobs:
|
||||
- template: job-test-project.yml
|
||||
parameters:
|
||||
platform: ${{ platform }}
|
||||
configuration: Release
|
||||
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
84
.pipelines/v2/templates/pipeline-ui-tests-automation.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
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 }}
|
||||
|
||||
- ${{ 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 }}
|
||||
|
||||
- ${{ 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 }}
|
@@ -92,4 +92,3 @@ 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
|
||||
|
||||
|
@@ -717,6 +717,10 @@ 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
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalculatorEngineCommon", "src\common\CalculatorEngineCommon\CalculatorEngineCommon.vcxproj", "{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -2599,6 +2603,22 @@ 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
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2768,7 +2788,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} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{7AC943C9-52E8-44CF-9083-744D8049667B} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
{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}
|
||||
@@ -2900,6 +2920,8 @@ 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}
|
||||
{0217E86E-3476-9946-DE8E-9D200CEBD47A} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
|
||||
|
@@ -21,67 +21,74 @@
|
||||
|
||||
- Create a new project and add the following references to the project file. Change the OutputPath to your own module's path.
|
||||
```
|
||||
<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>
|
||||
<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>
|
||||
|
||||
```
|
||||
- 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 RunFancyZonesTest : UITestBase
|
||||
{
|
||||
public RunFancyZonesTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
{
|
||||
}
|
||||
}
|
||||
[TestClass]
|
||||
public class HostModuleTests : UITestBase
|
||||
{
|
||||
public HostModuleTests()
|
||||
: base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Then you can start using session to perform the UI operations.
|
||||
- Then you can start performing the UI operations.
|
||||
|
||||
**Example**
|
||||
```
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UITests_KeyboardManager
|
||||
[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
|
||||
[TestCategory("Hosts File Editor #4")]
|
||||
public void TestEmptyView()
|
||||
{
|
||||
[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");
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// Maximize window
|
||||
var window = Session.Find<Window>(By.Name("Remap keys")).Maximize();
|
||||
// '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");
|
||||
|
||||
// Add Key Remapping
|
||||
this.Session.Find<Button>(By.Name("Add key remapping")).Click();
|
||||
window.Close();
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "EmptyView");
|
||||
|
||||
// Back to Settings
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
}
|
||||
// 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");
|
||||
}
|
||||
```
|
||||
|
||||
|
53
src/common/UITestAutomation/Element/CheckBox.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the CheckBox is checked.
|
||||
/// </summary>
|
||||
public bool IsChecked => this.Selected;
|
||||
|
||||
public CheckBox SetCheck(bool value = true, int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
if (this.IsChecked != value)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
Task.Delay(msPreAction).Wait();
|
||||
}
|
||||
|
||||
// Toggle the switch
|
||||
this.Click();
|
||||
if (msPostAction > 0)
|
||||
{
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
28
src/common/UITestAutomation/Element/ComboBox.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
74
src/common/UITestAutomation/Element/Custom.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Linq;
|
||||
using ABI.Windows.Foundation;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
@@ -25,8 +26,20 @@ 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()
|
||||
@@ -112,9 +125,12 @@ 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)
|
||||
/// <param name="msPreAction">Delay in milliseconds before performing the click action. Default is 500 ms.</param>
|
||||
/// <param name="msPostAction">Delay in milliseconds after performing the click action. Default is 500 ms.</param>
|
||||
public virtual void Click(bool rightClick = false, int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
PerformAction(
|
||||
(actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement);
|
||||
|
||||
@@ -131,7 +147,9 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
actions.Build().Perform();
|
||||
});
|
||||
},
|
||||
msPreAction,
|
||||
msPostAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,51 +170,20 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drag element move offset.
|
||||
/// Release action
|
||||
/// </summary>
|
||||
/// <param name="offsetX">The offsetX to move.</param>
|
||||
/// <param name="offsetY">The offsetY to move.</param>
|
||||
public void Drag(int offsetX, int offsetY)
|
||||
public void ReleaseAction()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY).Release();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
var releaseAction = new Actions(driver);
|
||||
releaseAction.Release().Perform();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drag element move to other element.
|
||||
/// Release key
|
||||
/// </summary>
|
||||
/// <param name="element">Move to this element.</param>
|
||||
public void Drag(Element element)
|
||||
public void ReleaseKey(Key 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);
|
||||
});
|
||||
KeyboardHelper.ReleaseKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -218,7 +205,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(By by, int timeoutMS = 3000)
|
||||
public T Find<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -226,7 +213,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
// leverage findAll to filter out mismatched elements
|
||||
var collection = this.FindAll<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
@@ -239,7 +226,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
public T Find<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
@@ -252,7 +239,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
public Element Find(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -264,7 +251,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
public Element Find(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
@@ -276,7 +263,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -308,7 +295,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
@@ -321,7 +308,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
@@ -333,11 +320,23 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@@ -360,5 +359,15 @@ namespace Microsoft.PowerToys.UITest
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save UI Element to a PNG file.
|
||||
/// </summary>
|
||||
/// <param name="path">the full path</param>
|
||||
internal void SaveToPngFile(string path)
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method SaveToPngFile with parameter: path = {path}");
|
||||
this.windowsElement.GetScreenshot().SaveAsFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
src/common/UITestAutomation/Element/Group.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,9 +24,12 @@ 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>
|
||||
public override void Click(bool rightClick = false)
|
||||
/// <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)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
PerformAction(
|
||||
(actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement, 10, 10);
|
||||
|
||||
@@ -40,7 +43,36 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
actions.Build().Perform();
|
||||
});
|
||||
},
|
||||
msPreAction,
|
||||
msPostAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Click the center of 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>
|
||||
|
101
src/common/UITestAutomation/Element/Pane.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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>
|
||||
/// 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>
|
||||
/// Simulates holding when dragging to target position.
|
||||
/// </summary>
|
||||
/// <param name="offsetX">The offsetX to move.</param>
|
||||
/// <param name="offsetY">The offsetY to move.</param>
|
||||
public void DragAndHold(int offsetX, int offsetY)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement).MoveByOffset(10, 10).ClickAndHold(windowElement).MoveByOffset(offsetX, offsetY);
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
public void ReleaseDrag()
|
||||
{
|
||||
var releaseAction = new Actions(this.Driver);
|
||||
releaseAction.Release().Perform();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
131
src/common/UITestAutomation/Element/Slider.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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}.");
|
||||
}
|
||||
}
|
||||
}
|
67
src/common/UITestAutomation/Element/Tab.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,8 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using OpenQA.Selenium;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
@@ -35,9 +33,10 @@ namespace Microsoft.PowerToys.UITest
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
// select all text and delete it
|
||||
windowElement.SendKeys(Keys.Control + "a");
|
||||
windowElement.SendKeys(Keys.Delete);
|
||||
windowElement.SendKeys(OpenQA.Selenium.Keys.Control + "a");
|
||||
windowElement.SendKeys(OpenQA.Selenium.Keys.Delete);
|
||||
});
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
PerformAction((actions, windowElement) =>
|
||||
|
36
src/common/UITestAutomation/Element/Thumb.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,7 +20,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<IReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
var items = findElementsFunc();
|
||||
var items = FindElementsWithRetry(findElementsFunc, timeoutMS);
|
||||
var res = items.Select(item =>
|
||||
{
|
||||
var element = item as WindowsElement;
|
||||
@@ -30,17 +30,30 @@ namespace Microsoft.PowerToys.UITest
|
||||
return new ReadOnlyCollection<T>(res);
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<ReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
private static ReadOnlyCollection<TW> FindElementsWithRetry<TW>(Func<IReadOnlyCollection<TW>> findElementsFunc, int timeoutMS)
|
||||
{
|
||||
var items = findElementsFunc();
|
||||
var res = items.Select(item =>
|
||||
{
|
||||
var element = item as WindowsElement;
|
||||
return NewElement<T>(element, driver, timeoutMS);
|
||||
}).Where(item => item.IsMatchingTarget()).ToList();
|
||||
var timeout = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
var retryIntervalMS = TimeSpan.FromMilliseconds(500);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
return new ReadOnlyCollection<T>(res);
|
||||
while (DateTime.Now - startTime < timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
var items = findElementsFunc();
|
||||
if (items.Count > 0)
|
||||
{
|
||||
return new ReadOnlyCollection<TW>((IList<TW>)items);
|
||||
}
|
||||
|
||||
Task.Delay(retryIntervalMS).Wait();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<TW>(new List<TW>());
|
||||
}
|
||||
|
||||
public static T NewElement<T>(WindowsElement? element, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
@@ -50,11 +63,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
Assert.IsNotNull(element, $"New Element {typeof(T).Name} error: element is null.");
|
||||
|
||||
T newElement = new T();
|
||||
if (timeoutMS > 0)
|
||||
{
|
||||
// Only set timeout if it is positive value
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(timeoutMS);
|
||||
}
|
||||
|
||||
newElement.SetSession(driver);
|
||||
newElement.SetWindowsElement(element);
|
||||
|
475
src/common/UITestAutomation/KeyboardHelper.cs
Normal file
@@ -0,0 +1,475 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,6 +29,49 @@ 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
|
||||
@@ -52,6 +95,8 @@ 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.
|
||||
@@ -60,6 +105,8 @@ 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",
|
||||
};
|
||||
}
|
||||
|
||||
|
37
src/common/UITestAutomation/MonitorInfoData.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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; }
|
||||
}
|
||||
}
|
||||
}
|
217
src/common/UITestAutomation/MouseHelper.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
133
src/common/UITestAutomation/ScreenCapture.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for capturing the screen with the mouse cursor.
|
||||
/// </summary>
|
||||
internal static class ScreenCapture
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetDC(IntPtr hWnd);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetCursorInfo(out CURSORINFO pci);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool DrawIconEx(IntPtr hdc, int x, int y, IntPtr hIcon, int cx, int cy, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a point with X and Y coordinates.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the cursor.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CURSORINFO
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the structure.
|
||||
/// </summary>
|
||||
public int CbSize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cursor state.
|
||||
/// </summary>
|
||||
public int Flags;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handle to the cursor.
|
||||
/// </summary>
|
||||
public IntPtr HCursor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the screen position of the cursor.
|
||||
/// </summary>
|
||||
public POINT PTScreenPos;
|
||||
}
|
||||
|
||||
private const int CURSORSHOWING = 0x00000001;
|
||||
private const int DESKTOPHORZRES = 118;
|
||||
private const int DESKTOPVERTRES = 117;
|
||||
private const int DINORMAL = 0x0003;
|
||||
|
||||
/// <summary>
|
||||
/// Captures the screen with the mouse cursor and saves it to the specified file path.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The file path to save the captured image.</param>
|
||||
private static void CaptureScreenWithMouse(string filePath)
|
||||
{
|
||||
IntPtr hdc = GetDC(IntPtr.Zero);
|
||||
int screenWidth = GetDeviceCaps(hdc, DESKTOPHORZRES);
|
||||
int screenHeight = GetDeviceCaps(hdc, DESKTOPVERTRES);
|
||||
ReleaseDC(IntPtr.Zero, hdc);
|
||||
|
||||
Rectangle bounds = new Rectangle(0, 0, screenWidth, screenHeight);
|
||||
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
|
||||
{
|
||||
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
|
||||
{
|
||||
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
|
||||
|
||||
CURSORINFO cursorInfo;
|
||||
cursorInfo.CbSize = Marshal.SizeOf<CURSORINFO>();
|
||||
if (GetCursorInfo(out cursorInfo) && cursorInfo.Flags == CURSORSHOWING)
|
||||
{
|
||||
using (System.Drawing.Graphics gIcon = System.Drawing.Graphics.FromImage(bitmap))
|
||||
{
|
||||
IntPtr hdcDest = gIcon.GetHdc();
|
||||
DrawIconEx(hdcDest, cursorInfo.PTScreenPos.X, cursorInfo.PTScreenPos.Y, cursorInfo.HCursor, 0, 0, 0, IntPtr.Zero, DINORMAL);
|
||||
gIcon.ReleaseHdc(hdcDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.Save(filePath, ImageFormat.Png);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a screenshot and saves it to the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to save the screenshot.</param>
|
||||
private static void CaptureScreenshot(string directory)
|
||||
{
|
||||
string filePath = Path.Combine(directory, $"screenshot_{DateTime.Now:yyyyMMdd_HHmmssfff}.png");
|
||||
CaptureScreenWithMouse(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timer callback method to capture a screenshot.
|
||||
/// </summary>
|
||||
/// <param name="state">The state object passed to the callback method.</param>
|
||||
public static void TimerCallback(object? state)
|
||||
{
|
||||
string directory = (string)state!;
|
||||
CaptureScreenshot(directory);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,14 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using static Microsoft.PowerToys.UITest.WindowHelper;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
@@ -17,35 +20,70 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public class Session
|
||||
{
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
public WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
private WindowsDriver<WindowsElement> WindowsDriver { get; set; }
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
private List<IntPtr> windowHandlers = new List<IntPtr>();
|
||||
|
||||
public Session(WindowsDriver<WindowsElement> root, WindowsDriver<WindowsElement> windowsDriver)
|
||||
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> pRoot, WindowsDriver<WindowsElement> pDriver, PowerToysModule scope, WindowSize size)
|
||||
{
|
||||
this.Root = root;
|
||||
this.WindowsDriver = windowsDriver;
|
||||
this.MainWindowHandler = IntPtr.Zero;
|
||||
this.Root = pRoot;
|
||||
this.WindowsDriver = pDriver;
|
||||
this.InitScope = scope;
|
||||
|
||||
if (size != WindowSize.UnSpecified)
|
||||
{
|
||||
// Attach to the scope & reset MainWindowHandler
|
||||
this.Attach(scope, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by selector.
|
||||
/// Cleans up the Session Exe.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
windowHandlers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(By by, int timeoutMS = 3000)
|
||||
public T Find<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
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);
|
||||
var collection = this.FindAll<T>(by, timeoutMS, global);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
Assert.IsTrue(collection.Count > 0, $"UI-Element({typeof(T).Name}) not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
@@ -55,62 +93,159 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
public T Find<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
return this.Find<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
public Element Find(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
return this.Find<Element>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
public Element Find(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
return this.Find<Element>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Has only one Element or its derived class by selector.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>True if only has one element, otherwise false.</returns>
|
||||
public bool HasOne<T>(By by, int timeoutMS = 5000, 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
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}");
|
||||
var foundElements = FindHelper.FindAll<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
if (by.GetIsAccessibilityId())
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElementsByAccessibilityId(by.GetAccessibilityId());
|
||||
var elements = driver.FindElementsByAccessibilityId(by.GetAccessibilityId());
|
||||
return elements;
|
||||
}
|
||||
else
|
||||
{
|
||||
var elements = this.WindowsDriver.FindElements(by.ToSeleniumBy());
|
||||
var elements = driver.FindElements(by.ToSeleniumBy());
|
||||
return elements;
|
||||
}
|
||||
},
|
||||
this.WindowsDriver,
|
||||
driver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements ?? new ReadOnlyCollection<T>([]);
|
||||
@@ -122,12 +257,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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,11 +270,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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
return this.FindAll<Element>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,55 +282,186 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard Action key.
|
||||
/// Close the main window.
|
||||
/// </summary>
|
||||
/// <param name="key1">The Keys1 to click.</param>
|
||||
/// <param name="key2">The Keys2 to click.</param>
|
||||
/// <param name="key3">The Keys3 to click.</param>
|
||||
/// <param name="key4">The Keys4 to click.</param>
|
||||
public void KeyboardAction(string key1, string key2 = "", string key3 = "", string key4 = "")
|
||||
public void CloseMainWindow()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
if (MainWindow != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key2))
|
||||
{
|
||||
actions.SendKeys(key1);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(key3))
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2);
|
||||
}
|
||||
else if (string.IsNullOrEmpty(key4))
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3);
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.SendKeys(key1).SendKeys(key2).SendKeys(key3).SendKeys(key4);
|
||||
}
|
||||
MainWindow.Close();
|
||||
MainWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
actions.Release();
|
||||
actions.Build().Perform();
|
||||
/// <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)
|
||||
{
|
||||
KeyboardHelper.SendKeys(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current position of the mouse cursor as a tuple.
|
||||
/// </summary>
|
||||
/// <returns>A tuple containing the X and Y coordinates of the cursor.</returns>
|
||||
public Tuple<int, int> GetMousePosition()
|
||||
{
|
||||
return MouseHelper.GetMousePosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the mouse cursor to the specified screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The new x-coordinate of the cursor.</param>
|
||||
/// <param name="y">The new y-coordinate of the cursor.</param
|
||||
public void MoveMouseTo(int x, int y, 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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
},
|
||||
msPreAction,
|
||||
msPostAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches to an existing PowerToys module.
|
||||
/// </summary>
|
||||
/// <param name="module">The PowerToys module to attach to.</param>
|
||||
/// <param name="size">The window size to set. Default is no change to window size</param>
|
||||
/// <returns>The attached session.</returns>
|
||||
public Session Attach(PowerToysModule module)
|
||||
public Session Attach(PowerToysModule module, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
string windowName = ModuleConfigData.Instance.GetModuleWindowName(module);
|
||||
return this.Attach(windowName);
|
||||
return this.Attach(windowName, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -203,35 +469,148 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// The session should be attached when a new app is started.
|
||||
/// </summary>
|
||||
/// <param name="windowName">The window name to attach to.</param>
|
||||
/// <param name="size">The window size to set. Default is no change to window size</param>
|
||||
/// <returns>The attached session.</returns>
|
||||
public Session Attach(string windowName)
|
||||
public Session Attach(string windowName, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
this.IsElevated = null;
|
||||
this.MainWindowHandler = IntPtr.Zero;
|
||||
|
||||
if (this.Root != null)
|
||||
{
|
||||
var window = this.Root.FindElementByName(windowName);
|
||||
Assert.IsNotNull(window, $"Failed to attach. Window '{windowName}' not found");
|
||||
// search window handler by window title (admin and non-admin titles)
|
||||
var timeout = TimeSpan.FromMinutes(2);
|
||||
var retryInterval = TimeSpan.FromSeconds(5);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
List<(IntPtr HWnd, string Title)>? matchingWindows = null;
|
||||
|
||||
while (DateTime.Now - startTime < timeout)
|
||||
{
|
||||
matchingWindows = WindowHelper.ApiHelper.FindDesktopWindowHandler(
|
||||
new[] { windowName, WindowHelper.AdministratorPrefix + windowName });
|
||||
|
||||
if (matchingWindows.Count > 0 && matchingWindows[0].HWnd != IntPtr.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Task.Delay(retryInterval).Wait();
|
||||
}
|
||||
|
||||
if (matchingWindows == null || matchingWindows.Count == 0 || matchingWindows[0].HWnd == IntPtr.Zero)
|
||||
{
|
||||
Assert.Fail($"Failed to attach. Window '{windowName}' not found after {timeout.TotalSeconds} seconds.");
|
||||
}
|
||||
|
||||
// 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 windowHandle = new nint(int.Parse(window.GetAttribute("NativeWindowHandle")));
|
||||
SetForegroundWindow(windowHandle);
|
||||
var hexWindowHandle = windowHandle.ToString("x");
|
||||
var appCapabilities = new AppiumOptions();
|
||||
|
||||
appCapabilities.AddAdditionalCapability("appTopLevelWindow", hexWindowHandle);
|
||||
appCapabilities.AddAdditionalCapability("deviceName", "WindowsPC");
|
||||
this.WindowsDriver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), appCapabilities);
|
||||
Assert.IsNotNull(this.WindowsDriver, "Attach WindowsDriver is null");
|
||||
|
||||
// Set implicit timeout to make element search retry every 500 ms
|
||||
this.WindowsDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
|
||||
this.windowHandlers.Add(this.MainWindowHandler);
|
||||
|
||||
if (size != WindowSize.UnSpecified)
|
||||
{
|
||||
WindowHelper.SetWindowSize(this.MainWindowHandler, size);
|
||||
}
|
||||
|
||||
// Set MainWindow
|
||||
MainWindow = Find<Window>(matchingWindows[0].Title);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNotNull(this.Root, $"Failed to attach to the window '{windowName}'. Root driver is null");
|
||||
}
|
||||
|
||||
Task.Delay(3000).Wait();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main window center coordinates.
|
||||
/// </summary>
|
||||
/// <returns>(int Left, int Top, int Right, int Bottom)</returns>
|
||||
public (int Left, int Top, int Right, int Bottom) GetMainWindowRect()
|
||||
{
|
||||
return WindowHelper.GetWindowRect(this.MainWindowHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches the specified executable with optional arguments and simulates a delay before and after execution.
|
||||
/// </summary>
|
||||
/// <param name="executablePath">The full path to the executable to launch.</param>
|
||||
/// <param name="arguments">Optional command-line arguments to pass to the executable.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before launching the executable. Default is 0 ms.</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after launching the executable. Default is 2000 ms.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminates all running processes that match the specified process name.
|
||||
/// Waits for each process to exit after sending the kill signal.
|
||||
/// </summary>
|
||||
/// <param name="processName">The name of the process to terminate (without extension, e.g., "notepad").</param>
|
||||
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>
|
||||
@@ -254,5 +633,26 @@ namespace Microsoft.PowerToys.UITest
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 500 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 500 ms</param>
|
||||
protected void PerformAction(Action action, int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
Task.Delay(msPreAction).Wait();
|
||||
}
|
||||
|
||||
action();
|
||||
|
||||
if (msPostAction > 0)
|
||||
{
|
||||
Task.Delay(msPostAction).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,39 +15,58 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
internal class SessionHelper
|
||||
public class SessionHelper
|
||||
{
|
||||
// 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; }
|
||||
private static WindowsDriver<WindowsElement>? root;
|
||||
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
private Process? appDriver;
|
||||
private static 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);
|
||||
|
||||
var winAppDriverProcessInfo = new ProcessStartInfo
|
||||
CheckWinAppDriverAndRoot();
|
||||
|
||||
var runnerProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
FileName = locationPath + this.runnerPath,
|
||||
Verb = "runas",
|
||||
};
|
||||
|
||||
this.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
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>
|
||||
/// Initializes WinAppDriver And Root.
|
||||
/// </summary>
|
||||
public void CheckWinAppDriverAndRoot()
|
||||
{
|
||||
if (SessionHelper.root == null || SessionHelper.appDriver?.SessionId == null || SessionHelper.appDriver == null || SessionHelper.appDriver.HasExited)
|
||||
{
|
||||
this.StartWindowsAppDriverApp();
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
SessionHelper.root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +75,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
public SessionHelper Init()
|
||||
{
|
||||
this.StartExe(locationPath + this.sessionPath);
|
||||
this.ExitExe(this.locationPath + this.sessionPath);
|
||||
this.StartExe(this.locationPath + this.sessionPath);
|
||||
|
||||
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
|
||||
|
||||
@@ -71,8 +91,11 @@ namespace Microsoft.PowerToys.UITest
|
||||
ExitScopeExe();
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -81,31 +104,15 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
Console.WriteLine($"appPath: {appPath}");
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a exe.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the application executable.</param>
|
||||
public void ExitExe(string path)
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void ExitExe(string appPath)
|
||||
{
|
||||
// Exit Exe
|
||||
string exeName = Path.GetFileNameWithoutExtension(path);
|
||||
string exeName = Path.GetFileNameWithoutExtension(appPath);
|
||||
|
||||
// PowerToys.FancyZonesEditor
|
||||
Process[] processes = Process.GetProcessesByName(exeName);
|
||||
foreach (Process process in processes)
|
||||
{
|
||||
@@ -121,6 +128,48 @@ 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);
|
||||
this.Driver = NewWindowsDriver(opts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="info">The path to the application executable.</param>
|
||||
private WindowsDriver<WindowsElement> NewWindowsDriver(AppiumOptions info)
|
||||
{
|
||||
// Create driver with retry
|
||||
var timeout = TimeSpan.FromMinutes(2);
|
||||
var retryInterval = TimeSpan.FromSeconds(5);
|
||||
DateTime startTime = DateTime.Now;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), info);
|
||||
return res;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (DateTime.Now - startTime > timeout)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Task.Delay(retryInterval).Wait();
|
||||
CheckWinAppDriverAndRoot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit now exe.
|
||||
/// </summary>
|
||||
@@ -134,16 +183,31 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
ExitExe(sessionPath);
|
||||
ExitScopeExe();
|
||||
StartExe(locationPath + sessionPath);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
public WindowsDriver<WindowsElement> GetRoot()
|
||||
{
|
||||
return SessionHelper.root!;
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetDriver()
|
||||
{
|
||||
Assert.IsNotNull(this.Driver, $"Failed to get driver. Driver is null.");
|
||||
return this.Driver;
|
||||
}
|
||||
|
||||
private void StartWindowsAppDriverApp()
|
||||
{
|
||||
var winAppDriverProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
Verb = "runas",
|
||||
};
|
||||
|
||||
this.ExitExe(winAppDriverProcessInfo.FileName);
|
||||
SessionHelper.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<PublishTrimmed>false</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -6,10 +6,16 @@ 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
|
||||
{
|
||||
@@ -17,19 +23,37 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Base class that should be inherited by all Test Classes.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class UITestBase
|
||||
public class UITestBase : IDisposable
|
||||
{
|
||||
public Session Session { get; set; }
|
||||
public required TestContext TestContext { get; set; }
|
||||
|
||||
private readonly SessionHelper sessionHelper;
|
||||
public required Session Session { get; set; }
|
||||
|
||||
public bool IsInPipeline { get; }
|
||||
|
||||
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
|
||||
|
||||
private readonly PowerToysModule scope;
|
||||
private readonly WindowSize size;
|
||||
private SessionHelper? sessionHelper;
|
||||
private System.Threading.Timer? screenshotTimer;
|
||||
private string? screenshotDirectory;
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified)
|
||||
{
|
||||
this.IsInPipeline = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("platform"));
|
||||
Console.WriteLine($"Running tests on platform: {Environment.GetEnvironmentVariable("platform")}");
|
||||
if (IsInPipeline)
|
||||
{
|
||||
NativeMethods.ChangeDisplayResolution(1920, 1080);
|
||||
NativeMethods.GetMonitorInfo();
|
||||
|
||||
// Escape Popups before starting
|
||||
System.Windows.Forms.SendKeys.SendWait("{ESC}");
|
||||
}
|
||||
|
||||
this.scope = scope;
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -38,6 +62,22 @@ namespace Microsoft.PowerToys.UITest
|
||||
[TestInitialize]
|
||||
public void TestInit()
|
||||
{
|
||||
CloseOtherApplications();
|
||||
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
|
||||
@@ -50,12 +90,32 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UnInitializes the test.
|
||||
/// Cleanups the test.
|
||||
/// </summary>
|
||||
[TestCleanup]
|
||||
public void TestClean()
|
||||
public void TestCleanup()
|
||||
{
|
||||
this.sessionHelper.Cleanup();
|
||||
if (IsInPipeline)
|
||||
{
|
||||
screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
Dispose();
|
||||
if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed
|
||||
or UnitTestOutcome.Error
|
||||
or UnitTestOutcome.Unknown)
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
AddScreenShotsToTestResultsDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
this.Session.Cleanup();
|
||||
this.sessionHelper!.Cleanup();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
screenshotTimer?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,47 +124,143 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(By by, int timeoutMS = 3000)
|
||||
protected T Find<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
return this.Session.Find<T>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(string name, int timeoutMS = 3000)
|
||||
protected T Find<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS);
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(By by, int timeoutMS = 3000)
|
||||
protected Element Find(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.Find(by, timeoutMS);
|
||||
return this.Session.Find(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// Shortcut for this.Session.Find<Element>(name, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(string name, int timeoutMS = 3000)
|
||||
protected Element Find(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.Find(name, timeoutMS);
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,12 +269,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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(by, timeoutMS);
|
||||
return this.Session.FindAll<T>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -127,12 +283,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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 5000, bool global = false)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS);
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,11 +296,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 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.FindAll<Element>(by, timeoutMS);
|
||||
return this.Session.FindAll<Element>(by, timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,11 +308,137 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 5000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 5000, bool global = false)
|
||||
{
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS, global);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls the page
|
||||
/// </summary>
|
||||
/// <param name="scrollCount">The number of scroll attempts.</param>
|
||||
/// <param name="direction">The direction to scroll.</param>
|
||||
/// <param name="msPreAction">Pre-action delay in milliseconds.</param>
|
||||
/// <param name="msPostAction">Post-action delay in milliseconds.</param>
|
||||
public void Scroll(int scrollCount = 5, string direction = "Up", int msPreAction = 500, int msPostAction = 500)
|
||||
{
|
||||
MouseActionType mouseAction = direction == "Up" ? MouseActionType.ScrollUp : MouseActionType.ScrollDown;
|
||||
for (int i = 0; i < scrollCount; i++)
|
||||
{
|
||||
Session.PerformMouseAction(mouseAction, msPreAction, msPostAction); // Ensure settings are visible
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -164,8 +446,8 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void RestartScopeExe()
|
||||
{
|
||||
this.sessionHelper.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.sessionHelper!.RestartScopeExe();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,8 +456,194 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public void ExitScopeExe()
|
||||
{
|
||||
this.sessionHelper.ExitScopeExe();
|
||||
this.sessionHelper!.ExitScopeExe();
|
||||
return;
|
||||
}
|
||||
|
||||
private void CloseOtherApplications()
|
||||
{
|
||||
// Close other applications
|
||||
var processNamesToClose = new List<string>
|
||||
{
|
||||
"PowerToys",
|
||||
"PowerToys.Settings",
|
||||
"PowerToys.FancyZonesEditor",
|
||||
};
|
||||
foreach (var processName in processNamesToClose)
|
||||
{
|
||||
foreach (var process in Process.GetProcessesByName(processName))
|
||||
{
|
||||
process.Kill();
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
160
src/common/UITestAutomation/VisualAssert.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
// 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 limitation 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't 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;
|
||||
}
|
||||
}
|
||||
}
|
33
src/common/UITestAutomation/VisualHelper.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
331
src/common/UITestAutomation/WindowHelper.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
// 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 main window center coordinates.
|
||||
/// </summary>
|
||||
/// <returns>(x, y)</returns>
|
||||
public static (int Left, int Top, int Right, int Bottom) GetWindowRect(IntPtr windowHandler)
|
||||
{
|
||||
if (windowHandler == IntPtr.Zero)
|
||||
{
|
||||
return (0, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rect = ApiHelper.GetWindowRect(windowHandler);
|
||||
return (rect.Left, rect.Top, rect.Right, rect.Bottom);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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");
|
||||
}
|
||||
}
|
||||
|
||||
public static (int Left, int Top, int Right, int Bottom) GetWindowRect(IntPtr hWnd)
|
||||
{
|
||||
if (hWnd == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentException("Invalid window handle");
|
||||
}
|
||||
|
||||
if (GetWindowRect(hWnd, out RECT rect))
|
||||
{
|
||||
return (rect.Left, rect.Top, rect.Right, rect.Bottom);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.6 KiB |
@@ -13,7 +13,7 @@ namespace Hosts.UITests
|
||||
public class HostModuleTests : UITestBase
|
||||
{
|
||||
public HostModuleTests()
|
||||
: base(PowerToysModule.Hosts)
|
||||
: base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -31,14 +31,17 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.EmptyViewShouldWork")]
|
||||
[TestCategory("Hosts File Editor #4")]
|
||||
public void TestEmptyView()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
Assert.IsTrue(this.HasOne<HyperlinkButton>("Add an entry"), "'Add an entry' button should be visible in the empty view");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "EmptyView");
|
||||
|
||||
// Click 'Add an entry' from empty-view for adding Host override rule
|
||||
this.Find<HyperlinkButton>("Add an entry").Click();
|
||||
@@ -46,8 +49,10 @@ namespace Hosts.UITests
|
||||
this.AddEntry("192.168.0.1", "localhost", false, false);
|
||||
|
||||
// Should have one row now and not more empty view
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
|
||||
Assert.IsFalse(this.Has<HyperlinkButton>("Add an entry"), "'Add an entry' button should be invisible if not empty view");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"), "NonEmptyView");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -58,17 +63,20 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.AddEntryButtonShouldWork")]
|
||||
[TestCategory("Hosts File Editor #4")]
|
||||
public void TestAddingEntry()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0, "Should have no row after removing all");
|
||||
Assert.IsFalse(this.Has<Button>("Delete"), "Should have no row after removing all");
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.Has<Button>("Delete"), "Should have one row now");
|
||||
|
||||
VisualAssert.AreEqual(this.TestContext, this.Find("Entries"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +90,8 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.CanNotAddMoreThenNighHosts")]
|
||||
[TestCategory("Hosts File Editor #5")]
|
||||
public void TestTooManyHosts()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
@@ -116,18 +125,48 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.ErrorMessageShowupIfNotRunAsAdmin")]
|
||||
[TestCategory("Hosts File Editor #8")]
|
||||
public void TestErrorMessageWithNonAdminPermission()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
if (this.Session.IsElevated == false)
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(
|
||||
this.FindAll<TextBlock>("The hosts file cannot be saved because the program isn't running as administrator.").Count == 1,
|
||||
"Should display host-file saving error if not run as administrator");
|
||||
Assert.IsTrue(
|
||||
this.FindAll<TextBlock>("The hosts file cannot be saved because the program isn't running as administrator.").Count == 1,
|
||||
"Should display host-file saving error if not run as administrator");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test No Error-message in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating error message should be shown if not run as admin.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("Hosts.Basic.NoErrorMessageShowupIfRunAsAdmin")]
|
||||
[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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +183,8 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Basic.FiltersControlShouldWork")]
|
||||
[TestCategory("Hosts File Editor #6")]
|
||||
public void TestFilterControl()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
@@ -212,7 +252,7 @@ namespace Hosts.UITests
|
||||
|
||||
// Close-filter-panel
|
||||
this.Find<Button>("Filters").Click();
|
||||
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed afer click Filter Button");
|
||||
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed after clicking Filter Button");
|
||||
}
|
||||
|
||||
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)
|
||||
@@ -243,25 +283,25 @@ namespace Hosts.UITests
|
||||
private void CloseWarningDialog()
|
||||
{
|
||||
// Find 'Accept' button which come in 'Warning' dialog
|
||||
if (this.FindAll("Warning").Count > 0 &&
|
||||
this.FindAll<Button>("Accept").Count > 0)
|
||||
if (this.FindAll("Warning", 1000).Count > 0 &&
|
||||
this.FindAll<Button>("Accept", 1000).Count > 0)
|
||||
{
|
||||
// Hide Warning dialog if any
|
||||
this.Find<Button>("Accept").Click();
|
||||
this.Find<Button>("Accept", 1000).Click();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllEntries()
|
||||
{
|
||||
// Delete all existing host-override rules
|
||||
foreach (var deleteBtn in this.FindAll<Button>("Delete"))
|
||||
foreach (var deleteBtn in this.FindAll<Button>("Delete", 1000))
|
||||
{
|
||||
deleteBtn.Click();
|
||||
this.Find<Button>("Yes").Click();
|
||||
this.Find<Button>("Yes", 1000).Click();
|
||||
}
|
||||
|
||||
// Should have no row left, and no more delete button
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0);
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete", 1000).Count == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,17 @@
|
||||
<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" />
|
||||
|
@@ -12,6 +12,11 @@ namespace Hosts.UITests
|
||||
[TestClass]
|
||||
public class HostsSettingTests : UITestBase
|
||||
{
|
||||
public HostsSettingTests()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Medium)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Warning Dialog at startup
|
||||
/// <list type="bullet">
|
||||
@@ -29,7 +34,9 @@ namespace Hosts.UITests
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestMethod("Hosts.Settings.ShowWarningDialogIfRunAsAdmin")]
|
||||
[TestCategory("Hosts File Editor #1")]
|
||||
[TestCategory("Hosts File Editor #9")]
|
||||
public void TestWarningDialog()
|
||||
{
|
||||
this.LaunchFromSetting(showWarning: true);
|
||||
@@ -51,10 +58,7 @@ namespace Hosts.UITests
|
||||
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
// wait for 500 ms to make sure Hosts File Editor is launched
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
this.Session.Attach(PowerToysModule.Hosts);
|
||||
this.Session.Attach(PowerToysModule.Hosts, WindowSize.Small_Vertical);
|
||||
|
||||
// Should show warning dialog
|
||||
Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
|
||||
@@ -68,7 +72,7 @@ namespace Hosts.UITests
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed after click Accept button in Warning Dialog");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
this.Session.CloseMainWindow();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
@@ -82,7 +86,7 @@ namespace Hosts.UITests
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
this.Session.CloseMainWindow();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
@@ -90,14 +94,9 @@ namespace Hosts.UITests
|
||||
|
||||
private bool IsHostsFileEditorClosed()
|
||||
{
|
||||
try
|
||||
if (this.Session.FindAll<Window>("Hosts File Editor").Count == 0 && this.Session.FindAll<Window>("Administrator: Hosts File Editor").Count == 0)
|
||||
{
|
||||
this.Session.FindAll<Window>("Hosts File Editor");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Validate if editor window closed by checking exception.Message
|
||||
return ex.Message.Contains("Currently selected window has been closed");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -121,7 +120,7 @@ namespace Hosts.UITests
|
||||
// launch Hosts File Editor
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
Task.Delay(500).Wait();
|
||||
Task.Delay(2000).Wait();
|
||||
|
||||
this.Session.Attach(PowerToysModule.Hosts);
|
||||
}
|
||||
|
640
src/modules/MouseUtils/MouseUtils.UITests/FindMyMouseTests.cs
Normal file
@@ -0,0 +1,640 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,498 @@
|
||||
// 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;
|
||||
Task.Delay(100).Wait();
|
||||
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);
|
||||
|
||||
var colorBackground = this.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 = this.GetPixelColorString(location.Item1, location.Item2);
|
||||
Assert.AreEqual(expectedColor, colorLeftClick);
|
||||
|
||||
colorLeftClick2 = this.GetPixelColorString(location.Item1 + radius - 1, location.Item2);
|
||||
|
||||
Assert.AreEqual(expectedColor, colorLeftClick2);
|
||||
|
||||
colorBackground = this.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 = this.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();
|
||||
}
|
||||
}
|
||||
}
|
250
src/modules/MouseUtils/MouseUtils.UITests/MouseJumpTests.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Mouse utilities").Count == 0)
|
||||
{
|
||||
RestartScopeExe();
|
||||
Session.SetMainWindowSize(WindowSize.Large);
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,404 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
<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>
|
@@ -0,0 +1,64 @@
|
||||
## [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 shortcut 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.
|
@@ -0,0 +1,58 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
291
src/modules/MouseUtils/MouseUtils.UITests/util/IOUtil.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
// 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;
|
||||
|
||||
// The MouseUtils module relies on simulating system-level input events (such as mouse movement or key presses) to test visual or behavioral responses.
|
||||
// The UI Test framework provides built-in methods for simulating mouse movement and clicks, which work for MouseUtils reliably on high-performance dev boxes.
|
||||
// However, on low-performance environments such as CI/CD pipelines or virtual machines, these simulated input events are not always correctly recognized by the operating system.
|
||||
// IOUtils class is added specifically for MouseUtils tests.
|
||||
// For any test scenario that involves simulating continuous mouse movement (e.g., detecting crosshair changes while moving the cursor),
|
||||
// input simulation methods in IOUtils class should be used.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
// 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
// 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");
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<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>
|
@@ -23,6 +23,7 @@
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const wchar_t ToolWindowClassName[] = L"FancyZones_ZonesOverlay";
|
||||
const wchar_t ToolWindowName[] = L"FancyZones_ZonesOverlay";
|
||||
}
|
||||
|
||||
using namespace FancyZonesUtils;
|
||||
@@ -59,7 +60,7 @@ namespace
|
||||
HWND windowFromPool = ExtractWindow();
|
||||
if (windowFromPool == NULL)
|
||||
{
|
||||
HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner);
|
||||
HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, NonLocalizable::ToolWindowName, WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner);
|
||||
Logger::info("Creating new ZonesOverlay window, hWnd = {}", (void*)window);
|
||||
FancyZonesWindowUtils::MakeWindowTransparent(window);
|
||||
|
||||
|
673
src/modules/fancyzones/UITests-FancyZones/DragWindowTests.cs
Normal file
@@ -0,0 +1,673 @@
|
||||
// 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.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZones.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualBasic.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
|
||||
namespace UITests_FancyZones
|
||||
{
|
||||
[TestClass]
|
||||
public class DragWindowTests : UITestBase
|
||||
{
|
||||
private static readonly IOTestHelper AppZoneHistory = new FancyZonesEditorFiles().AppZoneHistoryIOHelper;
|
||||
private static string nonPrimaryMouseButton = "Right";
|
||||
|
||||
private static string highlightColor = "#008CFF"; // set highlight color
|
||||
private static string inactivateColor = "#AACDFF"; // set inactivate zone color
|
||||
|
||||
// set screen margin
|
||||
private static int screenMarginTop;
|
||||
private static int screenMarginLeft;
|
||||
private static int screenMarginRight;
|
||||
private static int screenMarginBottom;
|
||||
|
||||
// set 1/4 margin
|
||||
private static int quarterX;
|
||||
private static int quarterY;
|
||||
|
||||
private static string powertoysWindowName = "PowerToys Settings"; // set powertoys settings window name
|
||||
|
||||
public DragWindowTests()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Medium)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
// ClearOpenWindows
|
||||
ClearOpenWindows();
|
||||
|
||||
// kill all processes related to FancyZones Editor to ensure a clean state
|
||||
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
|
||||
|
||||
AppZoneHistory.DeleteFile();
|
||||
this.RestartScopeExe();
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
|
||||
// Set a custom layout with 1 subzones and clear app zone history
|
||||
SetupCustomLayouts();
|
||||
|
||||
// Get the current mouse button setting
|
||||
nonPrimaryMouseButton = SystemInformation.MouseButtonsSwapped ? "Left" : "Right";
|
||||
|
||||
// get PowerToys window Name
|
||||
powertoysWindowName = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
|
||||
// Ensure FancyZones settings page is visible and enable FancyZones
|
||||
LaunchFancyZones();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that holding Shift while dragging shows all zones as expected.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")]
|
||||
[TestCategory("FancyZones_Dragging #1")]
|
||||
public void TestShowZonesOnShiftDuringDrag()
|
||||
{
|
||||
string testCaseName = nameof(TestShowZonesOnShiftDuringDrag);
|
||||
Pane dragElement = Find<Pane>(By.Name("Non Client Input Sink Window")); // element to drag
|
||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
||||
|
||||
var (initialColor, withShiftColor) = RunDragInteractions(
|
||||
preAction: () =>
|
||||
{
|
||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
||||
},
|
||||
postAction: () =>
|
||||
{
|
||||
Session.PressKey(Key.Shift);
|
||||
Task.Delay(500).Wait();
|
||||
},
|
||||
releaseAction: () =>
|
||||
{
|
||||
Session.ReleaseKey(Key.Shift);
|
||||
Task.Delay(5000).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
},
|
||||
testCaseName: testCaseName);
|
||||
|
||||
string zoneColorWithoutShift = GetOutWindowPixelColor(30);
|
||||
|
||||
Assert.AreNotEqual(initialColor, withShiftColor, $"[{testCaseName}] Zone display failed.");
|
||||
Assert.IsTrue(
|
||||
withShiftColor == inactivateColor || withShiftColor == highlightColor,
|
||||
$"[{testCaseName}] Zone display failed: withShiftColor was {withShiftColor}, expected {inactivateColor} or {highlightColor}.");
|
||||
Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] Zone display failed.");
|
||||
|
||||
Assert.AreEqual(zoneColorWithoutShift, initialColor, $"[{testCaseName}] Zone deactivated failed.");
|
||||
dragElement.ReleaseDrag();
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that dragging activates zones as expected.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")]
|
||||
[TestCategory("FancyZones_Dragging #2")]
|
||||
public void TestShowZonesOnDragDuringShift()
|
||||
{
|
||||
string testCaseName = nameof(TestShowZonesOnDragDuringShift);
|
||||
|
||||
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
||||
|
||||
var (initialColor, withDragColor) = RunDragInteractions(
|
||||
preAction: () =>
|
||||
{
|
||||
dragElement.Drag(offSet.Dx, offSet.Dy);
|
||||
Session.PressKey(Key.Shift);
|
||||
},
|
||||
postAction: () =>
|
||||
{
|
||||
dragElement.DragAndHold(0, 0);
|
||||
Task.Delay(5000).Wait();
|
||||
},
|
||||
releaseAction: () =>
|
||||
{
|
||||
dragElement.ReleaseDrag();
|
||||
Session.ReleaseKey(Key.Shift);
|
||||
},
|
||||
testCaseName: testCaseName);
|
||||
|
||||
Assert.AreNotEqual(initialColor, withDragColor, $"[{testCaseName}] Zone color did not change; zone activation failed.");
|
||||
Assert.AreEqual(highlightColor, withDragColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed.");
|
||||
|
||||
// double check by app-zone-history.json
|
||||
string appZoneHistoryJson = AppZoneHistory.GetData();
|
||||
string? zoneNumber = ZoneSwitchHelper.GetZoneIndexSetByAppName(powertoysWindowName, appZoneHistoryJson);
|
||||
Assert.IsNull(zoneNumber, $"[{testCaseName}] AppZoneHistory layout was unexpectedly set.");
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test toggling zones using a non-primary mouse click during window dragging.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that clicking a non-primary mouse button deactivates zones while dragging a window.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestToggleZonesWithNonPrimaryMouseClick")]
|
||||
[TestCategory("FancyZones_Dragging #3")]
|
||||
public void TestToggleZonesWithNonPrimaryMouseClick()
|
||||
{
|
||||
string testCaseName = nameof(TestToggleZonesWithNonPrimaryMouseClick);
|
||||
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
||||
|
||||
var (initialColor, withMouseColor) = RunDragInteractions(
|
||||
preAction: () =>
|
||||
{
|
||||
// activate zone
|
||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
||||
},
|
||||
postAction: () =>
|
||||
{
|
||||
// press non-primary mouse button to toggle zones
|
||||
Session.PerformMouseAction(
|
||||
nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick);
|
||||
},
|
||||
releaseAction: () =>
|
||||
{
|
||||
dragElement.ReleaseDrag();
|
||||
},
|
||||
testCaseName: testCaseName);
|
||||
|
||||
// check the zone color is deactivated
|
||||
Assert.AreNotEqual(highlightColor, withMouseColor, $"[{testCaseName}] Zone deactivation failed.");
|
||||
|
||||
// check the zone color is activated
|
||||
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test both use Shift and non primary mouse off settings.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that pressing the Shift key deactivates zones during a window drag-and-hold action.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestShowZonesWhenShiftAndMouseOff")]
|
||||
[TestCategory("FancyZones_Dragging #4")]
|
||||
public void TestShowZonesWhenShiftAndMouseOff()
|
||||
{
|
||||
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOff);
|
||||
Pane dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
||||
|
||||
var (initialColor, withShiftColor) = RunDragInteractions(
|
||||
preAction: () =>
|
||||
{
|
||||
// activate zone
|
||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
||||
},
|
||||
postAction: () =>
|
||||
{
|
||||
// press Shift Key to deactivate zones
|
||||
Session.PressKey(Key.Shift);
|
||||
Task.Delay(500).Wait();
|
||||
},
|
||||
releaseAction: () =>
|
||||
{
|
||||
dragElement.ReleaseDrag();
|
||||
Session.ReleaseKey(Key.Shift);
|
||||
},
|
||||
testCaseName: testCaseName);
|
||||
|
||||
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
|
||||
Assert.AreNotEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone deactivation failed.");
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test zone visibility when both Shift key and mouse settings are involved.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that zones are activated when Shift is pressed during drag, and deactivated by a non-primary mouse click.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestShowZonesWhenShiftAndMouseOn")]
|
||||
[TestCategory("FancyZones_Dragging #5")]
|
||||
public void TestShowZonesWhenShiftAndMouseOn()
|
||||
{
|
||||
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOn);
|
||||
|
||||
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
||||
var (initialColor, withShiftColor) = RunDragInteractions(
|
||||
preAction: () =>
|
||||
{
|
||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
||||
},
|
||||
postAction: () =>
|
||||
{
|
||||
Session.PressKey(Key.Shift);
|
||||
},
|
||||
releaseAction: () =>
|
||||
{
|
||||
},
|
||||
testCaseName: testCaseName);
|
||||
|
||||
Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] show zone failed.");
|
||||
|
||||
Session.PerformMouseAction(
|
||||
nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick);
|
||||
|
||||
string zoneColorWithMouse = GetOutWindowPixelColor(30);
|
||||
Assert.AreEqual(initialColor, zoneColorWithMouse, $"[{nameof(TestShowZonesWhenShiftAndMouseOff)}] Zone deactivate failed.");
|
||||
|
||||
Session.ReleaseKey(Key.Shift);
|
||||
dragElement.ReleaseDrag();
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that a window becomes transparent during dragging when the transparent window setting is enabled.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that the window appears transparent while being dragged.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestMakeDraggedWindowTransparentOn")]
|
||||
[TestCategory("FancyZones_Dragging #8")]
|
||||
public void TestMakeDraggedWindowTransparentOn()
|
||||
{
|
||||
var pixel = GetPixelWhenMakeDraggedWindow();
|
||||
Assert.AreNotEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOn)}] Window transparency failed.");
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that a window remains opaque during dragging when the transparent window setting is disabled.
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that the window is not transparent while being dragged.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestMakeDraggedWindowTransparentOff")]
|
||||
[TestCategory("FancyZones_Dragging #8")]
|
||||
public void TestMakeDraggedWindowTransparentOff()
|
||||
{
|
||||
var pixel = GetPixelWhenMakeDraggedWindow();
|
||||
Assert.AreEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOff)}] Window without transparency failed.");
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
private void Clean()
|
||||
{
|
||||
// clean app zone history file
|
||||
AppZoneHistory.DeleteFile();
|
||||
}
|
||||
|
||||
// Helper method to ensure the desktop has no open windows by clicking the "Show Desktop" button
|
||||
private void ClearOpenWindows()
|
||||
{
|
||||
string desktopButtonName;
|
||||
|
||||
// Check for both possible button names (Win10/Win11)
|
||||
if (this.FindAll<Microsoft.PowerToys.UITest.Button>("Show Desktop", 5000, true).Count == 0)
|
||||
{
|
||||
// win10
|
||||
desktopButtonName = "Show desktop";
|
||||
}
|
||||
else
|
||||
{
|
||||
// win11
|
||||
desktopButtonName = "Show Desktop";
|
||||
}
|
||||
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>(By.Name(desktopButtonName), 5000, true).Click(false, 500, 2000);
|
||||
}
|
||||
|
||||
// Setup custom layout with 1 subzones
|
||||
private void SetupCustomLayouts()
|
||||
{
|
||||
var customLayouts = new CustomLayouts();
|
||||
var customLayoutListWrapper = CustomLayoutsList;
|
||||
|
||||
if (TestContext.TestName == "TestMakeDraggedWindowTransparentOff")
|
||||
{
|
||||
customLayoutListWrapper = CustomLayoutsListWithTwo;
|
||||
}
|
||||
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
}
|
||||
|
||||
// launch FancyZones settings page
|
||||
private void LaunchFancyZones()
|
||||
{
|
||||
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
|
||||
{
|
||||
this.Find<NavigationViewItem>("Windowing & Layouts").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
|
||||
|
||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||
Find<Element>(By.AccessibilityId("HeaderPresenter")).Click();
|
||||
this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible
|
||||
ZoneBehaviourSettings(TestContext.TestName);
|
||||
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 500, 10000);
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
|
||||
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
|
||||
// Console.WriteLine($"after launch, Custom layout data: {customLayoutData}");
|
||||
try
|
||||
{
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Maximize").Click();
|
||||
|
||||
// Set the FancyZones layout to a custom layout
|
||||
this.Find<Element>(By.Name("Custom Column")).Click();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Console.WriteLine($"[Exception] Failed to attach to FancyZones window. Retrying...{ex.Message}");
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
SetupCustomLayouts();
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Maximize").Click();
|
||||
|
||||
// customLayoutData = FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.GetData();
|
||||
// Console.WriteLine($"after retry, Custom layout data: {customLayoutData}");
|
||||
|
||||
// Set the FancyZones layout to a custom layout
|
||||
this.Find<Element>(By.Name("Custom Column")).Click();
|
||||
}
|
||||
|
||||
// Get screen margins for positioning checks
|
||||
GetScreenMargins();
|
||||
|
||||
// Close layout editor window
|
||||
SendKeys(Key.Alt, Key.F4);
|
||||
|
||||
// make window small to detect zone easily
|
||||
Session.Attach(powertoysWindowName, WindowSize.Small);
|
||||
}
|
||||
|
||||
// Get the screen margins to calculate the dragged window position
|
||||
private void GetScreenMargins()
|
||||
{
|
||||
var rect = Session.GetMainWindowRect();
|
||||
screenMarginTop = rect.Top;
|
||||
screenMarginLeft = rect.Left;
|
||||
screenMarginRight = rect.Right;
|
||||
screenMarginBottom = rect.Bottom;
|
||||
(quarterX, quarterY) = ZoneSwitchHelper.GetScreenMargins(rect, 4);
|
||||
}
|
||||
|
||||
// Get the mouse color of the pixel when make dragged window
|
||||
private (string PixelInWindow, string TransPixel) GetPixelWhenMakeDraggedWindow()
|
||||
{
|
||||
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
|
||||
// maximize the window to make sure get pixel color more accurate
|
||||
dragElement.DoubleClick();
|
||||
|
||||
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
|
||||
Session.PressKey(Key.Shift);
|
||||
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
|
||||
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position
|
||||
Tuple<int, int> pos = GetMousePosition();
|
||||
string pixelInWindow = this.GetPixelColorString(pos.Item1, pos.Item2);
|
||||
Session.ReleaseKey(Key.Shift);
|
||||
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position
|
||||
string transPixel = this.GetPixelColorString(pos.Item1, pos.Item2);
|
||||
dragElement.ReleaseDrag();
|
||||
|
||||
return (pixelInWindow, transPixel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color of a pixel located just outside the application's window.
|
||||
/// </summary>
|
||||
/// <param name="spacing">
|
||||
/// The minimum spacing (in pixels) required between the window edge and screen margin
|
||||
/// to determine a safe pixel sampling area outside the window.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A string representing the color of the pixel at the computed location outside the window,
|
||||
/// </returns>
|
||||
private string GetOutWindowPixelColor(int spacing)
|
||||
{
|
||||
var rect = Session.GetMainWindowRect();
|
||||
int checkX, checkY;
|
||||
|
||||
if ((rect.Top - screenMarginTop) >= spacing)
|
||||
{
|
||||
checkX = rect.Left;
|
||||
checkY = screenMarginTop + (spacing / 2);
|
||||
}
|
||||
else if ((screenMarginBottom - rect.Bottom) >= spacing)
|
||||
{
|
||||
checkX = rect.Left;
|
||||
checkY = rect.Bottom + (spacing / 2);
|
||||
}
|
||||
else if ((rect.Left - screenMarginLeft) >= spacing)
|
||||
{
|
||||
checkX = rect.Left - (spacing / 2);
|
||||
checkY = rect.Top;
|
||||
}
|
||||
else if ((screenMarginRight - rect.Right) >= spacing)
|
||||
{
|
||||
checkX = rect.Right + (spacing / 2);
|
||||
checkY = rect.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(spacing), "No sufficient margin to sample outside the window.");
|
||||
}
|
||||
|
||||
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the mouse is in position
|
||||
string zoneColor = this.GetPixelColorString(checkX, checkY);
|
||||
return zoneColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs drag interactions during a FancyZones test and returns the initial and final zone highlight colors.
|
||||
/// </summary>
|
||||
/// <param name="preAction">An optional action to execute before the drag starts (e.g., setup or key press).</param>
|
||||
/// <param name="postAction">An optional action to execute after the drag is initiated but before it's released.</param>
|
||||
/// <param name="releaseAction">An optional action to execute when releasing the dragged window (e.g., mouse up).</param>
|
||||
/// <param name="testCaseName">The name of the test case for logging or diagnostics.</param>
|
||||
/// <returns>
|
||||
/// A tuple containing:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>InitialZoneColor</c>: The zone highlight color before interaction completes.</description></item>
|
||||
/// <item><description><c>FinalZoneColor</c>: The zone highlight color after interaction completes.</description></item>
|
||||
/// </list>
|
||||
/// </returns>
|
||||
private (string InitialZoneColor, string FinalZoneColor) RunDragInteractions(
|
||||
Action? preAction,
|
||||
Action? postAction,
|
||||
Action? releaseAction,
|
||||
string testCaseName)
|
||||
{
|
||||
// Invoke the pre-action
|
||||
preAction?.Invoke();
|
||||
|
||||
// Capture initial window state and zone color
|
||||
var initialWindowRect = Session.GetMainWindowRect();
|
||||
string initialZoneColor = GetOutWindowPixelColor(30);
|
||||
|
||||
// Invoke the post-action
|
||||
postAction?.Invoke();
|
||||
|
||||
// Capture final zone color after the interaction
|
||||
string finalZoneColor = GetOutWindowPixelColor(30);
|
||||
|
||||
releaseAction?.Invoke();
|
||||
|
||||
// Return initial and final zone colors
|
||||
return (initialZoneColor, finalZoneColor);
|
||||
}
|
||||
|
||||
// set the custom layout
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{63F09977-D327-4DAC-98F4-0C886CAE9517}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Custom Column",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper
|
||||
{
|
||||
Rows = 1,
|
||||
Columns = 1,
|
||||
RowsPercentage = new List<int> { 10000 },
|
||||
ColumnsPercentage = new List<int> { 10000 },
|
||||
CellChildMap = new int[][] { [0] },
|
||||
SensitivityRadius = 20,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10, // set spacing to 0 make sure the zone is full of the screen
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// set the custom layout with 1 subzones
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsListWithTwo = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{63F09977-D327-4DAC-98F4-0C886CAE9517}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Custom Column",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper
|
||||
{
|
||||
Rows = 1,
|
||||
Columns = 2,
|
||||
RowsPercentage = new List<int> { 10000 },
|
||||
ColumnsPercentage = new List<int> { 5000, 5000 },
|
||||
CellChildMap = new int[][] { [0, 1] },
|
||||
SensitivityRadius = 20,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private string GetZoneColor(string color)
|
||||
{
|
||||
// Click on the "Highlight color" group
|
||||
Find<Group>(color).Click();
|
||||
|
||||
// Optional: Ensure the hex textbox is found (to wait until the UI loads)
|
||||
var hexBox = Find<Element>(By.AccessibilityId("HexTextBox"));
|
||||
Task.Delay(500).Wait(); // Optional: Wait for the UI to update
|
||||
|
||||
// Get and return the RGB hex value text
|
||||
var hexColorElement = Find<Element>("RGB hex");
|
||||
|
||||
// return mouse to color set position
|
||||
Find<Group>(color).Click();
|
||||
|
||||
return hexColorElement.Text;
|
||||
}
|
||||
|
||||
// set the zone behaviour settings
|
||||
private void ZoneBehaviourSettings(string? testName)
|
||||
{
|
||||
// test settings
|
||||
Microsoft.PowerToys.UITest.CheckBox useShiftCheckBox = this.Find<Microsoft.PowerToys.UITest.CheckBox>("Hold Shift key to activate zones while dragging a window");
|
||||
Microsoft.PowerToys.UITest.CheckBox useNonPrimaryMouseCheckBox = this.Find<Microsoft.PowerToys.UITest.CheckBox>("Use a non-primary mouse button to toggle zone activation");
|
||||
Microsoft.PowerToys.UITest.CheckBox makeDraggedWindowTransparent = this.Find<Microsoft.PowerToys.UITest.CheckBox>("Make dragged window transparent");
|
||||
|
||||
Find<Microsoft.PowerToys.UITest.CheckBox>("Show zone number").SetCheck(false, 100);
|
||||
Find<Slider>("Opacity (%)").QuickSetValue(100); // make highlight color visible with opacity 100
|
||||
|
||||
// Get the highlight and inactivate color from appearance settings
|
||||
Find<Microsoft.PowerToys.UITest.ComboBox>("Zone appearance").Click();
|
||||
Find<Element>("Custom colors").Click();
|
||||
|
||||
// get the highlight (activated) and inactivate zone color
|
||||
highlightColor = GetZoneColor("Highlight color");
|
||||
inactivateColor = GetZoneColor("Inactive color");
|
||||
|
||||
this.Scroll(2, "Down");
|
||||
makeDraggedWindowTransparent.SetCheck(false, 500); // set make dragged window transparent to false or will influence the color comparison
|
||||
this.Scroll(6, "Up");
|
||||
|
||||
switch (testName)
|
||||
{
|
||||
case "TestShowZonesOnShiftDuringDrag":
|
||||
useShiftCheckBox.SetCheck(true, 500);
|
||||
useNonPrimaryMouseCheckBox.SetCheck(false, 500);
|
||||
break;
|
||||
case "TestShowZonesOnDragDuringShift":
|
||||
useShiftCheckBox.SetCheck(true, 500);
|
||||
useNonPrimaryMouseCheckBox.SetCheck(false, 500);
|
||||
break;
|
||||
case "TestToggleZonesWithNonPrimaryMouseClick":
|
||||
useShiftCheckBox.SetCheck(false, 500);
|
||||
useNonPrimaryMouseCheckBox.SetCheck(true, 500);
|
||||
break;
|
||||
case "TestShowZonesWhenShiftAndMouseOff":
|
||||
useShiftCheckBox.SetCheck(false, 500);
|
||||
useNonPrimaryMouseCheckBox.SetCheck(false, 500);
|
||||
break;
|
||||
case "TestShowZonesWhenShiftAndMouseOn":
|
||||
useShiftCheckBox.SetCheck(true, 500);
|
||||
useNonPrimaryMouseCheckBox.SetCheck(true, 500);
|
||||
break;
|
||||
case "TestMakeDraggedWindowTransparentOff":
|
||||
useShiftCheckBox.SetCheck(true, 500);
|
||||
useNonPrimaryMouseCheckBox.SetCheck(false, 500);
|
||||
break; // Added break to prevent fall-through
|
||||
case "TestMakeDraggedWindowTransparentOn":
|
||||
useNonPrimaryMouseCheckBox.SetCheck(false, 500);
|
||||
useShiftCheckBox.SetCheck(true, 500);
|
||||
this.Scroll(5, "Down"); // Pull the settings page up to make sure the settings are visible
|
||||
makeDraggedWindowTransparent.SetCheck(true, 500);
|
||||
this.Scroll(5, "Up");
|
||||
break; // Added break to prevent fall-through
|
||||
default:
|
||||
throw new ArgumentException("Unsupported Test Case.", testName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,673 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Automation;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZonesEditor.UITests;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
using static Microsoft.FancyZonesEditor.UnitTests.Utils.FancyZonesEditorHelper;
|
||||
|
||||
namespace UITests_FancyZones
|
||||
{
|
||||
[TestClass]
|
||||
public class LayoutApplyHotKeyTests : UITestBase
|
||||
{
|
||||
public LayoutApplyHotKeyTests()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
private static readonly EditorParameters.ParamsWrapper Parameters = new EditorParameters.ParamsWrapper
|
||||
{
|
||||
ProcessId = 1,
|
||||
SpanZonesAcrossMonitors = false,
|
||||
Monitors = new List<EditorParameters.NativeMonitorDataWrapper>
|
||||
{
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-1",
|
||||
MonitorInstanceId = "instance-id-1",
|
||||
MonitorSerialNumber = "serial-number-1",
|
||||
MonitorNumber = 1,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 0,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = true,
|
||||
},
|
||||
new EditorParameters.NativeMonitorDataWrapper
|
||||
{
|
||||
Monitor = "monitor-2",
|
||||
MonitorInstanceId = "instance-id-2",
|
||||
MonitorSerialNumber = "serial-number-2",
|
||||
MonitorNumber = 2,
|
||||
VirtualDesktop = "{FF34D993-73F3-4B8C-AA03-73730A01D6A8}",
|
||||
Dpi = 96,
|
||||
LeftCoordinate = 1920,
|
||||
TopCoordinate = 0,
|
||||
WorkAreaHeight = 1040,
|
||||
WorkAreaWidth = 1920,
|
||||
MonitorHeight = 1080,
|
||||
MonitorWidth = 1920,
|
||||
IsSelected = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 2,
|
||||
Columns = 2,
|
||||
RowsPercentage = new List<int> { 5000, 5000 },
|
||||
ColumnsPercentage = new List<int> { 5000, 5000 },
|
||||
CellChildMap = new int[][] { [0, 1], [2, 3] },
|
||||
SensitivityRadius = 30,
|
||||
Spacing = 26,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Grid-9",
|
||||
Info = new CustomLayouts().ToJsonElement(new GridInfoWrapper
|
||||
{
|
||||
Rows = 3,
|
||||
Columns = 3,
|
||||
RowsPercentage = new List<int> { 2333, 3333, 4334 },
|
||||
ColumnsPercentage = new List<int> { 2333, 3333, 4334 },
|
||||
CellChildMap = new int[][] { [0, 1, 2], [3, 4, 5], [6, 7, 8] },
|
||||
SensitivityRadius = 20,
|
||||
Spacing = 3,
|
||||
ShowSpacing = false,
|
||||
}),
|
||||
},
|
||||
new CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
Type = CustomLayout.Canvas.TypeToString(),
|
||||
Name = "Canvas custom layout",
|
||||
Info = new CustomLayouts().ToJsonElement(new CanvasInfoWrapper
|
||||
{
|
||||
RefHeight = 1040,
|
||||
RefWidth = 1920,
|
||||
SensitivityRadius = 10,
|
||||
Zones = new List<CanvasInfoWrapper.CanvasZoneWrapper>
|
||||
{
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 500,
|
||||
Height = 250,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 500,
|
||||
Y = 0,
|
||||
Width = 1420,
|
||||
Height = 500,
|
||||
},
|
||||
new CanvasInfoWrapper.CanvasZoneWrapper
|
||||
{
|
||||
X = 0,
|
||||
Y = 250,
|
||||
Width = 1920,
|
||||
Height = 500,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutHotkeys.LayoutHotkeysWrapper LayoutHotkeysList = new LayoutHotkeys.LayoutHotkeysWrapper
|
||||
{
|
||||
LayoutHotkeys = new List<LayoutHotkeys.LayoutHotkeyWrapper>
|
||||
{
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
Key = 0,
|
||||
LayoutId = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
},
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
Key = 1,
|
||||
LayoutId = "{0EB9BF3E-010E-46D7-8681-1879D1E111E1}",
|
||||
},
|
||||
new LayoutHotkeys.LayoutHotkeyWrapper
|
||||
{
|
||||
Key = 2,
|
||||
LayoutId = "{E7807D0D-6223-4883-B15B-1F3883944C09}",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
private static readonly LayoutTemplates.TemplateLayoutsListWrapper TemplateLayoutsList = new LayoutTemplates.TemplateLayoutsListWrapper
|
||||
{
|
||||
LayoutTemplates = new List<LayoutTemplates.TemplateLayoutWrapper>
|
||||
{
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Blank.TypeToString(),
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Rows.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 10,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Columns.TypeToString(),
|
||||
ZoneCount = 2,
|
||||
ShowSpacing = true,
|
||||
Spacing = 20,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Grid.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = false,
|
||||
Spacing = 10,
|
||||
SensitivityRadius = 30,
|
||||
},
|
||||
new LayoutTemplates.TemplateLayoutWrapper
|
||||
{
|
||||
Type = LayoutType.PriorityGrid.TypeToString(),
|
||||
ZoneCount = 3,
|
||||
ShowSpacing = true,
|
||||
Spacing = 1,
|
||||
SensitivityRadius = 40,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
LayoutTemplates layoutTemplates = new LayoutTemplates();
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.WriteData(layoutTemplates.Serialize(TemplateLayoutsList));
|
||||
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(CustomLayoutsList));
|
||||
|
||||
DefaultLayouts defaultLayouts = new DefaultLayouts();
|
||||
DefaultLayouts.DefaultLayoutsListWrapper defaultLayoutsListWrapper = new DefaultLayouts.DefaultLayoutsListWrapper
|
||||
{
|
||||
DefaultLayouts = new List<DefaultLayouts.DefaultLayoutWrapper>
|
||||
{
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Horizontal.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Focus.TypeToString(),
|
||||
ZoneCount = 4,
|
||||
ShowSpacing = true,
|
||||
Spacing = 5,
|
||||
SensitivityRadius = 20,
|
||||
},
|
||||
},
|
||||
new DefaultLayouts.DefaultLayoutWrapper
|
||||
{
|
||||
MonitorConfiguration = MonitorConfigurationType.Vertical.TypeToString(),
|
||||
Layout = new DefaultLayouts.DefaultLayoutWrapper.LayoutWrapper
|
||||
{
|
||||
Type = LayoutType.Custom.TypeToString(),
|
||||
Uuid = "{0D6D2F58-9184-4804-81E4-4E4CC3476DC1}",
|
||||
ZoneCount = 0,
|
||||
ShowSpacing = false,
|
||||
Spacing = 0,
|
||||
SensitivityRadius = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(defaultLayoutsListWrapper));
|
||||
|
||||
LayoutHotkeys layoutHotkeys = new LayoutHotkeys();
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.WriteData(layoutHotkeys.Serialize(LayoutHotkeysList));
|
||||
|
||||
AppliedLayouts appliedLayouts = new AppliedLayouts();
|
||||
AppliedLayouts.AppliedLayoutsListWrapper appliedLayoutsWrapper = new AppliedLayouts.AppliedLayoutsListWrapper
|
||||
{
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestMethod("FancyZones.Settings.TestApplyHotKey")]
|
||||
[TestCategory("FancyZones #1")]
|
||||
public void TestApplyHotKey()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
this.ControlQuickLayoutSwitch(true);
|
||||
|
||||
// Set Hotkey
|
||||
this.AttachFancyZonesEditor();
|
||||
var layout = "Grid custom layout";
|
||||
Session.Find<Element>(layout).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
const string key = "0";
|
||||
var hotkeyComboBox = Session.Find<Element>(By.AccessibilityId(AccessibilityId.HotkeyComboBox));
|
||||
Assert.IsNotNull(hotkeyComboBox);
|
||||
hotkeyComboBox.Click();
|
||||
var popup = Session.Find<Element>(By.ClassName(ClassName.Popup));
|
||||
Assert.IsNotNull(popup);
|
||||
popup.Find<Element>($"{key}").Click(); // assign a free hotkey
|
||||
|
||||
Task.Delay(3000).Wait();
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num0);
|
||||
Task.Delay(3000).Wait();
|
||||
this.AttachFancyZonesEditor();
|
||||
var element = this.Find<Element>(layout);
|
||||
Assert.IsTrue(element.Selected, $"{element.Selected} Grid custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
|
||||
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num1);
|
||||
Task.Delay(3000).Wait();
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Grid-9");
|
||||
Assert.IsTrue(element.Selected, $"{element.Selected} Grid-9 is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
|
||||
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num2);
|
||||
Task.Delay(3000).Wait();
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Canvas custom layout");
|
||||
Assert.IsTrue(element.Selected, $"{element.Selected} Canvas custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
}
|
||||
|
||||
/*
|
||||
[TestMethod]
|
||||
[TestCategory("FancyZones #2")]
|
||||
public void TestDragShiftHotKey()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
this.ControlQuickLayoutSwitch(true);
|
||||
|
||||
int screenWidth = 1920; // default 1920
|
||||
|
||||
int targetX = screenWidth / 2 / 3;
|
||||
int targetY = screenWidth / 2 / 2;
|
||||
|
||||
LaunchHostFromSetting();
|
||||
this.Session.Attach(PowerToysModule.Hosts, WindowSize.Large_Vertical);
|
||||
var hostsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
hostsView.DoubleClick(); // maximize the window
|
||||
|
||||
hostsView.HoldShiftToDrag(Key.Shift, targetX, targetY);
|
||||
SendKeys(Key.Num0);
|
||||
hostsView.ReleaseAction();
|
||||
hostsView.ReleaseKey(Key.Shift);
|
||||
SendKeys(Key.Alt, Key.F4);
|
||||
|
||||
// Attach FancyZones Editor
|
||||
this.AttachPowertoySetting();
|
||||
this.Find<Pane>(By.ClassName("InputNonClientPointerSource")).Click();
|
||||
this.OpenFancyZonesPanel(isMax: false);
|
||||
this.AttachFancyZonesEditor();
|
||||
var elements = this.FindAll<Element>("Grid custom layout");
|
||||
if (elements.Count == 0)
|
||||
{
|
||||
this.Session.Attach(PowerToysModule.Hosts, WindowSize.Large_Vertical);
|
||||
hostsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
hostsView.DoubleClick(); // maximize the window
|
||||
|
||||
hostsView.HoldShiftToDrag(Key.Shift, targetX, targetY);
|
||||
SendKeys(Key.Num0);
|
||||
hostsView.ReleaseAction();
|
||||
hostsView.ReleaseKey(Key.Shift);
|
||||
SendKeys(Key.Alt, Key.F4);
|
||||
this.AttachPowertoySetting();
|
||||
this.Find<Pane>(By.ClassName("InputNonClientPointerSource")).Click();
|
||||
this.OpenFancyZonesPanel(isMax: false);
|
||||
this.AttachFancyZonesEditor();
|
||||
elements = this.FindAll<Element>("Grid custom layout");
|
||||
}
|
||||
|
||||
Assert.IsTrue(elements[0].Selected, "Grid custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
|
||||
Clean();
|
||||
}
|
||||
*/
|
||||
|
||||
[TestMethod("FancyZones.Settings.HotKeyWindowFlashTest")]
|
||||
[TestCategory("FancyZones #3")]
|
||||
public void HotKeyWindowFlashTest()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
this.ControlQuickLayoutSwitch(true);
|
||||
|
||||
this.TryReaction();
|
||||
int tries = 24;
|
||||
Pull(tries, "down");
|
||||
var switchGroup = this.Find<Group>("Enable quick layout switch");
|
||||
switchGroup.Click();
|
||||
var checkbox1 = switchGroup.Find<Element>("Flash zones when switching layout");
|
||||
if (checkbox1.GetAttribute("Toggle.ToggleState") == "0")
|
||||
{
|
||||
checkbox1.Click();
|
||||
}
|
||||
|
||||
this.Session.PressKey(Key.Win);
|
||||
this.Session.PressKey(Key.Ctrl);
|
||||
this.Session.PressKey(Key.Alt);
|
||||
this.Session.PressKey(Key.Num0);
|
||||
bool res = this.IsWindowOpen("FancyZones_ZonesOverlay");
|
||||
Assert.IsTrue(res, $" HotKeyWindowFlash Test error: FancyZones_ZonesOverlay is not open");
|
||||
this.Session.ReleaseKey(Key.Win);
|
||||
this.Session.ReleaseKey(Key.Ctrl);
|
||||
this.Session.ReleaseKey(Key.Alt);
|
||||
this.Session.ReleaseKey(Key.Num0);
|
||||
|
||||
var checkbox2 = this.Find<CheckBox>("Flash zones when switching layout");
|
||||
if (checkbox2.GetAttribute("Toggle.ToggleState") == "1")
|
||||
{
|
||||
checkbox2.Click();
|
||||
}
|
||||
|
||||
// this.CloseFancyZonesEditor();
|
||||
Clean();
|
||||
}
|
||||
|
||||
[TestMethod("FancyZones.Settings.TestDisableApplyHotKey")]
|
||||
[TestCategory("FancyZones #4")]
|
||||
public void TestDisableApplyHotKey()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
this.ControlQuickLayoutSwitch(false);
|
||||
|
||||
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num0);
|
||||
this.AttachFancyZonesEditor();
|
||||
var element = this.Find<Element>("Grid custom layout");
|
||||
Assert.IsFalse(element.Selected, $"{element.Selected} Grid custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
|
||||
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num1);
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Grid-9");
|
||||
Assert.IsFalse(element.Selected, $"{element.Selected} Grid-9 is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
|
||||
SendKeys(Key.Win, Key.Ctrl, Key.Alt, Key.Num2);
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Canvas custom layout");
|
||||
Assert.IsFalse(element.Selected, $"{element.Selected} Canvas custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.AttachPowertoySetting();
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
[TestMethod("FancyZones.Settings.TestVirtualDesktopLayout")]
|
||||
[TestCategory("FancyZones #6")]
|
||||
public void TestVirtualDesktopLayout()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
|
||||
this.AttachFancyZonesEditor();
|
||||
var element = this.Find<Element>("Grid custom layout");
|
||||
element.Click();
|
||||
this.CloseFancyZonesEditor();
|
||||
this.ExitScopeExe();
|
||||
|
||||
// Add virtual desktop
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.D);
|
||||
this.RestartScopeExe();
|
||||
this.OpenFancyZonesPanel();
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Grid custom layout");
|
||||
Assert.IsTrue(element.Selected, $"{element.Selected} Canvas custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
|
||||
// close the virtual desktop
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.Right);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.F4);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
[TestMethod("FancyZones.Settings.TestVirtualDesktopLayoutExt")]
|
||||
[TestCategory("FancyZones #7")]
|
||||
public void TestVirtualDesktopLayoutExt()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
|
||||
this.AttachFancyZonesEditor();
|
||||
var element = this.Find<Element>("Grid custom layout");
|
||||
element.Click();
|
||||
this.CloseFancyZonesEditor();
|
||||
this.ExitScopeExe();
|
||||
|
||||
// Add virtual desktop
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.D);
|
||||
this.RestartScopeExe();
|
||||
this.OpenFancyZonesPanel();
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Grid-9");
|
||||
element.Click();
|
||||
this.CloseFancyZonesEditor();
|
||||
this.ExitScopeExe();
|
||||
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.Left);
|
||||
this.RestartScopeExe();
|
||||
this.OpenFancyZonesPanel();
|
||||
this.AttachFancyZonesEditor();
|
||||
element = this.Find<Element>("Grid custom layout");
|
||||
Assert.IsTrue(element.Selected, $"{element.Selected} Grid custom layout is not visible");
|
||||
this.CloseFancyZonesEditor();
|
||||
this.ExitScopeExe();
|
||||
|
||||
// close the virtual desktop
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.Right);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.F4);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
[TestMethod("FancyZones.Settings.TestDeleteCustomLayoutBehavior")]
|
||||
[TestCategory("FancyZones #8")]
|
||||
public void TestDeleteCustomLayoutBehavior()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
|
||||
this.AttachFancyZonesEditor();
|
||||
this.Find<Element>("Grid custom layout").Click();
|
||||
this.Find<Element>("Grid custom layout").Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the empty layout is selected
|
||||
Assert.IsTrue(Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.Blank])!.Selected);
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
[TestMethod("FancyZones.Settings.TestCreateGridLayoutChangeMonitorSetting")]
|
||||
[TestCategory("FancyZones #9")]
|
||||
public void TestCreateGridLayoutChangeMonitorSetting()
|
||||
{
|
||||
this.OpenFancyZonesPanel();
|
||||
this.AttachFancyZonesEditor();
|
||||
|
||||
string name = "Custom layout 1";
|
||||
this.Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
this.Session.Find<Element>(By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
this.Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify new layout presented
|
||||
Assert.IsNotNull(Session.Find<Element>(name));
|
||||
this.CloseFancyZonesEditor();
|
||||
|
||||
int nowHeight = UITestBase.MonitorInfoData.Monitors[UITestBase.MonitorInfoData.Monitors.Count - 1].PelsHeight;
|
||||
int nowWidth = UITestBase.MonitorInfoData.Monitors[UITestBase.MonitorInfoData.Monitors.Count - 1].PelsWidth;
|
||||
int height = UITestBase.MonitorInfoData.Monitors[0].PelsHeight;
|
||||
int width = UITestBase.MonitorInfoData.Monitors[0].PelsWidth;
|
||||
UITestBase.NativeMethods.ChangeDisplayResolution(width, height);
|
||||
this.AttachPowertoySetting();
|
||||
this.AttachFancyZonesEditor();
|
||||
var maxButton = this.Find<Button>("Maximize");
|
||||
maxButton.Click(); // maximize the window
|
||||
var resolution = this.Session.Find<Element>(By.AccessibilityId("Monitors")).Find<Element>("Monitor 1").Find<Element>(By.AccessibilityId("ResolutionText"));
|
||||
if (resolution.Text != "640 × 480")
|
||||
{
|
||||
this.CloseFancyZonesEditor();
|
||||
UITestBase.NativeMethods.ChangeDisplayResolution(nowWidth, nowHeight);
|
||||
Assert.AreEqual("640 × 480", resolution.Text);
|
||||
}
|
||||
|
||||
this.CloseFancyZonesEditor();
|
||||
UITestBase.NativeMethods.ChangeDisplayResolution(nowWidth, nowHeight);
|
||||
|
||||
Clean();
|
||||
}
|
||||
|
||||
private void OpenFancyZonesPanel(bool launchAsAdmin = false, bool isMax = false)
|
||||
{
|
||||
var windowingElement = this.Find<NavigationViewItem>("Windowing & Layouts");
|
||||
|
||||
// Goto FancyZones Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
windowingElement.Click();
|
||||
}
|
||||
|
||||
windowingElement.Find<Element>("FancyZones").Click();
|
||||
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
|
||||
if (isMax == true)
|
||||
{
|
||||
this.Find<Button>("Maximize").Click(); // maximize the window
|
||||
}
|
||||
|
||||
this.Find<Custom>("Editor").Find<TextBlock>(By.AccessibilityId("HeaderPresenter")).Click();
|
||||
}
|
||||
|
||||
private void ControlQuickLayoutSwitch(bool flag)
|
||||
{
|
||||
this.TryReaction();
|
||||
int tries = 24;
|
||||
Pull(tries, "down"); // Pull the setting page up to make sure the setting is visible
|
||||
this.Find<ToggleSwitch>("Enable quick layout switch").Toggle(flag);
|
||||
|
||||
tries = 24;
|
||||
Pull(tries, "up");
|
||||
}
|
||||
|
||||
private void TryReaction()
|
||||
{
|
||||
this.Find<Custom>("Editor").Find<TextBlock>(By.AccessibilityId("HeaderPresenter")).Click();
|
||||
}
|
||||
|
||||
private void AttachPowertoySetting()
|
||||
{
|
||||
Task.Delay(200).Wait();
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
|
||||
private void AttachFancyZonesEditor()
|
||||
{
|
||||
Task.Delay(4000).Wait();
|
||||
this.Find<Button>("Launch layout editor").Click();
|
||||
|
||||
Task.Delay(3000).Wait();
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
Task.Delay(3000).Wait();
|
||||
}
|
||||
|
||||
private void CloseFancyZonesEditor()
|
||||
{
|
||||
this.Session.Find<Button>("Close").Click();
|
||||
}
|
||||
|
||||
private void Clean()
|
||||
{
|
||||
// clean app zone history file
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.DeleteFile();
|
||||
FancyZonesEditorHelper.Files.LayoutHotkeysIOHelper.DeleteFile();
|
||||
FancyZonesEditorHelper.Files.LayoutTemplatesIOHelper.DeleteFile();
|
||||
}
|
||||
|
||||
private void Pull(int tries = 5, string direction = "up")
|
||||
{
|
||||
Key keyToSend = direction == "up" ? Key.Up : Key.Down;
|
||||
for (int i = 0; i < tries; i++)
|
||||
{
|
||||
SendKeys(keyToSend);
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchHostFromSetting(bool showWarning = false, bool launchAsAdmin = false)
|
||||
{
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Hosts File Editor").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Advanced").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("Hosts File Editor").Click();
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
|
||||
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
|
||||
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
|
||||
|
||||
// launch Hosts File Editor
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
Task.Delay(5000).Wait();
|
||||
}
|
||||
}
|
||||
}
|
313
src/modules/fancyzones/UITests-FancyZones/OneZoneSwitchTests.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
// 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.Threading.Tasks;
|
||||
using FancyZonesEditor.Models;
|
||||
using FancyZonesEditorCommon.Data;
|
||||
using Microsoft.FancyZones.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.FancyZonesEditor.UnitTests.Utils;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UITests_FancyZones
|
||||
{
|
||||
[TestClass]
|
||||
public class OneZoneSwitchTests : UITestBase
|
||||
{
|
||||
private static readonly int SubZones = 2;
|
||||
private static readonly IOTestHelper AppZoneHistory = new FancyZonesEditorFiles().AppZoneHistoryIOHelper;
|
||||
private static string powertoysWindowName = "PowerToys Settings"; // set powertoys settings window name
|
||||
|
||||
public OneZoneSwitchTests()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Medium)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
// kill all processes related to FancyZones Editor to ensure a clean state
|
||||
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
|
||||
AppZoneHistory.DeleteFile();
|
||||
|
||||
this.RestartScopeExe();
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
|
||||
// Set a custom layout with 1 subzones and clear app zone history
|
||||
SetupCustomLayouts();
|
||||
|
||||
// get PowerToys window Name
|
||||
powertoysWindowName = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
|
||||
// Launch FancyZones
|
||||
LaunchFancyZones();
|
||||
|
||||
// Launch the Hosts File Editor
|
||||
LaunchFromSetting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test switching between two snapped windows using keyboard shortcuts in FancyZones
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that after snapping two windows, the active window switches correctly using Win+PageDown.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[TestCategory("FancyZones #Switch between windows in the current zone #1")]
|
||||
public void TestSwitchWindow()
|
||||
{
|
||||
var (preWindow, postWindow) = SnapToOneZone();
|
||||
|
||||
string? activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreEqual(postWindow, activeWindowTitle);
|
||||
|
||||
// switch to the previous window by shortcut win+page down
|
||||
SendKeys(Key.Win, Key.PageDown);
|
||||
|
||||
activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreEqual(preWindow, activeWindowTitle);
|
||||
|
||||
Clean(); // close the windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test window switch behavior across virtual desktops in FancyZones
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that a window remains correctly snapped after switching desktops and can be switched using Win+PageDown.</description>
|
||||
/// </item>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestSwitchAfterDesktopChange")]
|
||||
[TestCategory("FancyZones #Switch between windows in the current zone #2")]
|
||||
public void TestSwitchAfterDesktopChange()
|
||||
{
|
||||
var (preWindow, postWindow) = SnapToOneZone();
|
||||
|
||||
string? windowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreEqual(postWindow, windowTitle);
|
||||
|
||||
// Add virtual desktop
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.D);
|
||||
|
||||
// return back
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.Left);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
string? returnWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreEqual(postWindow, returnWindowTitle);
|
||||
|
||||
// check shortcut
|
||||
SendKeys(Key.Win, Key.PageDown);
|
||||
string? activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreEqual(preWindow, activeWindowTitle);
|
||||
|
||||
// close the virtual desktop
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.Right);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
SendKeys(Key.Ctrl, Key.Win, Key.F4);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
|
||||
Clean(); // close the windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test window switching shortcut behavior when the shortcut is disabled in FancyZones settings
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Verifies that pressing Win+PageDown does not switch to the previously snapped window when the shortcut is disabled.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod("FancyZones.Settings.TestSwitchShortCutDisable")]
|
||||
[TestCategory("FancyZones #Switch between windows in the current zone #3")]
|
||||
public void TestSwitchShortCutDisable()
|
||||
{
|
||||
var (preWindow, postWindow) = SnapToOneZone();
|
||||
|
||||
string? activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreEqual(postWindow, activeWindowTitle);
|
||||
|
||||
// switch to the previous window by shortcut win+page down
|
||||
SendKeys(Key.Win, Key.PageDown);
|
||||
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
|
||||
|
||||
activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Assert.AreNotEqual(preWindow, activeWindowTitle);
|
||||
|
||||
Clean(); // close the windows
|
||||
}
|
||||
|
||||
private (string PreWindow, string PostWindow) SnapToOneZone()
|
||||
{
|
||||
this.Session.Attach(PowerToysModule.Hosts, WindowSize.Large_Vertical);
|
||||
|
||||
var hostsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
hostsView.DoubleClick(); // maximize the window
|
||||
|
||||
var rect = Session.GetMainWindowRect();
|
||||
var (targetX, targetY) = ZoneSwitchHelper.GetScreenMargins(rect, 4);
|
||||
var offSet = ZoneSwitchHelper.GetOffset(hostsView, targetX, targetY);
|
||||
|
||||
DragWithShift(hostsView, offSet);
|
||||
|
||||
string preWindow = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
|
||||
// Attach the PowerToys settings window to the front
|
||||
Session.Attach(powertoysWindowName, WindowSize.UnSpecified);
|
||||
string windowNameFront = ZoneSwitchHelper.GetActiveWindowTitle();
|
||||
Pane settingsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
|
||||
settingsView.DoubleClick(); // maximize the window
|
||||
|
||||
DragWithShift(settingsView, offSet);
|
||||
|
||||
string appZoneHistoryJson = AppZoneHistory.GetData();
|
||||
|
||||
string? zoneIndexOfFileWindow = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Hosts.exe", appZoneHistoryJson); // explorer.exe
|
||||
string? zoneIndexOfPowertoys = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Settings.exe", appZoneHistoryJson);
|
||||
|
||||
// check the AppZoneHistory layout is set and in the same zone
|
||||
Assert.AreEqual(zoneIndexOfPowertoys, zoneIndexOfFileWindow);
|
||||
|
||||
return (preWindow, powertoysWindowName);
|
||||
}
|
||||
|
||||
private void DragWithShift(Pane settingsView, (int Dx, int Dy) offSet)
|
||||
{
|
||||
Session.PressKey(Key.Shift);
|
||||
settingsView.DragAndHold(offSet.Dx, offSet.Dy);
|
||||
Task.Delay(1000).Wait(); // Wait for drag to start (optional)
|
||||
settingsView.ReleaseDrag();
|
||||
Task.Delay(1000).Wait(); // Wait after drag (optional)
|
||||
Session.ReleaseKey(Key.Shift);
|
||||
}
|
||||
|
||||
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
|
||||
{
|
||||
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
|
||||
{
|
||||
new CustomLayouts.CustomLayoutWrapper
|
||||
{
|
||||
Uuid = "{63F09977-D327-4DAC-98F4-0C886CAE9517}",
|
||||
Type = CustomLayout.Grid.TypeToString(),
|
||||
Name = "Custom Column",
|
||||
Info = new CustomLayouts().ToJsonElement(new CustomLayouts.GridInfoWrapper
|
||||
{
|
||||
Rows = 1,
|
||||
Columns = SubZones,
|
||||
RowsPercentage = new List<int> { 10000 },
|
||||
ColumnsPercentage = new List<int> { 5000, 5000 },
|
||||
CellChildMap = new int[][] { [0, 1] },
|
||||
SensitivityRadius = 20,
|
||||
ShowSpacing = true,
|
||||
Spacing = 10,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// clean window
|
||||
private void Clean()
|
||||
{
|
||||
// Close First window
|
||||
SendKeys(Key.Alt, Key.F4);
|
||||
|
||||
// Close Second window
|
||||
SendKeys(Key.Alt, Key.F4);
|
||||
|
||||
// clean app zone history file
|
||||
AppZoneHistory.DeleteFile();
|
||||
}
|
||||
|
||||
// Setup custom layout with 1 subzones
|
||||
private void SetupCustomLayouts()
|
||||
{
|
||||
var customLayouts = new CustomLayouts();
|
||||
var customLayoutListWrapper = CustomLayoutsList;
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(customLayoutListWrapper));
|
||||
}
|
||||
|
||||
// launch FancyZones settings page
|
||||
private void LaunchFancyZones()
|
||||
{
|
||||
// Goto FancyZones setting page
|
||||
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Windowing & Layouts").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("FancyZones").Click();
|
||||
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
|
||||
this.Session.SetMainWindowSize(WindowSize.Large);
|
||||
|
||||
// fixed settings
|
||||
this.Find<CheckBox>("Hold Shift key to activate zones while dragging a window").SetCheck(true, 500);
|
||||
|
||||
// should bind mouse to suitable zone for scrolling
|
||||
Find<Element>(By.AccessibilityId("HeaderPresenter")).Click();
|
||||
this.Scroll(9, "Down"); // Pull the setting page up to make sure the setting is visible
|
||||
bool switchWindowEnable = TestContext.TestName == "TestSwitchShortCutDisable" ? false : true;
|
||||
|
||||
this.Find<ToggleSwitch>("Switch between windows in the current zone").Toggle(switchWindowEnable);
|
||||
|
||||
Task.Delay(500).Wait(); // Wait for the setting to be applied
|
||||
this.Scroll(9, "Up"); // Pull the setting page down to make sure the setting is visible
|
||||
this.Find<Button>("Launch layout editor").Click(false, 500, 5000);
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
|
||||
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
|
||||
// Console.WriteLine($"after launch, Custom layout data: {customLayoutData}");
|
||||
try
|
||||
{
|
||||
// Set the FancyZones layout to a custom layout
|
||||
this.Find<Element>(By.Name("Custom Column")).Click();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Console.WriteLine($"[Exception] Failed to attach to FancyZones window. Retrying...{ex.Message}");
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
SetupCustomLayouts();
|
||||
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
|
||||
this.Session.Attach(PowerToysModule.FancyZone);
|
||||
|
||||
// customLayoutData = FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.GetData();
|
||||
// Console.WriteLine($"after retry, Custom layout data: {customLayoutData}");
|
||||
|
||||
// Set the FancyZones layout to a custom layout
|
||||
this.Find<Element>(By.Name("Custom Column")).Click();
|
||||
}
|
||||
|
||||
// Close layout editor window
|
||||
SendKeys(Key.Alt, Key.F4);
|
||||
this.Session.Attach(powertoysWindowName);
|
||||
}
|
||||
|
||||
private void LaunchFromSetting(bool showWarning = false, bool launchAsAdmin = false)
|
||||
{
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Hosts File Editor").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Advanced").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("Hosts File Editor").Click();
|
||||
Task.Delay(1000).Wait();
|
||||
|
||||
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
|
||||
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
|
||||
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
|
||||
|
||||
// launch Hosts File Editor
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
Task.Delay(5000).Wait();
|
||||
}
|
||||
}
|
||||
}
|
@@ -27,5 +27,15 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
<ProjectReference Include="..\editor\FancyZonesEditor\FancyZonesEditor.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
<ProjectReference Include="..\UITests-FancyZonesEditor\UITests-FancyZonesEditor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\editor\FancyZonesEditor\FancyZonesEditor.csproj" />
|
||||
<ProjectReference Include="..\UITests-FancyZonesEditor\UITests-FancyZonesEditor.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Internal;
|
||||
|
||||
namespace Microsoft.FancyZones.UITests.Utils
|
||||
{
|
||||
public class ZoneSwitchHelper
|
||||
{
|
||||
public static string? GetZoneIndexSetByAppName(string exeName, string json)
|
||||
{
|
||||
if (string.IsNullOrEmpty(exeName) || string.IsNullOrEmpty(json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var historyArray = doc.RootElement.GetProperty("app-zone-history");
|
||||
|
||||
foreach (var item in historyArray.EnumerateArray())
|
||||
{
|
||||
if (item.TryGetProperty("app-path", out var appPathElement) &&
|
||||
appPathElement.GetString() is string path &&
|
||||
path.EndsWith(exeName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var history = item.GetProperty("history");
|
||||
if (history.GetArrayLength() > 0)
|
||||
{
|
||||
return history[0].GetProperty("zone-index-set")[0].GetRawText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new InvalidOperationException("JSON parse error: " + ex.Message, ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
public static string GetActiveWindowTitle()
|
||||
{
|
||||
const int nChars = 256;
|
||||
StringBuilder buff = new StringBuilder(nChars);
|
||||
IntPtr handle = GetForegroundWindow();
|
||||
|
||||
if (GetWindowText(handle, buff, nChars) > 0)
|
||||
{
|
||||
return buff.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle the error if needed
|
||||
throw new InvalidOperationException("Failed to get window title.");
|
||||
}
|
||||
}
|
||||
|
||||
public static (int Dx, int Dy) GetOffset(Element element, int targetX, int targetY)
|
||||
{
|
||||
Assert.IsNotNull(element.Rect, "element is null");
|
||||
var rect = element.Rect.Value;
|
||||
return (targetX - rect.X, targetY - rect.Y);
|
||||
}
|
||||
|
||||
public static (int X, int Y) GetScreenMargins((int Left, int Top, int Right, int Bottom) rect, int quantile = 4)
|
||||
{
|
||||
if (quantile == 0)
|
||||
{
|
||||
throw new ArgumentException("Quantile cannot be zero.", nameof(quantile));
|
||||
}
|
||||
|
||||
int x = (rect.Left + rect.Right) / quantile;
|
||||
int y = (rect.Top + rect.Bottom) / quantile;
|
||||
return (x, y);
|
||||
}
|
||||
}
|
||||
}
|
@@ -19,7 +19,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class ApplyLayoutTests : UITestBase
|
||||
{
|
||||
public ApplyLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
@@ -195,12 +196,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplyCustomLayout()
|
||||
{
|
||||
@@ -234,7 +229,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(Parameters.Monitors[0].MonitorNumber, data.AppliedLayouts[0].Device.MonitorNumber);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.ApplyLayoutsOnEachMonitor")]
|
||||
[TestCategory("FancyZones Editor #10")]
|
||||
public void ApplyLayoutsOnEachMonitor()
|
||||
{
|
||||
// apply the layout on the first monitor
|
||||
@@ -261,7 +257,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(secondLayout.Uuid, data.AppliedLayouts.Find(x => x.Device.MonitorNumber == 2).AppliedLayout.Uuid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.ApplyTemplateWithDifferentParametersOnEachMonitor")]
|
||||
[TestCategory("FancyZones Editor #10")]
|
||||
public void ApplyTemplateWithDifferentParametersOnEachMonitor()
|
||||
{
|
||||
var layoutType = LayoutType.Columns;
|
||||
@@ -270,10 +267,10 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
// apply the layout on the first monitor, set parameters
|
||||
Session.Find<Element>(layoutName).Click();
|
||||
Session.Find<Element>(layoutName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
var expectedFirstLayoutZoneCount = int.Parse(slider.Text!, CultureInfo.InvariantCulture);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
@@ -281,16 +278,16 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Session.Find<Element>(PowerToys.UITest.By.AccessibilityId("Monitors")).Find<Element>("Monitor 2").Click();
|
||||
Session.Find<Element>(layoutName).Click();
|
||||
Session.Find<Element>(layoutName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Left);
|
||||
slider.SendKeys(Key.Left);
|
||||
var expectedSecondLayoutZoneCount = int.Parse(slider.Text!, CultureInfo.InvariantCulture);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// verify the layout on the first monitor wasn't changed
|
||||
Session.Find<Element>(PowerToys.UITest.By.AccessibilityId("Monitors")).Find<Element>("Monitor 1").Click();
|
||||
Session.Find<Element>(layoutName).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
Assert.AreEqual(expectedFirstLayoutZoneCount, int.Parse(slider.Text!, CultureInfo.InvariantCulture));
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
@@ -17,7 +17,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class CopyLayoutTests : UITestBase
|
||||
{
|
||||
public CopyLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -172,13 +173,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyTemplate_FromEditLayoutWindow")]
|
||||
[TestCategory("FancyZones Editor #4")]
|
||||
public void CopyTemplate_FromEditLayoutWindow()
|
||||
{
|
||||
string copiedLayoutName = TestConstants.TemplateLayoutNames[LayoutType.Focus] + " (1)";
|
||||
@@ -195,7 +191,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyTemplate_FromEditLayoutWindow")]
|
||||
[TestCategory("FancyZones Editor #4")]
|
||||
public void CopyTemplate_FromContextMenu()
|
||||
{
|
||||
string copiedLayoutName = TestConstants.TemplateLayoutNames[LayoutType.Rows] + " (1)";
|
||||
@@ -211,7 +208,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyTemplate_DefaultLayout")]
|
||||
[TestCategory("FancyZones Editor #13")]
|
||||
public void CopyTemplate_DefaultLayout()
|
||||
{
|
||||
string copiedLayoutName = TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid] + " (1)";
|
||||
@@ -243,7 +241,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(defaultLayouts.Serialize(DefaultLayouts), defaultLayouts.Serialize(defaultLayoutData));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyCustomLayout_FromEditLayoutWindow")]
|
||||
[TestCategory("FancyZones Editor #4")]
|
||||
public void CopyCustomLayout_FromEditLayoutWindow()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
@@ -260,7 +259,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyCustomLayout_FromContextMenu")]
|
||||
[TestCategory("FancyZones Editor #4")]
|
||||
public void CopyCustomLayout_FromContextMenu()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
@@ -276,7 +276,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == copiedLayoutName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyCustomLayout_DefaultLayout")]
|
||||
[TestCategory("FancyZones Editor #13")]
|
||||
public void CopyCustomLayout_DefaultLayout()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
@@ -308,7 +309,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(defaultLayouts.Serialize(DefaultLayouts), defaultLayouts.Serialize(defaultLayoutData));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CopyCustomLayout_Hotkey")]
|
||||
[TestCategory("FancyZones Editor #4")]
|
||||
public void CopyCustomLayout_Hotkey()
|
||||
{
|
||||
string copiedLayoutName = CustomLayouts.CustomLayouts[0].Name + " (1)";
|
||||
|
@@ -16,13 +16,15 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class CreateLayoutTests : UITestBase
|
||||
{
|
||||
public CreateLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
|
||||
// prepare test editor parameters with 2 monitors before launching the editor
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
EditorParameters.ParamsWrapper parameters = new EditorParameters.ParamsWrapper
|
||||
@@ -132,12 +134,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateWithDefaultName()
|
||||
{
|
||||
@@ -156,7 +152,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == name));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CreateWithCustomName")]
|
||||
[TestCategory("FancyZones Editor #3")]
|
||||
public void CreateWithCustomName()
|
||||
{
|
||||
string name = "Layout Name";
|
||||
@@ -177,7 +174,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == name));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CreateGrid")]
|
||||
[TestCategory("FancyZones Editor #3")]
|
||||
public void CreateGrid()
|
||||
{
|
||||
CustomLayout type = CustomLayout.Grid;
|
||||
@@ -193,7 +191,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Type == type.TypeToString()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CreateCanvas")]
|
||||
[TestCategory("FancyZones Editor #3")]
|
||||
public void CreateCanvas()
|
||||
{
|
||||
CustomLayout type = CustomLayout.Canvas;
|
||||
@@ -209,7 +208,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Type == type.TypeToString()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CancelGridCreation")]
|
||||
[TestCategory("FancyZones Editor #3")]
|
||||
public void CancelGridCreation()
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
@@ -223,7 +223,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(0, data.CustomLayouts.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CancelCanvasCreation")]
|
||||
[TestCategory("FancyZones Editor #3")]
|
||||
public void CancelCanvasCreation()
|
||||
{
|
||||
Session.Find<Element>(By.AccessibilityId(AccessibilityId.NewLayoutButton)).Click();
|
||||
|
@@ -20,7 +20,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class CustomLayoutsTests : UITestBase
|
||||
{
|
||||
public CustomLayoutsTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
CustomLayouts customLayouts = new CustomLayouts();
|
||||
FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.WriteData(customLayouts.Serialize(Layouts));
|
||||
|
||||
@@ -208,12 +209,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Name_Initialize()
|
||||
{
|
||||
@@ -292,14 +287,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var type = layout.Type;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
|
||||
var value = type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(layout.Info.GetRawText()).SensitivityRadius :
|
||||
new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).SensitivityRadius;
|
||||
var expected = value + 1; // one step right
|
||||
var expected = value; // if have one step right please + 1
|
||||
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
@@ -321,9 +316,9 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var type = layout.Type;
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
|
||||
var expected = type == CustomLayout.Canvas.TypeToString() ?
|
||||
new CustomLayouts().CanvasFromJsonElement(layout.Info.GetRawText()).SensitivityRadius :
|
||||
@@ -373,15 +368,15 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public void SpaceAroundZones_Slider_Save()
|
||||
{
|
||||
var layout = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString() && new CustomLayouts().GridFromJsonElement(x.Info.GetRawText()).ShowSpacing);
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).Spacing + 1; // one step right
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).Spacing; // one step right
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.PrimaryButton)).Click();
|
||||
|
||||
// verify the file
|
||||
var customLayouts = new CustomLayouts();
|
||||
@@ -397,9 +392,9 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Session.Find<Element>(layout.Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
var expected = new CustomLayouts().GridFromJsonElement(layout.Info.GetRawText()).Spacing;
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
|
@@ -20,7 +20,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class DefaultLayoutsTest : UITestBase
|
||||
{
|
||||
public DefaultLayoutsTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
var defaultLayouts = new DefaultLayouts();
|
||||
FancyZonesEditorHelper.Files.DefaultLayoutsIOHelper.WriteData(defaultLayouts.Serialize(Layouts));
|
||||
|
||||
@@ -237,20 +238,16 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Default_Initialize")]
|
||||
[TestCategory("FancyZones Editor #12")]
|
||||
public void Initialize()
|
||||
{
|
||||
CheckTemplateLayouts(LayoutType.Grid, null);
|
||||
CheckCustomLayouts(string.Empty, CustomLayouts.CustomLayouts[0].Uuid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Default_Assign_Cancel")]
|
||||
[TestCategory("FancyZones Editor #12")]
|
||||
public void Assign_Cancel()
|
||||
{
|
||||
// assign Focus as a default horizontal and vertical layout
|
||||
@@ -266,7 +263,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
CheckCustomLayouts(string.Empty, CustomLayouts.CustomLayouts[0].Uuid);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Default_Assign_Save")]
|
||||
[TestCategory("FancyZones Editor #12")]
|
||||
public void Assign_Save()
|
||||
{
|
||||
// assign Focus as a default horizontal and vertical layout
|
||||
|
@@ -22,7 +22,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class DeleteLayoutTests : UITestBase
|
||||
{
|
||||
public DeleteLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
FancyZonesEditorHelper.Files.ParamsIOHelper.WriteData(editorParameters.Serialize(Parameters));
|
||||
|
||||
@@ -215,19 +216,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Click();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.DeleteNotAppliedLayout")]
|
||||
[TestCategory("FancyZones Editor #5")]
|
||||
public void DeleteNotAppliedLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
@@ -239,13 +235,15 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsFalse(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.DeleteAppliedLayout")]
|
||||
[TestCategory("FancyZones Editor #5")]
|
||||
public void DeleteAppliedLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[0].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
@@ -264,13 +262,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(LayoutType.Blank.TypeToString(), appliedLayoutsData.AppliedLayouts.Find(x => x.Device.Monitor == Parameters.Monitors[0].Monitor).AppliedLayout.Type);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.CancelDeletion")]
|
||||
[TestCategory("FancyZones Editor #5")]
|
||||
public void CancelDeletion()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
Session.Find<Element>(deletedLayout).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
Session.Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.DeleteLayoutButton)).Click();
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is not removed
|
||||
Assert.IsNotNull(Session.Find<Element>(deletedLayout));
|
||||
@@ -282,12 +281,13 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.DeleteFromContextMenu")]
|
||||
[TestCategory("FancyZones Editor #5")]
|
||||
public void DeleteFromContextMenu()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the layout is removed
|
||||
Assert.IsTrue(Session.FindAll<Element>(deletedLayout).Count == 0);
|
||||
@@ -299,12 +299,13 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsFalse(data.CustomLayouts.Exists(x => x.Name == deletedLayout));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.DeleteDefaultLayout")]
|
||||
[TestCategory("FancyZones Editor #5")]
|
||||
public void DeleteDefaultLayout()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the default layout is reset to the "default" default
|
||||
Session.Find<Element>(TestConstants.TemplateLayoutNames[LayoutType.PriorityGrid]).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
@@ -318,12 +319,13 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(LayoutType.PriorityGrid.TypeToString(), data.DefaultLayouts.Find(x => x.MonitorConfiguration == configuration).Layout.Type);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.DeleteLayoutWithHotkey")]
|
||||
[TestCategory("FancyZones Editor #5")]
|
||||
public void DeleteLayoutWithHotkey()
|
||||
{
|
||||
var deletedLayout = CustomLayouts.CustomLayouts[1].Name;
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, deletedLayout, FancyZonesEditorHelper.ElementName.Delete);
|
||||
Session.KeyboardAction(Keys.Tab, Keys.Enter);
|
||||
Session.SendKeySequence(Key.Tab, Key.Enter);
|
||||
|
||||
// verify the hotkey is available
|
||||
Session.Find<Element>(CustomLayouts.CustomLayouts[0].Name).Find<Button>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
|
@@ -21,7 +21,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class EditLayoutTests : UITestBase
|
||||
{
|
||||
public EditLayoutTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -209,13 +210,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.OpenEditMode")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void OpenEditMode()
|
||||
{
|
||||
Session.Find<Element>(Layouts.CustomLayouts[0].Name).Find<Button>(By.AccessibilityId(AccessibilityId.EditLayoutButton)).Click();
|
||||
@@ -224,7 +220,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.OpenEditModeFromContextMenu")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void OpenEditModeFromContextMenu()
|
||||
{
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, Layouts.CustomLayouts[0].Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
@@ -232,7 +229,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_AddZone_Save")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_AddZone_Save()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
@@ -248,7 +246,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected.Zones.Count + 1, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_AddZone_Cancel")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_AddZone_Cancel()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
@@ -264,7 +263,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected.Zones.Count, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_DeleteZone_Save")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_DeleteZone_Save()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
@@ -280,7 +280,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected.Zones.Count - 1, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_DeleteZone_Cancel")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_DeleteZone_Cancel()
|
||||
{
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
@@ -296,7 +297,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected.Zones.Count, actual.Zones.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_MoveZone_Save")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_MoveZone_Save()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
@@ -333,7 +335,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_MoveZone_Cancel")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_MoveZone_Cancel()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
@@ -357,7 +360,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_ResizeZone_Save")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_ResizeZone_Save()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
@@ -366,7 +370,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.TopRightCorner)).Drag(xOffset, yOffset);
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Find<Thumb>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.TopRightCorner)).Drag(xOffset, yOffset);
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
|
||||
// check the file
|
||||
@@ -394,7 +398,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Canvas_ResizeZone_Cancel")]
|
||||
[TestCategory("FancyZones Editor #7")]
|
||||
public void Canvas_ResizeZone_Cancel()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
@@ -403,7 +408,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var canvas = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Canvas.TypeToString());
|
||||
FancyZonesEditorHelper.ClickContextMenuItem(Session, canvas.Name, FancyZonesEditorHelper.ElementName.EditZones);
|
||||
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Find<Element>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.TopRightCorner)).Drag(xOffset, yOffset);
|
||||
FancyZonesEditorHelper.GetZone(Session, zoneNumber, FancyZonesEditorHelper.ClassName.CanvasZone)?.Find<Thumb>(By.AccessibilityId(FancyZonesEditorHelper.AccessibilityId.TopRightCorner)).Drag(xOffset, yOffset);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// check the file
|
||||
@@ -421,7 +426,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Grid_SplitZone_Save")]
|
||||
[TestCategory("FancyZones Editor #8")]
|
||||
public void Grid_SplitZone_Save()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
@@ -450,7 +456,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Grid_SplitZone_Cancel")]
|
||||
[TestCategory("FancyZones Editor #8")]
|
||||
public void Grid_SplitZone_Cancel()
|
||||
{
|
||||
int zoneNumber = 1;
|
||||
@@ -481,7 +488,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Grid_MergeZones_Save")]
|
||||
[TestCategory("FancyZones Editor #8")]
|
||||
public void Grid_MergeZones_Save()
|
||||
{
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
@@ -515,7 +523,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.IsTrue(actual.CellChildMap[1].SequenceEqual([1, 2]));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Grid_MergeZones_Cancel")]
|
||||
[TestCategory("FancyZones Editor #8")]
|
||||
public void Grid_MergeZones_Cancel()
|
||||
{
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString());
|
||||
@@ -551,7 +560,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Grid_MoveSplitter_Save")]
|
||||
[TestCategory("FancyZones Editor #8")]
|
||||
public void Grid_MoveSplitter_Save()
|
||||
{
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
@@ -614,7 +624,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.Grid_MoveSplitter_Cancel")]
|
||||
[TestCategory("FancyZones Editor #8")]
|
||||
public void Grid_MoveSplitter_Cancel()
|
||||
{
|
||||
var grid = Layouts.CustomLayouts.Find(x => x.Type == CustomLayout.Grid.TypeToString() && x.Name == "Grid-9");
|
||||
|
@@ -25,13 +25,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class FirstLunchTest : UITestBase
|
||||
{
|
||||
public FirstLunchTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -141,12 +142,6 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FirstLaunch()
|
||||
{
|
||||
|
@@ -23,7 +23,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class LayoutHotkeysTests : UITestBase
|
||||
{
|
||||
public LayoutHotkeysTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -206,13 +207,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HotKey_Initialize")]
|
||||
[TestCategory("FancyZones Editor #11")]
|
||||
public void Initialize()
|
||||
{
|
||||
foreach (var layout in CustomLayouts.CustomLayouts)
|
||||
@@ -256,7 +252,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HotKey_Assign_Save")]
|
||||
[TestCategory("FancyZones Editor #11")]
|
||||
public void Assign_Save()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[2]; // a layout without assigned hotkey
|
||||
@@ -299,7 +296,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HotKey_Assign_Cancel")]
|
||||
[TestCategory("FancyZones Editor #11")]
|
||||
public void Assign_Cancel()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[2]; // a layout without assigned hotkey
|
||||
@@ -338,7 +336,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HotKey_Assign_AllPossibleValues")]
|
||||
[TestCategory("FancyZones Editor #11")]
|
||||
public void Assign_AllPossibleValues()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
@@ -384,7 +383,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HotKey_Reset_Save")]
|
||||
[TestCategory("FancyZones Editor #11")]
|
||||
public void Reset_Save()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[0]; // a layout with assigned hotkey
|
||||
@@ -424,7 +424,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HotKey_Reset_Cancel")]
|
||||
[TestCategory("FancyZones Editor #11")]
|
||||
public void Reset_Cancel()
|
||||
{
|
||||
var layout = CustomLayouts.CustomLayouts[0]; // a layout with assigned hotkey
|
||||
|
@@ -26,7 +26,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class TestCaseFirstLaunch : UITestBase
|
||||
{
|
||||
public TestCaseFirstLaunch()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -18,12 +18,12 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class RunFancyZonesEditorTest : UITestBase
|
||||
{
|
||||
public RunFancyZonesEditorTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[ClassInitialize]
|
||||
public static void ClassInitialize(TestContext testContext)
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
|
||||
@@ -163,12 +163,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
AppliedLayouts = new List<AppliedLayouts.AppliedLayoutWrapper> { },
|
||||
};
|
||||
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
|
||||
}
|
||||
|
||||
[ClassCleanup]
|
||||
public static void ClassCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@@ -73,13 +73,14 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
};
|
||||
|
||||
public TemplateLayoutsTests()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
@@ -191,13 +192,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
this.RestartScopeExe();
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.ZoneNumber_Cancel")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void ZoneNumber_Cancel()
|
||||
{
|
||||
var type = LayoutType.Rows;
|
||||
@@ -205,9 +201,9 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var expected = layout.ZoneCount;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.TemplateZoneSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Left);
|
||||
slider.SendKeys(Key.Left);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
@@ -218,7 +214,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HighlightDistance_Initialize")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void HighlightDistance_Initialize()
|
||||
{
|
||||
foreach (var (type, name) in TestConstants.TemplateLayoutNames)
|
||||
@@ -239,7 +236,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HighlightDistance_Save")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void HighlightDistance_Save()
|
||||
{
|
||||
var type = LayoutType.Focus;
|
||||
@@ -247,11 +245,11 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var value = layout.SensitivityRadius;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
|
||||
var expected = value + 1; // one step right
|
||||
var expected = value; // one step right
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
@@ -263,7 +261,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.HighlightDistance_Cancel")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void HighlightDistance_Cancel()
|
||||
{
|
||||
var type = LayoutType.Focus;
|
||||
@@ -271,9 +270,9 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var expected = layout.SensitivityRadius;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SensitivitySlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
// verify the file
|
||||
@@ -283,7 +282,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.SpaceAroundZones_Initialize")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void SpaceAroundZones_Initialize()
|
||||
{
|
||||
foreach (var (type, name) in TestConstants.TemplateLayoutNames)
|
||||
@@ -309,17 +309,18 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.SpaceAroundZones_Slider_Save")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void SpaceAroundZones_Slider_Save()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
var layout = Layouts.LayoutTemplates.Find(x => x.Type == type.TypeToString());
|
||||
var expected = layout.Spacing + 1;
|
||||
var expected = layout.Spacing;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
slider.SendKeys(Key.Right);
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Save).Click();
|
||||
@@ -331,7 +332,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.SpaceAroundZones_Slider_Cancel")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void SpaceAroundZones_Slider_Cancel()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
@@ -339,10 +341,10 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
var expected = layout.Spacing;
|
||||
Session.Find<Button>(TestConstants.TemplateLayoutNames[type]).Click();
|
||||
|
||||
var slider = Session.Find<Element>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
var slider = Session.Find<Custom>(PowerToys.UITest.By.AccessibilityId(AccessibilityId.SpacingSlider));
|
||||
Assert.IsNotNull(slider);
|
||||
slider.SendKeys(Keys.Right);
|
||||
Assert.AreEqual($"{expected + 1}", slider.Text);
|
||||
slider.SendKeys(Key.Right);
|
||||
Assert.AreEqual($"{expected}", slider.Text);
|
||||
|
||||
Session.Find<Button>(ElementName.Cancel).Click();
|
||||
|
||||
@@ -353,7 +355,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.SpaceAroundZones_Toggle_Save")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void SpaceAroundZones_Toggle_Save()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
@@ -376,7 +379,8 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.SpaceAroundZones_Toggle_Cancel")]
|
||||
[TestCategory("FancyZones Editor #6")]
|
||||
public void SpaceAroundZones_Toggle_Cancel()
|
||||
{
|
||||
var type = LayoutType.PriorityGrid;
|
||||
|
@@ -23,17 +23,12 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
public class UIInitializeTest : UITestBase
|
||||
{
|
||||
public UIInitializeTest()
|
||||
: base(PowerToysModule.FancyZone)
|
||||
: base(PowerToysModule.FancyZone, WindowSize.UnSpecified)
|
||||
{
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void TestCleanup()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod("FancyZonesEditor.Basic.EditorParams_VerifySelectedMonitor")]
|
||||
[TestCategory("FancyZones Editor #10")]
|
||||
public void EditorParams_VerifySelectedMonitor()
|
||||
{
|
||||
InitFileData();
|
||||
@@ -737,6 +732,7 @@ namespace Microsoft.FancyZonesEditor.UITests
|
||||
|
||||
private void InitFileData()
|
||||
{
|
||||
FancyZonesEditorHelper.Files.Restore();
|
||||
EditorParameters editorParameters = new EditorParameters();
|
||||
ParamsWrapper parameters = new ParamsWrapper
|
||||
{
|
||||
|
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using static FancyZonesEditorCommon.Data.AppZoneHistory.AppZoneHistoryWrapper;
|
||||
using static FancyZonesEditorCommon.Data.CustomLayouts;
|
||||
|
||||
namespace FancyZonesEditorCommon.Data
|
||||
{
|
||||
public class AppZoneHistory : EditorData<AppZoneHistory.AppZoneHistoryListWrapper>
|
||||
{
|
||||
public string File
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetDataFolder() + "\\Microsoft\\PowerToys\\FancyZones\\app-zone-history.json";
|
||||
}
|
||||
}
|
||||
|
||||
public struct AppZoneHistoryWrapper
|
||||
{
|
||||
public struct ZoneHistoryWrapper
|
||||
{
|
||||
public int[][] ZoneIndexSet { get; set; }
|
||||
|
||||
public DeviceIdWrapper Device { get; set; }
|
||||
|
||||
public string ZonesetUuid { get; set; }
|
||||
}
|
||||
|
||||
public struct DeviceIdWrapper
|
||||
{
|
||||
public string Monitor { get; set; }
|
||||
|
||||
public string MonitorInstance { get; set; }
|
||||
|
||||
public int MonitorNumber { get; set; }
|
||||
|
||||
public string SerialNumber { get; set; }
|
||||
|
||||
public string VirtualDesktop { get; set; }
|
||||
}
|
||||
|
||||
public string AppPath { get; set; }
|
||||
|
||||
public List<ZoneHistoryWrapper> History { get; set; }
|
||||
}
|
||||
|
||||
public struct AppZoneHistoryListWrapper
|
||||
{
|
||||
public List<AppZoneHistoryWrapper> AppZoneHistory { get; set; }
|
||||
}
|
||||
|
||||
public JsonElement ToJsonElement(ZoneHistoryWrapper info)
|
||||
{
|
||||
string json = JsonSerializer.Serialize(info, this.JsonOptions);
|
||||
return JsonSerializer.Deserialize<JsonElement>(json);
|
||||
}
|
||||
|
||||
public JsonElement ToJsonElement(DeviceIdWrapper info)
|
||||
{
|
||||
string json = JsonSerializer.Serialize(info, this.JsonOptions);
|
||||
return JsonSerializer.Deserialize<JsonElement>(json);
|
||||
}
|
||||
|
||||
public ZoneHistoryWrapper ZoneHistoryFromJsonElement(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<ZoneHistoryWrapper>(json, this.JsonOptions);
|
||||
}
|
||||
|
||||
public DeviceIdWrapper GridFromJsonElement(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<DeviceIdWrapper>(json, this.JsonOptions);
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,6 +20,8 @@ namespace Microsoft.FancyZonesEditor.UITests.Utils
|
||||
|
||||
public IOTestHelper LayoutTemplatesIOHelper { get; }
|
||||
|
||||
public IOTestHelper AppZoneHistoryIOHelper { get; }
|
||||
|
||||
public FancyZonesEditorFiles()
|
||||
{
|
||||
ParamsIOHelper = new IOTestHelper(new EditorParameters().File);
|
||||
@@ -28,6 +30,7 @@ namespace Microsoft.FancyZonesEditor.UITests.Utils
|
||||
DefaultLayoutsIOHelper = new IOTestHelper(new DefaultLayouts().File);
|
||||
LayoutHotkeysIOHelper = new IOTestHelper(new LayoutHotkeys().File);
|
||||
LayoutTemplatesIOHelper = new IOTestHelper(new LayoutTemplates().File);
|
||||
AppZoneHistoryIOHelper = new IOTestHelper(new AppZoneHistory().File);
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
@@ -38,6 +41,7 @@ namespace Microsoft.FancyZonesEditor.UITests.Utils
|
||||
DefaultLayoutsIOHelper.RestoreData();
|
||||
LayoutHotkeysIOHelper.RestoreData();
|
||||
LayoutTemplatesIOHelper.RestoreData();
|
||||
AppZoneHistoryIOHelper.RestoreData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -117,9 +117,9 @@ namespace Microsoft.FancyZonesEditor.UnitTests.Utils
|
||||
session.Find<Element>(By.ClassName(ClassName.ContextMenu)).Find<Element>(menuItem).Click();
|
||||
}
|
||||
|
||||
public static Element? GetZone(Session session, int zoneNumber, string zoneClassName)
|
||||
public static Custom? GetZone(Session session, int zoneNumber, string zoneClassName)
|
||||
{
|
||||
var zones = session.FindAll<Element>(By.ClassName(zoneClassName));
|
||||
var zones = session.FindAll<Custom>(By.ClassName(zoneClassName));
|
||||
foreach (var zone in zones)
|
||||
{
|
||||
try
|
||||
@@ -157,7 +157,7 @@ namespace Microsoft.FancyZonesEditor.UnitTests.Utils
|
||||
|
||||
public static void MoveSplitter(Session session, int index, int xOffset, int yOffset)
|
||||
{
|
||||
var thumbs = session.FindAll<Element>(By.ClassName(ClassName.Thumb));
|
||||
var thumbs = session.FindAll<Thumb>(By.ClassName(ClassName.Thumb));
|
||||
if (thumbs.Count == 0 || index >= thumbs.Count)
|
||||
{
|
||||
return;
|
||||
|
@@ -1,138 +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.IO;
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.FancyZonesEditor.UITests.Utils;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
|
||||
namespace Microsoft.FancyZonesEditor.UnitTests.Utils
|
||||
{
|
||||
public class FancyZonesEditorSession
|
||||
{
|
||||
protected const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723";
|
||||
private const string FancyZonesEditorPath = @"\..\..\..\PowerToys.FancyZonesEditor.exe";
|
||||
|
||||
private static FancyZonesEditorFiles? _files;
|
||||
|
||||
public static FancyZonesEditorFiles Files
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_files == null)
|
||||
{
|
||||
_files = new FancyZonesEditorFiles();
|
||||
}
|
||||
|
||||
return _files;
|
||||
}
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement>? Session { get; }
|
||||
|
||||
public WindowsElement? MainEditorWindow { get; }
|
||||
|
||||
public FancyZonesEditorSession(TestContext testContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Launch FancyZonesEditor
|
||||
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
path += FancyZonesEditorPath;
|
||||
|
||||
AppiumOptions opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", path);
|
||||
Session = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), opts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testContext.WriteLine(ex.Message);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(Session, "Session not initialized");
|
||||
|
||||
testContext.WriteLine("Session: " + Session.SessionId.ToString());
|
||||
testContext.WriteLine("Title: " + Session.Title);
|
||||
|
||||
// Set implicit timeout to make element search to retry every 500 ms
|
||||
Session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
|
||||
|
||||
// Find main editor window
|
||||
try
|
||||
{
|
||||
MainEditorWindow = Session.FindElementByAccessibilityId("MainWindow1");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Assert.IsNotNull(MainEditorWindow, "Main editor window not found");
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(TestContext testContext)
|
||||
{
|
||||
// Close the session
|
||||
if (Session != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// FZEditor application can be closed by explicitly closing main editor window
|
||||
MainEditorWindow?.SendKeys(Keys.Alt + Keys.F4);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
testContext.WriteLine(ex.Message);
|
||||
}
|
||||
|
||||
Session.Quit();
|
||||
Session.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private WindowsElement? GetLayout(string layoutName)
|
||||
{
|
||||
var listItem = Session?.FindElementByName(layoutName);
|
||||
Assert.IsNotNull(listItem, "Layout " + layoutName + " not found");
|
||||
return listItem;
|
||||
}
|
||||
|
||||
public WindowsElement? OpenContextMenu(string layoutName)
|
||||
{
|
||||
RightClick_Layout(layoutName);
|
||||
var menu = Session?.FindElementByClassName("ContextMenu");
|
||||
Assert.IsNotNull(menu, "Context menu not found");
|
||||
return menu;
|
||||
}
|
||||
|
||||
public void Click_CreateNewLayout()
|
||||
{
|
||||
var button = Session?.FindElementByAccessibilityId("NewLayoutButton");
|
||||
Assert.IsNotNull(button, "Create new layout button not found");
|
||||
button?.Click();
|
||||
}
|
||||
|
||||
public void Click_EditLayout(string layoutName)
|
||||
{
|
||||
var layout = GetLayout(layoutName);
|
||||
var editButton = layout?.FindElementByAccessibilityId("EditLayoutButton");
|
||||
Assert.IsNotNull(editButton, "Edit button not found");
|
||||
editButton.Click();
|
||||
}
|
||||
|
||||
public void RightClick_Layout(string layoutName)
|
||||
{
|
||||
var layout = GetLayout(layoutName);
|
||||
Actions actions = new Actions(Session);
|
||||
actions.MoveToElement(layout);
|
||||
actions.MoveByOffset(30, 30);
|
||||
actions.ContextClick();
|
||||
actions.Build().Perform();
|
||||
}
|
||||
}
|
||||
}
|
@@ -70,6 +70,12 @@ namespace Microsoft.FancyZonesEditor.UITests.Utils
|
||||
}
|
||||
}
|
||||
|
||||
// For get app zone history data
|
||||
public string GetData()
|
||||
{
|
||||
return ReadFile(_file);
|
||||
}
|
||||
|
||||
private string ReadFile(string fileName)
|
||||
{
|
||||
var attempts = 0;
|
||||
|