iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0

第五章開始。前面二十天講的每一個坑,都是我在自己的測試裡踩到的——而自己的測試有一個結構性的問題:

一個為了測函式庫而寫的應用程式,只能證明函式庫跟自己一致。

tests/win32_controls_app.pywpf_grid_app.ps1 這些測試用的小程式是我寫的。我知道每個控制項叫什麼、什麼時候建立、有沒有 automation_id。它們很好用,但它們不會給我意外。

所以這一章換一種做法:拿四個從來沒聽過這個函式庫的真實開源應用來驅動。


四個標的

挑選的標準只有一個:一個標的代表一代 Windows UI 技術

應用程式 技術 為什麼選它
Notepad++ Scintilla 最廣泛被內嵌的第三方編輯器控制項
WinMerge Win32 / MFC 純 Win32,零外部相依,而且是視覺工具
DB Browser for SQLite Qt 跨平台框架,自繪,靠 QAccessible 橋接
Files WinUI 3 / .NET 微軟最新的桌面堆疊,XAML content island

這四個加起來覆蓋了從 1990 年代的 MFC 到 2020 年代的 WinUI 3。而它們對 UI Automation 的支援程度差異之大,超過我原本的預期。


同一個抽象,四種覆蓋率

UIA 承諾的是一個統一的抽象:不管底下是什麼框架,你都用 find_descendantInvokeValue 這一組東西。實際量出來是這樣:

怎麼識別元素 讀得到內容嗎 表格
Win32/MFC(WinMerge) 按鈕靠名字(在地化);編輯窗格的 class name 含模組載入基底位址 完全不行 N/A
Scintilla(Notepad++) class name 穩定;工具列按鈕沒有 id WM_GETTEXT 可讀 N/A
Qt(DB Browser) automation_id 是完整物件路徑——但看你拿到哪個 Qt Text 元素可讀 pattern 不穩
WinUI 3(Files) automation_id 穩定英文——但側邊欄的是在地化名稱 Value pattern 可讀 N/A

這張表本身就是這一章的論點:UIA 的實際覆蓋率取決於每個框架的 provider 實作了多少,而失敗方式各不相同。 有的沒有 pattern,有的 pattern 時有時無,有的連 class name 都不穩定,有的同一個 app 內部就不一致。

沒有一個「通用寫法」能同時吃下四個。有的是四組各自量過的寫法,加上一個共通的原則:先量,再寫


四個 app,四種「起始狀態」機制

還沒開始測任何功能之前,先有一個共通的問題要解決:應用程式會記住上次的樣子。

這在人手上是貼心,在 CI 上是不可重現。四個標的,四種不同的機制:

App 不可重現的來源 處理方式
Notepad++ 上次的 session 與開啟的檔案 -nosession -multiInst -noPlugin
WinMerge 使用者設定 /noprefs
DB Browser 上次開的 db 與選的分頁 每次給一個新的 db 檔
Files user_settings.json 裡的幾個鍵 改成 false

Files 那個值得展開,因為它跟第四章講的 Store 版記事本 TabState 是同一個形狀,只是換了外觀——一個 JSON 鍵而不是一個資料夾:

DETERMINISTIC_STARTUP = {
    "ContinueLastSessionOnStartUp": False,
    "RestoreTabsOnStartup": False,
    "OpenSpecificPageOnStartup": False,
}

不設這些的話,Files 首次啟動會導覽到一個 版本說明頁,而那一頁是內嵌的 WebView2。整棵 UIA 樹的一半會變成 Chromium 的無障礙樹——包括一個 RootWebArea 節點,而它會被 find_text_input() 誤當成主要文字輸入回傳,造成定位直接失準。

四個 app、四種機制、同一個需求。這是 GUI CI 真正的入門門檻,而且沒有任何一個 app 的文件會告訴你。


一個關於「必須存在」的設計

這四組測試在我的機器上跑得動,在別人的機器上不會——因為別人沒裝這四個 app。最自然的寫法是這樣:

requires_notepadpp = pytest.mark.skipif(
    not NPP.exists(), reason="Notepad++ is not installed on this machine"
)

test_scintilla.py 從 Scintilla 那次就這樣寫,8 個測試。而我在做這一章的時候才發現:它從來沒有在 CI 上執行過。

CI 沒有裝 Notepad++,於是 8 個測試全部 skip,而摘要行寫的是「118 passed, 2 skipped」——那 2 個 skip 沒有人看。

一組會 skip 的測試,在儀表板上跟一組會通過的測試長得完全一樣。

這個問題跟它的修法留到 Day 29 一起講,因為它跟「fail-closed」是同一個主題。這裡先記下結論:本機缺 app 就 skip 是對的預設,release gate 上缺 app 必須是失敗,而這兩件事要能分開設定。


小結

  • 為測函式庫而寫的應用程式只能證明函式庫跟自己一致,不會給你意外
  • 四個標的覆蓋 MFC → Scintilla → Qt → WinUI 3,而它們對 UIA 的支援差異極大
  • UIA 是一個承諾統一的抽象,實際覆蓋率取決於每個 provider 實作了多少
  • 四個 app 有四種「記住上次狀態」的機制,全都要處理掉才有可重現的起點
  • skipif 是安靜的:會 skip 的測試在儀表板上跟通過的測試一樣

明天從 Scintilla 開始,而它的第一課是一個我連錯三次的結論:USER32 會幫你把系統訊息跨處理程序 marshal,但不會幫你 marshal 自訂訊息。


上一篇
Day 20:在 MacBook 上開一台 Windows 11 ARM64 來重現 CI
下一篇
Day 22:系統訊息會被 marshal,自訂訊息不會
系列文
Windows 桌面軟體 CI 實戰:從系統工具開發到 Zero-RDP 自動化測試錄影24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言