<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