Closed14

スクショテスト実装:screengrabでスクリーンショットを撮影する

nemoteanemotea

やりたいこと

  • screengrabでアプリの各画面のスクショを撮影する。
  • fastlaneで実行できるようにする。
  • MiniMagickでスクショの画像比較を行う。
  • GitLab CIで動くようにしたらプロジェクトへの導入完了。

fastlaneは導入済みとする。

nemoteanemotea

screengrabの公式ドキュメントはここ。Java想定なので適宜Kotlinに読み替えて理解する。
https://docs.fastlane.tools/actions/screengrab/

build.gradleに以下を追加。

dependencies {
    testImplementation 'junit:junit:X.XX'
    androidTestImplementation 'androidx.test:rules:X.X.X'
    androidTestImplementation 'tools.fastlane:screengrab:1.2.0'
}

とりあえずfastlane:screengrababは1.2.0で。

nemoteanemotea

AndroidManifest.xml (debug) に以下の設定を追加。

<!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

<!-- Allows for storing and retrieving screenshots -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!-- Allows changing locales -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />

エミュレータ上でスクショを撮影・保存・データ吸い上げなどを行うので、
ストレージにアクセスする権限が必要っぽい。

自動テストを動かすための設定なので、debug用のマニフェストファイルだけでよし。

nemoteanemotea

いろんな画面のテストケースが作られることになると思うので、まず抽象クラスを定義する。

abstract class ScreenshotTest {

    // ロケール自動切り替えをしてくれるらしい?UIテストなのであると嬉しい
    @Rule @JvmField
    val localeTestRule = LocaleTestRule()

    lateinit var activity: Any

    // Activity起動処理をジェネリクスで汎用化。
    inline fun <reified T> launchActivity(intent: Intent): T where T : Activity {
        this.activity = ActivityTestRule(T::class.java).launchActivity(intent)
        return this.activity as T
    }

    // スクショのキャプチャが改善されるらしいのでScreenshotStrategyを設定。
    @Before
    fun setupScreenshotStrategy() {
        Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
    }

    // スクショ撮影処理。同画面のダークモード版もチェックできるように対応。
    fun screenshot(suffix: String? = null, isNightMode :Boolean) {
        var name = activity::class.java.simpleName
        if (!suffix.isNullOrEmpty()) {
            name += "_$suffix"
        }

        if (isNightMode){
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
            name += "_Night"
        } else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
        }
        Thread.sleep(3000)
        Screengrab.screenshot(name)
    }
}
nemoteanemotea

先ほどのクラスを継承してテストケースを作っていきます。
撮影するだけっす。

class MainActivityTest : ScreenshotTest() {
    @Test
    fun testTakeScreenshot() {
        this.launchActivity<MainActivity>(Intent())
        screenshot(isNightMode = false)
    }
}

Espressoで UI操作を挟んだりできるようです。今はやらない。

nemoteanemotea

これだけじゃ自動テストとして成立しない(撮れた画像を目視チェックするしかない)ので、laneで実行できるようにして、最終的には画像比較できるようにします。

nemoteanemotea

fastlaneフォルダ直下にScreengrabfileを作成します。

locales                     ['ja-JP']
ending_locale               'ja-JP'
clear_previous_screenshots  true
skip_open_summary           true
tests_apk_path              '対象のテスト apk のパス'
app_apk_path                '対象のアプリ apk のパス'
output_directory            'fastlane/screenshots'
test_instrumentation_runner 'androidx.test.runner.AndroidJUnitRunner'
use_adb_root                true

screengrabの設定ファイルです。各パラメータの詳細は公式ページへ。

nemoteanemotea

fastlaneフォルダ直下のFastfileに、スクショテスト用laneを作成します。
まずはアプリのデバッグビルドとテストのapkが必要なのでビルド処理を書く。

gradle(task: "assemble", flavor: "flavor", build_type: "Debug")
gradle(task: "assemble", flavor: "flavor", build_type: "AndroidTest")

フレーバーやビルドタイプなどはプロジェクトによりけり。

nemoteanemotea

エミュレータが起動していなければスクショが撮れないので、laneから起動する処理を入れます。
https://developer.android.com/studio/run/emulator-commandline?hl=ja

system("~/Library/Android/sdk/emulator/emulator -avd #{avd_name} > /dev/null &")

こんな感じ。
起動チェック処理を入れて、起動ずみならスキップ、起動中なら待機するようにするといい。

nemoteanemotea

screengrab()で、先ほど作成したテストメソッドが実行されます。
出力先のフォルダパスはScreengrabfileから取得します。

properties = JavaProperties.load("Screengrabfile")
output_directory = properties[:output_directory]
output_directory = output_directory.sub(/^\'/, "")
output_directory = output_directory.sub(/\'$/, "")
screengrab(output_directory: output_directory)
nemoteanemotea

ステータスバーのアイコンや時刻が差分として検出されてしまうので、
スクショ画像はトリミング処理をかましてから保存します。
参考:Clean Status Bar

スクショ画像に問題なければ、比較用の正解画像として fastlane 配下のどこかにでも保存しておきます。

nemoteanemotea

長くなるので画像比較などは別のスクラップに書きます。
撮影処理はとりあえずここまでで完了。

このスクラップは2021/02/01にクローズされました