Skip to content

Commit ffce923

Browse files
@W-21933885: [MSDK Android] App Attestation Implementation (Fix TokenMigrationViewActivityTest Reliability For Firebase Test Lab)
1 parent 6cdb0d1 commit ffce923

1 file changed

Lines changed: 101 additions & 3 deletions

File tree

libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/ui/TokenMigrationViewActivityTest.kt

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ import androidx.compose.ui.test.onNodeWithContentDescription
4343
import androidx.lifecycle.ViewModel
4444
import androidx.lifecycle.ViewModelProvider
4545
import androidx.lifecycle.viewmodel.CreationExtras
46+
import androidx.test.espresso.Espresso
47+
import androidx.test.espresso.IdlingPolicies
48+
import androidx.test.espresso.IdlingRegistry
49+
import androidx.test.espresso.IdlingResource
4650
import androidx.test.rule.GrantPermissionRule
4751
import com.salesforce.androidsdk.R
4852
import com.salesforce.androidsdk.app.SalesforceSDKManager
@@ -56,13 +60,93 @@ import org.junit.Before
5660
import org.junit.Ignore
5761
import org.junit.Rule
5862
import org.junit.Test
63+
import org.junit.rules.Timeout
64+
import java.util.concurrent.TimeUnit
65+
66+
/**
67+
* IdlingResource that waits for a condition to become true.
68+
* Used to synchronize ViewModel state changes with test assertions.
69+
*/
70+
class ViewModelIdlingResource(
71+
private val resourceName: String,
72+
private val checkCondition: () -> Boolean
73+
) : IdlingResource {
74+
@Volatile
75+
private var callback: IdlingResource.ResourceCallback? = null
76+
77+
override fun getName(): String = resourceName
78+
79+
override fun isIdleNow(): Boolean {
80+
val idle = checkCondition()
81+
if (idle && callback != null) {
82+
callback?.onTransitionToIdle()
83+
}
84+
return idle
85+
}
86+
87+
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
88+
this.callback = callback
89+
}
90+
}
91+
92+
/**
93+
* Helper function to wait for a condition using IdlingResource.
94+
* Automatically registers and unregisters the idling resource.
95+
* Uses Espresso's built-in synchronization mechanism instead of manual polling.
96+
*
97+
* @param resourceName Name for the idling resource (for debugging)
98+
* @param condition Lambda that returns true when the condition is met
99+
* @param timeoutSeconds Maximum time to wait for the condition (default: 10 seconds)
100+
* @param block Code to execute once the condition is met
101+
*/
102+
inline fun <T> waitForCondition(
103+
resourceName: String,
104+
noinline condition: () -> Boolean,
105+
timeoutSeconds: Long = 10,
106+
block: () -> T
107+
): T {
108+
val idlingResource = ViewModelIdlingResource(resourceName, condition)
109+
110+
// Set custom timeout for this wait operation
111+
val previousTimeout = IdlingPolicies.getMasterIdlingPolicy()
112+
IdlingPolicies.setMasterPolicyTimeout(timeoutSeconds, TimeUnit.SECONDS)
113+
114+
IdlingRegistry.getInstance().register(idlingResource)
115+
try {
116+
// Let Espresso handle the synchronization - it will poll the IdlingResource
117+
// and wait until isIdleNow() returns true, respecting the timeout policy
118+
Espresso.onIdle()
119+
120+
return block()
121+
} catch (e: Exception) {
122+
// Provide better error message if timeout occurs
123+
if (e.message?.contains("IdlingResource") == true ||
124+
e.message?.contains("timeout") == true) {
125+
throw AssertionError("Timeout waiting for condition '$resourceName' after ${timeoutSeconds}s", e)
126+
}
127+
throw e
128+
} finally {
129+
IdlingRegistry.getInstance().unregister(idlingResource)
130+
// Restore previous timeout policy
131+
IdlingPolicies.setMasterPolicyTimeout(
132+
previousTimeout.idleTimeout,
133+
previousTimeout.idleTimeoutUnit
134+
)
135+
}
136+
}
59137

60-
@Ignore
61138
class TokenMigrationViewActivityTest {
62139

63140
@get:Rule
64141
val androidComposeTestRule = createAndroidComposeRule<ComponentActivity>()
65142

143+
/**
144+
* Global timeout rule for all tests in this class.
145+
* Each test will timeout after 30 seconds to accommodate slower Firebase Test Lab devices.
146+
*/
147+
@get:Rule
148+
val globalTimeout: Timeout = Timeout(30, TimeUnit.SECONDS)
149+
66150
// TODO: Remove if when min SDK version is 33
67151
@get:Rule
68152
val permissionRule: GrantPermissionRule = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -172,11 +256,18 @@ class TokenMigrationViewActivityTest {
172256
}
173257
}
174258

259+
// Update background color
175260
androidComposeTestRule.runOnIdle {
176261
backgroundColor.value = White
177262
}
178263

179-
androidComposeTestRule.runOnIdle {
264+
// Wait for ViewModel state to update
265+
waitForCondition(
266+
resourceName = "BackgroundColorUpdatedToWhite",
267+
condition = {
268+
backgroundColor.value == White
269+
}
270+
) {
180271
assertEquals(
181272
"Background color should update to White",
182273
White,
@@ -201,11 +292,18 @@ class TokenMigrationViewActivityTest {
201292
}
202293
}
203294

295+
// Update background color
204296
androidComposeTestRule.runOnIdle {
205297
backgroundColor.value = Red
206298
}
207299

208-
androidComposeTestRule.runOnIdle {
300+
// Wait for ViewModel state to update
301+
waitForCondition(
302+
resourceName = "BackgroundColorUpdatedToRed",
303+
condition = {
304+
backgroundColor.value == Red
305+
}
306+
) {
209307
assertEquals(
210308
"Background color should update to Red",
211309
Red,

0 commit comments

Comments
 (0)