Skip to content
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
278d6e9
initial
khmyznikov Jul 21, 2026
09819a5
add tolerance
khmyznikov Jul 21, 2026
536b1bb
fix cleanup?
khmyznikov Jul 21, 2026
2a41c84
iteration
khmyznikov Jul 21, 2026
d1caaf9
fix
khmyznikov Jul 21, 2026
84fdb27
fix
khmyznikov Jul 21, 2026
fe5ba06
add diagnostics
khmyznikov Jul 21, 2026
7777814
fixes
khmyznikov Jul 22, 2026
06ee468
Stabilization
khmyznikov Jul 22, 2026
d06c434
another try
khmyznikov Jul 22, 2026
eede0ca
fix the 2.jpg
khmyznikov Jul 22, 2026
a634fbe
to prev
khmyznikov Jul 23, 2026
a89deaf
check foreground
khmyznikov Jul 23, 2026
b9198c6
attempt to fix
khmyznikov Jul 23, 2026
6427e3d
address the last instability
khmyznikov Jul 23, 2026
c9e8759
to prev
khmyznikov Jul 23, 2026
f4bc0a2
to prev
khmyznikov Jul 23, 2026
051e3f9
to prev
khmyznikov Jul 24, 2026
cc32a1a
hide winapp driver window
khmyznikov Jul 24, 2026
a194200
update skill and apply learnings
khmyznikov Jul 27, 2026
508212d
Minor fix
khmyznikov Jul 27, 2026
559a27b
add new and update skills, update docs
khmyznikov Jul 28, 2026
b66f0f2
update the skill
khmyznikov Jul 28, 2026
8ad7ddd
Add VM resizing and shorter startup loop
khmyznikov Jul 28, 2026
61d0fda
Add file explorer addons UI Tests
khmyznikov Jul 29, 2026
52f8824
stabilization attempt
khmyznikov Jul 29, 2026
3522fc8
fix the win10 tests
khmyznikov Jul 29, 2026
736ab92
fix attempt
khmyznikov Jul 29, 2026
42cfaee
fix the thumbnails
khmyznikov Jul 29, 2026
ddd75e1
fix explorer view selection
khmyznikov Jul 30, 2026
c3a9d7b
share improvements
khmyznikov Jul 30, 2026
5354e3c
update migration skill
khmyznikov Jul 30, 2026
c5463fb
update skills
khmyznikov Jul 30, 2026
26e11df
update skill
khmyznikov Jul 30, 2026
1e43274
Add Image Resizer UI tests and VM support
khmyznikov Aug 2, 2026
c1cfdf1
tighten the agentic loop
khmyznikov Aug 3, 2026
98577c0
fix win11
khmyznikov Aug 3, 2026
781b1c7
Fix win10
khmyznikov Aug 3, 2026
85f0498
True fix for Win11?
khmyznikov Aug 4, 2026
76dab7e
Final fix for Win11?
khmyznikov Aug 4, 2026
8a40442
diagnostic
khmyznikov Aug 4, 2026
092da85
optimize skills
khmyznikov Aug 4, 2026
d4f99c6
test expr signing
khmyznikov Aug 4, 2026
5d8fa6a
change root cert
khmyznikov Aug 4, 2026
f459424
test cert fix
khmyznikov Aug 4, 2026
ae9b564
fix per user
khmyznikov Aug 4, 2026
5fc62b1
update the skill
khmyznikov Aug 4, 2026
9b48659
add a note about wslc
khmyznikov Aug 4, 2026
66ac715
clarify arm64 compat
khmyznikov Aug 5, 2026
3b979a6
add hyper-v template
khmyznikov Aug 6, 2026
016f6c3
remove docker-desktop way
khmyznikov Aug 6, 2026
e1a69fe
refine skills loop
khmyznikov Aug 6, 2026
52df673
update skills
khmyznikov Aug 7, 2026
1607ccd
pre-final
khmyznikov Aug 7, 2026
80d2e54
resolve failure
khmyznikov Aug 7, 2026
cd9da14
update skills
khmyznikov Aug 8, 2026
72462a3
address PR
khmyznikov Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions .github/skills/ui-tests-local-vm/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
name: ui-tests-local-vm
description: "Set up and run PowerToys UITest.Next suites in persistent local Windows VMs hosted by dockur/windows on Docker Desktop with WSL2/KVM. Defaults to Windows 10 LTSC for baseline coverage and uses a separate Windows 11 VM for explicitly Win11-specific behavior. Use for fast agentic UI-test iteration, reusable interactive desktops, non-admin scenarios, WinRM dispatch, payload refresh, evidence export, VM customization, or clean-baseline validation. Keywords: Windows 10, Windows 11, local VM, dockur, Docker, WSL2, KVM, QEMU, UI tests, UITest.Next, winappcli, WinRM, TRX."
license: MIT
---

# Local VM UI testing

Run PowerToys `.Next` UI tests in a persistent, interactive Windows VM while keeping product and test
execution off the host. Use this skill as the execution complement to
[ui-tests-migration](../ui-tests-migration/SKILL.md). Restore a known stopped-volume snapshot or
create a fresh named volume when clean-profile behavior must be validated.

Use Windows 10 Enterprise LTSC 2021 as the default guest. When requirements include Windows 11-only
behavior, complete the baseline pass on Windows 10 and run an additional, narrowly filtered pass in
a separate Windows 11 VM.

Start both guests with the default resource profile: 4 vCPUs and 8 GB RAM. Get the target suite
fully green before lowering resources with the `Constrained` profile (1 vCPU and 4 GB RAM).

## When to use this skill

Use it when asked to:

- Create or migrate UI tests and iterate repeatedly without reprovisioning Windows each run.
- Validate Explorer, hotkey, WebView2, shell-extension, foreground, or composed-visual behavior in a
real interactive desktop.
- Run tests as a true standard user while retaining a separate administrator control channel.
- Reuse unchanged PowerToys, winappcli, and .NET payloads and refresh only changed archives.
- Keep a reproducible local VM baseline with optional .NET 10, WebView2, diagnostics, or `msvsmon`.
- Collect durable `status.json`, TRX, transcripts, logs, screenshots, and failure attachments.
- Compare revisions in one stable VM before confirming the result in a fresh environment.

Do not treat a persistent VM as proof of clean-profile behavior. Caches, registrations, settings, and
first-run state survive between runs. Reset or recreate the VM when those are the behavior under test.

## Relationship to other UI-test skills

| Skill | Owns |
|---|---|
| `ui-tests-migration` | Test design, project scaffolding, framework APIs, assertions, lifecycle, and CI stability. |
| `ui-tests-local-vm` | Fast persistent-VM setup, deployment, interactive execution, evidence export, and iteration. |

Do not modify stabilized tests merely to make the local VM green. First prove that the suite executes,
produces assertion-bearing TRX, and has a useful success rate. Classify environment-specific failures
separately unless the task explicitly asks for stabilization.

## Guest OS policy

- Default to Windows 10 Enterprise LTSC 2021: `WINDOWS_VERSION=10l`, build 19044/21H2. This is newer
than the Windows 10 20H2 baseline.
- Run all behavior not explicitly scoped to a newer OS on the Windows 10 guest first.
- For Windows 11-only behavior, such as the tier-1 Explorer context menu, use another VM root,
container, and named volume with `WINDOWS_VERSION=11`, then rerun only the relevant tests with
`-Platform x64Win11`.
- Do not upgrade, repurpose, or overwrite the Windows 10 baseline volume to create the Windows 11
iteration. Preserve independent reset points and evidence for both guests.
- A Windows 11 host requirement does not make Windows 11 the default guest target.

## Required reads

Read only what the task needs:

1. [references/setup.md](references/setup.md) - Docker Desktop/WSL/KVM prerequisites, VM scaffolding,
OEM provisioning, credentials, standard-user desktop, and health checks.
2. [references/agentic-loop.md](references/agentic-loop.md) - payload contract, controller usage,
focused-to-suite iteration, evidence, verdicts, and reset strategy.
3. [references/customization.md](references/customization.md) - persistent image customization,
.NET 10, WebView2, `msvsmon`, ports, and golden-baseline guidance.
4. [references/troubleshooting.md](references/troubleshooting.md) - KVM, WinRM, interactive session,
UNC, scheduled-task, focus, timeout, and export failures.
5. [references/shell-extensions-and-signing.md](references/shell-extensions-and-signing.md) - **read
for any shell-extension module** (context menu, preview/thumbnail handler). Why unsigned CI PR
builds cannot register a sparse MSIX (0% on CI), classic (registry-COM, signing-free) vs modern
(sparse-MSIX) surfaces, Debug vs Release/`NDEBUG` gating, runtime detection, and reproducing CI's
classic scenario on a local signed VM.
6. [ui-tests-migration](../ui-tests-migration/SKILL.md) - required whenever test code or framework
behavior is being created, migrated, or stabilized.

## Default agentic cycle

```mermaid
flowchart LR
A[Design or edit test] --> B[Host build]
B --> C[Package changed payload]
C --> D[Start or reuse Win10 VM]
D --> E[Probe standard-user desktop]
E --> F[Run focused test]
F --> G[Export status TRX evidence]
G --> H{Need test change?}
H -- Yes --> A
H -- No --> I[Run module suite]
I --> J{Win11-specific checks?}
J -- Yes --> K[Run filtered checks in separate Win11 VM]
J -- No --> L[Optional baseline restore or fresh VM]
K --> L
```

Create and maintain this task list:

```markdown
- [ ] 1. Read ui-tests-migration guidance for the target test surface
- [ ] 1a. Read the target module's dev docs — `doc/devdocs/modules/<module>.md` (search `doc/devdocs/`,
including `common/`, if the exact file is missing) — for development-cycle gotchas such as
Release/`NDEBUG` registration gating, signed sparse-MSIX context menus, and Explorer restarts,
so a module's registration/deployment requirements do not surface as opaque test failures.
For shell-extension modules also read references/shell-extensions-and-signing.md.
- [ ] 2. Scaffold or verify the local VM - references/setup.md
- [ ] 3. Build product and test projects on the host to exit code 0
- [ ] 4. Package a lean exchange and verify archive hashes
- [ ] 5. Run the controller with -PlanOnly and inspect its request/plan
- [ ] 6. Probe the non-admin interactive desktop before test execution
- [ ] 7. Run one focused test; read the controller result's `.Failed` array (non-passed tests + first
error line) instead of re-parsing TRX, and use `scripts/Invoke-GuestScript.ps1` for guest-state inspection
- [ ] 8. Diagnose the first controlling failure without weakening assertions
- [ ] 9. Rebuild and rerun with -ReuseStagedPayload
- [ ] 10. Widen to the module suite and report pass rate/root-cause groups
- [ ] 11. Run Win11-only checks in a separate Windows 11 VM when required
- [ ] 12. Restore a known baseline or recreate the VM for clean-profile confirmation when required
```

## Quick start

Scaffold the VM directory outside the repository:

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

Follow [references/setup.md](references/setup.md) to create the untracked `.env`, boot the VM, and
save the administrator credential with Windows DPAPI. Then stage the archives described in
[references/agentic-loop.md](references/agentic-loop.md) and run:

```pwsh
pwsh .github\skills\ui-tests-local-vm\scripts\Invoke-LocalVmUiTest.ps1 `
-VmRoot X:\PowerToysUiTestVm `
-ExchangeRoot X:\PowerToysUiTestVm\shared\PowerToysUiTests\MyModule `
-TestExecutable MyModule.UITests.Next.exe `
-Filter 'Name=MyModule.FocusedTest' `
-Platform x64Win10 `
-BuildLabel (git rev-parse HEAD) `
-SuiteTimeout 15m `
-TimeoutMinutes 25 `
-ReuseStagedPayload
```

The controller starts the VM if needed, verifies the interactive standard-user token and desktop,
dispatches the shared guest runner through a limited interactive scheduled task, streams progress,
waits for parseable `status.json`, summarizes TRX, and leaves the persistent VM running by default.

## Non-negotiable rules

- Build on the host; run PowerToys and tests only in the VM when host execution is prohibited.
- Use the Windows 10 guest for the default pass. Add a separate Windows 11 pass only for requirements
that explicitly depend on Windows 11 behavior.
- Establish a fully green correctness baseline with the default (4 vCPU / 8 GB) resources before
running the same tests under `Constrained` (1 vCPU / 4 GB) resources.
- Keep VM files and writable exchange folders outside the repository.
- Bind management, RDP, viewer, and debugger ports to `127.0.0.1` unless remote access is intentional.
- Use HTTPS WinRM and a DPAPI-protected credential file. Never put credentials in prompts, scripts,
request JSON, source control, or command-line arguments.
- Run UI tests in an already logged-on standard-user desktop, never in session 0 or as `SYSTEM`.
- Keep a separate administrator account only for VM control and scheduled-task registration.
- Verify user, token integrity, Explorer presence, session ID, display size, and UNC access before tests.
- Use UNC paths across Explorer restarts; mapped drive letters are session-scoped conveniences.
- Run product/tests from guest-local storage, not from the SMB exchange.
- Reuse payloads by per-component hashes; refresh only changed tests/product/tools.
- Preserve assertions and visual thresholds. Classify VM-specific failures from evidence.
- Always parse TRX. A process exit code alone cannot distinguish assertions, zero tests, timeout, or
infrastructure failure.
- Keep the VM after normal runs for iteration. Stop it explicitly when idle; delete its volume only
for an intentional baseline reset.
- Final clean-profile claims require a restored known baseline or a recreated VM volume.

## Verdicts

| Verdict | Meaning |
|---|---|
| `PASS` | Selected tests executed and passed. |
| `FAIL` | Tests executed and assertion-bearing TRX contains failures. |
| `BLOCKED` | VM, KVM, WinRM, desktop, deployment, filter, or prerequisite prevented execution. |
| `ENVIRONMENT` | Tests executed, but evidence proves a VM/display/profile mismatch rather than product behavior. |

A partial pass rate is still proof that the agentic cycle executes when the task is to validate the
loop itself. Report the exact numerator/denominator and classify failure groups; do not alter tests
unless stabilization was requested.
210 changes: 210 additions & 0 deletions .github/skills/ui-tests-local-vm/references/agentic-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Agentic local-VM UI-test loop

This loop assumes the test project follows `Microsoft.PowerToys.UITest.Next`, builds as a
Microsoft.Testing.Platform executable, and has already passed the `ui-tests-migration` design and
CI-stability checks.

Use Windows 10 Enterprise LTSC 2021 as the default guest and run the full baseline there. If the
requirements explicitly include Windows 11 behavior, run only those checks again in a separate
Windows 11 VM after the Windows 10 pass.

## 1. Build on the host

Build only; do not launch PowerToys or tests on the host when the task forbids it.

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

git rev-parse HEAD
```

Exit code 0 is required. Record the build label before packaging.

## 2. Create a lean exchange

The exchange must be below `<VmRoot>\shared` so the controller can map it to `\\host.lan\Data`:

```text
<VmRoot>\shared\PowerToysUiTests\<Module>\
|-- ui-tests.zip
|-- powertoys-runtime.zip
|-- winappcli.zip
|-- dotnet-runtime.zip
|-- product-overlay.zip # optional
|-- MicrosoftEdgeWebView2RuntimeInstallerX64.exe # optional
`-- LocalVmResults\
```

The controller writes each request and its durable evidence under `LocalVmResults`.

Package archive contents directly:

```pwsh
Compress-Archive -Path '<test-output>\*' `
-DestinationPath '<exchange>\ui-tests.zip' -Force
Compress-Archive -Path '<product-runtime>\*' `
-DestinationPath '<exchange>\powertoys-runtime.zip' -Force
```

**Build the product runtime in Release for any shell-extension test.** The runtime context-menu
registration for Image Resizer, File Locksmith, New+, and PowerRename is compiled behind
`#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)`, so a **Debug** runtime silently omits it: the
module enables and logs normally, but the entry never appears in Explorer and menu assertions fail
with no obvious cause (for example, "Explorer did not show 'Resize with Image Resizer'"). CI ships
Release for this reason. If you must validate against a Debug runtime, rebuild only the affected
module DLL with `ENABLE_REGISTRATION` defined and overlay it via `product-overlay.zip`.

Use the repository-pinned winappcli build and a private .NET runtime matching the test executable.
Even when .NET 10 is installed in the VM baseline, the private runtime remains the default for
reproducibility and revision comparison.

The controller copies its bundled `templates/run-ui-tests.ps1`, computes per-component SHA-256
hashes, writes a run-specific request, and never maps the repository or build output directly into
Windows.

## 3. Validate the plan

Always run the first request with `-PlanOnly`:

```pwsh
pwsh .github\skills\ui-tests-local-vm\scripts\Invoke-LocalVmUiTest.ps1 `
-VmRoot X:\PowerToysUiTestVm `
-ExchangeRoot X:\PowerToysUiTestVm\shared\PowerToysUiTests\<Module> `
-TestExecutable <Module>.UITests.Next.exe `
-Filter 'Name=<focused-test>' `
-BuildLabel (git rev-parse HEAD) `
-PlanOnly
```

Check:

- `ExchangeRoot` in the request is a UNC under `\\host.lan\Data`.
- Test, product, winappcli, and .NET hashes are present.
- The filter uses `Name=`, `Name~`, `FullyQualifiedName~`, or `TestCategory=`.
- No password, token, or source path appears in the request.

## 4. Run one focused test

Use the default VM resource profile (4 vCPUs and 8 GB RAM) while creating and stabilizing tests. Do
not begin on the constrained profile: first prove the test and product behavior with sufficient CPU
and RAM.

```pwsh
pwsh .github\skills\ui-tests-local-vm\scripts\Invoke-LocalVmUiTest.ps1 `
-VmRoot X:\PowerToysUiTestVm `
-ExchangeRoot X:\PowerToysUiTestVm\shared\PowerToysUiTests\<Module> `
-TestExecutable <Module>.UITests.Next.exe `
-Filter 'Name=<focused-test>' `
-Platform x64Win10 `
-BuildLabel (git rev-parse HEAD) `
-DesktopWidth 1920 -DesktopHeight 1080 `
-SuiteTimeout 15m -TimeoutMinutes 25
```

Before the guest runner starts, the controller dispatches a probe into the interactive account and
requires:

- User is the configured standard user and is not an administrator.
- Session ID is greater than zero.
- Explorer exists in that session.
- Display dimensions match the request, unless both are zero.
- The guest UNC exchange is accessible.

Failure here is `BLOCKED`, not a test failure.

For an explicitly Windows 11-only check, point the same controller at a separate Windows 11 VM root
and exchange, use `-Platform x64Win11`, and apply a narrow filter. Do not run the ordinary suite only
on Windows 11, and do not reuse the Windows 10 volume as the Windows 11 guest.

## 5. Parse evidence

Each run writes:

```text
LocalVmResults\localvm-<timestamp-guid>\
|-- controller-plan.json
|-- request.json
|-- desktop-probe.ps1
|-- desktop-probe.json
|-- progress.json
|-- status.json
|-- local-vm-ui-tests.log
`-- TestResults\
|-- <suite>.trx
`-- <logs, screenshots, recordings, attachments>
```

The controller prints scalar TRX counters and per-test outcomes. Read both `status.json` and TRX:

- Assertion-bearing TRX failures are `FAIL`.
- Zero selected tests/MTP exit code 8 is `BLOCKED`.
- Missing desktop, WinRM, share, archive, or status is `BLOCKED`.
- Proven display/profile/compositor differences are `ENVIRONMENT`.
- An N/M pass rate proves the execution loop ran, even when the task did not ask to stabilize tests.

Do not modify an already stabilized suite merely because the local VM differs from CI. Report the
pass rate and group failures by controlling boundary first.

## 6. Iterate incrementally

After changing tests or product code:

1. Build the touched project to exit code 0.
2. Replace only the corresponding archive.
3. Rerun the same focused filter with `-ReuseStagedPayload`.
4. Confirm `RefreshedComponents` contains only the changed component.
5. Widen only after the focused behavior is understood.

```pwsh
pwsh .github\skills\ui-tests-local-vm\scripts\Invoke-LocalVmUiTest.ps1 `
<same parameters> `
-ReuseStagedPayload
```

The guest manifest persists under `C:\PowerToysUiTestRun`. Unchanged tests/product/winappcli/.NET
are not extracted again. WebView2 and other baseline tools remain installed.

The VM stays running after each run. Use `-SkipStart` when it is already healthy, and
`-StopVmAfterRun` only when no further iteration is expected.

## 7. Widen to the suite

Use a bounded category filter on the Windows 10 guest:

```pwsh
-Filter 'TestCategory=<Module>' -SuiteTimeout 45m -TimeoutMinutes 60
```

Report:

- Executed, passed, failed, and error counts.
- Exact pass rate.
- Root-cause groups, not only test names.
- Guest user/session/display and payload fingerprint.
- Export errors independently from assertion failures.

After reporting the Windows 10 baseline, run any Windows 11-specific subset against its independent
Windows 11 baseline and report that evidence separately.

Once the complete target suite is green, restart the same VM with `-ResourceProfile Constrained`
(1 vCPU and 4 GB RAM) and repeat the focused-to-suite progression. Keep the default-profile TRX as
the correctness baseline and classify failures that appear only under constrained resources
separately.

## 8. Confirm clean-profile behavior

A retained VM accumulates registry state, caches, thumbnail databases, WebView profiles, Settings,
and first-run suppressions. Choose one final confirmation based on risk:

- Restore a known stopped-volume snapshot.
- Create a new named volume and reinstall from the OEM baseline.

Do not call a retained run clean merely because the product archive was refreshed.

## Revision comparison

Hold VM volume, Windows build, display, account, tools, filter, and timeouts constant. Change only the
intentional test/product archive and record both fingerprints. For a clean-baseline comparison,
restore the same volume snapshot before each revision.
Loading