Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds ATF Accessibility checks to roboscreenshots #2448

Merged
merged 14 commits into from
Nov 7, 2024
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions datalayer/phone-ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ dependencies {
implementation(libs.compose.material3)
implementation(libs.material)
implementation(platform(libs.compose.bom))
implementation(libs.compose.material.iconscore)
implementation(libs.compose.material.iconsext)

testImplementation(libs.junit)

Expand Down
2 changes: 1 addition & 1 deletion datalayer/watch/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ package com.google.android.horologist.datalayer.watch {
method public suspend Object? markSetupNoLongerComplete(kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @Deprecated public suspend Object? markTileAsInstalled(String tileName, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @Deprecated public suspend Object? markTileAsRemoved(String tileName, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @CheckResult public suspend Object? startCompanion(String nodeId, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult public suspend Object? startCompanion(String nodeId, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
method public suspend Object? updateInstalledTiles(kotlin.coroutines.Continuation<? super kotlin.Unit>);
property public kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> connectedAndInstalledNodes;
property public final kotlinx.coroutines.flow.Flow<com.google.android.horologist.data.SurfacesInfo> surfacesInfo;
Expand Down
8 changes: 5 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[versions]
accompanist = "0.36.0"
androidx-benchmark = "1.3.3"
accessibilityTestFramework = "4.1.1"
androidx-complications-data = "1.2.1"
androidx-concurrent = "1.2.0"
androidx-constraintlayout-compose = "1.1.0"
Expand All @@ -13,8 +14,8 @@ androidx-test-ext = "1.2.1"
androidx-test-runner = "1.6.2"
androidx-wear-watchface = "1.2.1"
androidxActivity = "1.9.3"
androidxComposeBom = "2024.09.00-alpha"
androidxCore = "1.15.0"
androidxComposeBom = "2024.10.00"
androidxCore = "1.13.1"
androidxLifecycle = "2.8.7"
androidxNavigation = "2.8.3"
androidxPhoneInteractions = "1.1.0-alpha04"
Expand Down Expand Up @@ -65,6 +66,7 @@ wearToolingPreview = "1.0.0"
wearcompose = "1.5.0-alpha05"

[libraries]
accessibility-test-framework = { module = "com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework", version.ref = "accessibilityTestFramework" }
accompanist-testharness = { module = "com.google.accompanist:accompanist-testharness", version.ref = "accompanist" }
android-tools-build-gradle = { module = "com.android.tools.build:gradle", version.ref = "gradlePlugin" }
androidx-activity = { module = "androidx.activity:activity", version.ref = "androidxActivity" }
Expand Down Expand Up @@ -143,7 +145,7 @@ com-squareup-okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logg
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
compose-animation-animationgraphics = { group = "androidx.compose.animation", name = "animation-graphics" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
compose-bom = { group = "androidx.compose", name = "compose-bom-alpha", version.ref = "androidxComposeBom" }
compose-foundation-foundation = { group = "androidx.compose.foundation", name = "foundation" }
compose-foundation-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" }
compose-material-iconscore = { group = "androidx.compose.material", name = "material-icons-core" }
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class MediaPlayerA11yScreenshotTest : WearLegacyA11yTest() {
mediaPlayerScreen()
}

override val runAtf: Boolean
get() = false

private fun mediaPlayerScreen() {
val playerUiState = PlayerUiState(
playEnabled = true,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion roboscreenshots/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,24 @@ package com.google.android.horologist.screenshots.rng {
method @org.robolectric.ParameterizedRobolectricTestRunner.Parameters public java.util.List<com.google.android.horologist.screenshots.rng.WearDevice> devices();
}

@org.junit.runner.RunWith(AndroidJUnit4::class) @org.robolectric.annotation.Config(sdk={33}, qualifiers=com.github.takahirom.roborazzi.RobolectricDeviceQualifiers.WearOSLargeRound) @org.robolectric.annotation.GraphicsMode(org.robolectric.annotation.GraphicsMode.Mode.NATIVE) public abstract class WearLegacyA11yTest {
@org.junit.runner.RunWith(AndroidJUnit4::class) @org.robolectric.annotation.Config(sdk={34}, qualifiers=com.github.takahirom.roborazzi.RobolectricDeviceQualifiers.WearOSLargeRound) @org.robolectric.annotation.GraphicsMode(org.robolectric.annotation.GraphicsMode.Mode.NATIVE) public abstract class WearLegacyA11yTest {
ctor public WearLegacyA11yTest();
method @androidx.compose.runtime.Composable public void ComponentScaffold(kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @androidx.compose.runtime.Composable public void TestScaffold(kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public final void captureScreenA11yRoboImage(String filePath, com.github.takahirom.roborazzi.RoborazziOptions roborazziOptions);
method public final void captureScreenshot(optional String suffix);
method public void configureAccessibilityValidator(com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator validator);
method @org.junit.Rule public final androidx.compose.ui.test.junit4.ComposeContentTestRule getComposeRule();
method public coil.test.FakeImageLoaderEngine? getImageLoader();
method public boolean getRunAtf();
method @org.junit.Rule public final org.junit.rules.TestName getTestInfo();
method public float getTolerance();
method public final void runComponentTest(optional androidx.compose.ui.graphics.Color? background, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public final void runScreenTest(kotlin.jvm.functions.Function0<kotlin.Unit> content);
method public String testName(String suffix);
property @org.junit.Rule public final androidx.compose.ui.test.junit4.ComposeContentTestRule composeRule;
property public coil.test.FakeImageLoaderEngine? imageLoader;
property public boolean runAtf;
property @org.junit.Rule public final org.junit.rules.TestName testInfo;
property public float tolerance;
field public static final com.google.android.horologist.screenshots.rng.WearLegacyA11yTest.Companion Companion;
Expand Down
1 change: 1 addition & 0 deletions roboscreenshots/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dependencies {
api(projects.composeLayout)
api(projects.images.coil)
api(projects.tiles)
api(libs.accessibility.test.framework)

api(libs.kotlin.stdlib)
api(libs.okio)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.screenshots.a11y

import android.annotation.SuppressLint
import android.view.accessibility.AccessibilityManager
import org.robolectric.annotation.Implementation
import org.robolectric.annotation.Implements
import org.robolectric.shadows.ShadowAccessibilityManager
import org.robolectric.versioning.AndroidVersions.U

@Implements(AccessibilityManager::class)
internal class ExtraShadowAccessibilityManager : ShadowAccessibilityManager() {

/**
* This shadow method is required because {@link
* android.view.accessibility.DirectAccessibilityConnection} calls it to determine if any
* transformations have occurred on this window.
*/
@SuppressLint("PrivateApi")
@Implementation(minSdk = U.SDK_INT)
fun getWindowTransformationSpec(windowId: Int): Any {
val instance =
Class.forName("android.view.accessibility.IAccessibilityManager\$WindowTransformationSpec")
.getDeclaredConstructor().newInstance()

return instance
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.screenshots.a11y

import android.view.accessibility.AccessibilityNodeInfo
import org.robolectric.annotation.Implements

@Implements(AccessibilityNodeInfo::class)
internal class NonShadowShadowAccessibilityNodeInfo
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.google.android.horologist.screenshots.rng

import android.app.Application
import android.graphics.Bitmap
import android.os.Build
import android.os.Looper
import android.view.accessibility.AccessibilityManager
import androidx.compose.foundation.background
Expand All @@ -29,9 +30,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.tryPerformAccessibilityChecks
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.Root
Expand All @@ -46,6 +49,7 @@ import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
import com.github.takahirom.roborazzi.RoborazziOptions
import com.github.takahirom.roborazzi.ThresholdValidator
import com.github.takahirom.roborazzi.captureRoboImage
import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator
import com.google.android.horologist.compose.layout.AppScaffold
import com.google.android.horologist.compose.layout.ResponsiveTimeText
import com.google.android.horologist.screenshots.FixedTimeSource
Expand All @@ -59,9 +63,10 @@ import org.junit.runner.RunWith
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
import org.robolectric.shadows.ShadowBuild

@Config(
sdk = [33],
sdk = [34],
qualifiers = RobolectricDeviceQualifiers.WearOSLargeRound,
)
@RunWith(AndroidJUnit4::class)
Expand All @@ -78,6 +83,12 @@ public abstract class WearLegacyA11yTest {

public open val imageLoader: FakeImageLoaderEngine? = null

public open val runAtf: Boolean
get() = true

public open fun configureAccessibilityValidator(validator: AccessibilityValidator) {
}

public fun runScreenTest(
content: @Composable () -> Unit,
) {
Expand All @@ -87,6 +98,19 @@ public abstract class WearLegacyA11yTest {
}
}

if (runAtf && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// TODO change this check in ATF
ShadowBuild.setFingerprint("test_fingerprint")

composeRule.enableAccessibilityChecks()

val accessibilityValidator =
(composeRule as AndroidComposeTestRule<*, *>).accessibilityValidator!!
configureAccessibilityValidator(accessibilityValidator)

composeRule.onRoot().tryPerformAccessibilityChecks()
}

captureScreenshot()
}

Expand Down
Loading