winui-ui-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Scope — any Windows app

适用范围——所有Windows应用

winapp ui
drives Windows UI Automation (UIA), the accessibility layer every Windows UI framework exposes, so the AutomationId-based approach in this skill works on any Windows desktop app: Win32, WPF, WinForms, and WinUI 3, packaged or unpackaged. The file-picker tests below already drive the OS's Win32 file dialog through the same verbs. For a non-WinUI app, use the same verbs and script template and skip the WinUI-specific gotchas (x:Bind
LostFocus
commit, ContentDialog selectors, MSIX relaunch).
winapp ui
基于Windows UI Automation (UIA) 实现,这是所有Windows UI框架都会暴露的可访问性层,因此本技能中基于AutomationId的测试方法适用于任意Windows桌面应用:Win32、WPF、WinForms和WinUI 3,无论应用是打包还是未打包状态。下方的文件选择器测试已通过相同命令驱动操作系统的Win32文件对话框。对于非WinUI应用,可使用相同命令和脚本模板,跳过WinUI特有的注意事项(x:Bind的
LostFocus
提交机制、ContentDialog选择器、MSIX重启逻辑)。

Approach

测试方法

The goal of this skill is to validate UI and app functionality automatically, without manual interaction, by exercising the app's UI elements, verifying their state, and asserting that the app behaves as expected under test conditions.
There are two main approaches:
  1. Interactive exploration — manually run the app, use
    winapp ui <command>
    to explore the UI tree, find AutomationIds, verify element properties, and test functionality interactively. This is useful for discovery, but slow and expensive if repeated for every test iteration.
  2. Scripted batch testing — generate a
    ui-tests.ps1
    script that exercises all UI elements and asserts expected behavior in one pass. This allows you to run the tests automatically, capture results, and iterate quickly without manually interacting with the app each time.
Unless the user asked for interactive exploration, or you are unfamiliar with the code/app or need to explore the UI tree to discover AutomationIds for hidden or dynamically generated elements (flyouts, dialogs, lazy-loaded content), prefer scripted batch testing — it is faster, repeatable, and produces a record of pass/fail results that can be reviewed and acted on.
本技能的目标是通过操作应用UI元素、验证元素状态、断言应用在测试条件下的预期行为,实现UI与应用功能的自动化验证,无需人工交互。
主要有两种测试方式:
  1. 交互式探索——手动运行应用,使用
    winapp ui <command>
    探索UI树、查找AutomationId、验证元素属性并交互式测试功能。这种方式适用于功能探索,但如果每次测试迭代都重复操作,会非常耗时低效。
  2. 脚本化批量测试——生成
    ui-tests.ps1
    脚本,一键完成所有UI元素的操作与预期行为断言。这种方式支持自动化运行测试、捕获结果,无需每次手动操作应用即可快速迭代。
除非用户明确要求交互式探索,或者你不熟悉代码/应用,需要探索UI树来发现隐藏或动态生成元素(弹出菜单、对话框、懒加载内容)的AutomationId,否则优先选择脚本化批量测试——它速度更快、可重复执行,还能生成可查看和跟进的测试通过/失败记录。

winapp ui
Verbs

winapp ui
命令动词

  • Query:
    status
    ,
    list-windows
    ,
    inspect
    ,
    search
    ,
    get-property
    ,
    get-value
    ,
    get-focused
    ,
    wait-for
  • Interact:
    invoke
    ,
    click
    ,
    set-value
    ,
    focus
    ,
    scroll
    ,
    scroll-into-view
  • Advanced input:
    send-keys
    (synthetic keyboard + accelerators),
    hover
    (tooltips/flyouts),
    drag
    (drag-drop, reorder, sliders),
    touch
    (tap/swipe/pinch/stretch),
    pen
    (stylus ink, pressure/tilt/eraser)
  • Capture:
    screenshot
    ,
    record
    (H.264 MP4 video)
Run
winapp ui --cli-schema
for the complete command structure as JSON, or
winapp ui <verb> --help
for any single verb.
  • 查询类
    status
    list-windows
    inspect
    search
    get-property
    get-value
    get-focused
    wait-for
  • 交互类
    invoke
    click
    set-value
    focus
    scroll
    scroll-into-view
  • 高级输入类
    send-keys
    (模拟键盘+快捷键)、
    hover
    (触发工具提示/弹出菜单)、
    drag
    (拖放、重排序、滑块操作)、
    touch
    (点击/滑动/捏合/拉伸)、
    pen
    (手写笔输入、压力/倾斜/擦除功能)
  • 捕获类
    screenshot
    record
    (生成H.264 MP4视频)
运行
winapp ui --cli-schema
可获取完整的JSON格式命令结构,或运行
winapp ui <verb> --help
查看单个命令动词的详细说明。

Step 1: Use the Running App

步骤1:使用已运行的应用

If the app is already running, use its PID. Do NOT relaunch — use the PID already captured from the build step. If the app is not running, build and launch it using the guidance in the winui-dev-workflow skill.
如果应用已在运行,使用其PID。请勿重启应用——使用构建步骤中已捕获的PID即可。如果应用未运行,请按照winui-dev-workflow技能中的指导构建并启动应用。

Step 2: Write the Test Script

步骤2:编写测试脚本

If you wrote the code: Skip inspect — you already know all the AutomationIds and control structure from the XAML and code-behind. Write tests directly from that knowledge. Inspect misses popups, flyouts, dialogs, and lazy-loaded content anyway.
If you're verifying code you didn't write: Run inspect first to discover the UI:
powershell
winapp ui inspect -a <PID> --interactive
Then read the XAML files to find AutomationIds that aren't currently visible (flyout items, dialog buttons, secondary pages).
Create a
ui-tests.ps1
file that tests all the app's requirements in one pass:
powershell
undefined
如果你是代码开发者:跳过inspect操作——你已经从XAML和代码后置文件中了解所有AutomationId和控件结构。直接基于这些知识编写测试即可。Inspect工具会遗漏弹窗、弹出菜单、对话框和懒加载内容。
如果你要验证他人编写的代码:先运行inspect操作探索UI:
powershell
winapp ui inspect -a <PID> --interactive
然后读取XAML文件,查找当前不可见元素(弹出菜单项、对话框按钮、二级页面)的AutomationId。
创建
ui-tests.ps1
文件,一次性测试应用的所有需求:
powershell
undefined

ui-tests.ps1

ui-tests.ps1

param([Parameter(Mandatory)][int]$AppPid)
param([Parameter(Mandatory)][int]$AppPid)

NOTE: Do NOT name the parameter $Pid — it's read-only in PowerShell

注意:请勿将参数命名为$Pid——它是PowerShell中的只读自动变量

$ErrorActionPreference = 'Continue' $pass = 0; $fail = 0; $results = @()
$ErrorActionPreference = 'Continue' $pass = 0; $fail = 0; $results = @()

Get main window HWND (avoids PopupHost interference with JSON parsing)

获取主窗口HWND(避免PopupHost干扰JSON解析)

$windows = winapp ui list-windows -a $AppPid --json 2>$null | ConvertFrom-Json $hwnd = ($windows | Where-Object { $_.title -ne "PopupHost" } | Select-Object -First 1).hwnd
function Test-UI { param([string]$Name, [scriptblock]$Script) # IMPORTANT: Inside $Script, use 'throw' to signal failure — NOT 'exit 1' # (exit terminates the entire script, not just the test) try { $output = & $Script 2>&1 if ($LASTEXITCODE -eq 0) { $script:pass++; $script:results += @{ name = $Name; status = "PASS" } } else { $script:fail++; $script:results += @{ name = $Name; status = "FAIL"; detail = "$output" } } } catch { $script:fail++; $script:results += @{ name = $Name; status = "FAIL"; detail = "$_" } } }
$windows = winapp ui list-windows -a $AppPid --json 2>$null | ConvertFrom-Json $hwnd = ($windows | Where-Object { $_.title -ne "PopupHost" } | Select-Object -First 1).hwnd
function Test-UI { param([string]$Name, [scriptblock]$Script) # 重要提示:在$Script内,使用'throw'表示测试失败——不要使用'exit 1' # (exit会终止整个脚本,而不仅是当前测试) try { $output = & $Script 2>&1 if ($LASTEXITCODE -eq 0) { $script:pass++; $script:results += @{ name = $Name; status = "PASS" } } else { $script:fail++; $script:results += @{ name = $Name; status = "FAIL"; detail = "$output" } } } catch { $script:fail++; $script:results += @{ name = $Name; status = "FAIL"; detail = "$_" } } }

─── Element Existence ───

─── 元素存在性测试 ───

Test-UI "NavHome exists" { winapp ui wait-for "NavHome" -a $AppPid -t 3000 } Test-UI "NavSettings exists" { winapp ui wait-for "NavSettings" -a $AppPid -t 3000 }
Test-UI "导航主页存在" { winapp ui wait-for "NavHome" -a $AppPid -t 3000 } Test-UI "导航设置页存在" { winapp ui wait-for "NavSettings" -a $AppPid -t 3000 }

─── Navigation ───

─── 导航测试 ───

Test-UI "Navigate to Settings" { winapp ui invoke "NavSettings" -a $AppPid } Test-UI "Settings page loaded" { winapp ui wait-for "TxtUserName" -a $AppPid -t 3000 }
Test-UI "跳转到设置页" { winapp ui invoke "NavSettings" -a $AppPid } Test-UI "设置页加载完成" { winapp ui wait-for "TxtUserName" -a $AppPid -t 3000 }

─── Interactions ───

─── 交互操作测试 ───

Test-UI "Set username" { winapp ui set-value "TxtUserName" "TestUser" -a $AppPid } Test-UI "Click Save" { winapp ui invoke "BtnSave" -a $AppPid } # commits the TextBox binding Test-UI "Username value set" { winapp ui wait-for "TxtUserName" -a $AppPid --value "TestUser" -t 2000 }
Test-UI "设置用户名" { winapp ui set-value "TxtUserName" "TestUser" -a $AppPid } Test-UI "点击保存按钮" { winapp ui invoke "BtnSave" -a $AppPid } # 提交TextBox绑定值 Test-UI "用户名已设置" { winapp ui wait-for "TxtUserName" -a $AppPid --value "TestUser" -t 2000 }

─── Value assertions for different control types ───

─── 不同控件类型的值断言测试 ───

Test-UI "Theme is System default" { winapp ui wait-for "CmbTheme" -a $AppPid --value "System default" -t 2000 } Test-UI "Logging is off" { winapp ui wait-for "TglLogging" -a $AppPid --value "Off" -t 2000 }
Test-UI "主题为系统默认" { winapp ui wait-for "CmbTheme" -a $AppPid --value "System default" -t 2000 } Test-UI "日志功能已关闭" { winapp ui wait-for "TglLogging" -a $AppPid --value "Off" -t 2000 }

─── Accessibility Audit ───

─── 可访问性审计 ───

Only audit controls in the app's main window (exclude OS picker/popup controls)

仅审计应用主窗口内的控件(排除系统选择器/弹窗控件)

$allElements = (winapp ui inspect -a $AppPid --interactive --json 2>$null | ConvertFrom-Json).elements $appElements = @($allElements | Where-Object { $.type -match 'Button|TextBox|ComboBox|CheckBox|ToggleSwitch|TabItem|Edit' -and $.name -notmatch 'Minimize|Maximize|Close|System' -and # window chrome $.className -notmatch 'PickerHost|#32770|CabinetWClass' # OS dialogs }) $missingId = @($appElements | Where-Object { -not $.automationId }) if ($missingId.Count -eq 0) { $pass++; $results += @{ name = "All app controls have AutomationId"; status = "PASS" } } else { $fail++ $names = ($missingId | ForEach-Object { "$($.type) '$($.name)'" }) -join ", " $results += @{ name = "AutomationId coverage"; status = "FAIL"; detail = "Missing: $names" } }
$allElements = (winapp ui inspect -a $AppPid --interactive --json 2>$null | ConvertFrom-Json).elements $appElements = @($allElements | Where-Object { $.type -match 'Button|TextBox|ComboBox|CheckBox|ToggleSwitch|TabItem|Edit' -and $.name -notmatch 'Minimize|Maximize|Close|System' -and # 窗口边框控件 $.className -notmatch 'PickerHost|#32770|CabinetWClass' # 系统对话框 }) $missingId = @($appElements | Where-Object { -not $.automationId }) if ($missingId.Count -eq 0) { $pass++; $results += @{ name = "所有应用控件均包含AutomationId"; status = "PASS" } } else { $fail++ $names = ($missingId | ForEach-Object { "$($.type) '$($.name)'" }) -join ", " $results += @{ name = "AutomationId覆盖率"; status = "FAIL"; detail = "缺失:$names" } }

─── State Screenshots (capture each meaningful state for visual review) ───

─── 状态截图(捕获每个关键状态用于视觉审查) ───

New-Item -ItemType Directory -Force -Path "screenshots" | Out-Null winapp ui screenshot -a $AppPid -o "screenshots/01-initial.png" 2>$null
New-Item -ItemType Directory -Force -Path "screenshots" | Out-Null winapp ui screenshot -a $AppPid -o "screenshots/01-initial.png" 2>$null

...take more screenshots after key interactions above (mode switches, dialogs opened, etc.)

...在上述关键交互后截取更多截图(模式切换、对话框打开等场景)

─── Final Screenshot ───

─── 最终截图 ───

winapp ui screenshot -a $AppPid -o "test-screenshot.png" 2>$null
winapp ui screenshot -a $AppPid -o "test-screenshot.png" 2>$null

─── Results ───

─── 测试结果 ───

Write-Host "`nPassed: $pass | Failed: $fail" $results | Where-Object { $.status -eq "FAIL" } | ForEach-Object { Write-Host " FAIL: $($.name) — $($_.detail)" -ForegroundColor Red } $results | ConvertTo-Json | Out-File "test-results.json" if ($fail -gt 0) { exit 1 } else { exit 0 }
undefined
Write-Host "`n通过:$pass | 失败:$fail" $results | Where-Object { $.status -eq "FAIL" } | ForEach-Object { Write-Host " 失败:$($.name) — $($_.detail)" -ForegroundColor Red } $results | ConvertTo-Json | Out-File "test-results.json" if ($fail -gt 0) { exit 1 } else { exit 0 }
undefined

What to Test

测试覆盖范围

Write tests for every requirement from the user's prompt:
Requirement typeTest approach
"Has a button that does X"
search
to verify exists,
invoke
to click,
wait-for --value
to check result
"Text field shows value"
wait-for "TxtName" --value "expected"
— works for TextBox, TextBlock, labels
"Status bar contains text"
wait-for "StatusBar" --value "words" --contains
— substring match for dynamic content
"Dropdown is set to X"
wait-for "CmbTheme" --value "Dark"
— reads the selected item automatically
"Toggle is on/off"
wait-for "TglFeature" --value "On"
— reads the toggle state
"Navigation between pages"
invoke
nav item,
wait-for
a page-specific element to appear
"Open file dialog"
invoke
trigger,
list-windows
to find picker HWND, interact with
-w
"Save file dialog"Same as open — find picker with
list-windows
,
set-value
filename,
invoke
Save
"Right-click context menu"
click --right
on element,
invoke
the flyout MenuItem
"Keyboard shortcut (Ctrl+S, etc.)"
send-keys "ctrl+s" --via send-input
then
wait-for
the result
"Type into a TextBox/RichEditBox"
send-keys "text" --target "Id" --via send-input
(real per-key input)
"Tooltip / hover flyout appears"
hover
the element, then
wait-for
the tooltip/flyout
"Drag to reorder / resize / slider"
drag <from> <to>
then
wait-for --value
the new state
"Touch gesture (swipe/pinch/stretch)"
touch -g swipe/pinch/stretch
then assert the result
"Capture a repro clip of a flow"
record -a PID --duration-sec N -o clip.mp4
"Confirmation dialog"
invoke
trigger,
search
for dialog buttons,
invoke
Primary/Secondary/Close
"Data persists"Set values,
invoke
a button (to commit bindings), verify data file on disk (
Get-Content
+
ConvertFrom-Json
)
"All controls accessible"
inspect --interactive --json
+ check all have AutomationId
针对用户需求中的每一项要求编写测试:
需求类型测试方法
"存在执行X操作的按钮"使用
search
验证按钮存在,
invoke
点击按钮,
wait-for --value
检查操作结果
"文本字段显示指定值"
wait-for "TxtName" --value "预期值"
— 适用于TextBox、TextBlock、标签控件
"状态栏包含指定文本"
wait-for "StatusBar" --value "关键词" --contains
— 对动态内容进行子串匹配
"下拉框已设置为X"
wait-for "CmbTheme" --value "Dark"
— 自动读取选中项
"开关处于开/关状态"
wait-for "TglFeature" --value "On"
— 读取开关状态
"页面间导航"
invoke
导航项,
wait-for
页面专属元素出现
"打开文件对话框"
invoke
触发按钮,
list-windows
查找选择器HWND,使用
-w
参数交互
"保存文件对话框"与打开对话框操作相同 — 使用
list-windows
查找选择器,
set-value
设置文件名,
invoke
点击保存
"右键上下文菜单"
click --right
点击元素,
invoke
弹出菜单项
"键盘快捷键(如Ctrl+S)"
send-keys "ctrl+s" --via send-input
,然后
wait-for
检查结果
"在TextBox/RichEditBox中输入文本"
send-keys "文本内容" --target "Id" --via send-input
(模拟真实按键输入)
"工具提示/悬停弹出菜单显示"
hover
元素,然后
wait-for
工具提示/弹出菜单出现
"拖放重排序/调整大小/滑块操作"
drag <起始元素> <目标元素>
,然后
wait-for --value
验证新状态
"触摸手势(滑动/捏合/拉伸)"
touch -g swipe/pinch/stretch
,然后断言操作结果
"捕获流程复现视频"
record -a PID --duration-sec N -o clip.mp4
"确认对话框"
invoke
触发按钮,
search
查找对话框按钮,
invoke
点击主按钮/次要按钮/关闭按钮
"数据持久化"设置值,
invoke
按钮(提交绑定),验证磁盘上的数据文件(
Get-Content
+
ConvertFrom-Json
"所有控件可访问"
inspect --interactive --json
+ 检查所有控件是否包含AutomationId

Step 3: Run and Read Results

步骤3:运行测试并查看结果

powershell
.\ui-tests.ps1 -AppPid <PID>
Read
test-results.json
for structured pass/fail. Only fix code if tests fail.
powershell
.\ui-tests.ps1 -AppPid <PID>
查看
test-results.json
获取结构化的测试通过/失败结果。仅当测试失败时才需要修复代码。

Step 3.5: Look at the Screenshots

步骤3.5:查看截图

UIA assertions don't see clipping, overlap, wrong theming, or controls bleeding past their container — UIA returns
PASS
while the app is visually broken. Capture screenshots with
winapp ui screenshot
and view each PNG.
Capture the initial state and any state after a major interaction (the State Screenshots block in the script template above handles this).
Visual checklist — fail the run if any item is
no
:
  • No unintended scrollbars
  • No text ending in
    that shouldn't be
  • Hero elements fully visible (not sliced)
  • Right-edge controls fully visible
  • No overlapping rows
  • Content uses the available width — no asymmetric dead zones (e.g. content pinned to one edge leaving empty space on the other)
  • Spacing intentional — not cramped, not unintentionally vast
  • Theming matches the user's ask (Light/Dark/HighContrast if relevant)
  • Focus/hover/error states render if tested
If the checklist fails, it's a bug — fix before declaring done. Window too small → grow per
winui-design
Step 4.
UIA断言无法检测到控件裁剪、重叠、主题错误或控件超出容器边界等问题——即使应用视觉上已损坏,UIA仍会返回
PASS
务必使用
winapp ui screenshot
捕获截图并查看每张PNG图片。
捕获初始状态以及每次重大交互后的状态(上述脚本模板中的「状态截图」模块已处理此需求)。
视觉检查清单——若任意项为「否」则判定测试失败:
  • 无意外出现的滚动条
  • 不应显示省略号的文本未以
    结尾
  • 核心元素完全可见(未被截断)
  • 右侧边缘控件完全可见
  • 无重叠行
  • 内容充分利用可用宽度——无不对称空白区域(如内容固定在一侧,另一侧留有大量空白)
  • 间距合理——既不拥挤也不过于宽松
  • 主题符合用户要求(若相关则检查浅色/深色/高对比度模式)
  • 测试的焦点/悬停/错误状态正常渲染
如果清单中有未通过项,则属于bug——修复后再完成测试。若窗口过小,请按照
winui-design
步骤4调整窗口大小。

Step 4: Fix and Rerun (if the user asked for it)

步骤4:修复并重新运行(若用户要求)

If tests fail:
  1. Read the failure details from
    test-results.json
  2. Batch-fix all issues in one pass
  3. Rebuild with
    .\BuildAndRun.ps1
    (blocking mode — shows crash info if the fix broke something)
  4. Rerun
    .\ui-tests.ps1 -AppPid <PID>
    (parse PID from the
    launched (PID: XXXXX)
    output)
Maximum 2 fix-and-rerun cycles. If the same tests keep failing after 2 cycles, report them as known issues and move on — do not keep iterating.
如果测试失败:
  1. test-results.json
    中读取失败详情
  2. 批量修复所有问题
  3. 使用
    .\BuildAndRun.ps1
    重新构建应用(阻塞模式——若修复导致崩溃会显示错误信息)
  4. 重新运行
    .\ui-tests.ps1 -AppPid <PID>
    (从
    launched (PID: XXXXX)
    输出中解析PID)
最多进行2次修复-重运行循环。如果经过2次循环后相同测试仍失败,将其报告为已知问题并继续后续工作——不要持续迭代。

Assertion Reference

断言参考

Use
wait-for --value
as the primary assertion — it uses a smart fallback chain that reads the right value for any control type:
Control type
--value
reads from
Example
TextBlock / LabelName property
wait-for "LblTitle" --value "Home"
TextBox / NumberBoxValuePattern
wait-for "TxtName" --value "John"
RichEditBoxTextPattern
wait-for "Editor" --value "Hello"
ComboBoxSelected item (SelectionPattern)
wait-for "CmbTheme" --value "Dark"
ToggleSwitchToggle state (On/Off)
wait-for "TglDark" --value "On"
CheckBoxToggle state (On/Off)
wait-for "ChkAgree" --value "On"
Full assertion commands:
AssertionCommand
Element exists
winapp ui wait-for "Id" -a PID -t 3000
Element has exact value
winapp ui wait-for "Id" -a PID --value "expected" -t 3000
Value contains text
winapp ui wait-for "Id" -a PID --value "words" --contains -t 3000
Element gone
winapp ui wait-for "Id" -a PID --gone -t 3000
Specific property
winapp ui wait-for "Id" -a PID -p IsEnabled --value "True" -t 3000
Button clickable
winapp ui invoke "Id" -a PID
(exit code 0)
Set then verify
winapp ui set-value "Id" "text" -a PID
then
wait-for --value
Screenshot
winapp ui screenshot -a PID -o path.png
Dialog appeared
winapp ui list-windows -a PID --json
(check window count)
Right-click menu
winapp ui click "Id" -a PID --right
then
wait-for
menu item
Read raw property
winapp ui get-property "Id" -a PID -p IsEnabled --json
Read current value (no wait)
(winapp ui get-value "Id" -a PID --json | ConvertFrom-Json).text
— always pass
--json
when capturing into a variable (plain stdout can include advisory text like "Auto-selected HWND … from N windows"); otherwise prefer
wait-for --value
Scroll item into view
winapp ui scroll-into-view "Id" -a PID
— call before
wait-for
on virtualized ListView/repeater items below the fold
Set keyboard focus
winapp ui focus "Id" -a PID
— cleaner than clicking another control to trigger a TextBox
LostFocus
commit
Type real keystrokes into a control
winapp ui send-keys "text" --target "Id" -a PID --via send-input
Fire a keyboard accelerator/shortcut
winapp ui send-keys "ctrl+s" -a PID --via send-input
Hover to show tooltip/flyout
winapp ui hover "Id" -a PID
then
wait-for
Drag / reorder / slider gesture
winapp ui drag "From" "To" -a PID
Touch gesture
winapp ui touch "Id" -g swipe --direction up -a PID
Pen / ink stroke
winapp ui pen "InkCanvas" --path "x,y x,y" -a PID
Record a video clip
winapp ui record -a PID --duration-sec N -o clip.mp4
优先使用
wait-for --value
作为主要断言方式——它会智能选择合适的UIA模式读取值,适配所有控件类型:
控件类型
--value
读取来源
示例
TextBlock / LabelName属性
wait-for "LblTitle" --value "Home"
TextBox / NumberBoxValuePattern
wait-for "TxtName" --value "John"
RichEditBoxTextPattern
wait-for "Editor" --value "Hello"
ComboBox选中项(SelectionPattern)
wait-for "CmbTheme" --value "Dark"
ToggleSwitch开关状态(On/Off)
wait-for "TglDark" --value "On"
CheckBox勾选状态(On/Off)
wait-for "ChkAgree" --value "On"
完整断言命令:
断言类型命令
元素存在
winapp ui wait-for "Id" -a PID -t 3000
元素值完全匹配
winapp ui wait-for "Id" -a PID --value "预期值" -t 3000
值包含指定文本
winapp ui wait-for "Id" -a PID --value "关键词" --contains -t 3000
元素已消失
winapp ui wait-for "Id" -a PID --gone -t 3000
指定属性匹配
winapp ui wait-for "Id" -a PID -p IsEnabled --value "True" -t 3000
按钮可点击
winapp ui invoke "Id" -a PID
(退出码为0)
设置值后验证
winapp ui set-value "Id" "文本" -a PID
后执行
wait-for --value
截图
winapp ui screenshot -a PID -o path.png
对话框已弹出
winapp ui list-windows -a PID --json
(检查窗口数量)
右键菜单
winapp ui click "Id" -a PID --right
后执行
wait-for
查找菜单项
读取原始属性
winapp ui get-property "Id" -a PID -p IsEnabled --json
读取当前值(无需等待)
(winapp ui get-value "Id" -a PID --json | ConvertFrom-Json).text
— 将值捕获到变量时务必传递
--json
参数(纯标准输出可能包含"Auto-selected HWND … from N windows"这类提示文本);否则优先使用
wait-for --value
将元素滚动到视图中
winapp ui scroll-into-view "Id" -a PID
— 对虚拟化ListView/重复器中未显示的元素,在执行
wait-for
前调用此命令
设置键盘焦点
winapp ui focus "Id" -a PID
— 比点击其他控件触发TextBox的
LostFocus
提交更可靠
向控件输入真实按键
winapp ui send-keys "文本" --target "Id" -a PID --via send-input
触发键盘快捷键
winapp ui send-keys "ctrl+s" -a PID --via send-input
悬停显示工具提示/弹出菜单
winapp ui hover "Id" -a PID
后执行
wait-for
拖放/重排序/滑块操作
winapp ui drag "起始元素" "目标元素" -a PID
触摸手势
winapp ui touch "Id" -g swipe --direction up -a PID
手写笔输入
winapp ui pen "InkCanvas" --path "x,y x,y" -a PID
录制视频
winapp ui record -a PID --duration-sec N -o clip.mp4

Testing File Pickers

文件选择器测试

File/folder pickers (FileOpenPicker, FileSavePicker, FolderPicker) run in a separate
PickerHost
process but are fully interactable. The picker appears as an owned dialog window.
powershell
undefined
文件/文件夹选择器(FileOpenPicker、FileSavePicker、FolderPicker)运行在独立的
PickerHost
进程中,但完全支持交互操作。选择器会作为应用窗口的从属对话框显示。
powershell
undefined

1. Trigger the picker

1. 触发选择器

winapp ui invoke "BtnOpenFile" -a $AppPid
winapp ui invoke "BtnOpenFile" -a $AppPid

2. Find the picker window (it's a dialog owned by the app window)

2. 查找选择器窗口(它是应用窗口的从属对话框)

Start-Sleep 1 $allWindows = winapp ui list-windows -a $AppPid --json 2>$null | ConvertFrom-Json $picker = $allWindows | Where-Object { $_.title -match "Open|Save" } $pickerHwnd = $picker.hwnd
Start-Sleep 1 $allWindows = winapp ui list-windows -a $AppPid --json 2>$null | ConvertFrom-Json $picker = $allWindows | Where-Object { $_.title -match "Open|Save" } $pickerHwnd = $picker.hwnd

3. Interact with the picker using -w <HWND>

3. 使用-w <HWND>参数与选择器交互

Type a filename:

输入文件名:

winapp ui set-value "FileNameControlHost" "test.txt" -w $pickerHwnd
winapp ui set-value "FileNameControlHost" "test.txt" -w $pickerHwnd

Click Open/Save:

点击打开/保存:

winapp ui invoke "Open" -w $pickerHwnd # or "Save", "Cancel"
winapp ui invoke "Open" -w $pickerHwnd # 或"Save"、"Cancel"

Or cancel:

或取消:

winapp ui invoke "Cancel" -w $pickerHwnd
winapp ui invoke "Cancel" -w $pickerHwnd

4. Verify the app processed the file

4. 验证应用已处理文件

winapp ui wait-for "StatusBar" -a $AppPid -p Name --value "opened" -t 3000

**Tip:** Use `winapp ui inspect -w <pickerHwnd> --interactive` to discover the picker's controls — they include the folder tree, file list, filename textbox, and Open/Cancel buttons.
winapp ui wait-for "StatusBar" -a $AppPid -p Name --value "opened" -t 3000

**提示**:使用`winapp ui inspect -w <pickerHwnd> --interactive`探索选择器的控件——包括文件夹树、文件列表、文件名文本框以及打开/取消按钮。

Testing Context Menus and Flyouts

上下文菜单与弹出菜单测试

MenuFlyouts and ContextFlyouts are fully testable. They appear in the UI automation tree when open.
powershell
undefined
MenuFlyouts和ContextFlyouts完全可测试。它们打开后会立即出现在UI自动化树中。
powershell
undefined

1. Right-click to open a ContextFlyout

1. 右键点击打开ContextFlyout

winapp ui click "LstItems" -a $AppPid --right Start-Sleep 0.5
winapp ui click "LstItems" -a $AppPid --right Start-Sleep 0.5

2. The flyout MenuItems appear in the tree immediately

2. 弹出菜单项会立即出现在树中

Find them with inspect or search:

使用inspect或search查找:

winapp ui inspect -a $AppPid --interactive # shows MnuCopy, MnuDelete, etc.
winapp ui inspect -a $AppPid --interactive # 会显示MnuCopy、MnuDelete等项

3. Click a flyout item

3. 点击弹出菜单项

winapp ui invoke "MnuCopy" -a $AppPid
winapp ui invoke "MnuCopy" -a $AppPid

4. Verify the action

4. 验证操作结果

winapp ui wait-for "StatusText" -a $AppPid -p Name --value "Copied" -t 2000

**For MenuBar flyouts** (File, Edit, View menus):
```powershell
winapp ui wait-for "StatusText" -a $AppPid -p Name --value "Copied" -t 2000

**对于MenuBar弹出菜单**(文件、编辑、视图菜单):
```powershell

Click the menu header to open

点击菜单标题打开

winapp ui invoke "FileMenu" -a $AppPid Start-Sleep 0.5
winapp ui invoke "FileMenu" -a $AppPid Start-Sleep 0.5

Click the sub-item

点击子项

winapp ui invoke "MenuSaveAs" -a $AppPid
undefined
winapp ui invoke "MenuSaveAs" -a $AppPid
undefined

Testing ContentDialogs

ContentDialog测试

ContentDialogs are in-app controls (same window) — they appear directly in the UI tree when shown.
powershell
undefined
ContentDialog是应用内控件(与主窗口同进程)——显示后会直接出现在UI树中。
powershell
undefined

1. Trigger the dialog

1. 触发对话框

winapp ui invoke "BtnDelete" -a $AppPid Start-Sleep 0.5
winapp ui invoke "BtnDelete" -a $AppPid Start-Sleep 0.5

2. The dialog buttons appear in the tree

2. 对话框按钮会出现在树中

For a standard confirmation dialog:

对于标准确认对话框:

winapp ui search "Primary" -a $AppPid --json # finds the primary button winapp ui invoke "Primary" -a $AppPid # click "Yes"/"Delete"/"Save"
winapp ui search "Primary" -a $AppPid --json # 查找主按钮 winapp ui invoke "Primary" -a $AppPid # 点击"是"/"删除"/"保存"

Or:

或:

winapp ui invoke "Secondary" -a $AppPid # click "No"/"Don't Save" winapp ui invoke "Close" -a $AppPid # click "Cancel"
winapp ui invoke "Secondary" -a $AppPid # 点击"否"/"不保存" winapp ui invoke "Close" -a $AppPid # 点击"取消"

3. Wait for dialog to dismiss

3. 等待对话框关闭

winapp ui wait-for "Primary" -a $AppPid --gone -t 3000

**Tip:** ContentDialog buttons often don't have custom AutomationIds — use `inspect` to find the actual selector (slug or text match).
winapp ui wait-for "Primary" -a $AppPid --gone -t 3000

**提示**:ContentDialog按钮通常没有自定义AutomationId——使用`inspect`查找实际选择器(别名或文本匹配)。

Advanced Input: keyboard, hover, drag, touch & pen

高级输入:键盘、悬停、拖放、触摸与手写笔

Synthetic input beyond
invoke
/
click
/
set-value
. Each verb takes
-a <PID>
/
-w <HWND>
like the rest.
send-keys
— real keyboard input.
Named keys (
enter
,
tab
,
f5
), combos (
ctrl+shift+t
), raw
vk=0x42
, or literal text.
--via
selects the transport:
  • post-message
    (default) — HWND-targeted, no foreground needed; raises
    TextChanged
    but not per-character
    KeyDown
    .
  • send-input
    — OS-wide; real per-character
    KeyDown
    +
    TextChanged
    . Required for accelerators/shortcuts (
    KeyboardAccelerator
    , e.g.
    ctrl+t
    ) and for reliable typing into a WinUI 3 / WPF
    TextBox
    .
powershell
winapp ui send-keys "ctrl+s" -a $AppPid --via send-input                          # fire a Ctrl+S accelerator
winapp ui send-keys "hello world" --target "TxtName" -a $AppPid --via send-input  # focus then type
winapp ui send-keys --verbatim "down down enter" -a $AppPid                       # type the words, not the keys
--target
focuses first;
text=<literal>
/
--verbatim
type literally instead of interpreting key names. System combos (
win+r
,
alt+f4
) need
--allow-system-keys
+
--via send-input
(
win+l
stays blocked).
hover
— tooltips, flyouts, hover states.
Dwells on the element (
--dwell-time
, default 800 ms) so hover-triggered UI appears in the tree.
powershell
winapp ui hover "BtnInfo" -a $AppPid
winapp ui wait-for "InfoTooltip" -a $AppPid -t 2000
drag
— drag-drop, reorder, resize, sliders.
<from>
/
<to>
are each an element selector (its center) or screen
x,y
from
inspect
.
--hold-ms
long-presses before moving;
--dwell-ms
settles on the target before releasing (merge/latch targets).
powershell
winapp ui drag "ItemA" "ItemB" -a $AppPid          # reorder ItemA onto ItemB
winapp ui drag "SldVolume" 300,120 -a $AppPid      # drag a slider thumb to a point
touch
— touch gestures.
-g
:
tap
(default),
double-tap
,
long-press
,
swipe
,
pinch
,
stretch
;
--direction
/
--distance
/
--to-point
for swipes,
--fingers
for multi-touch. Needs an unlocked interactive desktop with the window foregroundable.
powershell
winapp ui touch "LstFeed" -g swipe --direction up --distance 400 -a $AppPid
winapp ui touch "ImgPhoto" -g stretch --distance 200 -a $AppPid    # pinch-to-zoom
pen
— pen/stylus.
Taps or draws ink;
--path "x,y x,y …"
for a multi-point stroke, with
--pressure
,
--tilt-x
/
--tilt-y
,
--eraser
(Win10 1809+).
powershell
winapp ui pen "InkCanvas" --path "50,50 120,80 200,60" --pressure 0.8 -a $AppPid
winapp ui pen "InkCanvas" --path "50,50 200,60" --eraser -a $AppPid
invoke
/
click
/
set-value
之外的模拟输入操作。每个命令动词都支持
-a <PID>
/
-w <HWND>
参数,与其他命令一致。
send-keys
——模拟真实键盘输入
。支持命名按键(
enter
tab
f5
)、组合键(
ctrl+shift+t
)、原始虚拟键码(
vk=0x42
)或文本内容。
--via
参数选择输入方式:
  • post-message
    (默认)——针对HWND发送消息,无需窗口处于前台;会触发
    TextChanged
    事件,但不会触发逐字符的
    KeyDown
    事件。
  • send-input
    ——系统级输入;模拟真实的逐字符
    KeyDown
    +
    TextChanged
    事件。对于快捷键(
    KeyboardAccelerator
    ,如
    ctrl+t
    )以及WinUI 3 / WPF
    TextBox
    的可靠输入是必需的
powershell
winapp ui send-keys "ctrl+s" -a $AppPid --via send-input                          # 触发Ctrl+S快捷键
winapp ui send-keys "hello world" --target "TxtName" -a $AppPid --via send-input  # 先聚焦再输入
winapp ui send-keys --verbatim "down down enter" -a $AppPid                       # 输入文本而非按键
--target
参数会先聚焦控件;
text=<文本内容>
/
--verbatim
参数会直接输入文本,而非解析按键名称。系统组合键(
win+r
alt+f4
)需要
--allow-system-keys
+
--via send-input
参数(
win+l
仍会被阻止)。
hover
——触发工具提示、弹出菜单、悬停状态
。在元素上停留指定时间(
--dwell-time
,默认800毫秒),使悬停触发的UI元素出现在树中。
powershell
winapp ui hover "BtnInfo" -a $AppPid
winapp ui wait-for "InfoTooltip" -a $AppPid -t 2000
drag
——拖放、重排序、调整大小、滑块操作
<from>
/
<to>
可以是元素选择器(元素中心)或从inspect获取的屏幕坐标
x,y
--hold-ms
参数设置拖动前的长按时间;
--dwell-ms
参数设置释放前在目标位置的停留时间(用于合并/锁定目标)。
powershell
winapp ui drag "ItemA" "ItemB" -a $AppPid          # 将ItemA拖到ItemB位置重排序
winapp ui drag "SldVolume" 300,120 -a $AppPid      # 将滑块拖动到指定坐标
touch
——模拟触摸手势
-g
参数指定手势类型:
tap
(默认)、
double-tap
long-press
swipe
pinch
stretch
;滑动手势支持
--direction
/
--distance
/
--to-point
参数,多点触摸支持
--fingers
参数。需要解锁的交互式桌面,且窗口可置于前台。
powershell
winapp ui touch "LstFeed" -g swipe --direction up --distance 400 -a $AppPid
winapp ui touch "ImgPhoto" -g stretch --distance 200 -a $AppPid    # 捏合缩放
pen
——模拟手写笔输入
。支持点击或绘制墨迹;
--path "x,y x,y …"
参数指定多点笔画路径,还支持
--pressure
--tilt-x
/
--tilt-y
--eraser
参数(Win10 1809及以上版本支持)。
powershell
winapp ui pen "InkCanvas" --path "50,50 120,80 200,60" --pressure 0.8 -a $AppPid
winapp ui pen "InkCanvas" --path "50,50 200,60" --eraser -a $AppPid

Recording a Video

视频录制

winapp ui record
captures the target window (or an element region) to an H.264 MP4 — handy for a repro clip of a flow or animation. Records until stopped (newline/EOF on stdin, or Ctrl+C);
--duration-sec N
gives a fixed-length clip (simplest for scripts). The MP4 is finalized on graceful stop.
powershell
winapp ui record -a $AppPid --duration-sec 6 --fps 30 -o "flow.mp4"
--max-edge N
downscales large windows;
--capture-screen
uses screen BitBlt to include popups/overlays outside the target window (also on
screenshot
).
winapp ui record
将目标窗口(或元素区域)捕获为H.264 MP4视频——适用于录制流程或动画的复现片段。录制会持续到停止(标准输入换行/EOF,或Ctrl+C);
--duration-sec N
参数可设置固定时长的录制(脚本中使用最简单)。视频会在正常停止时完成最终编码。
powershell
winapp ui record -a $AppPid --duration-sec 6 --fps 30 -o "flow.mp4"
--max-edge N
参数可缩小大窗口尺寸;
--capture-screen
参数使用屏幕BitBlt方式捕获,可包含目标窗口外的弹窗/覆盖层(
screenshot
命令也支持此参数)。

Key Gotchas

关键注意事项

  • set-value
    does NOT commit default TextBox bindings
    — WinUI 3
    x:Bind TwoWay
    on TextBox.Text updates the ViewModel on
    LostFocus
    by default. UIA
    set-value
    changes the text but doesn't trigger focus events. Fix: apps should use
    UpdateSourceTrigger=PropertyChanged
    on TextBox bindings (see design skill). If the app doesn't,
    invoke
    a button or
    click
    /
    focus
    another element after
    set-value
    to trigger
    LostFocus
    .
  • Set a
    RichEditBox
    with
    send-keys
    , not
    set-value
    — WinUI 3
    RichEditBox
    / WPF
    RichTextBox
    don't support UIA value-setting.
    focus
    (or
    --target
    ), then
    send-keys "…" --via send-input
    — which also raises real per-key
    KeyDown
    , so use it whenever a control reacts to individual keystrokes (or a
    KeyboardAccelerator
    ) rather than a bulk value change.
  • Verify persistence via the data file, not UI relaunch — killing and relaunching a packaged app from a test script is fragile (MSIX registration timing, PID issues). Instead, check the data file on disk:
    Get-Content $dataFile | ConvertFrom-Json
    and verify expected values.
  • Use
    $AppPid
    not
    $Pid
    $Pid
    is a read-only automatic variable in PowerShell
  • Use
    --value
    without
    -p
    — it auto-detects the right UIA pattern (TextPattern → ValuePattern → TogglePattern → SelectionPattern → Name). Only use
    -p PropertyName --value
    when you need a specific property like
    IsEnabled
  • File pickers need
    -w <HWND>
    — they run in a separate PickerHost process, so
    -a PID
    won't find them. Use
    list-windows
    to discover the picker HWND first
  • Flyouts need a short
    Start-Sleep
    after triggering — the menu items appear in the tree asynchronously
  • set-value
    不会提交默认TextBox绑定值
    ——WinUI 3中TextBox.Text的
    x:Bind TwoWay
    绑定默认在
    LostFocus
    事件时更新ViewModel。UIA的
    set-value
    会修改文本,但不会触发焦点事件。解决方法:应用应在TextBox绑定中使用
    UpdateSourceTrigger=PropertyChanged
    (参考设计技能)。如果应用未设置,在
    set-value
    invoke
    按钮或
    click
    /
    focus
    其他元素来触发
    LostFocus
    事件。
  • 使用
    send-keys
    设置RichEditBox值,而非
    set-value
    ——WinUI 3
    RichEditBox
    / WPF
    RichTextBox
    不支持UIA值设置。先
    focus
    (或使用
    --target
    参数),然后执行
    send-keys "…" --via send-input
    ——这也会触发真实的逐字符
    KeyDown
    事件,因此当控件需要响应单个按键(或
    KeyboardAccelerator
    )而非批量值更改时,应使用此方法。
  • 通过数据文件验证持久化,而非重启UI——从测试脚本中终止并重启打包应用容易出现问题(MSIX注册时序、PID问题)。相反,直接检查磁盘上的数据文件:
    Get-Content $dataFile | ConvertFrom-Json
    并验证预期值。
  • 使用
    $AppPid
    而非
    $Pid
    ——
    $Pid
    是PowerShell中的只读自动变量
  • 使用不带
    -p
    参数的
    --value
    ——它会自动检测合适的UIA模式(TextPattern → ValuePattern → TogglePattern → SelectionPattern → Name)。仅当需要验证特定属性(如
    IsEnabled
    )时,才使用
    -p PropertyName --value
    参数
  • 文件选择器需要
    -w <HWND>
    参数
    ——它们运行在独立的PickerHost进程中,因此
    -a PID
    无法找到它们。需先使用
    list-windows
    查找选择器的HWND
  • 弹出菜单触发后需短暂
    Start-Sleep
    ——菜单项会异步出现在UI树中