Skip to content

Commit 62ace44

Browse files
committed
add wait for idle and disable animations
1 parent 13aa105 commit 62ace44

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

android-snaptesting/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dependencies {
4848
implementation(libs.androidx.test.monitor)
4949
implementation(libs.androidx.test.runner)
5050
implementation(libs.androidx.ui.test.junit4.android)
51+
implementation(libs.espresso.core)
5152
}
5253

5354
apply("${rootProject.projectDir}/mavencentral.gradle")

android-snaptesting/src/main/java/com/telefonica/androidsnaptesting/screenshots/ScreenshotsRule.kt

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ import android.app.Activity
44
import android.graphics.Bitmap
55
import android.graphics.BitmapFactory
66
import android.os.Build
7+
import android.os.Looper
8+
import android.view.View
9+
import android.view.View.INVISIBLE
10+
import android.widget.EditText
11+
import android.widget.HorizontalScrollView
12+
import android.widget.ScrollView
713
import androidx.annotation.RequiresApi
814
import androidx.compose.ui.graphics.asAndroidBitmap
915
import androidx.compose.ui.test.captureToImage
1016
import androidx.compose.ui.test.junit4.ComposeTestRule
1117
import androidx.compose.ui.test.onRoot
18+
import androidx.test.espresso.Espresso
1219
import androidx.test.platform.app.InstrumentationRegistry
20+
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
1321
import androidx.test.runner.screenshot.Screenshot
1422
import com.dropbox.differ.ImageComparator
1523
import com.dropbox.differ.Mask
@@ -34,6 +42,9 @@ public class ScreenshotsRule(
3442

3543
private val directories = Directories()
3644

45+
private val ignoredViews: List<Int>
46+
get() = emptyList()
47+
3748
override fun apply(base: Statement, description: Description): Statement {
3849
className = description.className
3950
testName = description.methodName
@@ -62,15 +73,19 @@ public class ScreenshotsRule(
6273
activity: Activity,
6374
name: String? = null,
6475
) {
76+
val view = activity.findViewById<View>(android.R.id.content)
77+
6578
val bitmap = Screenshot.capture(activity).bitmap
66-
compareScreenshot(bitmap, name)
79+
compareScreenshot(bitmap, name, view)
6780
}
6881

6982
@Suppress("MemberVisibilityCanBePrivate")
7083
public fun compareScreenshot(
7184
bitmap: Bitmap,
7285
name: String? = null,
86+
view: View? = null,
7387
) {
88+
disableFlakyComponentsAndWaitForIdle(view)
7489
val resourceName = "${className}_${name ?: testName}.png"
7590
val fileName = "$resourceName.${System.nanoTime()}"
7691
saveScreenshot(fileName, bitmap)
@@ -141,4 +156,65 @@ public class ScreenshotsRule(
141156
)
142157
}
143158
}
159+
160+
private fun disableFlakyComponentsAndWaitForIdle(view: View? = null) {
161+
if (view != null) {
162+
disableAnimatedComponents(view)
163+
hideIgnoredViews(view)
164+
}
165+
if (notInAppMainThread()) {
166+
waitForAnimationsToFinish()
167+
}
168+
}
169+
170+
private fun disableAnimatedComponents(view: View) {
171+
runOnUi {
172+
hideEditTextCursors(view)
173+
hideScrollViewBars(view)
174+
}
175+
}
176+
177+
private fun hideEditTextCursors(view: View) {
178+
view.childrenViews<EditText>().forEach {
179+
it.isCursorVisible = false
180+
}
181+
}
182+
183+
private fun hideScrollViewBars(view: View) {
184+
view.childrenViews<ScrollView>().forEach {
185+
hideViewBars(it)
186+
}
187+
188+
view.childrenViews<HorizontalScrollView>().forEach {
189+
hideViewBars(it)
190+
}
191+
}
192+
193+
private fun hideViewBars(it: View) {
194+
it.isHorizontalScrollBarEnabled = false
195+
it.isVerticalScrollBarEnabled = false
196+
it.overScrollMode = View.OVER_SCROLL_NEVER
197+
}
198+
199+
private fun hideIgnoredViews(view: View) = runOnUi {
200+
view.filterChildrenViews { children -> children.id in ignoredViews }.forEach { viewToIgnore ->
201+
viewToIgnore.visibility = INVISIBLE
202+
}
203+
}
204+
205+
public fun waitForAnimationsToFinish() {
206+
getInstrumentation().waitForIdleSync()
207+
Espresso.onIdle()
208+
}
209+
210+
public fun runOnUi(block: () -> Unit) {
211+
if (notInAppMainThread()) {
212+
getInstrumentation().runOnMainSync { block() }
213+
} else {
214+
block()
215+
}
216+
}
217+
218+
private fun notInAppMainThread() = Looper.myLooper() != Looper.getMainLooper()
219+
144220
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.telefonica.androidsnaptesting.screenshots
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
6+
@Suppress("UNCHECKED_CAST")
7+
public inline fun <reified T : View> View.childrenViews(): List<T> = filterChildrenViews {
8+
it is T
9+
} as List<T>
10+
11+
public fun View.filterChildrenViews(filter: (View) -> Boolean): List<View> {
12+
val children = mutableSetOf<View>()
13+
val view = this
14+
if (view !is ViewGroup) {
15+
if (filter.invoke(view)) {
16+
children.add(view)
17+
}
18+
} else {
19+
for (i in 0 until view.childCount) {
20+
view.getChildAt(i).let {
21+
children.addAll(it.filterChildrenViews(filter))
22+
if (filter.invoke(it)) {
23+
children.add(it)
24+
}
25+
}
26+
}
27+
}
28+
29+
return children.toList()
30+
}

0 commit comments

Comments
 (0)