從命令列檢查並互動執行中的 Windows 應用程式。 AI 代理與開發者用於 UI 測試、除錯與自動化。
概觀
winapp ui 提供檢查及與應用程式介面互動的指令Windows
使用Windows 使用者介面自動化(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 能承受版面變更、本地化和樹狀結構的調整。
當沒有唯一 AutomationID 存在時,會產生 slug selector(例如 btn-close-d1a0)。
格式: prefix-name-hash。 雜湊值會驗證元素身份,但在介面變更後可能會過時。
檢查輸出格式
指令顯示 inspect 元素樹並以顏色輸出(selector 為青色,名稱為綠色,metadata 為灰色):
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,則直接使用(例如, TabView, NewTabButton)。
當沒有唯一的 AutomationId(AutomationId)時,會使用產生的 slug(例如 tab-newtab-5f5b)。
語意字條
Slug 的格式為: prefix-normalizedname-hash 其中:
- 前綴 — 三字母縮寫(BTN、TXT、CHK、CMB、ITM、TAB、IMG、LBL、PN、WIN、GRP、LNK、MNU 等)
- normalizedname — 以 AutomationId(偏好)或 Name 為小寫字母數字,最多 15 字元
- 雜湊 — 元素 RuntimeID 的 4-字十六進位雜湊(驗證元素身份)
slug 是殼層安全的(無特殊字元)、唯一,且可直接用作參數。 雜湊值提供過時偵測——如果元素已被替換,你會得到:「元素可能已變更。 重播檢查。」
沒有名稱或 AutomationID 的元素僅 pn-c8a3顯示前綴 + 雜湊值(例如 )。
多場比賽的消歧
輸出的 inspect/search 字條是唯一的,但會隨著版面變更而改變——當多個字模匹配時,會用在純字型或文字上。 當選擇器有歧義時,CLI 會用他們的 slug 列印所有匹配,這樣你就可以選對的那個並用那個 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 會自動選擇唯一可調用的元素。 如果多個可調用,則會列出所有帶有字條的匹配。
對於不可調用的搜尋結果(例如按鈕內的 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)
輸出中顯示的 slug(例如 btn-minimize-d1a0)可直接搭配其他指令使用:
winapp ui invoke btn-minimize-d1a0 -a notepad
取得性質
從元素讀取屬性值。 包含特定模式的狀態(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
集合值
在可編輯的元素上設定一個值(TextBox 代表 TextBox/ComboBox,數字代表滑桿)。
winapp ui set-value txt-textbox-a4b1 "Hello world" -a notepad
winapp ui set-value sld-volume-b2c3 75 -a myapp
取得值
讀取元素的當前值。 使用智慧備援鏈:TextPattern(RichEditBox、文件)→ ValuePattern(TextBox、滑桿)→ 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": "..." }
focus
把鍵盤焦點移到某個元素上。
winapp ui focus txt-textbox-a4b1 -a notepad
捲入檢視
將元素捲入可見區域。
winapp ui scroll-into-view itm-targetitem-c3d4 -a myapp
等著
等待元素出現、消失或某個數值達到目標。
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:v](search scroll垂直)或[scroll:h](水平)標記。
# 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
專注
顯示目前有鍵盤焦點的元素。
winapp ui get-focused -a myapp
列表視窗
列出應用程式中所有可見的視窗,包括彈出視窗和對話框。
winapp ui list-windows -a imageresizer
winapp ui list-windows -a Terminal
winapp ui list-windows # all windows (no filter)
框架支援
| Framework | 檢查 | 搜尋 | 叫用 | 集合值 | 螢幕擷取畫面 |
|---|---|---|---|---|---|
| WPF | ✅ 完整樹 | ✅ 所有性質 | ✅ 所有模式 | ✅ | ✅ |
| WinForms | ✅ | ✅ | ✅ | ✅ | ✅ |
| Win32 | ✅ | ✅ | ✅ | ✅ | ✅ |
| WinUI 3 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 電子 | ⚠️ Chromium 樹 | ⚠️ 限量 | ⚠️ 情況各異 | ⚠️ 情況各異 | ✅ |
| Flutter | ⚠️ 基本 | ⚠️ 基本 | ❌ 極簡 | ❌ | ✅ |
Troubleshooting
| 錯誤 | 原因 | 解法 |
|---|---|---|
| 「找不到運行應用程式」 | 應用程式無法執行或名稱不符 | 請檢查流程名稱或使用 PID |
| 「多個視窗匹配」 | 模糊 -a 值 |
從上述選項中選用-w <HWND> |
| 「有多個視窗」 | 程序有多個視窗 | 用 -w <HWND> 來鎖定特定目標 |
| 「選擇器匹配 N 元素」 | 模糊的舊有選擇器 | 從輸出中擷取 sug inspect[1],或附加[0]到 legacy selector |
| 「元素可能已經改變」 | 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 管線(GitHub Actions、Azure DevOps)中使用 winapp ui 指令來進行煙霧測試和 UI 驗證。
wait-for 與 --property--value 作為斷言 — 在逾時回傳退出代碼 1,自動失敗 CI 步驟。
在 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
Assert with JSON 輸出
--json搭配 PowerShell 或 jq 來處理更複雜的斷言:
在模式下的
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