5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】 CircleCI + Roborazzi + Showkase で VRT 試しました(改)

Last updated at Posted at 2024-03-31

はじめに

2023年3月からAndroidアプリエンジニアをしている KSND(GitHubX )といいます。

以前自分が書いた以下の記事では AWS S3 を使っていましたが、AWS S3 を使わなくて済むようにするなど改善点がたくさんあり書き直したいと思ったので今回この記事を書きました。

想定読者

  • Jetpack Compose を使っているAndroidアプリのプロジェクトに VRT(画像回帰テスト) を導入したい方
  • VRT 導入にあたって GitHub Actions ではなく、CircleCI を使わないといけない何かしらの理由のある方

実際に試した際の比較画像

image.png

VRT試したレボジトリ

(よかったらスターいただけるとかなり嬉しいです...)

使用したライブラリやサービス

Circle CI

Roborazzi

Showkase

GitHub CLI

VRTの大まかな流れ

大きくは PR変更時のビルドマージ後のビルド で処理が分かれます。

PR変更時のビルド

  1. VRT対象かを確認
  2. スクリーンショットを screenshots_〜 ブランチから CircleCICache を使用して取得
  3. Roborazziの比較用のオプションを使用して、ユニットテスト実行
  4. 作成された比較画像のみにし compare_〜 ブランチを作成してGitHubにpush
  5. 比較結果をコメント投稿(もしくは編集)

マージ後のビルド

  1. Roborazziのスクリーンショット作成用のコマンドを実行
  2. 作成されたスクリーンショットのみにし screenshots_〜 ブランチを作成してGitHubにpush
  3. 古くなったVRT用のブランチを削除

VRT導入の手順

以下の流れで説明します

  1. RoborazziShowkaseをプロジェクトに導入
  2. config.ymlの設定
  3. CircleCI GitHubの設定

1. RoborazziShowkaseをプロジェクトに導入

ShowkaseRootModule.kt
import com.airbnb.android.showkase.annotation.ShowkaseRoot
import com.airbnb.android.showkase.annotation.ShowkaseRootModule

@ShowkaseRoot
class ShowkaseRootModule : ShowkaseRootModule
  • private にしている Preview関数PreviewParameterProvider から private を削除
  • 永遠と続くアニメーションやダイアログを表示する Preview関数 に @ShowkaseComposable(skip = true) を設定しVRTから外す(簡単にVRTの対象にすることができないためスキップしてます)
  • Previewのテストを追加

最初は Showkase.getMetadata() が Not Found 状態になりますが、ビルドすれば解消されます

PreviewTest
// ref: https://github.com/DroidKaigi/conference-app-2023/pull/217
@RunWith(ParameterizedRobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(qualifiers = RobolectricDeviceQualifiers.Pixel6)
class PreviewTest(
    private val param: Pair<ShowkaseBrowserComponent, Int>,
) {

    @Test
    fun previewScreenshot() {
        val (showkaseBrowserComponent, count) = param
        val componentName = showkaseBrowserComponent.componentName.replace(" ", "")
        val filePath = DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + componentName + "_" + count + ".png"
        captureRoboImage(filePath) {
            // ライトモード、ダークモードの設定(Previewに設定するだけではShowkaseで切り替えできません)
            val newConfiguration = Configuration().apply {
                this.uiMode = if (componentName.contains(other = "dark", ignoreCase = true)) {
                    Configuration.UI_MODE_NIGHT_YES
                } else {
                    Configuration.UI_MODE_NIGHT_NO
                }
            }
            CompositionLocalProvider(LocalConfiguration provides newConfiguration) {
                showkaseBrowserComponent.component()
            }
        }
    }

    companion object {
        @ParameterizedRobolectricTestRunner.Parameters
        @JvmStatic
        fun components(): Iterable<Array<Any?>> {
            // PreviewParameterProviderを使用する場合componentNameが同じになるためカウント追加
            val countMap = mutableMapOf<String, Int>()
            return Showkase.getMetadata().componentList.map { showkaseBrowserComponent ->
                val componentName = showkaseBrowserComponent.componentName
                val count = countMap.getOrDefault(key = componentName, defaultValue = 0)
                countMap[componentName] = count + 1
                arrayOf(showkaseBrowserComponent to count)
            }
        }
    }
}
  • ./gradlew recordRoborazzi<ビルドバリアント(Debugなど)> を実行し、SUCCESSで app/build/outputs/roborazziに画像が生成されていることを確認

作成される画像のサイズを小さくしたい場合は、 Roborazzi の README にある roborazzi.record.resizeScale を設定し画像を粗くすることでできます

2. config.ymlの設定

config.yml 完成品(長すぎるので折りたたんでいます)

config.yml全体
version: 2.1

orbs:
  gh: circleci/github-cli@2.3.0

executors:
  android:
    docker:
      - image: cimg/android:2024.01

commands:
  check_is_skipping_vrt:
    steps:
      - gh/install
      - run:
          name: Gh login
          command: echo "$GITHUB_ACCESS_TOKEN" | gh auth login --with-token
      - run:
          name: Save IS_SKIPPING_VRT to env
          command: |
            is_skipping_vrt=false
            if [[ "$(echo $(git log -1 --pretty=%B))" == *"[skip vrt]"* ]]; then
              is_skipping_vrt=true
            else
              if [ -z "$CIRCLE_PULL_REQUEST" ] || [ "$(gh pr view $CIRCLE_BRANCH --json labels | jq '.labels | any(.name == "skip vrt")')" = "true" ]; then
                is_skipping_vrt=true
              fi
            fi
            echo "export IS_SKIPPING_VRT=$is_skipping_vrt" >> $BASH_ENV

  get_screenshots:
    steps:
      - run:
          name: Save BASE_BRANCH_NAME to env
          command: |
            pr=$(echo https://api.github.com/repos/${CIRCLE_PULL_REQUEST:19} | sed "s/\/pull\//\/pulls\//")
            base=$(curl -s -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" $pr | jq '.base.ref')
            echo "export BASE_BRANCH_NAME=${base}" >> $BASH_ENV
      - run:
          name: Save IS_EXIST_SCREENSHOTS to env
          command: |
            is_exist=false
            if [ $IS_SKIPPING_VRT = "false" ]; then
              is_exist=$(echo -n "$(git fetch origin screenshots_$BASE_BRANCH_NAME && echo true || echo false)")
            fi
            echo "export IS_EXIST_SCREENSHOTS=${is_exist}" >> $BASH_ENV
      - run:
          name: Checkout screenshots branch
          command: |
            if [ $IS_EXIST_SCREENSHOTS = "true" ]; then
              git fetch origin screenshots_$BASE_BRANCH_NAME
              git checkout screenshots_$BASE_BRANCH_NAME
            fi
      - run:
          name: Save SCREENSHOTS_TIMESTAMP to env
          command: |
            if [ $IS_EXIST_SCREENSHOTS = "false" ]; then
              echo "no-op" > timestamp
            fi
            echo "export SCREENSHOTS_TIMESTAMP=$(echo -n $(cat timestamp))" >> $BASH_ENV
      - run:
          name: Zip screenshots
          command: |
            mkdir -p ./temp/zip
            if [ $IS_EXIST_SCREENSHOTS = "true" ]; then
              zip -r ./temp/zip/screenshots.zip ./app/build/outputs/roborazzi
            fi
      - save_cache:
          paths:
            - ./temp/zip
          key: screenshots-{{ checksum "timestamp" }}
      - run:
          name: Checkout pr branch
          command: git checkout $CIRCLE_BRANCH
      - run:
          name: Restore timestamp file
          command: echo $SCREENSHOTS_TIMESTAMP > timestamp
      - restore_cache:
          key: screenshots-{{ checksum "timestamp" }}
      - run:
          name: Unzip screenshots
          command: |
            unzip ./temp/zip/screenshots.zip || true
            rm -rf ./temp || true

  set_locale_properties:
    steps:
      - run:
          name: Set local.properties
          command: |
            LOCAL_PROPERTIES_PATH=./local.properties
            echo "apiKey=$.Environment.DEBUG_AD_APPLICATION_ID" >> $LOCAL_PROPERTIES_PATH

  restore_and_save_gradle_cache:
    steps:
      - restore_cache:
          key: jars-{{ checksum "build.gradle.kts" }}-{{ checksum  "app/build.gradle.kts" }}-{{ checksum "gradle/libs.versions.toml" }}
      - save_cache:
          paths:
            - ~/.gradle
          key: jars-{{ checksum "build.gradle.kts" }}-{{ checksum  "app/build.gradle.kts" }}-{{ checksum "gradle/libs.versions.toml" }}

  unit_test:
    parameters:
      build_variant:
        type: string
    steps:
      - run:
          name: Unit test
          command: |
            if [ $IS_SKIPPING_VRT = "false" ]; then
              roborazzi_option="-Proborazzi.test.compare=true"
            fi
            ./gradlew test<< parameters.build_variant >> $roborazzi_option --stacktrace

  setting_git_config:
    steps:
      - run:
          name: Setting git config
          command: |
            git config --global user.name "$GITHUB_NAME"
            git config --global user.email "$GITHUB_EMAIL"

  push_screenshots_branch:
    steps:
      - setting_git_config
      - run:
          name: Push screenshots branch
          command: |
            git push origin --delete screenshots_$CIRCLE_BRANCH || true
          
            git checkout --orphan screenshots_$CIRCLE_BRANCH
            git rm --cached -rf .
          
            add_files=$(find . -type f -path "./app/build/outputs/roborazzi/*")
            for file in $add_files; do
              git add -f $file
            done
          
            echo $(date +%s) > timestamp
            git add -f timestamp
          
            echo -e "version: 2.1\njobs:\n no-op:\n  machine: true\n  steps:\n  - run: no-op\nworkflows:\n build:\n  jobs:\n   - no-op:\n      filters:\n       branches:\n        only: no-op" > .circleci/config.yml
            git add -f .circleci/config.yml
          
            git commit -m "Add screenshot"
            git clean -df
            git push origin HEAD:screenshots_$CIRCLE_BRANCH -f

  push_compare_branch:
    steps:
      - setting_git_config
      - run:
          name: Push compare branch
          command: |
            if [ $IS_SKIPPING_VRT = "false" ]; then
              git push origin --delete compare_$CIRCLE_BRANCH || true
            
              fileSize=$(echo $(find ./app/build/outputs/roborazzi -type f | grep -e '.*_compare.png' | wc -l | sed -e 's/ //g'))
              if [ $fileSize -ne 0 ]; then
                git checkout --orphan compare_$CIRCLE_BRANCH
                git rm --cached -rf .
            
                add_files=$(find . -type f -path "./app/build/outputs/roborazzi/*" -name "*_compare.png")
                for file in $add_files; do
                  git add -f $file
                done
            
                echo -e "version: 2.1\njobs:\n no-op:\n  machine: true\n  steps:\n  - run: no-op\nworkflows:\n build:\n  jobs:\n   - no-op:\n      filters:\n       branches:\n        only: no-op" > .circleci/config.yml
                git add .circleci/config.yml
            
                git commit -m "Add screenshot diff"
                git clean -df
                git push origin HEAD:compare_$CIRCLE_BRANCH -f
              fi
            fi

  comment_screenshot_diff:
    steps:
      - run:
          name: Create comment
          command: |
            echo "Snapshot diff report" > comment
            echo "| File name | Image |" >> comment
            echo "|-------|-------|" >> comment
            
            files=$(find . -type f -path "./app/build/outputs/roborazzi/*" -name "*_compare.png")
            for file in $files; do
              fileName=$(basename "$file" | sed -r 's/(.{20})/\1<br>/g')
              echo "| [$fileName](https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/blob/compare_$CIRCLE_BRANCH/$file) | ![](https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/blob/compare_$CIRCLE_BRANCH/$file?raw=true) |" >> comment
            done
      - run:
          name: Comment screenshot diff
          command: |
            if [ $IS_SKIPPING_VRT = "false" ]; then
              prNumber=$(echo $CIRCLE_PULL_REQUEST | sed "s:.*/::")
              url="https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${prNumber}/comments"
              commentId=$(echo -n $(curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" $url -v | jq '.[] | select(.body | test("^Snapshot diff report.*")) | .id'))
              failedComment="$(echo -e "Snapshot diff report\n:warning: **Failed to show Snapshot diff**\n$CIRCLE_BUILD_URL")"
              
              if [ -n "$commentId" ]; then
                comment="$(cat ./comment)"
                endpoint="/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/comments/$commentId"
                gh api --method PATCH -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" $endpoint -f body="$comment" ||
                gh api --method PATCH -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" $endpoint -f body="$failedComment"
              else
                gh pr comment "$CIRCLE_PULL_REQUEST" -F ./comment || gh pr comment "$CIRCLE_PULL_REQUEST" -b "$failedComment"
              fi
            fi

  cleanup_old_branch:
    parameters:
      prefix:
        type: string
      maximum_seconds_past:
        type: integer
        default:  2592000 # 30 days
    steps:
      - run:
          name: Cleanup old branch
          command: |
            git branch -r --format="%(refname:lstrip=3)" | grep << parameters.prefix >> | while read -r branch; do
              last_commit_date_timestamp=$(git log -1 --format=%ct "origin/$branch")
              now_timestamp=$(date +%s)
              if [ $((now_timestamp - last_commit_date_timestamp)) -gt << parameters.maximum_seconds_past >> ]; then
                git push origin --delete "$branch"
              fi
            done || true

jobs:
  unit_test:
    executor: android
    steps:
      - checkout
      - check_is_skipping_vrt
      - get_screenshots
      - set_locale_properties
      - restore_and_save_gradle_cache
      - unit_test:
          build_variant: ProdDebug
      - push_compare_branch
      - comment_screenshot_diff

  save_screenshots:
    executor: android
    steps:
      - checkout
      - set_locale_properties
      - restore_and_save_gradle_cache
      - run:
          name: Create screenshots
          command: ./gradlew recordRoborazziProdDebug --stacktrace
      - push_screenshots_branch
      - cleanup_old_branch:
          prefix: compare_
      - cleanup_old_branch:
          prefix: screenshots_
          maximum_seconds_past: 15552000 # 180 days

workflows:
  test:
    jobs:
      - unit_test
      - save_screenshots:
          filters:
            branches:
              only:
                - main
                - develop

(以下手順です)

  • orbsGitHub CLIを追加
config.yml
orbs:
  gh: circleci/github-cli@2.3.0
  • commandsに以下 8つ のコマンド追加
1. VRT対象かチェック
  • 以下いずれかの場合にVRTをスキップします
    • 最後のコミットメッセージに [skip vrt] が含まれている
    • CIRCLE_PULL_REQUEST が空の時 = PRがなくコメント表示できない
    • PRにskip vrtラベルが設定されている
config.yml
  check_is_skipping_vrt:
    steps:
      - gh/install
      - run:
          name: Gh login
          command: echo "$GITHUB_ACCESS_TOKEN" | gh auth login --with-token
      - run:
          name: Save IS_SKIPPING_VRT to env
          command: |
            is_skipping_vrt=false
            if [[ "$(echo $(git log -1 --pretty=%B))" == *"[skip vrt]"* ]]; then
              is_skipping_vrt=true
            else
              if [ -z "$CIRCLE_PULL_REQUEST" ] || [ "$(gh pr view $CIRCLE_BRANCH --json labels | jq '.labels | any(.name == "skip vrt")')" = "true" ]; then
                is_skipping_vrt=true
              fi
            fi
            echo "export IS_SKIPPING_VRT=$is_skipping_vrt" >> $BASH_ENV
2. スクリーンショットの取得
  • スクリーンショットは screenshots_〜 というブランチに保存しているのでチェックアウトして CircleCICache にZIP化をして保存した後、元のPRにチェックアウトして Cache から取り出しUNZIP化することでスクリーンショットを取得します
  • ZIP化しているのは、Cache の保存は基本的に使い回すためにありますが、今回は使い回すことがほとんどできないためなるべく容量を減らすためにしています
  • timestampを使用しているのは、CircleCICache は、キーの値が同じ場合は値が上書きされないことから、スクリーンショットを作成するときに付与することで必要なスクリーンショットを特定できるようにするためしています
config.yml
  get_screenshots:
    steps:
      - run:
          name: Save BASE_BRANCH_NAME to env
          command: |
            # ref: https://discuss.circleci.com/t/how-to-retrieve-a-pull-requests-base-branch-name-github/36911
            pr=$(echo https://api.github.com/repos/${CIRCLE_PULL_REQUEST:19} | sed "s/\/pull\//\/pulls\//")
            base=$(curl -s -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" $pr | jq '.base.ref')
            echo "export BASE_BRANCH_NAME=${base}" >> $BASH_ENV
      - run:
          name: Save IS_EXIST_SCREENSHOTS to env
          command: |
            is_exist=false
            if [ $IS_SKIPPING_VRT = "false" ]; then
              is_exist=$(echo -n "$(git fetch origin screenshots_$BASE_BRANCH_NAME && echo true || echo false)")
            fi
            echo "export IS_EXIST_SCREENSHOTS=${is_exist}" >> $BASH_ENV
      - run:
          name: Checkout screenshots branch
          command: |
            if [ $IS_EXIST_SCREENSHOTS = "true" ]; then
              git fetch origin screenshots_$BASE_BRANCH_NAME
              git checkout screenshots_$BASE_BRANCH_NAME
            fi
      - run:
          name: Save SCREENSHOTS_TIMESTAMP to env
          command: |
            if [ $IS_EXIST_SCREENSHOTS = "false" ]; then
              echo "no-op" > timestamp
            fi
            echo "export SCREENSHOTS_TIMESTAMP=$(echo -n $(cat timestamp))" >> $BASH_ENV
      - run:
          name: Zip screenshots
          command: |
            mkdir -p ./temp/zip
            if [ $IS_EXIST_SCREENSHOTS = "true" ]; then
              zip -r ./temp/zip/screenshots.zip ./app/build/outputs/roborazzi
            fi
      - save_cache:
          paths:
            - ./temp/zip
          key: screenshots-{{ checksum "timestamp" }}
      - run:
          name: Checkout pr branch
          command: git checkout $CIRCLE_BRANCH
      - run:
          name: Restore timestamp file
          command: echo $SCREENSHOTS_TIMESTAMP > timestamp
      - restore_cache:
          key: screenshots-{{ checksum "timestamp" }}
      - run:
          name: Unzip screenshots
          command: |
            unzip ./temp/zip/screenshots.zip || true
            rm -rf ./temp || true
3. ユニットテストの実行
  • PR変更時のビルドの時はRoborazziのcompareのオプションを使用することで比較画像を取得します
config.yml
  unit_test:
    parameters:
      build_variant:
        type: string
    steps:
      - run:
          name: Unit test
          command: |
            if [ $IS_SKIPPING_VRT = "false" ]; then
              roborazzi_option="-Proborazzi.test.compare=true"
            fi
            ./gradlew test<< parameters.build_variant >> $roborazzi_option --stacktrace
4. git の config 設定
  • こちらは CircleCIEnvironment Variables に設定する前提です
config.yml
  setting_git_config:
    steps:
      - run:
          name: Setting git config
          command: |
            git config --global user.name "$GITHUB_NAME"
            git config --global user.email "$GITHUB_EMAIL"
5. スクリーンショット用のブランチ ( screenshots_〜 )を作成しpush
  • 途中で、config.ymlを追加しているのは、ブランチ生成時に config.yml がないとCircleCIのログに警告が出てしまうのを抑えるためです
config.yml
  push_screenshots_branch:
    steps:
      - run:
          name: Push screenshots branch
          command: |
            git push origin --delete screenshots_$CIRCLE_BRANCH || true
          
            git checkout --orphan screenshots_$CIRCLE_BRANCH
            git rm --cached -rf .
          
            add_files=$(find . -type f -path "./app/build/outputs/roborazzi/*")
            for file in $add_files; do
              git add -f $file
            done
          
            echo $(date +%s) > timestamp
            git add -f timestamp
          
            echo -e "version: 2.1\njobs:\n no-op:\n  machine: true\n  steps:\n  - run: no-op\nworkflows:\n build:\n  jobs:\n   - no-op:\n      filters:\n       branches:\n        only: no-op" > .circleci/config.yml
            git add -f .circleci/config.yml
          
            git commit -m "Add screenshot"
            git clean -df
            git push origin HEAD:screenshots_$CIRCLE_BRANCH -f
6. 比較画像用のブランチ ( compare_〜 )を作成しpush
config.yml
  push_compare_branch:
    steps:
      - run:
          name: Push compare branch
          command: |
            if [ $IS_SKIPPING_VRT = "false" ]; then
              git push origin --delete compare_$CIRCLE_BRANCH || true
            
              fileSize=$(echo $(find ./app/build/outputs/roborazzi -type f | grep -e '.*_compare.png' | wc -l | sed -e 's/ //g'))
              if [ $fileSize -ne 0 ]; then
                git checkout --orphan compare_$CIRCLE_BRANCH
                git rm --cached -rf .
            
                add_files=$(find . -type f -path "./app/build/outputs/roborazzi/*" -name "*_compare.png")
                for file in $add_files; do
                  git add -f $file
                done
            
                echo -e "version: 2.1\njobs:\n no-op:\n  machine: true\n  steps:\n  - run: no-op\nworkflows:\n build:\n  jobs:\n   - no-op:\n      filters:\n       branches:\n        only: no-op" > .circleci/config.yml
                git add .circleci/config.yml
            
                git commit -m "Add screenshot diff"
                git clean -df
                git push origin HEAD:compare_$CIRCLE_BRANCH -f
              fi
            fi
7. 比較結果をコメント投稿(もしくは編集)
  • このコマンドでしかGitHub CLIを使わないのでここでインストールやログインを行っています
  • Snapshot diff reportが先頭についているコメントをPR内から探して、あった場合はそのコメントのIDを使用してコメントの編集をして、ない場合はコメント投稿をしています
  • コメントのテキストが長すぎると失敗するため、エラー回避としてエラー用のコメントを表示しています
config.yml
  comment_screenshot_diff:
    steps:
      - gh/install
      - run:
          name: Gh login
          command: echo "$GITHUB_ACCESS_TOKEN" | gh auth login --with-token
      - run:
          name: Create comment
          command: |
            echo "Snapshot diff report" > comment
            echo "| File name | Image |" >> comment
            echo "|-------|-------|" >> comment
            
            files=$(find . -type f -path "./app/build/outputs/roborazzi/*" -name "*_compare.png")
            for file in $files; do
              fileName=$(basename "$file" | sed -r 's/(.{20})/\1<br>/g')
              echo "| [$fileName](https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/blob/compare_$CIRCLE_BRANCH/$file) | ![](https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/blob/compare_$CIRCLE_BRANCH/$file?raw=true) |" >> comment
            done
      - run:
          name: Comment screenshot diff
          command: |
            if [ $IS_SKIPPING_VRT = "false" ]; then
              prNumber=$(echo $CIRCLE_PULL_REQUEST | sed "s:.*/::")
              url="https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${prNumber}/comments"
              commentId=$(echo -n $(curl -s -H "Authorization: token $GITHUB_ACCESS_TOKEN" $url -v | jq '.[] | select(.body | test("^Snapshot diff report.*")) | .id'))
              failedComment="$(echo -e "Snapshot diff report\n:warning: **Failed to show Snapshot diff**\n$CIRCLE_BUILD_URL")"
              
              if [ -n "$commentId" ]; then
                comment="$(cat ./comment)"
                endpoint="/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/comments/$commentId"
                gh api --method PATCH -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" $endpoint -f body="$comment" ||
                gh api --method PATCH -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" $endpoint -f body="$failedComment"
              else
                gh pr comment "$CIRCLE_PULL_REQUEST" -F ./comment || gh pr comment "$CIRCLE_PULL_REQUEST" -b "$failedComment"
              fi
            fi
8. 古くなったブランチを削除
  • parameters で どのブランチを削除するかと更新されない期間を指定して削除します
config.yml
  cleanup_old_branch:
    parameters:
      prefix:
        type: string
      maximum_seconds_past:
        type: integer
        default:  2592000 # 30 days
    steps:
      - run:
          name: Cleanup old branch
          command: |
            git branch -r --format="%(refname:lstrip=3)" | grep << parameters.prefix >> | while read -r branch; do
              last_commit_date_timestamp=$(git log -1 --format=%ct "origin/$branch")
              now_timestamp=$(date +%s)
              if [ $((now_timestamp - last_commit_date_timestamp)) -gt << parameters.maximum_seconds_past >> ]; then
                git push origin --delete "$branch"
              fi
            done || true
  • jobの設定
    • 以下のように上記で設定したコマンドを追加してください
      • × をつけた箇所はVRTとは関係なく説明不要かと思ったのでこの記事では説明しません
    • 以下の2つのProdDebug はビルドバリアントです
config.yml
jobs:
    unit_test:
    executor: android
    steps:
      - checkout # ×
      - check_is_pr
      - get_screenshots
      - set_locale_properties # ×
      - restore_and_save_gradle_cache # ×
      - unit_test:
          build_variant: ProdDebug
      - push_compare_branch
      - comment_screenshot_diff

  save_screenshots:
    executor: android
    steps:
      - checkout # ×
      - set_locale_properties # ×
      - restore_and_save_gradle_cache # ×
      - run:
          name: Create screenshots
          command: ./gradlew recordRoborazziProdDebug --stacktrace
      - push_screenshots_branch
      - cleanup_old_branch:
          prefix: compare_
      - cleanup_old_branch:
          prefix: screenshots_
          maximum_seconds_past: 15552000 # 180 days
  • workflowの設定
    • スクリーンショット保存はベースブランチとならなければ無駄になってしまうのでベースブランチの対象となるようなブランチのみに設定します
config.yml
workflows:
  test:
    jobs:
      - unit_test
      - save_screenshots:
          filters:
            branches:
              only:
                - main
                - develop

3. CircleCIGitHubの設定

以下の設定が必要になります

  1. write権限付きSSHキーを登録&設定
  2. GitHubで適切なアクセストークン作成&設定
  3. PRが作成された時にビルドが走るようにする
  4. Gitのconfigに設定するユーザーとEメール設定
1 write権限付きSSHキーを登録&設定

CircleCIからGitHubにアクセスする際使用するSSHキーがデフォルトだとread権限のみなのでpushさせるにはwrite権限が必要になります

  • circleci docs - Add additional SSH keys to CircleCIを参考にSSHキーを作成
  • 作成したSSHキーの秘密鍵CircleCIProject SettingsSSH KeysAdditional SSH Keysに登録してください(hostnameはGithub.com)
  • 作成したSSHキーの公開鍵をGitHubのプロジェクトの設定にあるDeploy keysAllow write accessのチェックをつけて設定(titleは任意項目です) 

元々設定しているread権限のSSHキーは削除が必要です

2 GitHubで適切なアクセストークン作成&設定
  • 以下の写真のようにrepowrite:orgread:orgを設定したパーソナルアクセストークンを作成
    image.png
  • 作成したパーソナルアクセストークンをCircleCIProject SettingsEnvironment VariablesGITHUB_ACCESS_TOKENという名前で登録
3 PRが作成された時にビルドが走るようにする

PRが作成される前にビルドが走るとPRのURLが取れずにCIが落ちます

  • CircleCIProject SettingsAdvanced SettingsOnly build pull requestsにチェックがついていない場合はチェックをつける
4 Gitのconfigに設定するユーザーとEメール設定
  • CircleCIProject SettingsEnvironment VariablesGITHUB_NAMEという名前でユーザー名を、GITHUB_EMAILという名前でEメールを登録

感想

試し始めた今年の年初からかなり長い時間かかりましたが、ある程度まともに動くVRTを作ることができたのではないかと思います。
この記事がたくさんの人の参考になったらかなり嬉しいです。
CircleCIを使用したAndroidのVRTの記事などは他に見たことがないので、改善点などがあったらすごく聞きたいです...

参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?