diff options
| author | 2022-11-30 17:11:55 +0800 | |
|---|---|---|
| committer | 2022-12-08 13:36:11 +0800 | |
| commit | 33064ded1a38e66784eca604a1f5584f4992d15b (patch) | |
| tree | 6cacc58e290066a6c855d97b32de0cc79c7a8f8a | |
| parent | ea89a254ecf3df92c63d4ee5463f17c86746de74 (diff) | |
Introduce SpaScreenshotTests for SPA screenshot testing.
This CL introduces the SpaScreenshotTests to test a given View for multiple configurations (devices, theme and
orientation).
PreferenceScreenshotTest is added as an example, more screendiff tests will be added in follow-up CLs.
Test: presubmit
Bug: 256770697
Change-Id: I6b62d3cc248cab5bfe9f447e595ba393e9e3c848
16 files changed, 467 insertions, 0 deletions
diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp new file mode 100644 index 000000000000..4e6b646da819 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/Android.bp @@ -0,0 +1,41 @@ +// +// Copyright (C) 2022 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 +// +// http://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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "SpaScreenshotTests", + test_suites: ["device-tests"], + + asset_dirs: ["assets"], + + srcs: ["src/**/*.kt"], + + certificate: "platform", + + static_libs: [ + "SpaLib", + "SpaLibTestUtils", + "androidx.compose.runtime_runtime", + "androidx.test.ext.junit", + "androidx.test.runner", + "mockito-target-minus-junit4", + "platform-screenshot-diff-core", + ], + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml new file mode 100644 index 000000000000..d59a1542498d --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2022 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 + + http://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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.spa.screenshot"> + + <uses-sdk android:minSdkVersion="21"/> + + <application> + <uses-library android:name="android.test.runner" /> + <activity android:name=".DebugActivity" android:exported="true" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Screenshot tests for SpaLib" + android:targetPackage="com.android.settingslib.spa.screenshot"> + </instrumentation> +</manifest> diff --git a/packages/SettingsLib/Spa/screenshot/AndroidTest.xml b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml new file mode 100644 index 000000000000..e0c08e811c46 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml @@ -0,0 +1,36 @@ +<!-- + Copyright (C) 2022 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 + + http://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. + --> + +<configuration description="Runs screendiff tests."> + <option name="test-suite-tag" value="apct-instrumentation" /> + <option name="test-suite-tag" value="apct" /> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="optimized-property-setting" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="SpaScreenshotTests.apk" /> + </target_preparer> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" + value="/data/user/0/com.android.settingslib.spa.screenshot/files/settings_screenshots" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.settingslib.spa.screenshot" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png Binary files differnew file mode 100644 index 000000000000..6086e2d15dd2 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png Binary files differnew file mode 100644 index 000000000000..aa6c5b7cd131 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png Binary files differnew file mode 100644 index 000000000000..cac990caad63 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png Binary files differnew file mode 100644 index 000000000000..f6298c0b62c4 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png Binary files differnew file mode 100644 index 000000000000..9391eeb01e74 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png Binary files differnew file mode 100644 index 000000000000..94e28437c04e --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png Binary files differnew file mode 100644 index 000000000000..b1d03c31362e --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png Binary files differnew file mode 100644 index 000000000000..95f19da3c52c --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt new file mode 100644 index 000000000000..814d4a1d1ede --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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 + * + * http://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.android.settingslib.spa.screenshot + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.view.View +import platform.test.screenshot.matchers.MSSIMMatcher +import platform.test.screenshot.matchers.PixelPerfectMatcher + +/** Draw this [View] into a [Bitmap]. */ +// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their +// tests. +fun View.drawIntoBitmap(): Bitmap { + val bitmap = + Bitmap.createBitmap( + measuredWidth, + measuredHeight, + Bitmap.Config.ARGB_8888, + ) + val canvas = Canvas(bitmap) + draw(canvas) + return bitmap +} + +/** + * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for + * screenshot *unit* tests. + */ +val UnitTestBitmapMatcher = + if (Build.CPU_ABI == "x86_64") { + // Different CPU architectures can sometimes end up rendering differently, so we can't do + // pixel-perfect matching on different architectures using the same golden. Given that our + // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the + // x86_64 architecture and use the Structural Similarity Index on others. + // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can + // do pixel perfect matching both at presubmit time and at development time with actual + // devices. + PixelPerfectMatcher() + } else { + MSSIMMatcher() + } + +/** + * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for + * screenshot *unit* tests. + * + * We use the Structural Similarity Index for integration tests because they usually contain + * additional information and noise that shouldn't break the test. + */ +val IntegrationTestBitmapMatcher = MSSIMMatcher() diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt new file mode 100644 index 000000000000..d7f42b3d1be3 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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 + * + * http://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.android.settingslib.spa.screenshot + +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.DisplaySpec + +/** + * The emulations specs for all 8 permutations of: + * - phone or tablet. + * - dark of light mode. + * - portrait or landscape. + */ +val DeviceEmulationSpec.Companion.PhoneAndTabletFull + get() = PhoneAndTabletFullSpec + +private val PhoneAndTabletFullSpec = + DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet) + +/** + * The emulations specs of: + * - phone + light mode + portrait. + * - phone + light mode + landscape. + * - tablet + dark mode + portrait. + * + * This allows to test the most important permutations of a screen/layout with only 3 + * configurations. + */ +val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal + get() = PhoneAndTabletMinimalSpec + +private val PhoneAndTabletMinimalSpec = + DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) + + DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false) + +object Displays { + val Phone = + DisplaySpec( + "phone", + width = 1440, + height = 3120, + densityDpi = 560, + ) + + val Tablet = + DisplaySpec( + "tablet", + width = 2560, + height = 1600, + densityDpi = 320, + ) +} diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt new file mode 100644 index 000000000000..25bc098b29b7 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 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 + * + * http://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.android.settingslib.spa.screenshot + +import androidx.test.platform.app.InstrumentationRegistry +import platform.test.screenshot.GoldenImagePathManager +import platform.test.screenshot.PathConfig + +/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */ +class SettingsGoldenImagePathManager( + pathConfig: PathConfig, + assetsPathRelativeToBuildRoot: String +) : + GoldenImagePathManager( + appContext = InstrumentationRegistry.getInstrumentation().context, + assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, + deviceLocalPath = + InstrumentationRegistry.getInstrumentation() + .targetContext + .filesDir + .absolutePath + .toString() + "/settings_screenshots", + pathConfig = pathConfig, + ) { + override fun toString(): String { + // This string is appended to all actual/expected screenshots on the device, so make sure + // it is a static value. + return "SettingsGoldenImagePathManager" + } +} diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt new file mode 100644 index 000000000000..7a7cf318fb4f --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 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 + * + * http://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.android.settingslib.spa.screenshot + +import androidx.activity.ComponentActivity +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ViewRootForTest +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onRoot +import com.android.settingslib.spa.framework.theme.SettingsTheme +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import platform.test.screenshot.DeviceEmulationRule +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.MaterialYouColorsRule +import platform.test.screenshot.ScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** A rule for Settings screenshot diff tests. */ +class SettingsScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + assetsPathRelativeToBuildRoot: String +) : TestRule { + private val colorsRule = MaterialYouColorsRule() + private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) + private val screenshotRule = + ScreenshotTestRule( + SettingsGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetsPathRelativeToBuildRoot + ) + ) + private val composeRule = createAndroidComposeRule<ComponentActivity>() + private val delegateRule = + RuleChain.outerRule(colorsRule) + .around(deviceEmulationRule) + .around(screenshotRule) + .around(composeRule) + private val matcher = UnitTestBitmapMatcher + + override fun apply(base: Statement, description: Description): Statement { + return delegateRule.apply(base, description) + } + + /** + * Compare [content] with the golden image identified by [goldenIdentifier] in the context of + * [testSpec]. + */ + fun screenshotTest( + goldenIdentifier: String, + content: @Composable () -> Unit, + ) { + // Make sure that the activity draws full screen and fits the whole display. + val activity = composeRule.activity + activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) } + + // Set the content using the AndroidComposeRule to make sure that the Activity is set up + // correctly. + composeRule.setContent { + SettingsTheme { + Surface( + color = MaterialTheme.colorScheme.background, + ) { + content() + } + } + } + composeRule.waitForIdle() + + val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view + screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) + } +} diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt new file mode 100644 index 000000000000..963182638293 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 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 + * + * http://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.android.settingslib.spa.screenshot + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Autorenew +import androidx.compose.material.icons.outlined.DisabledByDefault +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.SettingsIcon +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import platform.test.screenshot.DeviceEmulationSpec + +/** A screenshot test for ExampleFeature. */ +@RunWith(Parameterized::class) +class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletFull + private const val TITLE = "Title" + private const val SUMMARY = "Summary" + private const val LONG_SUMMARY = + "Long long long long long long long long long long long long long long long summary" + } + + @get:Rule + val screenshotRule = + SettingsScreenshotTestRule( + emulationSpec, + "frameworks/base/packages/SettingsLib/Spa/screenshot/assets" + ) + + @Test + fun testPreference() { + screenshotRule.screenshotTest("preference") { + RegularScaffold(title = "Preference") { + Preference(object : PreferenceModel { + override val title = TITLE + }) + + Preference(object : PreferenceModel { + override val title = TITLE + override val summary = SUMMARY.toState() + }) + + Preference(object : PreferenceModel { + override val title = TITLE + override val summary = LONG_SUMMARY.toState() + }) + + Preference(object : PreferenceModel { + override val title = TITLE + override val summary = SUMMARY.toState() + override val enabled = false.toState() + override val icon = @Composable { + SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault) + } + }) + + Preference(object : PreferenceModel { + override val title = TITLE + override val summary = SUMMARY.toState() + override val icon = @Composable { + SettingsIcon(imageVector = Icons.Outlined.Autorenew) + } + }) + } + } + } +}
\ No newline at end of file |