Android截图测试CI与本地模拟器不一致,如何配置相同模拟器?
问题:CI环境截图测试与本地不一致,如何对齐模拟器配置?
我的GitHub Action配置
name: Test and check build on: push: branches: ["main", "dev"] pull_request: branches: ["main", "dev", "hotfix"] jobs: build: runs-on: macos-latest if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 with: fetch-depth: "0" - name: set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Run Unit test run: ./gradlew testDebugUnitTest - name: Run connected tests uses: ReactiveCircus/android-emulator-runner@v2 with: api-level: 29 target: default arch: x86_64 profile: Nexus 6 script: ./gradlew connectedCheck - name: Build run: ./gradlew :app:assembleDebug
截图测试代码
/** * Simple on-device screenshot comparator that uses golden images present in * `androidTest/assets`. * * Minimum SDK is O. Densities between devices must match. * * Screenshots are saved on device in `/data/data/{package}/files/${BuildConfig.SCREENSHOTS_FOLDER}. */ @RequiresApi(Build.VERSION_CODES.O) fun assertScreenshotMatchesGolden( goldenName: String, node: SemanticsNodeInteraction ) { val bitmap = node.captureToImage().asAndroidBitmap() // Save screenshot to file for debugging saveScreenshot(goldenName, bitmap) try { InstrumentationRegistry.getInstrumentation() .context.resources.assets.open("$goldenName.png") } catch (ex: Exception) { null }?.use { BitmapFactory.decodeStream(it) }?.compare(bitmap) } private fun saveScreenshot(filename: String, bmp: Bitmap) { val canonicalPath = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.canonicalPath val path = "$canonicalPath/${BuildConfig.SCREENSHOTS_FOLDER}" val folder = File(path) if (!folder.exists()) { folder.mkdirs() } FileOutputStream("$path/$filename.png").use { out -> bmp.compress(Bitmap.CompressFormat.PNG, 100, out) } println("Saved screenshot to $path/$filename.png") } private fun Bitmap.compare(other: Bitmap) { if (this.width != other.width || this.height != other.height) { throw AssertionError("Size of screenshot does not match golden file (check device density)") } // Compare row by row to save memory on device val row1 = IntArray(width) val row2 = IntArray(width) for (column in 0 until height) { // Read one row per bitmap and compare this.getRow(row1, column) other.getRow(row2, column) if (!row1.contentEquals(row2)) { throw AssertionError("Sizes match but bitmap content has differences") } } } private fun Bitmap.getRow(pixels: IntArray, column: Int) { this.getPixels(pixels, 0, width, 0, column, width, 1) }
问题现象
Sizes match but bitmap content has differences
本地使用API 29的Nexus 6模拟器运行截图测试完全正常:先运行一次生成截图并复制到assets文件夹,再次运行测试即可通过。但CI环境执行时出现上述错误,推测是本地和CI的模拟器配置存在差异。
解决方案
- 锁定系统镜像版本:当前
target: default会拉取API 29的最新镜像,本地可能是旧版本,导致渲染差异。先在Android Studio的SDK Manager里查看本地API 29模拟器的镜像ID(比如system-images;android-29;google_apis;x86_64),然后在android-emulator-runner参数里添加system-image: [你的本地镜像ID],强制CI使用和本地完全一致的系统镜像。 - 统一显示参数:
- 添加
skin: nexus_6,确保模拟器皮肤和本地的Nexus 6完全匹配; - 用
orientation: portrait或landscape固定屏幕方向,避免本地和CI默认方向不同; - 加上
density: 480(Nexus 6的标准密度),强制指定屏幕密度,消除密度差异带来的渲染变化。
- 添加
- 禁用动画:CI模拟器的动画效果可能和本地不同,导致截图像素差异。在测试脚本前添加三条禁用动画的adb命令,确保界面渲染完全一致:
adb shell settings put global window_animation_scale 0.0 adb shell settings put global transition_animation_scale 0.0 adb shell settings put global animator_duration_scale 0.0 - 验证架构一致性:确认本地模拟器的架构是x86_64,和CI配置里的
arch: x86_64保持一致,避免不同架构的渲染差异。
修改后的GitHub Action配置示例
name: Test and check build on: push: branches: ["main", "dev"] pull_request: branches: ["main", "dev", "hotfix"] jobs: build: runs-on: macos-latest if: github.event.pull_request.draft == false steps: - uses: actions/checkout@v3 with: fetch-depth: "0" - name: set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Run Unit test run: ./gradlew testDebugUnitTest - name: Run connected tests uses: ReactiveCircus/android-emulator-runner@v2 with: api-level: 29 target: google_apis # 和本地镜像的target一致,比如default或google_apis arch: x86_64 profile: Nexus 6 skin: nexus_6 density: 480 orientation: portrait system-image: system-images;android-29;google_apis;x86_64 # 替换成本地实际的镜像ID script: | adb shell settings put global window_animation_scale 0.0 adb shell settings put global transition_animation_scale 0.0 adb shell settings put global animator_duration_scale 0.0 ./gradlew connectedCheck - name: Build run: ./gradlew :app:assembleDebug
内容的提问来源于stack exchange,提问作者1048576
相关产品推荐
相关产品推荐

