reg-suitとroborazziを使用したVRTを実装してみる

## はじめに
こんにちは、こんばんは、佐藤佑哉ことさとゆーです。今回はreg-suitとroborazziを使用したVRTを実装してみようと思います。
## 動機
最近のインターンでxmlファイルからJetpack Composeへの移行をした際に、Jetpack Composeに入れ替える時、変更前と変更後の画面を目視で比較する必要があるため、レビューなどで漏れてしまうことがありました。これを機に、機械的に分析してヒューマンエラーをなくすための、VRTを触ってみたいと思うようになりました。

## 使用技術の紹介
この記事で紹介するVRTツールの技術はreg-suitとroborazziです。


## roborazziとは
roborazziとは、Android端末を使わずにJVM上でスクリーンショットを撮影することができるライブラリです。スクリーンショットテストライブラリとして開発されており、テスト中に画像のキャプチャを取得し、事前に取得しておいた期待する画面のキャプチャと比較することで検証を行うことができます。
reg-suitを用いたのは、CA.flutterなどでも紹介されていた、

https://github.com/takahirom/roborazzi
## reg-suitとは
reg-suitとは、VRTのためのCLIを提供するCLIであり、現在の画像と以前の画像を比較し、HTMLに差異を検出してくれるため、差分を視覚的に表示することができます。

https://reg-viz.github.io/reg-suit/#github_integration


### 使用理由

- reg-suit:
    - [CA.flutter](https://cyberagent.connpass.com/event/298289/)の勉強会でこの技術を知り、本番のプロダクトでも使用されるくらいの信頼性と安定性がある
- roborazzi
    - nowinandroidや他の企業でも採用されており、非常に人気が高い
    - JVM上で動くため、Androidの環境に依存しないテストの実現が可能

## 実装フロー
今回は、VRTの実装の観点とreg-suitの技術の都合上、以下のように実装していきます。
- [ ] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
    - [ ] roborazziの導入
    - [ ] スクリーンショットを撮る
    - [ ] VRTを行う
- [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
    - [ ] reg-suitの導入
    - [ ] roborazziのスクリーンショットの出力先を変更する
    - [ ] 手動で変更前のスクリーンショットを入れておく
    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

## roborazziを使用して、画面をスクリーンショット

### roborazziの依存関係を追加

roborazziは、下記`README`に記載されているものを参考に依存関係を追加しました。

https://github.com/takahirom/roborazzi?tab=readme-ov-file#try-it-out

> - [ ] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [ ] スクリーンショットを撮る
>    - [ ] VRTを行う
>  - [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [ ] reg-suitの導入
>    - [ ] roborazziのスクリーンショットの出力先を変更する
>    - [ ] 手動で変更前のスクリーンショットを入れておく
>    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

### roborazziを使用してスクリーンショットを撮る
次に、roborazziを使用してスクリーンショットを撮ってみましょう。
スクリーンショットができるように、`gradle.properties`で次のプロパティを有効にします。
```properties
roborazzi.test.record=true
```

早速、スクリーショットができるようになったので、簡単なComposableをスクリーンショットしてみましょう。([ソースコードはこちら](https://github.com/dao0203/todo-sample-app/blob/main/feature/home/src/test/java/com/example/home/SampleTest.kt))

```kotlin
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [33], qualifiers = RobolectricDeviceQualifiers.Pixel7)
class SampleTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @Test
    fun sampleTest() {
        composeTestRule.setContent { MaterialTheme { Text("Hello, World!") } }
        composeTestRule.onRoot().captureRoboImage("src/test/screenshots/Sample/sample_test.png")
    }
}
```

このコードのみで簡単にComposableのスクリーンショットできるのがわかりました。([実際のスクリーンショットはこちら](https://github.com/dao0203/todo-sample-app/blob/main/feature/home/src/test/screenshots/sample/sample_test.png))

![sample_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/a2c3393a-8cf3-a593-3335-a1adce1142e2.png)

> - [ ] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [x] スクリーンショットを撮る
>    - [ ] VRTを行う
>  - [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [ ] reg-suitの導入
>    - [ ] roborazziのスクリーンショットの出力先を変更する
>    - [ ] 手動で変更前のスクリーンショットを入れておく
>    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

## roborazziを使用して、VRTを行う
次に、roborazziを使用して、VRTを行っていきます。
### 任意の画面をセット
任意のComposableをテストにセットし、変更前の画面のスクリーンショットを取得します。
```kotlin
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [33], qualifiers = RobolectricDeviceQualifiers.Pixel7)
class HomeScreenTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @get:Rule //後に説明
    val roborazziRule = DefaultRoborazziRule

    @Test
    fun homeContent_success() {
        val uiState = HomeUiState.Success(
            uncompletedTodos = emptyList(),
            completedTodos = emptyList(),
            categories = listOf(myTaskCategory),
            selectedCategoryId = 1
        )
        val snackBarHostState = SnackbarHostState()
        composeTestRule.setContent {
            MaterialTheme {
                HomeContent(
                    uiState = uiState,
                    /*...*/
                )
            }
        }
        composeTestRule.onRoot().captureRoboImage()
    }
}
```

次にComposableのレウアウトを変更します。
今回は、適当に空白を入れておきます。
```kotlin
Spacer(Modifier.height(8.dp))
```

### コマンドの入力
以下のコマンドを入力すると、変更前と変更後の画面のスクリーンショットを比較してくれます。
https://github.com/takahirom/roborazzi?tab=readme-ov-file#build-setup

```bash
.gradlew compareRoborazziDebug
```

入力後に次のディレクトリ`root_project/module_name/build/outputs/roborazzi/src/*`を見てみましょう。すると、スクリーンショットが3枚入っています。
| `name.png` | `name_actual.png` |
|:-:|:-:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/a0585c61-5803-21d9-3497-5e86e9b8db6e.png" width=200 > | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/8cba22b4-7410-0761-26a4-3f7a4d96d018.png" width=200 > |

| `name_compare.png` |
|:-:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/e33a365d-a6f1-86b5-d442-7ac9ad3f7c57.png">  |

変更前と変更後、差分のスクリーンショットを提供してくれているのがわかります。
また、ファイル`root_project/module_name/build/reports/roborazzi/index.html`
をみると、以下のローカルサイトを見ることができます。(一部のレイアウトを抜粋)

![スクリーンショット 2024-02-14 0.31.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/03a054ab-c7db-d61b-1075-1faf1d7f37ed.png)

> - [x] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [x] スクリーンショットを撮る
>    - [x] VRTを行う
> - [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [ ] reg-suitの導入
>    - [ ] roborazziのスクリーンショットの出力先を変更する
>    - [ ] 手動で変更前のスクリーンショットを入れておく
>    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

## reg-suitを使用してVRTを行う
### reg-suitの導入
reg-suitを本プロジェクトで導入するために次のコマンドを入力します。

```bash
$ reg-suit init
```

初期化が終わると、reg-suitの設定が始まります。
```bash
? Working directory of reg-suit. .reg # reg-suitのワーキングディレクトリのディレクトリ名を決定
? Append ".reg" entry to your .gitignore file. Yes
? Directory contains actual images. directory_contains_actual_images //変更後の画面のスクリーンショットのディレクトリ先
? Threshold, ranges from 0 to 1. Smaller value makes the comparison more sensitive. 0　## デフォルトは0
[reg-suit] info Set up reg-notify-github-plugin:
? notify-github plugin requires a client ID of reg-suit GitHub app. Open installation window in your browser Yes ## reg-notify-github-pluginを使用するために、リポジトリのClientIDが必要
? This repositoriys client ID of reg-suit GitHub app #RepositoryのClientIDを記入
```

上記のreg-suitの設定を完了すると、以下のファイルが出来上がってると思います。
```bash
root_project/ 
           ├ package.json
           ├ package-lock.json
           ├ regconfig.json
           ├ ...
```

> - [x] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [x] スクリーンショットを撮る
>    - [x] VRTを行う
> - [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [x] reg-suitの導入
>    - [ ] roborazziのスクリーンショットの出力先を変更する
>    - [ ] 手動で変更前のスクリーンショットを入れておく
>    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

## roborazziのスクリーンショットの出力先を変更する
reg-suitでは、スクリーンショットの機能はないため、roborazziを使用して補完します。

https://github.com/takahirom/roborazzi?tab=readme-ov-file#roborazzi-gradleproperties-options

```properties
## RoborazziRule.Options.outputDirectoryPathでアウトプット先を変更することができる。
roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory

## ファイル名をクラス名.メソッド名.pngと統一することができる
roborazzi.record.namingStrategy=testClassAndMethod
```

RoborazziRuleを使用して、スクリーンショットの出力先をreg-suit導入時に入れたディレクトリ先を記入します。

```kotlin
val DefaultRoborazziRule =
    RoborazziRule(RoborazziRule.Options(outputDirectoryPath = "../../actual_images"))

@get:Rule
val roborazziRule = DefaultRoborazziRule

```

現在は、Featureモジュール上でVRTを行っているためこのようにしておりますが、他のモジュール(designsytemmモジュールなど)階層が違うモジュールのVRTを行う際には、VRT専用のモジュールでも作ると、よりこのRoborazziRuleの汎用性が高まりそうだなと記事を書きながら気づきました。

> - [x] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [x] スクリーンショットを撮る
>    - [x] VRTを行う
> - [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [x] reg-suitの導入
>    - [x] roborazziのスクリーンショットの出力先を変更する
>    - [ ] 手動で変更前のスクリーンショットを入れておく
>    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

### 手動で変更前のスクリーンショットを入れておく

reg-suitでは、変更前のスクリーンショットを.reg/expectedに追加されている必要があるため、手動でスクリーンショットを入れておきます。

> - [x] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [x] スクリーンショットを撮る
>    - [x] VRTを行う
> - [ ] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [x] reg-suitの導入
>    - [x] roborazziのスクリーンショットの出力先を変更する
>    - [x] 手動で変更前のスクリーンショットを入れておく
>    - [ ] roborazziで変更後のスクリーンショットを撮り、VRTを行う

### roborazziで変更後のスクリーンショットを撮り、VRTを行う

roborazziから変更後のスクリーンショットを撮っておきます。
```bash
./gradlew recordRoborazziDebug # ./actual_imagesにスクリーンショットが出力される
```
次に以下のコマンドを入力し、reg-suitによるVRTを行います。
```bash
reg-suit run
```
これをすると、以下のディレクトリが自動生成されます
```bash
.reg/
   ├ actual/ # 変更後のスクリーンショットが出力される
   ├ diff/ # 差分
   ├ expeted/ # 手動でスクリーンショットを入れたディレクトリ
   ├ index.html # 差分レポート
   ├ ...
```

`index.html`はこちらになっております。reg-suitは機能満載で、差分がとてもみやすいサイトになっていました。

![スクリーンショット 2024-02-14 1.43.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/1947591c-4c0c-b208-c2ed-97d486e2b3df.png)

![スクリーンショット 2024-02-14 1.46.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989029/c239110f-d3d9-0282-d40f-cbf7bbfa42ce.png)

これで、roborazziとreg-suitを使用したVRTを実現することはできました🎉
> - [x] roborazziを使用して、先ほどのスクリーンショットとVRTを行う
>    - [x] roborazziの導入
>    - [x] スクリーンショットを撮る
>    - [x] VRTを行う
> - [x] reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
>    - [x] reg-suitの導入
>    - [x] roborazziのスクリーンショットの出力先を変更する
>    - [x] 手動で変更前のスクリーンショットを入れておく
>    - [x] roborazziで変更後のスクリーンショットを撮り、VRTを行う

# まとめ
本記事では、reg-suitとroborazziを使用したVRTを実装いたしました。
個人的に実装コストやAndroidアプリ開発における汎用性を考えると、roborazziがかなり使い勝手が良さそうです。インターフェースの細かな調整のレビューが重視される場合は、reg-suitを使用してみるのもありだと感じました！

誰かの開発の助けになれば幸いです🙇‍♂️