はじめに
2023年3月からAndroidアプリエンジニアをしている KSND(GitHub、X )といいます。
以前自分が書いた以下の記事では AWS S3
を使っていましたが、AWS S3
を使わなくて済むようにするなど改善点がたくさんあり書き直したいと思ったので今回この記事を書きました。
想定読者
-
Jetpack Compose
を使っているAndroidアプリのプロジェクトにVRT
(画像回帰テスト) を導入したい方 -
VRT
導入にあたってGitHub Actions
ではなく、CircleCI
を使わないといけない何かしらの理由のある方
実際に試した際の比較画像
VRT試したレボジトリ
(よかったらスターいただけるとかなり嬉しいです...)
使用したライブラリやサービス
Circle CI
Roborazzi
Showkase
GitHub CLI
VRTの大まかな流れ
大きくは PR変更時のビルド
、マージ後のビルド
で処理が分かれます。
PR変更時のビルド
- VRT対象かを確認
- スクリーンショットを
screenshots_〜
ブランチからCircleCI
のCache
を使用して取得 - Roborazziの比較用のオプションを使用して、ユニットテスト実行
- 作成された比較画像のみにし
compare_〜
ブランチを作成してGitHubにpush - 比較結果をコメント投稿(もしくは編集)
マージ後のビルド
- Roborazziのスクリーンショット作成用のコマンドを実行
- 作成されたスクリーンショットのみにし
screenshots_〜
ブランチを作成してGitHubにpush - 古くなったVRT用のブランチを削除
VRT導入の手順
以下の流れで説明します
1. Roborazzi
、Showkase
をプロジェクトに導入
- Roborazzi の README にある Build setup の箇所に記載されている通り依存関係を設定
- Showkase の README にある Instaallation の箇所に記載されている通り依存関係を設定
- appモジュールにShowkaseのルートモジュールを実装したクラスを作成(例:
app/src/main/java/ksnd/hiraganaconverter/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 状態になりますが、ビルドすれば解消されます
// 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
(以下手順です)
-
orbs
にGitHub CLI
を追加
orbs:
gh: circleci/github-cli@2.3.0
- commandsに以下 8つ のコマンド追加
1. VRT対象かチェック
- 以下いずれかの場合にVRTをスキップします
- 最後のコミットメッセージに
[skip vrt]
が含まれている -
CIRCLE_PULL_REQUEST
が空の時 = PRがなくコメント表示できない - PRに
skip vrt
ラベルが設定されている
- 最後のコミットメッセージに
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_〜
というブランチに保存しているのでチェックアウトしてCircleCI
のCache
にZIP化をして保存した後、元のPRにチェックアウトしてCache
から取り出しUNZIP化することでスクリーンショットを取得します - ZIP化しているのは、
Cache
の保存は基本的に使い回すためにありますが、今回は使い回すことがほとんどできないためなるべく容量を減らすためにしています -
timestamp
を使用しているのは、CircleCI
のCache
は、キーの値が同じ場合は値が上書きされないことから、スクリーンショットを作成するときに付与することで必要なスクリーンショットを特定できるようにするためしています
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のオプションを使用することで比較画像を取得します
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 設定
- こちらは
CircleCI
のEnvironment Variables
に設定する前提です
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のログに警告が出てしまうのを抑えるためです
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
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を使用してコメントの編集をして、ない場合はコメント投稿をしています - コメントのテキストが長すぎると失敗するため、エラー回避としてエラー用のコメントを表示しています
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
で どのブランチを削除するかと更新されない期間を指定して削除します
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
はビルドバリアントです
- 以下のように上記で設定したコマンドを追加してください
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の設定
- スクリーンショット保存はベースブランチとならなければ無駄になってしまうのでベースブランチの対象となるようなブランチのみに設定します
workflows:
test:
jobs:
- unit_test
- save_screenshots:
filters:
branches:
only:
- main
- develop
3. CircleCI
、GitHub
の設定
以下の設定が必要になります
1 write権限付きSSHキーを登録&設定
CircleCIからGitHubにアクセスする際使用するSSHキーがデフォルトだとread権限のみなのでpushさせるにはwrite権限が必要になります
- circleci docs - Add additional SSH keys to CircleCIを参考にSSHキーを作成
- 作成したSSHキーの秘密鍵を
CircleCI
のProject Settings
のSSH Keys
のAdditional SSH Keys
に登録してください(hostnameはGithub.com
) - 作成したSSHキーの公開鍵をGitHubのプロジェクトの設定にある
Deploy keys
にAllow write access
のチェックをつけて設定(titleは任意項目です)
元々設定しているread権限のSSHキーは削除が必要です
2 GitHubで適切なアクセストークン作成&設定
- 以下の写真のように
repo
、write:org
、read:org
を設定したパーソナルアクセストークンを作成
- 作成したパーソナルアクセストークンを
CircleCI
のProject Settings
のEnvironment Variables
にGITHUB_ACCESS_TOKEN
という名前で登録
3 PRが作成された時にビルドが走るようにする
PRが作成される前にビルドが走るとPRのURLが取れずにCIが落ちます
-
CircleCI
のProject Settings
のAdvanced Settings
のOnly build pull requests
にチェックがついていない場合はチェックをつける
4 Gitのconfigに設定するユーザーとEメール設定
-
CircleCI
のProject Settings
のEnvironment Variables
にGITHUB_NAME
という名前でユーザー名を、GITHUB_EMAIL
という名前でEメールを登録
感想
試し始めた今年の年初からかなり長い時間かかりましたが、ある程度まともに動くVRTを作ることができたのではないかと思います。
この記事がたくさんの人の参考になったらかなり嬉しいです。
CircleCIを使用したAndroidのVRTの記事などは他に見たことがないので、改善点などがあったらすごく聞きたいです...
参考
- Bash move * to subfolder fail: cannot move to a subdirectory of itself
- circleci Discuss - How to Retrieve a Pull Request’s Base Branch Name [GitHub]
- DevelopersIO - [GitHub Actions] 指定のBranchの存在チェックをする(git-fetchコマンド)
- GitHub - DroidKaigi/conference-app-2023
- GitHub - takahirom/roborazzi-compare-on-github-comment-sample
- スタディサプリ小学・中学講座にRoborazziを導入して半年が経過しました