diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt index b3a64b015812..8c90676f07ed 100644 --- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt @@ -38,6 +38,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.ichi2.anki.IntentHandler import com.ichi2.anki.R import com.ichi2.anki.testutil.GrantStoragePermission +import com.ichi2.anki.testutil.awaitResumedActivity import com.ichi2.anki.testutil.closeGetStartedScreenIfExists import com.ichi2.anki.testutil.grantPermissions import com.ichi2.anki.utils.isWindowCompact @@ -64,8 +65,7 @@ class PreferencesNavigationTest { assumeTrue(context.resources.isWindowCompact()) ActivityScenario.launch(IntentHandler::class.java) closeGetStartedScreenIfExists() - onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) - onView(withId(R.id.nav_settings)).perform(click()) + openSettings() onView(withId(com.bytehamster.lib.preferencesearch.R.id.search)).perform(click()) onView(allOf(withId(com.bytehamster.lib.preferencesearch.R.id.search), hasFocus())).perform(typeText("Controls")) // Checking the list of Settings Categories are displayed on the basis of our search "Controls" @@ -91,8 +91,7 @@ class PreferencesNavigationTest { assumeTrue(isTablet(context)) ActivityScenario.launch(IntentHandler::class.java) closeGetStartedScreenIfExists() - onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) - onView(withId(R.id.nav_settings)).perform(click()) + openSettings() onView(withId(com.bytehamster.lib.preferencesearch.R.id.search)).perform(click()) onView(allOf(withId(com.bytehamster.lib.preferencesearch.R.id.search), hasFocus())).perform(typeText("Card")) onView(withText(R.string.card_zoom)).perform(click()) @@ -101,4 +100,11 @@ class PreferencesNavigationTest { pressBack() onView(withClassName(endsWith("PreferencesActivity"))).check(doesNotExist()) } + + /** Opens [PreferencesActivity] via the navigation drawer. */ + private fun openSettings() { + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) + onView(withId(R.id.nav_settings)).perform(click()) + awaitResumedActivity() + } } diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/WaitUtils.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/WaitUtils.kt new file mode 100644 index 000000000000..ff385c2ae6f5 --- /dev/null +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/WaitUtils.kt @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.ichi2.anki.testutil + +import android.app.Activity +import android.os.SystemClock +import androidx.test.platform.app.InstrumentationRegistry +import com.ichi2.anki.TestUtils +import kotlin.test.fail +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +/** + * Polls until [condition] returns true, syncing with the instrumentation between attempts. + * Fails the test with [message] if the condition does not hold within [timeout]. + */ +fun waitUntil( + timeout: Duration = 10.seconds, + message: () -> String, + condition: () -> Boolean, +) { + val instrumentation = InstrumentationRegistry.getInstrumentation() + val deadline = SystemClock.uptimeMillis() + timeout.inWholeMilliseconds + while (SystemClock.uptimeMillis() < deadline) { + if (condition()) return + instrumentation.waitForIdleSync() + SystemClock.sleep(50) + } + fail(message()) +} + +/** + * Polls until an activity of type [T] is resumed. + * Fails the test if [T] does not resume within [timeout]. + */ +inline fun awaitResumedActivity(timeout: Duration = 10.seconds) = + waitUntil( + timeout, + message = { "Timed out waiting for ${T::class.java.simpleName}; resumed = ${TestUtils.activityInstance}" }, + ) { TestUtils.activityInstance is T }