diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInlineVerificationViewSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInlineVerificationViewSnapshotTests.swift index 4a44b84dba4..172cc8cf987 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInlineVerificationViewSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInlineVerificationViewSnapshotTests.swift @@ -14,6 +14,18 @@ import UIKit @MainActor class LinkInlineVerificationViewSnapshotTests: STPSnapshotTestCase { private let frame = CGRect(x: 0, y: 0, width: 328, height: 328) + private var testWindow: UIWindow? + + override func tearDown() { + // CRITICAL: Properly dismiss and nil out the test window to ensure + // any SwiftUI views and their async Tasks are cleaned up before the next test runs. + // Without this, the LinkInlineVerificationView's .onAppear Task can still be running + // and attempt to call start_verification, causing flaky test failures. + testWindow?.rootViewController = nil + testWindow?.isHidden = true + testWindow = nil + super.tearDown() + } @available(iOS 16.0, *) func testLinkInlineVerificationView_NoPaymentMethodPreview() { @@ -32,9 +44,9 @@ class LinkInlineVerificationViewSnapshotTests: STPSnapshotTestCase { let vc = UIHostingController(rootView: verificationView) // Need to host the SwiftUI view in a window for iOSSnapshotTestCase to work: - let window = UIWindow(frame: frame) - window.rootViewController = vc - window.makeKeyAndVisible() + testWindow = UIWindow(frame: frame) + testWindow?.rootViewController = vc + testWindow?.makeKeyAndVisible() STPSnapshotVerifyView(vc.view, identifier: nil, file: #filePath, line: #line) } @@ -62,9 +74,9 @@ class LinkInlineVerificationViewSnapshotTests: STPSnapshotTestCase { let vc = UIHostingController(rootView: verificationView) // Need to host the SwiftUI view in a window for iOSSnapshotTestCase to work: - let window = UIWindow(frame: frame) - window.rootViewController = vc - window.makeKeyAndVisible() + testWindow = UIWindow(frame: frame) + testWindow?.rootViewController = vc + testWindow?.makeKeyAndVisible() STPSnapshotVerifyView(vc.view, identifier: nil, file: #filePath, line: #line) } @@ -92,9 +104,9 @@ class LinkInlineVerificationViewSnapshotTests: STPSnapshotTestCase { let vc = UIHostingController(rootView: verificationView) // Need to host the SwiftUI view in a window for iOSSnapshotTestCase to work: - let window = UIWindow(frame: frame) - window.rootViewController = vc - window.makeKeyAndVisible() + testWindow = UIWindow(frame: frame) + testWindow?.rootViewController = vc + testWindow?.makeKeyAndVisible() STPSnapshotVerifyView(vc.view, identifier: nil, file: #filePath, line: #line) } diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PMMENetworkInitializationTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PMMENetworkInitializationTests.swift index 2b3866c0864..bc168d95560 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PMMENetworkInitializationTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PMMENetworkInitializationTests.swift @@ -28,6 +28,32 @@ class PMMENetworkInitializationTests: STPNetworkStubbingTestCase { override func setUp() { super.setUp() + // CRITICAL: Clear the global default publishable key that other tests may have set + // This is a static global that persists across test runs and can cause unexpected behavior + // when creating new STPAPIClient instances + StripeAPI.defaultPublishableKey = nil + + // Clear Link account context that might be set by previous tests + LinkAccountContext.shared.account = nil + + // Clear any persisted Link-related UserDefaults that might trigger unexpected behavior + UserDefaults.standard.clearLinkDefaults() + UserDefaults.standard.customerToLastSelectedPaymentMethod = nil + + // Clear any persisted attestation state in UserDefaults that might trigger unexpected network calls + // StripeAttest stores state keyed by publishable key - this stale state from previous test runs + // can cause unexpected /v1/consumers/sessions/start_verification calls + for key in [Self.usPublishableKey, Self.frenchPublishableKey, Self.britishPublishableKey, "pk_test_123", "pk_test_123456789"] { + let keysToRemove = ["keyID", "successfullyAttested", "dailyAttemptCount", "firstAttemptToday"].map { "\($0):\(key)" } + keysToRemove.forEach { UserDefaults.standard.removeObject(forKey: $0) } + } + + // CRITICAL: Clear HTTP cookies and cache from shared URLSession configuration + // These persist between test suite runs and can cause flaky tests! + // Specifically, Link session cookies from previous tests can trigger unexpected consumer session calls + HTTPCookieStorage.shared.removeCookies(since: Date.distantPast) + URLCache.shared.removeAllCachedResponses() + // Create a DownloadManager with the shared URLSession configuration // This allows the network stubbing recorder to intercept API requests let urlSessionConfig = StripeAPIConfiguration.sharedUrlSessionConfiguration diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheet+PaymentMethodAvailabilityTest.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheet+PaymentMethodAvailabilityTest.swift index de6f7612d5c..0b509d12974 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheet+PaymentMethodAvailabilityTest.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheet+PaymentMethodAvailabilityTest.swift @@ -135,6 +135,7 @@ final class PaymentMethodAvailabilityTest: XCTestCase { STPAPIClient.shared.publishableKey = originalPublishableKey XCTAssertTrue(isLinkSignupEnabled, "Link inline signup should be enabled for linkSignupOptInFeatureEnabled even if general signup disabled") + LinkAccountContext.shared.account = nil } func testIsLinkSignupEnabled_enabled_for_linkSignupOptInFeatureEnabled_if_email_provided() { @@ -153,6 +154,7 @@ final class PaymentMethodAvailabilityTest: XCTestCase { STPAPIClient.shared.publishableKey = originalPublishableKey XCTAssertTrue(isLinkSignupEnabled, "Link inline signup should be enabled for linkSignupOptInFeatureEnabled if an email was provided") + LinkAccountContext.shared.account = nil } func testIsLinkSignupEnabled_disabled_for_linkSignupOptInFeatureEnabled_if_no_email_provided() {