setoya-blog

システム開発技術、データ分析関連でお勉強したことや、山奥生活を綴る、テンション低めなブログです。

Appium Desktop、capybara、site_prismで、スマホ実機での画面キャプチャを自動的にとる

1. 背景

Railsで作っているWebサービスのスマホWeb版を提供することになり、サポートする端末も決まった。しかし、

  • サポート端末は毎年のように変わる可能性が高い
  • サポート端末は随時追加される可能性が高い

などの理由から、もし実機で見たときの画面の見栄えが正しいかを手動でテストするとしたらアホらしいね、ということになり、せめて画面キャプチャをとるスクリプトを書こうということになった。

デプロイ前に毎回行うテストを用意するというよりかは、たまにチェックをしたいときに実行するようなrakeタスクにしたいと思い、調べたり検証した結果、

をrakeタスクから使うことにした。 ちなみに、実機での実際の見栄えのチェックじゃなくて、特定のhtmlの要素が存在するかどうかなど、自動化しやすい点をチェックしたいのであれば、rakeタスクで画面キャプチャを取るのではなく、上記の3つを使って普通にテストを書けば良いと思われる。

2. 開発環境構築

最終的には実機での確認をしたいが、実機を買ったりレンタルしたりする前に、iOSのSimulatorやAndroidのEmulatorでスクリプトの動作確認まではしておこう、ということで、一旦、端末のエミュレータを使う前提で環境を作っていく。ブログエントリーのタイトルは、「スマホ実機での」と書いちゃってるが、まあいいか。macOSを使う前提にする。

2.1. Xcodeのインストール

Appium ServerがiOS系の端末と接続するときに、Xcodeの機能に依存していたりするので、Xcodeをインストールしておく。Mac App Storeからインストールするだけ。

2.2. Android Studioのインストール

  1. Android Studioの公式サイトのインストール手順に従って、インストールする

  2. インストール後に、適当なプロジェクトを1つ作る
    (プロジェクトを作らないとSDK managerとAVD managerをAndroid Studioのメニューから使えないため)

  3. [Tools -> SDK manager]を確認し、SDKやPlatform toolsなど必要なものが入っていなければ追加しておく。

  4. [Tools -> AVD manager]から、新しいバーチャル端末を1つ適当に作っておく。
    ここで作ったバーチャル端末を後でAndroid Emulatorとして使う。

  5. adbなど、あとでコマンドラインからも使えるようにしておきたいので、ANDROID_HOMEPATHを以下のように設定しておく。なお、ANDROID_HOMEの場所は、[File -> Ohter Settings -> Default Project Structure]で開く設定画面や、SDK managerの画面のAndroid SDK locationに記載されている。 .bash_profileに以下を追加。

#Androidの設定
export ANDROID_HOME=$HOME/Library/Android/sdk 
export PATH=$PATH:$ANDROID_HOME/platform-tools

設定後は、コマンドラインでadbが実行できるか確認しておく。

$ source ~/.bash_profile
$ adb

2.3. Appium Desktopのインストール

Appiumはnodeのnpmでもインストールできるが、

  • Appium Desktopは色々環境構築が楽そう
  • スマホアプリの要素を特定するためのインスペクタがAppium Desktopのものが便利そう

ということもあり、Appiumの公式チームが開発に関わっていると思われるAppium Desktopをインストールする。

公式サイトのリリースページから、dmgファイルをダウンロードして、普通にインストールするだけ。

2.4. Appium DesktopのJAVA_HOMEとANDROID_HOMEを設定

Appium serverが、Androidと接続する際に、内部的にadbコマンドを使うらしく、そういった処理のために、ANDROID_HOMEを設定しておく必要があるっぽいので、設定しておく。Appium Desktopを立ち上げた後、[Edit Configurations]ボタンから設定できるので、設定して保存しておく。

f:id:sessan:20181106164350p:plain
Appium desktopのConfig画面

なお、JAVA_HOMEは、Android Studioをインストールしたときに付属してインストールされたものを使うことにする。普段使っているJDKがどこかにあれば、それを指定してもOKだと思われる。(が、自分はJavaをインストールしていないので、Android Studioのものにした。)

Android Studioをインストールしたときに、付属してインストールされたJavaは、Android Studioで[File -> Other Settings -> Default Project Structure]で開く設定画面に記載されているので、それを確認しておく。

これで、環境構築は終わり。

3. rake タスクの開発

ここからはrakeタスクの中身の実装について説明していく。

3.1. Gemfileに必要なライブラリを追加

以下をGemfileに追加

gem 'capybara'
gem 'appium_lib'
gem 'selenium-webdriver'
gem 'appium_capybara'
gem 'site_prism'

3.2. ソースコード用のディレクトリの作成

今回は、以下のようにディレクトリを使うことにした。

lib
└── tasks
    └── mobile_view_check_task
        ├── devices_config
        │   ├── local
        │   └── production
        ├── mobile_view_check.rake
        └── pages

device_configs以下には、環境ごと、デバイスごとのAppiumのcapabilitiesを定義したテキストファイル(後述)を置いていく。 pagesディレクトリ以下に、site_prismで作ったPageオブジェクト用のファイルを置いていく。
mobile_view_check.rakeは、タスクを定義するためのファイル。

3.3. site_prismのページファイルを作成

実際仕事で使ったファイルは、ここには書けないので、Google検索ページを表示するコード例で説明しておく。

以下のような感じで、site_prismのページクラスを作っておく。 google_search.rb

require 'site_prism'
require 'capybara'
require 'capybara/dsl'

class GoogleSearch < SitePrism::Page
  set_url('https://www.google.com')

  element :search_word, 'input[name="q"]'
end

site_prismの詳しい使い方は、この記事では省略。公式サイトの説明が詳しいので、知りたい人は見ると良い。

3.4. Appiumで接続するcapabilitesを記載した設定ファイルを作成

iphone_simu_8plus.iniとして、以下のようなini形式のファイルを作成して、devices_config/localディレクトリ以下に配置。検証中は、理由は忘れたけど、waitを長めに設定していた。安定して動くようになったらもっと短くしても良いと思われる。

[caps]
platformName = "iOS"
deviceName    = "iPhone 8 Plus"
automationName = "XCUITest"
platformVersion = "12.1"
browserName = "Safari"
locale = "ja_JP"
language = "Japanese"

[appium_lib]
wait  = 60
server_url = "http://localhost:4723/wd/hub"

同様に、Android Emulatorのiniファイル(android_emu.ini)は、以下。

[caps]
platformName = "Android"
deviceName    = "Android Emulator"
automationName = "Appium"
browserName = "Chrome"
nativeWebScreenshot = true

[appium_lib]
wait  = 60
server_url = "http://localhost:4723/wd/hub"

最初、nativeWebScreenshot = trueを指定していなかったとき、以下のようなエラーが出て、画面キャプチャを撮れなかったので、追加した。

WebDriverError: An unknown server-side error occurred while processing the command. 
Original error: Could not proxy. Proxy error: Could not proxy command to remote server. 
Original error: Error: ESOCKETTIMEDOUT"

appiumで指定可能なcapabilitiesの一覧は公式サイトを見るのが良いと思われる。ハマったときに、ここをちゃんと読むと解決できることが多い。

ちなみに、Macで接続可能なiOS系のデバイスの一覧は、instrumentsコマンドで以下のようにすると出力することができる。

$ instruments -s devices

同様に、Androidの端末一覧は、adbコマンドで、以下のようにすればOK。

$ adb devices

3.5. rakeタスクの実装

main処理部分は、以下のようにして、iniファイルの数分だけ、デバイスごとにキャプチャを撮っていく。

namespace :mobile do
  desc 'スマートフォン対応ページに対して、自動的にスクリーンショットを取るタスク'
  task get_captures: :environment do

    device_config_dir = Rails.root.join('lib', 'tasks', 'mobile_view_check_task', 'devices_config').to_s
    @mobile_captures_root = Rails.root.join('mobile_captures').to_s

    capybara_setup

    env = ENV['env']

    Dir.glob("#{device_config_dir}/#{env}/*.ini").each do |config|
      device_setup(config)
      google_search
      device_teardown
    end

  end

# --- 途中省略 ---- #
end

以下、省略部分の説明。

capybara_setupメソッドには、全体で一回設定しておきたいcapybaraの設定を記載。

  def capybara_setup
    Capybara.configure do |config|
      config.run_server = false
      config.default_max_wait_time = 60

      # テストしたいwebサイトのホストを環境変数CAPYBARA_APP_HOSTに定義
      # dotenv-railsを使っている人であれば、.envファイルに定義する
      # これを定義しておけば、site_prismのset_urlを相対パスで記載可能
      config.app_host = ENV['CAPYBARA_APP_HOST']
    end
  end

デバイスごとのセットアップとお片付けメソッドを定義。あと、screenshotを保存するユーティリティメソッドも作っておいた。

  def device_setup(config_file)

    @device_name = File.basename(config_file, '.ini')

    Capybara.register_driver(@device_name.to_sym) do |app|
      opts = Appium.load_appium_txt file: config_file
      Appium::Capybara::Driver.new app, opts
    end

    Capybara.current_driver = @device_name.to_sym

  end

  def device_teardown
    Capybara.current_session.driver.quit
  end

  def save_screenshot(file_name)
    # スクリーンショットをとるのが早すぎて、失敗することが多いので、スクリーンショットを取る前は、2秒待つことにする。
    sleep(2)
    driver = Capybara.current_session.driver

    save_dir = "#{@mobile_captures_root}/#{@device_name}"
    # ディレクトリがなければ作っておく
    FileUtils.mkdir_p(save_dir) unless FileTest.exist?(save_dir)

    driver.browser.save_screenshot("#{save_dir}/#{file_name}")
  end
  def google_search
    search_page = GoogleSearch.new
    search_page.load
    search_page.wait_until_search_word_visible
    save_screeenshot('google_search.png')
  end

最終的には、以下のようなファイルたちができている状態になる。

lib
└── tasks
    └── mobile_view_check_task
        ├── devices_config
        │   ├── local
        │   │   ├── android_emu.ini
        │   │   └── iphone_sim_8plus.ini
        │   └── production
        ├── mobile_view_check.rake
        └── pages
            └── google_search.rb

3.6. rakeタスクの実行

実行すると、app_root/mobile_capturesディレクトリ以下に、デバイスごとのキャプチャ画像がどんどん作られていくので、先に.gitignoreには以下のようなやつを記載しておいたほうがよい。

.gitignore

# モバイルテストのキャプチャ画像は無視する
/mobile_captures/*.jpg
/mobile_captures/**/*.jpg
/mobile_captures/*.png
/mobile_captures/**/*.png

実行は、rakeファイルで書いた内容にしたがって、以下のように行う。 envで指定したディレクトリ以下の.iniファイルが使われるので、実行環境やその他の理由で使うデバイスを切り替えたい場合に、ディレクトリを分けておけばよい。自分は、local環境ではSimulatorとEmulatorだけを使うので、localディレクトリに.iniファイルを配置した上で、env=localを指定している。

実行前に、Android Emulatorは自分で立ち上げておかないといけない。Android StudioのAVD managerから立ち上げておく。

$ rake mobile:get_captures env=local

3.7. 実行後の結果確認

実行すると以下のように、エミュレータがうにょうにょ動き出し、mobile_captures以下にデバイスごとの画像が保存されていくので、画像をさっと確認すれば、目視による見栄えチェックが楽にできる。

f:id:sessan:20181107171852g:plain
iPhone Simulatorが動く様子