メインコンテンツまでスキップ

FlaUI UI 自動化指南

用途:透過 Microsoft UI Automation (UIA) 框架自動化操作 Windows 桌面應用程式

NuGetFlaUI.UIA3(推薦)/ FlaUI.UIA2

版本:5.0.0

授權:MIT

支援:.NET Framework 4.8、.NET 6+、.NET 8+


什麼是 FlaUI

FlaUI 是一個基於 Microsoft UI Automation 的 .NET 測試框架,用來自動化操作 Windows 桌面應用程式。它可以:

  • 啟動/附加到應用程式
  • 找到視窗上的控件(按鈕、文字框、DataGrid 等)
  • 模擬使用者操作(點擊、輸入文字、選擇項目)
  • 截取螢幕截圖
  • 驗證 UI 狀態

為什麼選 FlaUI

工具狀態適用場景備註
FlaUI活躍維護Windows 桌面應用純 .NET、UIA3 支援
WinAppDriver已停止維護微軟已不再更新
Playwright活躍Web 應用不支援桌面
Appium + WinAppDriver部分活躍跨平台測試架構複雜
TestStack.White已停止維護FlaUI 的前身

UIA2 vs UIA3

項目UIA2UIA3(推薦)
API基於 COM基於 COM,更新版本
效能較慢較快
WPF 支援部分完整
Win32 支援完整完整
Pattern 支援基本完整(含 ItemContainer、VirtualizedItem)

結論:永遠用 UIA3,除非目標應用只支援 UIA2。


安裝

dotnet add package FlaUI.UIA3
# FlaUI.Core 會作為相依套件自動安裝

官方測試工具(含 NUnit 基底類別、截圖、錄影):

dotnet add package FlaUI.TestUtilities

測試框架:

dotnet add package NUnit   # 或 xUnit
dotnet add package NUnit3TestAdapter
ヒント

FlaUInspect 是 FlaUI 附帶的 UI 自動化樹檢視工具,可用來查看目標應用程式的 AutomationId、Name、ClassName 等屬性。開發測試時必備。以系統管理員身分執行,並確保與目標應用程式的位元數(32/64-bit)一致。


核心概念

UIA 自動化的對象是一棵 Automation Element Tree。每個應用程式的 UI 都是樹狀結構,從 Application 往下展開到 Window,再到具體控制項。下圖以一個典型的設備控制視窗為例:

樹的特性:

  • 每個節點都是 AutomationElement:取得子節點後可進一步轉型為具體控制項(AsButton()AsTextBox() 等)
  • AutomationId 是黃金識別子:開發 WPF 時主動設定 AutomationProperties.AutomationId,測試端就有穩定的元素定位
  • 層級會比視覺看到的深:FlaUInspect 顯示的樹通常含有看不見的中介 Pane / Group,搜尋時優先用 FindFirstDescendant(遞迴搜尋)而非 FindFirstChild(只搜直接子節點)

Application — 啟動或附加應用程式

using FlaUI.Core;
using FlaUI.UIA3;

// 啟動新的應用程式
var app = Application.Launch("DeviceControl.exe");

// 附加到已運行的程式
var app = Application.Attach(Process.GetProcessesByName("DeviceControl")[0]);

// 附加到指定 PID
var app = Application.Attach(processId);

Window — 取得視窗

using var automation = new UIA3Automation();

// 取得主視窗(等待最多 10 秒)
var mainWindow = app.GetMainWindow(automation, TimeSpan.FromSeconds(10));

// 取得所有視窗
var allWindows = app.GetAllTopLevelWindows(automation);

AutomationElement — UI 元素

所有 UI 控件都是 AutomationElement,可進一步轉型為具體控件類型。


元素搜尋

搜尋策略優先順序

  1. AutomationId(最推薦):開發時設定,不受語系影響
  2. Name:控件的可見文字,多語系會變
  3. ClassName:控件的類別名稱
  4. ControlType:控件類型(Button、TextBox 等)

FindFirstDescendant — 找第一個符合條件的子元素

// 用 AutomationId 搜尋(最推薦)
var startButton = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("StartButton"));

// 用 Name 搜尋
var stopButton = mainWindow.FindFirstDescendant(cf =>
cf.ByName("停止"));

// 用 ControlType 搜尋
var firstTextBox = mainWindow.FindFirstDescendant(cf =>
cf.ByControlType(ControlType.Edit));

// 組合條件
var tempInput = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("TemperatureInput")
.And(cf.ByControlType(ControlType.Edit)));

FindAllDescendants — 找所有符合條件的子元素

// 找到所有按鈕
var allButtons = mainWindow.FindAllDescendants(cf =>
cf.ByControlType(ControlType.Button));

// 找到所有 TextBox
var allInputs = mainWindow.FindAllDescendants(cf =>
cf.ByControlType(ControlType.Edit));

FindFirstChild — 只搜尋直接子元素

FindFirstDescendant 更快,但只搜一層:

var panel = mainWindow.FindFirstChild(cf =>
cf.ByAutomationId("MainPanel"));

var button = panel.FindFirstChild(cf =>
cf.ByAutomationId("StartButton"));

ConditionFactory (cf) 常用方法

方法說明
cf.ByAutomationId("id")依 AutomationId 搜尋
cf.ByName("name")依 Name 搜尋
cf.ByClassName("class")依 ClassName 搜尋
cf.ByControlType(type)依 ControlType 搜尋
cf.ByText("text")依文字內容搜尋
.And(condition)組合條件(AND)
.Or(condition)組合條件(OR)

基本操作

點擊按鈕

var button = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("StartButton")).AsButton();

button.Invoke(); // 觸發按鈕(推薦,不需要焦點)
// 或
button.Click(); // 模擬滑鼠點擊

輸入文字

var textBox = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("IpAddressInput")).AsTextBox();

textBox.Text = "192.168.1.100"; // 直接設定值
// 或
textBox.Enter("192.168.1.100"); // 模擬鍵盤輸入

選擇 ComboBox

var comboBox = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("DeviceTypeCombo")).AsComboBox();

comboBox.Select("Modbus TCP"); // 依文字選擇
// 或
comboBox.Select(2); // 依索引選擇

ToggleSwitch / CheckBox

var toggle = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("AutoReconnectToggle")).AsToggleButton();

toggle.Toggle(); // 切換狀態
// 或 設定特定狀態
if (!toggle.IsToggled)
toggle.Toggle();

最小完整範例

using FlaUI.Core;
using FlaUI.UIA3;

// 啟動應用程式
var app = Application.Launch(@"C:\Program Files\DeviceControl\DeviceControl.exe");

using var automation = new UIA3Automation();

// 等待主視窗出現
var mainWindow = app.GetMainWindow(automation, TimeSpan.FromSeconds(15));

// 輸入設備 IP
var ipInput = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("DeviceIpInput")).AsTextBox();
ipInput.Text = "192.168.1.100";

// 點擊連線按鈕
var connectButton = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("ConnectButton")).AsButton();
connectButton.Invoke();

// 等待連線成功指示
var statusLabel = mainWindow.FindFirstDescendant(cf =>
cf.ByAutomationId("ConnectionStatus")).AsLabel();

// 等待文字變為「已連線」
Retry.WhileException(() =>
{
Assert.AreEqual("已連線", statusLabel.Text);
}, TimeSpan.FromSeconds(10));

// 結束
app.Close();

自動化操作時序

把上面範例的執行流程畫成 sequenceDiagram,可以看出 Test、FlaUI Application、Window、Element 之間的互動順序:

時序重點:

  • Launch + GetMainWindow:Launch 返回後不一定能立刻取得視窗,GetMainWindow 內建等待邏輯
  • FindFirstDescendant 是同步呼叫:UIA 即時掃描目前的元素樹;若控制項尚未渲染需要搭配 Retry / 等待
  • Invoke vs ClickInvoke 直接呼叫 UIA 的 InvokePattern(不需要焦點/座標),優先使用;Click 模擬滑鼠座標點擊,遇到「按鈕無 InvokePattern」時才退回此法
  • 狀態變化要用 Retry.WhileException:UI 操作後的狀態更新是非同步的,Retry.WhileException 會反覆評估直到斷言通過或 timeout

本指南結構

頁面內容
概觀(本頁)核心概念、元素搜尋、基本操作
自動化 PatternPage Object、等待策略、DataGrid、Dialog、截圖
最佳實踐AutomationId、等待、測試隔離、CI