Skip to content

test(e2e): drive legacy sign-in recovery tests through the real sign-in flow - #1394

Open
demolaf wants to merge 3 commits into
mainfrom
fix/legacy-recovery-sheet
Open

demolaf wants to merge 3 commits into
mainfrom
fix/legacy-recovery-sheet

Conversation

@demolaf

@demolaf demolaf commented Sep 14, 2026

Copy link
Copy Markdown
Member

The two legacy sign-in recovery tests drove the sheet through a test-only preview flag that set legacySignInRecovery and isPresented in the same runloop turn, so the recovery sheet asked to present while the auth picker sheet it is nested inside was still animating in. UIKit drops a presentation issued while another is already in progress, which made testLegacyRecoveryEmailLinkOptionNavigatesWithPrefilledEmail fail intermittently on CI, most often as the first test after a test runner relaunch. The .xcresult from a failing run shows the picker fully presented, the app unauthenticated, and no recovery view anywhere in the accessibility hierarchy.

Production never reaches that state, because legacySignInRecovery is only ever set from tryPresentLegacySignInRecovery after a failed sign-in, long after the picker has settled. Rather than paper over the timing, both tests now drive that real path: they seed an account carrying both password and emailLink sign-in methods, which is what makes buildLegacySignInRecovery return a context instead of nil, then fail a password sign-in so the sheet appears the way it does for a real user. The preview flag and its init-time assignment are gone, and the deferred Task { try signOut() } in TestView is now a synchronous call so it can no longer mutate observable auth state mid-presentation.

Verified locally against the auth emulator with five iterations of each test, all green.


Maintainer note: Fixes internal CPRN-452

One coverage note worth flagging: the email/password case can no longer verify the prefill itself. EmailAuthView sits at the picker root rather than being pushed, so it keeps its own @State across the sheet, and the test has necessarily typed the email into that field already in order to trigger the failed sign-in. Asserting the field afterwards therefore passes whether or not recovery does anything. That case now asserts the sheet is dismissed and is named accordingly, and the prefill behaviour is covered by the email link case, where EmailLinkView is built fresh by the navigation destination and its field genuinely starts empty.

…in flow

The legacy sign-in recovery tests set legacySignInRecovery and isPresented in the same runloop turn via a test-only preview flag, so the recovery sheet was requested while the auth picker sheet it is nested inside was still animating in. UIKit drops a presentation issued while another is in progress, which made testLegacyRecoveryEmailLinkOptionNavigatesWithPrefilledEmail fail intermittently on CI.

Production never produces that state, since legacySignInRecovery is only ever set from tryPresentLegacySignInRecovery after a failed sign-in, long after the picker has settled. Both tests now drive that real path instead, seeding an account that carries both password and emailLink sign-in methods so buildLegacySignInRecovery returns a context rather than nil.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the legacy sign-in recovery flow in the Firebase SwiftUI example app and its associated E2E tests. Key changes include moving to synchronous sign-out in the test view to prevent race conditions, updating the test suite to use a more robust recovery sheet presentation, and improving the emulator OOB code fetching logic. Review feedback highlighted several areas where force-unwrapping URLs could lead to runtime crashes, suggesting the use of safer guard-let patterns, and recommended using waitForExistence for UI elements to improve test stability in CI environments.

Comment thread e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift Outdated
Comment thread e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift Outdated
Comment thread e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift Outdated
Wait for the password field rather than asserting bare existence, and guard the URLs built in fetchOobCode and createLegacyRecoveryUser instead of force-unwrapping them, matching the idiom createTestUser already uses.
The email/password case asserted that the email field still held text the test itself had typed into it, which passes whether or not recovery does anything, because EmailAuthView sits at the picker root and keeps its own state across the sheet. It now asserts the sheet is actually dismissed, and is named for what it verifies. Prefill remains covered by the email link case, where EmailLinkView is built fresh by the navigation destination.

The email link assertion now polls for the value rather than reading it once, since the field becomes queryable before onAppear applies the prefill. createLegacyRecoveryUser also asserts the seeded account really reports both sign-in methods, so a change in emulator behaviour fails immediately instead of surfacing later as an unexplained timeout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant