Airtestでリスト系のUIを自動操作したときの困難と行った工夫

## はじめに 
本記事ではAirtestを利用して、スマートフォン用のゲームアプリにおけるリスト型のUI（以下リスト）を自動操作した際に直面した困難と、その解決のために行った工夫を紹介いたします。

Airtest自体の説明は、ほかの記事や公式ドキュメント等にお任せします。
簡単に言うと、スマートフォン用のアプリから切り取ったテンプレート画像で、画像認識AIを活用し、アプリを自動操作するというものです。
* 公式サイト…http://airtest.netease.com/
* Airtest公式ドキュメント…https://airtest.readthedocs.io/en/latest/index.html
* Airtest IDE公式ドキュメント…https://airtest.doc.io.netease.com/en/

実際のゲーム画面だと不都合がありそうなので、適当に作成したものではありますが、以下のようなゲームアプリのショップ画面を自動化する想定で説明させていただきます。
![スマホゲームの画面イメージ図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286534/61306b5f-c2f4-3e6b-9746-4b134ccd65d0.png)

## リストの操作を行う際の困難
### スワイプ操作が自動操作の失敗を誘発する
Airtestはユーザーの操作をエミュレートして自動操作を行う関係上、リストを動かすにはスワイプを行わなければいけません。

これがそもそも厄介で、そのまま```swipe((X1, Y1), (X2, Y2))```と実行すると、スワイプし終わった後に慣性が働いて、想定した以上にリストが動いてしまうことがあります（iOSのスクロール時の挙動を想像していただけると分かりやすいと思います）。

そのせいでスワイプ後に操作したいボタンが画面外に行ってしまい、自動操作に失敗してしまうということが起こります。

### 似た画像が並んでいることが多い
リストはアイテムのアイコンなど、似た画像が並んでいることが多いです。

そのためアイコンの画像を使って操作しようとした際に、Airtestの画像認識が想定とは別のアイコンを持ったリストの項目を、誤って操作してしまうことがあります。

上の画像の例でいうと、本当は2番目の項目を操作したいのに、1番目の項目のアイコンを誤認識してそちらを操作してしまうということです。

### ボタンが共通なので想定しているボタンが押せない
リスト上にあるボタンは、どの項目でも共通であることがほとんどです。

特定の項目にあるボタンをタップする必要がある場合、単純に```touch(リストのボタン画像)```としただけでは、想定通りのボタンをタップしてくれません。

上の例では「購入」ボタンがそれぞれの項目にありますが、3番目の「購入」ボタンが押したいとなったときに、ただ```touch(「購入」ボタンの画像)```とするだけでは駄目で、もうひと工夫が必要になります。

## 解決のために行った工夫
続いてこれらの困難を解決するために行った工夫を紹介します。

### スワイプは```swipe()```の引数を調整して使う
```swipe()```は引数を指定することで、スワイプの速さなどを調整することができます。
* duration…スワイプ開始から終了までの時間
* steps…スワイプを滑らかにする

durationでスワイプ時間を長くして、stepsでスワイプを滑らかにすることで慣性による滑りを減らすことができます。

自分の感覚値ではありますが、大体スワイプ時間を2秒前後、stepsを20前後にすると安定しやすいです。

また余談ですが```swipe()```の第2引数に、座標ではなくvectorを指定すると、画面サイズから自動的にスワイプ量を計算してくれるので、画面のサイズが異なる端末でも同じようにスワイプしてくれるようになります。
* vector…[X, Y]というリスト型の値でスワイプ方向を指定できる

詳しくは公式ドキュメントに記載されていますので、こちらをご覧ください。
https://airtest.readthedocs.io/en/latest/all_module/airtest.core.api.html#airtest.core.api.swipe

### 似た画像が多い画面では、テンプレート画像のthresholdの値を高くする
AirtestIDE上でテンプレート画像をダブルクリックすると、thresholdという値を変更できる項目があります。

これは、切り取ったテンプレート画像と、接続しているスマホ端末の画面上に表示されている画像とが、どれだけ似ていれば「同じ画像」であると判断するかを決める値で、デフォルトでは0.7になっています。

0.7で十分な場合も多いですが、似たような画像がいくつも表示されている画面では、想定と違う画像を誤認識する可能性があります。

ただ値が高すぎても、今度はちょっとした違いで別の画像と認識され、不便に感じることもあります。
0.8～0.9の間がいい塩梅だと思います。

ほかにも画像を切り取る際に、他の画像と一致している部分をなるべく避けるようにすると、認識の成功率が上がります。

### アイコンとボタンとの距離を計算して一番近いものをタップする
リストの各項目にあるボタンの中から特定の一つをタップしたいときには、アイコンなどの基準となる画像からボタンまでの距離を計算して、一番近いところにあるものをタップするという処理にしました。

例としてリストの3番目の項目の「購入」ボタンをタップしたいときには以下のようになります。

```python
# 操作したい項目のアイコンの座標を取得する
item_pos = exists(3番目のアイコンのテンプレート画像)
# 「購入」ボタンの座標をすべて取得する
buttons_list = find_all(「購入」ボタンのテンプレート画像)

# find_allで取得した情報から座標の情報をとってリスト化する
buttons_pos_list = [btn['result'] for btn in buttons_list]
# アイコンのY座標と「購入」ボタンのY座標の距離を計算してリスト化する
buttons_dist_list = [abs(btn[1] - item_pos[1]) for btn in buttons_pos_list]
# 距離が一番小さいもののインデックスを取得する
target_index = buttons_dist_list.index(min(buttons_dist_list))

# ボタンの座標リストから取得したインデックスで座標を選んでタッチする
touch(buttons_pos_list[target_index])
```
今回のように縦に並んでいるリストの場合は、X座標（横の位置）が等しく、Y座標（縦の位置）の違いだけを比較すればよいので、Y座標だけ計算しています。

## おわりに
Airtestは画像認識により自動操作を行っています。
ゲーム自体に設定が不要で外部から簡単に操作することができ便利なのですが、ゲームの中身に触れられないゆえに難しい操作もあります。

その一つがこのリストの操作のように、いくつもの似た要素の中から一つを選んで何かを行うというようなものです。

誤認識が発生しやすい画面の場合はエンジニアの側で工夫を凝らす必要が出てきます。