<-- mermaid -->

CIを用いたAndroidのAPI29以上でのVisual Regression Test

NewsPicks Androidエンジニアの sefwgweo です。

こちらでも紹介しましたがモバイルチームでは品質担保のために毎晩定時にE2Eテストを実行しており、今回はその中のVisual Regression Testに焦点をしぼって紹介します。

iOSのVisual Regression Test紹介記事にもある通り、Visual Regression Test導入前はデザイン崩れは目視でやっていましたが確認事項が多く見落としも出るため自動化に至りました。

本記事では特にVisual Regression Testの細かな説明はせず、Androidで導入時苦労した点をAPI28以下とAPI29以上のケースを例に説明していきます。

検証動作環境

API CI 端末 実行データ保存場所 正解データ保存場所 画像比較
28 Bitrise Pixel2.arm GoogleCloudStorage GoogleCloudStorage reg-suit
30 Bitrise MediumPhone.arm テスト端末内 GoogleCloudStorage reg-suit

テスト端末について

Visual Regression Testでは効率性の観点から、API29以上ではタブレットを除いた中で端末サイズが一番大きいものでテストしたくてMediumPhone.arm(FirebaseTestLab)を選択しました。(API28以下時は他のE2Eと同じ端末を使うほうが効率的と考えていたのでPixel2.arm(FirebaseTestLab)を用いていた)
しかしFirebaseTestLabで用意されているAPI32及びAPI33のMediumPhone.armだとBluetooth keeps stoppingエラーでテストが頻繁にFailするため、弊社ではAPI30を用いています。
Pixel2.armのAPI33でも動作することは確認済のため画面サイズにこだわらないならこちらでもよいかもしれません。
※もちろんFirebaseTestLabの実機を使用可能な場合は実機のほうが処理が早く、かつ安定もします。

API29以上のテストフロー

① CI経由でFirebaseTestLabにあるAndroid(MediumPhone.arm,API30)を起動し、テストを実行
② テスト実行内で所定の画面のスクショを撮り、端末内に保存
③ 上記2で端末内に保存したスクショを実行環境のローカルにコピー
④ reg-suitを用いて上記3に置いたスクショと、予めGoogle Cloud Storageに置いてある正解データとで画像差分比較
⑤ 画像差分検証結果をSlackに通知

API29以上の端末での実装について

メリット

  • API28以下の端末での実装で起きていた、スクショ保存時エラーを解消できればAPI29以上の端末でもVisual Regression Testが可能になる
  • Google製の ScreenShotter.takeScreenshotやScreenshot.captureはDeprecatedだったので刷新することで警告が減る
  • ライブラリで隠蔽されていた部分のカスタマイズ性があがる
  • スクショするメソッドを自由に決められる(弊社ではダイアログも対応しているUiDevice#takeScreenshotを使用しています)

デメリット

  • Google製の ScreenShotter.takeScreenshotやScreenshot.captureはスクショ保存時エラーが起きて使いづらいため、API28以下のテストフロー図の②〜④を自前で調査・実装する必要がある
    特に苦労したのが、どうやって端末に保存したスクショをCIのローカルに持ってくるかだった(API29以上のテストフロー図②〜③)

API29以上の端末で動作させるための手順

1. スクショ保存時エラー回避

getExternalMediaDirsで取得したディレクトリパス /sdcard/Android/media/com.hoge/ に置くことで、権限問題によりスクショが保存できなかったエラーは回避可能となります。
これによりAPI29以上のテストフロー②が実行可能になります。

2. 実行結果スクショ取得(保存したスクショを画像比較コマンド実行場所に移動)

birise.ymlに設定することで、FirebaseTestLab経由でのテスト時に端末からファイルが取り出し可能になります。
これによりAPI29以上のテストフロー③が実行可能になります。

2-1. BitriseでFirebaseTestLab用のプラグイン(virtual-device-testing-for-android)を使っている場合

以下をbitrise.ymlに追記します。

- virtual-device-testing-for-android@1: 
    inputs:
    - test_type: instrumentation
    - test_timeout: '3600'
    - inst_test_targets: "$E2E_CLASS"
    - test_devices: MediumPhone.arm,30,en,portrait
    - environment_variables: additionalTestOutputDir=/sdcard/Android/media/com.hoge/
    - directories_to_pull: /sdcard/Android/media/com.hoge/
    - download_test_results:true"
  • オプション environment_variables: additionalTestOutputDir は、前述のスクショ保存時エラー回避方法で取得したパス(/sdcard/Android/media/com.hoge/)を入れることでファイルが取り出し可能になる
  • オプション directories_to_pull は、テスト完了後にデバイスのストレージからダウンロードされるパスになる
  • オプション download_test_results は、false に設定するとダッシュボードにのみダウンロードされるようになる

以下スクリプトをbitrise.ymlに追記することでテスト端末からCIのローカルに取り出します。(取り出し時ファイルをreg-suitのためにリネームします)
${VDTESTING_DOWNLOADED_FILES_DIR}はBitriseでVirtualDeviceTesting実行時ファイルが一次保存される場所を示した環境変数
$SAVED_ACTUAL_DIRは実行環境ローカル保存パスが入る $IMAGE_PREFIXは端末内保存パスが入る(oriole-30-en-portrait_artifacts/sdcard/Android/media/com.hoge/)

$ mv ${VDTESTING_DOWNLOADED_FILES_DIR}/screenshottest_*.png ./${SAVED_ACTUAL_DIR}/
$ cd ${SAVED_ACTUAL_DIR}
$ ls | sed -r "s/($IMAGE_PREFIX)(screenshottest[_a-z0-9]+)\.png/mv & \2.png/g" | bash

2-2. BitriseでFirebaseTestLab用のプラグイン(virtual-device-testing-for-android)を使わず直接FirebaseTestLabを呼び出している場合

以下をbitrise.ymlに追加することでBitriseに用意されているvirtual-device-testing-for-androidを使わず直接FirebaseTestLabを実行可能になります。(Fastlane等を使っている場合に参考になると思います)
※${BUCKET}はGoogle Cloud Storageで用いるバゲット名を、${RESULT_DIR}はテスト結果保存パス、${E2E_CLASS}は実行テストクラスパスを環境にあわせて入れてください。

gcloud firebase test android run 
  --type instrumentation 
  --app $BITRISE_APK_PATH 
  --test $BITRISE_TEST_APK_PATH 
  --device model= MediumPhone.arm,version=30,locale=en,orientation=portrait 
  --timeout 45m 
  --use-orchestrator 
  --results-bucket=${BUCKET} 
  --results-dir=${RESULT_DIR} 
  --test-targets "${E2E_CLASS}" 
  --environment-variables additionalTestOutputDir=/sdcard/Android/media/com.hoge/ 
  --directories-to-pull /sdcard/Android/media/com.hoge/
  • オプション environment_variables: additionalTestOutputDir は、前述のスクショ保存時エラー回避方法で取得したパス(/sdcard/Android/media/com.hoge/)を入れることでファイルが取り出し可能になる
  • オプション directories_to_pull は、テスト完了後にデバイスのストレージからダウンロードされるパスになる

以下スクリプトをbitrise.ymlに追加することでGoogle Cloud StorageからCIのローカルにスクショを取り出せます。

$ gsutil cp gs://${BUCKET}/${RESULT_DIR}/MediumPhone.arm-30-en-portrait/artifacts/sdcard/Android/media/com.hoge/screenshottest_*.png ./${SAVED_ACTUAL_DIR}/

2-3. ローカル実行の場合

CIを使わずローカルで完結させたい場合の手順となります。
build.gradleに以下2行を追加することでローカルテスト時に端末から所定の場所に保存したスクショが取り出し可能になります。

testInstrumentationRunnerArguments["useTestStorageService"] = "true" 
testInstrumentationRunnerArguments["additionalTestOutputDir"] = "/sdcard/Android/media/com.hoge/"

adbコマンドで以下のようにpullしてもいいがAndroidStudioの機能を使うと簡単に端末からローカルにスクショを移動可能です。

$ adb pull $(adb shell 'ls /sdcard/Android/media/com.hoge/screenshottest_*.png') .

3. 画像比較とSlack通知

reg-suitで画像比較検証を行います。
API28以下もAPI29以上も、予めGoogle Cloud Storageに置いてある正解データとローカルに移動してきたスクショを比較検証します。
テストフロー④と⑤を実行します。

Bitriseで対話式のプラグインインストールを何も考えず行うとFailしていたため以下のように echo N | を用いて回避する必要がありました。

npm install -g reg-suit
npm install

# Pluginのインストール
npm i reg-notify-slack-plugin -D
echo N | reg-suit prepare -p notify-slack
npm i reg-publish-gcs-plugin -D
echo N | reg-suit prepare -p publish-gcs

npm i reg-simple-keygen-plugin -D
echo N | reg-suit prepare -p simple-keygen
            
# 実行
reg-suit run

[おまけ]API28以下のテストフロー(Screenshot.captureを用いたケース)

① CI経由でFirebaseTestLabにあるAndroid(Pixel2.arm,API28)を起動し、テストを実行
② テスト実行内で所定の画面のスクショを撮り、端末内に保存と同時にGoogleCloudStorageにも保存
③ 差分テストをするために、上記2で保存されたスクショをGoogle Cloud Storageから実行環境のローカルにコピー
④ reg-suitを用いて上記3と、予めGoogle Cloud Storageに置いてある正解データとで画像差分比較
⑤ 画像差分検証結果をSlackに通知

API28以下の端末での実装について

メリット

  • 標準ライブラリの ScreenShotter.takeScreenshotまたはScreenshot.captureを用いてスクショを撮ると、API28以下の端末であれば端末の /sdcard/screenshots に自動的に保存される(テストフロー図の②の部分が該当します)
  • Firebase Test Lab では/sdcard/screenshots に保存されたファイルは全て Google Cloud Storage にも自動的に保存してくれるようになっているため、テスト結果をGoogle Cloud Storageからダウンロード後reg-suitの比較をするだけ(テストフロー図の③〜④の部分が該当します)
  • ネットに沢山情報もあるため、導入も簡単(弊社でも最近まではScreenshot.captureを用いてテストしていた)

デメリット

  • Google製の ScreenShotter.takeScreenshotまたはScreenshot.captureを用いたままAPI29以上の端末で実行すると、スクショした結果をアプリ内に保存しようとした瞬間外部ストレージへのアクセスの仕様が変わったため以下のエラーで失敗する
  • その結果上記テストフロー②より先に進まないため、API29以上の端末で実行できるようにするためにはまずこのエラーを解消する必要がある
Failed to capture screenshot 
java.io.IOException: The directory /sdcard/screenshots does not exist and could not be created or is not writable.

おわりに

AndroidのVisual Regression Testについて紹介しました。
Visual Regression Testingを導入したことで、目視では気づけないようなわずかな違いも検出してくれるようになりました。

本記事がVisual Regression Testを導入しようとしている方に少しでも参考になれば幸いです。

Page top