从命令行检查并与之交互运行Windows应用程序。 由 AI 代理和开发人员用于 UI 测试、调试和自动化。
概述
winapp ui提供用于检查和与Windows应用 UI 交互的命令。
使用Windows UI 自动化(UIA)。 适用于任何Windows应用 - WPF、WinForms、Win32、Electron 和 WinUI 3。
大多数命令通过 UIA 模式驱动应用(无输入注入): ui click 是异常,对不支持 InvokePattern的控件使用真正的鼠标模拟。
快速入门
# Connect to any app and see its UI tree
winapp ui inspect -a notepad
# Find specific elements
winapp ui search Button -a notepad
# Activate an element
winapp ui invoke Close -a notepad
# Take a screenshot
winapp ui screenshot -a notepad
目标应用
按进程名称
winapp ui inspect -a notepad
winapp ui inspect -a slack # auto-picks visible window for multi-process apps
winapp ui inspect -a imageresizer # partial match: finds PowerToys.ImageResizer
按窗口标题
winapp ui inspect -a "LICENSE - Notepad"
winapp ui inspect -a "Fix WinApp" # partial title match
按 PID
winapp ui inspect -a 12345
按 HWND (稳定 — 在选项卡/标题更改中幸存下来)
# Discover HWNDs
winapp ui list-windows -a Terminal
→ HWND 985238: "🤖 Testing" (WindowsTerminal, PID 21228)
→ HWND 131906: "Fix WinApp" (WindowsTerminal, PID 21228)
# Target specific window
winapp ui inspect -w 131906
winapp ui screenshot -w 131906
用于 -a 发现, -w 用于稳定目标。 匹配多个窗口时 -a ,命令会用 HWND 列出这些窗口供你选择。
选择器
使用检查/搜索输出中显示的 [brackets] 选择器的目标元素。
有三种类型的选择器:
| Selector | 含义 | Example |
|---|---|---|
MinimizeButton |
AutomationId (在唯一时显示 — 稳定,首选) | winapp ui invoke MinimizeButton -a myapp |
btn-close-d1a0 |
语义 slug (当没有唯一的 AutomationId 时显示) | winapp ui invoke btn-close-d1a0 -a myapp |
Submit |
针对 Name/AutomationId 的纯文本搜索(不区分大小写的子字符串) | winapp ui invoke Submit -a myapp |
AutomationId 选择器 是开发人员集标识符(AutomationProperties.AutomationId 在 XAML 中)。
当 AutomationId 在整个 UI 树中是唯一的, inspect 并 search 直接将其显示为选择器时, 这些操作在布局更改、本地化和树结构调整中幸存下来。
当不存在唯一的 AutomationId 时,btn-close-d1a0将生成 Slug 选择器(例如)。
格式: prefix-name-hash. 哈希验证元素标识,但在 UI 更改后可能会过时。
检查输出格式
该 inspect 命令显示带有彩色输出的元素树(以青绿色表示选择器,名称为绿色,元数据为灰色):
TabView Tab (0,-1 1200x48)
TabListView List (4,-1 1100x48)
tab-newtab-5f5b TabItem "New Tab" (14,-1 200x48)
NewTabButton SplitButton "New Tab" [collapsed] (1104,5 96x36)
Found 10 elements (--depth 3). Use the first token as selector, e.g.: winapp ui invoke TabView -a terminal
每行 的第一个单词 是选择器 , 将其与其他命令一起使用 ui 。
当元素具有唯一的 AutomationId 时,它直接使用(例如,TabViewNewTabButton)。
当不存在唯一的 AutomationId 时,将使用生成的 slug(例如)。 tab-newtab-5f5b
语义污点
Slugs 使用格式:其中: prefix-normalizedname-hash
- prefix — 3 字母类型缩写(btn、txt、chk、cmb、itm、tab、img、lbl、pn、win、grp、lnk、mnu 等)
- normalizedname — AutomationId(首选)或 Name 中的小写字母数字,最大 15 个字符
- hash - 元素 RuntimeId 的 4 个字符哈希(验证元素标识)
Slugs 是 shell 安全(无特殊字符)、唯一的,可以直接用作参数。 哈希提供过期检测 - 如果元素已被替换,则会出现:“元素可能已更改。 重新运行检查。”
无名称或 AutomationId 的元素仅显示前缀 + 哈希(例如 pn-c8a3)。
消除多个匹配项的歧义
输出中的 Slugs 是唯一的 inspect/search ,但可以在布局更改之间更改 - 在多个匹配项时,在纯类型名称或文本上使用它们。 当选择器不明确时,CLI 会打印所有匹配项及其污点,以便你可以选取正确的匹配项,并使用该 slug 重新运行。
winapp ui search Button -a myapp # shows: btn-ok-a1b2 "OK", btn-cancel-c3d4 "Cancel"
winapp ui invoke btn-ok-a1b2 -a myapp # invoke using slug (preferred)
winapp ui invoke btn-cancel-c3d4 -a myapp # invoke the other Button by its slug
纯文本搜索
使用纯文本搜索元素 - 无需特殊语法:
winapp ui search Minimize -a notepad # finds elements with "Minimize" in Name or AutomationId
winapp ui search Close -a notepad # case-insensitive substring match
winapp ui invoke Minimize -a notepad # search + invoke in one step (disambiguates if needed)
winapp ui search "Save" -a notepad # find elements containing "Save"
winapp ui search "error" -a myapp # case-insensitive match
当文本搜索匹配多个元素(例如,SettingsExpander,其中组、按钮和文本)共享相同的名称时,CLI 会自动选取唯一可调用的元素。 如果多个是可调用的,它将列出所有匹配项与 slugs。
对于不可调用的搜索结果(例如 Button 内的 TextBlock),搜索会自动显示最接近 的可调用上级 ,即可用于 invoke的父元素。
这适用于所有搜索选择器:
lbl-savechanges-a1b2 "Save changes" (120,40 80x20)
^ invoke via: btn-save-c3d4 "Save"
可以直接使用图面选择器:
winapp ui invoke btn-save-c3d4 -a myapp # invoke the parent Button
指令
状态
连接到应用并显示连接信息。
winapp ui status -a notepad
winapp ui status -a notepad --json
检查
查看 UI 元素树。 输出显示层次结构的 2 空间缩进的语义污点:
winapp ui inspect -a notepad # full window tree, depth 3
winapp ui inspect -a notepad --depth 5 # deeper tree
winapp ui inspect txt-searchbox-e5f6 -a notepad # subtree rooted at element
winapp ui inspect --ancestors btn-close-d1a2 -a notepad # walk up from element to root
winapp ui inspect -a myapp --interactive # invokable elements only, auto-depth 8
winapp ui inspect -a myapp --hide-disabled # hide disabled elements
winapp ui inspect -a myapp --hide-offscreen # hide offscreen elements
示例输出(默认值):
win-aidevgalleryp-f1a3 "AI Dev Gallery Preview" (94,206 1280x1023)
pn-c8a3 (102,207 1264x1014)
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
itm-samples-3f2c "Samples" (102,330 72x62)
示例输出(--interactive — 仅可调用元素,平面列表):
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
btn-close-d1a2 "Close" (1318,206 48x48)
itm-home-7b3e "Home" (102,268 72x62)
itm-samples-3f2c "Samples" (102,330 72x62)
itm-models-9a4f "Models" (102,392 72x62)
元素可能显示以下状态标记:
-
[on]/[off]/[indeterminate]— 切换/复选框状态 -
[collapsed]/[expanded]— 树、组合框、菜单项的展开/折叠状态 -
[scroll:v]/[scroll:h]/[scroll:vh]— 可滚动容器(垂直、水平或两者兼有) -
[offscreen]— 元素在屏幕上不可见 -
[disabled]- 未启用元素 -
value="..."— 可编辑元素的当前文本内容(与名称不同时)
搜索
查找与选择器匹配的元素。 输出显示语义污点:
winapp ui search Button -a notepad # all buttons
winapp ui search Close -a notepad # finds elements with "Close" in name
winapp ui search SearchBox -a notepad # finds elements with "SearchBox" in name or AutomationId
winapp ui search Button --max 10 -a notepad # limit results
示例输出:
btn-minimize-d1a0 "Minimize" (1222,206 48x48)
btn-maximize-e2b1 "Maximize" (1270,206 48x48)
btn-close-d1a2 "Close" (1318,206 48x48)
输出中显示的 Slugs(例如, btn-minimize-d1a0)可以直接与其他命令一起使用:
winapp ui invoke btn-minimize-d1a0 -a notepad
get-property
从元素读取属性值。 包括模式特定的状态(ToggleState、Value、IsSelected 等)。
winapp ui get-property btn-submit-7a90 -a myapp # all properties
winapp ui get-property chk-checkbox-b2c3 -p ToggleState -a myapp # checkbox state
winapp ui get-property txt-textbox-a4b1 -p Value -a myapp # current text value
winapp ui get-property cmb-combobox-d5e6 -p ExpandCollapseState -a myapp # expanded or collapsed
屏幕截图
将窗口或元素捕获为 PNG。 当存在多个窗口(例如应用 + 打开对话框)时,它们将组合成一个 PNG,每个窗口都缝入其中。
winapp ui screenshot -a notepad # saves screenshot.png in cwd
winapp ui screenshot -a notepad --output my.png # custom filename
winapp ui screenshot -a notepad --json # returns file path as JSON
winapp ui screenshot -w 131906 # target specific HWND (+ its dialogs)
winapp ui screenshot txt-searchbox-e5f6 -a myapp # crop to element bounds
winapp ui screenshot -a myapp --capture-screen # capture from screen (includes popups/overlays)
当对话框或弹出窗口打开时,所有窗口都组合成一个 PNG,以便可以在单个图像中看到完整的 UI 状态。
需要捕获弹出菜单、下拉列表、浮出控件或工具提示覆盖时使用 --capture-screen 。 在 --capture-screen 模式(检测到空白帧后重试时),目标窗口将首先带到前台;正常窗口捕获不会移动窗口。
调用
以编程方式激活元素(单击按钮、切换复选框、展开组合框)。
winapp ui invoke btn-submit-7a90 -a myapp # by slug from inspect
winapp ui invoke btn-submit-a1b2 -a myapp # by slug from inspect/search
winapp ui invoke cmb-sizecombobox-b4c5 -a myapp # expand combo box
按顺序尝试模式:InvokePattern → TogglePattern → SelectionItemPattern → ExpandCollapsePattern。
click
使用鼠标模拟单击其屏幕坐标处的元素。 对不支持 InvokePattern 的控件(例如列标题、列表项)使用此选项。
winapp ui click btn-column1-a3f2 -a myapp # single click by slug
winapp ui click "Column1" -a myapp # single click by text search
winapp ui click btn-column1-a3f2 -a myapp --double # double-click
winapp ui click btn-column1-a3f2 -a myapp --right # right-click
set-value
在可编辑元素上设置值(TextBox/ComboBox 的文本,滑块编号)。
winapp ui set-value txt-textbox-a4b1 "Hello world" -a notepad
winapp ui set-value sld-volume-b2c3 75 -a myapp
get-value
从元素读取当前值。 使用智能回退链:TextPattern (RichEditBox, Document) → ValuePattern (TextBox, Slider) → SelectionPattern (ComboBox, RadioButton, TabView) →名称(标签)。
winapp ui get-value doc-texteditor-53ad -a notepad # read full document text
winapp ui get-value SearchBox -a myapp # read TextBox content
winapp ui get-value CmbTheme -a myapp # read ComboBox selected item
winapp ui get-value sld-volume-b2c3 -a myapp # read Slider value
winapp ui get-value lbl-title-a1b2 -a myapp --json # JSON: { "elementId": "...", "text": "..." }
焦点
将键盘焦点移动到元素。
winapp ui focus txt-textbox-a4b1 -a notepad
滚动到视图中
将元素滚动到可见区域。
winapp ui scroll-into-view itm-targetitem-c3d4 -a myapp
wait-for
等待元素出现、消失或让值达到目标。
winapp ui wait-for Button -a myapp --timeout 5000 # wait for any button
winapp ui wait-for btn-submit-7a90 -a myapp --timeout 5000 # wait for specific element
winapp ui wait-for CounterDisplay -a myapp --value "5" --timeout 5000 # wait for element value (smart fallback)
winapp ui wait-for lbl-status -a myapp --property Name --value "Done" --timeout 5000 # wait for specific property
winapp ui wait-for btn-submit-a1b2 --gone -a myapp --timeout 2000 # wait for element to disappear
winapp ui wait-for lbl-status -a myapp --value "Done" --contains # substring match instead of exact equality
滚动
滚动容器元素。 使用 - 查找(垂直)或[scroll:h](水平)标记的[scroll:v]可滚动容器search scroll。
# Find which elements are scrollable and in which direction
winapp ui search scroll -a myapp
# pn-scrollview-bfef Pane "scrollView" [scroll:v] (main content, vertical)
# pn-scrollviewer-bfb1 Pane "scrollViewer" [scroll:h] (horizontal list)
# Scroll the main content down
winapp ui scroll pn-scrollview-bfef --direction down -a myapp
# Jump to top/bottom
winapp ui scroll pn-scrollview-bfef --to bottom -a myapp
# If you target an element that's not scrollable, scroll walks up to find the nearest scrollable parent
winapp ui scroll itm-someitem-a1b2 --direction down -a myapp
get-focused
显示当前具有键盘焦点的元素。
winapp ui get-focused -a myapp
list-windows
列出应用的所有可见窗口,包括弹出窗口和对话框。
winapp ui list-windows -a imageresizer
winapp ui list-windows -a Terminal
winapp ui list-windows # all windows (no filter)
框架支持
| Framework | 检查 | 搜索 | 调用 | set-value | 屏幕截图 |
|---|---|---|---|---|---|
| WPF | ✅ 完整树 | ✅ 所有属性 | ✅ 所有模式 | ✅ | ✅ |
| WinForms | ✅ | ✅ | ✅ | ✅ | ✅ |
| Win32 | ✅ | ✅ | ✅ | ✅ | ✅ |
| WinUI 3 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 电子 | ⚠️ Chromium 树 | ⚠️ 有限公司 | ⚠️ 变化 | ⚠️ 变化 | ✅ |
| Flutter | ⚠️ 基本 | ⚠️ 基本 | ❌ 最小 | ❌ | ✅ |
故障排除
| Error | 原因 | 解决方案 |
|---|---|---|
| “找不到正在运行的应用” | 应用未运行或名称不匹配 | 检查进程名称或使用 PID |
| “多个窗口匹配” | 不 -a 明确值 |
从列出的选项中使用-w <HWND> |
| “具有多个窗口” | 进程有多个窗口 | 用于 -w <HWND> 定位特定目标 |
| “选择器匹配 N 个元素” | 不明确的旧选择器 | 将输出中的 slugs inspect 或追加[0][1]到旧版选择器 |
| “元素可能已更改” | Slug 哈希与当前元素不匹配 | 重新运行 inspect 或 search 获取新的污点 |
| “不支持任何调用模式” | 无法调用元素 | 在元素上用于 inspect 查找可调用的子级 |
| “找不到 UIA 窗口” | UIA 看不到该过程 | 用于 list-windows 查找 HWND,然后 -w |
| “窗口大小为零” | 窗口最小化 | 应用将自动还原 |
| 屏幕截图中未显示弹出窗口/下拉列表 | PrintWindow 不捕获覆盖 | 使用 --capture-screen 标志 |
常见模式
导航并验证
winapp ui invoke btn-settings-a1b2 -a myapp # click a button
winapp ui wait-for pn-settingspage-c3d4 -a myapp # wait for page to load
winapp ui screenshot -a myapp --output settings.png # verify visually
查找文本并调用其父级
# Search shows invokable ancestor; invoke auto-walks to it
winapp ui invoke 'Save changes' -a myapp
# Or search first to see what matches, then invoke
winapp ui search "Save changes" -a myapp; winapp ui invoke btn-save-c3d4 -a myapp
消除重复元素的歧义
winapp ui search '#Image' -a myapp; winapp ui invoke itm-image-a2b3 -a myapp
弹出窗口覆盖的屏幕截图
winapp ui set-value txt-searchbox-e5f6 "query" -a myapp; winapp ui screenshot -a myapp --capture-screen
导航、等待和验证(单链)
winapp ui invoke btn-settings-a1b2 -a myapp; winapp ui wait-for pn-settingspage-c3d4 -a myapp --timeout 3000; winapp ui screenshot -a myapp -o settings.png
发现、单击和验证
winapp ui inspect -a myapp --interactive; winapp ui invoke btn-submit-7a90 -a myapp; winapp ui screenshot -a myapp
文件对话框交互
文件打开/保存对话框是支持 UIA 的标准Windows对话:
# Trigger the dialog, find it, type the path, confirm
winapp ui invoke btn-openfilebtn-a2b3 -a myapp
winapp ui list-windows -a myapp # find dialog HWND
winapp ui set-value txt-1148-c4d5 "C:\path\to\file.png" -w <dialog-hwnd>
winapp ui invoke btn-open-e6f7 -w <dialog-hwnd>
用于 inspect -w <dialog-hwnd> --interactive 发现特定对话的实际污点。
为什么 ; 链接 (不是 &&)
当本机 CLI 写入 stderr 或使用 ANSI 转义序列时,PowerShell 的 && 操作员可能会冻结。 请 ; 改用 — 它无条件地运行每个命令,并避免此死锁。 这也适用于代理工作流:即使调用退出非零,你通常也希望运行屏幕截图。
CI 测试模式
在 CI 管道(GitHub Actions,Azure DevOps)中使用 winapp ui 命令进行冒烟测试和 UI 验证。
wait-for with --property and --value as an assertion — it returns exit code 1 on timeout, failing the CI step automatically.
在 GitHub Actions 中启动和测试
steps:
- name: Build
run: dotnet build MyApp.csproj -c Debug -p:Platform=x64
- name: Launch and test
run: |
$result = winapp run .\bin\x64\Debug\net8.0-windows10.0.26100.0\win-x64 --detach --json | ConvertFrom-Json
$appPid = $result.ProcessId
# Wait for window to initialize
winapp ui wait-for "Main Window" -a $appPid --timeout 30000
# Run tests — each wait-for exits non-zero on failure
winapp ui invoke "Login" -a $appPid
winapp ui wait-for "Dashboard" -a $appPid --timeout 10000
winapp ui screenshot -a $appPid -o dashboard.png
使用 断言元素状态 wait-for
wait-for --value 轮询元素的值与预期的字符串匹配,并使用与 相同的智能回退 get-value (TextPattern → ValuePattern → SelectionPattern → Name)。 返回匹配时退出代码 0,超时退出代码 1 - 使其成为 CI 友好断言。 用于 --property 改为检查特定的 UIA 属性。
# Assert: button click updated the counter (smart value fallback — works for TextBlock, TextBox, etc.)
winapp ui invoke "Counter Button" -a $pid
winapp ui wait-for "Counter Display" -a $pid --value "Count: 1" -t 5000
# Assert: text input was accepted
winapp ui set-value "Search Box" "hello world" -a $pid
winapp ui wait-for "Search Box" -a $pid --value "hello world" -t 3000
# Assert: checkbox was toggled (use --property for specific UIA properties)
winapp ui invoke "Dark Mode" -a $pid
winapp ui wait-for "Dark Mode" -a $pid --property ToggleState --value "On" -t 3000
# Assert: navigation happened (new page appeared)
winapp ui invoke "Settings" -a $pid
winapp ui wait-for "Settings Page" -a $pid -t 10000
# Assert: dialog was dismissed (element disappeared)
winapp ui invoke "Close" -a $pid
winapp ui wait-for "Dialog Title" -a $pid --gone -t 5000
使用 JSON 输出断言
与 PowerShell 或 jq 一 --json 起使用以获取更复杂的断言:
退出代码协定,
searchwait-for并且处于--json模式:当没有元素匹配(search)或等待超时时(wait-for),该命令将完全分析的结果信封写入 stdout({ "matchCount": 0, ... }或{ "found": false, "timedOut": true, ... })并返回退出代码 1。 Stderr 在--json模式下为空(记录器输出已取消)。 信封字段上的分支,或根据$LASTEXITCODE哪个更符合人体工学。
# Assert: search found exactly one match
$result = winapp ui search "Submit" -a $pid --json | ConvertFrom-Json
if ($result.matchCount -ne 1) { throw "Expected 1 Submit button, found $($result.matchCount)" }
# Assert: element has expected properties
# inspect --json returns { windows: [{ hwnd, title, elements: [...] }] };
# each window's elements[] is the nested tree (children rendered via .children).
$tree = winapp ui inspect "Counter Display" -a $pid --json | ConvertFrom-Json
$counter = $tree.windows[0].elements[0]
if ($counter.name -ne "Count: 3") { throw "Counter value wrong: $($counter.name)" }
完整冒烟测试示例
# Launch
$app = winapp run .\build-output --detach --json | ConvertFrom-Json
# Verify app loaded
winapp ui wait-for "Main Page" -a $app.ProcessId -t 30000
# Interact and assert
winapp ui invoke "Add Item" -a $app.ProcessId
winapp ui set-value "Item Name" "Test Item" -a $app.ProcessId
winapp ui invoke "Save" -a $app.ProcessId
winapp ui wait-for "Test Item" -a $app.ProcessId -t 5000 # assert item appeared in list
winapp ui wait-for "Save" -a $app.ProcessId --gone -t 3000 # assert save dialog closed
# Visual verification
winapp ui screenshot -a $app.ProcessId -o smoke-test.png