docker-android + AppImage版Appium DesktopでAndroidテスト自動化の環境をさっくり作る
うっすいネタ、いわゆる「使ってみました」ネタで恐縮です。あと、タグSeleniumってつけてますが兄弟?プロジェクトのAppiumネタです。
私の本業はいちおうソフトウェア自動テストアーキテクト*1 なわけです。主にSeleniumを使ったWebの自動テストが得意領域ってことになってるんですが、その周辺領域であるAppiumを用いたモバイルの自動テストはあんまり手を動かした経験がなく、教科書レベルのことは言えるけど……ってのがコンプレックスでした。
最近いろいろあって残業することなく早く帰るようにしてるので、自由になる時間をYouTubeの動画*2 観て溶かしてないで、ちったー手を動かしてみますかーってことで、マイパソコンでAppiumのテスト自動化開発環境を作ろうと思い立ちました。仕事ではJava + Selenide*3 なんですけど、まあ個人の遊びなので違う言語ってことでRubyで。
ご存知の通り?私は個人ではUbuntuしか使ってないので、Ubuntu上で環境を作りたい。なのですが、AppiumってNodeアプリなので、Nodeを入れなきゃなのですが、NodeもまたこれがLinuxディストリビューションのパッケージングポリシーと合わないことおびただしい*4 のでコンテナに閉じ込めたりしたい。さらにいうとAndroid SDKとかの管理もめんどくさい。なんかいいのないかなーとググっていたら。素晴らしいものがありましたよ。
AndroidのAVDとAppiumを封じ込めたコンテナ。docker run
一発で全部入り環境が立ち上がる。AVDの操作ははVNCとか入れずにブラウザーで確認可能。動画記録機能まである。これは最高なのでは……。
ということで使ってみました。
Ubuntuのバージョンは18.04 LTS(bionic beaver)です。
docker-android使ってみよう
むっちゃ簡単です。
docker run --privileged -d -p 6080:6080 -p 5554:5554 -p 5555:5555 -p 4723:4723 -e DEVICE="Samsung Galaxy S6" -e APPIUM=true --name android-container butomo1989/docker-android-x86-8.1
-e APPIUM=true
でAppiumサーバーも一緒にあげてます。
ちょっとコンテナサイズでかくて、初回実行時にはpullにしばらく待たされました*5 けど、あっさり動いた。ちょっと感動。
AppImage版Appium Desktopからつなぐ
Appiumサーバーちゃんと動いてること確認したいですね。
そのためには(もちろん、今後使うからでもありますけど)Appiumの開発ツールであるAppium Desktopも使いたい。しかしこいつも依存関係汚すのはやだなー。そう思いつつ公式の配布先↓ 見に行ったら。
あるじゃん。AppImage版。
AppImageについては深入りしませんが*6、よーはファイル取ってきて実行権限つけるだけで、インストール不要で使えるパッケージ。これなら環境汚さないしもう使わないと思ったらファイル消すだけでOKです。
早速取ってきて(依存関係全部抱いてる関係でちょっとでっかいです)、実行権限つけて起動。
今回はdocker-android内でAppiumサーバー動いてるので「Start Server」は押さずに、メニューから「File」>「New Session Window」を起動。セッション画面を起動します。
で、Desired Capabilitesを以下のようにして*7、単に設定画面を開いてみます。
{ "platformName": "android", "deviceName": "device", "appActivity": "Settings", "appPackage": "com.android.settings", "takesScreenshot": false }
Start Session! うりゃ!
ちゃんと動いてる! やったね。
スクリプトから起動してみる
ではRubyのスクリプトから起動してみましょうか。せっかくなので今度はアプリケーションをサイドロードして起動して、それを操作してみたいですね。
っと、その前に、apkを導入するときには、apkのあるフォルダを /root/tmp
にマウントしろとドキュメントに書いてあるので、動いてるコンテナを止めて再起動します。スクリプトを作るフォルダにapkは置くことにして、雑に $PWD
をマウントします。
docker stop android-container docker rm android-container docker run --privileged -d -p 6080:6080 -p 4723:4723 -p 5554:5554 -p 5555:5555 -e DEVICE="Samsung Galaxy S6" -e APPIUM=true -e CONNECT_TO_GRID=true -e APPIUM_HOST="127.0.0.1" -e APPIUM_PORT=4723 -e SELENIUM_HOST="172.17.0.1" -e SELENIUM_PORT=4444 -e MOBILE_WEB_TEST=true -v $PWD:/root/tmp --name android-container butomo1989/docker-android-x86-8.1
あれ、コマンド履歴見直すと CONNECT_TO_GRID
*8 はいらんのでは……。まあいいや。
アプリは、わたくし普段からAndroidアプリ開発してるわけではないので*9 手持ちのapkはなく、ので、Appiumのサンプル からApiDemos-debug.apkを取ってきて手元のディレクトリに置きました。
require 'test/unit' require 'appium_lib' class SimpleTest < Test::Unit::TestCase def setup desired_caps = { caps: { platformName: 'android', deviceName: 'device', app: '/root/tmp/ApiDemos-debug.apk', appActivity: '.ApiDemos', appPackage: 'io.appium.android.apis', takesScreenshot: false, }, appium_lib: { server_url: 'http://localhost:4723/wd/hub' } } driver = Appium::Driver.new(desired_caps, true) Appium.promote_appium_methods self.class driver.start_driver.manage.timeouts.implicit_wait = 20 end def teardown driver_quit end def test_sample puts "hello" end end
これでさくっと動いてしまいました。いやーすばらしい。
今のところテスト本体は空っぽなので、単にアプリ起動できたところまでしか確認できてませんけど。
これから、テストちょろちょろ書いていこうと思います。今回は 'test/unit' 使ってるけど、このテストフレームワークがいいよ! という推薦があれば教えていただきたいです。かなり昔に教えてもらったTurnipが気になってますが……。
まあともかく、docker-androidおすすめです。あとLinux使うならAppImage版Appium Desktopもチョー推薦です*10。
以下おまけあり。読みたい方は「続きを読む」をクリックしてください。
実はこんなにあっさりうまく行きませんでした。わたくしが間抜けなのがいけないのですけど。
失敗編
最初書いたスクリプトはこんな感じでした。違いは後ほど。
require 'test/unit' require 'appium_lib' class SimpleTest < Test::Unit::TestCase def setup desired_caps = { caps: { platformName: 'android', deviceName: 'device', app: '/root/tmp/ApiDemos-debug.apk', appActivity: '.ApiDemos', appPackage: 'io.appium.android.apis', takesScreenshot: false, }, } driver = Appium::Driver.new(desired_caps, true) Appium.promote_appium_methods self.class driver.start_driver.manage.timeouts.implicit_wait = 20 end def teardown driver_quit end def test_sample puts "hello" end end
これをえいっと実行すると……あれ? 怒られるよ?
Loaded suite /home/naruhiko/playground/ruby-appium/test Started E =============================================================================== Error: test_sample(SimpleTest): RuntimeError: App doesn't exist. /root/tmp/ApiDemos-debug.apk /home/naruhiko/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/appium_lib-9.14.3/lib/appium_lib/driver.rb:400:in `absolute_app_path' /home/naruhiko/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/appium_lib-9.14.3/lib/appium_lib/driver.rb:240:in `set_app_path' /home/naruhiko/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/appium_lib-9.14.3/lib/appium_lib/driver.rb:176:in `initialize' /home/naruhiko/playground/ruby-appium/test.rb:17:in `new' /home/naruhiko/playground/ruby-appium/test.rb:17:in `setup' =============================================================================== (略)
ん? App doesn't existとはなんぞ?
$ docker exec -it android-container ls /root/tmp ApiDemos-debug.apk Gemfile Gemfile.lock test.rb
あるじゃん……??
Appium Desktopで確認
じゃあAppium Desktopでもやってみましょう。Device Capabilitesはこんなふう。
{ "platformName": "android", "deviceName": "device", "takesScreenshot": false, "app": "/root/tmp/ApiDemos-debug.apk", "appActivity": ".ApiDemos", "appPackage": "io.appium.android.apis" }
で、セッションを起こすと……。
起動するじゃん!
スクリプトから起動してみる - 単なるオタンコでしたの巻
で、ここで冷静になってデバッグメッセージ見ると(再掲)、
Loaded suite /home/naruhiko/playground/ruby-appium/test Started E =============================================================================== Error: test_sample(SimpleTest): RuntimeError: App doesn't exist. /root/tmp/ApiDemos-debug.apk /home/naruhiko/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/appium_lib-9.14.3/lib/appium_lib/driver.rb:400:in `absolute_app_path' /home/naruhiko/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/appium_lib-9.14.3/lib/appium_lib/driver.rb:240:in `set_app_path' /home/naruhiko/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/appium_lib-9.14.3/lib/appium_lib/driver.rb:176:in `initialize' /home/naruhiko/playground/ruby-appium/test.rb:17:in `new' /home/naruhiko/playground/ruby-appium/test.rb:17:in `setup' =============================================================================== (略)
これRubyの世界でエラーになってるじゃないですか。
と、いうことは、Rubyスクリプトを動かしてる環境に /root/tmp/ApiDemos-debug.apk
がないよということですね。そりゃそうだ、そんなファイルないもの。
うーんDevice Capabilitesのapp:
はサーバ側に渡すものなので、スクリプト側で存在チェックされたらダメじゃん。バグか? いやいやそんなことはないと思うけど……と、デバッガ*11 で追っかけたら、appium_lib/driver.rb
のここだ。
def self.absolute_app_path(opts) raise 'opts must be a hash' unless opts.is_a? Hash caps = opts[:caps] || {} appium_lib_opts = opts[:appium_lib] || {} server_url = appium_lib_opts.fetch :server_url, false app_path = caps[:app] raise 'absolute_app_path invoked and app is not set!' if app_path.nil? || app_path.empty? # may be absolute path to file on remote server. # if the file is on the remote server then we can't check if it exists return app_path if server_url (略……以下いろんなシチュエーションのアプリ存在チェックが続く)
変数 server_url
がセットされてたら app_path
(Desired Capabiltiesの app:
に渡した値)のチェックをせずそのままサーバーに投げてくれるってことね。っていうか、このメソッドのコメントに説明書いてありますね。
# Converts app_path to an absolute path. # # opts is the full options hash (caps and appium_lib). If server_url is set # then the app path is used as is. # # if app isn't set then an error is raised. # # @return [String] APP_PATH as an absolute path
いや、そもそも、バックトレースに答え書いてあったじゃないか、デバッガ持ち出すまでもなかった……頭悪すぎです。
ということで、スクリプト側でAppiumサーバのURLセットしてなかったのが悪いと。今回はローカルでAppiumサーバーを立ててるときと同じく localhost:4723
でlistenしてたのでサーバーURL指定しなくてもスクリプトそのものは動いたんですが、Docker内で動いてるAppiumサーバーはホストから見たらリモートなのでちゃんと指定しなきゃいけない。
ということで、desired_caps
に appium_lib
も渡すようにして、そこで server_url
を指定するようにしたら動くようになりましたと、そういうわけです。
desired_caps = { caps: { platformName: 'android', deviceName: 'device', app: '/root/tmp/ApiDemos-debug.apk', appActivity: '.ApiDemos', appPackage: 'io.appium.android.apis', takesScreenshot: false, }, appium_lib: { server_url: 'http://localhost:4723/wd/hub' }
いやーお恥ずかしい。
もしこの間抜けがどなたかのお役に立てば幸いです。
*1:自動テストがprofessionであって、自動がつかないテストについては不勉強の至りです……。
*2:高田馬場ゲーセンミカドの動画がお気に入りです。
*3:Selenideいいですよねえ。機能も実装も素敵。もしJavaで素のWebDriver使って苦労してるならぜひ試してみましょう。
*4:言語系パッケージシステムのライフタイムと、Linuxディストリビューションのそれとは大きく違うのでそれはしょうがないですよね。
*5:というか、待ちきれなくて寝てしまいました。うちはあんまりネットワーク太くないので……。
*7:AppiumのRubyサンプルコード から拝借。
*8:こいつの意味は続きがあればそこで書くかも。さしあたりは公式読んでください。
*9:この活動の一環として将来は作ってみたいなとは思ってますけど……いつになるかは不明。
*10:というか一般にAppImageは便利。LibreOfficeとかでも。
*11:横ネタですけどRuby開発1億年ぶりぐらいなのですが、Visual Studio Code + ruby-debug-ide Gemでソースコードレベルデバッグが便利すぎて鼻血出た。このネタはRuby開発してる人なら当たり前すぎるので今日は割愛しますが、気が向いたら書きます。