From 60edc2d88b10b37a411c75420283e9d2e154fab2 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo <48495111+demolaf@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:26:53 +0100 Subject: [PATCH 1/2] fix(auth): improve user identifier retrieval (#2314) * fix(auth): improve user identifier retrieval * updates --- .../android/demo/HighLevelApiDemoActivity.kt | 6 ++- .../ui/auth/ui/screens/FirebaseAuthScreen.kt | 6 ++- .../com/firebase/ui/auth/util/UserUtils.kt | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 auth/src/main/java/com/firebase/ui/auth/util/UserUtils.kt diff --git a/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt b/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt index fcae3ea9c..ff008abcf 100644 --- a/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt +++ b/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt @@ -42,6 +42,8 @@ import com.firebase.ui.auth.configuration.theme.AuthUITheme import com.firebase.ui.auth.ui.screens.AuthSuccessUiContext import com.firebase.ui.auth.ui.screens.FirebaseAuthScreen import com.firebase.ui.auth.util.EmailLinkConstants +import com.firebase.ui.auth.util.displayIdentifier +import com.firebase.ui.auth.util.getDisplayEmail import com.google.firebase.auth.actionCodeSettings class HighLevelApiDemoActivity : ComponentActivity() { @@ -211,7 +213,7 @@ private fun AppAuthenticatedContent( when (state) { is AuthState.Success -> { val user = uiContext.authUI.getCurrentUser() - val identifier = user?.email ?: user?.phoneNumber ?: user?.uid.orEmpty() + val identifier = user.displayIdentifier() Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, @@ -263,7 +265,7 @@ private fun AppAuthenticatedContent( } is AuthState.RequiresEmailVerification -> { - val email = uiContext.authUI.getCurrentUser()?.email ?: stringProvider.emailProvider + val email = uiContext.authUI.getCurrentUser().getDisplayEmail(stringProvider.emailProvider) Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt index 5a065400c..21bb3ee7e 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt @@ -78,6 +78,8 @@ import com.firebase.ui.auth.ui.screens.email.EmailAuthScreen import com.firebase.ui.auth.ui.screens.phone.PhoneAuthScreen import com.firebase.ui.auth.util.EmailLinkPersistenceManager import com.firebase.ui.auth.util.SignInPreferenceManager +import com.firebase.ui.auth.util.displayIdentifier +import com.firebase.ui.auth.util.getDisplayEmail import com.google.firebase.auth.AuthCredential import com.google.firebase.auth.AuthResult import com.google.firebase.auth.MultiFactorResolver @@ -733,7 +735,7 @@ private fun AuthSuccessContent( onManageMfa: () -> Unit, ) { val user = authUI.getCurrentUser() - val userIdentifier = user?.email ?: user?.phoneNumber ?: user?.uid.orEmpty() + val userIdentifier = user.displayIdentifier() Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, @@ -783,7 +785,7 @@ private fun EmailVerificationContent( onSignOut: () -> Unit, ) { val user = authUI.getCurrentUser() - val emailLabel = user?.email ?: stringProvider.emailProvider + val emailLabel = user.getDisplayEmail(stringProvider.emailProvider) Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, diff --git a/auth/src/main/java/com/firebase/ui/auth/util/UserUtils.kt b/auth/src/main/java/com/firebase/ui/auth/util/UserUtils.kt new file mode 100644 index 000000000..8e25766e0 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/util/UserUtils.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.util + +import com.google.firebase.auth.FirebaseUser + +/** + * Returns the best available display identifier for the user, trying each field in order: + * email → phoneNumber → displayName → uid. + * + * Each field is checked for blank (not just null) so that an empty string returned by the + * Firebase SDK falls through to the next candidate rather than being displayed as-is. + * Returns an empty string if the user is null. + */ +fun FirebaseUser?.displayIdentifier(): String = + this?.email?.takeIf { it.isNotBlank() } + ?: this?.phoneNumber?.takeIf { it.isNotBlank() } + ?: this?.displayName?.takeIf { it.isNotBlank() } + ?: this?.uid + ?: "" + +/** + * Returns the user's email if it is non-blank, otherwise returns the provided [fallback]. + */ +fun FirebaseUser?.getDisplayEmail(fallback: String): String = + this?.email?.takeIf { it.isNotBlank() } ?: fallback From d1466d14e236de056478472b0c5db525879b5747 Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Fri, 17 Apr 2026 12:18:56 +0100 Subject: [PATCH 2/2] fix: ensure that when selecting phone or email, it routes straight to that screen (#2311) --- .../com/firebase/ui/auth/FirebaseAuthUI.kt | 44 +++---- .../ui/auth/ui/screens/FirebaseAuthScreen.kt | 43 +++++-- .../ui/screens/FirebaseAuthScreenRouteTest.kt | 118 ++++++++++++++++++ .../ui/screens/AnonymousAuthScreenTest.kt | 2 +- .../ui/auth/ui/screens/EmailAuthScreenTest.kt | 118 ++++++++---------- .../auth/ui/screens/GoogleAuthScreenTest.kt | 4 +- 6 files changed, 226 insertions(+), 103 deletions(-) create mode 100644 auth/src/test/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreenRouteTest.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt index 9f829a37f..f01cde6a7 100644 --- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt @@ -25,6 +25,7 @@ import com.google.firebase.Firebase import com.google.firebase.FirebaseApp import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseAuth.AuthStateListener +import com.google.firebase.auth.FirebaseAuth.IdTokenListener import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.auth import kotlinx.coroutines.CancellationException @@ -255,29 +256,8 @@ class FirebaseAuthUI private constructor( fun authStateFlow(): Flow { // Create a flow from FirebaseAuth state listener val firebaseAuthFlow = callbackFlow { - // Set initial state based on current auth state - val initialState = auth.currentUser?.let { user -> - // Check if email verification is required - if (!user.isEmailVerified && - user.email != null && - user.providerData.any { it.providerId == "password" } - ) { - AuthState.RequiresEmailVerification( - user = user, - email = user.email!! - ) - } else { - AuthState.Success(result = null, user = user, isNewUser = false) - } - } ?: AuthState.Idle - - trySend(initialState) - - // Create auth state listener - val authStateListener = AuthStateListener { firebaseAuth -> - val currentUser = firebaseAuth.currentUser - val state = if (currentUser != null) { - // Check if email verification is required + fun buildState(currentUser: FirebaseUser?): AuthState { + return if (currentUser != null) { if (!currentUser.isEmailVerified && currentUser.email != null && currentUser.providerData.any { it.providerId == "password" } @@ -296,15 +276,31 @@ class FirebaseAuthUI private constructor( } else { AuthState.Idle } - trySend(state) + } + + // Set initial state based on current auth state + val initialState = buildState(auth.currentUser) + + trySend(initialState) + + // Create auth state listener + val authStateListener = AuthStateListener { firebaseAuth -> + trySend(buildState(firebaseAuth.currentUser)) + } + + // AuthStateListener does not reliably fire for account linking, but IdTokenListener does. + val idTokenListener = IdTokenListener { firebaseAuth -> + trySend(buildState(firebaseAuth.currentUser)) } // Add listener auth.addAuthStateListener(authStateListener) + auth.addIdTokenListener(idTokenListener) // Remove listener when flow collection is cancelled awaitClose { auth.removeAuthStateListener(authStateListener) + auth.removeIdTokenListener(idTokenListener) } } diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt index 21bb3ee7e..fbf0bed2b 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreen.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -127,6 +128,10 @@ fun FirebaseAuthScreen( val emailLinkFromDifferentDevice = remember { mutableStateOf(null) } val lastSignInPreference = remember { mutableStateOf(null) } + val startRoute = remember(configuration.providers, configuration.isProviderChoiceAlwaysShown) { + getStartRoute(configuration) + } + val skipsMethodPicker = startRoute != AuthRoute.MethodPicker // Load last sign-in preference on launch LaunchedEffect(authState) { @@ -238,7 +243,7 @@ fun FirebaseAuthScreen( ) { NavHost( navController = navController, - startDestination = AuthRoute.MethodPicker.route, + startDestination = startRoute.route, enterTransition = configuration.transitions?.enterTransition ?: { fadeIn(animationSpec = tween(700)) }, @@ -321,7 +326,9 @@ fun FirebaseAuthScreen( }, onCancel = { pendingLinkingCredential.value = null - if (!navController.popBackStack()) { + if (skipsMethodPicker) { + onSignInCancelled() + } else if (!navController.popBackStack()) { navController.navigate(AuthRoute.MethodPicker.route) { popUpTo(AuthRoute.MethodPicker.route) { inclusive = true } launchSingleTop = true @@ -341,7 +348,9 @@ fun FirebaseAuthScreen( onSignInFailure(exception) }, onCancel = { - if (!navController.popBackStack()) { + if (skipsMethodPicker) { + onSignInCancelled() + } else if (!navController.popBackStack()) { navController.navigate(AuthRoute.MethodPicker.route) { popUpTo(AuthRoute.MethodPicker.route) { inclusive = true } launchSingleTop = true @@ -537,7 +546,7 @@ fun FirebaseAuthScreen( if (currentRoute != AuthRoute.Success.route) { navController.navigate(AuthRoute.Success.route) { - popUpTo(AuthRoute.MethodPicker.route) { inclusive = true } + popUpTo(navController.graph.findStartDestination().id) { inclusive = true } launchSingleTop = true } } @@ -550,7 +559,7 @@ fun FirebaseAuthScreen( pendingLinkingCredential.value = null if (currentRoute != AuthRoute.Success.route) { navController.navigate(AuthRoute.Success.route) { - popUpTo(AuthRoute.MethodPicker.route) { inclusive = true } + popUpTo(navController.graph.findStartDestination().id) { inclusive = true } launchSingleTop = true } } @@ -569,9 +578,9 @@ fun FirebaseAuthScreen( pendingResolver.value = null pendingLinkingCredential.value = null lastSuccessfulUserId.value = null - if (currentRoute != AuthRoute.MethodPicker.route) { - navController.navigate(AuthRoute.MethodPicker.route) { - popUpTo(AuthRoute.MethodPicker.route) { inclusive = true } + if (currentRoute != startRoute.route) { + navController.navigate(startRoute.route) { + popUpTo(navController.graph.findStartDestination().id) { inclusive = true } launchSingleTop = true } } @@ -582,9 +591,9 @@ fun FirebaseAuthScreen( pendingResolver.value = null pendingLinkingCredential.value = null lastSuccessfulUserId.value = null - if (currentRoute != AuthRoute.MethodPicker.route) { - navController.navigate(AuthRoute.MethodPicker.route) { - popUpTo(AuthRoute.MethodPicker.route) { inclusive = true } + if (currentRoute != startRoute.route) { + navController.navigate(startRoute.route) { + popUpTo(navController.graph.findStartDestination().id) { inclusive = true } launchSingleTop = true } } @@ -669,6 +678,18 @@ sealed class AuthRoute(val route: String) { object MfaChallenge : AuthRoute("auth_mfa_challenge") } +internal fun getStartRoute(configuration: AuthUIConfiguration): AuthRoute { + if (configuration.isProviderChoiceAlwaysShown || configuration.providers.size != 1) { + return AuthRoute.MethodPicker + } + + return when (configuration.providers.single()) { + is AuthProvider.Email -> AuthRoute.Email + is AuthProvider.Phone -> AuthRoute.Phone + else -> AuthRoute.MethodPicker + } +} + data class AuthSuccessUiContext( val authUI: FirebaseAuthUI, val stringProvider: AuthUIStringProvider, diff --git a/auth/src/test/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreenRouteTest.kt b/auth/src/test/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreenRouteTest.kt new file mode 100644 index 000000000..4108004dd --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/ui/screens/FirebaseAuthScreenRouteTest.kt @@ -0,0 +1,118 @@ +package com.firebase.ui.auth.ui.screens + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.configuration.authUIConfiguration +import com.firebase.ui.auth.configuration.auth_provider.AuthProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) +class FirebaseAuthScreenRouteTest { + + private lateinit var applicationContext: Context + + @Before + fun setUp() { + applicationContext = ApplicationProvider.getApplicationContext() + } + + @Test + fun `single email provider starts at email route`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + } + + assertThat(getStartRoute(configuration)).isEqualTo(AuthRoute.Email) + } + + @Test + fun `single phone provider starts at phone route`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null + ) + ) + } + } + + assertThat(getStartRoute(configuration)).isEqualTo(AuthRoute.Phone) + } + + @Test + fun `single google provider starts at method picker`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Google( + scopes = emptyList(), + serverClientId = "test-client-id" + ) + ) + } + } + + assertThat(getStartRoute(configuration)).isEqualTo(AuthRoute.MethodPicker) + } + + @Test + fun `single email provider shows picker when always shown is enabled`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + isProviderChoiceAlwaysShown = true + } + + assertThat(getStartRoute(configuration)).isEqualTo(AuthRoute.MethodPicker) + } + + @Test + fun `multiple providers start at method picker`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null + ) + ) + } + } + + assertThat(getStartRoute(configuration)).isEqualTo(AuthRoute.MethodPicker) + } +} diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt index 59c5d829a..743a26db4 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/AnonymousAuthScreenTest.kt @@ -167,7 +167,7 @@ class AnonymousAuthScreenTest { @Test fun `anonymous upgrade enabled links new user sign-up and emits RequiresEmailVerification auth state`() { val name = "Anonymous Upgrade User" - val email = "anonymousupgrade@example.com" + val email = "anonymous-upgrade-${System.currentTimeMillis()}@example.com" val password = "Test@123" val configuration = authUIConfiguration { context = applicationContext diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt index 423aa8d62..d438eb45b 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/EmailAuthScreenTest.kt @@ -149,7 +149,7 @@ class EmailAuthScreenTest { } @Test - fun `initial EmailAuthMode is SignIn`() { + fun `single email provider starts on email screen when provider choice always shown is false`() { val configuration = authUIConfiguration { context = applicationContext providers { @@ -167,15 +167,30 @@ class EmailAuthScreenTest { TestFirebaseAuthScreen(configuration = configuration, authUI = authUI) } - // Click on email provider in AuthMethodPicker - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() + assertDirectEmailStart() + } - composeAndroidTestRule.waitForIdle() + @Test + fun `single email provider shows method picker when provider choice always shown is true`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + isCredentialManagerEnabled = false + isProviderChoiceAlwaysShown = true + } - composeAndroidTestRule.onNodeWithText(stringProvider.signInDefault) - .assertIsDisplayed() + composeAndroidTestRule.setContent { + TestFirebaseAuthScreen(configuration = configuration, authUI = authUI) + } + + openEmailProviderFromMethodPicker() } @Test @@ -212,12 +227,7 @@ class EmailAuthScreenTest { currentAuthState = authState } - // Click on email provider in AuthMethodPicker - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + assertDirectEmailStart() composeAndroidTestRule.onNodeWithText(stringProvider.emailHint) .performScrollTo() @@ -306,12 +316,7 @@ class EmailAuthScreenTest { currentAuthState = authState } - // Click on email provider in AuthMethodPicker - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + assertDirectEmailStart() composeAndroidTestRule.onNodeWithText(stringProvider.emailHint) .performScrollTo() @@ -381,12 +386,7 @@ class EmailAuthScreenTest { currentAuthState = authState } - // Click on email provider in AuthMethodPicker - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + assertDirectEmailStart() composeAndroidTestRule.onNodeWithText(stringProvider.signInDefault) .assertIsDisplayed() @@ -471,12 +471,7 @@ class EmailAuthScreenTest { currentAuthState = authState } - // Click on email provider in AuthMethodPicker - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + assertDirectEmailStart() composeAndroidTestRule.onNodeWithText(stringProvider.signInDefault) .assertIsDisplayed() @@ -569,15 +564,7 @@ class EmailAuthScreenTest { currentAuthState = authState } - // Click on email provider in AuthMethodPicker - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() - - composeAndroidTestRule.onNodeWithText(stringProvider.signInDefault) - .assertIsDisplayed() + assertDirectEmailStart() // Click "Sign in with email link" button to switch to email link mode composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmailLink.uppercase()) @@ -744,6 +731,7 @@ class EmailAuthScreenTest { ) ) } + isProviderChoiceAlwaysShown = true } // Track auth state changes @@ -758,12 +746,7 @@ class EmailAuthScreenTest { // STEP 1: Sign up and verify credential saved println("TEST: Starting sign-up flow...") - // Click on email provider - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + openEmailProviderFromMethodPicker() // Click sign-up composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase()) @@ -816,13 +799,9 @@ class EmailAuthScreenTest { // STEP 3: Navigate to SignInUI screen to trigger credential retrieval println("TEST: Navigating to sign-in screen to trigger credential retrieval...") - // Click on email provider to show SignInUI, which will trigger auto-retrieval - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - composeAndroidTestRule.waitForIdle() shadowOf(Looper.getMainLooper()).idle() + clickEmailProviderFromMethodPicker() // SignInUI's LaunchedEffect should now trigger credential retrieval and auto-sign-in println("TEST: Waiting for automatic credential retrieval and auto-sign-in...") @@ -877,6 +856,7 @@ class EmailAuthScreenTest { ) ) } + isProviderChoiceAlwaysShown = true } var currentAuthState: AuthState = AuthState.Idle @@ -890,11 +870,7 @@ class EmailAuthScreenTest { // STEP 1: Sign up and save credential println("TEST: Starting sign-up flow...") - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + openEmailProviderFromMethodPicker() composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase()) .assertIsDisplayed() @@ -940,12 +916,9 @@ class EmailAuthScreenTest { // STEP 3: Navigate to SignInUI to trigger credential retrieval println("TEST: Navigating to sign-in screen...") - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - composeAndroidTestRule.waitForIdle() shadowOf(Looper.getMainLooper()).idle() + clickEmailProviderFromMethodPicker() println("TEST: Waiting for automatic credential retrieval and auto-sign-in...") @@ -997,11 +970,7 @@ class EmailAuthScreenTest { } // Sign up - composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) - .assertIsDisplayed() - .performClick() - - composeAndroidTestRule.waitForIdle() + assertDirectEmailStart() composeAndroidTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase()) .assertIsDisplayed() @@ -1078,4 +1047,21 @@ class EmailAuthScreenTest { } } } + + private fun assertDirectEmailStart() { + composeAndroidTestRule.waitForIdle() + composeAndroidTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + } + + private fun openEmailProviderFromMethodPicker() { + clickEmailProviderFromMethodPicker() + assertDirectEmailStart() + } + + private fun clickEmailProviderFromMethodPicker() { + composeAndroidTestRule.onNodeWithText(stringProvider.signInWithEmail) + .assertIsDisplayed() + .performClick() + } } diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt index 64103ec32..0bbdc1372 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/ui/screens/GoogleAuthScreenTest.kt @@ -149,13 +149,15 @@ class GoogleAuthScreenTest { @Test fun `anonymous upgrade with google links anonymous user and emits Success auth state`() = runTest { - val email = "anonymousupgrade@example.com" + val email = "anonymous-google-upgrade-${System.currentTimeMillis()}@example.com" + val sub = "anonymous-google-upgrade-${System.nanoTime()}" val name = "Anonymous Upgrade User" val photoUrl = "https://example.com/avatar.jpg" // Generate a JWT token for the Google account val mockIdToken = generateMockGoogleIdToken( email = email, + sub = sub, name = name, photoUrl = photoUrl )