From 2be749b03a0cb3336060d0852f9decac6cf32065 Mon Sep 17 00:00:00 2001 From: takahirom Date: Sun, 17 Nov 2024 11:39:35 +0900 Subject: [PATCH] Separate roborazzi-accessibility-check module --- roborazzi-accessibility-check/.gitignore | 1 + roborazzi-accessibility-check/build.gradle | 44 ++++++++++ .../consumer-rules.pro | 0 .../proguard-rules.pro | 21 +++++ .../roborazzi/ExampleInstrumentedTest.kt | 22 +++++ .../src/main/AndroidManifest.xml | 4 + .../com/github/takahirom/roborazzi/ATF.kt | 47 ----------- .../roborazzi/ATFAccessibilityChecker.kt | 81 +++++++++++++++++++ .../takahirom/roborazzi/ExampleUnitTest.kt | 16 ++++ roborazzi-junit-rule/build.gradle | 1 - .../takahirom/roborazzi/RoborazziRule.kt | 54 +++++-------- sample-android/build.gradle | 1 + .../roborazzi/sample/ComposeA11yTest.kt | 6 +- settings.gradle | 1 + 14 files changed, 216 insertions(+), 83 deletions(-) create mode 100644 roborazzi-accessibility-check/.gitignore create mode 100644 roborazzi-accessibility-check/build.gradle create mode 100644 roborazzi-accessibility-check/consumer-rules.pro create mode 100644 roborazzi-accessibility-check/proguard-rules.pro create mode 100644 roborazzi-accessibility-check/src/androidTest/java/com/github/takahirom/roborazzi/ExampleInstrumentedTest.kt create mode 100644 roborazzi-accessibility-check/src/main/AndroidManifest.xml rename {roborazzi-junit-rule => roborazzi-accessibility-check}/src/main/java/com/github/takahirom/roborazzi/ATF.kt (58%) create mode 100644 roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATFAccessibilityChecker.kt create mode 100644 roborazzi-accessibility-check/src/test/java/com/github/takahirom/roborazzi/ExampleUnitTest.kt diff --git a/roborazzi-accessibility-check/.gitignore b/roborazzi-accessibility-check/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/roborazzi-accessibility-check/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/roborazzi-accessibility-check/build.gradle b/roborazzi-accessibility-check/build.gradle new file mode 100644 index 000000000..1c737358b --- /dev/null +++ b/roborazzi-accessibility-check/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} +if (System.getenv("INTEGRATION_TEST") != "true") { + pluginManager.apply("com.vanniktech.maven.publish") +} + + +android { + namespace 'com.github.takahirom.roborazzi.accessibility.check' + compileSdk 34 + + defaultConfig { + minSdk 21 + targetSdk 32 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + buildFeatures { + } + testOptions { + unitTests { + includeAndroidResources = true + } + } +} + +dependencies { + implementation project(':roborazzi-junit-rule') + implementation project(':roborazzi') + implementation libs.androidx.test.ext.junit.ktx + compileOnly libs.robolectric + compileOnly libs.androidx.compose.ui.test + compileOnly libs.androidx.compose.ui.test.junit4 + api libs.accessibility.test.framework +} \ No newline at end of file diff --git a/roborazzi-accessibility-check/consumer-rules.pro b/roborazzi-accessibility-check/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/roborazzi-accessibility-check/proguard-rules.pro b/roborazzi-accessibility-check/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/roborazzi-accessibility-check/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/roborazzi-accessibility-check/src/androidTest/java/com/github/takahirom/roborazzi/ExampleInstrumentedTest.kt b/roborazzi-accessibility-check/src/androidTest/java/com/github/takahirom/roborazzi/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..f06fdc650 --- /dev/null +++ b/roborazzi-accessibility-check/src/androidTest/java/com/github/takahirom/roborazzi/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.github.takahirom.roborazzi + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.github.takahirom.roborazzi.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/roborazzi-accessibility-check/src/main/AndroidManifest.xml b/roborazzi-accessibility-check/src/main/AndroidManifest.xml new file mode 100644 index 000000000..44008a433 --- /dev/null +++ b/roborazzi-accessibility-check/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/ATF.kt b/roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATF.kt similarity index 58% rename from roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/ATF.kt rename to roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATF.kt index a07ea84b0..95b9c448d 100644 --- a/roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/ATF.kt +++ b/roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATF.kt @@ -1,10 +1,7 @@ package com.github.takahirom.roborazzi -import android.annotation.SuppressLint import android.view.View import androidx.annotation.RequiresApi -import androidx.compose.ui.platform.ViewRootForTest -import com.github.takahirom.roborazzi.RoborazziRule.ATFAccessibilityChecker import com.github.takahirom.roborazzi.RoborazziRule.CaptureRoot import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType @@ -12,55 +9,11 @@ import com.google.android.apps.common.testing.accessibility.framework.Accessibil import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult import com.google.android.apps.common.testing.accessibility.framework.Parameters import com.google.android.apps.common.testing.accessibility.framework.ViewChecker -import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityViewCheckException import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.BitmapImage import org.hamcrest.Matcher import org.hamcrest.Matchers -import org.robolectric.shadows.ShadowBuild -@SuppressLint("VisibleForTests") -@RequiresApi(34) -internal fun ATFAccessibilityChecker.runAccessibilityChecks( - captureRoot: CaptureRoot, - roborazziOptions: RoborazziOptions, -) { - // TODO remove this once ATF doesn't bail out - // https://github.com/google/Accessibility-Test-Framework-for-Android/blob/c65cab02b2a845c29c3da100d6adefd345a144e3/src/main/java/com/google/android/apps/common/testing/accessibility/framework/uielement/AccessibilityHierarchyAndroid.java#L667 - ShadowBuild.setFingerprint("roborazzi") - - if (captureRoot is CaptureRoot.Compose) { - val view = (captureRoot.semanticsNodeInteraction.fetchSemanticsNode().root as ViewRootForTest).view.rootView - - // Will throw based on configuration - val results = runAllChecks(roborazziOptions, view, captureRoot) - - // Report on any warnings in the log output if not failing - results.forEach { check -> - when (check.type) { - AccessibilityCheckResultType.ERROR -> roborazziErrorLog("Error: $check") - AccessibilityCheckResultType.WARNING -> roborazziErrorLog( - "Warning: $check" - ) - - AccessibilityCheckResultType.INFO -> roborazziReportLog( - "Info: $check" - ) - - else -> {} - } - } - - val failures = results.filter { it.type.ordinal <= failureLevel.ordinal } - if (failures.isNotEmpty()) { - throw AccessibilityViewCheckException(failures.toMutableList()) - } - - // TODO handle View cases -// } else if (captureRoot is CaptureRoot.View) { - } -} - @RequiresApi(34) internal fun ATFAccessibilityChecker.runAllChecks( roborazziOptions: RoborazziOptions, diff --git a/roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATFAccessibilityChecker.kt b/roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATFAccessibilityChecker.kt new file mode 100644 index 000000000..39031d821 --- /dev/null +++ b/roborazzi-accessibility-check/src/main/java/com/github/takahirom/roborazzi/ATFAccessibilityChecker.kt @@ -0,0 +1,81 @@ +package com.github.takahirom.roborazzi + +import android.annotation.SuppressLint +import android.os.Build +import androidx.compose.ui.platform.ViewRootForTest +import com.github.takahirom.roborazzi.RoborazziRule.CaptureRoot +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult +import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityViewCheckException +import org.hamcrest.Matcher +import org.robolectric.shadows.ShadowBuild + +@ExperimentalRoborazziApi +data class ATFAccessibilityChecker( + val checks: Set, + val suppressions: Matcher, + val failureLevel: AccessibilityCheckResultType, +) { + @SuppressLint("VisibleForTests") + fun runAccessibilityChecks( + captureRoot: CaptureRoot, + roborazziOptions: RoborazziOptions, + ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + roborazziErrorLog("Skipping accessibilityChecks on API " + Build.VERSION.SDK_INT + "(< ${Build.VERSION_CODES.UPSIDE_DOWN_CAKE})") + return + } + // TODO remove this once ATF doesn't bail out + // https://github.com/google/Accessibility-Test-Framework-for-Android/blob/c65cab02b2a845c29c3da100d6adefd345a144e3/src/main/java/com/google/android/apps/common/testing/accessibility/framework/uielement/AccessibilityHierarchyAndroid.java#L667 + ShadowBuild.setFingerprint("roborazzi") + + if (captureRoot is CaptureRoot.Compose) { + val view = + (captureRoot.semanticsNodeInteraction.fetchSemanticsNode().root as ViewRootForTest).view.rootView + + // Will throw based on configuration + val results = runAllChecks(roborazziOptions, view, captureRoot) + + // Report on any warnings in the log output if not failing + results.forEach { check -> + when (check.type) { + AccessibilityCheckResultType.ERROR -> roborazziErrorLog("Error: $check") + AccessibilityCheckResultType.WARNING -> roborazziErrorLog( + "Warning: $check" + ) + + AccessibilityCheckResultType.INFO -> roborazziReportLog( + "Info: $check" + ) + + else -> {} + } + } + + val failures = results.filter { it.type.ordinal <= failureLevel.ordinal } + if (failures.isNotEmpty()) { + throw AccessibilityViewCheckException(failures.toMutableList()) + } + + // TODO handle View cases +// } else if (captureRoot is CaptureRoot.View) { + } + } + + companion object +} + +data class AccessibilityChecksValidate( + val checker: ATFAccessibilityChecker +) : RoborazziRule.AccessibilityChecks { + override fun runAccessibilityChecks( + captureRoot: CaptureRoot, + roborazziOptions: RoborazziOptions + ) { + checker.runAccessibilityChecks( + captureRoot = captureRoot, + roborazziOptions = roborazziOptions, + ) + } +} diff --git a/roborazzi-accessibility-check/src/test/java/com/github/takahirom/roborazzi/ExampleUnitTest.kt b/roborazzi-accessibility-check/src/test/java/com/github/takahirom/roborazzi/ExampleUnitTest.kt new file mode 100644 index 000000000..426caaf20 --- /dev/null +++ b/roborazzi-accessibility-check/src/test/java/com/github/takahirom/roborazzi/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.github.takahirom.roborazzi + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/roborazzi-junit-rule/build.gradle b/roborazzi-junit-rule/build.gradle index 199055d78..7d453d29a 100644 --- a/roborazzi-junit-rule/build.gradle +++ b/roborazzi-junit-rule/build.gradle @@ -40,7 +40,6 @@ dependencies { implementation project(':roborazzi') implementation libs.androidx.test.ext.junit.ktx compileOnly libs.robolectric - compileOnly libs.androidx.test.ext.junit.ktx compileOnly libs.androidx.compose.ui.test compileOnly libs.androidx.compose.ui.test.junit4 api libs.accessibility.test.framework diff --git a/roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/RoborazziRule.kt b/roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/RoborazziRule.kt index fdae76987..f2d662346 100644 --- a/roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/RoborazziRule.kt +++ b/roborazzi-junit-rule/src/main/java/com/github/takahirom/roborazzi/RoborazziRule.kt @@ -1,13 +1,8 @@ package com.github.takahirom.roborazzi -import android.os.Build import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.test.espresso.ViewInteraction -import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType -import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck -import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult -import org.hamcrest.Matcher import org.junit.rules.TestWatcher import org.junit.runner.Description import org.junit.runners.model.Statement @@ -65,21 +60,20 @@ class RoborazziRule private constructor( ) @ExperimentalRoborazziApi - data class ATFAccessibilityChecker( - val checks: Set, - val suppressions: Matcher, - val failureLevel: AccessibilityCheckResultType, - ) { - companion object - } - - @ExperimentalRoborazziApi - sealed interface AccessibilityChecks { - data class Validate( - val checker: ATFAccessibilityChecker - ) : AccessibilityChecks - - data object Disabled : AccessibilityChecks + interface AccessibilityChecks { + fun runAccessibilityChecks( + captureRoot: CaptureRoot, + roborazziOptions: RoborazziOptions, + ) + // Use `roborazzi-accessibility-check`'s AccessibilityChecksValidate + data object Disabled : AccessibilityChecks { + override fun runAccessibilityChecks( + captureRoot: CaptureRoot, + roborazziOptions: RoborazziOptions + ) { + // Do nothing + } + } } sealed interface CaptureType { @@ -119,7 +113,8 @@ class RoborazziRule private constructor( ) : CaptureType } - internal sealed interface CaptureRoot { + @InternalRoborazziApi + sealed interface CaptureRoot { object None : CaptureRoot class Compose( val composeRule: ComposeTestRule, @@ -183,21 +178,16 @@ class RoborazziRule private constructor( ) { val evaluate: () -> Unit = { try { - val accessibilityValidator = ((options.accessibilityChecks as? AccessibilityChecks.Validate)?.checker) + val accessibilityChecks = options.accessibilityChecks // TODO enable a11y before showing content base.evaluate() - if (accessibilityValidator != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - accessibilityValidator.runAccessibilityChecks( - captureRoot = captureRoot, - roborazziOptions = options.roborazziOptions - ) - } else { - roborazziErrorLog("Skipping accessibilityChecks on API " + Build.VERSION.SDK_INT + "(< ${Build.VERSION_CODES.UPSIDE_DOWN_CAKE})") - } - } + accessibilityChecks.runAccessibilityChecks( + captureRoot = captureRoot, + roborazziOptions = options.roborazziOptions + ) + } catch (e: Exception) { throw e } diff --git a/sample-android/build.gradle b/sample-android/build.gradle index 6a967196a..7f82c8842 100644 --- a/sample-android/build.gradle +++ b/sample-android/build.gradle @@ -64,6 +64,7 @@ dependencies { testImplementation(project(":roborazzi-ai-openai")) testImplementation project(":roborazzi-compose") testImplementation project(":roborazzi-junit-rule") + testImplementation project(":roborazzi-accessibility-check") implementation libs.androidx.compose.material3 implementation libs.androidx.compose.ui diff --git a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComposeA11yTest.kt b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComposeA11yTest.kt index 4a7d89708..d5aebf4e2 100644 --- a/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComposeA11yTest.kt +++ b/sample-android/src/test/java/com/github/takahirom/roborazzi/sample/ComposeA11yTest.kt @@ -20,12 +20,12 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.ATFAccessibilityChecker +import com.github.takahirom.roborazzi.AccessibilityChecksValidate import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziOptions.RecordOptions import com.github.takahirom.roborazzi.RoborazziRule -import com.github.takahirom.roborazzi.RoborazziRule.ATFAccessibilityChecker -import com.github.takahirom.roborazzi.RoborazziRule.AccessibilityChecks import com.github.takahirom.roborazzi.RoborazziRule.CaptureType import com.github.takahirom.roborazzi.RoborazziRule.Options import com.github.takahirom.roborazzi.atf @@ -67,7 +67,7 @@ class ComposeA11yTest { applyDeviceCrop = true ), ), - accessibilityChecks = AccessibilityChecks.Validate( + accessibilityChecks = AccessibilityChecksValidate( checker = atfAccessibilityChecker ) ) diff --git a/settings.gradle b/settings.gradle index 76a15c9b9..9ce4c11f6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,7 @@ dependencyResolutionManagement { rootProject.name = "roborazzi-root" include ':roborazzi' include ':roborazzi-junit-rule' +include ':roborazzi-accessibility-check' include ':roborazzi-compose-desktop' include ':roborazzi-compose-ios' include ':roborazzi-compose'