mcpField
W
winapp-ui-automation

winapp-ui-automation

github.com/microsoft测试与质量1.3k61 次安装今天更新
代码仓库

关于 winapp-ui-automation

winapp-ui-automation 是供 Agent 和开发者通过命令行检查、操作运行中 Windows 应用界面的技能。它支持跨框架的 UI 自动化,并能协调并发的桌面工作流。

主要功能

  1. 01检查和搜索 UI 元素
  2. 02点击、调用并输入内容
  3. 03等待 UI 状态变化
  4. 04截取屏幕并录制操作
  5. 05协调并发 UI 工作流

适用场景

  1. 01开发期间验证 UI 状态
  2. 02自动化 Windows 应用测试流程
  3. 03通过可见界面行为排查问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/winappcli --skill winapp-ui-automation

安装后在 Claude Code 中输入/winapp-ui-automation确认已加载。

附带文件references/ui-json-envelope.md

详细介绍

如果你无法亲自运行 winapp(没有 shell,或命令被拒绝),请向用户提供其项目所需的准确 winapp 命令,而不只是描述步骤。

适用场景

  • 从命令行检查正在运行的 Windows 应用的 UI
  • AI Agent 与 Windows 应用交互(点击按钮、读取文本、截取屏幕截图)
  • 在开发或测试期间验证 UI 状态
  • 不使用 Playwright 或 Selenium 自动化 UI 工作流
  • 根据用户看到并可操作的内容,调试 WinUI 3、WPF、WinForms、Win32 或 Electron 应用的 UI
  • 不适用于 WinUI 3 应用的 XAML 内部问题(值或绑定为何以特定方式解析、哪个 XAML 行创建了某个元素、实时编辑属性、UI 审查意见):请使用 winui-devtools

前置条件

对于 WinUI 3 XAML 源码/树、绑定或依赖属性的诊断与编辑, 请使用 winui-devtools 技能和 winapp devtools。这需要附加目标,或明确授权使用 --attach 进行注入;仅提供 PID 并不代表同意。AutomationId 可在这两种工具中用作选择器;生成的 slug 无法用于 DevTools。对于跨框架操作、屏幕截图、等待和无障碍功能, 请继续使用 winapp ui。

在驱动场景时测量 WinUI 工作,请使用 性能技能,在 UI 操作前后设置捕获标记。

  • 对于 UIA 模式(任意应用):无需设置,可用于任何正在运行的 Windows 应用
  • 对于会注入输入的动词(click、hover、drag、touch、pen、scroll --wheel/--wheel-horizontal、send-keys --via send-input):需要未锁定的交互式桌面,且目标窗口能够置于前台。在锁定或安全桌面上,这些操作会立即失败,并返回 no_interactive_desktop。基于 UIA 模式的动词(inspect、search、get-*、wait-for、set-value、invoke、scroll --direction/--to)适用于无头/锁定会话,在 CI 中应优先使用。
  • screenshot 不属于该组:它始终需要独占执行时段,因此会排在其他 UI 工作流之后;捕获也可能需要可用的交互式桌面。引擎会在目标最小化时将其还原;如果无法进行帧捕获或使用了 --capture-screen,则会尝试将目标置于前台。
  • --capture-screen 需要且仅需一个窗口。-w <hwnd> 可指定一个窗口:截取该窗口的屏幕区域,包括其上方所有可见内容。如果 -a 匹配到多个顶层窗口或所属窗口,命令会在捕获前以 invalid_arguments 失败;请运行 winapp ui list-windows -a <app> 并使用 -w <hwnd> 重试。
  • 如果可能同时运行其他 UI 工作流,请为每个逻辑工作流设置一个 workflow id(见下文)。不设置也不会出错,但系统无法识别你的命令属于同一工作流。

与其他 UI 工作流协调

Windows 只有一个前台窗口、一个键盘焦点、一个光标和一个输入流。因此,winapp ui 会让桌面操作命令轮流协作,以免并发工作流抢走彼此的焦点或关闭彼此的菜单。此机制始终启用。只读命令永远不会等待。

跨命令保持桌面状态需要选择启用——不带 ID 时,每条命令都是一次性操作,结束后立即释放桌面:

# Set once per logical UI workflow — same value for cooperating calls, different values for
# independent workflows (even from the same agent).
$env:WINAPP_UI_WORKFLOW_ID = [guid]::NewGuid().ToString()

使用 Agent 操作时,以下规则尤为重要:

  • 每次工具调用通常都会启动一个全新的 shell,且没有进程祖先关系回退机制,因此,只有注入的 ID 才能对命令分组。不带 ID 时,每次调用都是独立的工作流。
  • 带 ID 的工作流会在最后一条命令执行后保留操作权四秒。这足以覆盖同一脚本中的连续命令;但在你思考期间,操作权会有意过期。仅在无法明确表示操作已完成时,将其视为后备机制,而不要依赖它来结束操作。
  • **完成已知操作序列时,运行 winapp ui yield。**它会立即交出桌面,而不会让等待中的工作流白等用不到的四秒宽限期。重复交出操作权,或在宽限期过后才交出,都会安全成功。
  • **思考间隔后,重新执行初始化操作。**其他工作流可能已经使用了桌面,因此请重新打开菜单/重新导航、重新解析元素,然后再操作。不要假定临时 UI 状态仍然存在。
  • 对于已知操作序列,优先使用一个紧凑脚本,而不是多次往返调用:winapp ui invoke View -w $hwnd; winapp ui search "Status bar" -w $hwnd; winapp ui click "Status bar" -w $hwnd; winapp ui yield。
  • record 与其所属工作流共用操作权,因此运行期间,同一工作流中的点击和输入都会被记录——但前提是两个命令都带有相同的 ID。不带 ID 的 record 会在整个录制期间阻塞其他所有工作流。
  • **排队顺序遵循所有者优先,然后按 FIFO 排序。**活跃工作流可以继续发出命令,优先于其他已在等待的工作流;它交出操作权或宽限期过期后,再按到达顺序服务等待者。
  • **等待时间不限,且可取消。**等待一秒后,stderr 会提示哪个操作占用了桌面以及已占用多久(使用 --json 和 --quiet 时不显示);按 Ctrl+C 会以状态码 130 退出,并返回错误代码 cancelled,命令不会执行。
  • 没有硬性上限——长时间运行的脚本、无休止的录制或故障循环,都可能阻塞其他会修改界面的工作流,直到该操作完成或被停止。

永远不会等待的命令:status、list-windows、inspect、search、get-*、wait-for。 会等待操作权但不会独占桌面的命令(适用于无头/锁定会话):set-value、scroll-into-view、scroll --direction/--to、record。这些命令会修改应用,因此会排在其他工作流之后。在自己的工作流中,它们可以与其他共享操作重叠——record 正是通过这种方式捕获它所录制的 set-value 调用——但如果自己的工作流中有更早的 DesktopExclusive 命令,它们仍会等待。因此,click 后接 set-value 时,会按你写下的顺序执行。 会独占桌面的命令:invoke、click、drag、hover、scroll --wheel/--wheel-horizontal、 touch、pen、focus、send-keys、screenshot。

# Finish a workflow deliberately rather than leaving the desktop reserved for four more seconds.
winapp ui yield

常见模式

在特定容器中读取
winapp ui search "Welcome to MyApp" -a myapp --root MailRow --type Text --class-name TextBlock
winapp ui get-value Subject -a myapp --root MailRow --type TextBox
winapp ui wait-for Subject -a myapp --root MailRow --type Edit --value Ready --timeout 10000
winapp ui search --class-name CalendarSurface -a myapp   # no text selector needed

在 search、get-property、get-value、wait-for 以及操作一个已选元素的命令(invoke、set-value、click、focus 等,但不包括 send-keys --target)中,可一起或分别使用 --root、--type 和 --class-name。 --type 和 --class-name 无需选择器即可使用;除 search 外,其他命令都必须匹配到且仅匹配一个元素。若匹配不明确,则会列出前五个匹配项及其 slug。单独使用 --root 不是查询。对于 set-value,请将 "" 作为选择器。 对于 click、hover、touch 和 pen,筛选条件不能与 --at 或 --path 同时使用。根元素必须唯一;只有其后代会被匹配。wait-for 会在每次轮询时重新解析根元素,即使它最初不存在也不例外。类型名称和完整的字面 ClassName 值均忽略大小写。 仅有以下类型别名:TextBox → Edit 和 TextBlock → Text。 有关完整的类型词汇、边界和错误行为,请参阅限定范围和类型的查询。

发现并交互
# See what's clickable, then screenshot for context
winapp ui inspect -a myapp --interactive; winapp ui screenshot -a myapp

# Click and verify the page changed
winapp ui invoke btn-settings-a1b2 -a myapp; winapp ui wait-for pn-settingspage-c3d4 -a myapp --timeout 3000; winapp ui screenshot -a myapp

# Fill a form and submit
winapp ui set-value txt-searchbox-e5f6 "hello" -a myapp; winapp ui invoke btn-submit-7a90 -a myapp; winapp ui screenshot -a myapp
查找可见文本并点击
# Search by text — output shows invokable ancestor
winapp ui search "Save changes" -a myapp
# Output:
#   lbl-savechanges-a1b2 "Save changes" (120,40 80x20)
#         ^ invoke via: btn-save-c3d4 "Save"

# Invoke by text — auto-walks to parent Button
winapp ui invoke 'Save changes' -a myapp
导航多页面应用
# Click nav item, wait for page, inspect what's available
winapp ui invoke itm-samples-3f2c -a myapp; winapp ui wait-for pn-samplespage-b4e7 -a myapp; winapp ui inspect -a myapp --interactive
在测试中选择精确操作
winapp ui invoke SettingsCategory -a myapp --action select
winapp ui invoke AgreeCheckbox -a myapp --action toggle-on --json
winapp ui invoke Open -w <dialog-HWND> --type Button --action invoke

使用 --action 可避免自动模式和祖先回退。省略该参数则使用现有的自动行为。使用 --root、--type 或 --class-name 时,筛选条件遵循与读取查询相同的匹配规则:在所选应用/窗口中必须恰好匹配一个元素,否则命令会失败且不执行操作,同时跳过祖先回退。有关支持的操作、范围、幂等切换和故障恢复,请参阅操作参考;有关操作结果,请参阅 JSON 信封。

消除重复元素的歧义
# Text matches by substring, but an element named exactly the text wins
winapp ui invoke Submit -a myapp      # acts on Button "Submit", not Button "Submit Order"
# When several elements share that name, the error shows slugs for each — pick the right one
winapp ui invoke Save -a myapp
# → Selector matched 2 elements:
#   [0] Button "Save" → btn-save-a1b2
#   [1] Button "Save" → btn-save-c3d4
# Use the slug: winapp ui invoke btn-save-c3d4 -a myapp

关键概念

  • 选择器方括号:inspect 和 search 的输出会用 [方括号] 显示选择器——在其他 ui 命令中使用括号内的值。选择器可以是 AutomationId(稳定,由开发者设置)或生成的 slug(例如 btn-name-hash)。
  • AutomationId 选择器:如果元素具有唯一的 AutomationId,它会直接成为选择器(例如 [MinimizeButton])。这类选择器在布局变化和本地化后仍然有效,是稳定定位的首选方式。
  • Slug 选择器:如果不存在唯一的 AutomationId,则会使用生成的 slug(例如 [btn-close-a2b3])。格式为:prefix-name-hash。UI 发生变化后可能会失效。
  • 纯文本搜索:search 和 invoke 接受纯文本——search Minimize 会查找 Name 或 AutomationId 中包含 "Minimize" 的元素(子字符串匹配,不区分大小写)。无需特殊语法。对单个元素执行操作的命令会优先使用精确匹配的 AutomationId,其次匹配 Name 与文本完全一致(忽略大小写)的唯一元素;search 仍会列出所有匹配项。
  • --interactive 标志:筛选出可调用、点击或设置值的元素(包括文本文档),自动深度为 8——这是查看可操作内容的最快方式
  • 显示可调用祖先:如果搜索结果不可调用,则会显示最近的可调用父元素及其选择器
  • ; 链接:用 ; 链接命令,即可在一次调用中执行多个操作,减少 Agent 往返次数
  • -a 与 -w:使用 -a 按名称/标题/PID 查找应用。使用 -w <HWND> 稳定地定位窗口
  • 元素标记:切换项使用 [on]/[off],折叠状态使用 [collapsed]/[expanded],可滚动容器使用 [scroll:v]/[scroll:h]/[scroll:vh],以及 [offscreen]、[disabled];可编辑元素使用 value="..."

用法

连接并发现
# Core loop and examples: `winapp ui --help`. Connect and see interactive elements in one call:
winapp ui status -a myapp; winapp ui inspect -a myapp --interactive
检查元素树
winapp ui inspect -a myapp --interactive      # elements you can invoke, click, or set-value; auto-depth 8
winapp ui inspect -a myapp --depth 5          # deeper tree at depth 5
winapp ui inspect txt-searchbox-e5f6 -a myapp  # subtree rooted at element
winapp ui inspect btn-settings-a1b2 -a myapp --ancestors  # walk up from element to root
winapp ui inspect -a myapp --hide-offscreen   # hide offscreen elements
查找元素
winapp ui search Close -a myapp               # finds elements with "Close" in name or automationId
winapp ui search Button -a myapp              # finds elements with "Button" in name (also matches type names)
winapp ui search image -a myapp               # case-insensitive substring match
截图
# App screenshot (may include owned windows in a labeled composite)
winapp ui screenshot -a myapp --output page.png

# Crop to element; capture with popups visible
winapp ui screenshot txt-searchbox-e5f6 -a myapp --output search.png
winapp ui list-windows -a myapp # use the main window's HWND below
winapp ui screenshot -w <hwnd> --capture-screen --output with-popups.png

# Bring window to foreground first (matches what the user is currently seeing)
winapp ui screenshot -a myapp --focus --output focused.png

即使明确指定了主 HWND,默认捕获也会包含所属窗口;它会生成一张带标签的合成图像,而不是多个单独的图像文件。对于打开的菜单、弹出面板、工具提示或教学提示中的项目,元素选择器会从该项目的弹出窗口裁剪,因此无需为其使用 --capture-screen。关于范围和屏幕上叠加层的位置,请参阅 截图。使用 --on sandbox 时,报告的截图路径是交付到主机上的目标路径。在附加了 WinUI DevTools 的应用中,截图不会包含其工具栏、标记、评论面板和检查器窗口。

录制视频(H.264 MP4)

将窗口或元素区域录制为 MP4。对于 Agent 和脚本,建议使用正数 --duration-sec N;否则录制会一直等待停止信号。npm 辅助工具要求提供 durationSec(整数,范围为 1–86400)。它们的中止信号会强制取消,而非正常停止。

# Record a window for 10s at 15 fps
winapp ui record -a myapp --duration-sec 10 --fps 15 --output demo.mp4

# Recommended agent evidence: MP4 plus timestamped JPEGs and an NDJSON index
winapp ui record -a myapp --frames --duration-sec 10 --fps 10 --output evidence.mp4 --json

# Include overlays/popups (captures from screen DC; may include occluding windows)
winapp ui record -a myapp --capture-screen --duration-sec 5 --output with-popups.mp4

# Show the mouse pointer and click rings (for demos of click-driven flows)
winapp ui record -a myapp --cursor --duration-sec 10 --output demo.mp4

# Programmatic stop: pipe a newline to stop and finalize the MP4 (for agent/script callers)
"" | winapp ui record -a myapp --json --output capture.mp4
  • 默认情况下,--duration-sec 0 会一直录制,直到按下 Ctrl+C、输入换行符或重定向的 stdin 到达 EOF。
  • --frames 会写入 <output-name>.frames,其中包含清单、NDJSON 索引和变化的 JPEG 图像。它支持 1–30 fps 和 --max-edge 64–4096(默认值为 1280),上限为 1 GiB。使用 elapsedMs 来限定过渡时长。
  • --cursor 会绘制指针,并在每次点击时绘制一个圆环;添加 --no-click-highlight 可保留指针而不显示圆环。使用 --frames 时,每行 frames.ndjson 会在图像像素坐标中增加 cursor(x、y、visible、inFrame)和 clicks(button、kind、elapsedMs、x、y)。
  • 默认情况下,已有的录制输出会被拒绝。请使用新路径,或明确指定 --overwrite,在新录制完成后替换它们。之前的帧目录会被归档,而不会删除。如果发生部分失败,请保留报告的证据并遵循 recoveryHint。
  • 默认录制会在窗口原位绘制打开的菜单、弹出面板和工具提示;无需设置标志。它们会被裁剪到窗口边界内,其他进程的弹出窗口则需要使用 --capture-screen(请参阅 录制中的菜单和弹出面板)。
  • --capture-screen 会从屏幕 DC 捕获,以包含叠加层和弹出窗口;捕获前会先将窗口置于前台。如果 WGC 不可用且未传入 --capture-screen,CLI 会返回错误——请使用 --capture-screen 重新运行,以同意通过屏幕 DC 捕获。由于屏幕 DC 会捕获实际位于前方的内容,因此会在捕获前立即验证目标是否位于前台;如果激活被拒绝,命令会失败并返回 foreground_not_target,且不会写入任何内容,而不是返回其他窗口的图像。
  • 如果提供的选择器与任何元素都不匹配,命令会立即失败并返回 element_not_found(而不是默默录制整个窗口)。
  • 对于打开的菜单、弹出面板、工具提示或教学提示中的项目,选择器会从该项目的弹出窗口录制,而不是从其后方的窗口录制。
  • --json 会将最终结果写入 stdout,并将每行一个 JSON 事件写入 stderr。
悬停(用于工具提示、弹出面板和悬停状态)

--dwell-time <ms> 设置悬停后等待的时长(默认值:800,范围:0–10000)。

winapp ui list-windows -a myapp # use the main window's HWND below, not the tooltip's

# Hover to trigger tooltip, then capture it (default 800ms dwell)
winapp ui hover btn-info-a1b2 -a myapp; winapp ui screenshot -w <hwnd> --capture-screen --output tooltip.png

# Longer dwell for apps with slow tooltip timers
winapp ui hover btn-info-a1b2 -a myapp --dwell-time 1200; winapp ui screenshot -w <hwnd> --capture-screen
点击或悬停屏幕坐标点

当需要按坐标点而非元素中心按下时(例如在 WebView2 页面内,或大型控件的某个部分),请传入 --at x,y,并使用 ui inspect 获取屏幕坐标,而不要使用选择器。--at 不能与选择器同时使用。

winapp ui click --at 1520,780 -a myapp            # add --right or --double as needed
winapp ui hover --at 1520,780 -a myapp
发送键盘输入

合成按键——相当于键盘版的 click。用于方向键、Tab、Enter 导航、快捷键和逐键输入(与 set-value 的原子写入相对)。令牌以空格分隔:命名按键(enter、down、tab、esc、f5)、修饰键组合(ctrl+shift+t)、字面文本(hello)和原始虚拟键(vk=0xNN)。

# Keyboard navigation then commit
winapp ui send-keys "down down enter" -a myapp

# Type the literal words "down down enter" instead of pressing those keys (text= escapes each token)
winapp ui send-keys "text=down text=down text=enter" -a myapp

# Same intent, less typing: --verbatim types the whole argument literally (and keeps exact whitespace)
winapp ui send-keys "down down enter" -a myapp --verbatim

# Shortcut: select all and delete
winapp ui send-keys "ctrl+a delete" -a myapp

# Focus a field, then type text into it
winapp ui send-keys "Hello world" --target txt-name-a1b2 -a myapp

# Transport: --via post-message (default, HWND-targeted, bypasses UIPI) or send-input (OS-wide)
winapp ui send-keys "enter" -a myapp --via send-input

# Fire a global hotkey: win+... is refused by default (acts on the shell); opt in with --allow-system-keys
winapp ui send-keys "win+shift+v" -a myapp --via send-input --allow-system-keys

# Screen-reader command: hold capslock or insert (Narrator+Ctrl+F12 toggles Narrator developer mode)
winapp ui send-keys "ctrl+capslock+f12" -a myapp --via send-input
  • 屏幕阅读器命令将 capslock 或 insert 作为修饰键按住(ctrl+capslock+f12),并且必须使用 --via send-input;因为屏幕阅读器不会接收已发布的按键,所以 post-message 会报错。如果没有运行屏幕阅读器,按住 capslock 会开启或关闭 Caps Lock(命令会发出警告);按住 insert 可避免这种情况。
  • 以修饰键开头但中间部分未知的令牌(ctrl+a+b)会报错,而不会被视为字面文本;请使用 text= 或 --verbatim 输入。
  • 默认的 post-message 以 HWND 为目标,可跨完整性级别工作,但无法触发 WH_KEYBOARD_LL 全局快捷键。它会自动将目标重定向到目标窗口的焦点子控件,因此经典 Win32/WinForms 子窗口控件(例如编辑框)可以接收输入。WinUI 3 / UWP / XAML 控件没有窗口句柄,会忽略已发布的 WM_CHAR/WM_KEYDOWN——post-message 无法向它们传递按键或文本(命令会发出警告,但仍以退出代码 0 结束,因为 PostMessage 无法确认是否传递成功)。对于 WinUI 3 / UWP / XAML 应用,请使用 --via send-input。
  • 与按键/修饰键名称冲突的令牌(例如 enter、down、ctrl+a)会被作为相应按键按下。要将其作为字面文本输入,请在前面加上 text=——text=enter 会输入单词 "enter";串联多个 text= 令牌即可输入字面短语,例如 text=down text=down text=enter。在 text= 的值中使用反斜杠转义,可输入令牌解析器原本会合并的空白字符:\s→空格,\t→制表符,\n→换行符,\r→CR,\\→反斜杠(例如,text=a\s\sb → "a b")。如果整个参数都是字面文本,请使用 --verbatim,而非逐个转义令牌:它会原样输入整个 keys 参数(不解析按键/组合键/vk=/text=),并保留精确空白——send-keys "down down enter" --verbatim 会输入这些单词。(--verbatim 不会解码反斜杠转义;要输入控制字符,请使用 text= 令牌。)
  • send-input 会发送完全真实的输入,但输入会发往前台窗口;从提升权限的进程向 AppContainer/AppX 注入时会被 UIPI 阻止。它会拒绝系统保留组合键(win+l、alt+f4、ctrl+shift+esc、ctrl+alt+del、alt+tab、……),因为这些组合键作用于操作系统/外壳,而非仅作用于目标——传入 --allow-system-keys 即表示允许(例如,触发 PowerToys 的 win+shift+v 或 win+r 等全局快捷键),或者使用 --via post-message(限定于窗口)将其直接发送到窗口。即使使用 --allow-system-keys,win+l 和 ctrl+alt+del 仍会被阻止——win+l 会通过 LockWorkStation() 锁定工作站(自动化无法解除),而 ctrl+alt+del 是安全注意序列(SAS),无论是否启用该标志,Windows 都会丢弃注入的此类输入,因此会报错(invalid_arguments),而不会错误地报告成功。在已锁定/安全桌面上,send-input 会立即失败并返回 no_interactive_desktop。
  • 逐键事件:两种传输方式都会让命名按键/组合键触发真实的 KeyDown。对于输入的字面文本,--via send-input 会将每个字符映射到其 VK(+Shift),让每个字符触发真实的 KeyDown 和由操作系统合成的 WM_CHAR(TextChanged)——如果下游逻辑依赖 KeyDown(例如 WinUI 3/WPF TextBox),请使用此方式;使用前先将目标窗口置于前台。--via post-message 会向焦点子控件逐字符发布 WM_CHAR(触发 TextChanged,并将正确的文本写入可跨完整性级别接收输入的经典 Win32 控件),但不会逐字符触发 KeyDown;而且由于 WinUI 3 / UWP / XAML 控件没有窗口句柄,完全无法向它们传递输入(命名按键和文本都不行);此类控件请使用 --via send-input。
  • 使用 --via send-input 输入较长字面文本时,会自动限速:文本会被拆分成较小的字符块,每次通过一次 SendInput 注入一块,并在同一轮注入的各块之间短暂停顿,让目标有时间处理输入队列并确保每个字符都能输入。一次不间断的突发输入会使队列溢出并静默丢弃字符,即使命令报告成功也是如此。限速仅适用于单次较长的文本输入——短文本、按键名称和修饰键组合(包括 ctrl+a delete 这样的序列)不会增加延迟;如果负载大到需要限速,命令会发出信息性警告。由于每个限速字符块都会输入到当前前台窗口,send-input 会在每个后续字符块注入前重新确认目标仍处于前台;如果注入过程中焦点离开目标,则会以 foreground_not_target 中止,避免焦点中途变化后剩余文本被输入到其他窗口(会改变焦点的组合键,例如 alt+tab,不受此规则限制,也不会被视为焦点偏移)。对于大批量文本,若控件支持,请优先使用 set-value(原子操作,无需按键或窗口处于前台)。
拖动(重新排序、调整大小、滑块、拖放)

使用 drag <from> <to>,在一个位置按下鼠标按钮,移动到另一位置,然后松开;每个端点都是元素选择器(使用元素中心)或来自 ui inspect 的屏幕 x,y 坐标。该命令使用 SendInput 和中间移动,让应用收到逼真的 WM_MOUSEMOVE 事件流。

# Reorder one item onto another (center → center)
winapp ui drag itm-card-9f8e itm-slot-2c1a -a myapp

# Element center → screen coordinates (as reported by `ui inspect`)
winapp ui drag itm-card-9f8e 300,400 -a myapp

# Raw screen coordinates → screen coordinates
winapp ui drag 120,200 480,200 -a myapp

# Right-button drag
winapp ui drag itm-card-9f8e itm-trash-0001 -a myapp --right
  • 选择器会从/向元素中心拖动;x,y 是屏幕坐标,使用与 ui inspect/search 报告的相同坐标系。拖动前会重新解析元素端点;如果元素仍在动画中,则会以 target_moved 失败;在已锁定/安全桌面上,拖动会以 no_interactive_desktop 失败。
触控手势(轻点、滑动、捏合、拉伸、长按)

注入合成触控。触点锚点可以是元素选择器(元素中心),也可以是明确的 --at x,y 屏幕坐标。优先使用现代合成指针设备,不可用时回退到旧版触控注入 API。

# Tap an element center; or tap explicit screen coordinates
winapp ui touch btn-ok-1a2b -a myapp
winapp ui touch -a myapp --at 320,240

# Long-press, swipe, and two-finger pinch/stretch (zoom)
winapp ui touch tile-photo-7b3c -a myapp --gesture long-press --hold-ms 600
winapp ui touch -a myapp --at 100,300 --gesture swipe --to-point 400,300
winapp ui touch img-map-9f8e -a myapp --gesture pinch --distance 200
winapp ui touch img-map-9f8e -a myapp --gesture stretch --distance 200 --angle 90
  • 手势:tap(默认)、double-tap、long-press、swipe、pinch、stretch。--fingers 可设为 1–10(捏合/拉伸始终为 2)。--angle 可设为 0–359,用于设置捏合/拉伸时手指的轴向(0 为水平,90 为垂直)。如果没有非零且位于前台的目标,则拒绝执行(no_target/foreground_not_target/no_interactive_desktop);每个坐标都会根据目标窗口进行检查,点位在窗口外时会发出非致命警告(在 --json 中为 warnings[]),但仍会继续注入,与鼠标命令一致。如果设备不支持注入触控,命令会返回真实的 Win32 错误,而不会虚报成功。在远程桌面/VM 会话中,即使退出码为 0,也不保证事件已送达;命令会附加送达情况不确定的警告(在 --json 中为 warnings[]);请通过截图验证效果。
笔/手写笔(轻点和墨迹笔划)

注入合成笔输入(Windows 10 1809+)。目标可以是元素中心、明确的 --at,也可以是完整的 --path 墨迹笔划,并可控制压力/倾斜/橡皮擦。

# Pen tap at element center; firm tap at explicit coords
winapp ui pen canvas-1a2b -a myapp
winapp ui pen -a myapp --at 320,240 --pressure 0.8

# Draw an ink stroke, or erase along one
winapp ui pen -a myapp --path "100,100 150,120 210,140 260,120"
winapp ui pen -a myapp --path "100,100 260,100" --eraser
  • --pressure 范围为 0.0–1.0,--tilt-x/--tilt-y 范围为 ±90°,--eraser 用于橡皮擦端。注入安全要求与 touch 相同:必须有非零且位于前台的目标窗口,并检查每个墨迹点(在窗口外 → 非致命的 warnings[] 提示,但仍会继续注入)。远程桌面上的笔输入路由尤其不可靠——退出码为 0 可能表示调用成功,但没有笔输入送达应用;命令会附加送达情况不确定的警告(在 --json 中为 warnings[])。请在本地交互式桌面上验证笔输入工作流。
读取元素状态
# Read text/value content (works for RichEditBox, TextBox, ComboBox, Slider, labels)
winapp ui get-value doc-texteditor-53ad -a notepad
winapp ui get-value SearchBox -a myapp
winapp ui get-value CmbTheme -a myapp              # reads ComboBox selected item via SelectionPattern

# Check toggle/selection state, value, scroll position
winapp ui get-property chk-agreecheckbox-b2c3 -a myapp --property ToggleState
winapp ui get-property txt-textbox-a4b1 -a myapp --property Value
winapp ui get-property cmb-modellist-d5e6 -a myapp --property IsSelected

# Read formatting across the whole text document (not the selection)
winapp ui get-property Document -a myapp --property FontWeight --json

# See what has keyboard focus
winapp ui get-focused -a myapp

对于 get-focused,-a 会包含所选进程中的窗口;-w 会将焦点限制在该确切窗口,不包括其所属弹出窗口。即使控件未提供自己的进程 ID,也可能通过父窗口属于目标。有关焦点无匹配和查询错误的处理方式,请参阅 references/ui-json-envelope.md。

要检查空白或已清空的字段,请使用 winapp ui wait-for SearchBox -a myapp --value ""。 JSON 输出以及如何单独读取辅助功能标签,请参阅空值行为。

get-property 还接受 FontName、FontSize、ForegroundColor、IsItalic 和 StrikethroughStyle。省略 --property 可包含全部六种格式属性。 应将 Mixed、NotSupported 和 Unavailable 视为不同的状态,而不是格式值。名称区分大小写;未知名称会以 invalid_arguments 失败。 有关单位和状态含义,请参阅格式参考;有关 JSON 结构,请参阅 references/ui-json-envelope.md。

设置值

为通过键盘输入准备控件,请使用 winapp ui focus <selector> -a <app> (在 Sandbox 中操作时保留 --on sandbox)。此命令会激活控件所在的窗口,并在成功前验证窗口同时处于前台且已获得键盘焦点。它会先还原最小化的窗口。如果命令提示目标窗口已最小化(某些应用,例如 Calculator,会在还原前隐藏界面),请运行 winapp ui focus -a <app>(不带选择器)以还原并激活窗口,然后重试。不要通过截图来解决焦点问题。如果激活被拒绝,请检查是否存在阻止操作的对话框,并请用户激活预期窗口;不要循环重试,也不要切换到本地桌面。有关失败代码和恢复方法,请参阅焦点参考。

set-value 通过回退链以编程方式写入(不发送按键,也不需要窗口处于前台):ValuePattern → RangeValuePattern(数值)→ 对仅支持 TextPattern 的编辑控件使用 LegacyIAccessible put_accValue。

winapp ui set-value txt-searchbox-e5f6 "hello" -a myapp        # TextBox/ComboBox via ValuePattern
winapp ui set-value sld-volume-b2c3 75 -a myapp                # Slider via RangeValuePattern
winapp ui set-value doc-compose-9f3a "hello" -a myapp          # RichEdit/compose box via LegacyIAccessible
  • 只要无障碍功能实现了 put_accValue,LegacyIAccessible 回退方式就能操作不公开 ValuePattern 的富文本编辑/撰写控件(原生 Win32 富文本编辑控件以及 Chromium/Electron/WebView2 撰写框通常都支持)。
  • WinUI 3 RichEditBox 和 WPF RichTextBox 不支持以编程方式设置值——这是有意设计的:对于 UI Automation 的值 API,它们是只读的(支持 Text 模式,但没有可设置的 Value 模式)。set-value 会报出明确错误;若要在这些控件中输入,请改用 send-keys(需要桌面已解锁且窗口处于前台)。注意,get-value 仍可通过 TextPattern 读取它们。
滚动容器
# Find scrollable containers — look for [scroll:v] (vertical) or [scroll:h] (horizontal)
winapp ui search scroll -a myapp
# Output:
#   pn-scrollview-bfef Pane "scrollView" [scroll:v] (2127,296 1191x965)
#   pn-scrollviewer-bfb1 Pane "scrollViewer" [scroll:h] (2127,296 1191x216)

# Scroll vertically
winapp ui scroll pn-scrollview-bfef --direction down -a myapp

# Scroll to top/bottom
winapp ui scroll pn-scrollview-bfef --to bottom -a myapp

# Scroll and then inspect for newly visible elements
winapp ui scroll pn-scrollview-bfef --direction down -a myapp; winapp ui search TargetItem -a myapp

# Synthesize real mouse-wheel input over the element (1 = one notch up, -1 = down) — tests wheel handlers (zoom, custom scroll)
winapp ui scroll img-map-a1b2 --wheel -1 -a myapp

# Horizontal (tilt) wheel: 1 = one notch right, -1 = left — pans timelines and other horizontal-wheel handlers
winapp ui scroll pn-timeline-c3d4 --wheel-horizontal 3 -a myapp
等待 UI 状态
winapp ui wait-for btn-submit-a1b2 -a myapp --timeout 5000
winapp ui wait-for itm-status-c3d4 -a myapp --value "Complete" --timeout 5000

提示

  • 将 --interactive 与 inspect 一起用作首条命令——它只显示可操作的内容
  • 使用 ; 串联命令以减少往返次数(关于为何不使用 &&,请见下文说明)
  • 使用输出中的 slug 定位特定元素——它们经过哈希验证,且对 shell 安全
  • 使用纯文本搜索查找元素:search Minimize、invoke Submit
  • 当文本匹配到多个元素时,名称与该文本完全一致的元素是唯一胜出者;若仍有多个匹配项,错误信息会显示每个元素的 slug——请选择正确的一个
  • 在 invoke 后使用 get-property --property ToggleState 验证复选框/切换控件的状态
  • scroll 会自动查找最近的可滚动父级
  • 要捕获打开的菜单、飞出菜单、工具提示或教学提示中的某个项目,请将其选择器传给 screenshot 或 record;捕获内容来自其弹出窗口
  • 当需要在窗口上保留弹出内容,或捕获应用不拥有的叠加层时,请按照 Screenshot 的说明使用 -w <hwnd> --capture-screen 选择窗口
  • 按照 Hover 的说明,在原位捕获工具提示和由悬停触发的 UI
  • 使用 --focus 在捕获前将目标窗口置于前台,而不切换到屏幕 DC 捕获(默认捕获路径使用 Windows.Graphics.Capture,即使窗口被遮挡也能工作)
  • 使用 --hide-disabled 和 --hide-offscreen 减少干扰信息
为什么用 ; 而不是 &&

使用 ;(而不是 &&)串联命令。PowerShell 的 && 运算符在原生命令行工具向 stderr 写入内容或使用 ANSI 转义序列时可能会卡住——这会导致管道死锁。; 会无条件运行每条命令,避免此问题。这也更适合 Agent 工作流:即使 invoke 以非零退出码结束,通常也仍需要运行截图,以查看出了什么问题。

文件对话框的解决方法

文件打开/保存对话框是支持 UIA 的标准 Windows 对话框。请使用现有命令与其交互:

# 1. Trigger the dialog (e.g., click "Open File" button)
winapp ui invoke btn-openfilebtn-a2b3 -a myapp

# 2. Find the dialog window
winapp ui list-windows -a myapp
# → Shows the main window + the dialog HWND
# Note: untitled zero-size windows are hidden by default; use --show-hidden to include them

# 3. Target the dialog, type the file path, and confirm
winapp ui set-value txt-1148-c4d5 "C:\path\to\file.png" -w <dialog-hwnd>
winapp ui invoke btn-open-e6f7 -w <dialog-hwnd>

注意:标准文件对话框中的文件名输入框通常具有 AutomationId 1148。使用 inspect -w <dialog-hwnd> --interactive 查找实际的 slug。

JSON 输出封装

ui inspect、ui get-focused、ui search 和 ui wait-for 的 --json 封装在 v0.3.1 中进行了调整。DPI 上下文和带类型的 get-property 元素在 v0.6.3 及更高版本中可用。主要变化:

  • ui inspect --json 现在将元素嵌套在 windows[].elements[] 下(之前是扁平的 elements[])。
  • 每个被检查的窗口和 ui status --json 目标都会报告 windowDpi、scale、dpiAwareness 和 coordinateSpace: "physical-screen-pixels"。这些是目标窗口的 DPI 上下文。如果无法读取 DPI,所选目标会立即失败,而不会默认使用 96;如果次要窗口在遍历期间消失,则会附带 dpiError,并省略这四个上下文字段。
  • 成功的 ui get-focused --json 查询会输出 { "hasFocus": false } 或 { "hasFocus": true, "element": {...} }(之前是裸 null);查询失败时会以非零状态退出,并在 stderr 上输出标准 JSON 错误,而不是返回 hasFocus 结果。
  • ui search --json 返回 { "matchCount", "hasMore", "matches" };ui wait-for --json 返回 { "found", "waitedMs", "element"?, "timedOut" }。
  • ui get-property --json 会保留 elementId 及其字符串值 properties 映射,并添加经过清理、带类型的 element。
  • 带类型的元素使用 type(而非 controlType),并以物理屏幕像素表示数值型 x、y、width 和 height。0,0,0,0 是 UIA 的空矩形/无显示 UI 矩形;isOffscreen 仍是独立字段。
  • 搜索和 wait-for 元素可能包含 invokableAncestor 字段(形状与元素相同)。
  • 已移除每个元素的 id、parentSelector 和 windowHandle — 请使用 selector 作为公开句柄。

包含示例的完整架构:references/ui-json-envelope.md。

在 Windows Sandbox 中自动化

winapp run . --on sandbox --detach
winapp ui inspect --on sandbox -a MyApp
winapp ui screenshot --on sandbox -a MyApp -o .\result.png

在后续 UI 命令之前使用 --detach,并在访客 PID 或窗口句柄中保留 --on sandbox。将相同的 WINAPP_UI_WORKFLOW_ID 注入协同运行的访客调用;这些调用全部完成后,使用 winapp ui yield --on sandbox 结束。

使用 winapp-sandbox 处理设置授权、已连接客户端要求、设置/重新连接期间的短暂焦点变化,以及捕获恢复。应用截图和录制内容(包括默认文件名和 --frames 目录)都会返回到主机。

相关技能

  • winapp-sandbox:在隔离的 Windows Sandbox 中运行和自动化应用
  • winapp-setup:将 Windows SDK 添加到项目
  • winapp-package:将应用打包为 MSIX

故障排查

错误原因解决方案
"No running app found"名称错误或应用未运行尝试使用进程名称、窗口标题或 PID
"Multiple windows match"多个窗口与 -a 匹配使用列出的选项中的 -w <HWND>
"Selector matched N elements"文本查询匹配了多个元素使用错误中显示的建议中的 slug,或使用 inspect 输出中的 slug
"Element may have changed"slug 哈希与当前元素不匹配重新运行 inspect 以获取最新的 slug
"does not support any invoke pattern"无法调用该元素如果存在可调用的祖先 slug,错误中会显示它——请使用该 slug
"No UIA window found"UIA 无法看到该窗口使用 list-windows 查找 HWND,然后使用 -w
截图中未显示弹出窗口默认捕获路径不包含无所有者的覆盖层如需选择打开的菜单或弹出面板中的某一项,请传入其选择器。如需原位显示弹出窗口或捕获无所有者的覆盖层,请按照 截图 中的说明,使用 -w <hwnd> --capture-screen 选择窗口
--capture-screen 返回 foreground_not_targetWindows 拒绝了激活请求(焦点窃取防护、UAC 提示或其他窗口自行激活),因此屏幕捕获会记录错误的窗口单击目标窗口,关闭抢占焦点的窗口,然后重试;或者移除 --capture-screen,直接捕获窗口
录制期间出现 element_not_found已提供选择器,但元素不在树中重新运行 inspect 或 search 以获取新的选择器
录制期间出现 ambiguous_selector纯文本选择器匹配了多个元素使用错误消息建议中的 slug,或使用 inspect 输出中的 slug
录制期间 WGC 不可用WGC 捕获初始化失败;不会静默回退检查 GPU/驱动程序;使用 --capture-screen 明确请求屏幕 DC 捕获

CLI 参考

运行 winapp <command> --help 查看当前命令选项,或运行 winapp --cli-schema 查看完整的机器可读命令架构。

评论

支持 Markdown