@@ -85,8 +85,11 @@ import io.mockk.verify
8585import org.junit.Assert.assertEquals
8686import org.junit.Assert.assertFalse
8787import org.junit.Assert.assertTrue
88+ import org.junit.Rule
8889import org.junit.Test
90+ import org.junit.rules.Timeout
8991import org.junit.runner.RunWith
92+ import java.util.concurrent.TimeUnit
9093
9194/* *
9295 * IdlingResource that waits for a condition to become true.
@@ -117,15 +120,32 @@ class ViewModelIdlingResource(
117120/* *
118121 * Helper function to wait for a condition using IdlingResource.
119122 * Automatically registers and unregisters the idling resource.
123+ *
124+ * @param resourceName Name for the idling resource (for debugging)
125+ * @param condition Lambda that returns true when the condition is met
126+ * @param timeoutSeconds Maximum time to wait for the condition (default: 10 seconds)
127+ * @param block Code to execute once the condition is met
120128 */
121129inline fun <T > waitForCondition (
122130 resourceName : String ,
123131 noinline condition : () -> Boolean ,
132+ timeoutSeconds : Long = 10,
124133 block : () -> T
125134): T {
126135 val idlingResource = ViewModelIdlingResource (resourceName, condition)
127136 IdlingRegistry .getInstance().register(idlingResource)
128137 try {
138+ val startTime = System .currentTimeMillis()
139+ val timeoutMillis = TimeUnit .SECONDS .toMillis(timeoutSeconds)
140+
141+ // Wait for condition with timeout
142+ while (! condition()) {
143+ if (System .currentTimeMillis() - startTime > timeoutMillis) {
144+ throw AssertionError (" Timeout waiting for condition '$resourceName ' after ${timeoutSeconds} s" )
145+ }
146+ Thread .sleep(50 ) // Poll every 50ms
147+ }
148+
129149 return block()
130150 } finally {
131151 IdlingRegistry .getInstance().unregister(idlingResource)
@@ -135,6 +155,13 @@ inline fun <T> waitForCondition(
135155@RunWith(AndroidJUnit4 ::class )
136156class ScreenLockActivityScenarioTest {
137157
158+ /* *
159+ * Global timeout rule for all tests in this class.
160+ * Each test will timeout after 30 seconds to accommodate slower Firebase Test Lab devices.
161+ */
162+ @get:Rule
163+ val globalTimeout: Timeout = Timeout (30 , TimeUnit .SECONDS )
164+
138165 @Test
139166 fun screenLockActivity_appliesDefaults_whenCreated () {
140167 launch<ScreenLockActivity >(
@@ -707,7 +734,7 @@ class ScreenLockActivityScenarioTest {
707734 errString = errorString
708735 )
709736
710- // Wait for accessibility event to be sent
737+ // Wait for accessibility event to be sent (longer timeout for accessibility)
711738 waitForCondition(
712739 resourceName = " AccessibilityEventSent" ,
713740 condition = {
@@ -717,7 +744,8 @@ class ScreenLockActivityScenarioTest {
717744 } catch (e: AssertionError ) {
718745 false
719746 }
720- }
747+ },
748+ timeoutSeconds = 15
721749 ) {
722750 verify(exactly = 1 ) { accessibilityManager.sendAccessibilityEvent(capture(capturingSlot)) }
723751 assertTrue(capturingSlot.captured.text.toString().contains(authenticationErrorString))
@@ -764,7 +792,7 @@ class ScreenLockActivityScenarioTest {
764792 errString = errorString
765793 )
766794
767- // Wait for accessibility event to be sent
795+ // Wait for accessibility event to be sent (longer timeout for accessibility)
768796 waitForCondition(
769797 resourceName = " AccessibilityEventSent" ,
770798 condition = {
@@ -774,7 +802,8 @@ class ScreenLockActivityScenarioTest {
774802 } catch (e: AssertionError ) {
775803 false
776804 }
777- }
805+ },
806+ timeoutSeconds = 15
778807 ) {
779808 verify(exactly = 1 ) { accessibilityManager.sendAccessibilityEvent(capture(capturingSlot)) }
780809 assertTrue(capturingSlot.captured.text.toString().contains(authenticationErrorString))
@@ -820,7 +849,7 @@ class ScreenLockActivityScenarioTest {
820849 screenLockManager = screenLockManager,
821850 )
822851
823- // Wait for accessibility event to be sent
852+ // Wait for accessibility event to be sent (longer timeout for accessibility)
824853 waitForCondition(
825854 resourceName = " AccessibilityEventSent" ,
826855 condition = {
@@ -830,7 +859,8 @@ class ScreenLockActivityScenarioTest {
830859 } catch (e: AssertionError ) {
831860 false
832861 }
833- }
862+ },
863+ timeoutSeconds = 15
834864 ) {
835865 verify(exactly = 1 ) { accessibilityManager.sendAccessibilityEvent(capture(capturingSlot)) }
836866 assertTrue(capturingSlot.captured.text.toString().contains(activity.getString(sf__screen_lock_auth_success)))
@@ -874,7 +904,7 @@ class ScreenLockActivityScenarioTest {
874904 screenLockManager = null ,
875905 )
876906
877- // Wait for accessibility event to be sent
907+ // Wait for accessibility event to be sent (longer timeout for accessibility)
878908 waitForCondition(
879909 resourceName = " AccessibilityEventSent" ,
880910 condition = {
@@ -884,7 +914,8 @@ class ScreenLockActivityScenarioTest {
884914 } catch (e: AssertionError ) {
885915 false
886916 }
887- }
917+ },
918+ timeoutSeconds = 15
888919 ) {
889920 verify(exactly = 1 ) { accessibilityManager.sendAccessibilityEvent(capture(capturingSlot)) }
890921 assertTrue(capturingSlot.captured.text.toString().contains(activity.getString(sf__screen_lock_auth_success)))
@@ -928,7 +959,7 @@ class ScreenLockActivityScenarioTest {
928959 sdkConfiguration = AndroidSdkConfigurationQ
929960 )
930961
931- // Wait for accessibility event to be sent
962+ // Wait for accessibility event to be sent (longer timeout for accessibility)
932963 waitForCondition(
933964 resourceName = " AccessibilityEventSent" ,
934965 condition = {
@@ -938,7 +969,8 @@ class ScreenLockActivityScenarioTest {
938969 } catch (e: AssertionError ) {
939970 false
940971 }
941- }
972+ },
973+ timeoutSeconds = 15
942974 ) {
943975 verify(exactly = 1 ) { accessibilityManager.sendAccessibilityEvent(capture(capturingSlot)) }
944976 assertTrue(capturingSlot.captured.text.toString().contains(activity.getString(sf__screen_lock_auth_success)))
0 commit comments