【NoCode】5年目iOSエンジニアが、アプリのE2Eテスト自動化サービスを作った話

iOSエンジニア歴も早5年目となりました。

その経験を活かして、iOSアプリのテスト自動化サービス「<a href="https://smart-qa.io">SmartQA</a>」を作りました！

今回の開発では、Firebaseが大活躍してくれました。

SmartQAをリリースする上で、AppiumやFirebaseの技術的な知見をここに残せたらと思います。

## 概要

![cover-icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264666/42f2da56-f1ca-8524-449c-fd91896fa7d4.png)

<a href="https://smart-qa.io">SmartQA - E2Eテスト自動化をノーコードで</a>

ビルドをアップロードするだけで、**ブラウザ上**で簡単にテストの**自動化**ができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264666/fa3e0446-80d1-7cf1-70fe-91df94f6ee72.png)


UIが変わってしまった場合でも、要素探索を**自動で修復**してくれます。

複数端末対応しているので、「**iPhone SE**や**iPad**だと**クラッシュ**してしまった」なんてことも検知できます。

現在はiOSアプリのみ対応しております。

## 私について

10歳の頃にアセンブリに出会ったのがきっかけで、プログラミングの世界にのめり込みました。

どんなプログラムも足し算・引き算・比較・ジャンプで動いてることに感動したことを今でも覚えています。

京都大学工学部情報学科を卒業して、<a href="https://levetty.co.jp">Levetty株式会社</a>を立ち上げました。

在学中にiOSエンジニアとして働き始めました。

当時京都では学生がエンジニアとして働ける会社がほとんどなく、大学生協のアルバイト募集に**稀に出現するエンジニア募集**を見つけるしかありませんでした。

![IMG_6460.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264666/eb1249e3-b725-b448-8202-ab1e240f1a7c.jpeg)

大学生協を毎日クロールするスクリプトを組んで、LINEに通知して最速で応募したりしてました。

## 開発体制

開発期間としては、9月に実現できるのか技術検証を行い、10月から開発を始めました。

基本的に僕1人で開発を進めていたのですが、仲の良いエンジニア3人に手伝ってもらえたのがすごく大きかったです。

設計を綺麗にしてくれたり、インフラ整えてくれたり、なかなか手が回らない部分の実装をしてくれたり、サービスのアイデアをいただけたり。

とても感謝しています。

## 使用した技術

### Appium
iOSやAndroidでシミュレータを操作するのに必要なライブラリです。

iOSで使う場合は、内部で**XCUITest**を使用しています。

### Firebase
- Authentication
- Cloud Firestore
- Cloud Functions
- Cloud PubSub
- Cloud Storage
- Hosting

### React.js / Redux / TypeScript

フロントは、SPA(シングルページアプリケーション)で作成しました。

Material-UIが大活躍してくれました。

### Express / Node.js / TypeScript

テスト実行サーバー、macOS上で動作する簡易的なサーバーは全てExpressで実装しました。

### AWS CDK / Ansible

サーバー側の実装は、**Cloud Functions**に寄せていました。

しかし、540秒の実行時間の制限がきつく、テスト実行処理はAWSの**EC2**で運用することになりました。

手伝ってくれている凄腕エンジニアが**AWS CDK**と**Ansible**を使って構築してくれていました。

macOSを何台も用意する必要があるので、Ansibleを触ったのですが、革命を感じました。

CDKについては、僕は触ってないので分かりません、、。が、めちゃくちゃ良いみたいです。

## 苦労したこと

### 大量のMacのセットアップ

iOSのシミュレータを使用するには、macOS環境が必要です。

SmartQAでは、macOSを貸し出してくれる**MacStadium**というサービスを利用することにしました。

借りたMacを1つ1つ設定していくのですが、それはもう大変でした。

途中からAnsibleで自動化され楽にはなったものの、それでもXcodeをインストールするのは手動じゃないとダメだったりします。(2ファクタ認証の関係で)

### 平成時代から続くiOSシミュレータ自体のバグ

5年前に、AppleのDeveloperフォーラムに投稿されている内容です。
<a href="https://developer.apple.com/forums/thread/24308">Hardware keyboard not working in simulator (toggle in on)</a>

Macの**キーボード**をシミュレータに**接続しない設定**で起動しても、**接続されている**バグがあるんですよね。

キーボードが接続されていると、**UITextField**にフォーカスするために、**2回タップ**が必要だったり。

実際とは違う挙動をしてしまうんですよね。

**もちろんApple信者としては、仕様として受け入れることにしました**

調べた限り対策は1つしかなく、ここを2回クリックしてオンオフするしかないみたいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264666/6c4a5bb0-137d-0be3-3298-012b2478d9aa.png)

AppleScriptというものをこの時初めて知り、起動直後に**Connect Hardware Keyboard**を2回クリックするスクリプトを用意しました。

``` AppleScript
tell application "System Events"
  set hwKB to value of attribute "AXMenuItemMarkChar" of menu item "Connect Hardware Keyboard" of menu 1 of menu item "Keyboard" of menu 1 of menu bar item "I/O" of menu bar 1 of application process "Simulator"
  click menu item "Connect Hardware Keyboard" of menu 1 of menu item "Keyboard" of menu 1 of menu bar item "I/O" of menu bar 1 of application process "Simulator"
  click menu item "Connect Hardware Keyboard" of menu 1 of menu item "Keyboard" of menu 1 of menu bar item "I/O" of menu bar 1 of application process "Simulator"
  if ((hwKB as string) is equal to "missing value") then
    do shell script "echo 'hardware keyboard is off'"
  else
    click menu item "Connect Hardware Keyboard" of menu 1 of menu item "Keyboard" of menu 1 of menu bar item "I/O" of menu bar 1 of application process "Simulator"
  end if
end tell
```

### iOSアプリのUI要素のXMLの情報量が極端に少ない！

Appiumでは、**XCUITest**の**debugDescription**を利用してWebでいうところのDOMを取得しています。

そのDOMを**XML形式**で取得できるのですが、**class**や**id**などの情報があるWebに比べて情報量が圧倒的に少ないです。

```XML
<XCUIElementTypeStaticText type="XCUIElementTypeStaticText" value="パスワード" name="パスワード" label="パスワード" enabled="true" x="24" y="87" width="75" height="20"/>
<XCUIElementTypeSecureTextField type="XCUIElementTypeSecureTextField" value="Abc1234" name="" label="" enabled="true" x="190" y="87" width="161" height="21"/>
<XCUIElementTypeTextField type="XCUIElementTypeTextField" value="ピッカー入力" name="" label="" enabled="true" x="24" y="131" width="327" height="22"/>
<XCUIElementTypeButton type="XCUIElementTypeButton" name="Login" label="Login" enabled="true" x="165" y="185" width="45" height="34">
  <XCUIElementTypeStaticText type="XCUIElementTypeStaticText" value="Login" name="Login" label="Login" enabled="true" x="165" y="191" width="45" height="22"/>
</XCUIElementTypeButton>
```

こちらがXMLの一部です。

**UIButton**や**UITextField**、**UILabel**について得られる情報は、要素の**種類**・**座標**・**サイズ**・**値**があります。

```XML
<XCUIElementTypeImage type="XCUIElementTypeImage" enabled="true" x="171" y="598" width="45" height="44"/>
<XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" x="235" y="612" width="17" height="16"/>
```

しかし、**UIImageView**や**UIView**の場合は、**座標**と**サイズ**だけが頼りになります。

そこで、何かしらの方法を用いて**要素の推定**を行う必要があります。

今回は、**ルールベース**と**AI**を組み合わせて推定することにしました。

ちなみに、ソースコードを変更できるのであれば、**Accessibility Identifier**を割り当てると、**name**が固有の値になるので、追従がしやすくなります。

### Appiumの謎のエラーでシミュレータが起動しなくなる

クラウドのmacOSで、いつも通り何度もシミュレータを立ち上げていたところ、Appiumでエラーが出力され、シミュレータが強制終了するようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264666/eca596bb-320f-4761-d45b-2b3fcc2ca8ce.png)

Appiumの再インストール、Xcodeの最新版をインストールなど様々な方法を試しても治りませんでした。

ふと、macOS自体を**再起動**したところ正常に動き始めました。

**？？？？**

憶測ですが、シミュレータを立ち上げごとにゾンビプロセスが生成されてしまってるのかもしれません。

**この日を境にmacOSは、定期的に再起動される運用になりました**

## よかったこと

### Appiumサーバーを複数立てることで並列化が簡単

テストの実行を早く終わらせるために、並列化をしようと試みました。

Appiumサーバーを複数起動することで実現することができました！

(**WebDriverAgent**と**Appium**のポートがかぶらないように気をつけてください)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">M1 Mac上で同一シミュレータを複数立ち上げる、人類の夢を達成しました。<br><br>E2Eテストが一瞬で終わる世界線作る！ <a href="https://t.co/P3JdxByXhR">pic.twitter.com/P3JdxByXhR</a></p>&mdash; こーや (@koyataroo) <a href="https://twitter.com/koyataroo/status/1360302512905940994?ref_src=twsrc%5Etfw">February 12, 2021</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### FirestoreのCollection Groupが強力

SmartQAでは、セキュリティを最大限考慮した設計になっています。

`project`配下に情報を置くことで、セキュリティルールで縛りやすくしています。

その代わり、横断検索が大変になってしまいます。

2019年にリリースされた**Collection Group**で、解決することができました。

```
/project/{projectID}/collectionA/{documentID}
```

通常、このような階層で`collectionA`に対して検索する場合、それぞれのプロジェクトに対して実行しなければいけません。

しかし、**CollectionGroup**では、**同じコレクション名**であれば横断的に検索をすることができます。

どんな階層に存在していても可能です。

インデックスを設定するだけで使用できて、非常に簡単でした。

## さいごに

**インフラ**から**バックエンド**・**フロント**、更にはmacOSの**シミュレータの資源割付**まで、幅広い実装でエンジニア人生の集大成ともいえるプロダクトでした。

テスト実行の安定化や、機能追加に励んでいきたいと思います！

長期的には、Android対応などやることがたくさんあります。

全然人手が足らなくて、エンジニアさんやQAさんを絶賛募集しております。
少しでも興味があれば、contact@levetty.co.jpか<a href="https://twitter.com/koyataroo">@koyataroo</a>に連絡お待ちしております！