Roborazziでテスト用のコードを書かずにスクリーンショットテストを追加する
はじめに
先日開催されたDroidKaigi 2024の「仕組みから理解する!Composeプレビューを様々なバリエーションでスクリーンショットテストしよう」のセッションにて、Roborazziで@Preview
のプレビューを使用してスクリーンショットテストを行うことができる機能の説明がありました。
元々Roborazziを使ってスクリーンショットテスト(VRT)を行っていましたが、テスト用のコードを記述するのに煩わしさを感じていたところでもあったため、これはすごい!と思い、試してみました。
サンプルコードはこちらになります。
使ってみた感想
そこまで大きな手間なく@Preview
で作ったものをスクリーンショットテスト(VRT)に使いまわせるようになるため、かなり便利だと感じました。
これに加え、前述のセッションでも言及されていたデバイス情報のパラメータを使用すると、かなりコスパ良くスクリーンショットテストを行うことができるのではないかと思います。
実際に触ってみて、
自分の環境ではroborazzi-compose-preview-scanner-support
が必須であったが、エラーメッセージ等に出力されなかった- スクリーンショットの出力先を変えるのはちょっと手間だった
といった点は留意しておくと良さそうです。
使ってみる
以下は実際に試してみた手順を紹介していきます。
テストする画面を作る
まずはテスト対象とする画面を用意します。
今回はごく簡単にTopAppBar、Text、TextButtonと、カスタムしたスナックバーを表示するのみの画面を用意しました。
Roborazziを導入する
続いてRoborazziおよび必要なライブラリであるRobolectricを導入します。
[versions]
robolectric = "4.13"
roborazzi = "1.26.0"
[libraries]
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
roborazzi-core = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
roborazzi-compose = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose", version.ref = "roborazzi" }
roborazzi-rule = { group = "io.github.takahirom.roborazzi", name = "roborazzi-junit-rule", version.ref = "roborazzi" }
[plugins]
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
[bundles]
roborazzi = [ "roborazzi-core", "roborazzi-compose", "roborazzi-rule" ]
plugins {
alias(libs.plugins.roborazzi)
}
android {
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
dependencies {
testImplementation(libs.androidx.espresso.core)
testImplementation(libs.robolectric)
testImplementation(libs.bundles.roborazzi)
}
plugins {
alias(libs.plugins.roborazzi) version libs.versions.roborazzi apply false
}
ここまでで、以下のようなテストコードを追加することによりスクリーンショットテストを行うことができるようになりました。
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class MainScreenScreenShotTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun captureMainScreen() {
ActivityScenario.launch(MainActivity::class.java)
captureRoboImage(
filePath = "screenshots/${this.javaClass.name}.png",
) {
MainScreen()
}
}
}
プレビューからスクリーンショットを自動生成する
ここからが本題となります。
まず、Previewからスクリーンショットを生成するのにComposablePreviewScannerを使用するため、そちらのHow to set upを参考に、ComposablePreviewScannerを導入します。
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
[versions]
composablePreviewScanner = "0.3.0"
[libraries]
composablepreviewscanner = { group = "com.github.sergio-sastre.ComposablePreviewScanner", name = "android", version.ref = "composablePreviewScanner" }
dependencies {
testImplementation(libs.composablepreviewscanner)
}
これでComposablePreviewScannerの準備は完了です。
続いてRoborazziの設定を行います。
READMEにあるようにgenerateComposePreviewRobolectricTests
のenableをtrueに設定します。
roborazzi {
@OptIn(ExperimentalRoborazziApi::class)
generateComposePreviewRobolectricTests {
enable = true
}
}
すると、以下のエラーが発生しました。
A problem occurred configuring project ':app'.
> java.lang.IllegalStateException: Please set roborazzi.generateComposePreviewRobolectricTests.packages in the generatePreviewTests extension or set roborazzi.generateComposePreviewRobolectricTests.enable = false.See https://github.com/sergio-sastre/ComposablePreviewScanner?tab=readme-ov-file#how-to-use for more information.
エラー内容に従ってテスト対象とするパッケージを追記します。
roborazzi {
@OptIn(ExperimentalRoborazziApi::class)
generateComposePreviewRobolectricTests {
enable = true
packages = listOf("com.kktaro.roborazzipreviewsample")
}
}
その後Syncすると以下のメッセージが表示されました。
Roborazzi: Please set 'robolectric.pixelCopyRenderMode = hardware' (Robolectric 4.12.2+) in the 'testOptions' block in the 'build.gradle' file. This is advisable to avoid issues with the fidelity of the images.
こちらも指示に従って追加します。
testOptions {
unitTests {
isIncludeAndroidResources = true
all {
it.systemProperties["robolectric.pixelCopyRenderMode"] = "hardware"
}
}
}
これでさらにSyncするとメッセージが出なくなりました。
のでいざ./gradlew recordRoborazziDebug
を実行すると...
Unresolved reference 'getComposePreviewTester'.
上記エラーでこけてしまいました。
どうやら自動生成されるファイルに含まれるComposePreviewTester
のインターフェースがio.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support
パッケージに定義されているようなので、こちらを依存関係に追加します。
[libraries]
roborazzi-composablepreviewscanner = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose-preview-scanner-support", version.ref = "roborazzi" }
[bundles]
roborazzi = [ "roborazzi-core", "roborazzi-compose", "roborazzi-rule", "roborazzi-composablepreviewscanner" ]
これで@Preview
により設定したプレビューのスクリーンショットが撮れるようになりました🎉
が、
-
private
のプレビューのスクリーンショットが撮れない - スクリーンショットの出力先が
build/
配下にある
の問題があるため、これらの設定を行います。
privateプレビューのスクリーンショットを追加する
こちらはREADMEにあるように設定を有効化するのみでOKです。
roborazzi {
@OptIn(ExperimentalRoborazziApi::class)
generateComposePreviewRobolectricTests {
enable = true
packages = listOf("com.kktaro.roborazzipreviewsample")
includePrivatePreviews = true
}
}
これによりprivate
で定義しているプレビューのスクリーンショットが出力されるようになります。
スクリーンショットの出力先を変更する
./gradlew verifyRoborazziDebug
によりUIの変更を検知できるようにするため、スクリーンショットもGit管理に含めるように変更します。
デフォルトではbuild/
配下に出力されるため、app/screenshot
に出力されるように変更します。
出力先を直接修正するプロパティは生えていないので、ComposePreviewTester
を自前で実装する必要があります。
出力先を決定しているのはRoborazziが提供しているAndroidComposePreviewTester
のtest
メソッドのcaptureRoboImageの部分です。
こちらを参考に、自前のComposePreviewTester
を実装し、任意の出力先に変更します。
@OptIn(ExperimentalRoborazziApi::class)
class MyComposePreviewTester : ComposePreviewTester<AndroidPreviewInfo> {
override fun previews(): List<ComposablePreview<AndroidPreviewInfo>> {
val options = options()
return AndroidComposablePreviewScanner()
.scanPackageTrees(*options.scanOptions.packages.toTypedArray())
.let {
if (options.scanOptions.includePrivatePreviews) {
it.includePrivatePreviews()
} else {
it
}
}
.getPreviews()
}
override fun test(preview: ComposablePreview<AndroidPreviewInfo>) {
val name = AndroidPreviewScreenshotIdBuilder(preview)
.ignoreClassName()
.build()
val filePath = "screenshot/$name.png"
preview.captureRoboImage(filePath)
}
}
更に作成したファイルのパスをRoborazziに渡すことで、自前で用意したものが使われるようになります。
roborazzi {
@OptIn(ExperimentalRoborazziApi::class)
generateComposePreviewRobolectricTests {
enable = true
packages = listOf("com.kktaro.roborazzipreviewsample")
includePrivatePreviews = true
testerQualifiedClassName = "com.kktaro.roborazzipreviewsample.MyComposePreviewTester"
}
}
これによりapp/screenshots/
にスクリーンショットが出力されるようになりました🎉
Discussion