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


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

その経験を活かして、iOSアプリのテスト自動化サービス「SmartQA」を作りました!

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

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

概要

SmartQA - E2Eテスト自動化をノーコードで

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

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

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

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

私について

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

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

京都大学工学部情報学科を卒業して、Levetty株式会社を立ち上げました。

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

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

大学生協を毎日クロールするスクリプトを組んで、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 CDKAnsibleを使って構築してくれていました。

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

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

苦労したこと

大量のMacのセットアップ

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

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

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

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

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

5年前に、AppleのDeveloperフォーラムに投稿されている内容です。
Hardware keyboard not working in simulator (toggle in on)

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

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

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

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

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

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

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では、XCUITestdebugDescriptionを利用してWebでいうところのDOMを取得しています。

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

<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の一部です。

UIButtonUITextFieldUILabelについて得られる情報は、要素の種類座標サイズがあります。

<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"/>

しかし、UIImageViewUIViewの場合は、座標サイズだけが頼りになります。

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

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

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

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

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

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

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

????

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

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

よかったこと

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

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

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

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

FirestoreのCollection Groupが強力

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

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

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

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

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

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

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

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

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

さいごに

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

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

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

全然人手が足らなくて、エンジニアさんやQAさんを絶賛募集しております。
少しでも興味があれば、[email protected]@koyatarooに連絡お待ちしております!