Files
PowerToys/.github/skills/ui-tests-local-vm/templates/vm/Reset-LocalVm.ps1
Gleb Khmyznikov e48152c52d [UITests] Add UITest.Next suites (Image Resizer, Peek, File Explorer add‑ons, File Locksmith) + local‑VM tooling and CI test‑signing (#49671)
## Summary

Adds end‑to‑end UI tests on the `Microsoft.PowerToys.UITest.Next`
(winappcli) framework for three
modules, grows the shared `.Next` test framework with the helpers those
suites needed, and adds the
CI plumbing that lets shell‑extension tests exercise the **real**
Windows 11 modern context menu.
Also ships two agent skills that document how to write and run these
tests.

Product runtime behavior is **unchanged** — the only product edits are
test‑observability hooks in Peek
and a unit‑test project exclude.

Closes: https://github.com/microsoft/PowerToys/issues/40660
https://github.com/microsoft/PowerToys/issues/49424
https://github.com/microsoft/PowerToys/issues/40661

## What's added

### New UI test suites
- **Image Resizer** —
`src/modules/imageresizer/tests/ImageResizer.UITests`: context‑menu
enable/disable
tracking, the resize dialog, custom presets, every fit mode, every unit,
filename format, keep‑date,
  shrink‑only, replace‑in‑place, and orientation.
- **Peek** — `src/modules/peek/Peek.UITests.Next`: file‑preview coverage
across image/text/archive/
  markdown types with per‑arch visual baselines.
- **File Explorer add‑ons** —
`src/modules/previewpane/PreviewPane.UITests`: Preview Pane handlers and
  thumbnail providers.

### `UITestAutomation.Next` framework
- New helpers: `ExplorerShell` (Shell selection/view‑mode interop),
`WaitHelper` (structured stable
waits), `WindowControl` (foreground/context‑menu/process control),
`VisualAssert` (image compare),
  `WindowHelper`.
- Updates to `Session`, `UITestBase`, `SettingsConfigHelper`,
`WinappCli`.
- New `UITestAutomation.Next.UnitTests` project covering the new
wait/settings/CLI helpers.

### CI — sign sparse MSIX so the modern menu registers
- **`.pipelines/signSparsePackages.ps1`** — self‑signs each sparse
context‑menu MSIX with a
publisher‑matching test certificate and force‑trusts it (machine
stores), so
`AddPackageByUriAsync` succeeds on otherwise‑unsigned PR builds. Robust
`signtool` discovery with a
  NuGet fallback; test‑only trust that asserts no security.
- Wired into **`.pipelines/v2/templates/job-test-project.yml`** as a
best‑effort step covering the
run‑in‑place, machine‑install, and per‑user‑install locations. Signs
nothing it can't (skips
  already‑signed packages) and never fails the job.

### Product changes (test observability only)
- **Peek `FilePreview.xaml` / `.xaml.cs`** — a named `LoadingIndicator`
and a hidden automation peer
that exposes the current preview state as text, so tests can read load
state deterministically. No
  runtime behavior change.
- **`ImageResizer.UnitTests.csproj`** — exclude the sibling
`ImageResizer.UITests\**` folder from the
  unit‑test compilation.

### Agent skills & docs
- **New `ui-tests-local-vm` skill** — run `.Next` suites in persistent
dockur/windows VMs: setup,
agentic loop, image customization, troubleshooting, the shell‑extension
**signing** reference, plus
  controller/guest scripts and VM templates.
- **Updated `ui-tests-migration` skill** — WinAppDriver/Selenium →
`.Next` porting guidance
(CI stability, Explorer/shell‑extension test design, patterns &
pitfalls).
- **`doc/devdocs/development/ui-tests.md`** — updated for the `.Next`
workflow.

## Testing
- All three suites pass locally and in CI across **x64 Win10**, **x64
Win11**, and **arm64** (machine
  and per‑user install legs).

## Reviewer notes
- No product runtime behavior changes; product edits are limited to the
Peek test hooks above.
- The CI signing step is a **test‑only** trust anchor (self‑signed,
scoped to the agent) and is
best‑effort, so it can only add modern‑menu coverage and never regress
the job.
2026-08-10 19:55:03 +02:00

107 lines
3.4 KiB
PowerShell

# Copyright (c) Microsoft Corporation
# The Microsoft Corporation licenses this file to you under the MIT license.
# See the LICENSE file in the project root for more information.
<#
.SYNOPSIS
Manages clean-baseline checkpoints for the Hyper-V UI-test guest.
.DESCRIPTION
Restoring a standard checkpoint returns the guest to the exact state it was captured in, including
the logged-on desktop, which makes it the fast equivalent of recreating the VM. Use it for every
clean-profile claim instead of trusting a long-lived, mutated guest.
.EXAMPLE
pwsh ./Reset-LocalVm.ps1 -List
.EXAMPLE
pwsh ./Reset-LocalVm.ps1 -Restore
.EXAMPLE
pwsh ./Reset-LocalVm.ps1 -CreateBaseline -CheckpointName 'webview2-installed'
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'List')]
param(
[string]$ConfigPath = (Join-Path $PSScriptRoot 'vm.config.psd1'),
[Parameter(ParameterSetName = 'List')]
[switch]$List,
[Parameter(Mandatory, ParameterSetName = 'Restore')]
[switch]$Restore,
[Parameter(Mandatory, ParameterSetName = 'Create')]
[switch]$CreateBaseline,
[Parameter(ParameterSetName = 'Restore')]
[Parameter(ParameterSetName = 'Create')]
[string]$CheckpointName,
[Parameter(ParameterSetName = 'Restore')]
[switch]$StartAfterRestore
)
$ErrorActionPreference = 'Stop'
if (-not (Test-Path $ConfigPath -PathType Leaf)) {
throw "Configuration was not found: $ConfigPath"
}
$configuration = Import-PowerShellDataFile $ConfigPath
$effectiveCheckpoint = if ([string]::IsNullOrWhiteSpace($CheckpointName)) {
$configuration.BaselineCheckpointName
}
else {
$CheckpointName
}
try {
Import-Module Hyper-V -ErrorAction Stop
Get-VMHost -ErrorAction Stop | Out-Null
}
catch {
throw 'BLOCKED: Hyper-V is not accessible from this shell. Run from an elevated PowerShell 7 terminal, or add this account to the local "Hyper-V Administrators" group.'
}
$vm = Get-VM -Name $configuration.VmName -ErrorAction SilentlyContinue
if ($null -eq $vm) {
throw "Virtual machine '$($configuration.VmName)' does not exist."
}
if ($CreateBaseline) {
if ($PSCmdlet.ShouldProcess($vm.Name, "Create checkpoint '$effectiveCheckpoint'")) {
Get-VMCheckpoint -VMName $vm.Name -Name $effectiveCheckpoint -ErrorAction SilentlyContinue |
Remove-VMCheckpoint -Confirm:$false
Checkpoint-VM -Name $vm.Name -SnapshotName $effectiveCheckpoint
}
}
elseif ($Restore) {
$checkpoint = Get-VMCheckpoint -VMName $vm.Name -Name $effectiveCheckpoint -ErrorAction SilentlyContinue
if ($null -eq $checkpoint) {
throw "Checkpoint '$effectiveCheckpoint' does not exist for '$($vm.Name)'."
}
if ($PSCmdlet.ShouldProcess($vm.Name, "Restore checkpoint '$effectiveCheckpoint'")) {
if ($vm.State -eq 'Running') {
Stop-VM -Name $vm.Name -TurnOff -Force
}
Restore-VMCheckpoint -VMName $vm.Name -Name $effectiveCheckpoint -Confirm:$false
if ($StartAfterRestore) {
Start-VM -Name $vm.Name
}
}
}
$vm = Get-VM -Name $configuration.VmName
[pscustomobject]@{
VmName = $vm.Name
State = [string]$vm.State
BaselineCheckpointName = $configuration.BaselineCheckpointName
Checkpoints = @(Get-VMCheckpoint -VMName $vm.Name -ErrorAction SilentlyContinue | ForEach-Object {
[pscustomobject]@{
Name = $_.Name
CreationTime = $_.CreationTime
ParentCheckpointName = $_.ParentCheckpointName
}
})
} | ConvertTo-Json -Depth 4