diff --git a/app/src/androidTest/java/com/escodro/alkaa/NotificationFlowTest.kt b/app/src/androidTest/java/com/escodro/alkaa/NotificationFlowTest.kt index 97fbee2e6..d45081a07 100644 --- a/app/src/androidTest/java/com/escodro/alkaa/NotificationFlowTest.kt +++ b/app/src/androidTest/java/com/escodro/alkaa/NotificationFlowTest.kt @@ -3,6 +3,7 @@ package com.escodro.alkaa import android.app.Notification import androidx.annotation.StringRes import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText @@ -16,6 +17,8 @@ import com.escodro.local.model.Task import com.escodro.local.provider.DaoProvider import com.escodro.task.R import com.escodro.test.DisableAnimationsRule +import com.escodro.test.waitUntilExists +import com.escodro.test.waitUntilNotExists import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.After @@ -96,6 +99,9 @@ internal class NotificationFlowTest : KoinTest { // Run the PendingIntent in the notification notificationManager!!.activeNotifications.first().notification.contentIntent.send() + // Wait until the title is displayed + composeTestRule.waitUntilExists(hasText(name)) + // Validate the task detail was opened composeTestRule.onNodeWithText(name).assertIsDisplayed() composeTestRule @@ -151,8 +157,10 @@ internal class NotificationFlowTest : KoinTest { // Run the PendingIntent in the "Done" action button notificationManager!!.activeNotifications.first().notification.actions[1].actionIntent.send() + // Wait until the task is complete and no longer visible + composeTestRule.waitUntilNotExists(hasText(name)) + // Validate the task is now updated as "completed" - Thread.sleep(300) val task = daoProvider.getTaskDao().getTaskById(id) assertTrue(task!!.completed) diff --git a/app/src/androidTest/java/com/escodro/alkaa/SearchFlowTest.kt b/app/src/androidTest/java/com/escodro/alkaa/SearchFlowTest.kt index afea6c197..813a8b6b5 100644 --- a/app/src/androidTest/java/com/escodro/alkaa/SearchFlowTest.kt +++ b/app/src/androidTest/java/com/escodro/alkaa/SearchFlowTest.kt @@ -2,6 +2,7 @@ package com.escodro.alkaa import androidx.annotation.StringRes import androidx.compose.ui.test.hasSetTextAction +import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithContentDescription @@ -14,6 +15,8 @@ import com.escodro.alkaa.navigation.NavGraph import com.escodro.designsystem.AlkaaTheme import com.escodro.local.provider.DaoProvider import com.escodro.test.DisableAnimationsRule +import com.escodro.test.waitUntilExists +import com.escodro.test.waitUntilNotExists import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before @@ -75,7 +78,8 @@ internal class SearchFlowTest : KoinTest { onNode(hasSetTextAction()).performTextInput(query) onAllNodesWithText(text = query, useUnmergedTree = true)[1].assertExists() - Thread.sleep(1000) + // Wait until the task is shown + waitUntilExists(hasText(FAKE_TASKS[0].title)) // Drop the first task and validate others are not shown FAKE_TASKS.drop(1).forEach { task -> @@ -90,7 +94,8 @@ internal class SearchFlowTest : KoinTest { with(composeTestRule) { onNode(hasSetTextAction()).performTextInput("query") - Thread.sleep(1000) + // Wait until the first task is not visible + waitUntilNotExists(hasText(FAKE_TASKS[0].title)) FAKE_TASKS.forEach { task -> // Validate all tasks are shown diff --git a/app/src/androidTest/java/com/escodro/alkaa/TaskFlowTest.kt b/app/src/androidTest/java/com/escodro/alkaa/TaskFlowTest.kt index 6d8bd5ae8..bb255936f 100644 --- a/app/src/androidTest/java/com/escodro/alkaa/TaskFlowTest.kt +++ b/app/src/androidTest/java/com/escodro/alkaa/TaskFlowTest.kt @@ -3,6 +3,7 @@ package com.escodro.alkaa import androidx.annotation.StringRes import androidx.compose.ui.test.assertIsSelected import androidx.compose.ui.test.hasSetTextAction +import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText @@ -11,6 +12,7 @@ import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTextReplacement import androidx.test.platform.app.InstrumentationRegistry import com.escodro.alkaa.fake.CoroutinesDebouncerFake +import com.escodro.alkaa.model.HomeSection import com.escodro.alkaa.navigation.NavGraph import com.escodro.core.coroutines.CoroutineDebouncer import com.escodro.designsystem.AlkaaTheme @@ -19,6 +21,7 @@ import com.escodro.local.provider.DaoProvider import com.escodro.test.DisableAnimationsRule import com.escodro.test.Events import com.escodro.test.onChip +import com.escodro.test.waitUntilExists import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before @@ -188,6 +191,7 @@ internal class TaskFlowTest : KoinTest { ).performClick() // Wait the list to be loaded - Thread.sleep(1000) + val taskTitle = context.getString(HomeSection.Tasks.title) + composeTestRule.waitUntilExists(hasText(taskTitle)) } } diff --git a/libraries/test/build.gradle.kts b/libraries/test/build.gradle.kts index dd3c0efd0..7432550c9 100644 --- a/libraries/test/build.gradle.kts +++ b/libraries/test/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { api(libs.coroutines.test) - implementation(libs.compose.uitest) { + implementation(libs.bundles.composetest) { exclude(group = "androidx.core", module = "core-ktx") exclude(group = "androidx.fragment", module = "fragment") exclude(group = "androidx.customview", module = "customview") diff --git a/libraries/test/src/main/java/com/escodro/test/ComposeContentTestRuleExtensions.kt b/libraries/test/src/main/java/com/escodro/test/ComposeContentTestRuleExtensions.kt new file mode 100644 index 000000000..2618e8660 --- /dev/null +++ b/libraries/test/src/main/java/com/escodro/test/ComposeContentTestRuleExtensions.kt @@ -0,0 +1,27 @@ +package com.escodro.test + +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.junit4.ComposeContentTestRule + +/** + * Waits until the given matcher satisfied or until the [timeoutMillis] is reached. + * + * See: https://medium.com/androiddevelopers/alternatives-to-idling-resources-in-compose-tests-8ae71f9fc473 + */ +fun ComposeContentTestRule.waitUntilExists( + matcher: SemanticsMatcher, + timeoutMillis: Long = 3_000L +) = waitUntilNodeCount(matcher = matcher, count = 1, timeoutMillis = timeoutMillis) + +fun ComposeContentTestRule.waitUntilNotExists( + matcher: SemanticsMatcher, + timeoutMillis: Long = 3_000L +) = waitUntilNodeCount(matcher = matcher, count = 0, timeoutMillis = timeoutMillis) + +private fun ComposeContentTestRule.waitUntilNodeCount( + matcher: SemanticsMatcher, + count: Int, + timeoutMillis: Long = 3_000L +) = waitUntil(timeoutMillis) { + onAllNodes(matcher).fetchSemanticsNodes().size == count +}