首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

Microsoft Active Accessibility:打造自動化測試工具的利器

     編輯:關於VC++

摘要

Active Accessibility (MSAA) 初衷是協助身障人士使用電腦 (螢幕放大、閱讀器、觸覺滑鼠),實際應用更廣:驅動其他軟體、模擬使用者輸入進行軟體測試。本文從基本概念出發,帶領開發一個測試應用程式,展示如何與常見控制項/UI 元素互動並處理 WinEvents。

技術背景

  • MSAA 1.0 於 1997 年 5 月推出,提供從任意 UI 元素擷取資訊的有效方法,實現程式化存取。
  • 支援作業系統:Win98、Win2000 內建;Win95 (英文版) 與 NT 4.0 SP6+ 需安裝 RDK/SDK (下載: MSDN)。
  • 適用場景:輔助工具、驅動其他軟體、自動化測試 (模擬鍵盤/滑鼠輸入)。

核心概念

可存取 UI 元素與 IAccessible 介面

  • 支援 MSAA 的 UI 元素稱為「可存取」。
  • 大多數情況意味著元素支援 IAccessible 介面。
  • MSAA 世界中,可存取元素即以 IAccessible 介面表示。

取得 IAccessible 介面指標的方法

  1. MSAA 函數:AccessibleObjectFromPoint、AccessibleObjectFromWindow 等。
  2. IAccessible 方法:get_accChild、get_accParent。

關鍵屬性:名稱、角色、狀態

對測試軟體最重要的三個屬性。例:檔案總管「尋找」對話框中「查找範圍:」組合方塊——名稱「查找範圍:」、角色 combobox、狀態「可見」。

SDK 工具:Object Inspector

顯示游標指向 UI 元素的屬性,幫助熟悉 MSAA 世界。

WinEvents:監視 UI 事件

  • 可存取元素觸發的事件稱 WinEvents:建立、名稱/狀態/位置/鍵盤焦點變化時觸發。
  • 事件清單見 WINABLE.H,名稱以 EVENT_OBJECT 或 EVENT_SYSTEM 開頭。
  • 機制似 Windows Hook。設定 Hook 可在進程外 (WINEVENT_OUTOFCONTEXT) 或進程內 (WINEVENT_INCONTEXT) 執行回呼。

鍵盤/滑鼠輸入模擬:SendInput

非 MSAA 一部分,但結合使用極自然。用於操作不完全支援訊息/IAccessible 方法的新 UI 元素。

MSAA 內部運作機制

核心功能由 OLEACC.DLL 提供。調用回傳 IAccessible 指標的函數時:

  1. 檢查元素是否內建支援 IAccessible (由應用程式實作)。
  2. 否則檢查視窗類別名:若為 USER/COMCTL32 支援類別,OLEACC.DLL 建立 Proxy 實作 IAccessible。大多數 COMCTL32 控制項有 Proxy 支援。
  3. 內建支援例子:自訂控制項、Owner-drawn、無視窗控制項。開發者需自行正確實作屬性/方法,定義名稱/角色等。
  4. 若類別名不被識別,建立預設 Proxy,僅提供基本 HWND 資訊 (位置、有效性、可見性),無特定控制項資訊。

理解運行時機制對開發使用標準控制項的應用至關重要——這意味著無需重寫應用即可自動相容 MSAA。名稱基於 Win32 控制項名稱,角色基於功能定義。

UI 元素父/子關係

每個 UI 元素有 IAccessible 指標與 子 ID 識別碼。所有可存取元素在 MSAA 術語中形成父/子樹。

例:「查找範圍:」組合方塊的祖先是「名稱和位置」屬性頁 (非直接)。

原則:每個基於 HWND 的 UI 元素有兩級 IAccessible:代表整個 HWND 的父級,代表客戶區/非客戶區 (捲軸、選單等) 的子級。被標籤標示的控制項 (如「查找範圍:」組合方塊) 在父/子樹中包含兩個條目:標籤名稱/角色、靜態文字與控制項相關介面。最高層父級為桌面視窗。

IAccessible/子 ID 對:為何不只用介面指標?

對於擁有許多子項的控制項 (如列表方塊),兩種設計:

  1. 控制項與每個子項各自支援 IAccessible → 增加記憶體消耗 (每個子項建立獨立 COM 物件)。
  2. 僅控制項支援 IAccessible,子項透過父介面 + 子 ID 存取。子 ID 為 VT_I4 型別,含程式決定的唯一值或序號 (第一個子項 ID=1 遞增)。

支援 IAccessible 的父元素自身亦以此對表示,子 ID 定義為 CHILDID_SELF (值 0)。子 ID 總是相對於特定 IAccessible 介面。

使用 MSAA 功能時,必須提供正確的 IAccessible/子 ID 對。若子項支援自身介面,用其介面/CHILDID_SELF;否則用父介面/子 ID。get_accChild 可檢查子項是否支援自身介面。

開發自動化測試軟體實戰

範例模擬使用者與「尋找: 所有檔案」對話框互動:用 FindWindow 檢查對話框是否存在,不存在則掛鉤 WinEvents 等待;出現後取得 IAccessible,導航至「包含文字:」編輯框、「命名:」組合方塊設定值,按下「立即尋找」按鈕,最後模擬 Alt-F4 關閉。代碼處理 Win2000 與 NT 4.0/9x 視窗結構差異。

核心課題:高效搜尋 UI 元素

測試程式必須在合理時間內找到目標元素的 IAccessible/子 ID 對。策略:

  • 用 Win32 FindWindow/EnumWindows 找到最近的視窗句柄 (比 MSAA 樹搜尋快得多)。
  • 呼叫 AccessibleObjectFromWindow 取得視窗對應 IAccessible。
  • 若需訪問同一對話框多控制項,找到父視窗後用 AccessibleObjectFromWindow 取得父 IAccessible,再順藤摸瓜導航子元素。

四種取得 IAccessible/子 ID 對的方法

  1. AccessibleObjectFromEvent:在 WinEventProc 回呼中調用,參數傳遞自事件,輸出觸發事件元素的介面/子 ID 對。
  2. AccessibleObjectFromPoint:指定螢幕座標取得元素介面/子 ID 對。
  3. AccessibleObjectFromWindow:取得視窗/客戶區/非客戶區控制項介面,子 ID 恆為 CHILDID_SELF。範例首次調用即用此法。
    if (S_OK == (hr = AccessibleObjectFromWindow(hWndMainWindow, OBJID_WINDOW, IID_IAccessible, (void**)&paccMainWindow)))
  4. 父/子樹導航 (FindChild):遞歸搜尋符合名稱、角色、類別的子元素。步驟:
    1. 取得父元素子項數 (get_accChildCount)。
    2. 列舉子項:檢查是否支援 IEnumVARIANT,支援則用 Next 取得 IDispatch 或子 ID;不支援則子 ID 為序號。
    3. 呼叫 get_accChild 檢查子項是否支援自身 IAccessible,支援則用其介面/CHILDID_SELF,否則用父介面/子 ID。
    4. 比對名稱、角色 (整數比對)、類別,匹配則返回;否則遞歸搜尋支援 IAccessible 的子項。

在元素上執行動作

accDoDefaultAction 是關鍵:每個可存取元素有預設動作 (按鈕=按下、核取方塊=切換)。範例按下「立即尋找」:

if(FindChild(paccMainWindow, lpstrName, "push button", "Button", &paccControl, &varControl)) {
    hr = paccControl->accDoDefaultAction(varControl);
    paccControl->Release(); VariantClear(&varControl);
}

結合 Win32 API 設定編輯框/組合方塊值:

if(FindChild(paccMainWindow, "Containing text:", "editable text", "Edit", &paccControl, &varControl)) {
    WindowFromAccessibleObject(paccControl, &hWnd);
    SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)"My Text");
    paccControl->Release(); VariantClear(&varControl);
}

模擬鍵盤/滑鼠輸入 (SendInput)

範例模擬 Alt-F4 關閉對話框:

INPUT input[4]; memset(input, 0, sizeof(input));
input[0].type = input[1].type = input[2].type = input[3].type = INPUT_KEYBOARD;
input[0].ki.wVk = input[2].ki.wVk = VK_MENU;   // Alt
input[1].ki.wVk = input[3].ki.wVk = VK_F4;     // F4
input[2].ki.dwFlags = input[3].ki.dwFlags = KEYEVENTF_KEYUP; // 釋放鍵 (關鍵!)
input[0].ki.time = input[1].ki.time = input[2].ki.time = input[3].ki.time = GetTickCount();
SendInput(4, input, sizeof(INPUT));

注意:KEYEVENTF_KEYUP 必須設定,否則按鍵永不釋放。每次調用可用 GetAsyncKeyState 檢查鍵狀態、BlockInput 阻擋干擾。

常見問題與對策

  1. 控制項部分/不支援 MSAA:內建支援或 OLEACC.DLL 不支援的控制項最棘手。對策:
    • 模擬鍵盤/滑鼠 (利用熱鍵/Tab 導航 + Enter),但軟體版本更新熱鍵/Tab 順序變更即失效。
    • 若控制項支援 IAccessible 讀取資訊方法,用 accLocation 取得座標模擬滑鼠點擊,較鍵盤導航可靠 (名稱/角色變更機率較 Tab 順序低)。
  2. 滑鼠動作限制:目標控制項必須可見且未被遮擋。IAccessible 方法實現若內用 SendInput 同受限,其他技術或可處理不可見元素。
  3. 多執行緒初始化問題:主調線程用 COINIT_MULTITHREADED 時,WindowFromAccessibleObject 對某些控制項失效 (COM 禁止 MTA 呼叫同步輸入方法)。對策:建立 STA 助手線程 (COINIT_APARTMENTTHREADED),主線程失敗時轉交給助手線程處理。
  4. WinEvent 回呼中 MSAA 呼叫失效:COM 伺服器處理特定事件時不處理回呼中的 MSAA 呼叫。對策:過濾問題事件,或盡量用 Win32 API 替代 (如用 GetWindowText 代替 get_accName),較安全雖不適用全自訂控制項。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved