...
iOSアプリのテストを高速化するロケーターの選び方
E2Eテスト自動化プラットフォームのMagicPodの開発や運用をしていると、日常的にWebブラウザ及びモバイルアプリのE2Eテストを眺める機会に恵まれます。E2Eテストが好きな人には最高な環境の1つだと思います。さて、AppiumやSelenium等のOSS、またはE2Eテスト自動化プラットフォームを使ってE2Eテストを運用していく上でのよくあるお困りごとの1つに「テストが遅い」というのがあります。MagicPodユーザ様からも割とよく聞かれます。今回は最近よく聞かれるiOSアプリテストがAndroidのテストと比べて遅いというところを改善する手段の1つとしてロケーターの話をします。着地点としては「XPath」は避け、なるべく「Accessibility ID」や「iOS Class Chain」を使おうというお話です。
目次
- ロケーターとは
- iOSアプリテストで使えるロケーター
- XPath
- Accessibility ID
- iOS Class Chain
- 要素検索のパフォーマンス比較
- まとめ
1. ロケーターとは
WebアプリやモバイルアプリでE2Eテストを行う時、画面上から操作対象の要素(e.g. ボタン、リンク、入力欄)を指定する必要があります。これを指定する文字列のことをロケーターと言います。例えば、以下のようなiOSアプリの画面があったとします
この画面上で、「Activity Indicators」要素をタップしたいとします。iOSアプリやAndroidアプリの画面にもWebアプリにおけるDOMツリーに相当する概念 (ページソースやUIツリーと呼んだりします) があり、この例のiOSアプリの画面であれば、以下のようなページソースが取得できます (説明の簡単化のため、「Activity Indicators」付近のページソースを抜粋しています)。
<XCUIElementTypeCell type="XCUIElementTypeCell" enabled="true" visible="true" x="0" y="64" width="375" height="44">
<XCUIElementTypeStaticText type="XCUIElementTypeStaticText" enabled="true" visible="true" x="16" y="69" width="136" height="20" name="Activity Indicators" label="Activity Indicators" value="Activity Indicators">
</XCUIElementTypeStaticText>
<XCUIElementTypeStaticText type="XCUIElementTypeStaticText" enabled="true" visible="true" x="16" y="90" width="176" height="14" name="ActivityIndicatorViewController" label="ActivityIndicatorViewController" value="ActivityIndicatorViewController">
</XCUIElementTypeStaticText>
<XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="15" y="107" width="360" height="1">
</XCUIElementTypeOther>
<XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="16" y="108" width="359" height="0">
</XCUIElementTypeOther>
<XCUIElementTypeButton type="XCUIElementTypeButton" enabled="false" visible="true" x="348" y="79" width="11" height="14" name="chevron" label="chevron">
</XCUIElementTypeButton>
</XCUIElementTypeCell>
「Activity Indicators」要素をタップするにあたり、まずは目的の要素を探す必要がありますが、ここがロケーターの出番です。例えば、目的の要素は「XCUIElementTypeStaticText」タグであり、そのname属性に「Activity Indicators」という値を持つので、
//XCUIElementTypeStaticText[@name='Activity Indicators'] というXPathと呼ばれる種類のロケーターを使うと「Activity Indicators」要素を見つけることができます。あとは、目的の要素に対してタップ操作のAPIを呼ぶだけです。今回はタップ操作を例にしましたが、テキストの入力やピッカー操作等も同様に要素の探索が必要であり、同様にロケーターを使います。多くの場合、要素を探してこなければ何の操作もできないので、ロケーターというのはE2Eテストには必要不可欠です。ちなみに、MagicPodの場合はブラウザ画面やモバイルアプリの画面をスキャンすると、要素ごとに候補となるロケーターを何個か作り、良さげなものを見繕ってくれます。しかもそれらは基本的にユニーク(複数の要素が同時に見つかってしまうことがない)です。MagicPodの何気にすごいところの1つです。
2. iOSアプリテストで使えるロケーター
iOSのテストで使用可能なロケーターの種類 (英語だとLocator Strategyと呼んだりします) としては次のものがあります。
Locator Strategy | From |
class name | Selenium |
id | Selenium |
name | Selenium |
xpath | Selenium |
css selector | Selenium |
accessibility id | Appium |
-ios predicate string | Appium |
-ios class chain | Appium |
モバイルアプリを自動操作するためのフレームワークとして出発したAppiumは後発なので、Webブラウザを自動操作するためのフレームワークであるSeleniumのロケーターを追随してサポートしていたりします。class nameからcss selectorまでがそれにあたります。つまり、それらはWeb由来のロケーターということになります。一方、残りの3種類は、iOSネイティブ (より正確にはXCUITesetネイティブ) なロケーターといえます。さて、ここからはMagicPodが対応しているXPath、Accessibility IDおよびiOS Class Chainについて詳しく見ていきます。
2-1. XPath
こちらはSelenium経験者にはお馴染みのロケーターといえます。テスト対象の画面の階層構造に沿って要素を探してくる仕組みであるため、基本的に画面上のどの要素であれ見つけてくることができます。冒頭でロケーターの説明に使った //XCUIElementTypeStaticText[@name='Activity Indicators'] もXPathのロケーターでしたが、このロケーターが何を条件に探しているかというと
- 画面上の任意の場所にあること (「//」の部分)
- XCUIElementTypeStaticTextタグをもつこと
- name属性値に'Activity Indicators'という値を持つこと
という3つの条件で探しています。このようにXPathの使い勝手は非常に魅力的な一方で、このXPathロケーターはiOS的にはパフォーマンスが悪いです。なぜかというと、iOSネイティブにサポートされた手段ではなく、Appium側で追加の処理 (iOSネイティブなソースツリーを一旦XMLの形に変換し、XMLな要素を見つけてから、iOSネイティブな要素に再び変換する。ちなみに、ネイティブじゃないというのは、例えば英語しか喋れない人に日本語を喋らすような感じなので、遅くなります。)する必要があるためです。使い勝手はいいけど、パフォーマンスがよくないというのが、iOSアプリのテストにおけるXPathロケーターの立ち位置です。
2-2. Accessibility ID
XPathは遅い、ということは何を使えばいいのでしょうか。MagicPodでもよくお勧めしているロケーターの1つにAccessibility IDというのがあります。Accessibility IDというのはiOS側で用意されている、画面要素を識別するためのidで、自由に開発者が割り振ることができます。これを使うとXPathと比較して圧倒的に速く画面要素を見つけてくることができます。先ほどの要素の例だと、単に Activity Indicators という値をAccessibility IDとして使うことができるので、それを使うと良いでしょう。このAccessibility IDの弱点としては、開発者に割り振ってもらう必要があることです。要素に一意に割り振られたIDを使う方式なので、XPathほどの使い勝手はありません。
2-3. iOS Class Chain
iOS Class ChainはAccessibility IDより使い勝手が良く、要素の親子関係や兄弟関係、属性値による要素探索が可能です。XPathほど柔軟な指定はできないのですが、XPathよりも高速に要素を探索できます。そのため、MagicPodでもよくXPathの代替としてお勧めしています。例えば、先ほどの要素の例だと、 **/XCUIElementTypeStaticText[`name == "Activity Indicators"`] というiOS Class Chainのロケーターが使います。この意味としては、画面上のすべての「XCUIElementTypeStaticText」要素からname属性に「Activity Indicators」を持つものを見つける、という意味になります。iOS Class Chainのロケーターの作り方をより知りたい方、英語が気にならない方は、こちらもご覧ください。
3. 要素検索のパフォーマンス比較
さて、XPathは遅いと言ったからには比較が必要だと思います。そこで、やってみました。内容としては先ほどの画面に対し、XPath、Accessibility IDおよびiOS Class Chainでそれぞれ要素を1000回探索するというものです。各ロケーターのパフォーマンスを調べた結果は以下の通りです。
locator strategy | Accessibility ID | iOS Class Chain | XPath |
average (ms) | 132.383 | 131.741 | 563.456 |
median (ms) | 129 | 128 | 557 |
min (ms) | 126 | 125 | 440 |
max (ms) | 174 | 178 | 1117 |
こうやって並べてみると、やはりXPathの遅さが目立ちます。他の2つと比較して、約4倍遅くなりました。一方、iOS Class Chainは使い勝手が良い割に、Accessibility IDとほとんど差がありませんでした。おそらく他の2つは内部的には同じ実装であるからだと思われます。ちなみに今回の比較は以下の環境で実施しています。
- macOS: Ventura 13.0
- architecture: arm64
- Xcode: 14.1 (Build version 14B47b)
- Platfrom: iOS16.1 simulator
- Appium server: 2.0.0-beta.46
- XCUITest driver: 4.12.3 (WebDriverAgent: 4.10.10)
- Appium client: WebdriverIO 7.19.5
- node: v16.13.2 (x86)
- npm: 8.1.2 (x86)
4. まとめ
XPathロケーターはその使い勝手の良さ故に多用しがちですが、iOSアプリのテストの文脈においてはパフォーマンスが良いとは言えません。Accessibility IDやiOS Class Chain、それから本日は端折りましたがiOS Predicate Stringロケーターもパフォーマンスに優れているため、iOSアプリのテストが遅くてお困りの際は、これらのiOSネイティブなロケーターの使用を考えてみては如何でしょうか。