We seem to have an issue with AndroidComposeRule, we have a simple test with waitUntil {} function:
@RunWith(AndroidJUnit4::class)
class IdenfyFaceReauthenticationFlowTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<FaceReauthenticationActivity>()
private lateinit var faceReauthenticationActivity: FaceReauthenticationActivity
@Before
fun setUp() {
faceReauthenticationActivity = composeTestRule.activity
faceReauthenticationActivity.idenfyMainViewModel.idenfyInternalSettings.isInitialViewClosed = false
}
@Test
fun test1() {
composeTestRule.waitUntil(8000) {
composeTestRule.onAllNodesWithText(faceReauthenticationActivity.getString(R.string.idenfy_camera_onboarding_view_continue_button_title_v2)).fetchSemanticsNodes(false).isNotEmpty()
}
composeTestRule.onNodeWithText(faceReauthenticationActivity.getString(R.string.idenfy_camera_onboarding_view_continue_button_title_v2)).performClick()
Thread.sleep(6000)
//Face Reauthentication Results View V2
IdenfyView().waitForView(ViewMatchers.withId(R.id.idenfy_button_face_reauthentication_results_continue)).perform(
ViewActions.click())
Thread.sleep(1000)
val activityResult = composeTestRule.activityRule.scenario.result!!
activityResult.resultData.setExtrasClassLoader(this::class.java.classLoader)
val faceReauthenticationResult: FaceReauthenticationResult =
activityResult.resultData.getParcelableExtra(IdenfyController.IDENFY_FACE_REAUTHENTICATION_RESULT)!!
assert(faceReauthenticationResult.faceReauthenticationStatus == FaceReauthenticationStatus.SUCCESS)
}
@After
fun tearDown() {
composeTestRule.activityRule.scenario.close()
}
}
The problem that we are facing, is that SOMETIMES the test fails with the following error:
androidx.compose.ui.test.junit4.android.ComposeNotIdleException: Idling resource timed out: possibly due to compose being busy.
IdlingResourceRegistry has the following idling resources registered:
- [busy] androidx.compose.ui.test.junit4.android.ComposeIdlingResource@df5cec9
All registered idling resources: Compose-Espresso link
at androidx.compose.ui.test.junit4.android.EspressoLink_androidKt.rethrowWithMoreInfo(EspressoLink.android.kt:135)
at androidx.compose.ui.test.junit4.android.EspressoLink_androidKt.runEspressoOnIdle(EspressoLink.android.kt:109)
at androidx.compose.ui.test.junit4.android.EspressoLink.runUntilIdle(EspressoLink.android.kt:78)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitForIdle(AndroidComposeTestRule.android.kt:289)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule.access$waitForIdle(AndroidComposeTestRule.android.kt:155)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$AndroidTestOwner.getRoots(AndroidComposeTestRule.android.kt:441)
at androidx.compose.ui.test.TestContext.getAllSemanticsNodes$ui_test_release(TestOwner.kt:95)
at androidx.compose.ui.test.SemanticsNodeInteractionCollection.fetchSemanticsNodes(SemanticsNodeInteraction.kt:234)
at androidx.compose.ui.test.SemanticsNodeInteractionCollection.fetchSemanticsNodes$default(SemanticsNodeInteraction.kt:227)
at com.idenfy.idenfySdk.flowtests.IdenfyFaceReauthenticationFlowTests$reauthenticationOldStatusReturned_failedStatusReturned$1.invoke(IdenfyFaceReauthenticationFlowTests.kt:145)
at com.idenfy.idenfySdk.flowtests.IdenfyFaceReauthenticationFlowTests$reauthenticationOldStatusReturned_failedStatusReturned$1.invoke(IdenfyFaceReauthenticationFlowTests.kt:144)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntil(AndroidComposeTestRule.android.kt:317)
at com.idenfy.idenfySdk.flowtests.IdenfyFaceReauthenticationFlowTests.reauthenticationOldStatusReturned_failedStatusReturned(IdenfyFaceReauthenticationFlowTests.kt:144)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at androidx.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$AndroidComposeStatement.evaluateInner(AndroidComposeTestRule.android.kt:357)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$AndroidComposeStatement.evaluate(AndroidComposeTestRule.android.kt:346)
at androidx.compose.ui.test.junit4.android.EspressoLink$getStatementFor$1.evaluate(EspressoLink.android.kt:63)
at androidx.compose.ui.test.junit4.IdlingResourceRegistry$getStatementFor$1.evaluate(IdlingResourceRegistry.jvm.kt:160)
at androidx.compose.ui.test.junit4.android.ComposeRootRegistry$getStatementFor$1.evaluate(ComposeRootRegistry.android.kt:150)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:154)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:395)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2252)
Caused by: androidx.test.espresso.IdlingResourceTimeoutException: Wait for [Compose-Espresso link] to become idle timed out
at androidx.test.espresso.IdlingPolicy.handleTimeout(IdlingPolicy.java:16)
at androidx.test.espresso.base.UiControllerImpl$5.resourcesHaveTimedOut(UiControllerImpl.java:4)
at androidx.test.espresso.base.IdlingResourceRegistry$Dispatcher.handleTimeout(IdlingResourceRegistry.java:44)
at androidx.test.espresso.base.IdlingResourceRegistry$Dispatcher.handleMessage(IdlingResourceRegistry.java:12)
at android.os.Handler.dispatchMessage(Handler.java:108)
at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:53)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:155)
at androidx.test.espresso.base.UiControllerImpl.loopMainThreadUntilIdle(UiControllerImpl.java:129)
at androidx.test.espresso.Espresso$1.run(Espresso.java:2)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.Handler.handleCallback(Handler.java:907)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7625)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)
The test seems to get stuck at this line (The composable is definitely visible):
composeTestRule.waitUntil(8000) {
composeTestRule.onAllNodesWithText(faceReauthenticationActivity.getString(R.string.idenfy_camera_onboarding_view_continue_button_title_v2)).fetchSemanticsNodes(false).isNotEmpty()
}
Our dependencies (With 1.0.5 compose version):
//Compose
implementation “androidx.compose.ui:ui:$compose_version”
implementation “androidx.compose.material:material:$compose_version”
implementation “androidx.compose.ui:ui-tooling-preview:$compose_version”
implementation ‘androidx.lifecycle:lifecycle-runtime-ktx:2.4.0’
implementation ‘androidx.activity:activity-compose:1.4.0’
//Compose testing
debugImplementation “androidx.compose.ui:ui-test-manifest:1.0.5"
Is something wrong in our implementation?
EDIT
After some more tries, we discovered the cause of this problem, we have two mutable states:
val currentInstructionDescription: MutableState<String> = remember {
mutableStateOf("")
}
val progress = remember {
mutableStateOf(0.0f)
}
These two states are changing all the time in the view (every second), one of them changes the progress of a progress bar, another updates a text:
LaunchedEffect(key1 = Unit, block = {
val timer = (0..Int.MAX_VALUE)
.asSequence()
.asFlow()
.onEach { delay(1000) }
timer.collect {
progress.value = progressValueInFloat // increasing value
currentInstructionDescription.value = "Custom String"
}
})
Having this code disabled, the tests run fine and idling resources get idled. Maybe there is something wrong with our implementation of this code?