Windows Application Driverで(レガシー)Windowsアプリを動かす

[Windows Application Driver](https://github.com/microsoft/WinAppDriver)とを使って、レガシーなWindowsアプリ(Win32)の簡単な自動テストを書く機会があったので、環境の構築とさわりをまとめます。[^1]

## 環境

* Windows 10 Pro 1909
* [Python 3.8.1](https://www.python.org/downloads/release/python-381/)
* [Windows Application Driver v1.2-RC](https://github.com/microsoft/WinAppDriver/releases/tag/v1.2-RC) 

## 環境構築

1. Windowsを **開発者モード** に変更します。検索ボックスで **開発者向け設定** を検索して以下のように選択します。  
   ![devmode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30716/8cb19cea-f448-7a3f-46fc-693428ee524f.png)
2. [Python](https://www.python.org/)をインストールします。パスを通すのを忘れずに:slight_smile:
3. [Windows Application Driver](https://github.com/microsoft/WinAppDriver)をインストールします。MSIをダウンロードして実行すればOK。

## Windows Application Driverでなにか動かしてみる

まずスクリプトの実行前に、コマンドプロンプトから[Windows Application Driver](https://github.com/microsoft/WinAppDriver)を起動します。起動すると待機状態になります。

```
c:\Program Files (x86)\Windows Application Driver>WinAppDriver.exe
Windows Application Driver listening for requests at: http://127.0.0.1:4723/
Press ENTER to exit.
```

とりあえず動かして見ようと思いサンプルを探すと、英語UIでないと動かないものになっています:frowning2: このため、メモ帳を起動、閉じようとして、保存する、の流れを書きました。

```python:wda_test.py
import unittest
import os
import time
from appium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common import exceptions

save_file = "c:\\temp\\test.txt"

def remove_save_file():
    if os.path.exists(save_file):
        os.remove(save_file)

class WindowsApplicationDriverTests(unittest.TestCase):

    @classmethod
    def setUpClass(self):
        remove_save_file()
        desired_caps = {}
        desired_caps["app"] = r"C:\Windows\System32\notepad.exe"
        self.driver = webdriver.Remote(
            command_executor='http://127.0.0.1:4723',
            desired_capabilities= desired_caps)

    @classmethod
    def tearDownClass(self):
        remove_save_file()
        self.driver.quit()

    def test_edit_and_close_with_save(self):
        # 文字の入力
        self.driver.find_element_by_class_name("Notepad").send_keys("abcdefg")
        # メモ帳の終了指示
        self.driver.find_element_by_class_name("Notepad").send_keys(Keys.ALT, Keys.F4)
        # 保存確認メッセージの表示待ち、保存ボタンを押す
        time.sleep(1)
        self.driver.find_element_by_name("保存する(S)").send_keys(Keys.ENTER)
        # 名前を付けて保存ダイアログの表示待ち
        time.sleep(3)
        # ファイル名を入力
        self.driver.find_element_by_name("名前を付けて保存").send_keys(save_file)
        # 保存ボタンを押す
        self.driver.find_element_by_name("保存(S)").send_keys(Keys.ENTER)

if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(WindowsApplicationDriverTests)
    unittest.TextTestRunner(verbosity=2).run(suite)
```

メッセージやダイアログの表示待ちを入れないと、想定通りに操作が入らないことがあるので注意が必要です。
以下のようにスクリプトを実行するとメモ帳が自動実行されるのを確認できます。

```
python wad_test.py
```

## 感想

* 要素の指定にクラス名やAutomationIdの指定もありますが、そもそもテストしやすく作っていないレガシーなアプリだと、要素の名称で探すほうが簡単かなという感じがします。テストが壊れやすくなルカナという気もしますが、UIを大きく変えるような対象でもないなら、そうでもないと割り切れるかも。
* メッセージやダイアログの表示待ちをもうちょっとスマートにしたいと、こことかいろいろ試してみましたが、汎用的な決定打が見つけられずサンプルはsleepにしています。

## 参考

* [The Windows Driver](http://appium.io/docs/en/drivers/windows/)
* [Appium を使った Windows ネイティブアプリケーションの UI テスト自動化](https://qiita.com/yuuhu04/items/5a96608ad96eccee34a0)
* [Handling multiple windows in Python Selenium](http://allselenium.info/handling-multiple-windows-python-selenium/)

[^1]: 書いてみた結果、参考サイトで終わってたみたいな感じ:pensive: