UIのテストの自動化試してみた

## はじめに

アプリケーションのインストーラーのテストを自動化したいと思って、UIのテストの自動化を少し試してみました。
今回の記事は、環境などについての備忘録的なもので次回、がっつり使った記事を書こうと思います。

## 準備

今回利用するのは、以下です。

Appium & Windows Application Driver

>Appiumは、iOS/Android/Windowsデスクトッププラットフォームでアプリケーション操作を自動化するためのオープンソースツールです。 ネイティブアプリ、モバイルウェブアプリ、ハイブリッドアプリなど、様々なモバイルアプリをターゲットとした自動実行が可能

>Windows Application Driver（WinAppDriver）は、Microsoftが開発したAppium互換のWindowsアプリケーションテストドライバーです。これは、Windows 10のネイティブアプリケーション（UWP）やクラシックWindowsアプリケーション（WPF、Windows Formsなど）のUIテストを自動化するために使用されます。

AppiumとWinAppDriverの組み合わせで自動テストを行います。

### インストールする

[Appium](http://appium.io/)をいずれかの方法でインストールします。

・デスクトップGUI版をインストールする

https://github.com/appium/appium-desktop/releases

・CLI版をインストールする
```
npm install -g appium
```

次にWindows Application Driverをインストールします。

https://github.com/microsoft/WinAppDriver/releases

正しくインストールされているかどうかを確認したい場合は以下を確認します。

- インストールディレクトリ（デフォルトでは C:\Program Files\Windows Application Driver）に移動し、WinAppDriver.exeが存在することを確認します。
- コマンドプロンプトまたはPowerShellを管理者モードで開きます。
- 以下コマンドを実行します。
`"C:\Program Files\Windows Application Driver\WinAppDriver.exe"`
- インストールされていれば、以下のようなメッセージが表示されます。
`Windows Application Driver listening for requests at: http://127.0.0.1:4723/`

#### 注意

>Developer mode is not enabled. Enable it through Settings and restart Windows Application Driver
Failed to initialize: 0x80004005

上記のようなエラーが出る場合は、Windowsのデベロッパーモードを有効にする必要があります。
この設定があるので、会社マシンにて自動テスト環境を作るのは難しく、最終的には仮想マシン上などにテスト環境を構築する必要性がありそうです。

デベロッパーモードの設定の仕方は以下です。

どうもWindowsのバージョンによって設定する場所が違うみたいで自分が調べた限りの設定箇所を残しておきます。
- Windows 10 では以下のいずれか
  - 設定　→　プライバシーとセキュリティ　→　上から3つ目くらいにある「開発者向け」から
  - 設定　→　更新とセキュリティ　→　下から2つ目くらいにある「開発者向け」から
- Windows 11 では以下のいずれか
  - 設定　→　プライバシーとセキュリティ　→　上から3つ目くらいにある「開発者向け」から
  - 設定　→　システム　→　真ん中くらいにある「開発者向け」から
（22H2バージョンの自分はここでした）

## テストプロジェクトを用意して動作確認する

私は.NETを利用したアプリケーションの開発者で最終的に作成したいのも.NETなので、.NETで説明します。
まずは、テスト対象となるサンプルのデスクトップアプリケーションを用意します。

1. Visual Studioで「新しいプロジェクトの作成」を行います。
2. 「WPFアプリケーション」を作成し、テストなので適当にxamlにテキストボックスとボタンを配置します。
3. ここで大事なのが配置したコントロールに名前を付けることです。
    後々AppiumでUIテストを行う際に名前指定ができるとテスト作成しやすくなります。

以下コード例です。

```xml
<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
            <!--テキストボックス-->
            <TextBox x:Name="InputTextBox" Height="23" Width="200"/>
            <!--テキストボックスの内容クリアボタン-->
            <Button x:Name="ClearButton" Content="クリア" Height="23" Width="75" Click="ClearButton_Click"/>
        </StackPanel>
    </Grid>
</Window>
```

```cs
using System.Windows;
namespace WpfApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        /// <summary>
        /// ボタンクリック時にテキストボックスの内容をクリアする
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClearButton_Click(object sender, RoutedEventArgs e)
        {
            InputTextBox.Text = string.Empty;
        }
    }
}
```

次にテストを実施するプロジェクトを作成します。

1. ソリューションから新しいプロジェクトを追加します。
2. 「MSTest テスト プロジェクト」を作成します。
3. テストメソッドにAppiumを使ってUIテストを記述します。

以下コード例です。

WpfAppIdには先程作成したWPFアプリケーションのexeのパスを指定します。

```cs
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
namespace TestMSTestProject
{
    [TestClass]
    public class UnitTest1
    {
        private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723"; // Windows Application DriverのURL
        private const string WpfAppId = @"D:\work\Test\WpfApp\bin\Debug\net8.0-windows\WpfApp.exe"; // WPFアプリケーションの実行可能ファイルのフルパス
        private static WindowsDriver<WindowsElement> _app; // AppiumのWindowsドライバインスタンス
        [ClassInitialize] // このメソッドがテストクラスの初期化メソッドであることを示す属性
        public static void Setup(TestContext context)
        {
            var appOptions = new AppiumOptions(); // AppiumOptionsオブジェクトを作成
            appOptions.AddAdditionalCapability("app", WpfAppId); // WPFアプリケーションの実行可能ファイルのフルパスを設定
            appOptions.AddAdditionalCapability("platformName", "Windows"); // プラットフォーム名を設定（Windows）
            appOptions.AddAdditionalCapability("deviceName", "WindowsPC"); // デバイス名を設定（WindowsPC）
            _app = new WindowsDriver<WindowsElement>(new Uri(WindowsApplicationDriverUrl), appOptions); // Windowsドライバインスタンスを作成し、_appに代入
            _app.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
        }
        [ClassCleanup] // このメソッドがテストクラスの終了メソッドであることを示す属性
        public static void TearDown()
        {
            _app?.Quit(); // ドライバインスタンスを終了（アプリケーションを閉じる）
        }
        [TestMethod] // このメソッドがテストメソッドであることを示す属性
        public void ClearButton_ClearsTextBoxContent()
        {
            Thread.Sleep(2000); //2秒待つ
            var inputTextBox = _app.FindElementByAccessibilityId("InputTextBox");// 名前で要素を検索し、テキストボックスの要素を取得
            inputTextBox.SendKeys("Sample Text"); // テキストボックスにテキストを入力
            Thread.Sleep(2000); //2秒待つ
            var clearButton = _app.FindElementByAccessibilityId("ClearButton"); // 名前で要素を検索し、クリアボタンの要素を取得
            clearButton.Click(); // クリアボタンをクリック
            Assert.AreEqual(string.Empty, inputTextBox.Text); // テキストボックスのテキストが空であることを検証（アサーション）
        }
    }
}
```

ここまで準備できれば、テスト実行すると自動的にexeが実行されてUIが操作され、そのときのUIの状態を評価してテストすることができています。

次回は、実際に私の作成しているアプリケーションのテスト仕様にもとづいてUIテストを作成し、そのときの知見などを記事にしたいと思います。