スクショテスト実装:screengrabでスクリーンショットを撮影する
やりたいこと
- screengrabでアプリの各画面のスクショを撮影する。
- fastlaneで実行できるようにする。
- MiniMagickでスクショの画像比較を行う。
- GitLab CIで動くようにしたらプロジェクトへの導入完了。
fastlaneは導入済みとする。
screengrabの公式ドキュメントはここ。Java想定なので適宜Kotlinに読み替えて理解する。
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で。
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用のマニフェストファイルだけでよし。
自動テストのコードを作成していく。
エミュレータにアクセスするテストなので、ローカル単体テストではなくインストゥルメンテーションテスト扱い。
(test)ではなく(androidTest)として作成すること。
いろんな画面のテストケースが作られることになると思うので、まず抽象クラスを定義する。
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)
}
}
...と思ったけど、ActivityTestRule
さん、deprecatedじゃないですか。。
ActivityTestRuleを使用している箇所を、ActivityScenarioRuleを使うように変更したほうがいいかも。とりあえずこのままで。
先ほどのクラスを継承してテストケースを作っていきます。
撮影するだけっす。
class MainActivityTest : ScreenshotTest() {
@Test
fun testTakeScreenshot() {
this.launchActivity<MainActivity>(Intent())
screenshot(isNightMode = false)
}
}
Espressoで UI操作を挟んだりできるようです。今はやらない。
これだけじゃ自動テストとして成立しない(撮れた画像を目視チェックするしかない)ので、laneで実行できるようにして、最終的には画像比較できるようにします。
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の設定ファイルです。各パラメータの詳細は公式ページへ。
fastlaneフォルダ直下のFastfileに、スクショテスト用laneを作成します。
まずはアプリのデバッグビルドとテストのapkが必要なのでビルド処理を書く。
gradle(task: "assemble", flavor: "flavor", build_type: "Debug")
gradle(task: "assemble", flavor: "flavor", build_type: "AndroidTest")
フレーバーやビルドタイプなどはプロジェクトによりけり。
エミュレータが起動していなければスクショが撮れないので、laneから起動する処理を入れます。
system("~/Library/Android/sdk/emulator/emulator -avd #{avd_name} > /dev/null &")
こんな感じ。
起動チェック処理を入れて、起動ずみならスキップ、起動中なら待機するようにするといい。
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)
ステータスバーのアイコンや時刻が差分として検出されてしまうので、
スクショ画像はトリミング処理をかましてから保存します。
参考:Clean Status Bar
スクショ画像に問題なければ、比較用の正解画像として fastlane 配下のどこかにでも保存しておきます。
長くなるので画像比較などは別のスクラップに書きます。
撮影処理はとりあえずここまでで完了。