Files
PowerToys/doc/devdocs
Gleb Khmyznikov 74e6c3ad79 [UITests] New framework around WinApp CLI, no WinAppDriver or Selenium. (#48467)
# Add winappcli-based UI test harness (no WinAppDriver / Selenium)

## Summary

Introduces a new UI test harness — `Microsoft.PowerToys.UITest.Next` —
that drives PowerToys
modules through Microsoft's
[winappcli](https://github.com/microsoft/WinAppCli) (UI Automation
CLI) instead of WinAppDriver + Selenium. Engine is a single executable
shelled out from
C#; no third-party NuGet packages, no driver process, no Appium server.
Adds two real consumers:
a full ColorPicker end-to-end scenario and a Settings shell navigation
smoke test.

This is opt-in and additive — the existing `UITestAutomation` library
and the
WinAppDriver-based test projects are untouched. Both can coexist while
we evaluate the new
harness.

Inspired in part by
[#48414](https://github.com/microsoft/PowerToys/pull/48414), which lands
the same architectural bet (winappcli, AutomationId selectors, no
WinAppDriver) at a smaller
scope. This PR generalizes it into a reusable library.

## Why

WinAppDriver + Selenium is a legacy pre-agentic solution that is no
longer actively maintained. It's unreliable, heavyweight, and slow. To
achieve 100% UI test coverage, we should leverage modern, reliable
solutions, and WinApp CLI is a strong candidate.

## What's in this PR

### Harness library —
[`src/common/UITestAutomation.Next/`](src/common/UITestAutomation.Next/)

| File | Purpose |
|---|---|
| [`WinappCli.cs`](src/common/UITestAutomation.Next/WinappCli.cs) |
Process wrapper around `winapp.exe`. `Invoke` / `InvokeAssertSuccess` /
`InvokeJson` / `IsAvailable` / `TryResolveExecutable`. `Result` carries
the args and emits `DescribeFailure()` like `winapp ui invoke X -w 12345
-> exit 1; stderr: ...` |
| [`Session.cs`](src/common/UITestAutomation.Next/Session.cs) | Test
session, scoped by either HWND (`-w`) or process (`-a`) via
`TargetScope`. `Find<T>` / `FindAll<T>` / `Inspect` / `Screenshot` /
`SendKeys`. `Session.FromProcess(...)` factory for the
single-window-per-process case |
|
[`SessionHelper.cs`](src/common/UITestAutomation.Next/SessionHelper.cs)
| Owns the launch + window-readiness flow. Static `EnsureRunning(scope,
timeout)` returns whether the call had to launch (so cleanup only kills
what we started). Uses `UseShellExecute=true` so child handles don't
keep MSTest hanging |
| [`UITestBase.cs`](src/common/UITestAutomation.Next/UITestBase.cs) |
MSTest base class. Pre-flights `WinappCli.IsAvailable()` once per
process and fails fast with the install hint if `winapp.exe` isn't on
PATH |
| [`Element/*.cs`](src/common/UITestAutomation.Next/Element/) |
`Element`, `Button`, `ToggleSwitch`, `TextBox`, `NavigationViewItem`,
`Window`. `Click` / `MouseClick` / `Focus` / `GetProperty` / `GetValue`
/ `HelpText` / `WaitForProperty` / `WaitForGone` plus coords
(`X`/`Y`/`Width`/`Height`) |
| [`By.cs`](src/common/UITestAutomation.Next/By.cs) | `By.Name` /
`By.AccessibilityId` / `By.Id` / `By.Slug` |
| [`Windows.cs`](src/common/UITestAutomation.Next/Windows.cs) |
`WindowsFinder.ListAll` / `ListByApp` / `WaitForWindowByApp` /
`WaitForWindowByProcess`. Notes the winappcli bug where unfiltered
`list-windows` drops untitled windows |
|
[`WindowControl.cs`](src/common/UITestAutomation.Next/WindowControl.cs)
| Tolerant Win32 helpers — `TryCloseByApp` / `TryFocusByApp` /
`SafeCloseAndFocus` / `TryKillProcess` — for `finally` blocks |
|
[`KeyboardHelper.cs`](src/common/UITestAutomation.Next/KeyboardHelper.cs)
| Hybrid `keybd_event` + `SendKeys.SendWait` chord sender — required for
global PowerToys hotkeys |
| [`MouseHelper.cs`](src/common/UITestAutomation.Next/MouseHelper.cs) |
`MoveTo` / `LeftClick` / `RightClick` / `LeftClickAt` Win32 wrappers |
|
[`ClipboardHelper.cs`](src/common/UITestAutomation.Next/ClipboardHelper.cs)
| STA-thread `Clipboard` access with `WaitForText` |
|
[`ModuleConfigData.cs`](src/common/UITestAutomation.Next/ModuleConfigData.cs)
| `PowerToysModule` enum + path/process-name resolution |

### Tests


**[`src/modules/colorPicker/ColorPicker.UITests/`](src/modules/colorPicker/ColorPicker.UITests/)**
— replaces the previous empty `UITest-ColorPicker` stub. One test,
[`ColorPickerEndToEndTests.NavigateReadShortcutActivateAndCapture`](src/modules/colorPicker/ColorPicker.UITests/ColorPickerEndToEndTests.cs),
drives the full E2E:
1. Navigate to the Color Picker page via the dashboard utilities stack
2. Toggle the module OFF, verify `PowerToys.ColorPickerUI` exits; toggle
ON, verify it respawns
3. Read the activation shortcut from the page's `ShortcutControl`
(`EditButton.HelpText`)
4. Clear clipboard, park cursor, send the chord
5. Wait for the picker overlay window
6. Read the displayed HEX from a hidden XAML automation peer (see below)
7. Left-click to capture; assert the clipboard value matches the peer's
HEX
8. Wait for the editor window and assert the captured color appears in
its tree


**[`src/settings-ui/Settings.UITests/`](src/settings-ui/Settings.UITests/)**
— `SettingsNavigationSmokeTests.NavigationItem_NavigatesWithoutCrashing`
is one `[TestMethod]` parameterized with `[DynamicData]`, producing 31
discrete results — one per `NavigationViewItem` in
[`ShellPage.xaml`](src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml).
For each item: navigate, settle 250ms, assert `PowerToys.Settings` is
still alive. Catches FailFast regressions in
`ShellViewModel.Frame_NavigationFailed` that pure-logic unit tests can't
reach (the failure path needs a `NavigationFailedEventArgs` which is a
sealed WinRT projection).

### Product change


**[`src/modules/colorPicker/ColorPickerUI/Views/MainView.xaml`](src/modules/colorPicker/ColorPickerUI/Views/MainView.xaml)**
— adds a hidden `TextBlock` automation peer:

```xml
<TextBlock
    x:Name="ColorHexAutomationPeer"
    AutomationProperties.AutomationId="ColorHexAutomationPeer"
    IsHitTestVisible="False"
    Opacity="0"
    Text="{Binding ColorText}" />
```

The visible `ColorTextBlock` has `AutomationProperties.Name="{Binding
ColorName}"`, which masks the HEX value in the UIA tree (you see "White"
instead of `#FFFFFF`). This zero-impact peer mirrors `ColorText` so
tests can read the actually-displayed HEX. `Opacity=0` +
`IsHitTestVisible=False` keep it out of the visual layout and out of
accessibility focus.

### Project wiring

- [`PowerToys.slnx`](PowerToys.slnx) — registers `UITestAutomation.Next`
under `/common/`, `ColorPicker.UITests` under
`/modules/colorpicker/Tests/`, and `Settings.UITests` under
`/settings-ui/Tests/`. Original `UITest-ColorPicker` stub csproj
removed.
-
[`.github/actions/spell-check/expect.txt`](.github/actions/spell-check/expect.txt)
— adds `winapp` / `winappcli`.

### Not in this PR

- No pipeline changes. `winapp.exe` is expected to be pre-staged on the
test agent image. If it's missing, `UITestBase` fails the first test
with the install hint (`winget install Microsoft.winappcli`) rather than
producing 30 opaque per-test errors.
- No changes to the legacy `UITestAutomation` library or any of the
existing `*.UITests` projects.

## Validation

- All three projects build clean on `x64|Debug` (empty
`build.<config>.<plat>.errors.log`):
  - `src/common/UITestAutomation.Next/`
  - `src/modules/colorPicker/ColorPicker.UITests/`
  - `src/settings-ui/Settings.UITests/`
- Both tests run in Test Explorer / `dotnet test` via
Microsoft.Testing.Platform (already enabled repo-wide in
`Directory.Build.props`).
- Local runs: ColorPicker E2E green; Settings smoke green across all 31
nav items.
- `winapp 0.3.2` from `winget install Microsoft.winappcli`.

## Notes for reviewers

- **`UseShellExecute = true`** in `SessionHelper.EnsureRunning` is
intentional — `false` makes child processes inherit the test host's
stdin/stdout/stderr handles, which keeps MTP/Test Explorer marking the
run as "in progress" until the spawned PowerToys exits.
- **Process-scope (`-a`) targeting** in the Settings smoke test handles
single-instance handoff: the EXE you launch may exit with code 0
immediately after signalling an existing owner, so the alive check uses
`Process.GetProcessesByName` rather than the launcher PID.
- **AutomationId-only selectors** in the Settings smoke list keep the
test localization-independent. Parent groups have
`SelectsOnInvoked="False"` and only expand on click — `Element.Click`
tries `InvokePattern → TogglePattern → SelectionItemPattern →
ExpandCollapsePattern` so the same call works for both leaves and
groups.
- **Untitled-window discovery**: filtered `winapp ui list-windows -a
<name>` returns windows that the unfiltered call drops (e.g. ColorPicker
editor). `WindowsFinder.ListByApp` uses the filtered form. Reported
upstream.

## Before Merge

- Add the `winappcli` install step to the UI-test pipeline.
2026-07-08 13:58:08 +08:00
..
2026-02-11 22:59:53 +01:00
2026-05-21 17:27:52 +02:00
2023-03-21 10:27:29 +01:00

PowerToys Developer Documentation

Welcome to the PowerToys developer documentation. This documentation provides information for developers who want to contribute to PowerToys or understand how it works.

Getting Started

Prerequisites

  1. Windows 10 April 2018 Update (version 1803) or newer
  2. Visual Studio 2026 (recommended) or Visual Studio 2022 17.4+ with the following workloads/components:
    • Desktop Development with C++
    • WinUI application development
    • .NET desktop development
    • Windows 10 SDK (10.0.22621.0)
    • Windows 11 SDK (10.0.26100.3916)
  3. .NET 8 SDK
  4. Enable long paths in Windows (see Enable Long Paths for details)

Tip: You can install Visual Studio with all required workloads automatically using the WinGet configuration files in the repository:

winget configure .config\configuration.winget

Pick the file that matches your VS edition (e.g., configuration.vsProfessional.winget or configuration.vsEnterprise.winget).

Fork, Clone, and Set Up

  1. Fork the repo on GitHub if you haven't already
  2. Clone your fork locally
  3. Run the automated setup script (recommended):
.\tools\build\setup-dev-environment.ps1

This script will:

  • Enable Windows long path support (requires administrator privileges)
  • Enable Windows Developer Mode (requires administrator privileges)
  • Guide you through installing required Visual Studio components from .vsconfig
  • Initialize git submodules

Run with -Help to see all available options.

Manual setup (if you prefer not to use the script)

Install Visual Studio dependencies

  1. Open the PowerToys.slnx file.
  2. If you see a dialog that says install extra components in the solution explorer pane, click install

Alternatively, import the .vsconfig file from the repository root using Visual Studio Installer to install all required workloads.

Initialize submodules

This is a one-time step required before you can compile most parts of PowerToys.

  1. Open a terminal
  2. Navigate to the folder you cloned PowerToys to.
  3. Run git submodule update --init --recursive

Building

Using Visual Studio

  • Open PowerToys.slnx in Visual Studio.
  • In the Solutions Configuration drop-down menu select Release or Debug.
  • From the Build menu choose Build Solution, or press Control+Shift+b on your keyboard.
  • The build process may take several minutes depending on your computer's performance. Once it completes, the PowerToys binaries will be in your repo under x64\Release\.
    • You can run x64\Release\PowerToys.exe directly without installing PowerToys, but some modules (i.e. PowerRename, ImageResizer, File Explorer extension etc.) will not be available unless you also build the installer and install PowerToys.

Using Command Line

You can also build from the command line using the provided scripts in tools\build\:

# Build the full solution (auto-detects platform)
.\tools\build\build.ps1

# Build with specific configuration
.\tools\build\build.ps1 -Platform x64 -Configuration Release

# Build only essential projects (runner + settings) for faster iteration
.\tools\build\build-essentials.ps1

# Build everything including the installer (Release only)
.\tools\build\build-installer.ps1

Debugging

See Debugging for detailed debugging techniques, including Visual Studio setup, attaching to child processes, and troubleshooting build errors.

Creating a New PowerToy

See Creating a New PowerToy for an end-to-end guide covering module architecture, settings integration, installer packaging, and testing.

Building Command Palette Extensions

If you want to build your own extensions for Command Palette, check out the Command Palette extensibility documentation. It covers how to create, package, and distribute custom extensions that integrate with Command Palette.

Development Guidelines

Rules

  • Follow the pattern of what you already see in the code.
  • Coding style.
  • Try to package new functionality/components into libraries that have nicely defined interfaces.
  • Package new functionality into classes or refactor existing functionality into a class as you extend the code.
  • When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.

GitHub Workflow

  • Before starting to work on a fix/feature, make sure there is an open issue to track the work.
  • Add the In progress label to the issue, if not already present. Also add a Cost-Small/Medium/Large estimate and make sure all appropriate labels are set.
  • If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date.
  • If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
  • Before opening a PR, ensure your changes build successfully locally and functionality tests pass. This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions.
  • When opening a PR, follow the PR template.
  • When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge.
  • When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it.
  • Use the Squash and merge option to merge a PR. If you don't want to squash it because there are logically different commits, use Rebase and merge.
  • Close issues automatically when referenced in a PR. You can use closing keywords in the body of the PR to have GitHub automatically link your PR to the issue.

Core Architecture

Common Components

  • Context Menu Handlers - How PowerToys implements and registers Explorer context menu handlers
  • Monaco Editor - How PowerToys uses the Monaco code editor component across modules

Tools

Processes

Other Resources

Building the Installer

Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains the MSI and handles more complex installation logic.

  • The EXE installs all prerequisites and installs PowerToys via the MSI. It has additional features such as the installation flags (see below).
  • The MSI installs the PowerToys binaries.

The installer can only be compiled in Release mode; steps 1 and 2 must be performed before the MSI can be compiled.

  1. Compile PowerToys.slnx. Instructions are listed above.
  2. Compile BugReportTool.sln tool. Path from root: tools\BugReportTool\BugReportTool.sln (details listed below)
  3. Compile StylesReportTool.sln tool. Path from root: tools\StylesReportTool\StylesReportTool.sln (details listed below)
  4. Compile PowerToysSetup.slnx Path from root: installer\PowerToysSetup.slnx (details listed below)

See Installer for more details on building and debugging the installer.