[Python] Windows 應用程式自動化 GUI Automation — WinAppDriver
最近心血來潮玩玩 WinAppDriver ,使用起來簡單方便,也能夠整合 Appium,程式語法跟 Selenium 很接近,還能跟 Azure Pipeline 做整合,就來寫個筆記記錄下來~
使用者介面 (UI) 的測試一直是所有 QA & RD 的痛點吧 QAQ 每次有新的版本就必需跑一遍 Regression,為了確保品值穩定。
但往往使用者介面如果做了自動化測試常常會有不穩定的情況,Windows Application GUI 自動化雖然很多工具可以選擇,例如早期使用過 Pywinauto 做 Windows 使用者介面自動化,長期下來顯得難以維護,程式碼龐大,語法複雜...實在有點悲劇。
就來看看 WinAppDriver 是怎麼做的吧!

WinAppDriver 安裝

可以到微軟的 WinAppDriver GitHub. 下載安裝包 msi 檔。
安裝完成之後 WinAppDriver 會安裝到 C:\Program Files (x86)\Windows Application Driver 這個路徑底下。


打開 cmd.exe 之後執行 WinAppDriver.exe 會發現,出現了錯誤。原因是因為需要先把 Win10 的開發人員模式開啟。
設定 > 開發人員專用 > 開發人員模式。

接著,再次執行 WinAppDriver.exe 就能在 console 上看到 WinAppDriver 已啟動成功在 http://127.0.0.1:4723

也可以自行改變 IP and Port,可以使用:
WinAppDriver.exe /h

如果是想把 WinAppDriver 寫下 log 檔並且跑在背景執行,可以使用:
Start /B WinAppDriver.exe > C:\WinAppDriver.logWindows 應用程式元件定位
如果之前有寫過 Selenium 必定知道元件定位需要一些屬性例如 id, name, class…,這些元件屬性需要透過 Element Inspector 去定位,微軟爸爸有提供一個工具讓開發者使用,需要先安裝 Windows 10 SDK。
下載完成後執行安裝檔,選擇所需要的 Tools 。
當然~你想要全部裝也是可以:D

安裝完成後可至安裝路徑開啟 Inspector.exe
C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64
接著,開啟小算盤作為範例,打開標記顯示。
當選取元件時,可以清楚的知道目前所選的是哪個元件。

來理解一下,元件屬性與 Appium Client API 的對應關係表:


當選擇標記某個 Application GUI 元件時,會在 inspector.exe 內看到這些屬性,當你了解屬性的對應之後就可以開始寫程式囉~
程式碼開發
Python 套件安裝
pip install Appium-Python-Client連接 Application
這裡使用 Speccy 為例,想做出一個右邊黃色框框主要資訊畫面的截圖。

from appium import webdriverdef setup_application_driver():
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Speccy\Speccy64.exe"
desired_caps['paltformName'] = "Windows"
desired_caps['deviceName'] = "WindowsPC"
driver = webdriver.Remote(
command_executor='http://127.0.0.1:4723',
desired_capabilities=desired_caps)
return driver使用上面這段程式碼,連結已啟動的 WinAppDriver。
( 注意:如果有改 IP or Port 記得修改程式碼內容 )

如上圖,在 inspector.exe 選擇使用 class name 作為定位元件的方式 ( 因為其他的屬性都不夠單一 ,容易造成定位錯誤 )。
接著,透過下面這段程式碼實現截圖功能:
from io import BytesIO
from PIL import Imagedef operate_speccy(driver: webdriver):
full_png = driver.get_screenshot_as_png()
take_element_screenshot(full_png, driver.find_element_by_class_name("SpeccyTree"), "main_window.png")def take_element_screenshot(full_png, element, filename):
location = element.location
size = element.size
left = location["x"]
top = location["y"]
right = location["x"] + size["width"]
bottom = location["y"] + size["height"] im = Image.open(BytesIO(full_png))
im = im.crop((left, top, right, bottom))
im.save(filename)因為使用 Appium 所提供的 element screenshot api 出現未知錯誤導致無法完成物件截圖,所以這裡這段 element screenshot 的做法,是一個 workaround.
這段採用的邏輯是,先用 Appium 提供的完整 Application 截圖,把整張畫面擷取下來,再使用 Appium 定位的元件的位置(長,寬,高),當作圖片裁切的點位依據,也就是對應到上方的 function “take_element_screenshot” 程式碼片段。
結果
下圖是經由程式碼截圖出來的結果,可以發現到因為程式碼執行的很快速,在 Speccy 還在 loading 時,就已經把圖片截圖完成,如果是要擷取正確的資訊,需要做一些等待或是找一個判斷條件來使程式做優雅的等待:D

初次玩玩的小筆記,等之後多踩一點地雷~再繼續研究~
附上完整程式碼:
