Intelligent Technology's Technical Blog

株式会社インテリジェントテクノロジーの技術情報ブログです。

Appium iOS編再び

間藤です。

前回に引き続きAppiumです。今年のはじめにAppiumをiOS編として取り上げた際は、Safariでの動作確認が今一つでした。新しいAppiumではどうかということで試した結果をレポートします。前回とは違った問題が発生しています。

実行環境等

以前にも書いたように、Safariを起動するため、SafariLauncherを利用することに変わりはありません。今回もappiumのgithubリポジトリを利用することを前提に話をすすめます。

ホストPC OS X Mavericks v10.9.4
Xcode 5.1.1
Appium 1.2.0
iOS実機 7.1.1
テスト対象アプリ SafariLauncher、Mobile Safari
テストスクリプト sample-code/examples/java/junit/src/test/java/com/saucelabs/appium/SafariTest.java

準備

SafariLauncherを実機にインストールするためにビルドが必要です。

> ./reset.sh --ios --real-safari

ビルドが正常終了すれば、build/SafariLauncher/SafariLauncher.zipが作成されているはずです。
前回試した時は、AppiumがSafariLauncherの実機インストールで失敗していましたが、今回はインストールできるようになっています。但し、zipファイルの配置先を調整する必要があります。

【/lib/devices/ios/safari.js】

this.args.app = path.resolve(__dirname,
    "../../../build/SafariLauncher/SafariLauncher.zip");
this.configureLocalApp(cb);

上記のコードがSafariLauncher.zipのパスを決めているところだと思うのですが、このスクリプトの配置されたパスからの相対パスでzipファイルを参照するようになっているため、上記の手順で作成したzipファイルを参照してくれません。ですから、以下のようにして作成したzipを所定の位置にコピーしておく必要があります。
※パスはお使いの環境に合わせて読み替えてください。

> cp -p build/SafariLauncher/SafariLauncher.zip /Users/matoh/.nvm/v0.10.29/lib/node_modules/appium/build/SafariLauncher/

あるいは、cloneしたリポジトリにあるappiumを起動するかです。こうすると、結果的に先ほど作成したzipが参照されるようになります。

> node bin/appium -U (UDID)

テストの実行

まずappiumのサーバを起動します。
(SafariLauncherをzipをコピーしないのであれば、上記の方法で起動)

> appium -U (UDID)

次に、ios_webkit_debug_proxyを起動します。(インストール方法はリンク先をご参照ください)

> ios_webkit_debug_proxy -c (UDID)

そして、テストを実行します。

> mvn test -Dtest=com.saucelabs.appium.SafariTest

テストに失敗するようであれば、それは以前にも取り上げた問題が起きた可能性があります。
以下のようなログが出ていれば、SafariLauncherがSafariを起動するタイミングを調整する必要があります。SafariLauncherの設定画面で調整が可能です。過去の記事でも説明しているので参考にしてください。

debug: [INST] 2014-07-09 04:13:54 +0000 Fail: Could not start script, target application is not frontmost.
・・・
debug: [INSTSERVER] Instruments exited with code 0

同期処理をスマートに

サンプル(SafariTest.java)では、ページ遷移してからページがロードされるタイミングを計るためにsleepを使っています。

driver.get("http://saucelabs.com/test/guinea-pig");
Thread.sleep(1000);
WebElement idElement = driver.findElement(By.id("i_am_an_id"));

これを改善(sleepに頼らない)するため、implicitlyWaitが使えます。これにより、要素の検索時にその要素が見つからなかった場合、設定された時間だけ要素の出現を待機するようになります。

driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);

このサンプルでは、サブミットによるページ遷移もあります。

WebElement submitElement = driver.findElement(By.id("submit"));
assertNotNull(submitElement);
submitElement.click();

この後に

WebElement yourCommentsElement = driver.findElement(By.id("your_comments"));

と続くのですが、この"your_comments"という要素は、遷移前のページにも存在するためにimplicitlyWaitが想定したように機能しません。そのため、sleepしないように修正すると、タイミングによっては以降のAssertでNGとなり、テストが失敗することがあります。
そこで、ExpectedConditionsを使います。

Wait<WebDriver> wait = new WebDriverWait(driver, 30);
boolean ret = wait.until(textToBePresentInElement(By.id("your_comments"), "This is an awesome comment"));

ページ遷移後、"your_comments"要素のテキストは、"This is an awesome comment"となるはずなので、それを待機するというものです。こういった同期処理は、自動テストで躓きやすいところなので、注意が必要です。

修正したコードをGistにあげておきます。

SafariTest.java

キャプチャができません

過去の経験があったので、ここまでは順調でした。
次に、画面キャプチャを試してみたのですが、これがうまくいきませんでした。(Gistにあげたサンプルもキャプチャをとるようにしてあります)
前回試した際は、SafariLauncherのタイミング調整でキャプチャできるようになったのですが、今回はどうもうまくいきません。どうやら別の問題が起きているようです。

debug: Sending command to instruments: au.capture('screenshot631b2254-e79d-4975-8d61-fe9d534f20c5')
debug: [INST] 2014-07-08 08:17:18 +0000 Error: Error during eval: eval code
       eval@[native code]
startProcessing@file:///Users/matoh/.nvm/v0.10.29/lib/node_modules/appium/611FCC8D-B82B-4B90-9AFB-5F3A3F8F18F2/commands.js:120:30
bootstrap@file:///Users/matoh/.nvm/v0.10.29/lib/node_modules/appium/8A89F12B-3FDD-4619-817D-049404C0E11B/bootstrap.js:18:29
global code@file:///Users/matoh/.nvm/v0.10.29/lib/node_modules/appium/2D715DE0-C2C8-45B3-BB8C-1E2F96C486C0/bootstrap-0ae9068adb69bb02.js:9:10

debug: Socket data received (49 bytes)
debug: Socket data being routed.
debug: Got result from instruments: {"status":17,"value":"Can't find variable: au"}

以下のような情報を見つけました。

https://groups.google.com/forum/#!searchin/appium-discuss/capture/appium-discuss/a-lI8NySY6Q/zqYw9ZYb6OcJ

これによれば、1人は「俺は動くけど」と書いていて、もう1人が「SafariLauncherを使った実機上でのテスト(つまりSafariを使ったテスト)ではスクリーンショットは取得できないよ」とあります。どちらかと言えば、後者のほうが説得力ありますが、今年のはじめに私が試したときにはキャプチャできてたので、もしかすると私の手順等に問題があるのかもしれません。

シミュレータならどうか

実機ではキャプチャできなかったので、シミュレータならどうかを試してみたところ、こちらはうまくいきました。ただ、シミュレータの場合、そもそもSafariLauncherを利用していません。以下のログを見るとAppium-MobileSafari.appを使っていることがわかります。

debug: Spawning instruments with command: /Applications/Xcode.app/Contents/Developer/usr/bin/instruments -t /Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.bundle/Contents/Resources/Automation.tracetemplate -w iPhone Retina (3.5-inch) - Simulator - iOS 7.1 /tmp/Appium-MobileSafari.app -e UIASCRIPT /Users/matoh/Library/Application Support/appium/bootstrap/bootstrap-0ae9068adb69bb02.js -e UIARESULTSPATH /tmp/appium-instruments

ですから、実機でキャプチャできない原因を探るには、あまり役に立ちませんでした。
なお、処理の過程でシミュレータのMobile Safariを一時的に削除するようですが、権限の問題でエラーになることがあります。その場合は、以下のコマンドを実行してください。

> sudo authorize_ios

シミュレータを利用する際、今回私は別のところではまったので、最後にこのことを連携しておきたいと思います。
シミュレータでキャプチャできるかを確認しようと考えて、appiumのサーバをUDIDの指定なしで起動し、同じテストを実行してみました。そうしたところ以下のようなエラーが出てしまって、どうしてもテストが失敗してしまいます。

2014-07-09 10:25:07.271 instruments[1458:507] The application that opened iOS Simulator failed to send all of the required information (sessionUUID, sdkRoot, deviceInfo).

情報取集したところ、以下のような問題があることがわかりました。

Simulator failed to send all of the required information · Issue #2129 · appium/appium · GitHub

今一つこれに記載されている内容を把握できてませんが、シミュレータデバイスの切り替え(例えば、iPadからiPhone)がうまいくいかないということなのかと思います。
そこで、iOSシミュレータが起動したときに選択されるデイバスと、テストで指定しているdeviceNameが一致するように調整したところ動作するようになりました。

f:id:IntelligentTechnology:20140709155513p:plain

上記の設定なら、

capabilities.setCapability("deviceName", "iPhone Retina (3.5-inch) Simulator");

のようにするとよいようです。
以前はこのようなことを気にしないでも使えていたように思うのですが、たまたまうまくいっていただけだったのかもしれません。