I was running an infinite “animation” using a Canv...
# compose
a
I was running an infinite “animation” using a Canvas and the code in 🧵. Weirdly, a UI test kept failing because the Node tree didn’t contain this Composable at all, nor the Snackbar that I was checking if it was displayed. Visually, both were being displayed. When I replaced
withFrameMillis
to
withInfiniteAnimationFrameMillis
, in the code, the UI test passed and the Node tree was correct. My question is, is this a bug I should file?
Here’s the Composable:
Copy code
@Composable
fun CustomSnackbar(snackbarHostState: SnackbarHostState) {
    val snackbarData = snackbarHostState.currentSnackbarData
    Box(contentAlignment = Alignment.BottomCenter) {
        AnimatedVisibility(
            visible = snackbarData != null,
            enter = fadeIn(),
            exit = fadeOut(),
        ) {
            LaunchedEffect(Unit) {
                while(true) {
                    withFrameMillis { frameMs ->
                    }
                }
            }
            Canvas(Modifier.fillMaxSize(), onDraw = {
                drawRect(Color.Black, size = Size(100f, 100f))
            })
        }
        SnackbarHost(hostState = snackbarHostState)
    }
}
And here’s the UI test
Copy code
composeTestRule.onNodeWithText("Show Canvas").performClick()

        composeTestRule.onRoot().printToLog("RootNodeTree")
        composeTestRule.onNodeWithText("Button was clicked").assertIsDisplayed()
If this isn’t a bug, is using
while(true) { withFrameMillis { } }
a bad practice? It seems like this has something to do with the coroutine cancellation policy.
z
This is not a bug, this is exactly why
withInfiniteAnimationFrameMillis
exists. And yes, running an infinite animation without declaring it as such is a bad practice. Espresso will not consider the main thread to be idle while anything has called withFrameMillis so if you call that in a loop, the thread will be “busy” as long as that loop is running.
withInfiniteAnimationFrameMillis
will immediately cancel the calling coroutine in UI tests to avoid keeping the main thread busy.