Files
Gleb KhmyznikovandCopilot 13669b7fb1 [UI Tests] Power Display driver backed tests (#51855)
<!--
HARD RULE: You (the human contributor) must write this PR description in
your own words.
AI tools and agents: Do NOT write the Summary, Detailed Description, or
Validation Steps sections.
Leave them for the human. You may put AI-generated text ONLY inside an
"AI-generated summary" <details> block at the bottom.
Maintainers will close PRs whose description they think was not written
by a human.
See
https://github.com/microsoft/PowerToys/blob/main/CONTRIBUTING.md#pull-request-descriptions-must-be-written-by-a-human
-->

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

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

- [ ] Closes: #51800
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the 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

<details>
<summary>AI-generated summary</summary>

Test-only UMDF/IddCx virtual monitors for Power Display, with
independently observed native DDC/CI brightness, hot-plug controls,
unsupported-capability and communication-failure modes, five new UI
scenarios, and an isolated ARM64 CI lane. No shipping product or
installer changes and no boot-policy changes. Reproduction and cleanup
instructions: `.pipelines/powerdisplay-idd-poc/README.txt`.

Claude Opus 5.5 completed `.github/prompts/review-pr.prompt.md`: 0 high,
4 medium, 10 low and 2 informational findings. Relevant findings were
addressed in `2273b0093daa0fefa0f176141d870c8035c45dcf`: stronger
discovery/debounced-persistence assertions, session/stale-element
handling, adapter readiness and native I2C error semantics, 27
ownership/preflight safety tests, licensing, verified package-cache
reuse, fixture-only suite selection and documentation.

The unsupported-card visibility suggestion was not adopted: existing
production discovery intentionally ignores monitors without capability
evidence. The test instead requires the exact healthy monitor and actual
re-read capabilities callbacks. The complete production-probe artifact
is retained for documented manual diagnostics; duplicate SDK/WDK
downloads were addressed without adding driver-only build switches.

Post-review validation at `2273b0093daa0fefa0f176141d870c8035c45dcf`:

- `tools\build\build.cmd -Path
src\modules\powerdisplay\PowerDisplay.UITests -Platform x64
-Configuration Release` - PASS.
- `tools\build\build.cmd -Path
src\modules\powerdisplay\PowerDisplay.UITests -Platform ARM64
-Configuration Release` - PASS.
- `.pipelines\powerdisplay-idd-poc\Build-Poc.ps1 -WorkRoot
C:\PowerToysUiTestVm\tools\display-idd-poc -OutputRoot
C:\PowerToysUiTestVm\payload-staging\display-idd-review-51855
-PackageCache C:\PowerToysUiTestVm\tools\display-idd-poc\packages
-Platform x64` - PASS, including signing/catalog generation.
- The same native build command with `-Platform ARM64` - PASS, including
signing/catalog generation.
- `Invoke-Pester -Script $tests.FullName -ExcludeTag NativeCompiler
-PassThru -Quiet`, with the complete selection from
`.pipelines/v2/templates/steps-test-mwb-infrastructure.yml` - 898/898
locally; 898/898 per CI architecture, 1796 total. Zero failed/skipped.
- Full unfiltered local matrix through `Invoke-LocalVmUiTest.ps1` -
13/13 each on Windows 10/11 Default (actual 4 CPU/8 GB) and Constrained
(actual 1 CPU/4 GB); 52 passes, zero skips/export errors. Local product
payload was the existing managed/ReadyToRun overlay, not a claim of a
fresh full-product source build.
- Fresh scoped `buildNow` [CI build
162600848](https://dev.azure.com/microsoft/c93e867a-8815-43c1-92c4-e7dd5404f1e1/_build/results?buildId=162600848)
- succeeded on attempt 1 at the exact pushed SHA; source-built Release
x64/ARM64 product and 13/13 each on Windows 10 x64, Windows 11 x64 and
isolated Windows 11 ARM64 (39 UI passes, zero non-passing results).
- All three driver evidence artifacts verified: newly owned INF
binding/version, PnP problem 0, both initial arrivals, protocol version
2 with 31 executed self-test assertions, and successful owned cleanup.
Release Runner/Settings companion signatures and trust-cleanup steps
verified on all jobs. ARM64 uses the preconfigured Secure Boot off /
TESTSIGNING image; no script changes that policy.
- Local owned driver/packages and journaled trust were removed, both
owned baselines restored, both VMs stopped, and host signing keys
removed. Worktree clean.

An initial local tray failure was an unrelated OneDrive backup
advertisement covering the icon, confirmed by screenshot/video; stopping
that guest standard user's OneDrive process in the session-only wrapper
allowed the unchanged test and complete matrix to pass. A planned
Windows Update restart interrupted Win11 startup before provisioning;
validation resumed after the desktop recovered. Neither required
repository test or product changes.

### Shared UMDF pipeline infrastructure

The subsequently approved unification is committed and pushed as
`ae28d77bfd482675f5640d086bf2aecfabcccfcb`.

- `.pipelines/umdf/UmdfBuild.Common.ps1` shares pinned
checkout/patching, verified SDK/WDK acquisition, neutral versioned
dependency caching, signing/catalog generation, hashes and managed
output discovery. Both original build entry points and artifact layouts
remain.
- `.pipelines/umdf/UmdfCi.Common.ps1` shares fixture metadata,
setup/cleanup, environment restoration and emergency recovery. The
legacy HID selector/environment entry points remain compatibility
wrappers.
- `.pipelines/umdf/UmdfDriver.Common.ps1` shares atomic journals,
validated ownership, hashes/public certificates, signatures, trust and
diagnostic/removal primitives. Managers retain their existing journal
fields, distinct hardware IDs/subjects, approval gates, readiness
criteria and opposite device/package cleanup ordering. HID now retains
journal-owned trust when removal fails instead of removing trust
prematurely.
- Compile-time ARM64 routing and evidence publishing are shared without
merging fixture jobs or admitting ordinary suites to the TESTSIGNING
image. Shared-helper changes select both suites. Both managers and their
staged shared-helper deployment are documented.

Validation at the exact unification SHA:

- `tools\build\build.cmd -Path
src\modules\GrabAndMove\Tests\GrabAndMove.UITests -Platform x64
-Configuration Release` and the same command with `-Platform ARM64` -
PASS.
- `tools\build\build.cmd -Path
src\modules\powerdisplay\PowerDisplay.UITests -Platform x64
-Configuration Release` and the same command with `-Platform ARM64` -
PASS.
- `.pipelines\umdf-hid-smoke\Build-Smoke.ps1 -WorkRoot
C:\PowerToysUiTestVm\tools\umdf-unify-hid -OutputRoot
C:\PowerToysUiTestVm\payload-staging\umdf-unify-51855\umdf-hid
-Platforms x64 -PackageCache
C:\PowerToysUiTestVm\tools\display-idd-poc\packages -DriverOnly` and the
same command with `-Platforms ARM64` - PASS, including signing/catalog
generation.
- `.pipelines\powerdisplay-idd-poc\Build-Poc.ps1 -WorkRoot
C:\PowerToysUiTestVm\tools\display-idd-poc -OutputRoot
C:\PowerToysUiTestVm\payload-staging\umdf-unify-51855\powerdisplay-idd
-Platform x64 -PackageCache
C:\PowerToysUiTestVm\tools\display-idd-poc\packages` and the same
command with `-Platform ARM64` - PASS.
- `Invoke-Pester -Script $tests.FullName -ExcludeTag NativeCompiler
-PassThru -Quiet`, using the full updated CI selection from
`steps-test-mwb-infrastructure.yml` - 916/916 locally and 916/916 on
each CI build architecture (1832 total).
- Eight full, unfiltered local VM runs - Grab and Move 7/7 and Power
Display 13/13 on each Win10/Win11 Default (4 CPU/8 GB) and Constrained
(1 CPU/4 GB) profile. Total 80/80, no skips/export errors; per-suite
driver cleanup verified.
- The archived Settings XBF lacked the current Grab and Move
exclusion-control automation ID. `tools\build\build.cmd -Path
src\settings-ui\Settings.UI -Platform x64 -Configuration Release` -
PASS; refreshing only Settings and its dependencies/assets made the
unchanged exclusion test pass. The final local matrix used that current
Settings build with the existing managed/ReadyToRun module overlay, not
a fresh full-product claim. No product or C# UI-test source changes were
made.
- Expanded YAML preview verified six jobs: two builds, two x64
combined-suite jobs and two one-suite ARM64 jobs. Fresh scoped
`buildNow` [CI build
162635811](https://dev.azure.com/microsoft/Dart/_build/results?buildId=162635811)
succeeded on attempt 2 at the unchanged SHA: Win10 x64 20/20, Win11 x64
20/20, ARM64 HID 7/7 and ARM64 Display 13/13. All 60 UI results passed.
- Attempt 1,
[162625157](https://dev.azure.com/microsoft/Dart/_build/results?buildId=162625157),
was 59/60 due solely to an unchanged ARM64 HID foreground-handoff
assertion. Screenshot, recording, console and driver evidence were
inspected: native move/resize and binding succeeded, owned cleanup
passed. The full same-SHA rerun passed without weakening or editing
assertions.
- All six final fixture evidence artifacts verified: architecture/image
policy, PnP problem 0, HID's three children, Display's newly owned
INF/version and both monitors, 31 protocol assertions, and owned
cleanup. Required Release Runner/Settings signing and trust cleanup
verified in all four test jobs.
- Both local baselines restored, VMs stopped, only owned checkpoints
removed, and owned host signing key/trust absent. Local, remote, PR and
validated CI revisions match.

This proves emulated Windows/HID/DDC/CI integration, not physical-panel
backlight behavior, WMI hardware control or physical mixed-DPI signoff.
The PR intentionally remains a draft; the human contributor must write
the visible prose sections before maintainer review.

### Foreground-flake investigation and stabilization

The earlier unchanged-SHA retry was evidence of an intermittent failure,
not a stability fix. The test-only fix is committed and pushed as
`4647cc2ea693931df0300dfbf5e13e5ce4850118`.

The old modifier test required the exact Settings HWND to own foreground
before sending End/Enter. It now expands and selects the real WinUI
control through coordinate-free UIA, searches the exact Settings PID's
popup trees, deduplicates identical list-item selectors, and checks both
selected UI text and persisted modifier value. The original real-HID
Windows/Alt move, resize, negative behavior, exact geometry and
unchanged-process assertions remain. The unused cross-process
foreground-transfer helper was removed.

The historical ARM64 `GetForegroundWindow()==0` cause was not
reproduced: a controlled delayed-activation probe passed with the old
helper. This fix removes an unnecessary foreground dependency from
settings selection; it does not claim to repair Windows foreground
activation generally.

Repeated testing exposed two additional test-side failure modes, both
addressed before final sign-off:

- A cached ComboBox selector became stale after selecting Windows key
rebuilt the Alt-only settings cards. Each read now reacquires the
control and retries only known stale-element failures. The captured
error JSON-escapes apostrophes, so the narrow RuntimeId classifier uses
unescaped ASCII message fragments. Five probes of the actual compiled
classifier, including unrelated-assertion and other-exception negative
controls, passed.
- On the one-CPU Win11 profile, the UI showed the correct selected value
but three fresh observations did not fit into ten seconds. Logs showed
individual search/get-value calls taking 4.440s and 2.601s; screenshot
and recording confirmed successful selection. The three-observation
assertion is retained, with a 30-second bounded budget and explicit
stable-sample diagnostics.

The new `ModifierSelectionWorksWhileFixtureOwnsForeground` regression
alternates ten real UIA selections, requiring fixture foreground before
each, unchanged pointer position, correct selected/persisted values and
stable module PID.

Final-source local evidence (earlier failed/paused source series are
excluded):

| Guest/profile | Fresh focused processes | Focused cases | Full-suite
cases |
|---|---:|---:|---:|
| Win10 Default, actual 4 CPU / 8 GB | 10 | 20/20 | 8/8 |
| Win10 Constrained, actual 1 CPU / 4 GB | 10 | 20/20 | 8/8 |
| Win11 Default, actual 4 CPU / 8 GB | 10 | 20/20 | 8/8 |
| Win11 Constrained, actual 1 CPU / 4 GB | 10 | 20/20 | 8/8 |

Total: 40 fresh focused processes, 80/80 focused cases, **400
alternating selections counted from regression TRX**, plus 32/32
full-suite cases. Zero skipped/non-executed cases or export errors. All
requests used test archive SHA-256
`34A776AAA2245BCB57D2275E2CDEB13F88BAD9E2E41F685869FE66D85109491C`; the
three source-file hashes were checked against the published revision.
Local product payload remains the previously described
managed/ReadyToRun overlay with current-source Settings, not a claim of
a fresh full-product local build.

Validation commands and results:

- From `src\modules\GrabAndMove\Tests\GrabAndMove.UITests`,
`C:\Develop\GitHub\PowerToys\tools\build\build.cmd -Platform x64
-Configuration Release` - PASS, including StyleCop.
- From the same directory,
`C:\Develop\GitHub\PowerToys\tools\build\build.cmd -Platform ARM64
-Configuration Release` - PASS, including StyleCop.
- `git diff --check` - PASS.

The session-only `$script` below is `Run-GamFocusValidation.ps1`, a
wrapper around the repository's `Invoke-LocalVmUiTest.ps1`; no
validation wrapper was added to the repository. `$filter` is
`Name~ChangingModifierToWinUpdatesMoveAndResizeWithoutRestart|Name~ModifierSelectionWorksWhileFixtureOwnsForeground`.

```powershell
& $script -BuildLabel gam-focus-stable-full-win10-default -OperatingSystems Win10 -Profiles Default -ExpectedCases 8
# PASS 8/8
& $script -BuildLabel gam-focus-stable-full-win10-constrained -OperatingSystems Win10 -Profiles Constrained -ExpectedCases 8
# PASS 8/8
& $script -BuildLabel gam-focus-stable-full-win11-default -OperatingSystems Win11 -Profiles Default -ExpectedCases 8
# PASS 8/8
& $script -BuildLabel gam-focus-stable-full-win11-constrained -OperatingSystems Win11 -Profiles Constrained -ExpectedCases 8
# PASS 8/8
& $script -BuildLabel gam-focus-stable-repeat-win10-default -OperatingSystems Win10 -Profiles Default -Filter $filter -Repetitions 10 -ExpectedCases 2
# PASS 20/20
& $script -BuildLabel gam-focus-stable-repeat-win10-constrained -OperatingSystems Win10 -Profiles Constrained -Filter $filter -Repetitions 10 -ExpectedCases 2
# PASS 20/20
& $script -BuildLabel gam-focus-stable-repeat-win11-default -OperatingSystems Win11 -Profiles Default -Filter $filter -Repetitions 10 -ExpectedCases 2
# PASS 20/20
& $script -BuildLabel gam-focus-stable-repeat-win11-constrained -OperatingSystems Win11 -Profiles Constrained -Filter $filter -Repetitions 10 -ExpectedCases 2
# PASS 20/20
```

Fresh scoped `buildNow` [CI
162664091](https://dev.azure.com/microsoft/Dart/_build/results?buildId=162664091)
succeeded on **attempt 1** at the exact fix SHA. Both source-built
Release architectures and all five jobs passed: Win10 x64 8/8, Win11 x64
8/8, isolated Win11 ARM64 8/8, and infrastructure 916/916 per build
architecture (1832 total). Both exact modifier scenarios were
individually verified in all three UI runs. All three native
readiness/owned-cleanup artifacts and Runner/Settings
companion-signing/trust-cleanup steps were verified.

ARM64 evidence is one full-suite process including ten regression
selections, not multiple fresh focused ARM64 processes. This is bounded
stability evidence, not proof that no future flake is possible.

All eight final local HID provisioning/cleanup evidence sets were
exported and verified. Both local baselines were restored, VMs stopped,
only the exact owned GAM checkpoints removed, and journal-owned
driver/companion trust removed. The host companion signing key/trust is
absent. The PR remains a draft; visible prose sections are intentionally
left for the human contributor to write in their own words.
</details>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 07:32:54 +00:00

23 KiB

UI tests framework

PowerToys provides UI-test frameworks for modules and Settings. New tests should use Microsoft.PowerToys.UITest.Next, which drives Windows UI Automation through winappcli and runs as a Microsoft.Testing.Platform executable. The legacy Microsoft.PowerToys.UITest framework uses WinAppDriver/Selenium and remains documented for existing suites and migration baselines.

Agent-assisted workflows

Two repository skills cover the complete implementation and validation loop:

  • UI-tests migration skill: create new .Next test projects, port legacy WinAppDriver tests, design stable selectors/waits/lifecycle, and prepare tests for CI.
  • Local-VM UI-tests skill: create persistent Windows 10 and Windows 11 Hyper-V guests, stage current build/test artifacts, execute tests in a standard-user interactive desktop, and collect durable TRX/log/screenshot/video evidence.

For new or migrated tests, use both skills. Build first, then use the local VMs as the default live agentic loop: run one deterministic test, diagnose and fix it, and finally widen to the complete module suite on both supported Windows versions.

Module-specific constraints are documented with the module; for example, see the PowerRename UI-test notes for command-line selection, Boost engine lifetime, and signed shell-extension requirements. Read the Environment Variables safety notes before running that suite: it temporarily replaces the real User PATH and must run only in a disposable VM, not on a working machine.

Before running tests

.Next tests

  • Build the PowerToys runtime and .UITests.Next test executable.
  • Install the pinned winappcli runtime or set WINAPP_CLI_PATH. The pipeline helper is .pipelines/InstallWinAppCli.ps1.
  • Use a live interactive desktop. UIA, foreground input, Explorer, hotkeys, and rendering do not work in session 0.
  • Exit an existing PowerToys instance before a host-desktop run. The harness owns the runner and module lifecycle.

The .Next value-setting wrappers pass target options before --, followed by the literal selector and value. Keep this boundary when adding wrappers: application arguments such as --payload "text" and negative numeric values must not be parsed as winapp options.

Legacy tests

Running tests

.Next tests

Build the focused project with the repository script, then run the produced Microsoft.Testing.Platform executable directly:

tools\build\build.cmd `
  -Path src\modules\<Module>\Tests\<Module>.UITests.Next `
  -Platform x64 `
  -Configuration Debug

$exe = 'x64\Debug\tests\<Module>.UITests.Next\net10.0-windows10.0.26100.0\<Module>.UITests.Next.exe'
& $exe `
  --filter 'TestCategory=<Module>' `
  --report-trx `
  --report-trx-filename module.trx `
  --results-directory .\TestResults\<Module> `
  --timeout 7m

Use explicit filter properties such as Name=, Name~, FullyQualifiedName~, or TestCategory=. A bare display name can select zero tests. The 7m timeout above is a focused-filter example; choose a larger value for a module or project-wide run.

Legacy tests

  • Exit PowerToys if it's running.

  • Open PowerToys.slnx in Visual Studio and build the solution.

  • Run tests in the Test Explorer (Test > Test Explorer or Ctrl+E, T).

Optional virtual HID input

Some modules intentionally reject injected input. In a dedicated test VM with the PowerToys UMDF virtual HID test driver already provisioned, opt the existing helpers into real device input.

For Grab and Move's complete local build, self-signing, provisioning, execution and cleanup workflow, see the module-specific testing guide.

For example:

using var input = UmdfHidInput.Activate();
KeyboardHelper.PressKey(Key.Alt);
try
{
    MouseHelper.Drag(300, 300, 396, 364);
}
finally
{
    KeyboardHelper.ReleaseKey(Key.Alt);
}

The scope is process-wide; input tests must not run in parallel. Disposal releases held device keys/buttons and restores the default helpers. Activation fails if the driver is missing, with no SendInput fallback. The current report descriptor supports six simultaneous non-modifier keys, primary-monitor mouse coordinates, and whole wheel detents (120 units).

Run driver-backed gestures on an active, unlocked console test desktop, not inside RDP or Hyper-V Enhanced Session. The current backend is not an RDP-session input injector: the device can open while its input never reaches that session's cursor. Use a physical console or Hyper-V Basic Session; a host-side RDP connection is fine when tests execute in a guest's console desktop.

Call MouseHelper.MoveTo explicitly before click, wheel, or relative movement sequences. The HID backend retains the last requested position because feature-report completion can precede the OS cursor update; it does not track physical/injected mouse movement during the scope. Keyboard-only scopes do not reposition the mouse. Restore any original off-primary position after disposing the scope, when the default mouse backend is active again.

Driver provisioning is separate, elevated test infrastructure; tests themselves run non-elevated. The test package targets UMDF 2.31 on Windows 10 2004+ and Windows 11 and is never shipped in the PowerToys installer. The Microsoft sample-derived driver is MS-PL licensed; its attribution and license accompany the test artifacts. ARM64 requires a separate, isolated CI image preconfigured with TESTSIGNING enabled. Other ARM64 suites and all x64 suites retain their ordinary images.

Security: provision this driver only in disposable, isolated test VMs without real credentials. While installed, local code can emit hardware-style keyboard/mouse input, including input to elevated desktops. Do not install it on a developer workstation. Do not change ordinary test images' boot/signing policies. Only the dedicated ARM64 image is provisioned for TESTSIGNING by its owner; the pipeline never enables it, disables Secure Boot/HVCI, or reboots to change policy. Remove the device, package and temporary trust immediately after testing. Activation never installs a driver or changes machine trust.

Build the test-only package on the build host (Visual Studio C++ tools and the SDK are required):

.\.pipelines\umdf-hid-smoke\Build-Smoke.ps1 `
  -WorkRoot C:\HidBuild\work -OutputRoot C:\HidBuild\artifacts -Platforms x64 -DriverOnly

Copy the x64 artifact directory and Manage-SmokeDriver.ps1 into the dedicated VM. In the VM's separate administrator control session, provision the package with a persistent ownership journal:

.\Manage-SmokeDriver.ps1 -Action Install -ArtifactDirectory C:\HidTest\x64 `
  -StatePath C:\HidTest\state.json -ResultsDirectory C:\HidTest\results

Run the built module executable in the logged-on standard-user desktop, using the normal runtime/winappcli setup described above:

.\GrabAndMove.UITests.exe --report-trx --results-directory C:\HidTest\test-results

Always clean up from the administrator control session, including after a failed install/test:

.\Manage-SmokeDriver.ps1 -Action Cleanup `
  -StatePath C:\HidTest\state.json -ResultsDirectory C:\HidTest\results

Cleanup only removes journaled test trust and owned present devices/packages, and retains the journal on failure. Preserve that journal until cleanup succeeds. CI provisions automatically for the x64 Grab and Move suite. Implicit ARM64 selections skip that suite with an infrastructure warning while the dedicated image is unconfigured; explicitly requesting it reports BLOCKED_INFRASTRUCTURE rather than a passing zero-test run.

Dedicated ARM64 UMDF fixture CI image

The umdfHidArm64Image template parameter is declared by .pipelines\v2\templates\pipeline-ui-tests-automation.yml and pipeline-ci-build.yml, then passed explicitly through the stage/job templates so it is available during structural expansion:

  • Manual internal UI-test runs default to SHINE-W11-Preview-Testing-arm64-TestSigning through ImageOverride. The image parameter permits only that name or disabled to turn off routing; root templates translate disabled to the internal empty-image convention.
  • PR CI enables the image only in the verified internal collection (SHINE-INT-Testing-arm64). The shared CI template defaults to disabled; public shine-oss PR CI explicitly keeps it disabled until the image is provisioned and verified there. Implicit ARM64 selections then skip the UMDF fixture suites with a warning while other suites still run.
  • Image isolation is explicit: ordinary jobs retain the pool's default image. Do not make the TESTSIGNING image the pool default. Registering the image in an OSS pool alone does not opt public CI into it; its root pipeline configuration must also be updated.
  • The dedicated English-language Windows 11 ARM64 image needs TESTSIGNING enabled before boot, an elevated agent, and the same logged-on standard-user desktop/UI-test prerequisites as the ordinary test image. The job checks the architecture and existing boot configuration; it does not alter them. Actual driver-load readiness is still checked separately.

Once configured, PR selection, full builds and official/specific-build runs route only GrabAndMove.UITests to a separate _UmdfHid job and PowerDisplay.UITests to a separate _UmdfDisplay job. Each dedicated job selects exactly its own suite; all/mixed selections keep other suites on the ordinary pool. Power Display's test-only IddCx fixture requires architecture-matched build/provisioning and exports native readiness, protocol and owned-cleanup evidence. This route verifies emulated DDC/CI, not physical backlight, WMI or mixed-DPI hardware behavior. Official/specific-build runs retain both machine-level and per-user jobs. Native ARM64 driver artifacts are built on the ordinary build agents, never installed there. The experimental ARM64 transport-smoke path also explicitly selects the TESTSIGNING image.

Running .Next tests in persistent local VMs

The supported local backend is a pair of persistent Hyper-V guests driven through PowerShell Direct: Windows 10 and Windows 11, each with an already logged-on standard-user desktop. The VMs reveal first-run, profile, Explorer, WebView2, foreground, and lifecycle assumptions without modifying the host profile, while retaining staged payloads for a fast edit/build/rerun loop.

One-time host setup

Scaffold a VM root outside the repository, then follow the generated next steps to create the untracked configuration:

pwsh .github\skills\ui-tests-local-vm\scripts\Initialize-LocalVm.ps1 `
  -DestinationRoot C:\PowerToysUiTestVm

pwsh .github\skills\ui-tests-local-vm\scripts\Initialize-LocalVmHost.ps1 `
  -VmRoot C:\PowerToysUiTestVm `
  -CheckOnly

If -CheckOnly reports IsReady=false, a human must run the elevated setup command it prints. Hyper-V group membership, the DPAPI-protected guest administrator credential, and guest creation cannot be completed by an agent. See the setup reference for install media, vm.config.psd1, Windows 10/11 guest creation, and baseline checkpoints.

Run the agentic loop

Create a module exchange containing ui-tests.zip, powertoys-runtime.zip, winappcli.zip, and dotnet-runtime.zip as described in the agentic-loop reference. Payloads are extracted to guest-local storage; tests are never run directly from a host share.

$vmRoot = 'C:\PowerToysUiTestVm'
$exchange = "$vmRoot\shared\PowerToysUiTests\<Module>"

pwsh .github\skills\ui-tests-local-vm\scripts\Invoke-LocalVmUiTest.ps1 `
  -VmName PowerToysUiTest-Win11 `
  -ConfigurationPath "$vmRoot\vm.config.psd1" `
  -VmRoot $vmRoot `
  -ExchangeRoot $exchange `
  -TestExecutable '<Module>.UITests.Next.exe' `
  -Filter 'Name=<Module>.FocusedTest' `
  -Platform x64Win11 `
  -BuildLabel (git rev-parse HEAD) `
  -SuiteTimeout 15m `
  -TimeoutMinutes 25 `
  -ReuseStagedPayload

The controller starts the guest if needed, validates the standard-user token, Explorer session, and desktop size, then runs the test through a limited interactive scheduled task. It streams progress and returns status.json, TRX counters, per-test failures, logs, screenshots, and retained failure recordings under <ExchangeRoot>\LocalVmResults\<runId>.

After each source change, rebuild and replace only the changed archive, then rerun the same focused filter with -ReuseStagedPayload. Widen only after that behavior is understood. A module is complete only after the full category filter passes with executed == total on both Windows 10 and Windows 11; restore the baseline checkpoint for the final clean-profile confirmation. See the local-VM troubleshooting guide for desktop, PowerShell Direct, payload, shell-extension signing, and evidence failures.

Running tests in pipeline

The PowerToys UI test pipeline provides flexible options for building and testing:

Full builds with test compilation enabled and UI-tests-only builds first run the MWB/shared winapp CLI PowerShell infrastructure tests using Pester 3.4.0 under PowerShell 7. The build publishes NUnit results and fails if any selected test is missing, skipped or failed. Optional NativeCompiler integration tests require explicit tool/runtime paths and are excluded from this fast gate.

Pipeline Options

  • buildSource: Select the build type for testing:

    • latestMainOfficialBuild: Downloads and uses the latest official PowerToys build from main branch; does not support Mouse Without Borders Sandbox tests
    • buildNow: Builds PowerToys from current source code and uses it for testing
    • buildNowSlim: Builds the same current-source product, but downloads and installs its installer rather than the whole artifact
    • specificBuildId: Downloads a specific PowerToys build using the build ID specified in specificBuildId parameter; does not support Mouse Without Borders Sandbox tests

    Default value: buildNow

  • specificBuildId: When buildSource is set to specificBuildId, specify the exact PowerToys build ID to download and test against.

    Default value: "xxxx" (placeholder, enter actual build ID when using specificBuildId option)

    When to use this:

    • Testing against a specific known build for reproducibility
    • Regression testing against a particular build version
    • Validating fixes in a specific build before release

    Usage: Enter the build ID number (e.g., 12345) to download that specific build. Only used when buildSource is set to specificBuildId.

  • uiTestModules: Specify which UI test modules to build and run. This parameter controls both the .csproj projects to build and the .dll test assemblies to execute. Examples:

    • ['UITests-FancyZones'] - Only FancyZones UI tests
    • ['MouseUtils.UITests'] - Only MouseUtils UI tests
    • ['UITests-FancyZones', 'MouseUtils.UITests'] - Multiple specific modules
    • Leave empty to build and run all UI test modules

    Important: The uiTestModules parameter values must match both the test project names (for .csproj selection during build) and the test assembly names (for .dll execution during testing).

    Mouse Without Borders: Selecting MouseWithoutBorders.UITests automatically prepares its current-source x64 Release runtime bundle and runs the dedicated Sandbox suite; there is no separate enable/disable switch. Empty all-modules selections include it on buildNow and buildNowSlim. An explicit MWB selection with an official build is blocked; an empty all-modules official selection skips MWB with a warning and still runs the other suites. ARM64 Sandbox execution is skipped independently of the other ARM64 suites. PR builds prepare the bundle only when the affected-project resolver selects MWB. To omit MWB, select the other desired modules explicitly.

    MWB executes inside Run UI Tests and shares Publish UI Test Results with the other suites. Its isolated setup/standard-user execution preserves the existing timeouts and privilege boundaries. Restore UI-test prerequisites always attempts recovery and cleanup, even after failure or cancellation. Selection/skip reasons are logged; no separate selection artifact is uploaded. Allowlisted prerequisite reports are published as UI-test diagnostics only for failed, canceled or incomplete runs, including setup failures without TRX. Successful runs retain their normal test-result attachments without an extra prerequisite artifact.

Build Modes

  1. Official Build Testing (buildSource = latestMainOfficialBuild or specificBuildId)

    • Downloads and installs official PowerToys build (latest from main or specific build ID)
    • Builds only UI test projects (all or specific based on uiTestModules)
    • Runs UI tests against installed PowerToys
    • Tests both machine-level and per-user installation modes automatically
  2. Current Source Build Testing (buildSource = buildNow)

    • Builds entire PowerToys solution from current source code
    • Builds UI test projects (all or specific based on uiTestModules)
    • Runs UI tests against freshly built PowerToys
    • Uses artifacts from current pipeline build

Note

: All modes support the uiTestModules parameter to control which specific UI test modules to build and run. Both machine-level and per-user installation modes are tested automatically when using official builds.

Pipeline Access

How to add the first UI tests for your modules

Use the UI-tests migration skill for new .Next projects and ports. It contains the current executable project scaffold, API mapping, naming, CI-stability checklist, and validated examples.

The project sample below describes the legacy WinAppDriver framework and is retained for existing legacy suites. Do not use it as the starting point for a new .Next project.

  • Follow the naming convention: {ModuleFolder}/Tests/{ModuleName}-{TestType(Fuzz/UI/Unit)}Tests

  • Create a new project and add the following references to the project file. Change the OutputPath to your own module's path.

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

    Set Scope: The default scope starts from the PowerToys settings UI. If you want to start from your own module, set the constructor as shown below:

    Specify Scope:

      [TestClass]
      public class HostModuleTests : UITestBase
      {
          public HostModuleTests()
              : base(PowerToysModule.Hosts, WindowSize.Small_Vertical)
          {
          }
      }
    
  • Then you can start performing the UI operations.

Example

[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.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();

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

Visual assertions in UITestAutomation.Next

The .Next harness compares complete, equal-sized window captures using local structural similarity (SSIM), with a default threshold of 95%. It does not resize images, crop their frames, or mask pixels. The existing embedded baseline images are used unchanged, and capture retries remain bounded to 15 seconds. The legacy harness shown above retains its original comparison behavior.

The score is the pixel-weighted mean of non-overlapping 8-by-8 blocks, including partial blocks at the image edges. Each RGB channel uses local mean, variance and covariance with the standard 8-bit SSIM stabilizers (0.01 * 255)^2 and (0.03 * 255)^2. Alpha is compared independently; the lower of the RGB and alpha scores is used, so an opaque capture cannot pass by becoming transparent. Dimension mismatches are retried during settling and reported explicitly if they persist.

This replaces the .Next harness's former 64-bit average hash. That hash's 1.5625-percentage-point steps were not percentages of matching pixels: a small frame-color change could alter the global average and flip unrelated hash bits, while uniform black and white images could have identical hashes. Local SSIM keeps changes local and detects missing preview structure. Regression tests cover blank images, removed content, inverted structure, color and alpha changes, dimension mismatches, and partial edge blocks. Peek PNG uses the same 95% threshold as the other previews; there is no architecture-specific exception.

Extra tools and information

Accessibility Tools: While working on tests, you may need a tool that helps you to view the element's accessibility data, e.g. for finding the button to click. For this purpose, you could use AccessibilityInsights.