From 9adf5ad13ff0e2613cada2004970601673b57ed5 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 24 Feb 2024 19:00:01 +0000 Subject: [PATCH] Add Fix workflow (#6) Add 2% tolerance to screenshot testing for subtle differences between Linux and macOS Add a workflow to update the screenshots from GitHub Actions --- .github/workflows/build_workflow.main.kts | 30 ++++++++++- .github/workflows/build_workflow.yaml | 16 ++++++ .github/workflows/fix_workflow.main.kts | 50 +++++++++++++++++++ .github/workflows/fix_workflow.yaml | 48 ++++++++++++++++++ shared/build.gradle.kts | 1 + .../sample/watchfaces/ClockScreenshotTest.kt | 39 ++++++++++++--- 6 files changed, 174 insertions(+), 10 deletions(-) create mode 100755 .github/workflows/fix_workflow.main.kts create mode 100644 .github/workflows/fix_workflow.yaml diff --git a/.github/workflows/build_workflow.main.kts b/.github/workflows/build_workflow.main.kts index a5ea783..089bdbd 100755 --- a/.github/workflows/build_workflow.main.kts +++ b/.github/workflows/build_workflow.main.kts @@ -4,10 +4,12 @@ import io.github.typesafegithub.workflows.actions.actions.CheckoutV4 import io.github.typesafegithub.workflows.actions.actions.SetupJavaV4 +import io.github.typesafegithub.workflows.actions.actions.UploadArtifactV4 import io.github.typesafegithub.workflows.actions.gradle.GradleBuildActionV3 import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest import io.github.typesafegithub.workflows.domain.triggers.PullRequest import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.expressions.expr import io.github.typesafegithub.workflows.dsl.workflow import io.github.typesafegithub.workflows.yaml.writeToFile @@ -19,11 +21,17 @@ workflow( ), sourceFile = __FILE__.toPath(), ) { - job(id = "build-and-test", runsOn = UbuntuLatest) { + job( + id = "build-and-test", + runsOn = UbuntuLatest + ) { uses(name = "Check out", action = CheckoutV4()) uses( name = "Setup Java", - action = SetupJavaV4(distribution = SetupJavaV4.Distribution.Temurin, javaVersion = "17") + action = SetupJavaV4( + distribution = SetupJavaV4.Distribution.Temurin, + javaVersion = "17" + ) ) uses( name = "Build", @@ -31,5 +39,23 @@ workflow( arguments = "test", ) ) + uses( + name = "Upload reports", + action = UploadArtifactV4( + name = "Roborazzi", + path = listOf("shared/build/outputs/roborazzi"), + retentionDays = UploadArtifactV4.RetentionPeriod.Default, + ), + `if` = expr { always() } + ) + uses( + name = "Upload test reports", + action = UploadArtifactV4( + name = "Junit", + path = listOf("**/build/reports/tests"), + retentionDays = UploadArtifactV4.RetentionPeriod.Default, + ), + `if` = expr { always() } + ) } }.writeToFile() diff --git a/.github/workflows/build_workflow.yaml b/.github/workflows/build_workflow.yaml index eae7377..5a3bb48 100644 --- a/.github/workflows/build_workflow.yaml +++ b/.github/workflows/build_workflow.yaml @@ -41,3 +41,19 @@ jobs: uses: 'gradle/gradle-build-action@v3' with: arguments: 'test' + - id: 'step-3' + name: 'Upload reports' + uses: 'actions/upload-artifact@v4' + with: + name: 'Roborazzi' + path: 'shared/build/outputs/roborazzi' + retention-days: '0' + if: '${{ always() }}' + - id: 'step-4' + name: 'Upload test reports' + uses: 'actions/upload-artifact@v4' + with: + name: 'Junit' + path: '**/build/reports/tests' + retention-days: '0' + if: '${{ always() }}' diff --git a/.github/workflows/fix_workflow.main.kts b/.github/workflows/fix_workflow.main.kts new file mode 100755 index 0000000..05bb178 --- /dev/null +++ b/.github/workflows/fix_workflow.main.kts @@ -0,0 +1,50 @@ +#!/usr/bin/env kotlin + +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:1.11.0") + +import io.github.typesafegithub.workflows.actions.actions.CheckoutV4 +import io.github.typesafegithub.workflows.actions.actions.SetupJavaV4 +import io.github.typesafegithub.workflows.actions.gradle.GradleBuildActionV3 +import io.github.typesafegithub.workflows.actions.stefanzweifel.GitAutoCommitActionV5 +import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest +import io.github.typesafegithub.workflows.domain.triggers.WorkflowDispatch +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow +import io.github.typesafegithub.workflows.yaml.writeToFile + +workflow( + name = "Fix workflow", + on = listOf( + WorkflowDispatch(), + ), + sourceFile = __FILE__.toPath(), +) { + job( + id = "fix-branch", + runsOn = UbuntuLatest, + // Ensure no auto-commits are ever made on the main/default branch. + // This is an extra security measure, especially since we don't control what the + // "stefanzweifel/git-auto-commit-action@v5" GitHub Action could theoretically do. + `if` = expr { github.ref_name + " != " + github.eventWorkflowDispatch.repository.default_branch } + ) { + uses(name = "Check out", action = CheckoutV4()) + uses( + name = "Setup Java", + action = SetupJavaV4(distribution = SetupJavaV4.Distribution.Temurin, javaVersion = "17") + ) + uses( + name = "Record Screenshots", + action = GradleBuildActionV3( + arguments = "verifyAndRecordRoborazziDebug", + ) + ) + uses( + name = "Commit Screenshots", + action = GitAutoCommitActionV5( + filePattern = "**/src/test/screenshots/*.png", + disableGlobbing = true, + commitMessage = "🤖 Updates screenshots" + ) + ) + } +}.writeToFile() diff --git a/.github/workflows/fix_workflow.yaml b/.github/workflows/fix_workflow.yaml new file mode 100644 index 0000000..f0ec29e --- /dev/null +++ b/.github/workflows/fix_workflow.yaml @@ -0,0 +1,48 @@ +# This file was generated using Kotlin DSL (.github/workflows/fix_workflow.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt + +name: 'Fix workflow' +on: + workflow_dispatch: {} +jobs: + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/fix_workflow.yaml'' && ''.github/workflows/fix_workflow.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/fix_workflow.yaml''' + fix-branch: + runs-on: 'ubuntu-latest' + needs: + - 'check_yaml_consistency' + if: '${{ github.ref_name != github.event.repository.default_branch }}' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Setup Java' + uses: 'actions/setup-java@v4' + with: + java-version: '17' + distribution: 'temurin' + - id: 'step-2' + name: 'Record Screenshots' + uses: 'gradle/gradle-build-action@v3' + with: + arguments: 'verifyAndRecordRoborazziDebug' + - id: 'step-3' + name: 'Commit Screenshots' + uses: 'stefanzweifel/git-auto-commit-action@v5' + with: + commit_message: '🤖 Updates screenshots' + file_pattern: '**/src/test/screenshots/*.png' + disable_globbing: 'true' diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index c9e1419..5176fb7 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -17,6 +17,7 @@ android { } buildFeatures { compose = true + buildConfig = true } composeOptions { kotlinCompilerExtensionVersion = versionFor(AndroidX.compose.compiler) diff --git a/shared/src/test/kotlin/org/splitties/compose/oclock/sample/watchfaces/ClockScreenshotTest.kt b/shared/src/test/kotlin/org/splitties/compose/oclock/sample/watchfaces/ClockScreenshotTest.kt index 1f1c669..47638a0 100644 --- a/shared/src/test/kotlin/org/splitties/compose/oclock/sample/watchfaces/ClockScreenshotTest.kt +++ b/shared/src/test/kotlin/org/splitties/compose/oclock/sample/watchfaces/ClockScreenshotTest.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalRoborazziApi::class) + package org.splitties.compose.oclock.sample.watchfaces import androidx.compose.foundation.layout.fillMaxSize @@ -5,20 +7,22 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot -import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.ExperimentalRoborazziApi +import com.github.takahirom.roborazzi.RoborazziOptions +import com.github.takahirom.roborazzi.ThresholdValidator import com.github.takahirom.roborazzi.captureRoboImage +import com.louiscad.composeoclockplayground.shared.BuildConfig import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Assume import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.ParameterizedRobolectricTestRunner +import org.junit.rules.TestRule import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import org.splitties.compose.oclock.OClockRootCanvas +import java.io.File @Config( sdk = [33], @@ -26,19 +30,38 @@ import org.splitties.compose.oclock.OClockRootCanvas ) @GraphicsMode(GraphicsMode.Mode.NATIVE) abstract class ClockScreenshotTest { - @get:Rule + @get:Rule(order = 0) + val debugOnly = TestRule { base, _ -> + Assume.assumeTrue("Screenshot tests not supported for release", BuildConfig.DEBUG) + + base + } + + @get:Rule(order = 1) val composeRule = createComposeRule() abstract val device: WearDevice + // generous to allow for mac/linux differences + open val tolerance = 0.02f + + open val roborazziOptions: RoborazziOptions + get() = RoborazziOptions( + compareOptions = RoborazziOptions.CompareOptions( + resultValidator = ThresholdValidator(tolerance) + ) + ) + @Before fun check() { - // Robolectric RNG not supported on Windows // https://github.com/robolectric/robolectric/issues/8312 - assumeFalse(System.getProperty("os.name")?.startsWith("Windows") ?: false) + assumeFalse("Robolectric RNG not supported on Windows", System.getProperty("os.name")?.startsWith("Windows") ?: false) } fun runTest(isAmbient: Boolean = false, clock: @Composable () -> Unit) { + val filePath = + File("src/test/screenshots/${this.javaClass.simpleName}_${device.id}${if (isAmbient) "_ambient" else ""}.png") + RuntimeEnvironment.setQualifiers("+w${device.dp}dp-h${device.dp}dp") composeRule.setContent { @@ -50,6 +73,6 @@ abstract class ClockScreenshotTest { } composeRule.onRoot() - .captureRoboImage("src/test/screenshots/${this.javaClass.simpleName}_${device.id}${if (isAmbient) "_ambient" else ""}.png") + .captureRoboImage(filePath = filePath.path, roborazziOptions = roborazziOptions) } }