使用者介面自動化

從命令列檢查並互動執行中的 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,則直接使用(例如, TabViewNewTabButton)。 當沒有唯一的 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 雜湊值與當前元素不符 重播 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 管線(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