<lambda>null1 package org.robolectric.integration.roborazzi
2 
3 import android.app.Activity
4 import android.app.AlertDialog
5 import android.app.Application
6 import android.content.ComponentName
7 import android.graphics.Color
8 import android.graphics.drawable.ColorDrawable
9 import android.os.Build.VERSION_CODES.S
10 import android.os.Bundle
11 import android.widget.FrameLayout
12 import android.widget.LinearLayout
13 import android.widget.TextView
14 import androidx.test.core.app.ActivityScenario
15 import androidx.test.platform.app.InstrumentationRegistry
16 import com.github.takahirom.roborazzi.ExperimentalRoborazziApi
17 import com.github.takahirom.roborazzi.RoborazziOptions
18 import com.github.takahirom.roborazzi.RoborazziRule
19 import com.github.takahirom.roborazzi.captureScreenRoboImage
20 import kotlin.reflect.KClass
21 import org.junit.Rule
22 import org.junit.Test
23 import org.junit.runner.RunWith
24 import org.robolectric.RobolectricTestRunner
25 import org.robolectric.Shadows
26 import org.robolectric.annotation.Config
27 import org.robolectric.annotation.GraphicsMode
28 import org.robolectric.integration.roborazzi.RoborazziDialogTestActivity.Companion.OUTPUT_DIRECTORY_PATH
29 
30 /**
31  * Integration Test for Roborazzi
32  *
33  * This test is not intended to obstruct the release of Robolectric. In the event that issues are
34  * detected which do not stem from Robolectric, the test can be temporarily disabled, and an issue
35  * can be reported on the Roborazzi repository.
36  *
37  * Run ./gradlew integration_tests:roborazzi:recordRoborazziDebug
38  * -Drobolectric.alwaysIncludeVariantMarkersInTestName=true to record the reference
39  * screenshots(golden images). Run ./gradlew integration_tests:roborazzi:verifyRoborazziDebug
40  * -Drobolectric.alwaysIncludeVariantMarkersInTestName=true to check the screenshots.
41  */
42 @RunWith(RobolectricTestRunner::class)
43 @Config(sdk = [S])
44 @GraphicsMode(GraphicsMode.Mode.NATIVE)
45 @OptIn(ExperimentalRoborazziApi::class)
46 class RoborazziCaptureTest {
47   @get:Rule
48   val roborazziRule =
49     RoborazziRule(
50       options =
51         RoborazziRule.Options(
52           outputDirectoryPath = OUTPUT_DIRECTORY_PATH,
53           roborazziOptions =
54             RoborazziOptions(recordOptions = RoborazziOptions.RecordOptions(resizeScale = 0.5)),
55         )
56     )
57 
58   @Test
59   // For reducing repository size, we use small size
60   @Config(qualifiers = "w50dp-h40dp")
61   fun checkViewWithElevationRendering() {
62     hardwareRendererEnvironment {
63       setupActivity(RoborazziViewWithElevationTestActivity::class)
64 
65       captureScreenWithRoborazzi()
66     }
67   }
68 
69   @Test
70   // For reducing repository size, we use small size
71   @Config(qualifiers = "w110dp-h120dp")
72   fun checkDialogRendering() {
73     hardwareRendererEnvironment {
74       setupActivity(RoborazziDialogTestActivity::class)
75 
76       captureScreenWithRoborazzi()
77     }
78   }
79 
80   private fun setupActivity(activityClass: KClass<out Activity>) {
81     registerActivityToPackageManager(checkNotNull(activityClass.java.canonicalName))
82     ActivityScenario.launch(activityClass.java)
83   }
84 
85   private fun captureScreenWithRoborazzi() {
86     try {
87       captureScreenRoboImage()
88     } catch (e: AssertionError) {
89       throw AssertionError(
90         """
91         |${e.message}
92         |Please check the screenshot in $OUTPUT_DIRECTORY_PATH
93         |If you want to update the screenshot,
94         |run `./gradlew integration_tests:roborazzi:recordRoborazziDebug -Drobolectric.alwaysIncludeVariantMarkersInTestName=true` and commit the changes.
95         |"""
96           .trimMargin(),
97         e,
98       )
99     }
100   }
101 
102   companion object {
103     // TODO(hoisie): `robolectric.screenshot.hwrdr.native` is obsolete, remove it after the next
104     // Robolectric point release.
105     const val USE_HARDWARE_RENDERER_NATIVE_ENV = "robolectric.screenshot.hwrdr.native"
106     const val PIXEL_COPY_RENDER_MODE = "robolectric.pixelCopyRenderMode"
107   }
108 }
109 
registerActivityToPackageManagernull110 private fun registerActivityToPackageManager(activity: String) {
111   val appContext: Application =
112     InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
113   Shadows.shadowOf(appContext.packageManager)
114     .addActivityIfNotPresent(ComponentName(appContext.packageName, activity))
115 }
116 
117 @Suppress("ForbiddenComment")
hardwareRendererEnvironmentnull118 private fun hardwareRendererEnvironment(block: () -> Unit) {
119   val originalHwrdrOption =
120     System.getProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, null)
121   val originalPixelCopyOption =
122     System.getProperty(RoborazziCaptureTest.PIXEL_COPY_RENDER_MODE, null)
123   // This cause ClassNotFoundException: java.nio.NioUtils
124   // TODO: Remove comment out after fix this issue
125   // https://github.com/robolectric/robolectric/issues/8081#issuecomment-1858726896
126   // System.setProperty(USE_HARDWARE_RENDERER_NATIVE_ENV, "true")
127   try {
128     block()
129   } finally {
130     if (originalHwrdrOption == null) {
131       System.clearProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV)
132       System.clearProperty(RoborazziCaptureTest.PIXEL_COPY_RENDER_MODE)
133     } else {
134       System.setProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, originalHwrdrOption)
135       System.setProperty(RoborazziCaptureTest.PIXEL_COPY_RENDER_MODE, originalPixelCopyOption)
136     }
137   }
138 }
139 
140 private class RoborazziViewWithElevationTestActivity : Activity() {
141 
onCreatenull142   override fun onCreate(savedInstanceState: Bundle?) {
143     setTheme(android.R.style.Theme_Light_NoTitleBar)
144     super.onCreate(savedInstanceState)
145     setContentView(
146       LinearLayout(this).apply {
147         orientation = LinearLayout.VERTICAL
148         fun Int.toDp(): Int = (this * resources.displayMetrics.density).toInt()
149 
150         // View with elevation
151         addView(
152           FrameLayout(this@RoborazziViewWithElevationTestActivity).apply {
153             background = ColorDrawable(Color.MAGENTA)
154             elevation = 10f
155             addView(TextView(this.context).apply { text = "Txt" })
156           },
157           LinearLayout.LayoutParams(
158               LinearLayout.LayoutParams.MATCH_PARENT,
159               LinearLayout.LayoutParams.MATCH_PARENT,
160             )
161             .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) },
162         )
163       }
164     )
165   }
166 }
167 
168 private class RoborazziDialogTestActivity : Activity() {
onCreatenull169   override fun onCreate(savedInstanceState: Bundle?) {
170     setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar)
171     super.onCreate(savedInstanceState)
172     setContentView(
173       LinearLayout(this).apply {
174         orientation = LinearLayout.VERTICAL
175         fun Int.toDp(): Int = (this * resources.displayMetrics.density).toInt()
176 
177         // View with elevation
178         addView(
179           TextView(this.context).apply { text = "Under the dialog" },
180           LinearLayout.LayoutParams(
181               LinearLayout.LayoutParams.WRAP_CONTENT,
182               LinearLayout.LayoutParams.WRAP_CONTENT,
183             )
184             .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) },
185         )
186       }
187     )
188     AlertDialog.Builder(this).setTitle("Dlg").setPositiveButton("OK") { _, _ -> }.show()
189   }
190 
191   companion object {
192     const val OUTPUT_DIRECTORY_PATH = "src/screenshots"
193   }
194 }
195