Skip to content

Commit c77a7d7

Browse files
Merge branch 'development' into improve-tests
2 parents 4f27e15 + 4a52b73 commit c77a7d7

11 files changed

Lines changed: 173 additions & 86 deletions

File tree

.github/workflows/swiftui-auth.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,7 @@ jobs:
189189
xcodebuild test-without-building \
190190
-scheme FirebaseSwiftUIExampleUITests \
191191
-destination 'platform=iOS Simulator,name=iPhone 17' \
192-
-parallel-testing-enabled YES \
193-
-maximum-concurrent-test-simulator-destinations 2 \
192+
-parallel-testing-enabled NO \
194193
-enableCodeCoverage YES \
195194
-resultBundlePath FirebaseSwiftUIExampleUITests.xcresult | tee FirebaseSwiftUIExampleUITests.log | xcpretty --test --color --simple
196195

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ extension AuthPickerView: View {
161161
VStack {
162162
authService.renderButtons()
163163
}
164-
.padding(.horizontal, proxy.size.width * 0.18)
164+
.padding(.horizontal, proxy.size.width * 0.14)
165165
}
166166
}
167167

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAEnrolmentView.swift

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ private enum FocusableField: Hashable {
2626
@MainActor
2727
public struct MFAEnrolmentView {
2828
@Environment(AuthService.self) private var authService
29+
@Environment(\.reportError) private var reportError
2930

3031
@State private var selectedFactorType: SecondFactorType = .sms
3132
@State private var phoneNumber = ""
@@ -81,12 +82,16 @@ public struct MFAEnrolmentView {
8182
isLoading = true
8283
defer { isLoading = false }
8384

84-
let session = try await authService.startMfaEnrollment(
85-
type: selectedFactorType,
86-
accountName: authService.currentUser?.email,
87-
issuer: authService.configuration.mfaIssuer
88-
)
89-
currentSession = session
85+
do {
86+
let session = try await authService.startMfaEnrollment(
87+
type: selectedFactorType,
88+
accountName: authService.currentUser?.email,
89+
issuer: authService.configuration.mfaIssuer
90+
)
91+
currentSession = session
92+
} catch {
93+
reportError?(error)
94+
}
9095
}
9196
}
9297

@@ -97,23 +102,27 @@ public struct MFAEnrolmentView {
97102
isLoading = true
98103
defer { isLoading = false }
99104

100-
let fullPhoneNumber = selectedCountry.dialCode + phoneNumber
101-
let verificationId = try await authService.sendSmsVerificationForEnrollment(
102-
session: session,
103-
phoneNumber: fullPhoneNumber
104-
)
105-
// Update session status
106-
currentSession = EnrollmentSession(
107-
id: session.id,
108-
type: session.type,
109-
session: session.session,
110-
totpInfo: session.totpInfo,
111-
phoneNumber: fullPhoneNumber,
112-
verificationId: verificationId,
113-
status: .verificationSent,
114-
createdAt: session.createdAt,
115-
expiresAt: session.expiresAt
116-
)
105+
do {
106+
let fullPhoneNumber = selectedCountry.dialCode + phoneNumber
107+
let verificationId = try await authService.sendSmsVerificationForEnrollment(
108+
session: session,
109+
phoneNumber: fullPhoneNumber
110+
)
111+
// Update session status
112+
currentSession = EnrollmentSession(
113+
id: session.id,
114+
type: session.type,
115+
session: session.session,
116+
totpInfo: session.totpInfo,
117+
phoneNumber: fullPhoneNumber,
118+
verificationId: verificationId,
119+
status: .verificationSent,
120+
createdAt: session.createdAt,
121+
expiresAt: session.expiresAt
122+
)
123+
} catch {
124+
reportError?(error)
125+
}
117126
}
118127
}
119128

@@ -124,18 +133,22 @@ public struct MFAEnrolmentView {
124133
isLoading = true
125134
defer { isLoading = false }
126135

127-
let code = session.type == .sms ? verificationCode : totpCode
128-
try await authService.completeEnrollment(
129-
session: session,
130-
verificationId: session.verificationId,
131-
verificationCode: code,
132-
displayName: displayName
133-
)
134-
135-
// Reset form state on success
136-
resetForm()
137-
138-
authService.navigator.clear()
136+
do {
137+
let code = session.type == .sms ? verificationCode : totpCode
138+
try await authService.completeEnrollment(
139+
session: session,
140+
verificationId: session.verificationId,
141+
verificationCode: code,
142+
displayName: displayName
143+
)
144+
145+
// Reset form state on success
146+
resetForm()
147+
148+
authService.navigator.clear()
149+
} catch {
150+
reportError?(error)
151+
}
139152
}
140153
}
141154

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAManagementView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extension MultiFactorInfo: Identifiable {
2323
@MainActor
2424
public struct MFAManagementView {
2525
@Environment(AuthService.self) private var authService
26+
@Environment(\.reportError) private var reportError
2627

2728
@State private var enrolledFactors: [MultiFactorInfo] = []
2829
@State private var isLoading = false
@@ -43,6 +44,7 @@ public struct MFAManagementView {
4344
enrolledFactors = freshFactors
4445
isLoading = false
4546
} catch {
47+
reportError?(error)
4648
isLoading = false
4749
}
4850
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAResolutionView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ private enum FocusableField: Hashable {
2424
@MainActor
2525
public struct MFAResolutionView {
2626
@Environment(AuthService.self) private var authService
27+
@Environment(\.reportError) private var reportError
2728

2829
@State private var verificationCode = ""
2930
@State private var totpCode = ""
@@ -72,6 +73,7 @@ public struct MFAResolutionView {
7273
self.verificationId = verificationId
7374
isLoading = false
7475
} catch {
76+
reportError?(error)
7577
isLoading = false
7678
}
7779
}
@@ -93,6 +95,7 @@ public struct MFAResolutionView {
9395
authService.navigator.clear()
9496
isLoading = false
9597
} catch {
98+
reportError?(error)
9699
isLoading = false
97100
}
98101
}

FirebaseSwiftUI/FirebaseFacebookSwiftUI/Sources/Services/FacebookProviderAuthUI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public class FacebookProviderSwift: CredentialAuthProviderSwift {
105105
"`rawNonce` has not been generated for Facebook limited login"
106106
)
107107
}
108-
let credential = OAuthProvider.credential(withProviderID: providerId,
108+
let credential = OAuthProvider.credential(providerID: .facebook,
109109
idToken: idToken.tokenString,
110110
rawNonce: nonce)
111111
return credential

e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExample.xcodeproj/xcshareddata/xcschemes/FirebaseSwiftUIExample.xcscheme

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,25 @@
2828
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2929
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
3030
shouldUseLaunchSchemeArgsEnv = "YES"
31-
shouldAutocreateTestPlan = "YES">
31+
shouldAutocreateTestPlan = "YES"
32+
codeCoverageEnabled = "NO"
33+
onlyGenerateCoverageForSpecifiedTargets = "NO">
34+
<TestPlans>
35+
</TestPlans>
36+
<Testables>
37+
<TestableReference
38+
skipped = "NO"
39+
parallelizable = "NO"
40+
testExecutionOrdering = "random">
41+
<BuildableReference
42+
BuildableIdentifier = "primary"
43+
BlueprintIdentifier = "46F89C412D64A86D000F8BC0"
44+
BuildableName = "FirebaseSwiftUIExampleUITests.xctest"
45+
BlueprintName = "FirebaseSwiftUIExampleUITests"
46+
ReferencedContainer = "container:FirebaseSwiftUIExample.xcodeproj">
47+
</BuildableReference>
48+
</TestableReference>
49+
</Testables>
3250
</TestAction>
3351
<LaunchAction
3452
buildConfiguration = "Debug"

e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExample/TestView.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ struct TestView: View {
3434

3535
init() {
3636
Auth.auth().useEmulator(withHost: "localhost", port: 9099)
37-
38-
Auth.auth().settings?.isAppVerificationDisabledForTesting = true
3937
Task {
4038
try signOut()
4139
}

e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/FirebaseSwiftUIExampleUITests.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,6 @@ final class FirebaseSwiftUIExampleUITests: XCTestCase {
175175
XCTAssertTrue(signUpButton.exists, "Sign-Up button should exist")
176176
signUpButton.tap()
177177

178-
// Wait for the auth screen to disappear (email field should no longer exist)
179-
let emailFieldDisappeared = NSPredicate(format: "exists == false")
180-
let expectation = XCTNSPredicateExpectation(
181-
predicate: emailFieldDisappeared,
182-
object: emailField
183-
)
184-
let result = XCTWaiter().wait(for: [expectation], timeout: 10.0)
185-
XCTAssertEqual(result, .completed, "Email field should disappear after sign-up")
186-
187178
// Wait for user creation and signed-in view to appear
188179
let signedInText = app.staticTexts["signed-in-text"]
189180
XCTAssertTrue(

e2eTest/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,24 @@
2222
import XCTest
2323

2424
final class MFAEnrollmentUITests: XCTestCase {
25+
var app: XCUIApplication!
26+
2527
override func setUpWithError() throws {
2628
continueAfterFailure = false
2729
}
30+
31+
override func tearDownWithError() throws {
32+
// Clean up: Terminate app
33+
if let app = app {
34+
app.terminate()
35+
}
36+
app = nil
37+
38+
// Small delay between tests to allow emulator to settle
39+
Thread.sleep(forTimeInterval: 0.5)
40+
41+
try super.tearDownWithError()
42+
}
2843

2944
// MARK: - MFA Management Navigation Tests
3045

@@ -35,7 +50,7 @@ final class MFAEnrollmentUITests: XCTestCase {
3550
// Create user in test runner before launching app
3651
try await createTestUser(email: email)
3752

38-
let app = createTestApp(mfaEnabled: true)
53+
app = createTestApp(mfaEnabled: true)
3954
app.launch()
4055

4156
// Sign in first to access MFA management
@@ -67,7 +82,7 @@ final class MFAEnrollmentUITests: XCTestCase {
6782
// Create user in test runner before launching app
6883
try await createTestUser(email: email)
6984

70-
let app = createTestApp(mfaEnabled: true)
85+
app = createTestApp(mfaEnabled: true)
7186
app.launch()
7287

7388
// Sign in and navigate to MFA management
@@ -102,7 +117,7 @@ final class MFAEnrollmentUITests: XCTestCase {
102117
// Create user in test runner before launching app
103118
try await createTestUser(email: email)
104119

105-
let app = createTestApp(mfaEnabled: true)
120+
app = createTestApp(mfaEnabled: true)
106121
app.launch()
107122

108123
// Navigate to MFA enrollment
@@ -131,7 +146,7 @@ final class MFAEnrollmentUITests: XCTestCase {
131146
// Create user in test runner before launching app
132147
try await createTestUser(email: email)
133148

134-
let app = createTestApp(mfaEnabled: true)
149+
app = createTestApp(mfaEnabled: true)
135150
app.launch()
136151

137152
// Navigate to MFA enrollment
@@ -168,7 +183,7 @@ final class MFAEnrollmentUITests: XCTestCase {
168183
let email = createEmail()
169184
try await createTestUser(email: email, verifyEmail: true)
170185

171-
let app = createTestApp(mfaEnabled: true)
186+
app = createTestApp(mfaEnabled: true)
172187
app.launch()
173188

174189
// 2) Sign in to reach SignedInView
@@ -196,7 +211,7 @@ final class MFAEnrollmentUITests: XCTestCase {
196211
// 6) Select UK country code and enter phone number (without dial code)
197212
// Find and tap the country selector - try multiple approaches since it's embedded in the
198213
// TextField
199-
let countrySelector = app.staticTexts["+1"].firstMatch
214+
let countrySelector = app.buttons["phone-number-field"].firstMatch
200215
XCTAssertTrue(countrySelector.waitForExistence(timeout: 5))
201216

202217
countrySelector.tap()
@@ -210,8 +225,13 @@ final class MFAEnrollmentUITests: XCTestCase {
210225
// Enter phone number (without dial code)
211226
let phoneField = app.textFields["phone-number-field"]
212227
XCTAssertTrue(phoneField.waitForExistence(timeout: 10))
213-
let phoneNumberWithoutDialCode = "7444555666"
214-
try pasteIntoField(phoneField, text: phoneNumberWithoutDialCode, app: app)
228+
// Generate unique phone number using timestamp to avoid conflicts between tests
229+
let uniqueId = Int(Date().timeIntervalSince1970 * 1000) % 1000000
230+
let phoneNumberWithoutDialCode = "7\(String(format: "%09d", uniqueId))"
231+
UIPasteboard.general.string = phoneNumberWithoutDialCode
232+
phoneField.tap()
233+
phoneField.press(forDuration: 1.2)
234+
app.menuItems["Paste"].tap()
215235

216236
let displayNameField = app.textFields["display-name-field"]
217237
XCTAssertTrue(displayNameField.waitForExistence(timeout: 10))
@@ -223,15 +243,31 @@ final class MFAEnrollmentUITests: XCTestCase {
223243
sendCodeButton.tap()
224244

225245
// 7) Retrieve verification code from the Auth Emulator and complete setup
226-
let verificationCodeField = app.textFields["verification-code-field"]
227-
XCTAssertTrue(verificationCodeField.waitForExistence(timeout: 15))
246+
let verificationCodeField1 = app.otherElements["Digit 1 of 6"].textFields.firstMatch
247+
let verificationCodeField2 = app.otherElements["Digit 2 of 6"].textFields.firstMatch
248+
let verificationCodeField3 = app.otherElements["Digit 3 of 6"].textFields.firstMatch
249+
let verificationCodeField4 = app.otherElements["Digit 4 of 6"].textFields.firstMatch
250+
let verificationCodeField5 = app.otherElements["Digit 5 of 6"].textFields.firstMatch
251+
let verificationCodeField6 = app.otherElements["Digit 6 of 6"].textFields.firstMatch
252+
XCTAssertTrue(verificationCodeField1.waitForExistence(timeout: 15))
228253

229254
// Fetch the latest SMS verification code generated by the emulator for this phone number
230255
// The emulator stores the full phone number with dial code
231256
let fullPhoneNumber = "+44\(phoneNumberWithoutDialCode)"
232257
let code = try await getLastSmsCode(specificPhone: fullPhoneNumber)
233258

234-
try pasteIntoField(verificationCodeField, text: code, app: app)
259+
// Paste each digit into the corresponding text field
260+
let codeDigits = Array(code)
261+
let fields = [verificationCodeField1, verificationCodeField2, verificationCodeField3,
262+
verificationCodeField4, verificationCodeField5, verificationCodeField6]
263+
264+
for (index, digit) in codeDigits.enumerated() where index < fields.count {
265+
let field = fields[index]
266+
UIPasteboard.general.string = String(digit)
267+
field.tap()
268+
field.press(forDuration: 1.2)
269+
app.menuItems["Paste"].tap()
270+
}
235271

236272
// Test resend code button exists
237273
let resendButton = app.buttons["resend-code-button"]
@@ -276,7 +312,7 @@ final class MFAEnrollmentUITests: XCTestCase {
276312
// Create user in test runner before launching app (with email verification)
277313
try await createTestUser(email: email, verifyEmail: true)
278314

279-
let app = createTestApp(mfaEnabled: true)
315+
app = createTestApp(mfaEnabled: true)
280316
app.launch()
281317

282318
// Navigate to MFA enrollment and select TOTP
@@ -335,7 +371,7 @@ final class MFAEnrollmentUITests: XCTestCase {
335371
// Create user in test runner before launching app
336372
try await createTestUser(email: email)
337373

338-
let app = createTestApp(mfaEnabled: true)
374+
app = createTestApp(mfaEnabled: true)
339375
app.launch()
340376

341377
// Navigate to MFA enrollment
@@ -355,7 +391,7 @@ final class MFAEnrollmentUITests: XCTestCase {
355391
// Create user in test runner before launching app
356392
try await createTestUser(email: email)
357393

358-
let app = createTestApp(mfaEnabled: true)
394+
app = createTestApp(mfaEnabled: true)
359395
app.launch()
360396

361397
// Navigate to MFA enrollment

0 commit comments

Comments
 (0)