用户界面自动化

从命令行检查并与之交互运行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 树中是唯一的, inspectsearch 直接将其显示为选择器时, 这些操作在布局更改、本地化和树结构调整中幸存下来。

当不存在唯一的 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 哈希与当前元素不匹配 重新运行 inspectsearch 获取新的污点
“不支持任何调用模式” 无法调用元素 在元素上用于 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