Windows Automation 沒有想像中的難

我們時常能看到 Google 和 Apple 在每年的 Google IO 或 WWDC 上都會發佈一些測試相關的框架,且在目前 Mobile 裝置趨近飽和的驅使下,各家測試人員肯定會針對自家產品開發自動化測試來加速出版前測試,也因此讓這些框架的使用普及率大幅上升,網路上也能找到非常多相關的文章或部落格提供開發者參考。

反觀 Microsoft 這邊,我們卻較少看到官方釋出的一些測試框架新消息,難道 Microsoft 真的沒有官方的自動化測試框架嗎?其實 Microsoft 官方一直有一套名為 WinAppDriver 的自動化測試框架,但因為在前期時提供的支援度及效能較低,也鮮少被廣泛使用,直到去年中微軟釋出的最新的更新,不但在 API 支援度上有大幅提升,而且準確度及執行速度甚至比想像中的快速,今天就讓我們來趟 WinAppDriver 之旅吧!

我們將整個自動化測試流程拆成三大區塊來分別完成,就可以輕鬆的串起整個自動化流程,以下共分為:

  • 框架工具:WinAppDriver
  • 元件工具:Inspect.exe
  • 開發工具:Visual Studio

WinAppDriver

1. 直接安裝最新的 Release 版本吧!
2. 執行 WinAppDriver 建立 Session

由於 WinAppDriver 是一套類 Selenium 的 UI 測試框架,且部分功能的定義及開發也是由 Appium 所提供,因此 WinAppDriver 的執行與 Appium 相同,需要先建立一條 Session 來監聽 UI Requests,我們可以使用以下兩種不同的方法來執行 WinAppDriver

  • 前往 WinAppDriver 安裝位置( 預設在 C:\Program Files (x86)\Windows Application Driver)—> 在 WinAppDriver.exe 上點擊右鍵 —> Run As Administrator
  • 加入 WinAppDriver 安裝位置( 預設在 C:\Program Files (x86)\Windows Application Driver)至環境變數內 —> 使用 Administrator 開啟 PowerShell 輸入 WinAppDriver 執行即可

以上操作都會開啟預設在 Port 4723 的 Session,開啟後我們就能準備進行下個步驟囉!
(如果有需要開啟其他 Port 或更改預設 Port,可利用參數帶入,在此就不多做示範了。)

註:若該使用者非管理員,務必使用管理員權限來執行 WinAppDriver。參考

Inspect.exe

1. 確認電腦上有安裝 Windows 10 SDK

(預設安裝在 C:\Program Files (x86)\Windows Kits\10\bin\

2. 開啟最新版本的 Inspect.exe

如下圖所示,在 Windows Kits\10\bin\ 資料夾內應該會看到不同版本的 SDK,選擇最新版本的資料夾進入並找到 Inspect.exe 執行
Capture

3. 透過 Inspect.exe 尋找 App 內的元件識別

透過官方支援的 API 可得知有幾項 Attribute 是我們可以利用來找元件的,以下列出我們較常使用的 API 及分別的優缺點:

API Inspect.exe 對應的 Attribute 優點 缺點
FindElementByAccessibilityId AutomationId 每個元件有特別的識別 ID 需要請開發人員協助加上
FindElementByClassName ClassName 每個元件有特別的類別 頁面上可能會有重複的類別
FindElementByName Name 每個元件有特別的識別名稱 頁面上可能會有重複的名稱

如下圖所示,你可以透過 Inspect 看到 App 元件各種 Attribute 的資料,因此我們就能透過這些 Attribute 來找尋我們所需的元件了。

inspect

範例:尋找計算機的「9」元件

  • session.FindElementByAccessibilityId(“num9Button")
  • session.FindElementByClassName(“Button") (其他按鍵的 ClassName 可能也一樣)
  • session.FindElementByName(“Nine") (頁面上容易有其他的 9 出現,可能名稱也會是 Nine)

因此非常推薦皆使用 AutomationId 來尋找元件

Visual Studio

準備好測試框架、準備好元件工具,我們就能開始來寫測試啦!

前置作業

1. 建立 Visual Studio 測試專案

檔案 —> 新增 —> 專案 —> Visual C# —> 測試 —> 單元測試專案

2. 將 NuGet 套件管理改為「PackageReference」

不產生額外的 .config 檔來包裝 Packages,而會直接將 Packages 寫在 .csproj Reference 內

工具 —> NuGet 套件管理員 —> 套件管理員設定 —> 一般 —> 套件管理 —> 預設套件管理格式 —> 將 Packages.config 改為 PackageReference

3. 將 Appium.WebDriver 新增至專案內

專案 —> 管理 NuGet 套件 —> 搜尋 Appium.WebDriver —> 安裝

常用 Methods

在此為大家列出一些我們常使用的 Methods 使用細節給大家參考,基本上組合這些動作就可以滿足不少測試情境了!

1. 準備 待測 App 的 App ID

以下範例是建立 WindowsDriver Session 的方法,Uri 位址則視 WinAppDriver 架設的位址所定。

DesiredCapabilities appCapabilities = new DesiredCapabilities();
appCapabilities.SetCapability("app", AppId);
session = new WindowsDriver(new Uri("http://127.0.0.1:4723"), appCapabilities);
  • 如果待測 App 是傳統 Win32 Application 的話,AppId 直接帶入 exe 檔執行位置即可,如:
appCapabilities.SetCapability("app", @"C:\Windows\System32\notepad.exe");
  • 如果待測 App 是傳 UWP Application 的話,AppId 直接就要帶入 PackageFamilyName,如:
appCapabilities.SetCapability("app", "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");

透過 PowerShell 查詢 PackageFamilyName 的方法:
> Get-AppxPackage *YourAppName*
範例:查詢計算機的 Package Detail:
> Get-AppxPackage *Calculator*

2. 設定 WindowsDriver 靜態等待時間

WindowsDriver 每一個動作的預設 Timeout 時間,以下範例為 3 秒,意指 WindowsDriver 執行的每一個動作要是在 3 秒內找不到元件即視為失敗。

session.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(3));
3. 送出鍵盤事件

SendKeys() API 送出 Control, Alt, Shift 時,對於 Windows OS 來說其實是「按住不放的狀態」,因此在 SendKeys(Keys.Control + “C") 後面還需要多加一個 Keys.Control 來放開按鍵。

// 按下 a 鍵
session.Keyboard.SendKeys("a");

// 按下 Esc 鍵
session.Keyboard.SendKeys(Keys.Escape);

// 按下 Control + C
session.Keyboard.SendKeys(Keys.Control + "C" + Keys.Control);
4. 滑動頁面

若要在 Windows Application 上做 Scroll 的動作,需要把 session 定義成一個 RemoteTouchScreen,再對該 RemoteTouchScreen 做位置的控制,範例如下:

RemoteTouchScreen TouchScreen;
TouchScreen = new RemoteTouchScreen(session);

// 在 TouchScreen (0,0) 位置下壓
// 滑動到 (1920, 1080) 的位置放開
TouchScreen.Down(0, 0);
TouchScreen.Up(1920, 1080);

5. 取複數元件

使用 FindElement 預設會取到第一個 matched 到的 WindowsElement
使用 FindElements 會取到一組 WindowsElement 的 Collection

// 點擊頁面上第 1 個按鈕
session.FindElementByAccessibilityId("Button").Click();

// 點擊頁面上第 2 個按鈕
session.FindElementsByAccessibilityId("Button")[1].Click();
6. 調整 App 視窗大小
// 設定為 1000 * 10000
session.Manage().Window.Size = new Size(1000, 1000);

// 設定為最大化
session.Manage().Window.Maximize();
7. 對特定元件做動作
// 點擊
session.FindElementByAccessibilityId("num9Button").Click();

// 輸入
session.FindElementByAccessibilityId("textField").SendKeys("Hello");

熟悉以上各個動作後,接著我們就試著寫寫測試案例吧!

撰寫測試

微軟提供了不少 測試範例 給大家參考,在這邊我們也來示範搭配上述提到的 Methods 實作一條簡單的測試案例吧!

  • 測試案例:點擊首頁的第一顆播放按鈕,會開始播放歌曲
  • 前置步驟:登入 App
  • 測試步驟:點擊 播放 按鈕
  • 預期行為:確認歌曲播放 Playing Bar 有出現、確認 Playing Bar 上的按鈕為「暫停按鈕」
  • Teardown:登出 App
namespace KKBOX.UWP.UITests.Test
{
    [TestClass]
    public class SampleTest
    {
        // Test Suite 的前置步驟
        [ClassInitialize]
        public static void Setup(TestContext context)
        {
            // 設定 Session
            DesiredCapabilities appCapabilities = new DesiredCapabilities();
            appCapabilities.SetCapability("app", "ThisIsTheIdOfMyApp");
            session = new WindowsDriver(new Uri("http://127.0.0.1:4723"), appCapabilities);
            Assert.IsNotNull(session);

            // 設定隱含等待為 3 秒
            session.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(3));

            // 設定為最大化
            session.Manage().Window.Maximize();
        }

        // 測試的前置步驟
        [TestInitialize]
        public void Setup()
        {
            // 對帳號密碼欄位分別輸入值
            session.FindElementByAccessibilityId("accountField").SendKeys("account@account.com");
            session.FindElementByAccessibilityId("paswordField").SendKeys("password");

            // 按下 Enter 鍵
            session.SendKeys(Keys.Enter);
        }

        // 測試案例
        [TestMethod]
        public void TestPlay()
        {
            session.FindElementByAccessibilityId("playButton").Click();
            // or session.FindElementsByAccessibilityId("playButton")[0].Click();

            // 確認 Playing Bar 有出現
            Assert.IsTrue(session.FindElementByAccessibilityId("playingBar").Displayed);

            // 確認 Playing Bar 上有「暫停」按鈕
            Assert.IsTrue(session.FindElementByAccessibilityId("playingBar").FindElementByAccessibilityId("playButton").Displayed);
        }

        // 測試的 TearDown
        [TestCleanup]
        public void TearDown()
        {
            session.FindElementByAccessibilityId("logoutButton").Click();
        }

        // Test Suite 的 TearDown
        [ClassCleanup]
        public static void Quit()
        {
            // 關閉 App、關閉 Session 連線
            session.Quit();
            session = null;
        }
    }
}

總結

整趟流程看下來感覺有點複雜,但實際上我們將整趟流程細分為 WinAppDriver、Inspect.exe 及 Visual Studio 三大區塊來分別完成的話,你會發現要建立起 Windows Automation 流程並沒有想像中複雜,尤其 WinAppDriver 及 Inspext.exe 僅需在開發初期設置,後續測試案例的開發也僅是利用 Methods 實作測試案例,若再搭配上實用的 Design Patterns ,絕對能讓開發更加順手及便利。如果你的產品也有支援 Windows 平台,那推薦你也開始使用 WinAppDriver 來打造你的 Windows 自動化測試吧!

參考資料

發表留言