學習 UWP 的 Automation 做自動化測試

自己開發的 App 每次都要人工測試,速度慢又重複浪費時間,根據 Use Coded UI test to test your code 介紹學習自動化測試 App。

根據 Use Coded UI test to test your code 説明在 Visual Studio 2019 之後不再支援 Coded UI Test,需改用 Appium with WinAppDriver 測試 desktop 與 UWP apps,如果是 Xamarin 則利用 Xamarin.UITest 搭配 NUnit test framework 測試 iOS 與 Android apps。

Windows Application Driver 支援在 Windows 10 上對 UWP, WinForms, WPF 與傳統的 Win32 應用程式進行 UI 自動化測試。 WinAppDriver 搭配 Appium 建立一個 local server (預設 IP address: 127.0.0.1, port: 4723),藉由對 Appium 下指令 (HTTP Messages/APIs) 到 WebDriver 並轉換 Windows 搜尋 UI Controls 與對應的事件。概念如下: 可參考 Installing and Running Windows Application Driver 將 Windows Application Driver 安裝到 Windows 10 中,並特別注意安裝之後的目錄在:Run WinAppDriver.exe from the installation directory (E.g. C:\Program Files (x86)\Windows Application Driver)

您可以使用方式改變監聽的 port ,如下範例:

WinAppDriver.exe 4727
WinAppDriver.exe 10.0.0.10 4725
WinAppDriver.exe 10.0.0.10 4723/wd/hub

詳細可參考 Installing and Running Windows Application Driver

根據 Set a unique automation property for UWP controls for testing 介紹,要做到 UI auto testing 需要在 App 的 UI Controls 加入 AutomationProperties 的設定:

  • AutomationProperties.AutomationId
  • AutomationProperties.Name

可以選擇設定 AutomationId 或是 Name 來使用,如果您在 control 裏面沒有設定的話,例如:

<Button Name="ButtonX" />
<Button Content="ButtonZ" />

這兩者都是隱性的處理,AutomationId 會自動使用 Control 的 Name,而 AutomationProperties.Name 則使用 Content 的内容; 但是我個人建議給明確的指定,在寫測試時會比較方便,如下:

<Button AutomationProperties.AutomationId="ButtonY" />
<Button AutomationProperties.Name="ButtonZ" />

任何的 XAML Controls 都可以加入 AutomationProperties,例如 ListView 的 ListItem 可以在 binding 根據資料加上 AutomationId,例如:

<ListBox Name="listBox1" ItemsSource="{Binding Source={StaticResource employees}}">
   <ListBox.ItemTemplate>
      <DataTemplate>
         <Button Content="{Binding EmployeeName}" AutomationProperties.AutomationId="{Binding EmployeeID}"/>
      </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

如果您想找到是 ListView 中的任何一筆資料的話,可改用 class name 的方式,例如:FindElementsByClassName

安裝好 WinAppDriver 與設定 AutomationProperties 後,接著建立 Unit Test Project 並加入 Appium.WebDriver 的 Nuget,來與 Windows Application Driver 進行 UI auto testing。

  1. 建立一個 .NET Framework 的 Unit Test Project,並加入 Appium.WebDriver 的 Nuget;
  2. 建立管理 WindowsDriver<WindowsElement> 管理 Session,並設定測試的進入點:
    UWP
    // 指定 AUMID 與 Appium 建立的 local server
    DesiredCapabilities appCapabilities = new DesiredCapabilities();
    appCapabilities.SetCapability("app", "{AUMID}");
    AlarmClockSession = new WindowsDriver(new Uri("http://127.0.0.1:4723"), appCapabilities);
    
    有關於 AUMID 的取得可以從 Find the Application User Model ID of an installed app 找到方法。 Classic Windows Application
    // Launch Notepad
    DesiredCapabilities appCapabilities = new DesiredCapabilities();
    appCapabilities.SetCapability("app", @"C:\Windows\System32\notepad.exe");
    appCapabilities.SetCapability("appArguments", @"MyTestFile.txt");
    appCapabilities.SetCapability("appWorkingDir", @"C:\MyTestFolder\");
    NotepadSession = new WindowsDriver(new Uri("http://127.0.0.1:4723"), appCapabilities);
    
    使用 application 的完整執行路徑,可搭配 appArguments 傳入啓動參數與設定執行的目錄 appWorkingDir。 其他啓動時的參數,可參考:Supported Capabilities
  3. WindowsDriver<WindowsElement> 代表建立與 Appium 的連綫,需通過它向 Appium 下達指令,詳細範例:
    public class RadioSession
    {
        private const string WinAppDriverUrl = "http://127.0.0.1:4723";
    
        private const string AppId = "{AUMID}";
    
        public WindowsDriver Session { get; private set; }
    
        public RadioSession()
        {
            // 啓動 App 並建立 session
            DesiredCapabilities appCapabilities = new DesiredCapabilities();
            appCapabilities.SetCapability("app", AppId);
            Session = new WindowsDriver(new Uri(WinAppDriverUrl), appCapabilities);
    
            // 檢查是否有正確建立 session
            Assert.IsNotNull(Session);
            Assert.IsNotNull(Session.SessionId);
    
            // 設定搜尋 element 的 timeout 時間,避免沒有回應時整個卡住
            Session.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(3));
        }
    }
  4. 舉例找到下圖 ListView 中的任何一個 Item 並點擊它:
    可利用 Inspect.exe 路徑: C:\Program Files (x86)\Windows Kits\10\bin\{version}\{platform} 找到 ListView 的名字。再提醒,XAML 的 control 要記得設定 AutomationProperties 或是給與 x:Name。範例程式如下:
    [TestClass]
    public class UnitTest1
    {
        // 建立 Session 來保持與 Appium 互動
        private readonly RadioSession session = new RadioSession();
    
        [TestMethod]
        public void TestMethod1()
        {
            // 找到 ListView
             WindowsElement gridView = session.Session.FindElementByAccessibilityId("radioGridView");
    
           // 找到任何一個 Item 
           var items = gridView.FindElementsByClassName("GridViewItem");
    
           Assert.IsTrue(items.Count > 0);
    
           // 點擊任何一個 Item
           items[0].Click();
        }
    }

做完上面的步驟您已經可以對 App 進行 UI Test 了。

在搜尋 Elements 時可以參考 Supported Locators to Find UI Elements 的介紹有更多的方式。

補充

  • 如果您的專案組合了 CEFSharp 的話,可以參考 CefSettings and BrowserSettings 的做法,開啓 RemoteDebuggingPort 搭配 ChromeDriver - WebDriver for Chrome的方式來測試裏面的内容。
  • 如果拿到一個 App 找不到 AutomationId 時,可以搭配 Inspect.exe 來搜尋 App 中想要元件的 Id 或 Name。How can I try out WinAppDriver functionality?
  • Test Project 幾個需要注意的地方:
    • ClassInitialize 與 ClassCleanup
      每一個 TestClass 只能有一個 ClassInitialize ,它會在所有 TestMethods 被執行前優先處理;而 ClassCleanup 則是在所有 TestMethods 被完成後才會執行;常用來做一些測試前參數的準備與測試後面還原。<
    • TestInitialize 與 TestCleanup
      TestInitialize 會在每個 TestMethods 被執行前處理;TestCleanup 則是在每個 TestMethods 被完成後面執行。

======

如果您是個人開發者也許不太會用到該篇的内容,但我建議還是能參考著使用。

因爲 UI Auto testing 導入之後,讓手動測試變成自動化,可以測試更多比較偶發的行爲。

本篇内容希望對於要入門 Windows apps 做 UI Auto testing 的人有所幫助,謝謝。

References