Compare commits

..

170 Commits

Author SHA1 Message Date
Xiaofeng Wang (from Dev Box)
d421ea7cd9 Update retry interval 2025-06-06 11:15:08 +08:00
Xiaofeng Wang (from Dev Box)
b76b37fbff Add debug log 2025-06-06 11:05:19 +08:00
Zhaopeng Wang (from Dev Box)
7396ea0c47 delete change code 2025-06-06 05:22:51 +08:00
Zhaopeng Wang (from Dev Box)
c91ef00113 move attach to session.cs 2025-06-05 21:25:53 +08:00
Zhaopeng Wang (from Dev Box)
30e32a895a move attach to helper 2025-06-05 18:51:30 +08:00
Zhaopeng Wang (from Dev Box)
98ef617338 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-06-05 16:58:25 +08:00
Zhaopeng Wang (from Dev Box)
195e63393c add retry new driver 2025-06-05 16:58:13 +08:00
Xiaofeng Wang (from Dev Box)
ba4f8c4db8 Remove duplicate fancyzone editor runs 2025-06-05 16:10:36 +08:00
Zhaopeng Wang (from Dev Box)
1b8decc26f fix restart scope code 2025-06-05 13:24:31 +08:00
Zhaopeng Wang (from Dev Box)
bf533387e8 change setting size 2025-06-05 02:00:42 +08:00
Zhaopeng Wang (from Dev Box)
03aa5b9db8 delete quit 2025-06-05 01:58:55 +08:00
Zhaopeng Wang (from Dev Box)
c8ed1a4123 delete restart winapp code 2025-06-04 19:51:45 +08:00
Zhaopeng Wang (from Dev Box)
b8f2ba5695 add setting windows size 2025-06-04 16:16:00 +08:00
Zhaopeng Wang (from Dev Box)
27f27d6c39 change quit position 2025-06-04 12:34:27 +08:00
Zhaopeng Wang (from Dev Box)
ea84438a2b delete restart winappdriver 2025-06-04 01:01:26 +08:00
Zhaopeng Wang (from Dev Box)
39b75d6449 Merge branch 'main' into feature/UITestAutomation 2025-06-03 17:27:28 +08:00
Zhaopeng Wang (from Dev Box)
4d6f19a044 add driver quit 2025-06-03 15:57:19 +08:00
Zhaopeng Wang (from Dev Box)
b7199abab6 add restart windowAppDriver 2025-06-03 15:49:02 +08:00
Yu Leng
693ab67831 [cmdpal][AOT] make some built-in extensions able to be published with native AOT enabled (#39802)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Base on my test, some built-in extensions can be published with native
AOT enabled with a little changes. (see this branch:
https://github.com/microsoft/PowerToys/pull/39605).

1. SystemCommands: no change need. Removed some unused code.
2. WinGet: disable marshalling. and do a little changes in
CreateInstance.
3. WindowWalker: use source generation to call CoCreateInstance.
4. WindowsTerminal: use source generation to call
IApplicationActivationManager interface.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #39869
- [x] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2025-06-03 14:24:13 +08:00
Xiaofeng Wang (from Dev Box)
f52c507067 Only keep fancyzone editor in CI 2025-06-03 10:18:08 +08:00
Dustin L. Howett
c6a4ee1441 build: try even harder to find a working VC tools version (#39862)
I can't explain this, but VS 17.14 ships with VisualCpp.Tools.Core
version 14.44.35208 but the files say 14.44.35207.

See https://github.com/microsoft/terminal/pull/18996 for more info.
2025-06-02 15:47:56 -05:00
Xiaofeng Wang (from Dev Box)
d875088d1a Fix Host Editor Setting tests 2025-05-30 21:50:17 +08:00
mbartlett21
89c33fae68 [peek, explorer] Update QOI reader for 3-channel images (#39304)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Make sure we account for Bitmap row length being a multiple of 4 when
reading Qoi files.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #31948
- [ ] **Communication:** Not discussed, but a bug fix
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments



<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Built and run and restarted file explorer.
Sample files from my comment on the issue (after rewriting each one to
force the thumbnail to regenrate):

![image](https://github.com/user-attachments/assets/5874f958-0f03-4683-abe3-d54c979ad2a1)
2025-05-30 18:32:40 +08:00
Xiaofeng Wang (from Dev Box)
940165d9fd Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-05-30 14:27:54 +08:00
Xiaofeng Wang (from Dev Box)
e56d0a3bd1 Add try-catch in FindElements 2025-05-30 14:27:44 +08:00
Zhaopeng Wang (from Dev Box)
276bde8e3e disable one case 2025-05-30 14:20:50 +08:00
Yu Leng
c9922302e5 [PowerRename] Add ModificationTime and AccessTime support when renaming with $YY-$MM-$DD patterns (#39653)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
1. Add new configuration to control this behaviour. By default, set to
creation time to align with previous version
2. Add UI in PowerRename's MainWindow
3. Implement the logic


![image](https://github.com/user-attachments/assets/fde6731b-73f9-453f-8b68-6ce66589f44a)


Original discussion here:
https://github.com/microsoft/PowerToys/pull/38186

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #36040
- [x] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
2025-05-30 13:07:20 +08:00
Xiaofeng Wang (from Dev Box)
5feda6cc9d Timeout in findelement 2025-05-30 12:43:39 +08:00
Kai Tao
498ef676f4 Runner: Add log when error happen in setting startup (#39801)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Add log when error happen in setting startup 

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [X] **Closes:** #39798
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [X] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed


![image](https://github.com/user-attachments/assets/2208c65f-4d42-4612-a77e-d92a748fd723)

Set error in code deliberately, and verify that the error appears in the
log.
2025-05-30 10:03:36 +08:00
Zhaopeng Wang (from Dev Box)
ff9aab3003 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-05-29 19:18:54 +08:00
Zhaopeng Wang (from Dev Box)
cbc519ee1a add retry 2025-05-29 19:18:45 +08:00
zhw
3821e8427c [Localization] Add comments in Resources.resw (#39778)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR mainly add comments in
src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw to
solve issues raised by localization team.

---------

Co-authored-by: ArleneYu <yuzw@pku.edu.cn>
2025-05-29 18:09:05 +08:00
Xiaofeng Wang (from Dev Box)
86557f27fd Address comments 2025-05-29 17:07:33 +08:00
Xiaofeng Wang (from Dev Box)
220ddffb66 Remove unused code 2025-05-29 16:08:22 +08:00
Xiaofeng Wang (from Dev Box)
7e2e5f5d71 remove coverlist 2025-05-29 15:48:04 +08:00
Xiaofeng Wang (from Dev Box)
2eeaf6bc44 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-05-29 15:45:38 +08:00
Xiaofeng Wang (from Dev Box)
3eec619b98 Address comments 2025-05-29 15:45:23 +08:00
Zhaopeng Wang (from Dev Box)
2c68a29e20 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-05-29 15:22:05 +08:00
Zhaopeng Wang (from Dev Box)
1568d028b0 add retry 2025-05-29 15:21:54 +08:00
Mengyuan
8659ae12bf [UITestAutomation -> FancyZones] Remove Display Name of TestSwitchWindow (#39797)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
remove test method displayname to fix 'No test matches the given
testcase filter' error.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2025-05-29 13:15:28 +08:00
Royce Williams
964e5c9d5e cmdpal README: minor typo (#39764)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Minor typo it's -> its

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
README-only change

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
n/a
2025-05-28 20:13:05 +00:00
Michael Jolley
48d9d19df1 CmdPal: Styling critical context items using the SystemFillColorCriticalBrush (#39645)
Styles context items based on the IsCritical property.


![image](https://github.com/user-attachments/assets/aa1ee0b0-de09-45af-8f5e-74dff666fbb4)


![image](https://github.com/user-attachments/assets/90a7f750-c949-4f76-b699-db2e29251414)

Closes #38307
2025-05-28 14:35:26 -05:00
Kai Tao
d21b7fac7b Log: Clear mwb module interface old version log and clear dotnet old version log (#39652)
* Initial plan for issue

* Add cleanup of old version log folders for Mouse Without Borders

Co-authored-by: vanzue <69313318+vanzue@users.noreply.github.com>

* Add try-catch block to cleanup_old_logs function

Co-authored-by: vanzue <69313318+vanzue@users.noreply.github.com>

* Refactor Mouse Without Borders log cleanup to use common mechanism

Co-authored-by: vanzue <69313318+vanzue@users.noreply.github.com>

* Investigate .NET logger cleanup mechanism for old version logs

Co-authored-by: vanzue <69313318+vanzue@users.noreply.github.com>

* delete in other thread

* clear mwb previous folder

* slow down the deletion

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-05-28 18:53:35 +08:00
Zhaopeng Wang (from Dev Box)
39be3e11a6 hot fix 2025-05-28 16:36:13 +08:00
XiaofengWang
2cc3da5020 Merge branch 'main' into feature/UITestAutomation 2025-05-28 16:24:36 +08:00
XiaofengWang
6916c9fa5a Fix spell error (#39771)
* Fix spell error

* Fix spell error

* Fix spell errors

* Fix spell errors

---------

Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-05-28 15:56:30 +08:00
Xiaofeng Wang (from Dev Box)
d2f17ca848 Remove unnecessary code 2025-05-28 15:07:32 +08:00
Mengyuan
02a9269435 [Fuzz] Add Fuzz testing for FancyZones (#38165)
* Add FancyZones.FuzzTests Module for fuzzing grid from json element

* remove unuse test cs

* fix spell error

* remove specific modify in job-fuzz.yml

* add Testably.Abstractions.FileSystem.Interface.dll

* fix spell error

* add dependency dll

* add annotations

* fix pipeline error  need to import 'Common.Dotnet.CsWinRT.props'
2025-05-28 14:56:37 +08:00
PesBandi
f642720087 [CmdPal][Calc]Trim leading = to support pasting formulas (#39743)
* [CmdPal][Calc]Trim leading `=` to support pasting formulas

* Update README.md

* Update README.md
2025-05-28 14:25:07 +08:00
Xiaofeng Wang (from Dev Box)
5118454db3 Remove unnecessary code 2025-05-28 13:54:00 +08:00
zhaopeng wang
d5e3899472 Dev/zhaopengwang/UI test automation fancyzone (#39740)
* add test case and fix bug

* add test

* add test

* add test

* add test case

* add fancyzones window name and fix test case error

* add test case

* add test case

* add test case After creating a virtual desktop

* add test case delete layout

* add test case grid layout change monitor resolution

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error and only run fancyzone uitest

* disable other ui test code

* fix build error

* fix pipeline error

* fix pipeline error

* add restart scope exe

* enable other test code

* fix Spelling error

* add error log

* add global find

* Change Fancyzone SendKey function

* add Fancyzone Editor Test case message

* add test code

* drag code refine

* add windowHelper

* fix find element type error

* fix build error

* fix test error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* add attach time

* fix piprline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* add close app

* add pipeline code and only test fancyzones

* fix pipeline error

* change pipeline file

* change pipeline file

* change pipeline file

* add install donet windowsdesktop

* add delay

* change pipeline file

* change namespace

* fix pipeline run low time

* add delay

* fix pipeline error

* add delay

* fix error

* add find window tries

* fix pipeline error

* fix error

* change test code

* remove not use value

* merge feature/UITestAutomation

* fix pipeline error

* change code

* fix error

* delete try more time Attach

* add delay

* change sleep function

* change close app position

---------

Co-authored-by: Zhaopeng Wang (from Dev Box) <zhaopengwang@microsoft.com>
2025-05-28 13:27:41 +08:00
yaqingmi
8384219232 Fix pipeline errors in MouseUtils tests (#39755)
* fix pipeline errors

* Add annotations for IOUtil

---------

Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-05-28 12:26:51 +08:00
Mengyuan
8291e2e4e0 [UITestAutomation > Fancyzones] Add Retry logic for launching the FancyZones Editor (#39765)
* fancytest oneswitch test

* add GetActiveWindowTitle

* testcase1 add set layout

* change shortcut to win pgdn

* add hold key

* Snap several windows to one zone, verify switching works.

* pack snaptoonezone

* pack snaptoonezone

* Add Fancy Zone Switch tests

* Add Fancyzones switch zones

* remove code and change zoneuuid to zonesets

* fix spell error and remove files

* remove unuse keyup in elements

* fix spell error

* fix spell error

* remove to common api

* fix spell error

* fix pipeline fail

* add annotations on exe and process

* add dragging tests case

* add dragging tests case TestShowZonesOnDragDuringShift

* change get out window pixel color

* add non-primary mouse test cases

* add make drag window transparent

* clean test initialize code

* fix spell error

* fix make drag setting conflicts with shift and non primary mouse setting tests issue

* add annotations for testmethod and GetWindowRect

* fix spell error

* fix spell error

* change assert prompt

* fix testcase name not match

* set fancyzones tests only in pipeline

* fix pipeline error fancyzones

* add get highlight and  inactivate zone color from ui

* add window pre post

* add rect func

* remove keydown and drag

* FIX JSON GETSTRING BUG

* change scrolling times

* fix get out window

* add drag window

* make c file explorer window larger

* remove drag window

* one switch tests change explorer exe to hosts file exe

* set restart in drag

* recover switch tests

* fix scroll position error

* add clean

* clean host

* clean layouts file

* remove during drag case

* resolve conflicts

* fix get pixel color

* add delay when get pixel color

* add delay and make maximize the window

* change onezone to twozone

* add restart scope exe

* add annotations for OneZoneSwitchTests.cs

* clean logger and add annotations

* merge windows helper and pane element

* add test name and test category

* reduce delay time

* resolve conflicts and remove ununsed namespace

* fix spell error

* fix the restart position

* add clean desktop windows and remove unuse code

* clear open window

* only run fancyzones related dll

* add log on customlayouts data

* remove clean the window in switchwindow tests

* rename namespace

* set fancyzones only pipeline

* remove restore

* set layouts without restart

* remove restart

* add delay and retry

* add delay and retry

* fix relaunch error

* add delay time

* tests all uitests

* extend delay time

* close window in onezone switch

* clean console write

* fix conflicts

* test all ui tests

* retry launch

* fix spell error

* move release drag to pane element

* address cannot found custom layouts

* move restart to set customlayouts

* move restart to set customlayouts

* fix spell error

* move restart

* restore data

* add delay time

* test all ui tests

* add clean fancyzones editor window

* remove log

* change get active window Title

* change get active window Title

* align onezone switch tests initialize and try catch with dragwindow

* remove unuse code

* add appzone history deletefile
2025-05-28 10:38:38 +08:00
XiaofengWang
52d9822fac Add retry in attach (#39751)
Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-05-27 17:08:58 +08:00
Bradley Myers
53b989857b Option to toggle the system tray icon (#23220)
* Added option to toggle the system tray icon

At the moment, this hides the icon making the settings window inaccessible without first modifying the general `settings.json` file.

* Use IPC messages to manage the tray icon settings

* Fix launching second window binds to active settings process

* Added context menu option to hide tray icon

* Added Exit PT button to settings ui NavigationView.PaneFooter

* Moved DllImports to NativeMethods.cs

* Sentence case titles

* Fix whitespace

* Re-add exit icon to NavView

* Re-added toggle switch to new UI

* Fix build

* Fix build after merge main

* Fix the string to display

* add shut down buttons

* finish polish

* fix string

* Styling tweaks to titlebar and settingscards

* fix comment

* fix unit test

* fix ut

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: vanzue <vanzue@outlook.com>
Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2025-05-26 17:03:35 +08:00
Mengyuan
f65a4495b0 [UITestAutomation FancyZones] Add Environment Cleanup to FancyZones UI Tests (#39722)
* fancytest oneswitch test

* add non-primary mouse test cases

* add make drag window transparent

* clean test initialize code

* fix spell error

* fix make drag setting conflicts with shift and non primary mouse setting tests issue

* add annotations for testmethod and GetWindowRect

* fix spell error

* fix spell error

* change assert prompt

* fix testcase name not match

* set fancyzones tests only in pipeline

* fix pipeline error fancyzones

* add get highlight and  inactivate zone color from ui

* add window pre post

* add rect func

* remove keydown and drag

* FIX JSON GETSTRING BUG

* change scrolling times

* fix get out window

* add drag window

* make c file explorer window larger

* remove drag window

* one switch tests change explorer exe to hosts file exe

* set restart in drag

* recover switch tests

* fix scroll position error

* add clean

* clean host

* clean layouts file

* remove during drag case

* resolve conflicts

* fix get pixel color

* add delay when get pixel color

* add delay and make maximize the window

* change onezone to twozone

* add restart scope exe

* add annotations for OneZoneSwitchTests.cs

* clean logger and add annotations

* merge windows helper and pane element

* add test name and test category

* reduce delay time

* resolve conflicts and remove ununsed namespace

* fix spell error

* fix the restart position

* add clean desktop windows and remove unuse code

* clear open window

* only run fancyzones related dll

* add log on customlayouts data

* remove clean the window in switchwindow tests

* rename namespace

* set fancyzones only pipeline

* remove restore

* set layouts without restart

* remove restart

* add delay and retry

* add delay and retry

* fix relaunch error

* add delay time

* tests all uitests

* extend delay time

* close window in onezone switch

* clean console write

* fix conflicts

* test all ui tests

* retry launch

* fix spell error

* move release drag to pane element

* address cannot found custom layouts

* move restart to set customlayouts

* move restart to set customlayouts

* fix spell error

* move restart

* restore data

* add delay time

* test all ui tests

* add clean fancyzones editor window

* remove log
2025-05-26 13:58:42 +08:00
Yu Leng
718e725571 [cmdpal] Fix calculator error when pressing Space (#39679)
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-05-26 11:37:38 +08:00
yaqingmi
fd9641cdc5 Fix UITest pipeline errors for MouseUitls module (#39656)
* Adjust sleep time between actions

* Adjust sleep time between actions2

* Fix pipeline errors

* Adjust wait time between clear and send input

---------

Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-05-23 16:51:25 +08:00
Mengyuan
147c0d3ec5 [UITestAutomation > Fancyzones] Add UI tests on Dragging window settings (#39111)
* fancytest oneswitch test

* add GetActiveWindowTitle

* testcase1 add set layout

* change shortcut to win pgdn

* add hold key

* Snap several windows to one zone, verify switching works.

* pack snaptoonezone

* pack snaptoonezone

* Add Fancy Zone Switch tests

* Add Fancyzones switch zones

* remove code and change zoneuuid to zonesets

* fix spell error and remove files

* remove unuse keyup in elements

* fix spell error

* fix spell error

* remove to common api

* fix spell error

* fix pipeline fail

* add annotations on exe and process

* add dragging tests case

* add dragging tests case TestShowZonesOnDragDuringShift

* change get out window pixel color

* add non-primary mouse test cases

* add make drag window transparent

* clean test initialize code

* fix spell error

* fix make drag setting conflicts with shift and non primary mouse setting tests issue

* add annotations for testmethod and GetWindowRect

* fix spell error

* fix spell error

* change assert prompt

* fix testcase name not match

* set fancyzones tests only in pipeline

* fix pipeline error fancyzones

* add get highlight and  inactivate zone color from ui

* add window pre post

* add rect func

* remove keydown and drag

* FIX JSON GETSTRING BUG

* change scrolling times

* fix get out window

* add drag window

* make c file explorer window larger

* remove drag window

* one switch tests change explorer exe to hosts file exe

* set restart in drag

* recover switch tests

* fix scroll position error

* add clean

* clean host

* clean layouts file

* remove during drag case

* resolve conflicts

* fix get pixel color

* add delay when get pixel color

* add delay and make maximize the window

* change onezone to twozone

* add restart scope exe

* add annotations for OneZoneSwitchTests.cs

* clean logger and add annotations

* merge windows helper and pane element

* add test name and test category

* reduce delay time

* resolve conflicts and remove ununsed namespace

* fix spell error

* fix the restart position

* add clean desktop windows and remove unuse code

* clear open window

* only run fancyzones related dll

* add log on customlayouts data

* remove clean the window in switchwindow tests

* rename namespace

* set fancyzones only pipeline

* remove restore

* set layouts without restart

* remove restart

* add delay and retry

* add delay and retry

* fix relaunch error

* add delay time

* tests all uitests

* extend delay time

* close window in onezone switch

* clean console write

* fix conflicts

* test all ui tests

* retry launch

* fix spell error

* move release drag to pane element

* address cannot found custom layouts

* move restart to set customlayouts
2025-05-23 13:07:42 +08:00
Gordon Lam
a804bf86a4 Update to WinAppSDK 1.7.2 (#39592)
Update to WinAppSDK 1.7.2
2025-05-23 11:19:24 +08:00
XiaofengWang
e1316e6631 Dev/xiaofengwang/fix hosts error (#39630)
* Retry in startExe

* Update baseline image for x64-win11

* Update baseline image for x64-win11

* update job-test-project.yml

---------

Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-05-21 15:34:50 +08:00
yaqingmi
4d1dbea5d1 Update MouseUtils UI test doc (#39606)
* Update Testcase names

* Update MouseUtils Checklist information

Update MouseUtils Checklist information

* Update ui-automation-cover-list.md

---------

Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-05-21 15:18:28 +08:00
yaqingmi
46aa43cb69 Yaqingmi/dev/UI automation2 (#39561)
* Fix Windows Size set fail problem

* Fix pipeline errors

* Fix dragging block

* Update Release-Test-Checklist*.md

* Fix spelling errors

---------

Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-05-20 10:34:21 +08:00
Xiaofeng Wang (from Dev Box)
01359c4b0c Fix pipeline issues 2025-05-19 15:35:14 +08:00
Xiaofeng Wang (from Dev Box)
508b95cf5b update rerunFailedThreshold 2025-05-19 13:47:42 +08:00
Xiaofeng Wang (from Dev Box)
5946b98f87 Add retry times 2025-05-19 11:35:35 +08:00
Xiaofeng Wang (from Dev Box)
e61e5b6d83 Add retry logic for StartExe and update SendKeys in Element to protected 2025-05-16 17:00:38 +08:00
Xiaofeng Wang (from Dev Box)
67272ad073 Add wait time before startExe 2025-05-16 12:40:40 +08:00
zhaopeng wang
a79126373f Fix pipeline error and refine framework code (#39440)
* add test case and fix bug

* add test

* add test

* add test

* add test case

* add fancyzones window name and fix test case error

* add test case

* add test case

* add test case After creating a virtual desktop

* add test case delete layout

* add test case grid layout change monitor resolution

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error and only run fancyzone uitest

* disable other ui test code

* fix build error

* fix pipeline error

* fix pipeline error

* add restart scope exe

* enable other test code

* fix Spelling error

* add error log

* add global find

* Change Fancyzone SendKey function

* add Fancyzone Editor Test case message

* add test code

* drag code refine

* add windowHelper

* fix find element type error

* fix build error

* fix test error

* fix pipeline error

* fix pipeline error

* fix pipeline error

---------

Co-authored-by: Zhaopeng Wang (from Dev Box) <zhaopengwang@microsoft.com>
2025-05-15 17:42:48 +08:00
Xiaofeng Wang (from Dev Box)
3688995511 Update hosts visual file and add cleanup wait time 2025-05-15 16:55:08 +08:00
Xiaofeng Wang (from Dev Box)
255a11df85 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-05-15 14:56:54 +08:00
XiaofengWang
d713a3001d Merge main branch (#39409)
* empowering users to maximize OOBE to their heart desire (#37823)

empowering users to maximize to their heart desire

* [Bug report] - Auto fill bug report parameters. (#37991)

* [cmdpal] fix a broken link in README.md (#38714)

related issue: #38713

* [cmdpal] Add Open URL fallback command for WebSearch ext (#38685)

## Summary of the Pull Request
1. Add new fallback command for websearch

https://github.com/user-attachments/assets/39362d66-db59-42d4-b07c-7bfd60b2e420

## PR Checklist

- [x] **Closes:** #38497

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [CmdPal] Tray icon settings (#38672)

<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

Added a settings to enable/disable the system tray icon (enabled by default).
Adopter the term "system tray icon" for consistency with Windows 11 settings.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #38407

* Add Zhiwei as part of PowerToys! (#38744)

* Add Zhiwei as part of PowerToys!

* Fix the expect.txt for Zhiwei

* Fix the case problem on expect.txt zhiwei => Zhiwei

* [PowerToysRun][Docs] Add QuickNotes to Third-Party plugins (#38663)

* Add QuickNotes plugin to third-party Run plugins documentation

* chore: add ruslanlap to spelling allow-list

* chore: add ruslanlap to spelling allow-list

* chore: add ruslanlap to spelling allow-list

* Add ruslanlap to allowed names and remove from expected words list

* Add securityContext to configuration files (#38017)

For the winget DSC, Setting developer mode, installing Visual Studio 2022 & fetching and installing VS components all require elevation. Added securityContext: elevated for these resources. These configurations can now be invoked from user context, and will prompt for a single UAC to run resources that require elevation in a separate process.

* Change log extension from .txt => .log  (#33813)

* Change log extension

From .txt to .log

* Also add workspace logs

---------

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>

* [KeyboardManager] Fix modifier Key (Not right or left) stuck  (#37930)

* Fix ctr,alt,shift getting stuck

* more changes

* Update src/modules/keyboardmanager/common/Helpers.h

Co-authored-by: Hao Liu <liuhaobupt@163.com>

---------

Co-authored-by: Hao Liu <liuhaobupt@163.com>

* "ǔ" changed to "ŭ" in GetDefaultLetterKeyEPO (#37791)

* Fix Color Picker resource leak (#38122) (#38147)

* Fix Color Picker resource leak (#38122)

Added a using statement to properly dispose of the Graphics object created from the Bitmap. This fixes resource leak.

* Fix CI complain

* Update MouseInfoProvider.cs

fix whitespace

---------

Co-authored-by: Kai Tao <69313318+vanzue@users.noreply.github.com>

* Upgrading some of the Testing framework items (#38779)

* starting to get some of the baseline

* Update NOTICE.md

* Upgrading streamjson gets the others on same version of newtonsoft.json

* Update PowerToys.Settings.csproj

* Update NOTICE.md

* upgrading to adjust fix vulnerability (#38784)

* upgrading to adjust fix vulnerablitlity

* Update NOTICE.md

* Update NuGet packages in NOTICE.md

* [PowerToysRun][Docs] Add Weather and Pomodoro to Third-Party plugins (#38760)

* [PowerToysRun][Docs] Add Weather and Pomodoro to Third-Party plugins

* [Spell-check] Add ruslanlap to allowed names and update expect.txt

* Update expect.txt

* Update names.txt

* Update names.txt

* Update names.txt

* Update names.txt

* [ColorPicker]Only close on escape if focused (#37895)

* [cmdpal] Setting a new alias should remove the old one (#38193)

* [cmdpal] Fix alias update

* Fix the command palette extension alias update issue

* clean code

---------

Co-authored-by: vanzue <vanzue@outlook.com>

* Apply security best practices for GitHub Actions / Dependency  (#38552)

This update aligns with Microsoft's security guidelines by pinning all GitHub Action tags and Docker tags to their full-length commits. This practice ensures immutability and reduces the risk of supply chain attacks. Note that 1st and 2nd party actions do not require hash pinning.

* Rename the `[Ee]xts` dir to `ext` (#38852)

**WARNING:** This PR will probably blow up all in-flight PRs

at some point in the early days of CmdPal, two of us created seperate
`Exts` and `exts` dirs. Depending on what the casing was on the branch
that you checked one of those out from, it'd get stuck like that on your
PC forever.

Windows didn't care, so we never noticed.

But GitHub does care, and now browsing the source on GitHub is basically
impossible.

Closes #38081

* Upgrading Boost dependencies (#38782)

Upgrading Boost

* upgrading toolkit + suppressing warnings (#38746)

* upgrade toolkit to latest

* supressing warnings

* Update expect.txt

making generic

* Update suppression comments for AOT compatibility

* Fix case for 'MVVMTK' in spell-check file

* Update NOTICE.md

* [CNF] Only enable experimental features if they exist (#37690)

* [PowerRename] Add 12-hour time format patterns with AM/PM support (#30703) (#38723)

* [PowerRename][Feature] Add new date/time formatting patterns to GetDatedFileName

* [PowerRename][UI] Add date/time shortcut patterns to cheat sheet

* [PowerRename][Tests] Add tests for new date/time formatting patterns

* [PowerRename] [Refactor] Simplify AM/PM string handling in time patterns

* [cmdpal] Fix empty file name issue when create new ext in "Create New Extension" command. (#38864)

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [QuickAccent] Fix on-screen keyboard activation (#37581)

* fix on-screen keyboard activation

* cleanup

* CmdPal: Add low-level keyboard hook for global hotkey (#38250)

Adds the ability to make the global CmdPal hotkey a low-level hook. This is needed for `win+space`, `win+r`, et al. 

I've only added this to the root hotkey. Other hotkeys will still use the normal `RegisterHotkey` ones. We can re-evaluate this for 0.2+.



Originally: https://github.com/zadjii-msft/PowerToys/issues/349

Solves: https://github.com/microsoft/PowerToys/issues/38297

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>

* Adds support for JUMBO thumbnails in the helper (#38539)

Adds a parameter to `Toolkit.ThumbnailHelper.GetThumbnail` to retrieve the largest possible icon from the file. For most use cases, the normal icon size will be good for list items and page icons. 

But for details, you'll want to use the JUMBO icons, and to retrieve them, we need to get the icon from a different API. 

As a drive-by, I also have us fetching the highest-res app icon for UWP's rather than the lowest-res icon.

Solves #38238 

Screenshots:
| before | after | 
| ------ | ----- |
| ![image](https://github.com/user-attachments/assets/8aebf163-2f71-45c5-9bee-052ef5528c58) | ![image](https://github.com/user-attachments/assets/7d7b417a-d8d0-4234-ad2b-446a4ca804ba) |
| ![image](https://github.com/user-attachments/assets/3aa21305-2d5f-40a5-a091-fbe5ca5f332c) | ![image](https://github.com/user-attachments/assets/beb5e62f-c649-4cbc-8f6e-8d2c1655cac0) |

* [PowerToysRun] Add version info to plugin error messages (#38491)

* changes

* fix resx

* Don't auto-hide when an MSAL dialog is opened on us (#38850)

Apps that want to show MSAL dialogs on the Command Palette would explode if they passed CmdPal's HWND to WithParentActivityOrWindow. It's not entirely clear why, but MSAL would explode if the parent HWND is hidden.

When the MSAL dialog opened, we'd hide ourselves, and badda bing, badda boom, the extension would crash.

MSAL dialogs will set us to WS_DISABLED right before the dialog is opened. Easy solution. Don't hide ourselves, if we're disabled.

Helps some friends not depend on the existence of Teams :P

* Immediately select search text on opening page (#38842)

closes #38712
closes #38315
related to #38379 (and might sufficiently close that too)

This immediately selects the search text when a page is loaded.
![38712-select-search-text-000](https://github.com/user-attachments/assets/9db8b455-9afb-4b11-9a30-8ddaa23cdb64)

* Update the settings form when ext settings are saved (#38851)

This one was subtle - the Settings class in the toolkit didn't ever change the items for a SettingsContentPage. For the main settings window, this was problematic. It would only ever hang onto one instance of that CommandSettings.SettingsContentPage, and never re-retrieve the value from it.

This fixes that issue, by making sure to raise an ItemsChanged in the settings changed handler, so that we automatically pull down the new settings forms.

For settings that were added to commands, as a context item, this wasn't an issue. They were always returning new forms to the host, with the current settings values in it.

Closes #38191

* Powertoys for Linear plugin added (#38883)

* Update thirdPartyRunPlugins.md

* Update names.txt

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>

* [cmdpal] Run cmdpalette from runner locally (#38725)

* empowering users to maximize OOBE to their heart desire (#37823)

empowering users to maximize to their heart desire

* resume main

* Trust selfsign cert in localmachine\root to make msix available

* minor fix

* retry signing

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>

* [cmdpal] Port v1 calculator extension (#38629)

* init

* update

* Remove duplicated cp command

* Change the long desc

* Update notice.md

* Use the same icon for fallback item

* Add Rappl to expect list

* update notice.md

* Move the original order back.

* Make Radians become default choice

* Fix empty result

* Remove unused settings.
Move history back.
Refactory the query logic

* fix typo

* merge main

* CmdPal: minor calc updates (#38914)

A bunch of calc updates

* maintain the visibility of the history
* add other formats to the context menu #38708
* some other icon tidying

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>

* Add the list item requested shortcuts back (#38573)

* [x] Re-adds the context menu shortcut text
* [x] Hooks up the keybindings to the search box so that you can just press the keys while you have an item selected, and do a context command
* [x] Hook these keybindings up to the context flyout itself
* [x] Adds a sample for testing

Solves #38271

* [CmdPal] A11y improvements (#38840)

This PR introduces the following changes:

- Adding the right a11y labels - as a result, Accesibility Insights is no longer flagging any errors: https://github.com/microsoft/PowerToys/issues/38395
- Removing and tweaking a few animations, addressing: https://github.com/microsoft/PowerToys/issues/38438
- Localization improvements

* [CmdPalette > Time and Date] Custom formats (Port #37743) and other plugin improvements - 2 (#38952)

* port changes from broken PR

* fixes

* fix formatting

* [cmdpal] Add fallback item for system command. (#38865)

* init

* Remove unused cache

* merge main

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [CmdPal] Fixed #38961 (#38988)

* [CmdPal] Fixed #38961

* [CmdPal] Fixed #38961

* [CmdPal] Fix #38961 Wrong Message for "Open Recycle Bin" Command

* [CmdPal] Fix #38961 Wrong Message for "Open Recycle Bin" Command

---------

Co-authored-by: Aung Khaing Khant <aungkhaingkhant@advent-soft.com>

* [RegPreview] Init with header and add NEW button (#37626)

* default value

* add new button

* fix tool tip

* update button symbol

* feedback changes

* spellcheck

* Fix build break because of miss Signing for new files (#39014)

* Add CmdPalKeyboardService.dll as part of sign
* Move 3rd party signing to another section

* release: don't publish private symbols for 100 years (#39015)

* [ImageResizer]Fix: Deleting an Image Resizer preset deletes the wrong preset (#38476)

* [ImageResizer]Fix: Deleting an Image Resizer preset deletes the wrong preset

* update the helper

* sort items

* [PowerAccent] Cancel previous ShowToolbar task if a new one is triggered (#37757)

* Cancel previous ShowToolbar task if a new one is triggered

* more changes

* Fix space trigger starting at center

* Removed unneded code

* More changes

* Addressed feedback

* Fix another edge case

* [cmdpal] Add setting "ignore hotkey when full screen"  (#38923)

* init

* merge main

* merge main

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [Workspaces]Fix for steam games capture&launch: capture and correctly launch steam games. (#38380)

* Workspaces fix: capture steam games.

* minor fix

* Launch steam apps by url appmodeluserid instead of directly exe call.

* fix copilot comment

* fix

* remove unnecessary string

* expect words

* white list words

* Order of alphabet

* exclude thin frame if it's not a steam game.

* fix build

* fix regression

* adjust comment

* [Fuzzing] Add PowerRename Fuzzing and C++ Project Setup Guidance (#38922)

* add fuzz

* update solution

* update pipeline

* update solution

* remove arm64

* use reference

* revert the code

* add fuzzing readme

* update solution

* fix spell-check

* Parent reference don't need update

* remove fuzzing config

* add debug config

* update the config

* [CmdPal] Hide commands that shouldn't be visible (#39011)

hide commands from more commands

* [CmdPal] Tweaks to the detailspane UX (#38972)

Details pane UX tweaks. Closes: https://github.com/microsoft/PowerToys/issues/38973

Before:
![image](https://github.com/user-attachments/assets/ba7aca91-31ef-4571-b4ca-0951abe73c21)


After:
![image](https://github.com/user-attachments/assets/c17179b0-4319-4176-bac7-b4ca140bc624)

* [CmdPal] Better support for long labels in empty content and commandbar (#38974)

Closes: #38970

Before:
<img width="605" alt="image" src="https://github.com/user-attachments/assets/8310e08f-c471-4663-9000-bfd1eb8c99f3" />

After:
<img width="514" alt="image" src="https://github.com/user-attachments/assets/b1e4c5f6-cd6b-42b2-9a23-3e1e3642202a" />

* CmdPal: Tidy up some winget experiences (#38174)

I'm filing this so that I don't lose it on this machine I use less often. We can probably hold it out of 0.90


Fixes:
* If a package is installed, we always display the version as "Unknown"
  * also deals with a case where getting the package metadata could fail, and we'd hide the list item. That's only possible in the "installed, no updates available" case
* Allow package updates, add an icon for updates
* moves off the preview winget API onto a higher stable version

* Fully initialize context menus when they change (#38998)

If we don't slow-initialize the whole menu when it changes, then we won't see that there's secondary (& more) commands.  

Tested this with the extension from [waaverecords/CmdPal.Ext.Spotify#4](https://github.com/waaverecords/CmdPal.Ext.Spotify/pull/4)

Closes #38959 


Also seemingly closes #38347 - seems that needed additional bumping of the `EmptyContent`'s

* Pre-creation of generated folder for any csproj (#39018)

* Test for Precreation of generated folder for any csproj to prevent random build break

* Update to WinAppSDK 1.7 latest version (#39016)

* Update to WinAppSDK 1.7 latest version
* Update UpdateVersions.ps1

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.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: Clint Rutkas <clint@rutkas.com>

* upgrade the boost dependencies for Fuzzing Project (#39057)

* [CmdPal] Added fallback for time and date (#38918)

* Added fallback for time and date

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* only support week/now/time/year query

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* Add week option

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* Changed setting for time date fallback.

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* update globalization string

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* use week of year.

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* update

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

---------

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* [cmdpal] Ref to AotCompatibility in some cmdpal project. (#39061)

* Ref to AotCompatibility

* Typo issue

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [Settings] Fix null CmdPal HotKey crash (#39052)

fixed null cmdpal hotkey crash when settings.json not exists or not define hotkey

* Add support for filterable, nested context menus (#38776)

_targets #38573_

At first I just wanted to add support for nested context menus.

But then I also had to add a search box, so the focus wouldn't get weird.

End result:

![nested-menus-001](https://github.com/user-attachments/assets/4e8f1ec8-4b09-4095-9b81-caf7abde8aea)

This gets rid of the need to have the search box and the command bar both track item keybindings - now it's just in the command bar.

Closes #38299
Closes #38442

* [Tool] Script to build an installer locally (#39017)

* add script to build a installer

* minor fix

* fix search path for msix file

* fix sign

* fix sign

* fix spelling

* Fix powershell5 can't recognize emoji

* ensure-wix

* bring cmdpal available during local build

* remove early quit

* fix marco

* add logger

* doc

* add a note

* self review

* fix macro def

* add functionality to export cert so that other machine can install it.

* spelling

* 37405 Advanced Paste: Image To Text doesn't work with English (Canada) (#37806)

* [AdvancedPaste] [Fix Bug] Create ocrEngine from user profile language

GetOCRLanguage may fail based on language tag not matching (en-CA does not match en-GB or en-US), however user profile language may be valid.

* Update exception message.

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* update

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

---------

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
Co-authored-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* Color Picker: add Oklab and Oklch color formats (#38052)

* Resolve Resources.resw conflict

* Update CIE LCh chroma practical upper bound according to CSS spec

* Add review suggestions

* Add WIP tests (lch and oklch do not pass yet)

* Deduplicate Lab to LCh converter method

* Update expect.txt

* Fix liberty test color

* Reimplement oklab with better precision

* Remove CIE LCh

* Add tooltip for color param descriptions

* Update spell-check expect.txt with new words

* Remove 'cielch' and 'lch' from expect.txt

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com>

* [PTRun] Allow preventing usage based ordering results (#37491)

* Allow preventing selected result data retrieval

* Updated implementation to calculate sort order on result and update property name to better reflect purpose

* Update Result.cs sort order method name

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>

* Align with the name GetSortOrderScore

---------

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>

* [cmdpal] [AOT] make Clipboard/System/WebSearch/WindowsSettings ext become AOT compatible. (#39080)


Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* Update the ADO path for tsa.json (#39079)

The previous ADO area path for Powertoys was gone, we need to update to new one.

* Start progress on AoT. (#39051)

* starting AoT flag push

* Few more

* bookmarks

* Really? The VM project compiles?

* Disable publish AOT before we really testing it.

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [CmdPal] Launch cmd pal with retry (#39039)

* Add Retry when enable

* Add correct for the checking logic

* Retry in another thread (#39042)

* launch thread

* dev

* fix a thread safety

* improve

* improve

* make code clear

* Fix comment

* fix comment

* improve

* self review

* fix & log

* silent fail if not reach 10 times

* fix a ci build flag error

* fix a macro

* some simple improve

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>

* [cmdpal][aot] Remove some unused file in CmdPal.Common and mark it as AOT compatible.  (#39110)

* Remove unused com interface

* Remove unused file

* Remove unused file

* Remove unused file

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* Wait to update SearchText until we've actually updated SearchText (#39093)

Closes #38829

If we always UpdateProperty here, then there's a possible
race condition, where we raise the PropertyChanged(SearchText)
before the subclass actually retrieves the new SearchText from the
model. In that race situation, if the UI thread handles the
PropertyChanged before ListViewModel fetches the SearchText, it'll
think that the old search text is the _new_ value.

* [Tool] Delete export pfx function to remove use of hard coded password (#39144)

don't need export pfx functionality

* [cmdpal] Support search any file in fallback command (#38455)

## Summary of the Pull Request
1. Add new setting to control this behaviour
2. Support always on and only when file exist in the fallback command
3. Change the condition of updateFallbackCommand in toplevelvm

demo:

https://github.com/user-attachments/assets/19e4ced3-30ad-44f4-8f3a-93620f46bb3d


## PR Checklist

- [x] **Closes:** #38370

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>

* [cmdpal][AOT] clean up some AOT related issue (#39163)

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* Fix for missing CmdPal extensions docs (#39173)

* CmdPal: Start extensions in parallel (#39203)

This reduces our extension startup time by approximately 70% on my
machine (I have 17 extensions). I'd guess the gains scale with the
number of extensions. That's 8s -> 3s on average, and now I also get 2.5s reloads.

This retains the order of the list of extensions, by only starting the
processes in parallel. Once we have all the command provider instances,
then actually retrieving the commands.

It also adds a timeout on startup & load, so that one misbehaving extension won't block everyone else.

closes: #38529

* [Infra-BuildScript] Add PowerShell Script to Enforce Shared Common.Dotnet.CsWinRT.props in all CSharp Projects under Src Sub-Folder (#37811)

* Add Powershell script to validate whether CSharp project correctly import shared props, update pipeline to enforce such validation, and fixed all projects that didn't import this shared props correctly

* add common props for fuzz test project

* update the path

* Only scans projects in src sub-folder

* Update .pipelines/verifyCommonProps.ps1

* Update csproj to include Common.Dotnet.CsWinRT.props

* Fix indentation in RegistryPreview.FuzzTests.csproj

* exclude TemplateCmdPalExtension.csproj in validation process

* exclude TemplateCmdPalExtension.csproj in validation process

---------

Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
Co-authored-by: Jerry Xu <nxu@microsoft.com>

* Dismiss the details pane when the list gets emptied (#39206)

I cannot find an issue for this. I swear I filed it somewhere.

If you open winget, search for "terminal", wait till it loads, then
hit `esc`, we'll clear the search and empty the list, but never actually
hide the details pane. That looks weird.

This fixes that.

Closes _nothing i guess_.

* cmdpal: unset the command if we don't find a command (#39208)

On a reload, the system commands fallback would leave the "restart"
fallback behind, for the same reason as what we found around
e40372c & ef264d9 in #38455

* cmdpal: fix a leak in the extension template (#39209)

There's apparently a footgun with the way we're using ComServer, which
results in us leaking the extension processes when we think we've
disposed them

The fix unfortunately has to be on the extension side. Extensions
published prior to 0.2 will need to manually fix this.

closes: #39045

* [CmdPal] Prevent maximizing (#39220)

Prevent CmdPal window from maximizing when user double-click the title bar area.

 Closes: #39096

* [CmdPal] MinWidth/Height and DPI-aware launch dimensions (#38637)

* MinWidth/Height and DPI-aware launch dimensions

* Making MainWindow DPI aware too

* Moving toastwindow to WinUIEx too

* Update MainWindow.xaml.cs

* Reverting back to the working logic

* Localizing settings window title

* Xaml formatting

* Update SettingsWindow.xaml.cs

* CmdPal: Cloak the window instead of hiding it (#39170)

This avoids the few frames of "flicker in" that XAML does when the window is SW_SHOW'n

closes #38384
closes #38404
closes #38438

* Lazy-fetch the settings for extensions (#38844)

This PR stops us from synchronously initializing the settings page for every extension (including built-in's) on startup. That incurs a small penalty that really adds up the more extensions a user has.

Instead, we'll now only initialize the `CommandSettings` object when we first actually need it. 

From a relatively unscientific test, this saves approximately 10% on the initialization of builtin commands, and for my setup, it trims about 28% off extension initialization (across all built-in's / extensions):

branch | Built-in load (ms) | Extension load (ms) | %Δ builtin | %Δ extensions | 
-- | -- | -- | -- | -- |
main | 1455 | 6867.6 | | |
this PR | 1309.2 | 4919 | -10.02% | -28.37%

Closes #38321

* [cmdpal] protect cmdpal from crash if adaptive card fails (#39264)

<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
     
Card = AdaptiveCard.FromJsonString(cardJson) is called in catch block, if it fails, app will crash.

* CmdPal: URI activate, rather than using shell:AppsFolder (#39269)

* Use a URI handler for launching

this is a test

* I think we can review this

* Add a settings URI too

* [CmdPal] Open CmdPal settings from PT settings (#39262)

* Turning description into a hyperlink

* Update expect.txt

* Update CmdPalPage.xaml.cs

* update xaml format.

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* update.

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>

* Added logger

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

* update

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>

---------

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Co-authored-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
Co-authored-by: Shawn Yuan <shuaiyuan@microsoft.com>

* CmdPal: Actually observe Details (#39263)

Extensions can change the properties on their Details, and they should
be observable, but they weren't. This is because the ShellPage is
ultimately responsible for exposing the details, but it doesn't own the
details. The selected ListItemViewModel from the ListPage does.

This PR just adds a event handler on ListViewModel. We'll attach/detach
that handler to ListItemViewModels as the selection changes. In the body
of that handler, we'll let the ShellPage know when the details object
changes (by sending ShowDetails/HideDetails messages).

Closes #39216

* Bump toolkit version to 0.2.0 in template (#39292)

This bumps the version of the toolkit consumed by the template to 0.2.0

~Ironically, I have not yet published 0.2. I'm spinning that CI build currently. But I'll have that uploaded tomorrow morning at the latest~

EDIT: package is uploaded now

* Remove new label from command palette (#39318)

* remove new label from cmdpal

* fix xaml styling

* Fix settings crash issue when clicking "Open Cmdpal settings..." (#39322)

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>

* [cmdpal] Disable "ignore shortcut when full screen" by default (#39323)

disable by default

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [cmdpal] Fix winget cannot install after clicking "install" button issue (#39324)

init

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>

* [Fuzzing test] Use valid areaPath in OneFuzz configuration (#39368)

change areaPath for fuzzing test config

* Add log for cmd pal ext to trace exceptions (#39326)

* Add log to trace error for apps.

* Add bookmark log

* registry exception log

* fix

* Added logger for cmdpal extensions.

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>

* remove noise

* Update

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>

* change log level

* change level

* Fix comments

* Fixed comments.

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>

* Resolve comments

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>

---------

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Co-authored-by: Shawn Yuan <shuaiyuan@microsoft.com>

* release: stop hardcoding version numbers for the telemetry package (#39390)

* Update Areapath in tsa.json to align latest one (#39391)

* [cmdpal] Add some logs for WinGet extension (#39329)

* [Fuzzing test] Use valid areaPath in OneFuzz configuration (#39393)

* Bump our telemetry package version (#39388)

Data collection is hard.

Our internal package, which was last bumped around August 2024,
mistakenly changed a load bearing string from `ETW_GROUP` to
`MSPG_GROUP`. The former sets the ETW group id. The later does nothing.

This PR represents bumping our dependency to the version with the fix.

Considering that none of our data for CmdPal worked anyways, I took the
opportunity to rename a bunch of our events that had totally generic
names.

Closes #38704

re: #38032
regressed around: #34078

* [Deps] Update .NET packages from 9.0.4 to 9.0.5 (#39404)

Updates .NET 9 Runtime / Library packages to the latest 9.0.5 servicing release for security fixes.

This PR also updates the version of System.Text.Json to 9.0.5 in the CmdPal extension template.

* [Fuzz] Add DLL Reference in OneFuzzConfig.json to Fix Hosts Fuzz Bug (#39398)

* add Testably.Abstractions.FileSystem.Interface.dll

* fix spell error

---------

Signed-off-by: Shawn Yuan <shuai.yuan.zju@gmail.com>
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.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: Kai Tao <69313318+vanzue@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 <n.xu@outlook.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>
Co-authored-by: Mengyuan <162882040+chenmy77@users.noreply.github.com>
Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-05-14 15:39:19 +08:00
Xiaofeng Wang (from Dev Box)
614af1dd77 Merge main branch 2025-05-14 15:11:54 +08:00
XiaofengWang
90a489352b Support pipeline running on win11 x64 (#39328)
* Support pipeline running on win11-x64

* Update naming

* Update dependency

* Update jobs

* update format

* update format

* update naming

* Update jobs to stages

* Update Artifacts Name

* Update TestArtifactsName

* Update variable format

---------

Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-05-09 16:52:39 +08:00
zhaopeng wang
c2dddab4ac Dev/zhaopengwang/UI test automation fancyzone (#39135)
* add test case and fix bug

* add test

* add test

* add test

* add test case

* add fancyzones window name and fix test case error

* add test case

* add test case

* add test case After creating a virtual desktop

* add test case delete layout

* add test case grid layout change monitor resolution

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error

* fix pipeline error and only run fancyzone uitest

* disable other ui test code

* fix build error

* fix pipeline error

* fix pipeline error

* add restart scope exe

* enable other test code

* fix Spelling error

* add error log

---------

Co-authored-by: Zhaopeng Wang (from Dev Box) <zhaopengwang@microsoft.com>
2025-04-29 15:25:39 +08:00
Kai Tao
1eb60302df [Workspaces UI Test] Add first UI automation for workspaces editor (#39143)
Add first UI automation for workspaces editor
2025-04-29 14:23:51 +08:00
XiaofengWang
11781d11e9 Dev/xiaofengwang/update doc (#39142)
* Update UITests.md

* update UITests.md

---------

Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-04-29 11:44:29 +08:00
yaqingmi
1a14dce5a3 Add FindMyMouseSettings Tests (#39078)
* empowering users to maximize OOBE to their heart desire (#37823)

empowering users to maximize to their heart desire

* Add More Elements support for UITestAutomation

* Add Mouse Click and Base Components Test

* Add FindMyMouse Tests for Enable and Disable

* Add Mouse Highlighter Enable/Disable Test Case

* Add Mouse Actions Support for UITest Framework

* Delete src/modules/MouseUtils/FindMyMouse.UITests/FindMyMouseSettingTests.cs

* Update NameSpace and remove redundant code

* Update the SessionHelper and Sleep Time for Actions

* Add SendKey for Session and Fix Spelling Errors

* Add Tests for Mouse Pointer Crosshairs Enable/Disable

* Add Sleep Time to Action Parameters

* Add Edit System Settings

* Add MouseJump Enable/Disable Tests

* Fix Pipeline error for MouseHighlighter

* Fix pipeline error

* Resolve Spelling errors

* Fix pipeline errors

* Add FindMyMouse Setting Tests

* Update the Sleep time

* Update Sleep Time

* Fix pipeline errors

* Revert pipeline settings

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-04-25 11:43:47 +08:00
Xiaofeng Wang (from Dev Box)
211bb15be0 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-04-23 17:07:20 +08:00
Xiaofeng Wang (from Dev Box)
2752b217e5 Add cover list and TestCategory 2025-04-23 17:07:02 +08:00
XiaofengWang
b0afc57e77 Add timeout for ui test job (#39037)
Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-04-23 11:43:22 +08:00
yaqingmi
2eb51a1861 Add Tests for MouseJump and Fix Pipeline Errors (#39024)
* empowering users to maximize OOBE to their heart desire (#37823)

empowering users to maximize to their heart desire

* Add More Elements support for UITestAutomation

* Add Mouse Click and Base Components Test

* Add FindMyMouse Tests for Enable and Disable

* Add Mouse Highlighter Enable/Disable Test Case

* Add Mouse Actions Support for UITest Framework

* Delete src/modules/MouseUtils/FindMyMouse.UITests/FindMyMouseSettingTests.cs

* Update NameSpace and remove redundant code

* Update the SessionHelper and Sleep Time for Actions

* Add SendKey for Session and Fix Spelling Errors

* Add Tests for Mouse Pointer Crosshairs Enable/Disable

* Add Sleep Time to Action Parameters

* Add Edit System Settings

* Add MouseJump Enable/Disable Tests

* Fix Pipeline error for MouseHighlighter

* Fix pipeline error

* Resolve Spelling errors

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-04-22 17:17:13 +08:00
Mengyuan
91077c71a9 [UITestAutomation > UITests-Fancyzones] Add UI tests on Switch between windows in the current zone (#38999)
* fancytest oneswitch test

* add GetActiveWindowTitle

* testcase1 add set layout

* change shortcut to win pgdn

* add hold key

* Snap several windows to one zone, verify switching works.

* pack snaptoonezone

* pack snaptoonezone

* Add Fancy Zone Switch tests

* Add Fancyzones switch zones

* remove code and change zoneuuid to zonesets

* fix spell error and remove files

* remove unuse keyup in elements

* remove to common api

* fix spell error
2025-04-22 17:08:55 +08:00
XiaofengWang
997622fac8 Separate visual check by platform (#39022)
* Separate visual check by pipeline platform

* Update baseline file

* Add image to attachments when baseline file does not exist

* Update baseline resource file

---------

Co-authored-by: Xiaofeng Wang (from Dev Box) <xiaofengwang@microsoft.com>
2025-04-22 14:49:28 +08:00
yaqingmi
ddb9d8ecc6 UI Test Automation for FindMyMouse Enable/Disable and MouseHighlighter Enable/Disable (#38891)
* empowering users to maximize OOBE to their heart desire (#37823)

empowering users to maximize to their heart desire

* Add More Elements support for UITestAutomation

* Add Mouse Click and Base Components Test

* Add FindMyMouse Tests for Enable and Disable

* Add Mouse Highlighter Enable/Disable Test Case

* Add Mouse Actions Support for UITest Framework

* Delete src/modules/MouseUtils/FindMyMouse.UITests/FindMyMouseSettingTests.cs

* Update NameSpace and remove redundant code

* Update the SessionHelper and Sleep Time for Actions

* Add SendKey for Session and Fix Spelling Errors

* Add Tests for Mouse Pointer Crosshairs Enable/Disable

* Add Sleep Time to Action Parameters

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Yaqing Mi (from Dev Box) <yaqingmi@microsoft.com>
2025-04-21 15:20:57 +08:00
Zhaopeng Wang (from Dev Box)
856cbaa293 fix command palette pipeline error 2025-04-18 15:42:53 +08:00
Zhaopeng Wang (from Dev Box)
4067be17b2 fix merge 2025-04-18 14:53:33 +08:00
Zhaopeng Wang (from Dev Box)
b4020c6720 delete createmonitor 2025-04-18 14:33:53 +08:00
Xiaofeng Wang (from Dev Box)
f87c1bc448 Update hosts baseline file 2025-04-18 14:28:43 +08:00
Xiaofeng Wang (from Dev Box)
f49daa24e0 Run all UI tests in automation pipeline 2025-04-17 16:46:38 +08:00
Xiaofeng Wang (from Dev Box)
c1c9720992 keep original image 2025-04-17 16:42:20 +08:00
Xiaofeng Wang (from Dev Box)
750bb429c5 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-04-17 16:37:32 +08:00
Xiaofeng Wang (from Dev Box)
7f5aaf26dc Test visual in pipeline 2025-04-17 16:37:27 +08:00
Mengyuan
af8d5402c8 [UITestAutomation > KeyBoardHelper] Add PressKey, ReleaseKey, and KeyDownAndDrag combinationschen/UI automationtools (#38921)
* Add PressKey and ReleaseKey Seperately Add KeyDownAndDrag combination operator in element
2025-04-17 16:27:03 +08:00
Xiaofeng Wang (from Dev Box)
77852f2137 Update keyboardhelper to lowercase 2025-04-15 16:56:13 +08:00
Xiaofeng Wang (from Dev Box)
b902d3adf0 Update pipeline-ui-tests-automation.yml 2025-04-15 10:56:33 +08:00
Xiaofeng Wang (from Dev Box)
82ff0615d4 Add parameter in job-build-project.yml 2025-04-15 10:54:25 +08:00
Xiaofeng Wang (from Dev Box)
5081d6d31e Add build yml for ui automation 2025-04-15 10:43:00 +08:00
Xiaofeng Wang (from Dev Box)
abec5eb96a update yml format 2025-04-14 16:36:46 +08:00
Xiaofeng Wang (from Dev Box)
71ec39ff89 Separate UI automation pipeline yml file 2025-04-14 16:09:00 +08:00
Zhaopeng Wang (from Dev Box)
017b9e6339 create new virtual monitor 2025-04-14 12:03:15 +08:00
Zhaopeng Wang (from Dev Box)
c8b1a925b3 check montor number 2025-04-14 10:59:30 +08:00
Zhaopeng Wang (from Dev Box)
e0e3772cdd fix change display resolution bug 2025-04-14 10:46:15 +08:00
Zhaopeng Wang (from Dev Box)
6b8798fb94 add change display resolution 2025-04-14 08:21:32 +08:00
Zhaopeng Wang (from Dev Box)
836cfbf698 test change display resolution 2025-04-12 20:55:29 +08:00
Zhaopeng Wang (from Dev Box)
91d504511f delete assert 2025-04-11 17:21:29 +08:00
Zhaopeng Wang (from Dev Box)
3d3682672c add enum display setting 2025-04-11 16:08:43 +08:00
Zhaopeng Wang (from Dev Box)
b9474e9f60 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-04-11 14:28:17 +08:00
Zhaopeng Wang (from Dev Box)
c5b29188b8 add display resolution change 2025-04-11 14:28:07 +08:00
Xiaofeng Wang (from Dev Box)
e59b6d3051 Fix sendkey issue 2025-04-11 14:00:15 +08:00
Xiaofeng Wang (from Dev Box)
6625289a10 Add env in VSTest@3 2025-04-11 12:27:43 +08:00
Xiaofeng Wang (from Dev Box)
2f0dae347f Add env in VSTest@3 2025-04-11 12:18:33 +08:00
Xiaofeng Wang (from Dev Box)
f3ddba8aa5 Update pipeline parameter 2025-04-11 10:19:01 +08:00
Zhaopeng Wang (from Dev Box)
4744d23857 test in pipeline 2025-04-11 03:18:51 +08:00
Zhaopeng Wang (from Dev Box)
c42f8b0a1b fix error 2025-04-11 01:38:39 +08:00
Zhaopeng Wang (from Dev Box)
70da33783d add test code 2025-04-11 00:44:03 +08:00
Xiaofeng Wang (from Dev Box)
5fa416b962 Add GetDisplaySize 2025-04-10 17:28:45 +08:00
Xiaofeng Wang (from Dev Box)
aa7d89c8de Remove duplicate init 2025-04-10 17:11:44 +08:00
Xiaofeng Wang (from Dev Box)
fd7cccefa2 Continuous screenshots in pipeline env 2025-04-10 15:55:23 +08:00
Xiaofeng Wang (from Dev Box)
ae2bb61d26 Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/PowerToys into feature/UITestAutomation 2025-04-10 15:47:37 +08:00
Xiaofeng Wang (from Dev Box)
52b4150e3b Continuous Screenshots in pipeline env 2025-04-10 15:47:32 +08:00
Zhaopeng Wang (from Dev Box)
6dc0211867 Merge branch 'main' into feature/UITestAutomation 2025-04-10 11:14:25 +08:00
Xiaofeng Wang (from Dev Box)
fc4d3a96c3 Add GetPixelGolor 2025-04-09 17:38:54 +08:00
Xiaofeng Wang (from Dev Box)
a347d740ca Add MouseHelper 2025-04-09 17:12:36 +08:00
Xiaofeng Wang (from Dev Box)
e8359931ac Add Enum Key 2025-04-09 16:00:35 +08:00
Xiaofeng Wang (from Dev Box)
1cb215d1a6 Add KeyboardHelper 2025-04-09 14:40:39 +08:00
Zhaopeng Wang (from Dev Box)
0f6ff59d2e merge main and fix error 2025-04-01 17:39:28 +08:00
Xiaofeng Wang (from Dev Box)
25a8c95049 Common out continuous screenshots 2025-03-26 14:02:09 +08:00
Xiaofeng Wang (from Dev Box)
c60c79af67 Send ESC in testinit 2025-03-26 11:40:33 +08:00
Xiaofeng Wang (from Dev Box)
dd5279b9b8 Add screenshots to result 2025-03-26 09:57:27 +08:00
Xiaofeng Wang (from Dev Box)
78921eb7d3 Add TestEmptyView back 2025-03-25 16:44:22 +08:00
Xiaofeng Wang (from Dev Box)
9173ac27df Continuous screenshots for failed cases 2025-03-25 16:36:13 +08:00
Xiaofeng Wang (from Dev Box)
ce57d818a7 Remove TestEmptyView 2025-03-20 17:34:13 +08:00
Xiaofeng Wang (from Dev Box)
595231f64b Add waiting time before starting to run 2025-03-17 14:42:07 +08:00
Jerry Xu
7894c1e5ad Merge branch 'feature/UITestAutomation' of https://github.com/microsoft/powertoys into feature/UITestAutomation 2025-03-17 14:01:26 +08:00
Jerry Xu
d2d71bf797 Save last-screenshot for all failed ui-test-cases 2025-03-17 14:01:23 +08:00
Jerry Xu
3eb5ca294a Save last-screenshot for all failed ui-test-cases 2025-03-17 12:30:00 +08:00
Xiaofeng Wang (from Dev Box)
0277938f18 Set window always on top 2025-03-15 15:04:43 +08:00
Xiaofeng Wang (from Dev Box)
dd7b6f3ea9 Update CloseWarningDialog for testing 2025-03-15 13:05:28 +08:00
Xiaofeng Wang (from Dev Box)
0187d1abcd Add waiting time after window resize 2025-03-15 11:41:56 +08:00
Xiaofeng Wang (from Dev Box)
7bd2b50126 Implement retry logic instead of using ImplicitWait 2025-03-15 10:00:28 +08:00
Jerry Xu
b127611462 Prototype save to attachment 2025-03-14 16:38:05 +08:00
Xiaofeng Wang (from Dev Box)
622770134e Comment out visualassert 2025-03-14 16:13:58 +08:00
Xiaofeng Wang (from Dev Box)
ceca607142 Update xpath in RemoveAllEntries 2025-03-14 14:38:08 +08:00
Xiaofeng Wang (from Dev Box)
77a5ef7d32 Check window in RemoveAllEntries 2025-03-14 12:13:22 +08:00
Xiaofeng Wang (from Dev Box)
75f2b0927c Update visual default fuzz value 2025-03-13 23:20:13 +08:00
Xiaofeng Wang (from Dev Box)
f54ab6ebd5 Add VisualHelper 2025-03-13 22:22:00 +08:00
Xiaofeng Wang (from Dev Box)
dd5997ab06 Update click in NavigationViewItem 2025-03-13 16:27:31 +08:00
Xiaofeng Wang (from Dev Box)
6bdbb6b552 Add click hold time 2025-03-13 15:47:38 +08:00
Xiaofeng Wang (from Dev Box)
84dd551d84 Extend waiting time 2025-03-13 13:19:07 +08:00
Xiaofeng Wang (from Dev Box)
83bb82322d Add CloseMainWindow in Session and Update HostSettingTests 2025-03-12 22:08:25 +08:00
Jerry Xu
c4f8f09fab Initialize setting app w/ Medium size 2025-03-12 20:13:56 +08:00
Jerry Xu
9c95835384 Address script out-of-date issue 2025-03-12 16:41:40 +08:00
Jerry Xu
1867ac8f02 Support VisualAssert - Image based validation 2025-03-12 15:51:40 +08:00
Jerry Xu
127e079efe fix build issue 2025-03-12 14:13:35 +08:00
Jerry Xu
03540e307c sync & merge 2025-03-12 14:02:53 +08:00
Xiaofeng Wang (from Dev Box)
a10578f7d3 Run UI tests in CI pipeline 2025-03-12 13:57:14 +08:00
Zhaopeng Wang
9c08c957e5 add Automation clean up code
Rebase from origin/dev/nxu/ImproveUIAutomation
2025-03-12 13:55:44 +08:00
Jerry Xu
c73b88f575 sync & merge 2025-03-12 13:47:37 +08:00
Jerry Xu
fc1c4abd0a sync 2025-03-12 13:46:25 +08:00
Jerry Xu
22efeb3f63 Improve UIAutomation to support:
1. SetWindowSize
2. Auto-close
3. Better window search logic
2025-03-12 13:37:23 +08:00
Xiaofeng Wang (from Dev Box)
63c4089441 Run UI tests in CI pipeline 2025-03-12 11:36:32 +08:00
Zhaopeng Wang
fd206ecdee add Automation clean up code 2025-03-12 11:36:32 +08:00
Jerry Xu
d243b58715 Fix code-style 2025-02-24 15:51:53 +08:00
Jerry Xu
abf4626843 Merge branch 'dev/nxu/ImproveUIAutomation' of https://github.com/microsoft/powertoys into dev/nxu/ImproveUIAutomation 2025-02-24 15:47:08 +08:00
Jerry Xu
3874a3b893 Exclude all UI-Test projects instead of just fancyZone UITest 2025-02-24 15:47:05 +08:00
Jerry Xu
8687b310db Exclude all UI-Test projects instead of just fancyZone UITest 2025-02-24 15:43:56 +08:00
Jerry Xu
c7ed8ee0c3 Add double-click 2025-02-24 15:43:05 +08:00
Jerry Xu
8c21f794af Improve UITest Automation 2025-02-24 15:28:46 +08:00
Jerry Xu
18befd7149 Improve UITest Automation 2025-02-24 15:24:33 +08:00
156 changed files with 8989 additions and 1026 deletions

View File

@@ -189,6 +189,7 @@ CImage
cla
CLASSDC
CLASSNOTAVAILABLE
cleanmgr
clickable
clickonce
CLIENTEDGE
@@ -273,6 +274,7 @@ currentculture
CURRENTDIR
CURSORINFO
cursorpos
CURSORSHOWING
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
@@ -341,7 +343,9 @@ DESELECTOTHERS
DESIGNINFO
DESKTOPABSOLUTEEDITING
DESKTOPABSOLUTEPARSING
DESKTOPHORZRES
desktopshorcutinstalled
DESKTOPVERTRES
devblogs
devdocs
devmgmt
@@ -352,6 +356,7 @@ DFX
DIALOGEX
digicert
dimm
DINORMAL
DISABLEASACTIONKEY
DISABLENOSCROLL
diskmgmt
@@ -395,6 +400,7 @@ DVASPECTINFO
DVD
dvr
DVTARGETDEVICE
dwflags
dwl
dwm
dwmapi
@@ -482,6 +488,7 @@ FANCYZONESDRAWLAYOUTTEST
FANCYZONESEDITOR
FARPROC
fff
FFFF
FILEEXPLORER
FILEFLAGS
FILEFLAGSMASK
@@ -600,6 +607,7 @@ helptext
HGFE
hglobal
hhk
HHmmssfff
hhx
Hiber
Hiberboot
@@ -608,6 +616,7 @@ hicon
HIDEREADONLY
HIDEWINDOW
Hif
hightlight
HIMAGELIST
himl
hinst
@@ -745,6 +754,7 @@ isocpp
iss
issecret
ISSEPARATOR
istep
ith
ITHUMBNAIL
IUI
@@ -1272,6 +1282,7 @@ prvpane
psapi
pscid
PSECURITY
psexec
psfgao
psfi
PSMODULEPATH
@@ -1533,6 +1544,7 @@ SLGP
sln
SMALLICON
smartphone
smileys
SMTO
SNAPPROCESS
snk
@@ -1875,6 +1887,7 @@ WINDOWPOSCHANGING
WINDOWSBUILDNUMBER
windowssearch
windowssettings
windowsterminal
WINDOWSTYLES
WINDOWSTYLESICON
winerror
@@ -1984,6 +1997,7 @@ Zoneszonabletester
Zoomin
zoomit
ZOOMITX
Zorder
ZXk
ZXNs
zzz
@@ -1995,3 +2009,4 @@ culori
Evercoder
LCh
CIELCh
CLSCTXINPROCALL

View File

@@ -126,16 +126,15 @@ Get-ChildItem -Path $rootPath -Recurse packages.config | ForEach-Object {
}
# Update Directory.Packages.props file
$propsFile = [System.IO.Path]::Combine($rootPath,"Directory.Packages.props")
if (Test-Path $propsFile) {
$file = Read-FileWithEncoding -Path $propsFile
Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object {
$file = Read-FileWithEncoding -Path $_.FullName
$content = $file.Content
if ($content -match '<PackageVersion Include="Microsoft.WindowsAppSDK"') {
$newVersionString = '<PackageVersion Include="Microsoft.WindowsAppSDK" Version="' + $WinAppSDKVersion + '" />'
$oldVersionString = '<PackageVersion Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*" />'
$content = $content -replace $oldVersionString, $newVersionString
Write-FileWithEncoding -Path $propsFile -Content $content -Encoding $file.encoding
Write-Host "Modified " $propsFile
Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
Write-Host "Modified " $_.FullName
}
}
@@ -144,8 +143,8 @@ Get-ChildItem -Path $rootPath -Recurse *.vcxproj | ForEach-Object {
$file = Read-FileWithEncoding -Path $_.FullName
$content = $file.Content
if ($content -match '\\Microsoft.WindowsAppSDK.') {
$newVersionString = '\Microsoft.WindowsAppSDK.' + $WinAppSDKVersion + '\'
$oldVersionString = '\\Microsoft.WindowsAppSDK.[-.0-9a-zA-Z]*\\'
$newVersionString = '\Microsoft.WindowsAppSDK.' + $WinAppSDKVersion
$oldVersionString = '\\Microsoft.WindowsAppSDK.(?=[-.0-9a-zA-Z]*\d)[-.0-9a-zA-Z]*' #positive lookahead for at least a digit
$content = $content -replace $oldVersionString, $newVersionString
Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
Write-Host "Modified " $_.FullName

View File

@@ -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
@@ -530,7 +533,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:

View File

@@ -11,24 +11,36 @@ parameters:
- name: useLatestWebView2
type: boolean
default: false
- name: isUIAutomationPipeline
type: boolean
default: false
jobs:
- job: Test${{ parameters.platform }}${{ parameters.configuration }}
displayName: Test ${{ parameters.platform }} ${{ parameters.configuration }}
timeoutInMinutes: 300
variables:
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 and(eq(parameters.isUIAutomationPipeline, true), eq(parameters.platform, 'x64Win11')) }}:
demands: ImageOverride -equals SHINE-W11-Testing
${{ else }}:
name: SHINE-INT-Testing-arm64
${{ else }}:
${{ if ne(parameters.platform, 'ARM64') }}:
name: SHINE-OSS-Testing-x64
${{ if and(eq(parameters.isUIAutomationPipeline, true), eq(parameters.platform, 'x64Win11')) }}:
demands: ImageOverride -equals SHINE-W11-Testing
${{ else }}:
name: SHINE-OSS-Testing-arm64
steps:
@@ -101,8 +113,17 @@ jobs:
vsTestVersion: 'toolsInstaller'
uiTests: true
rerunFailedTests: true
testAssemblyVer2: |
**\UITests-FancyZones.dll
**\UITests-FancyZonesEditor.dll
!**\obj\**
!**\ref\**
${{ if eq(parameters.isUIAutomationPipeline, true) }}:
testAssemblyVer2: |
**\*UITest*.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
${{ else }}:
testAssemblyVer2: |
**\UITests-FancyZonesEditor.dll
!**\obj\**
!**\ref\**
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
env:
platform: '$(TestPlatform)'

View File

@@ -0,0 +1,87 @@
variables:
- name: runCodesignValidationInjectionBG
value: false
- name: EnablePipelineCache
value: true
- ${{ if eq(parameters.enableMsBuildCaching, true) }}:
- name: EnablePipelineCache
value: true
parameters:
- name: buildPlatforms
type: object
default:
- x64
- arm64
- name: enableMsBuildCaching
type: boolean
default: false
- name: useVSPreview
type: boolean
default: false
- name: useLatestWebView2
type: boolean
default: false
stages:
- ${{ each platform in parameters.buildPlatforms }}:
- stage: Build_${{ platform }}
displayName: Build ${{ platform }}
dependsOn: []
jobs:
- template: job-build-project.yml
parameters:
pool:
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
name: SHINE-INT-L
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
buildConfigurations: [Release]
enablePackageCaching: true
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
runTests: false
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win10
displayName: Test x64Win10
dependsOn:
- Build_${{platform}}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win10
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
isUIAutomationPipeline: true
- ${{ if eq(platform, 'x64') }}:
- stage: Test_x64Win11
displayName: Test x64Win11
dependsOn:
- Build_${{platform}}
jobs:
- template: job-test-project.yml
parameters:
platform: x64Win11
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
isUIAutomationPipeline: true
- ${{ if ne(platform, 'x64') }}:
- stage: Test_${{ platform }}
displayName: Test ${{ platform }}
dependsOn:
- Build_${{platform}}
jobs:
- template: job-test-project.yml
parameters:
platform: ${{ platform }}
configuration: Release
useLatestWebView2: ${{ parameters.useLatestWebView2 }}
isUIAutomationPipeline: true

View File

@@ -1,7 +1,28 @@
$VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -include packages -format xml))
$VSPackages = $VSInstances.instances.instance.packages.package
$LatestVCPackage = ($VSInstances.instances.instance.packages.package | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" })
$LatestVCPackage = ($VSPackages | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" })
$LatestVCToolsVersion = $LatestVCPackage.version;
$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property 'resolvedInstallationPath')
$VCToolsRoot = Join-Path $VSRoot "VC\Tools\MSVC"
# We have observed a few instances where the VC tools package version actually
# differs from the version on the files themselves. We might as well check
# whether the version we just found _actually exists_ before we use it.
# We'll use whichever highest version exists.
$PackageVCToolPath = Join-Path $VCToolsRoot $LatestVCToolsVersion
If ($Null -Eq (Get-Item $PackageVCToolPath -ErrorAction:Ignore)) {
$VCToolsVersions = Get-ChildItem $VCToolsRoot | ForEach-Object {
[Version]$_.Name
} | Sort -Descending
$LatestActualVCToolsVersion = $VCToolsVersions | Select -First 1
If ([Version]$LatestVCToolsVersion -Ne $LatestActualVCToolsVersion) {
Write-Output "VC Tools Mismatch: Directory = $LatestActualVCToolsVersion, Package = $LatestVCToolsVersion"
$LatestVCToolsVersion = $LatestActualVCToolsVersion.ToString(3)
}
}
Write-Output "Latest VCToolsVersion: $LatestVCToolsVersion"
Write-Output "Updating VCToolsVersion environment variable for job"
Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion"

View File

@@ -91,5 +91,4 @@ if ($totalFailures -gt 0) {
}
Write-Host -ForegroundColor Green "All " $referencedFileVersionsPerDll.keys.Count " libraries are mentioned with the same version across the dependencies.`r`n"
exit 0
exit 0

View File

@@ -55,7 +55,7 @@
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250401001" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />

View File

@@ -1472,7 +1472,7 @@ SOFTWARE.
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.22621.2428
- Microsoft.WindowsAppSDK 1.7.250401001
- Microsoft.WindowsAppSDK 1.7.250513003
- Microsoft.WindowsPackageManager.ComInterop 1.10.340
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
- Microsoft.Xaml.Behaviors.Wpf 1.1.39

View File

@@ -706,10 +706,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.System", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj", "{64B88F02-CD88-4ED8-9624-989A800230F9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZones.FuzzTests", "src\modules\fancyzones\FancyZones.FuzzTests\FancyZones.FuzzTests.csproj", "{0217E86E-3476-9946-DE8E-9D200CEBD47A}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "src\modules\cmdpal\CmdPalKeyboardService\CmdPalKeyboardService.vcxproj", "{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseUtils.UITests", "src\modules\MouseUtils\MouseUtils.UITests\MouseUtils.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesEditorUITest", "src\modules\Workspaces\WorkspacesEditorUITest\WorkspacesEditorUITest.csproj", "{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2582,6 +2588,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
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.Build.0 = Debug|ARM64
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|x64.ActiveCfg = Debug|x64
@@ -2596,6 +2618,14 @@ Global
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|ARM64.ActiveCfg = Release|ARM64
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.ActiveCfg = Release|x64
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.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
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.Build.0 = Debug|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.ActiveCfg = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|ARM64.Build.0 = Release|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.ActiveCfg = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2735,7 +2765,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}
@@ -2866,6 +2896,9 @@ 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}
EndGlobalSection

View File

@@ -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");
}
```

View File

@@ -1,18 +0,0 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="ExtensionTargetsAppSdkCppProp">
<Import Project="$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImportsWinAppSdkCppProp" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +0,0 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.WindowsAppSDK.1.7.250401001\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(MsbuildThisFileDirectory)\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
</Project>

View File

@@ -92,6 +92,9 @@ namespace Microsoft.PowerToys.FilePreviewCommon
var run = 0;
var chunksLen = fileSize - QOI_PADDING_LENGTH;
var x = 0;
var rowAdd = bitmapData.Stride - (channels * bitmapData.Width);
for (var dataIndex = 0; dataIndex < dataLength; dataIndex += channels)
{
if (run > 0)
@@ -153,6 +156,14 @@ namespace Microsoft.PowerToys.FilePreviewCommon
bitmapPixel[3] = pixel.A;
}
}
x++;
if (x == bitmapData.Width)
{
// We align dataIndex with the stride
dataIndex += rowAdd;
x = 0;
}
}
bitmap.UnlockBits(bitmapData);

View File

@@ -6,9 +6,10 @@ using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using PowerToys.Interop;
namespace ManagedCommon
@@ -40,25 +41,72 @@ namespace ManagedCommon
/// <param name="isLocalLow">If the process using Logger is a low-privilege process.</param>
public static void InitializeLogger(string applicationLogPath, bool isLocalLow = false)
{
string basePath;
if (isLocalLow)
{
applicationLogPath = Environment.GetEnvironmentVariable("userprofile") + "\\appdata\\LocalLow\\Microsoft\\PowerToys" + applicationLogPath + "\\" + Version;
basePath = Environment.GetEnvironmentVariable("userprofile") + "\\appdata\\LocalLow\\Microsoft\\PowerToys" + applicationLogPath;
}
else
{
applicationLogPath = Constants.AppDataPath() + applicationLogPath + "\\" + Version;
basePath = Constants.AppDataPath() + applicationLogPath;
}
if (!Directory.Exists(applicationLogPath))
string versionedPath = Path.Combine(basePath, Version);
if (!Directory.Exists(versionedPath))
{
Directory.CreateDirectory(applicationLogPath);
Directory.CreateDirectory(versionedPath);
}
var logFilePath = Path.Combine(applicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
var logFilePath = Path.Combine(versionedPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
// Clean up old version log folders
Task.Run(() => DeleteOldVersionLogFolders(basePath, versionedPath));
}
/// <summary>
/// Deletes old version log folders, keeping only the current version's folder.
/// </summary>
/// <param name="basePath">The base path to the log files folder.</param>
/// <param name="currentVersionPath">The path to the current version's log folder.</param>
private static void DeleteOldVersionLogFolders(string basePath, string currentVersionPath)
{
try
{
if (!Directory.Exists(basePath))
{
return;
}
var dirs = Directory.GetDirectories(basePath)
.Select(d => new DirectoryInfo(d))
.OrderBy(d => d.CreationTime)
.Where(d => !string.Equals(d.FullName, currentVersionPath, StringComparison.OrdinalIgnoreCase))
.Take(3)
.ToList();
foreach (var directory in dirs)
{
try
{
Directory.Delete(directory.FullName, true);
LogInfo($"Deleted old log directory: {directory.FullName}");
Task.Delay(500).Wait();
}
catch (Exception ex)
{
LogError($"Failed to delete old log directory: {directory.FullName}", ex);
}
}
}
catch (Exception ex)
{
LogError("Error cleaning up old log folders", ex);
}
}
public static void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)

View 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;
}
}
}

View 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();
}
}
}

View 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();
});
}
}
}

View File

@@ -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);
}
}
}

View 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;
}
}
}

View File

@@ -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>

View 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();
}
});
}
}
}

View 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}.");
}
}
}

View 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();
}
});
}
}
}

View File

@@ -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) =>

View 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();
});
}
}
}

View File

@@ -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);

View 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);
}
}
}

View File

@@ -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",
};
}

View 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; }
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -15,11 +15,13 @@ 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; }
@@ -27,27 +29,34 @@ namespace Microsoft.PowerToys.UITest
private WindowsDriver<WindowsElement>? Driver { get; set; }
private Process? appDriver;
private Process? runner;
private PowerToysModule scope;
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
public SessionHelper(PowerToysModule scope)
{
this.scope = scope;
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
this.locationPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var winAppDriverProcessInfo = new ProcessStartInfo
this.StartWindowsAppDriverApp();
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>
@@ -56,7 +65,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.");
@@ -73,6 +83,11 @@ namespace Microsoft.PowerToys.UITest
{
appDriver?.Kill();
appDriver?.WaitForExit(); // Optional: Wait for the process to exit
if (this.scope == PowerToysModule.PowerToysSettings)
{
runner?.Kill();
runner?.WaitForExit(); // Optional: Wait for the process to exit
}
}
catch (Exception ex)
{
@@ -81,31 +96,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 +120,55 @@ 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
TimeSpan timeout = TimeSpan.FromMinutes(2);
DateTime startTime = DateTime.Now;
int retryCount = 0;
while (true)
{
try
{
var res = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), info);
return res;
}
catch (Exception ex)
{
if (DateTime.Now - startTime > timeout)
{
Console.WriteLine("Timeout reached. Throwing exception.");
throw;
}
TimeSpan retryInterval = retryCount < 2
? TimeSpan.FromSeconds(5)
: TimeSpan.FromSeconds(20);
Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Exception occurred: {ex.Message}, Retrying after {retryInterval.TotalSeconds} seconds...");
Task.Delay(retryInterval).Wait();
retryCount++;
}
}
}
/// <summary>
/// Exit now exe.
/// </summary>
@@ -134,7 +182,7 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
public void RestartScopeExe()
{
ExitExe(sessionPath);
ExitScopeExe();
StartExe(locationPath + sessionPath);
}
@@ -145,5 +193,17 @@ namespace Microsoft.PowerToys.UITest
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);
this.appDriver = Process.Start(winAppDriverProcessInfo);
}
}
}

View File

@@ -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>

View File

@@ -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}");
}
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View File

@@ -152,6 +152,7 @@
</data>
<data name="ClipboardHistoryImage" xml:space="preserve">
<value>Image data</value>
<comment>Label used to represent an image in the clipboard history</comment>
</data>
<data name="ClipboardHistoryItemMoreOptionsButton.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>More options</value>
@@ -194,15 +195,19 @@
</data>
<data name="TranscodeToMp3" xml:space="preserve">
<value>Transcode to .mp3</value>
<comment>Option to transcode audio files to MP3 format</comment>
</data>
<data name="TranscodeToMp4" xml:space="preserve">
<value>Transcode to .mp4 (H.264/AAC)</value>
<comment>Option to transcode video files to MP4 format with H.264 video codec and AAC audio codec</comment>
</data>
<data name="TranscodeErrorGeneral" xml:space="preserve">
<value>An error occurred while transcoding media file</value>
<comment>Error message displayed when media conversion fails for an unknown or general reason</comment>
</data>
<data name="TranscodeErrorUnsupportedCodec" xml:space="preserve">
<value>The media file contains an unsupported codec</value>
<comment>Error message displayed when media conversion fails due to an unsupported codec in the source file</comment>
</data>
<data name="PasteButtonAutomation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Paste</value>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -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);
}
}
}

View File

@@ -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" />

View File

@@ -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);
}

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\WinAppSdkCppPre.props" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
@@ -134,16 +136,24 @@
<ResourceCompile Include="PowerToys.MeasureToolCore.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\WinAppSdkCppPost.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.7.250513003\build\native\Microsoft.WindowsAppSDK.targets'))" />
</Target>
</Project>

View File

@@ -4,5 +4,5 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250401001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
</packages>

View 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();
}
}
}

View File

@@ -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();
}
}
}

View 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();
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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.

View File

@@ -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
}
}
}

View 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);
}
}
}

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

View File

@@ -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];
}
}
}

View File

@@ -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.");
}
}
}
}

View File

@@ -13,6 +13,7 @@
#include <common/utils/winapi_error.h>
#include <common/utils/processApi.h>
#include <common/utils/elevation.h>
#include <common/utils/logger_helper.h>
HINSTANCE g_hInst_MouseWithoutBorders = 0;
@@ -409,9 +410,12 @@ public:
{
app_name = L"MouseWithoutBorders";
app_key = app_name;
std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(app_key));
logFilePath.append(LogSettings::mouseWithoutBordersLogPath);
Logger::init(LogSettings::mouseWithoutBordersLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::mouseWithoutBordersLoggerName);
std::filesystem::path oldLogPath(PTSettingsHelper::get_module_save_folder_location(app_key));
oldLogPath.append("LogsModuleInterface");
LoggerHelpers::delete_old_log_folder(oldLogPath);
try
{

View File

@@ -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");
}
}

View File

@@ -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>

View File

@@ -1,4 +1,4 @@
<Project>
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
@@ -9,7 +9,7 @@
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250401001" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Text.Json" Version="9.0.5" />

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<RootNamespace>TemplateCmdPalExtension</RootNamespace>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>

View File

@@ -11,6 +11,7 @@
<!-- Other merged dictionaries here -->
<ResourceDictionary Source="Styles/Button.xaml" />
<ResourceDictionary Source="Styles/Colors.xaml" />
<ResourceDictionary Source="Styles/TextBlock.xaml" />
<ResourceDictionary Source="Styles/TextBox.xaml" />
<ResourceDictionary Source="Styles/Settings.xaml" />
<ResourceDictionary Source="Controls/Tag.xaml" />

View File

@@ -34,8 +34,13 @@
Orientation="Vertical"
Spacing="4" />
<!-- Template for actions in the mode actions dropdown button -->
<DataTemplate x:Key="ContextMenuViewModelTemplate" x:DataType="viewmodels:CommandContextItemViewModel">
<cmdpalUI:ContextItemTemplateSelector
x:Key="ContextItemTemplateSelector"
Critical="{StaticResource CriticalContextMenuViewModelTemplate}"
Default="{StaticResource DefaultContextMenuViewModelTemplate}" />
<!-- Template for context items in the context item menu -->
<DataTemplate x:Key="DefaultContextMenuViewModelTemplate" x:DataType="viewmodels:CommandContextItemViewModel">
<Grid AutomationProperties.Name="{x:Bind Title, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
@@ -63,6 +68,38 @@
Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" />
</Grid>
</DataTemplate>
<!-- Template for context items flagged as critical -->
<DataTemplate x:Key="CriticalContextMenuViewModelTemplate" x:DataType="viewmodels:CommandContextItemViewModel">
<Grid AutomationProperties.Name="{x:Bind Title, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<cpcontrols:IconBox
Width="16"
Height="16"
Margin="4,0,0,0"
HorizontalAlignment="Left"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource ContextItemTitleTextBlockCriticalStyle}"
Text="{x:Bind Title, Mode=OneWay}" />
<TextBlock
Grid.Column="2"
Margin="16,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{StaticResource ContextItemCaptionTextBlockCriticalStyle}"
Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" />
</Grid>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
@@ -237,7 +274,7 @@
Margin="-16,-12,-16,-12"
IsItemClickEnabled="True"
ItemClick="CommandsDropdown_ItemClick"
ItemTemplate="{StaticResource ContextMenuViewModelTemplate}"
ItemTemplateSelector="{StaticResource ContextItemTemplateSelector}"
ItemsSource="{x:Bind ViewModel.ContextMenu.FilteredItems, Mode=OneWay}"
KeyDown="CommandsDropdown_KeyDown"
SelectionMode="Single">

View File

@@ -0,0 +1,21 @@
// 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.CmdPal.UI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI;
internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
{
public DataTemplate? Default { get; set; }
public DataTemplate? Critical { get; set; }
protected override DataTemplate? SelectTemplateCore(object item)
{
return ((CommandContextItemViewModel)item).IsCritical ? Critical : Default;
}
}

View File

@@ -39,9 +39,7 @@
</PropertyGroup>
<!-- BODGY: XES Versioning and WinAppSDK get into a fight about the app manifest, which breaks WinAppSDK. -->
<Target Name="RearrangeXefVersioningAndWinAppSDKResourceGeneration"
DependsOnTargets="GetNewAppManifestValues;CreateWinRTRegistration"
BeforeTargets="XesWriteVersionInfoResourceFile">
<Target Name="RearrangeXefVersioningAndWinAppSDKResourceGeneration" DependsOnTargets="GetNewAppManifestValues;CreateWinRTRegistration" BeforeTargets="XesWriteVersionInfoResourceFile">
<PropertyGroup>
<!-- XES uses this property to store the "final" location of the app manifest, before it erases it.
We have to update it to the value WinAppSDK set it to. -->

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:CommunityToolkit.WinUI.Converters">
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<Style
x:Key="ContextItemTitleTextBlockCriticalStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Property="FontWeight" Value="Normal" />
</Style>
<Style
x:Key="ContextItemCaptionTextBlockCriticalStyle"
BasedOn="{StaticResource CaptionTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</Style>
</ResourceDictionary>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup>
<PathToRoot>..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250401001</WasdkNuget>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003</WasdkNuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />
<PropertyGroup Label="Globals">

View File

@@ -1,6 +1,6 @@
# ![cmdpal logo](./Microsoft.CmdPal.UI/Assets/Stable/StoreLogo.scale-100.png) Command Palette
Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at it's core, the Command Palette is your one-stop launcher to start _anything_.
Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_.
By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.

View File

@@ -35,7 +35,7 @@ public static class CalculateHelper
{
if (string.IsNullOrWhiteSpace(input))
{
throw new ArgumentNullException(paramName: nameof(input));
return false;
}
if (!RegValidExpressChar.IsMatch(input))

View File

@@ -23,6 +23,9 @@ public static partial class QueryHelper
CultureInfo inputCulture = settings.InputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
CultureInfo outputCulture = settings.OutputUseEnglishFormat ? new CultureInfo("en-us") : CultureInfo.CurrentCulture;
// In case the user pastes a query with a leading =
query = query.TrimStart('=');
// Happens if the user has only typed the action key so far
if (string.IsNullOrEmpty(query))
{
@@ -32,6 +35,11 @@ public static partial class QueryHelper
NumberTranslator translator = NumberTranslator.Create(inputCulture, new CultureInfo("en-US"));
var input = translator.Translate(query.Normalize(NormalizationForm.FormKC));
if (string.IsNullOrWhiteSpace(input))
{
return ErrorHandler.OnError(isFallbackSearch, query, Properties.Resources.calculator_expression_empty);
}
if (!CalculateHelper.InputValid(input))
{
return null;

View File

@@ -132,6 +132,15 @@ namespace Microsoft.CmdPal.Ext.Calc.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Please enter an expression.
/// </summary>
public static string calculator_expression_empty {
get {
return ResourceManager.GetString("calculator_expression_empty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Expression wrong or incomplete.
/// </summary>

View File

@@ -199,4 +199,7 @@
<data name="calculator_copy_binary" xml:space="preserve">
<value>Copy binary</value>
</data>
<data name="calculator_expression_empty" xml:space="preserve">
<value>Please enter an expression</value>
</data>
</root>

View File

@@ -1,144 +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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Ext.System.Helpers;
[SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")]
public sealed class Native
{
public enum HRESULT : uint
{
/// <summary>
/// Operation successful.
/// </summary>
S_OK = 0x00000000,
/// <summary>
/// Operation successful. (negative condition/no operation)
/// </summary>
S_FALSE = 0x00000001,
/// <summary>
/// Not implemented.
/// </summary>
E_NOTIMPL = 0x80004001,
/// <summary>
/// No such interface supported.
/// </summary>
E_NOINTERFACE = 0x80004002,
/// <summary>
/// Pointer that is not valid.
/// </summary>
E_POINTER = 0x80004003,
/// <summary>
/// Operation aborted.
/// </summary>
E_ABORT = 0x80004004,
/// <summary>
/// Unspecified failure.
/// </summary>
E_FAIL = 0x80004005,
/// <summary>
/// Unexpected failure.
/// </summary>
E_UNEXPECTED = 0x8000FFFF,
/// <summary>
/// General access denied error.
/// </summary>
E_ACCESSDENIED = 0x80070005,
/// <summary>
/// Handle that is not valid.
/// </summary>
E_HANDLE = 0x80070006,
/// <summary>
/// Failed to allocate necessary memory.
/// </summary>
E_OUTOFMEMORY = 0x8007000E,
/// <summary>
/// One or more arguments are not valid.
/// </summary>
E_INVALIDARG = 0x80070057,
/// <summary>
/// The operation was canceled by the user. (Error source 7 means Win32.)
/// </summary>
/// <SeeAlso href="https://learn.microsoft.com/windows/win32/debug/system-error-codes--1000-1299-"/>
/// <SeeAlso href="https://en.wikipedia.org/wiki/HRESULT"/>
E_CANCELLED = 0x800704C7,
}
public static class ShellItemTypeConstants
{
/// <summary>
/// Guid for type IShellItem.
/// </summary>
public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe");
/// <summary>
/// Guid for type IShellItem2.
/// </summary>
public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93");
}
/// <summary>
/// The following are ShellItem DisplayName types.
/// </summary>
[Flags]
public enum SIGDN : uint
{
NORMALDISPLAY = 0,
PARENTRELATIVEPARSING = 0x80018001,
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000,
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
public interface IShellItem
{
void BindToHandler(
nint pbc,
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
out nint ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
}
/// <summary>
/// <see href="https://learn.microsoft.com/windows/win32/stg/stgm-constants">see all STGM values</see>
/// </summary>
[Flags]
public enum STGM : long
{
READ = 0x00000000L,
WRITE = 0x00000001L,
READWRITE = 0x00000002L,
CREATE = 0x00001000L,
}
}

View File

@@ -32,6 +32,11 @@
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="NativeMethods.txt" />
<AdditionalFiles Include="NativeMethods.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false,
"comInterop": {
"preserveSigMethods": [ "*" ]
}
}

View File

@@ -20,20 +20,25 @@ public class WindowsPackageManagerStandardFactory : WindowsPackageManagerFactory
protected override T CreateInstance<T>(Guid clsid, Guid iid)
{
var pUnknown = IntPtr.Zero;
try
unsafe
{
var hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_ALL, iid, out var result);
Marshal.ThrowExceptionForHR(hr);
pUnknown = Marshal.GetIUnknownForObject(result);
return MarshalGeneric<T>.FromAbi(pUnknown);
}
finally
{
// CoCreateInstance and FromAbi both AddRef on the native object.
// Release once to prevent memory leak.
if (pUnknown != IntPtr.Zero)
try
{
Marshal.Release(pUnknown);
var hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_ALL, iid, out var result);
Marshal.ThrowExceptionForHR(hr);
pUnknown = new IntPtr(result);
return MarshalGeneric<T>.FromAbi(pUnknown);
}
finally
{
// CoCreateInstance and FromAbi both AddRef on the native object.
// Release once to prevent memory leak.
if (pUnknown != IntPtr.Zero)
{
Marshal.Release(pUnknown);
}
}
}
}

View File

@@ -36,6 +36,7 @@ internal sealed class ContextMenuHelper
contextMenu.Add(new CommandContextItem(new KillProcessCommand(windowData))
{
RequestedShortcut = KeyChordHelpers.FromModifiers(true, false, false, false, (int)VirtualKey.Delete, 0),
IsCritical = true,
});
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
@@ -11,18 +12,16 @@ namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
/// Interface for accessing Virtual Desktop Manager.
/// Code used from <see href="https://learn.microsoft.com/archive/blogs/winsdk/virtual-desktop-switching-in-windows-10"./>
/// </summary>
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[GeneratedComInterface]
[Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[System.Security.SuppressUnmanagedCodeSecurity]
internal interface IVirtualDesktopManager
public partial interface IVirtualDesktopManager
{
[PreserveSig]
int IsWindowOnCurrentVirtualDesktop([In] IntPtr hTopLevelWindow, [Out] out int onCurrentDesktop);
int IsWindowOnCurrentVirtualDesktop(IntPtr hTopLevelWindow, out int onCurrentDesktop);
[PreserveSig]
int GetWindowDesktopId([In] IntPtr hTopLevelWindow, [Out] out Guid desktop);
int GetWindowDesktopId(IntPtr hTopLevelWindow, out Guid desktop);
[PreserveSig]
int MoveWindowToDesktop([In] IntPtr hTopLevelWindow, [MarshalAs(UnmanagedType.LPStruct)][In] Guid desktop);
int MoveWindowToDesktop(IntPtr hTopLevelWindow, ref Guid desktop);
}

View File

@@ -13,7 +13,7 @@ using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessage
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
[SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")]
public static class NativeMethods
public static partial class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int EnumWindows(EnumWindowsProc callPtr, IntPtr lParam);
@@ -99,31 +99,14 @@ public static class NativeMethods
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetFirmwareType(ref FirmwareType FirmwareType);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
[DllImport("user32")]
public static extern void LockWorkStation();
[DllImport("Powrprof.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
public static extern uint SHEmptyRecycleBin(IntPtr hWnd, uint dwFlags);
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern HRESULT SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern HRESULT SHCreateStreamOnFileEx(string fileName, STGM grfMode, uint attributes, bool create, System.Runtime.InteropServices.ComTypes.IStream reserved, out System.Runtime.InteropServices.ComTypes.IStream stream);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
[DllImport("rpcrt4.dll")]
public static extern int UuidCreateSequential(out GUIDDATA Uuid);
[LibraryImport("ole32.dll")]
[return: MarshalAs(UnmanagedType.U4)]
public static partial uint CoCreateInstance(
ref Guid rclsid,
IntPtr pUnkOuter,
uint dwClsContext,
ref Guid riid,
out IntPtr rReturnedComObject);
}
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "These are the names used by win32.")]
@@ -161,19 +144,6 @@ public static class Win32Constants
public const int RPC_S_UUID_LOCAL_ONLY = 0x720;
}
public static class ShellItemTypeConstants
{
/// <summary>
/// Guid for type IShellItem.
/// </summary>
public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe");
/// <summary>
/// Guid for type IShellItem2.
/// </summary>
public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93");
}
public enum HRESULT : uint
{
/// <summary>
@@ -1124,26 +1094,6 @@ public enum ExtendedWindowStyles : uint
WS_EX_NOACTIVATE = 0x8000000,
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
public interface IShellItem
{
void BindToHandler(
IntPtr pbc,
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
}
/// <summary>
/// The following are ShellItem DisplayName types.
/// </summary>

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Text;
using Microsoft.CmdPal.Ext.WindowWalker.Properties;
@@ -48,6 +49,10 @@ public class VirtualDesktopHelper
/// </summary>
private readonly List<Guid> _availableDesktops = [];
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private readonly uint CLSCTXINPROCALL = 0x17;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
/// <summary>
/// Id of the current visible Desktop.
/// </summary>
@@ -55,15 +60,27 @@ public class VirtualDesktopHelper
private static readonly CompositeFormat VirtualDesktopHelperDesktop = System.Text.CompositeFormat.Parse(Properties.Resources.VirtualDesktopHelper_Desktop);
private Guid iVirtualDesktopManagerCLSID = new("aa509086-5ca9-4c25-8f95-589d3c07b48a");
private Guid iVirtualDesktopManagerIID = new("a5cd92ff-29be-454c-8d04-d82879fb3f1b");
/// <summary>
/// Initializes a new instance of the <see cref="VirtualDesktopHelper"/> class.
/// </summary>
/// <param name="desktopListUpdate">Setting to configure if the list of available desktops should update automatically or only when calling <see cref="UpdateDesktopList"/>. Per default this is set to manual update (false) to have less registry queries.</param>
public VirtualDesktopHelper(bool desktopListUpdate = false)
{
var cw = new StrategyBasedComWrappers();
try
{
_virtualDesktopManager = (IVirtualDesktopManager)new CVirtualDesktopManager();
var hr = NativeMethods.CoCreateInstance(ref this.iVirtualDesktopManagerCLSID, nint.Zero, CLSCTXINPROCALL, ref iVirtualDesktopManagerIID, out var virtualDesktopManagerPtr);
if (hr != 0)
{
throw new ArgumentException($"Failed to create IVirtualDesktopManager instance. HR: 0x{hr:X}");
}
_virtualDesktopManager = (IVirtualDesktopManager)cw.GetOrCreateObjectForComInstance(virtualDesktopManagerPtr, CreateObjectFlags.None);
}
catch (COMException ex)
{
@@ -409,7 +426,7 @@ public class VirtualDesktopHelper
/// <param name="hWindow">Handle of the top level window.</param>
/// <param name="desktopId">Guid of the target desktop.</param>
/// <returns><see langword="True"/> on success and <see langword="false"/> on failure.</returns>
public bool MoveWindowToDesktop(IntPtr hWindow, in Guid desktopId)
public bool MoveWindowToDesktop(IntPtr hWindow, ref Guid desktopId)
{
if (_virtualDesktopManager == null)
{
@@ -417,7 +434,7 @@ public class VirtualDesktopHelper
return false;
}
var hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, desktopId);
var hr = _virtualDesktopManager.MoveWindowToDesktop(hWindow, ref desktopId);
if (hr != (int)HRESULT.S_OK)
{
ExtensionHost.LogMessage(new LogMessage() { Message = "VirtualDesktopHelper.MoveWindowToDesktop() failed: An exception was thrown when moving the window ({hWindow}) to another desktop ({desktopId})." });
@@ -455,7 +472,7 @@ public class VirtualDesktopHelper
}
Guid newDesktop = _availableDesktops[windowDesktopNumber - 1];
return MoveWindowToDesktop(hWindow, newDesktop);
return MoveWindowToDesktop(hWindow, ref newDesktop);
}
/// <summary>
@@ -486,7 +503,7 @@ public class VirtualDesktopHelper
}
Guid newDesktop = _availableDesktops[windowDesktopNumber + 1];
return MoveWindowToDesktop(hWindow, newDesktop);
return MoveWindowToDesktop(hWindow, ref newDesktop);
}
/// <summary>

View File

@@ -3,18 +3,12 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using ManagedCommon;
using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.UI;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands;
@@ -68,7 +62,23 @@ internal sealed partial class LaunchProfileAsAdminCommand : InvokableCommand
private void Launch(string id, string profile)
{
var appManager = new ApplicationActivationManager();
ComWrappers cw = new StrategyBasedComWrappers();
var hr = NativeHelpers.CoCreateInstance(ref NativeHelpers.ApplicationActivationManagerCLSID, IntPtr.Zero, NativeHelpers.CLSCTXINPROCALL, ref NativeHelpers.ApplicationActivationManagerIID, out var appManagerPtr);
if (hr != 0)
{
throw new ArgumentException($"Failed to create IApplicationActivationManager instance. HR: 0x{hr:X}");
}
var appManager = (IApplicationActivationManager)cw.GetOrCreateObjectForComInstance(
appManagerPtr, CreateObjectFlags.None);
if (appManager == null)
{
throw new ArgumentException("Failed to get IApplicationActivationManager interface");
}
const ActivateOptions noFlags = ActivateOptions.None;
var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake);
try

View File

@@ -3,18 +3,12 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using ManagedCommon;
using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.UI;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Commands;
@@ -38,7 +32,22 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
private void Launch(string id, string profile)
{
var appManager = new ApplicationActivationManager();
ComWrappers cw = new StrategyBasedComWrappers();
var hr = NativeHelpers.CoCreateInstance(ref NativeHelpers.ApplicationActivationManagerCLSID, IntPtr.Zero, NativeHelpers.CLSCTXINPROCALL, ref NativeHelpers.ApplicationActivationManagerIID, out var appManagerPtr);
if (hr != 0)
{
throw new ArgumentException($"Failed to create IApplicationActivationManager instance. HR: 0x{hr:X}");
}
var appManager = (IApplicationActivationManager)cw.GetOrCreateObjectForComInstance(
appManagerPtr, CreateObjectFlags.None);
if (appManager == null)
{
throw new ArgumentException("Failed to get IApplicationActivationManager interface");
}
const ActivateOptions noFlags = ActivateOptions.None;
var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab, _openQuake);
try

View File

@@ -1,24 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
// Application Activation Manager Class
[ComImport]
[Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
public class ApplicationActivationManager : IApplicationActivationManager
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
@@ -18,14 +19,13 @@ public enum ActivateOptions
}
// ApplicationActivationManager
[ComImport]
[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
[Guid("2e941141-7f97-4756-ba1d-9decde894a3d")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IApplicationActivationManager
public partial interface IApplicationActivationManager
{
IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
void ActivateApplication(string appUserModelId, string arguments, ActivateOptions options, out uint processId);
IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
void ActivateForFile(string appUserModelId, IntPtr /*IShellItemArray* */ itemArray, string verb, out uint processId);
IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
void ActivateForProtocol(string appUserModelId, IntPtr /* IShellItemArray* */itemArray, out uint processId);
}

View File

@@ -0,0 +1,32 @@
// 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.CmdPal.Ext.WindowsTerminal.Helpers;
public sealed partial class NativeHelpers
{
public const uint CLSCTXINPROCALL = 0x17;
[LibraryImport("ole32.dll")]
public static partial uint CoCreateInstance(
ref Guid rclsid,
IntPtr pUnkOuter,
uint dwClsContext,
ref Guid riid,
out IntPtr rReturnedComObject);
#pragma warning disable CA2211 // Non-constant fields should not be visible
#pragma warning disable SA1401 // Fields should be private
public static Guid ApplicationActivationManagerCLSID = new Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C");
public static Guid ApplicationActivationManagerIID = new Guid("2e941141-7f97-4756-ba1d-9decde894a3d");
#pragma warning restore SA1401 // Fields should be private
#pragma warning restore CA2211 // Non-constant fields should not be visible
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PathToRoot>..\..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250401001</WasdkNuget>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.7.250513003</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428</WindowsSdkBuildToolsNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2903.40</WebView2Nuget>

View File

@@ -3,5 +3,5 @@
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250401001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
</packages>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.FuzzTest.props" />
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\tests\FancyZones.FuzzTests\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\editor\FancyZonesEditor\Utils\ParsingResult.cs" Link="ParsingResult.cs" />
<Compile Include="..\FancyZonesEditorCommon\Data\CustomLayouts.cs" Link="CustomLayouts.cs" />
<Compile Include="..\FancyZonesEditorCommon\Data\EditorData`1.cs" Link="EditorData`1.cs" />
<Compile Include="..\FancyZonesEditorCommon\Data\LayoutDefaultSettings.cs" Link="LayoutDefaultSettings.cs" />
<Compile Include="..\FancyZonesEditorCommon\Utils\DashCaseNamingPolicy.cs" Link="DashCaseNamingPolicy.cs" />
<Compile Include="..\FancyZonesEditorCommon\Utils\IOUtils.cs" Link="IOUtils.cs" />
<Compile Include="..\FancyZonesEditorCommon\Utils\StringUtils.cs" Link="StringUtils.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
<ItemGroup>
<Content Include="OneFuzzConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -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.Text.Json;
using FancyZonesEditorCommon.Data;
using FancyZonesEditorCommon.Utils;
using static FancyZonesEditorCommon.Data.CustomLayouts;
namespace FancyZones.FuzzTests
{
public class FuzzTests
{
public static void FuzzGridFromJsonElement(ReadOnlySpan<byte> input)
{
if (input.Length < 4)
{
return;
}
int inputData = BitConverter.ToInt32(input.Slice(0, 4));
// mock user input for custom-layouts.json
string mockCustomLayouts = $@"{{""custom-layouts"": [{{
""uuid"": ""{{B8C275E-A7BC-485F-A35C-67B69164F51F}}"",
""name"": ""Custom layout 1"",
""type"": ""grid"",
""info"": {{
""rows"": {inputData},
""columns"": {inputData},
""rows-percentage"": [ {inputData} ],
""columns-percentage"": [ {inputData}, {inputData}, {inputData} ],
""cell-child-map"": [ [{inputData}, {inputData}, {inputData}] ],
""show-spacing"": true,
""spacing"": {inputData},
""sensitivity-radius"": {inputData}
}}
}}]}}";
CustomLayoutListWrapper wrapper;
try
{
wrapper = JsonSerializer.Deserialize<CustomLayoutListWrapper>(mockCustomLayouts, JsonOptions);
}
catch (JsonException)
{
return;
}
List<CustomLayouts.CustomLayoutWrapper> customLayouts = wrapper.CustomLayouts;
if (customLayouts == null)
{
return;
}
// Get Layout Info from mockCustomLayouts
foreach (var zoneSet in customLayouts)
{
if (zoneSet.Uuid == null || zoneSet.Uuid.Length == 0)
{
return;
}
CustomLayouts deserializer = new CustomLayouts();
// Fuzzing the deserializer
_ = deserializer.GridFromJsonElement(zoneSet.Info.GetRawText());
}
}
private static JsonSerializerOptions JsonOptions
{
get
{
return new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
WriteIndented = true,
};
}
}
}
}

View File

@@ -0,0 +1,5 @@
// 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.
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

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