本節では、@Previewアノテーションが付けられたComposeのプレビュー画面のスクリーンショットを自動的に撮る方法を学ぶ。
「ViewModelを結合してComposeをテストする」でも「UI Stateが更新されたら意図した画面に変化すること」を確認する方法を学んだが、確認できる内容はComposeのセマンティックツリーとして表現されている情報の範囲に限られていた。 セマンティックツリーの情報だけでは、たとえば次のような観点の確認をすることは難しい。
- 異なる画面サイズ(タブレットなど)でレイアウト崩れが発生していないか
- ダークモードで表示させたときに配色が正しいか
- トグルスイッチやボタン、テキストフィールドなど、複数の状態をもつコンポーネントが、各状態で正しく表示されているか
このような、より「見た目」に近いレイヤーでの確認を行うには、画面のスクリーンショットを自動的に撮影・保存するようなテストを実行し、 保存されたスクリーンショットを目視で確認する必要がある。 とはいえ、すべてのスクリーンショットを継続して目視で確認し続けることは現実的ではない。 その解決手段として登場したのがVisual Regression Test (VRT)である。
VRTでは、コードの修正前後で保存された画面スクリーンショットをピクセル単位で比較し、意図しない画面の変化(レイアウト崩れや、フォント・色の適用ミスなど)がないことを(目視で)確認するテスト手法である。 VRTのためのツールを使えば、画像の差分を可視化したレポートを生成できるため、そのレポートを元に差分のある画像だけを確認対象にすれば、目視の負担を大きく軽減できる。
以降ではShowkaseとRoborazziを使って、 Composeのプレビュー画面のスクリーンショットを自動的に撮り、その差分レポートを生成する方法を説明する。
このハンズオンで利用するツールを紹介する。VRTを実現するための画像比較ツールについては次の節で紹介する。
URL: https://github.com/airbnb/Showkase
Showkaseは、@Previewアノテーションが付けられたComposeのプレビュー画像の一覧画面(Showkaseブラウザ)をKAPT/KSPを用いて自動生成するツールである。
デバッグメニューなどで次のコードを呼び出すようにするだけで、簡単にUIカタログをアプリに組み込むことができる。
startActivity(Showkase.getBrowserIntent(context))
このハンズオンでは、@Previewアノテーションが付けられたComposeのプレビュー画像を収集するために本ツールを利用する。
URL: https://github.com/takahirom/roborazzi
Roborazziは、Robolectricを使って(Local JVMで)、Androidの画面スクリーンショットを撮影できるツールである。 (技術的にはRobolectric 4.10より導入されたRobolectric Native Graphics (RNG)を使って実現されている) また、過去に撮影したスクリーンショットとの画像差分レポートを出力する機能も備えている。
これまでの画面のスクリーンショットを撮るテストは、Androidのエミュレータまたは実機で動作させるInstrumented Testとして実行する必要があった。 Instrumented Testは実行時間が長いため、Pull Requestのチェックタスクとして毎回実行するのは現実的でなかった。 一方、RoborazziはLocal JVM上で高速にスクリーンショットを撮影できるため、そのような頻繁に実行する用途でも使えるようになった。 ただし、Robolectricは画面のレンダリングをエミュレーションしているに過ぎないため、Andorid端末でレンダリングした結果と完全には一致しない点に注意が必要である。
| メリット | デメリット | |
|---|---|---|
| Roborazzi + Robolectric | 高速に実行できる | 画面のレンダリング結果が実機のものとは多少異なる※1 (シャドウの有無など) |
| Instrumented Testで動作するスクリーンショット取得API UiAutomatorの takeScreenshot()など |
より忠実度の高いレンダリング結果が得られる (利用するツールによる※2) |
実行に時間がかかる |
- ※1 RobolectricにShadow Rendering Supportを導入する動きがあるため、今後はより忠実度の高いレンダリング結果が得られる見込み
- ※2 スクリーンショット取得APIによるレンダリング結果の違いについては「Androidのテストで利用できるスクリーンショット取得APIのまとめ」を参照のこと
ShowkaseはKSPとKAPTの両方をサポートしているものの、KSPでしか使えない機能があるため、KSPを使った設定方法を紹介する。
トップレベルのbuild.gradle.ktsでKSPプラグインの使用を宣言する。
plugins {
id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
}
モジュールレベルのbuild.gradle.ktsのうち @Previewアノテーションが含まれる全モジュール について、KSPプラグインを適用し、Showkaseのランタイムライブラリを追加する。
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("com.airbnb.android:showkase:1.0.3")
ksp("com.airbnb.android:showkase-processor:1.0.3")
}
デフォルトではShowkaseはprivateなPreview関数があるとビルドエラーになるが、エラーにせず無視し、単に収集対象外にしたい場合は次の宣言も追加する。
ksp {
arg("skipPrivatePreviews", "true")
}
アプリケーションモジュール(典型的にはappモジュール)に、次のようなクラスを定義する。クラス名は任意でよい。
@ShowkaseRoot
class MyRootModule: ShowkaseRootModule
既存のPreview関数について、スクリーンショット対象にしたいかどうかに応じて次のいずれかの修正をする。
-
スクリーンショット対象にしたいPreview関数は、アクセス修飾子を
internalまたはそれより広くする -
スクリーンショット対象にしたくないPreview関数は、アクセス修飾子を
privateするか、次のように@ShowkaseComposableアノテーションを付ける@ShowkaseComposable(skip = true) @Preview @Composable fun MyComposable() { ... }
後者の「スクリーンショット対象にしたくないPreview関数」は、同じ画面を複数の画面サイズ・ロケールなどでそれぞれプレビューしていると必要になることがある。
@Preview(locale = "ja") や @Preview(device = Devices.TABLET)のような@Previewのオプション引数は、Showkaseでは一部を除いて無視されてしまう。
たとえば、次のように複数ロケールで同じ画面をプレビューしている場合、Android Studioではそれぞれ指定されたロケールでプレビュー画像が表示されるが、Showkaseではすべて同じ(デフォルトロケールの)画像になってしまう。このようなときは、(重複する)片方をスクリーンショット対象から除外するとよい。
(@Previewのオプション引数を無視せず両方ともスクリーンショット対象にする方法は、後で「プレビューでは確認できないComposeの画面のスクリーンショットを撮る」にて説明する)
@Composable
fun MyComposable() { ... }
@Preview(locale = "ja")
fun MyJapaneseComposable() {
MyComposable()
}
@Preview(locale = "en)
fun MyEnglishComposable() {
MyComposable()
}
なお、Showkaseは本来UIカタログ画面を自動生成するツールであるため、この方法でスクリーンショット対象となったものは自動的にUIカタログにも含まれる。
デバッグメニューなどから次のコードを実行できるようにし、Showkaseブラウザ(Showkaseが提供するUIカタログ画面)を起動してみる。
※サンプルのNow in Android Appアプリにはデバッグメニューがないため、後述のadbコマンドを使う方法で確認する
startActivity(Showkase.getBrowserIntent(context))
または、getBrowserIntent()の実装を参考に、次のようにadbコマンドでShowkaseブラウザのActivityを起動してみてもよい。
adb shell am start-activity -n {アプリケーションID}/com.airbnb.android.showkase.ui.ShowkaseBrowserActivity -e SHOWKASE_ROOT_MODULE {ShowkaseRootModuleインターフェイスの実装クラス名}
その結果、https://github.com/airbnb/Showkase/blob/master/assets/showkase_features.png にあるようなShowkaseブラウザ画面が表示されるはずである。 スクリーンショット対象にしたいPreview関数のみが一覧に載っていれば正しくセットアップできている。
動作確認の結果、ビルドできない、Showkaseブラウザに出てくるはずの画面が出てこない、などの事象が発生した場合は、次の点を確認してみるとよい。
ShowkaseはGradleのキャッシュと相性が悪いことあるので、次の方法でGradleのキャッシュを削除、または無効化してみる。
gradle.propertiesに以下を追加する。
org.gradle.caching=false
org.gradle.unsafe.configuration-cache=false
-
Configuration Cacheを削除する
rm -rf .gradle/configuration-cache/ -
--no-build-cacheオプションを付けてcleanする./gradlew clean --no-build-cache
KSPプラグインの適用漏れがあると、Showkaseブラウザに表示されるはずのプレビュー画像が表示されない、という形で問題が表面化する。
以前に触れたように、@Previewアノテーションが含まれている全モジュールに対してKSPプラグイン適用すればよいのだが、
マルチプレビューアノテーションが使われている場合には更なる注意が必要となる。
Jetpack Composeのマルチプレビューアノテーションとは、次のように複数のPreviewアノテーションを1つにまとめた独自アノテーションのことである。
@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
annotation class DevicePreviews
マルチプレビューアノテーションが指定されたComposable関数をShowkaseの収集対象に含めたい場合は、そのアノテーションが定義されたモジュールにもKSPプラグインを適用する必要がある。 もし適用が漏れてしまうと、マルチプレビューアノテーションが指定されたComposable関数だけShowkaseブラウザに表示されなくなってしまう。
RoborazziはRobolectricに依存しているため、RoborazziとRobolectricの両方を設定する方法を紹介する。
トップレベルのbuild.gradle.ktsでRoborazziプラグインの使用を宣言する。
plugins {
id("io.github.takahirom.roborazzi") version "1.26.0" apply false
}
アプリケーションモジュール(典型的にはappモジュール)のbuild.gradle.ktsでRoborazziプラグインを適用し、必要なライブラリを追加する。
plugins {
id("io.github.takahirom.roborazzi") version "1.26.0"
}
android {
testOptions {
unitTests {
// Robolectricで必要
isIncludeAndroidResources = true
}
}
dependencies {
testImplementation("io.github.takahirom.roborazzi:roborazzi:1.26.0")
testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.26.0")
testImplementation("org.robolectric:robolectric:4.13")
}
スクリーンショット画像の出力先ディレクトリを設定しておく。次の例では{プロジェクトルート}/screenshotsディレクトリにスクリーンショットが保存される。
(デフォルトでは{そのモジュールのルート}/build/output/roborazziとなる)
roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory
android {
testOptions {
unitTests {
all {
it.systemProperty(
"roborazzi.output.dir",
rootProject.file("screenshots").absolutePath
)
}
}
}
}
Showkaseによって収集されたPreview関数(Showkaseブラウザで一覧できているPreview関数)それぞれについて、Roborazziでスクリーンショットを保存するテストを書いていく。
次の2つの段階にわけて説明する。
- Showkaseによって収集された
Preview関数をテストコード内から呼び出す - テストコード内から呼び出した
Preview関数のスクリーンショットを保存する
Showkaseによって収集されたPreview関数のリストに関する情報は次のコードで取得できるため、それをパラメトライズドテストの入力に使う。
Showkase.getMetadata().componentList
Robolectricのパラメトライズドテストは @RunWith(ParameterizedRobolectricTestRunner::class) と宣言することで利用できる。
@JvmStaticで宣言された関数の戻り値(ArrayのIterable)1つ1つがコンストラクタのパラメーターに設定され、テストメソッドが呼び出される。
@RunWith(ParameterizedRobolectricTestRunner::class)
class AllPreviewScreenshotTest(
private val showkaseBrowserComponent: ShowkaseBrowserComponent,
) {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun test() {
composeTestRule.setContent {
// showkaseBrowserComponent.component() がPreview指定されているComposable関数
showkaseBrowserComponent.component()
}
// ここでスクリーンショットを取得する
}
companion object {
@ParameterizedRobolectricTestRunner.Parameters
@JvmStatic
fun components(): Iterable<Array<*>> = Showkase.getMetadata().componentList.map { arrayOf(it) }
}
}
Roborazziでは、composeTestRuleから辿れるセマンティックツリーのNodeに対してcaptureRoboImage()という関数を提供している。そのため、
composeTestRule.onRoot().captureRoboImage()
とすることで目的のComposable関数のスクリーンショットを取得できる。
また、RoborazziはRobolectric Native Graphics (RNG)を利用するため、アノテーション@GraphicsMode(GraphicsMode.Mode.NATIVE)も必要となる。
@Config(qualifiers = ...)で、前提とするデバイスも指定できる。
この方法でデバイスを指定すると、そのデバイスの画面サイズでレンダリングした結果をスクリーンショットとして保存できる。
@RunWith(ParameterizedRobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(qualifiers = RobolectricDeviceQualifiers.Pixel7)
class AllPreviewScreenshotTest(
private val showkaseBrowserComponent: ShowkaseBrowserComponent,
) {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun test() {
composeTestRule.setContent {
// showkaseBrowserComponent.component() がPreview指定されているComposable関数
showkaseBrowserComponent.component()
}
// ここでスクリーンショットを取得する
composeTestRule.onRoot().captureRoboImage()
}
companion object {
@ParameterizedRobolectricTestRunner.Parameters
@JvmStatic
fun components(): Iterable<Array<*>> = Showkase.getMetadata().componentList.map { arrayOf(it) }
}
}
ここまでの手順にしたがってテストを書き、実行してみよう。
gradle.propertiesに次の行を追加し、Android Studio上でテストを実行してみよう。
roborazzi.test.record=true
成功するとbuild/reports/roborazzi/index.htmlに結果レポートが生成されるので、内容を確認してみよう。
- 背景画像は表示されましたか?
SettingsDialog.ktのPreviewSettingsDialog関数に付いているアノテーション
@ShowkaseComposable(skip = true)を削除し、改めてテストを実行してみよう。
- テストは成功しましたか?失敗した場合どのようなエラーになりましたか?
ここまでのコードでは、いくつかのエッジケースで動かないことがあるため、より洗練された形にしていく。
Jetpack Composeの仕様では、Composable関数がプレビューのために実行されているかどうかは、LocalInspectionMode.currentがtrueになっているかどうかで判定できる
(「コンポーザブルのプレビューで UI をプレビューする」参照)。
そのためLocalInspectionMode.currentの値によって条件分岐しているようなComposable関数を、
IDEでプレビューしたときと同じ状態でスクリーンショットを撮るには、
次のようにLocalInspectionMode.currentをtrueにしてからComposable関数を実行する必要がある。
composeTestRule.setContent {
CompositionLocalProvider(
LocalInspectionMode provides true,
) {
showkaseBrowserComponent.component()
}
}
次のようなケースでは、1つのComposable関数につき、複数のプレビューが存在する。 そのため、スクリーンショットのファイル名をComposable関数のフルネームにしただけではファイル名が重複し、 すべてのスクリーンショットを保存できないことがある。
- 1つのComposable関数に複数の
@Previewアノテーションが宣言されている場合 - 複数の
@Previewを1つにまとめたカスタムマルチプレビューアノテーションが使われている場合 - 1つのComposable関数にさまざまなパターンのパラメーターを渡した結果をプレビューできる
PreviewParameterProviderが使われている場合
そこで、次のように、Showkaseが提供するユニークなキーshowkaseBrowserComponent.componentKeyをファイル名に含めることで重複を避けるのが望ましい。
val filePath = "${testCase.showkaseBrowserComponent.componentKey}.png"
composeTestRule.setContent {
...
}
composeTestRule.onRoot().captureRoboImage(filePath)
ただし、Kotlinのパッケージ名が長いなどの要因で、ファイル名がファイルシステムの長さ制限を超えてしまうことがある。
そのようなときはcomponentKeyの一部をファイル名から取り除くなど、ファイル名が短くなるように工夫する。
ダイアログを表示している場合など、状況によってはcomposeTestRule.onRoot()が1つに定まらないことがある。
その場合は「スクリーンショットを撮りたいノードには子ノードを持っているはず」という条件を用いてスクリーンショットを撮るノードを1つに絞り込む必要がある。
kotlin.runCatching {
// 複数Windowがある場合、子コンポーネントがいる最初のものをとってくる
composeTestRule.onAllNodes(isRoot())
.filter(hasAnyChild())
.onFirst()
.assertExists() // 念のため存在していることをassertしている。assertに失敗したらnullを返し、captureScreenRoboImage()を使って全画面スクリーンショットを撮る
}.getOrNull()?.captureRoboImage(filePath) ?: captureScreenRoboImage(filePath)
ここで、hasAnyChild()の定義は次のとおり
fun hasAnyChild(): SemanticsMatcher {
return SemanticsMatcher("hasAnyChildThat") {
it.children.isNotEmpty()
}
}
ParameterizedRobolectricTestRunnerでは、デフォルトでは{テストメソッドの名前}[{通番}]というテスト名になるため、
テストが失敗したときに、どのPreview関数が原因なのか特定するのが難しい。
テスト名は@ParameterizedRobolectricTestRunner.Parametersの引数で指定できるため、
次のように(ファイル名で使った)componentKeyをテスト名に含めるようにする。
パラメトライズドテストでこのようなカスタマイズをするときは、パラメーターを格納するクラス(次の例ではTestCaseクラス)を導入すると便利に使える。
class AllPreviewScreenshotTest(
private val testCase: TestCase
) {
...
companion object {
// パラメーターを独自クラスにする
class TestCase(
val showkaseBrowserComponent: ShowkaseBrowserComponent
) {
// ここにテスト名に含めたい情報を入れる
override fun toString() = showkaseBrowserComponent.componentKey
}
// {index}は通し番号、{0}は`arrayOf(...)`の第1引数を`toString()`したものになるため、
// `test[[5] com.example_MyPreview_null_null_0_null]` といったテスト名となる
@ParameterizedRobolectricTestRunner.Parameters(name = "[{index}] {0}")
@JvmStatic
fun components(): Iterable<Array<*>> = Showkase.getMetadata().componentList.map {
arrayOf(TestCase(it))
}
}
}
これらの工夫をまとめると、最終的に次のようなテストになる。
@RunWith(ParameterizedRobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(qualifiers = RobolectricDeviceQualifiers.Pixel7)
class AllPreviewScreenshotTest(
private val testCase: TestCase
) {
@get:Rule
val composeTestRule = createComposeRule()
@OptIn(ExperimentalRoborazziApi::class)
@Test
fun test() {
val filePath = "${testCase.showkaseBrowserComponent.componentKey}.png"
composeTestRule.setContent {
CompositionLocalProvider(
LocalInspectionMode provides true,
) {
testCase.showkaseBrowserComponent.component()
}
}
kotlin.runCatching {
// 複数Windowがある場合、子コンポーネントがいる最初のものをとってくる
composeTestRule.onAllNodes(isRoot())
.filter(hasAnyChild())
.onFirst()
.assertExists() // 念のため存在していることをassertしている。assertに失敗したらnullを返し、captureScreenRoboImage()を使って全画面スクリーンショットを撮る
}.getOrNull()?.captureRoboImage(filePath) ?: captureScreenRoboImage(filePath)
}
fun hasAnyChild(): SemanticsMatcher {
return SemanticsMatcher("hasAnyChildThat") {
it.children.isNotEmpty()
}
}
companion object {
class TestCase(
val showkaseBrowserComponent: ShowkaseBrowserComponent
) {
override fun toString() = showkaseBrowserComponent.componentKey
}
@ParameterizedRobolectricTestRunner.Parameters(name = "[{index}] {0}")
@JvmStatic
fun components(): Iterable<Array<*>> = Showkase.getMetadata().componentList.map {
arrayOf(TestCase(it))
}
}
}
テストコードを最終形にして、もう一度テストを実行してみよう。
- テストは成功するようになりましたか?
- 背景画像は表示されるようになりましたか?
RoborazziのGradleプラグインによって、Roborazzi専用のGradleタスクが作られる。 それらのタスクについて説明する。
テストコードを実行し、その結果得られたスクリーンショット画像を保存し、その一覧レポートを生成する。 レポートなどの情報の保存先は次のとおり。
| 情報 | 保存先 |
|---|---|
| スクリーンショット一覧レポート | build/reports/roborazzi/index.html |
| 結果が格納されたJSONファイル | build/test-results/roborazzi/ |
| テストコード(JUnit)の実行結果レポート | app/build/reports/tests/test{ビルドバリアント}UnitTest/index.html |
なお、スクリーンショット画像の出力先ディレクトリは、Roborazziの全タスク共通で「スクリーンショット画像の出力先ディレクトリの設定」で設定したディレクトリになる。
このタスクで生成されるレポートのイメージは次のとおり。
テストコードを実行し、その結果得られたスクリーンショット画像と既存のスクリーンショット画像を比較し、その比較レポートを生成する。 比較元となるスクリーンショット画像は、前述のスクリーンショット画像の出力先ディレクトリにあるものが使われる。
比較元の画像と差分があった場合には、差分に関する情報が保存される。情報の保存先は次のとおり。
| 情報 | 保存先 |
|---|---|
| (ピクセル単位で比較した)差分画像 | build/outputs/roborazzi/{オリジナルのファイル名}_compare.png |
| 今回のテストで取得したスクリーンショット画像 | {スクリーンショット画像の出力先ディレクトリ}/{オリジナルのファイル名}_actual.png |
| スクリーンショット比較レポート | build/reports/roborazzi/index.html |
| 結果が格納されたJSONファイル | build/test-results/roborazzi/ |
| テストコード(JUnit)の実行結果レポート | app/build/reports/tests/test{ビルドバリアント}UnitTest/index.html |
このタスクで生成されるレポートでは、増えた画像、差分ありの画像、差分なしの画像に分類されて表示される。
「スクリーンショットの比較」と同じだが、画像に差分が生じたテストは失敗する。 失敗したテストは「テストコード(JUnit)の実行結果レポート」に記録されている。
「スクリーンショットの比較結果の検証」と同じだが、次の点が異なる。
- 比較元の画像と差分があった場合、新しい画像(「スクリーンショットの比較結果の検証」では末尾が
_acutual.pngという名前で保存されていた画像)でオリジナルの画像を上書きする - 比較元の画像と差分があった場合
_actual.pngという名前で終わるファイルは作られない。差分画像である_compare.pngという名前で終わるファイルは作られる
Android Gradle Pluginが提供する、標準のテストタスク(test{ビルドバリアント}UnitTest)でも、Roborazziの専用タスクと同じ内容を実現できる。
その場合はGradleプロパティ(gradle.propertiesに宣言するプロパティ)の宣言内容によって動作が決まる。
| Gradleプロパティ | test{ビルドバリアント}UnitTest で実行されるタスク |
|---|---|
| (何も宣言しない) | (スクリーンショットは保存されない。通常のJUnit Testとして実行される) |
roborazzi.test.record=true |
スクリーンショットの記録:recordRoborazzi{ビルドバリアント} |
roborazzi.test.compare=true |
スクリーンショットの比較:compareRoborazzi{ビルドバリアント} |
roborazzi.test.verify=true |
スクリーンショット比較結果の検証:verifyAndRecordRoborazzi{ビルドバリアント} |
roborazzi.test.record=trueとroborazzi.test.verify=trueの両方を指定 |
スクリーンショット比較結果の検証と比較元画像の更新:verifyAndRecordRoborazzi{ビルドバリアント} |
この機能は、Android Studioで開いたテストコードからテストを実行したいときに便利である。
通常のユースケースでは、Roborazziでスクリーンショットを撮りたいときには、それ以外の(スクリーンショットを撮らない)ユニットテストを実行すると時間が無駄になってしまう。 ところが、Roborazzi専用のGradleタスクであってもすべてのユニットテストが実行されてしまう。
そうではなく、スクリーンショットを撮るテストだけを実行したいときは、JUnit4のCategories機能を活用する。 JUnit 4のCategories機能を使うと、次のことを実現できる。
@Categoryアノテーションを使って、テストメソッドに「カテゴリー」を付与できる- テスト実行時に、特定のカテゴリーだけを実行する
スクリーンショットを撮るテストにだけ特定のカテゴリーを付与すれば、この機能を使ってスクリーンショットを撮るテストだけを実行できる。 その手順を説明する。
適当な名前でマーカーインターフェイスを定義する。この名前がカテゴリー名となる。
package com.example.androidtuesday
interface ScreenshotTests
@Category(ScreenshotTests::class)
@Test
fun test() {
// スクリーンショットを撮るテスト
}
次のように宣言すると、Gradleプロパティscreenshotを指定したときはScreenshotTestsカテゴリーのテストだけを実行できるようになる。
(例: ./gradlew recordRoborazziDebug -Pscreenshot)
testOptions {
unitTests.all {
it.useJUnit {
if (hasProperty("screenshot")) {
includeCategories("com.example.androidtuesday.ScreenshotTests")
}
}
}
}
「スクリーンショットの記録」タスクを実行してから、わざとPreview画面の内容を変更してみよう。 その後、その他のタスクを実行し、どのようなレポートが生成されるか確認してみよう。

