@@ -4,12 +4,19 @@ import android.app.Activity
44import android.graphics.Bitmap
55import android.graphics.BitmapFactory
66import android.os.Build
7+ import android.os.Looper
8+ import android.view.View
9+ import android.widget.EditText
10+ import android.widget.HorizontalScrollView
11+ import android.widget.ScrollView
712import androidx.annotation.RequiresApi
813import androidx.compose.ui.graphics.asAndroidBitmap
914import androidx.compose.ui.test.captureToImage
1015import androidx.compose.ui.test.junit4.ComposeTestRule
1116import androidx.compose.ui.test.onRoot
17+ import androidx.test.espresso.Espresso
1218import androidx.test.platform.app.InstrumentationRegistry
19+ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
1320import androidx.test.runner.screenshot.Screenshot
1421import com.dropbox.differ.ImageComparator
1522import com.dropbox.differ.Mask
@@ -51,7 +58,7 @@ public class ScreenshotsRule(
5158 @RequiresApi(Build .VERSION_CODES .O )
5259 public fun compareScreenshot (
5360 rule : ComposeTestRule ,
54- name : String? ,
61+ name : String? = null ,
5562 ) {
5663 rule.waitForIdle()
5764 val bitmap = rule.onRoot().captureToImage().asAndroidBitmap()
@@ -60,16 +67,20 @@ public class ScreenshotsRule(
6067
6168 public fun compareScreenshot (
6269 activity : Activity ,
63- name : String? ,
70+ name : String? = null ,
6471 ) {
72+ val view = activity.findViewById<View >(android.R .id.content)
73+
74+ disableFlakyComponentsAndWaitForIdle(view)
75+
6576 val bitmap = Screenshot .capture(activity).bitmap
6677 compareScreenshot(bitmap, name)
6778 }
6879
6980 @Suppress(" MemberVisibilityCanBePrivate" )
7081 public fun compareScreenshot (
7182 bitmap : Bitmap ,
72- name : String?
83+ name : String? = null,
7384 ) {
7485 val resourceName = " ${className} _${name ? : testName} .png"
7586 val fileName = " $resourceName .${System .nanoTime()} "
@@ -141,4 +152,58 @@ public class ScreenshotsRule(
141152 )
142153 }
143154 }
155+
156+ private fun disableFlakyComponentsAndWaitForIdle (view : View ? = null) {
157+ if (view != null ) {
158+ disableAnimatedComponents(view)
159+ }
160+ if (notInAppMainThread()) {
161+ waitForAnimationsToFinish()
162+ }
163+ }
164+
165+ private fun disableAnimatedComponents (view : View ) {
166+ runOnUi {
167+ hideEditTextCursors(view)
168+ hideScrollViewBars(view)
169+ }
170+ }
171+
172+ private fun hideEditTextCursors (view : View ) {
173+ view.childrenViews<EditText >().forEach {
174+ it.isCursorVisible = false
175+ }
176+ }
177+
178+ private fun hideScrollViewBars (view : View ) {
179+ view.childrenViews<ScrollView >().forEach {
180+ hideViewBars(it)
181+ }
182+
183+ view.childrenViews<HorizontalScrollView >().forEach {
184+ hideViewBars(it)
185+ }
186+ }
187+
188+ private fun hideViewBars (it : View ) {
189+ it.isHorizontalScrollBarEnabled = false
190+ it.isVerticalScrollBarEnabled = false
191+ it.overScrollMode = View .OVER_SCROLL_NEVER
192+ }
193+
194+ public fun waitForAnimationsToFinish () {
195+ getInstrumentation().waitForIdleSync()
196+ Espresso .onIdle()
197+ }
198+
199+ public fun runOnUi (block : () -> Unit ) {
200+ if (notInAppMainThread()) {
201+ getInstrumentation().runOnMainSync { block() }
202+ } else {
203+ block()
204+ }
205+ }
206+
207+ private fun notInAppMainThread () = Looper .myLooper() != Looper .getMainLooper()
208+
144209}
0 commit comments