PowerToys UI 自动化测试框架实战指南:.Next/winappcli 新体系、持久化本地 VM 与 CI 流水线全流程
【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys
本文以 PowerToys 仓库的 UI 测试框架开发文档 为主线,系统讲解其两代 UI 自动化测试体系:新一代Microsoft.PowerToys.UITest.Next(基于winappcli驱动 Windows UI Automation)与旧版 WinAppDriver/Selenium 框架,涵盖本地运行、持久化 Hyper-V 虚拟机验证、CI 流水线参数,以及如何为一个模块编写第一批 UI 测试。读完本文,你将掌握.Next测试工程从搭建、编译、单测调试到双系统(Windows 10 / Windows 11)全量绿灯的完整实操链路。
框架总览:两代 UI 测试体系如何并存
PowerToys 为模块和 Settings 提供两套 UI 测试框架,当前共存于仓库中:
Microsoft.PowerToys.UITest.Next(新,推荐):通过winappcli(winapp.exe)驱动 Windows UI Automation,编译为 Microsoft.Testing.Platform 可执行程序。其基础类与公共 API 位于 src/common/UITestAutomation.Next,包括UITestBase、Session、By、WindowControl、WaitHelper、VisualAssert等;工程文件为 UITestAutomation.Next.csproj。新测试一律使用该框架。Microsoft.PowerToys.UITest(旧):基于 WinAppDriver + Selenium,位于 src/common/UITestAutomation。文档声明它仅保留给既有测试套件与迁移基线参考,不要用它作为新.Next工程起点。
两者关键差异可以从 UITestBase.cs 的类注释中直接印证:
| 维度 | .Next新体系 | 旧版体系 |
|---|---|---|
| 驱动引擎 | winappcli,每个 UI 调用 shell 出子进程winapp.exe并解析其 JSON | WinAppDriver 服务(默认127.0.0.1:4723)+ Selenium/Appium |
| 运行时形态 | Microsoft.Testing.Platform 可执行程序(.exe),直接运行并产出 TRX | MSTest DLL,经 Test Explorer /vstest.console.exe执行 |
| 依赖 | 除 MSTest 外无第三方 NuGet 依赖 | Selenium/Appium 相关包 |
| 选择器 | 无 XPath/CssSelector,采用By与 winappcli 选择器语法 | 支持 XPath 等 Selenium 惯用法 |
| 元素模型 | 无状态元素(stateless),每次操作即时解析 | 有状态WindowsElement |
从源码注释可确认.Next基础类刻意保持与旧版相同的“外观形态”——继承同一个命名风格的UITestBase,在测试中继续使用Session/Find<T>,以便迁移大部分工作是机械化的 API 映射而非重写。仓库中已有多个.Next参考工程,例如 FancyZones.UITests.Next、Peek.UITests.Next、PowerRename.UITests.Next 与 ScreenRuler.UITests.Next。
Agent 辅助工作流:两个仓库 Skill 覆盖“实现—验证”闭环
文档明确了两个配套的仓库级 Skill,覆盖 UI 测试从创建到在真实桌面验证的完整循环:
- UI-tests migration skill:负责创建新的
.Next测试工程、移植旧版 WinAppDriver 测试、设计稳定的选择器/等待/生命周期,并为 CI 准备测试。该 Skill 按场景区分两种工作流:场景 A(移植)——已存在旧版[Module].UITests工程时,新建并列的[Module].UITests.Next工程做 1:1 重实现;场景 B(从零)——模块没有任何 UI 测试、但有真人验收清单 markdown 时,新建[Module].UITests工程并把每条清单转成自动化用例。 - Local-VM UI-tests skill:负责创建可持久化的 Windows 10 与 Windows 11 Hyper-V 客户机,暂存当前构建/测试产物,在标准用户交互桌面中执行测试,并收集可持久化的 TRX/日志/截图/视频证据。
对于新写或迁移的测试,两个 Skill 应配合使用:先在宿主机编译通过,再把本地 VM 作为默认的“活体 agentic 循环”——先跑一个确定性测试、诊断并修复,最后才在两种受支持的 Windows 版本上扩展到完整模块套件。VM 会暴露首次运行、profile、Explorer、WebView2、前台窗口与进程生命周期等假设,且不会污染宿主 profile。
模块特有的约束记录在各模块的开发文档中。例如文档专门指出 PowerRename 的 UI 测试涉及命令行选择、Boost 引擎生命周期与已签名 shell 扩展要求,详见 doc/devdocs/modules/powerrename.md 的 UI 测试说明;ScreenRuler 的说明见 doc/devdocs/modules/screenruler.md。
运行前准备
.Next测试前置条件
- 构建产物:编译 PowerToys 运行时与
.UITests.Next测试可执行程序。 - 安装固定版本的
winappcli:可以安装官方 pin 定的运行时版本,或设置环境变量WINAPP_CLI_PATH指向winapp.exe;流水线辅助脚本为 .pipelines/InstallWinAppCli.ps1。迁移 Skill 中提到其安装命令为winget install Microsoft.winappcli。注意:.Next工程只在运行期需要winapp.exe,编译期零托管依赖,因此没有安装 winappcli 也能做编译验证。 - 使用真实交互桌面:UIA、前台输入、Explorer、热键与渲染在 session 0 中都无法工作——无头环境跑不了 UI 测试。
- 退出已运行的 PowerToys 实例:宿主桌面运行时,harness 拥有 runner 与模块生命周期的控制权,测试会自行拉起
PowerToys.exe(如--open-settings),旧实例会干扰启动与前台切换。
这一“harness 拥有生命周期”的约束可在 UITestBase.cs 源码中得到印证:基类内置StaleProcessNames(默认含PowerToys、PowerToys.Settings、PowerToys.FancyZonesEditor),每个测试前会先杀掉残留进程,保证从干净桌面状态启动;默认每次测试都是“独立启动 + 拆除”,只有子类将ReuseScopeAcrossTests置为true时才按类复用同一窗口。
旧版(Legacy)测试前置条件
- 安装 Windows Application Driverv1.2.1到默认目录(
C:\Program Files (x86)\Windows Application Driver)。 - 在 Windows 设置中启用开发人员模式(Developer Mode)。
运行测试
运行.Next测试
用仓库构建脚本编译目标工程,然后直接运行产出的 Microsoft.Testing.Platform 可执行程序:
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几个参数要点:
- 过滤器:应使用显式过滤属性,如
Name=、Name~、FullyQualifiedName~或TestCategory=。裸的显示名可能选中 0 个测试。 - 超时:上面的
7m只是聚焦过滤示例;模块级或工程级全量运行请调大超时值。 - 若编译失败,可查看工程旁的
build.<Configuration>.<Platform>.errors.log。从迁移 Skill 的说明可知,当依赖图中包含UITestAutomation.Next的 COM 引用时不要用裸dotnet build(.NET SDK MSBuild 无法执行ResolveComReference,会报 MSB4803),应使用仓库构建脚本或 Visual Studio 的全框架 MSBuild;dotnet restore仅用于首次生成project.assets.json。
另外,.Next测试不需要提升权限(新 harness 以非提升方式启动 runner),这与旧版(旧 harness 以runas提升方式启动 PowerToys)不同。
运行旧版测试
- 如果 PowerToys 正在运行,先退出。
- 在 Visual Studio 中打开
PowerToys.slnx并构建解决方案。 - 在Test Explorer中运行测试(菜单
Test > Test Explorer,或快捷键Ctrl+E, T)。
迁移 Skill 特别提醒:旧版套件若要命令行/CI 基线运行,需要以管理员身份先启动WinAppDriver.exe,再用vstest.console.exe执行(旧 harness 通过ProcessStartInfo { Verb = "runas" }提升启动 PowerToys,非提升的测试宿主会在启动阶段以误导性的Win32Exception级联全部失败)。
在持久化本地 VM 中运行.Next测试
文档推荐的本地执行后端是一对通过PowerShell Direct驱动的持久化 Hyper-V 客户机:Windows 10 与 Windows 11,每台都有已登录的标准用户桌面。VM 能暴露首次运行、profile、Explorer、WebView2、前台与生命周期假设,同时保留已暂存产物以便快速“编辑→构建→重跑”。
一次性宿主设置
在仓库外建一个 VM 根目录,然后按生成的后续步骤创建不入库的配置:
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如果-CheckOnly报告IsReady=false,必须由人类运行它打印出的提权命令——Hyper-V 组成员身份、受 DPAPI 保护的客户机管理员凭据以及客户机创建,都无法由 Agent 自动完成。完整过程参见 setup 参考文档,其中包含安装介质获取、vm.config.psd1编写、Windows 10/11 客户机创建与基线检查点(checkpoint)建立。
运行 Agentic 循环
先准备一个模块交换目录(module exchange),内含ui-tests.zip、powertoys-runtime.zip、winappcli.zip与dotnet-runtime.zip四个压缩包,具体打包规则见 agentic-loop 参考文档。产物会被解压到客户机本地存储,测试从不直接从宿主共享盘执行。
$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控制器会按需启动客户机,验证标准用户 token、Explorer 会话与桌面尺寸,再通过受限的交互式计划任务运行测试,持续流式输出进度,最终返回status.json、TRX 计数、逐测试失败信息、日志、截图以及保留的失败录像,全部落在<ExchangeRoot>\LocalVmResults\<runId>下。
迭代节奏(务必遵守):
- 每次源码变更后,只重建并替换发生变化的压缩包,然后用相同的聚焦过滤器配合
-ReuseStagedPayload重跑;待该行为被充分理解后再逐步放宽范围。 - 一个模块只有在 Windows 10 与 Windows 11 上**全量类别过滤器都通过(
executed == total)**才算完成。 - 最终做“干净 profile”确认前,应恢复基线检查点(
Reset-LocalVm.ps1 -Restore)。 - 桌面、PowerShell Direct、payload、shell 扩展签名与证据导出等问题,参见 本地 VM 故障排查指南。
相关资源约定也值得注意:默认资源档为 4 vCPU/8 GB RAM,套件全绿后再考虑降为Constrained档(1 vCPU/4 GB);-Platform参数并非装饰——它会以platform环境变量传入客户机、参与视觉基线命名,只允许x64Win10、x64Win11或ARM64三种取值(Windows on ARM 宿主只能跑 Windows 11 ARM64 客户机)。
在 CI 流水线中运行测试
PowerToys 的 UI 测试流水线提供灵活的构建与测试选项:
Pipeline 参数
buildSource:选择测试用的构建来源。
latestMainOfficialBuild:下载并使用 main 分支最新的官方 PowerToys 构建(默认值);buildNow:基于当前源码现场构建 PowerToys 并用于测试;specificBuildId:通过specificBuildId参数指定的构建 ID 下载特定构建。
specificBuildId:当
buildSource = specificBuildId时,填写要下载并测试的确切构建 ID。默认值为占位符"xxxx"。适用场景:针对特定已知构建做可复现测试、针对某一构建版本做回归验证、发布前在特定构建上验证修复。用法是填入构建 ID 数字(如12345)。uiTestModules:指定要构建与运行的 UI 测试模块。该参数同时控制要构建的
.csproj工程与要执行的.dll测试程序集。示例:['UITests-FancyZones']——只跑 FancyZones UI 测试;['MouseUtils.UITests']——只跑 MouseUtils UI 测试;['UITests-FancyZones', 'MouseUtils.UITests']——同时跑多个指定模块;- 留空则构建并运行全部 UI 测试模块。
重要:
uiTestModules的取值必须同时匹配测试工程名(构建期.csproj选择)与测试程序集名(执行期.dll运行)。
构建模式
- 官方构建测试(
buildSource = latestMainOfficialBuild或specificBuildId)- 下载并安装官方 PowerToys 构建(main 最新版或指定构建 ID);
- 只构建 UI 测试工程(全部或按
uiTestModules选取); - 对已安装的 PowerToys 运行 UI 测试;
- 自动同时测试机器级与每用户安装模式。
- 当前源码构建测试(
buildSource = buildNow)- 从当前源码构建整个 PowerToys 解决方案;
- 构建 UI 测试工程(全部或按
uiTestModules选取); - 对刚构建的 PowerToys 运行 UI 测试;
- 使用当前流水线构建的产物。
所有模式都支持
uiTestModules参数来控制具体构建与运行的模块;使用官方构建时,机器级与每用户安装模式会被自动测试。
为模块添加第一批 UI 测试
新工程一律走迁移 Skill
无论是新写.Next工程还是移植旧用例,都应使用 UI-tests migration skill——其中包含当前的可执行工程脚手架、API 映射、命名规则、CI 稳定性清单与已验证的示例工程。建议按顺序阅读它的 framework-differences(新旧框架概念差异)、api-mapping(逐行 API 对照表)、project-setup(csproj 脚手架与工程放置/命名规则)、patterns-and-pitfalls(常见模式与陷阱)以及 ci-stability(CI 稳定性要点与预检清单)等参考文档。
工程命名约定
测试工程统一放在模块的Tests子目录下,命名格式为{ModuleFolder}/Tests/{ModuleName}-{TestType(Fuzz/UI/Unit)}Tests。.Next工程按“场景 A”规则命名为[Module].UITests.Next并与旧工程并列共存,而不是删除旧工程。下图展示了仓库内 UI 测试工程的目录组织形态:
旧版工程的工程文件模板(仅存量套件参考)
下面这个工程文件样例描述的是旧版 WinAppDriver 框架,仅保留给既有旧套件使用;新.Next工程请勿照抄(.Next模板在迁移 Skill 的templates/Module.UITests.Next.csproj,特征是OutputType=Exe、TargetFramework=net10.0-windows10.0.26100.0、IsTestingPlatformApplication、RunVSTest=false且只ProjectReference到UITestAutomation.Next.csproj)。
<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>注意将OutputPath改为自己模块的路径。
编写测试类:继承 UITestBase 并指定 Scope
测试类继承自UITestBase。默认 Scope 从 PowerToys 设置界面开始;如果要从自己的模块窗口开始,就在构造函数中指定模块与窗口尺寸:
[TestClass] public class HostModuleTests : UITestBase { public HostModuleTests() : base(PowerToysModule.Hosts, WindowSize.Small_Vertical) { } }在.Next框架中,这个构造函数签名与UITestBase保持一致:基类构造函数接受scope(默认PowerToysModule.PowerToysSettings)、size(可选固定窗口尺寸)与enableModules(非 null 时,会在启动 runner 前把全局settings.json精确配置为只启用这些模块,形成确定性的模块基线)三个参数。
然后就可以开始执行 UI 操作,例如:
[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"); }从这段示例可以看出 UI 测试的通用骨架:先用可见性断言确认空视图状态,用VisualAssert.AreEqual对Entries元素做视觉基线比对,再通过元素查找与Click()驱动交互,最后用正/反断言验证状态迁移。.Next的VisualAssert支持把“WinUI/WebView 组合内容”纳入视觉基线,ScreenRecording会在失败时保留桌面录像与截图,方便事后诊断。
辅助工具与设计要点
- 可访问性工具:编写测试时需要查看元素的 accessibility 数据(例如确认某个按钮的可达名称)来定位点击目标。Windows 上的Accessibility Insights是官方推荐的检查工具(可查看 UIA 树、元素属性与 AutomationId)。
- 选择器设计:新框架使用
By与 winappcli 选择器语法(无 XPath/CssSelector),且元素为无状态——每次操作即时解析;多窗口发现通过Session的作用域(窗口级 vs 进程级,如Session.FromProcess)完成。 - 可寻址性兜底:若某个控件(如图标按钮)只有 tooltip 标签、没有 UIA Name,迁移 Skill 允许的唯一产品侧改动是给它添加
AutomationProperties.AutomationId(禁止使用x:Name,也禁止更大的隐藏 peer/状态字符串等改动)。 - 稳定性优先:优先使用“权威信号 + 等待”而不是固定 sleep;交互前做前台/完整性校验;失败证据(截图/视频)先于一切结论查看——断言只能告诉你“找到 0 行”,截图才能告诉你列表确实是空的还是选择器写错了。
总结:一条从“写测试”到“全绿发布”的完整链路
把文档与两个 Skill 串起来,PowerToys UI 测试的标准推进路径是:① 用迁移 Skill 搭建.Next工程并按稳定性清单写测试 → ② 宿主机构建到 exit code 0 → ③ 在本地 VM(先 Win10 后 Win11)跑一个确定性测试并诊断 → ④ 修复后以-ReuseStagedPayload增量重跑、逐步放宽 → ⑤ 两套系统全量executed == total→ ⑥ 恢复基线检查点做干净 profile 终验 → ⑦ 提交 CI,用buildSource+uiTestModules参数控制官方构建或现场构建的流水线验证。理解.Next的 winappcli 引擎模型、生命周期归属(harness 拥有 runner 与模块生命周期)以及“测试必须在真实交互桌面运行”这三个前提,是避免大部分“本地绿、CI 红”循环的关键。
【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考