Remove SystemUI ScreenshotTestRule (1/2)
This CL removes SystemUI ScreenshotTestRule now that most of what it does was contributed back to the platform screenshot library in ag/19135707. Bug: 230832101 Test: atest SystemUIGoogleScreenshotTests Change-Id: I38d01bcd35e6263752c69539dd65950d898f53de Merged-In: I38d01bcd35e6263752c69539dd65950d898f53de
This commit is contained in:
@@ -26,11 +26,7 @@ android_library {
|
||||
manifest: "AndroidManifest.xml",
|
||||
|
||||
srcs: [
|
||||
// All files in this library should be in Kotlin besides some exceptions.
|
||||
"src/**/*.kt",
|
||||
|
||||
// This file was forked from google3, so exceptionally it can be in Java.
|
||||
"src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java",
|
||||
],
|
||||
|
||||
resource_dirs: [
|
||||
|
||||
@@ -23,6 +23,4 @@
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.SystemUI.Screenshot" />
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.systemui.testing.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]. */
|
||||
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()
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* 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.systemui.testing.screenshot;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import android.app.UiAutomation;
|
||||
import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.IdlingRegistry;
|
||||
import androidx.test.espresso.IdlingResource;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* Note: This file was forked from
|
||||
* google3/third_party/java_src/android_libs/material_components/screenshot_tests/java/android/
|
||||
* support/design/scuba/color/DynamicColorsTestUtils.java.
|
||||
*/
|
||||
|
||||
/** Utility that helps change the dynamic system colors for testing. */
|
||||
@RequiresApi(32)
|
||||
public class DynamicColorsTestUtils {
|
||||
|
||||
private static final String TAG = DynamicColorsTestUtils.class.getSimpleName();
|
||||
|
||||
private static final String THEME_CUSTOMIZATION_KEY = "theme_customization_overlay_packages";
|
||||
private static final String THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY =
|
||||
"android.theme.customization.system_palette";
|
||||
|
||||
private static final int ORANGE_SYSTEM_SEED_COLOR = 0xA66800;
|
||||
private static final int ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR = -8235756;
|
||||
|
||||
private DynamicColorsTestUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on an orange
|
||||
* seed color, and then wait for the change to propagate to the app by comparing
|
||||
* android.R.color.system_accent1_600 to the expected orange value.
|
||||
*/
|
||||
public static void updateSystemColorsToOrange() {
|
||||
updateSystemColors(ORANGE_SYSTEM_SEED_COLOR, ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
|
||||
* {@code seedColor}, and then wait for the change to propagate to the app by comparing
|
||||
* android.R.color.system_accent1_600 to {@code expectedSystemAccent1600}.
|
||||
*/
|
||||
public static void updateSystemColors(
|
||||
@ColorInt int seedColor, @ColorInt int expectedSystemAccent1600) {
|
||||
Context context = getInstrumentation().getTargetContext();
|
||||
|
||||
int actualSystemAccent1600 =
|
||||
ContextCompat.getColor(context, android.R.color.system_accent1_600);
|
||||
|
||||
if (expectedSystemAccent1600 == actualSystemAccent1600) {
|
||||
String expectedColorString = Integer.toHexString(expectedSystemAccent1600);
|
||||
Log.d(
|
||||
TAG,
|
||||
"Skipped updating system colors since system_accent1_600 is already equal to "
|
||||
+ "expected: "
|
||||
+ expectedColorString);
|
||||
return;
|
||||
}
|
||||
|
||||
updateSystemColors(seedColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
|
||||
* {@code seedColor}, and then wait for the change to propagate to the app by checking
|
||||
* android.R.color.system_accent1_600 for any change.
|
||||
*/
|
||||
public static void updateSystemColors(@ColorInt int seedColor) {
|
||||
Context context = getInstrumentation().getTargetContext();
|
||||
|
||||
// Initialize system color idling resource with original system_accent1_600 value.
|
||||
ColorChangeIdlingResource systemColorIdlingResource =
|
||||
new ColorChangeIdlingResource(context, android.R.color.system_accent1_600);
|
||||
|
||||
// Update system theme color setting to trigger fabricated resource overlay.
|
||||
runWithShellPermissionIdentity(
|
||||
() ->
|
||||
Settings.Secure.putString(
|
||||
context.getContentResolver(),
|
||||
THEME_CUSTOMIZATION_KEY,
|
||||
buildThemeCustomizationString(seedColor)));
|
||||
|
||||
// Wait for system color update to propagate to app.
|
||||
IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();
|
||||
idlingRegistry.register(systemColorIdlingResource);
|
||||
Espresso.onIdle();
|
||||
idlingRegistry.unregister(systemColorIdlingResource);
|
||||
|
||||
Log.d(TAG,
|
||||
Settings.Secure.getString(context.getContentResolver(), THEME_CUSTOMIZATION_KEY));
|
||||
}
|
||||
|
||||
private static String buildThemeCustomizationString(@ColorInt int seedColor) {
|
||||
String seedColorHex = Integer.toHexString(seedColor);
|
||||
Map<String, String> themeCustomizationMap = new HashMap<>();
|
||||
themeCustomizationMap.put(THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY, seedColorHex);
|
||||
return new JSONObject(themeCustomizationMap).toString();
|
||||
}
|
||||
|
||||
private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
|
||||
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
|
||||
uiAutomation.adoptShellPermissionIdentity();
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
uiAutomation.dropShellPermissionIdentity();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ColorChangeIdlingResource implements IdlingResource {
|
||||
|
||||
private final Context mContext;
|
||||
private final int mColorResId;
|
||||
private final int mInitialColorInt;
|
||||
|
||||
private ResourceCallback mResourceCallback;
|
||||
private boolean mIdleNow;
|
||||
|
||||
ColorChangeIdlingResource(Context context, @ColorRes int colorResId) {
|
||||
this.mContext = context;
|
||||
this.mColorResId = colorResId;
|
||||
this.mInitialColorInt = ContextCompat.getColor(context, colorResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ColorChangeIdlingResource.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdleNow() {
|
||||
if (mIdleNow) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int currentColorInt = ContextCompat.getColor(mContext, mColorResId);
|
||||
|
||||
String initialColorString = Integer.toHexString(mInitialColorInt);
|
||||
String currentColorString = Integer.toHexString(currentColorInt);
|
||||
Log.d(TAG, String.format("Initial=%s, Current=%s", initialColorString,
|
||||
currentColorString));
|
||||
|
||||
mIdleNow = currentColorInt != mInitialColorInt;
|
||||
Log.d(TAG, String.format("idleNow=%b", mIdleNow));
|
||||
|
||||
if (mIdleNow) {
|
||||
mResourceCallback.onTransitionToIdle();
|
||||
}
|
||||
return mIdleNow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
|
||||
this.mResourceCallback = resourceCallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* 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.systemui.testing.screenshot
|
||||
|
||||
import android.app.UiModeManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.os.Build
|
||||
import android.os.UserHandle
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.WindowManagerGlobal
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import platform.test.screenshot.GoldenImagePathManager
|
||||
import platform.test.screenshot.PathConfig
|
||||
import platform.test.screenshot.PathElementNoContext
|
||||
import platform.test.screenshot.ScreenshotTestRule
|
||||
import platform.test.screenshot.matchers.MSSIMMatcher
|
||||
import platform.test.screenshot.matchers.PixelPerfectMatcher
|
||||
|
||||
/**
|
||||
* A base rule for screenshot diff tests.
|
||||
*
|
||||
* This rules takes care of setting up the activity according to [testSpec] by:
|
||||
* - emulating the display size and density.
|
||||
* - setting the dark/light mode.
|
||||
* - setting the system (Material You) colors to a fixed value.
|
||||
*
|
||||
* @see ComposeScreenshotTestRule
|
||||
* @see ViewScreenshotTestRule
|
||||
*/
|
||||
class ScreenshotTestRule(private val testSpec: ScreenshotTestSpec) : TestRule {
|
||||
private var currentDisplay: DisplaySpec? = null
|
||||
private var currentGoldenIdentifier: String? = null
|
||||
|
||||
private val pathConfig =
|
||||
PathConfig(
|
||||
PathElementNoContext("model", isDir = true) {
|
||||
currentDisplay?.name ?: error("currentDisplay is null")
|
||||
},
|
||||
)
|
||||
private val matcher = if (shouldUsePerfectMatching()) {
|
||||
PixelPerfectMatcher()
|
||||
} else {
|
||||
MSSIMMatcher()
|
||||
}
|
||||
|
||||
private val screenshotRule =
|
||||
ScreenshotTestRule(
|
||||
SystemUIGoldenImagePathManager(
|
||||
pathConfig,
|
||||
currentGoldenIdentifier = {
|
||||
currentGoldenIdentifier ?: error("currentGoldenIdentifier is null")
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
private fun shouldUsePerfectMatching(): Boolean {
|
||||
// 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.
|
||||
return Build.CPU_ABI == "x86_64"
|
||||
}
|
||||
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
// The statement which call beforeTest() before running the test and afterTest() afterwards.
|
||||
val statement =
|
||||
object : Statement() {
|
||||
override fun evaluate() {
|
||||
try {
|
||||
beforeTest()
|
||||
base.evaluate()
|
||||
} finally {
|
||||
afterTest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return screenshotRule.apply(statement, description)
|
||||
}
|
||||
|
||||
private fun beforeTest() {
|
||||
// Update the system colors to a fixed color, so that tests don't depend on the host device
|
||||
// extracted colors. Note that we don't restore the default device colors at the end of the
|
||||
// test because changing the colors (and waiting for them to be applied) is costly and makes
|
||||
// the screenshot tests noticeably slower.
|
||||
DynamicColorsTestUtils.updateSystemColorsToOrange()
|
||||
|
||||
// Emulate the display size and density.
|
||||
val display = testSpec.display
|
||||
val density = display.densityDpi
|
||||
val wm = WindowManagerGlobal.getWindowManagerService()
|
||||
val (width, height) = getEmulatedDisplaySize()
|
||||
wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, density, UserHandle.myUserId())
|
||||
wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, width, height)
|
||||
|
||||
// Force the dark/light theme.
|
||||
val uiModeManager =
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.targetContext
|
||||
.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
|
||||
uiModeManager.setApplicationNightMode(
|
||||
if (testSpec.isDarkTheme) {
|
||||
UiModeManager.MODE_NIGHT_YES
|
||||
} else {
|
||||
UiModeManager.MODE_NIGHT_NO
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun afterTest() {
|
||||
// Reset the density and display size.
|
||||
val wm = WindowManagerGlobal.getWindowManagerService()
|
||||
wm.clearForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, UserHandle.myUserId())
|
||||
wm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY)
|
||||
|
||||
// Reset the dark/light theme.
|
||||
val uiModeManager =
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.targetContext
|
||||
.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
|
||||
uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_AUTO)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the content of [view] with the golden image identified by [goldenIdentifier] in the
|
||||
* context of [testSpec].
|
||||
*/
|
||||
fun screenshotTest(goldenIdentifier: String, view: View) {
|
||||
val bitmap = drawIntoBitmap(view)
|
||||
|
||||
// Compare bitmap against golden asset.
|
||||
val isDarkTheme = testSpec.isDarkTheme
|
||||
val isLandscape = testSpec.isLandscape
|
||||
val identifierWithSpec = buildString {
|
||||
append(goldenIdentifier)
|
||||
if (isDarkTheme) append("_dark")
|
||||
if (isLandscape) append("_landscape")
|
||||
}
|
||||
|
||||
// TODO(b/230832101): Provide a way to pass a PathConfig and override the file name on
|
||||
// device to assertBitmapAgainstGolden instead?
|
||||
currentDisplay = testSpec.display
|
||||
currentGoldenIdentifier = goldenIdentifier
|
||||
screenshotRule.assertBitmapAgainstGolden(bitmap, identifierWithSpec, matcher)
|
||||
currentDisplay = null
|
||||
currentGoldenIdentifier = goldenIdentifier
|
||||
}
|
||||
|
||||
/** Draw [view] into a [Bitmap]. */
|
||||
private fun drawIntoBitmap(view: View): Bitmap {
|
||||
val bitmap =
|
||||
Bitmap.createBitmap(
|
||||
view.measuredWidth,
|
||||
view.measuredHeight,
|
||||
Bitmap.Config.ARGB_8888,
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
view.draw(canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
/** Get the emulated display size for [testSpec]. */
|
||||
private fun getEmulatedDisplaySize(): Pair<Int, Int> {
|
||||
val display = testSpec.display
|
||||
val isPortraitNaturalPosition = display.width < display.height
|
||||
return if (testSpec.isLandscape) {
|
||||
if (isPortraitNaturalPosition) {
|
||||
display.height to display.width
|
||||
} else {
|
||||
display.width to display.height
|
||||
}
|
||||
} else {
|
||||
if (isPortraitNaturalPosition) {
|
||||
display.width to display.height
|
||||
} else {
|
||||
display.height to display.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SystemUIGoldenImagePathManager(
|
||||
pathConfig: PathConfig,
|
||||
private val currentGoldenIdentifier: () -> String,
|
||||
) :
|
||||
GoldenImagePathManager(
|
||||
appContext = InstrumentationRegistry.getInstrumentation().context,
|
||||
deviceLocalPath =
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.targetContext
|
||||
.filesDir
|
||||
.absolutePath
|
||||
.toString() + "/sysui_screenshots",
|
||||
pathConfig = pathConfig,
|
||||
) {
|
||||
// This string is appended to all actual/expected screenshots on the device. We append the
|
||||
// golden identifier so that our pull_golden.py scripts can map a screenshot on device to its
|
||||
// asset (and automatically update it, if necessary).
|
||||
override fun toString() = currentGoldenIdentifier()
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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.systemui.testing.screenshot
|
||||
|
||||
/** The specification of a device display to be used in a screenshot test. */
|
||||
data class DisplaySpec(
|
||||
val name: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val densityDpi: Int,
|
||||
)
|
||||
|
||||
/** The specification of a screenshot diff test. */
|
||||
class ScreenshotTestSpec(
|
||||
val display: DisplaySpec,
|
||||
val isDarkTheme: Boolean = false,
|
||||
val isLandscape: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Return a list of [ScreenshotTestSpec] for each of the [displays].
|
||||
*
|
||||
* If [isDarkTheme] is null, this will create a spec for both light and dark themes, for
|
||||
* each of the orientation.
|
||||
*
|
||||
* If [isLandscape] is null, this will create a spec for both portrait and landscape, for
|
||||
* each of the light/dark themes.
|
||||
*/
|
||||
fun forDisplays(
|
||||
vararg displays: DisplaySpec,
|
||||
isDarkTheme: Boolean? = null,
|
||||
isLandscape: Boolean? = null,
|
||||
): List<ScreenshotTestSpec> {
|
||||
return displays.flatMap { display ->
|
||||
buildList {
|
||||
fun addDisplay(isLandscape: Boolean) {
|
||||
if (isDarkTheme != true) {
|
||||
add(ScreenshotTestSpec(display, isDarkTheme = false, isLandscape))
|
||||
}
|
||||
|
||||
if (isDarkTheme != false) {
|
||||
add(ScreenshotTestSpec(display, isDarkTheme = true, isLandscape))
|
||||
}
|
||||
}
|
||||
|
||||
if (isLandscape != true) {
|
||||
addDisplay(isLandscape = false)
|
||||
}
|
||||
|
||||
if (isLandscape != false) {
|
||||
addDisplay(isLandscape = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = buildString {
|
||||
// This string is appended to PNGs stored in the device, so let's keep it simple.
|
||||
append(display.name)
|
||||
if (isDarkTheme) append("_dark")
|
||||
if (isLandscape) append("_landscape")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.systemui.testing.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 SystemUI screenshot tests. */
|
||||
class SystemUIGoldenImagePathManager(
|
||||
pathConfig: PathConfig,
|
||||
) :
|
||||
GoldenImagePathManager(
|
||||
appContext = InstrumentationRegistry.getInstrumentation().context,
|
||||
deviceLocalPath =
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.targetContext
|
||||
.filesDir
|
||||
.absolutePath
|
||||
.toString() + "/sysui_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 "SystemUIGoldenImagePathManager"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.systemui.testing.screenshot
|
||||
|
||||
import android.app.Activity
|
||||
@@ -11,21 +27,35 @@ 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 View screenshot diff tests. */
|
||||
class ViewScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
|
||||
/** A rule for View screenshot diff unit tests. */
|
||||
class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
|
||||
private val colorsRule = MaterialYouColorsRule()
|
||||
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
|
||||
private val screenshotRule =
|
||||
ScreenshotTestRule(
|
||||
SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
|
||||
)
|
||||
private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
|
||||
private val screenshotRule = ScreenshotTestRule(testSpec)
|
||||
|
||||
private val delegate = RuleChain.outerRule(screenshotRule).around(activityRule)
|
||||
private val delegateRule =
|
||||
RuleChain.outerRule(colorsRule)
|
||||
.around(deviceEmulationRule)
|
||||
.around(screenshotRule)
|
||||
.around(activityRule)
|
||||
private val matcher = UnitTestBitmapMatcher
|
||||
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
return delegate.apply(base, description)
|
||||
return delegateRule.apply(base, description)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the content of the view provided by [viewProvider] with the golden image identified
|
||||
* by [goldenIdentifier] in the context of [testSpec].
|
||||
* by [goldenIdentifier] in the context of [emulationSpec].
|
||||
*/
|
||||
fun screenshotTest(
|
||||
goldenIdentifier: String,
|
||||
@@ -46,13 +76,17 @@ class ViewScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
|
||||
// Check that the content is what we expected.
|
||||
val content = activity.requireViewById<ViewGroup>(android.R.id.content)
|
||||
assertEquals(1, content.childCount)
|
||||
screenshotRule.screenshotTest(goldenIdentifier, content.getChildAt(0))
|
||||
screenshotRule.assertBitmapAgainstGolden(
|
||||
content.getChildAt(0).drawIntoBitmap(),
|
||||
goldenIdentifier,
|
||||
matcher
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the content of the dialog provided by [dialogProvider] with the golden image
|
||||
* identified by [goldenIdentifier] in the context of [testSpec].
|
||||
* identified by [goldenIdentifier] in the context of [emulationSpec].
|
||||
*/
|
||||
fun dialogScreenshotTest(
|
||||
goldenIdentifier: String,
|
||||
@@ -81,7 +115,11 @@ class ViewScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
|
||||
// Check that the content is what we expected.
|
||||
val dialog = dialog ?: error("dialog is null")
|
||||
try {
|
||||
screenshotRule.screenshotTest(goldenIdentifier, dialog.window.decorView)
|
||||
screenshotRule.assertBitmapAgainstGolden(
|
||||
dialog.window.decorView.drawIntoBitmap(),
|
||||
goldenIdentifier,
|
||||
matcher,
|
||||
)
|
||||
} finally {
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user