mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
# 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.
173 lines
6.7 KiB
Markdown
173 lines
6.7 KiB
Markdown
---
|
||
description: 'Top-level AI contributor guidance for developing PowerToys - a collection of Windows productivity utilities'
|
||
applyTo: '**'
|
||
---
|
||
|
||
# PowerToys – AI contributor guide
|
||
|
||
This is the top-level guidance for AI contributions to PowerToys. Keep changes atomic, follow existing patterns, and cite exact paths in PRs.
|
||
|
||
## Overview
|
||
|
||
PowerToys is a set of utilities for power users to tune and streamline their Windows experience.
|
||
|
||
| Area | Location | Description |
|
||
|------|----------|-------------|
|
||
| Runner | `src/runner/` | Main executable, tray icon, module loader, hotkey management |
|
||
| Settings UI | `src/settings-ui/` | WinUI/WPF configuration app communicating via named pipes |
|
||
| Modules | `src/modules/` | Individual PowerToys utilities (each in its own subfolder) |
|
||
| Common Libraries | `src/common/` | Shared code: logging, IPC, settings, DPI, telemetry, utilities |
|
||
| Build Tools | `tools/build/` | Build scripts and automation |
|
||
| Documentation | `doc/devdocs/` | Developer documentation |
|
||
| Installer | `installer/` | WiX-based installer projects |
|
||
|
||
For architecture details and module types, see [Architecture Overview](doc/devdocs/core/architecture.md).
|
||
|
||
## Conventions
|
||
|
||
For detailed coding conventions, see:
|
||
|
||
- [Coding Guidelines](doc/devdocs/development/guidelines.md) – Dependencies, testing, PR management
|
||
- [Coding Style](doc/devdocs/development/style.md) – Formatting, C++/C#/XAML style rules
|
||
- [Logging](doc/devdocs/development/logging.md) – C++ spdlog and C# Logger usage
|
||
|
||
### Component-specific instructions
|
||
|
||
These instruction files are automatically applied when working in their respective areas:
|
||
|
||
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md) – IPC contracts, schema migrations
|
||
- [Common Libraries](.github/instructions/common-libraries.instructions.md) – ABI stability, shared code guidelines
|
||
|
||
## Build
|
||
|
||
### Prerequisites
|
||
|
||
- Visual Studio 2022 17.4+ or Visual Studio 2026
|
||
- Windows 10 1803+ (April 2018 Update or newer)
|
||
- Initialize submodules once: `git submodule update --init --recursive`
|
||
|
||
### Build commands
|
||
|
||
| Task | Command |
|
||
|------|---------|
|
||
| First build / NuGet restore | `tools\build\build-essentials.cmd` |
|
||
| Build current folder | `tools\build\build.cmd` |
|
||
| Build with options | `build.ps1 -Platform x64 -Configuration Release` |
|
||
|
||
### Build discipline
|
||
|
||
1. One terminal per operation (build → test). Do not switch or open new ones mid-flow
|
||
2. After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`)
|
||
3. Use scripts to build: `tools/build/build.ps1` or `tools/build/build.cmd`
|
||
4. For first build or missing NuGet packages, run `build-essentials.cmd` first
|
||
5. **Exit code 0 = success; non-zero = failure** – treat this as absolute
|
||
6. On failure, read the errors log: `build.<config>.<platform>.errors.log`
|
||
7. Do not start tests or launch Runner until the build succeeds
|
||
|
||
### Build logs
|
||
|
||
Located next to the solution/project being built:
|
||
|
||
- `build.<configuration>.<platform>.errors.log` – errors only (check this first)
|
||
- `build.<configuration>.<platform>.all.log` – full log
|
||
- `build.<configuration>.<platform>.trace.binlog` – for MSBuild Structured Log Viewer
|
||
|
||
For complete details, see [Build Guidelines](tools/build/BUILD-GUIDELINES.md).
|
||
|
||
## Tests
|
||
|
||
### Test discovery
|
||
|
||
- Find test projects by product code prefix (e.g., `FancyZones`, `AdvancedPaste`)
|
||
- Look for sibling folders or 1-2 levels up named `<Product>*UnitTests` or `<Product>*UITests`
|
||
|
||
### Running tests
|
||
|
||
1. **Build the test project first**, wait for exit code 0
|
||
2. Run via VS Test Explorer (`Ctrl+E, T`) or `vstest.console.exe` with filters
|
||
3. **Avoid `dotnet test`** in this repo – use VS Test Explorer or vstest.console.exe
|
||
|
||
### Test types
|
||
|
||
| Type | Requirements | Setup |
|
||
|------|--------------|-------|
|
||
| Unit Tests | Standard dev environment | None |
|
||
| UI Tests | WinAppDriver v1.2.1, Developer Mode | Install from [WinAppDriver releases](https://github.com/microsoft/WinAppDriver/releases/tag/v1.2.1) |
|
||
| Fuzz Tests | OneFuzz, .NET 10 | See [Fuzzing Tests](doc/devdocs/tools/fuzzingtesting.md) |
|
||
|
||
### Test discipline
|
||
|
||
1. Add or adjust tests when changing behavior
|
||
2. If tests skipped, state why (e.g., comment-only change, string rename)
|
||
3. New modules handling file I/O or user input **must** implement fuzzing tests
|
||
|
||
### Special requirements
|
||
|
||
- **Mouse Without Borders**: Requires 2+ physical computers (not VMs)
|
||
- **Multi-monitor utilities**: Test with 2+ monitors, different DPI settings
|
||
|
||
For UI test setup details, see [UI Tests](doc/devdocs/development/ui-tests.md).
|
||
|
||
## Boundaries
|
||
|
||
### Ask for clarification when
|
||
|
||
- Ambiguous spec after scanning relevant docs
|
||
- Cross-module impact (shared enum/struct) is unclear
|
||
- Security, elevation, or installer changes involved
|
||
- GPO or policy handling modifications needed
|
||
|
||
### Areas requiring extra care
|
||
|
||
| Area | Concern | Reference |
|
||
|------|---------|-----------|
|
||
| `src/common/` | ABI breaks | [Common Libraries Instructions](.github/instructions/common-libraries.instructions.md) |
|
||
| `src/runner/`, `src/settings-ui/` | IPC contracts, schema | [Runner & Settings UI Instructions](.github/instructions/runner-settings-ui.instructions.md) |
|
||
| Installer files | Release impact | Careful review required |
|
||
| Elevation/GPO logic | Security | Confirm no regression in policy handling |
|
||
|
||
### What not to do
|
||
|
||
- Don't merge incomplete features into main (use feature branches)
|
||
- Don't break IPC/JSON contracts without updating both runner and settings-ui
|
||
- Don't add noisy logs in hot paths
|
||
- Don't introduce third-party deps without PM approval and `NOTICE.md` update
|
||
|
||
## Validation Checklist
|
||
|
||
Before finishing, verify:
|
||
|
||
- [ ] Build clean with exit code 0
|
||
- [ ] Tests updated and passing locally
|
||
- [ ] No unintended ABI breaks or schema changes
|
||
- [ ] IPC contracts consistent between runner and settings-ui
|
||
- [ ] New dependencies added to `NOTICE.md`
|
||
- [ ] PR is atomic (one logical change), with issue linked
|
||
|
||
## Documentation Index
|
||
|
||
### Core architecture
|
||
|
||
- [Architecture Overview](doc/devdocs/core/architecture.md)
|
||
- [Runner](doc/devdocs/core/runner.md)
|
||
- [Settings System](doc/devdocs/core/settings/readme.md)
|
||
- [Module Interface](doc/devdocs/modules/interface.md)
|
||
|
||
### Development
|
||
|
||
- [Coding Guidelines](doc/devdocs/development/guidelines.md)
|
||
- [Coding Style](doc/devdocs/development/style.md)
|
||
- [Logging](doc/devdocs/development/logging.md)
|
||
- [UI Tests](doc/devdocs/development/ui-tests.md)
|
||
- [Fuzzing Tests](doc/devdocs/tools/fuzzingtesting.md)
|
||
|
||
### Build & tools
|
||
|
||
- [Build Guidelines](tools/build/BUILD-GUIDELINES.md)
|
||
- [Tools Overview](doc/devdocs/tools/readme.md)
|
||
|
||
### Instructions (auto-applied)
|
||
|
||
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md)
|
||
- [Common Libraries](.github/instructions/common-libraries.instructions.md)
|