diff --git a/BrowserKit/Sources/OnboardingKit/Views/Common/OnboardingBottomSheetViewController.swift b/BrowserKit/Sources/OnboardingKit/Views/Common/OnboardingBottomSheetViewController.swift index 723424455da55..86c3a3e318fab 100644 --- a/BrowserKit/Sources/OnboardingKit/Views/Common/OnboardingBottomSheetViewController.swift +++ b/BrowserKit/Sources/OnboardingKit/Views/Common/OnboardingBottomSheetViewController.swift @@ -28,9 +28,12 @@ public class OnboardingBottomSheetViewController: UIViewController, /// The last calculated height for the bottom sheet custom detent. private var lastCalculatedHeight: CGFloat = 0 private var child: UIViewController? + /// Closure called when the bottom sheet is dismissed via the close button + public var onDismiss: (() -> Void)? private lazy var closeButton: UIButton = .build { $0.addAction(UIAction(handler: { [weak self] _ in + self?.onDismiss?() self?.dismiss(animated: true) }), for: .touchUpInside) if #available(iOS 26, *) { diff --git a/firefox-ios/Client/Coordinators/Launch/LaunchCoordinator.swift b/firefox-ios/Client/Coordinators/Launch/LaunchCoordinator.swift index 05848dbc19dce..aaba6f26ef334 100644 --- a/firefox-ios/Client/Coordinators/Launch/LaunchCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Launch/LaunchCoordinator.swift @@ -29,6 +29,7 @@ final class LaunchCoordinator: BaseCoordinator, let windowUUID: WindowUUID let themeManager: ThemeManager = AppContainer.shared.resolve() weak var parentCoordinator: LaunchCoordinatorDelegate? + private var onboardingService: OnboardingService? init(router: Router, windowUUID: WindowUUID, @@ -236,17 +237,6 @@ final class LaunchCoordinator: BaseCoordinator, router.present(viewController, animated: false) } - private lazy var onboardingService: OnboardingService = { - OnboardingService( - windowUUID: windowUUID, - profile: profile, - themeManager: themeManager, - delegate: self, - navigationDelegate: self, - qrCodeNavigationHandler: self - ) - }() - // MARK: - Intro @MainActor private func presentModernIntroOnboarding(with manager: IntroScreenManagerProtocol, @@ -264,11 +254,23 @@ final class LaunchCoordinator: BaseCoordinator, onboardingVariant: manager.onboardingVariant ) + // Create onboardingService and store it directly - don't create local variable + self.onboardingService = OnboardingService( + windowUUID: windowUUID, + profile: profile, + themeManager: themeManager, + delegate: self, + navigationDelegate: self, + qrCodeNavigationHandler: self + ) + self.onboardingService?.telemetryUtility = telemetryUtility + let flowViewModel = OnboardingFlowViewModel( onboardingCards: onboardingModel.cards, skipText: .Onboarding.LaterAction, onActionTap: { @MainActor [weak self] action, cardName, completion in - self?.onboardingService.handleAction( + guard let onboardingService = self?.onboardingService else { return } + onboardingService.handleAction( action, from: cardName, cards: onboardingModel.cards, @@ -277,7 +279,8 @@ final class LaunchCoordinator: BaseCoordinator, ) }, onMultipleChoiceActionTap: { [weak self] action, cardName in - self?.onboardingService.handleMultipleChoiceAction( + guard let onboardingService = self?.onboardingService else { return } + onboardingService.handleMultipleChoiceAction( action, from: cardName ) @@ -286,6 +289,7 @@ final class LaunchCoordinator: BaseCoordinator, guard let self = self else { return } manager.didSeeIntroScreen() SearchBarLocationSaver().saveUserSearchBarLocation(profile: profile) + self.onboardingService = nil parentCoordinator?.didFinishLaunch(from: self) } ) @@ -309,7 +313,9 @@ final class LaunchCoordinator: BaseCoordinator, ) } - flowViewModel.onDismiss = { cardName in + flowViewModel.onDismiss = { [weak self] cardName in + guard let self = self else { return } + self.onboardingService = nil telemetryUtility.sendDismissOnboardingTelemetry(from: cardName) } diff --git a/firefox-ios/Client/Frontend/Onboarding/Models/OnboardingTelemetryUtility.swift b/firefox-ios/Client/Frontend/Onboarding/Models/OnboardingTelemetryUtility.swift index 25d30f22b806d..653b508f8bb6b 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Models/OnboardingTelemetryUtility.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Models/OnboardingTelemetryUtility.swift @@ -106,6 +106,20 @@ final class OnboardingTelemetryUtility: OnboardingTelemetryProtocol { gleanWrapper.recordEvent(for: GleanMetrics.Onboarding.closeTap, extras: extras) } + func sendGoToSettingsButtonTappedTelemetry() { + let extras = GleanMetrics.OnboardingDefaultBrowserSheet.GoToSettingsButtonTappedExtra( + onboardingVariant: onboardingVariant.rawValue + ) + gleanWrapper.recordEvent(for: GleanMetrics.OnboardingDefaultBrowserSheet.goToSettingsButtonTapped, extras: extras) + } + + func sendDismissButtonTappedTelemetry() { + let extras = GleanMetrics.OnboardingDefaultBrowserSheet.DismissButtonTappedExtra( + onboardingVariant: onboardingVariant.rawValue + ) + gleanWrapper.recordEvent(for: GleanMetrics.OnboardingDefaultBrowserSheet.dismissButtonTapped, extras: extras) + } + private struct BaseExtras { let cardType: String let flowType: String diff --git a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingCardDelegate.swift b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingCardDelegate.swift index ba253d19b1762..79912190b41bd 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingCardDelegate.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingCardDelegate.swift @@ -109,16 +109,20 @@ extension OnboardingCardDelegate where Self: OnboardingViewControllerProtocol, let instructionsVC = OnboardingInstructionPopupViewController( viewModel: popupViewModel, windowUUID: windowUUID, - buttonTappedFinishFlow: { - self.advance( + buttonTappedFinishFlow: { [weak self] in + self?.advance( numberOfPages: 1, from: name, completionIfLastCard: completionIfLastCard ) + self?.viewModel.telemetryUtility.sendGoToSettingsButtonTappedTelemetry() } ) let bottomSheetVC = OnboardingBottomSheetViewController(windowUUID: windowUUID) + bottomSheetVC.onDismiss = { [weak self] in + self?.viewModel.telemetryUtility.sendDismissButtonTappedTelemetry() + } bottomSheetVC.configure( closeButtonModel: CloseButtonViewModel( a11yLabel: .CloseButtonTitle, diff --git a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift index d30f12daa1141..cde471cfd9906 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift @@ -14,7 +14,7 @@ final class OnboardingService: FeatureFlaggable { // MARK: - Properties private weak var delegate: OnboardingServiceDelegate? private weak var navigationDelegate: OnboardingNavigationDelegate? - private let qrCodeNavigationHandler: QRCodeNavigationHandler? + private weak var qrCodeNavigationHandler: QRCodeNavigationHandler? private var hasRegisteredForDefaultBrowserNotification = false private var userDefaults: UserDefaultsInterface private var windowUUID: WindowUUID @@ -28,6 +28,7 @@ final class OnboardingService: FeatureFlaggable { private let defaultApplicationHelper: ApplicationHelper private let notificationCenter: NotificationProtocol private let searchBarLocationSaver: SearchBarLocationSaverProtocol + weak var telemetryUtility: OnboardingTelemetryProtocol? init( userDefaults: UserDefaultsInterface = UserDefaults.standard, @@ -198,6 +199,7 @@ final class OnboardingService: FeatureFlaggable { activityEventHelper.chosenOptions.insert(.setAsDefaultBrowser) activityEventHelper.updateOnboardingUserActivationEvent() registerForNotification() + telemetryUtility?.sendGoToSettingsButtonTappedTelemetry() defaultApplicationHelper.openSettings() } @@ -213,6 +215,7 @@ final class OnboardingService: FeatureFlaggable { } private func handleOpenIosFxSettings(from cardName: String) { + telemetryUtility?.sendGoToSettingsButtonTappedTelemetry() defaultApplicationHelper.openSettings() } @@ -339,7 +342,10 @@ final class OnboardingService: FeatureFlaggable { let instructionsVC = OnboardingInstructionPopupViewController( viewModel: popupViewModel, windowUUID: windowUUID, - buttonTappedFinishFlow: completion + buttonTappedFinishFlow: { [weak self] in + self?.telemetryUtility?.sendGoToSettingsButtonTappedTelemetry() + completion() + } ) let bottomSheetVC = OnboardingBottomSheetViewController(windowUUID: windowUUID) diff --git a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingTelemetryProtocol.swift b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingTelemetryProtocol.swift index 4c78d69bc3f68..8f90a864b3a36 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingTelemetryProtocol.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingTelemetryProtocol.swift @@ -14,4 +14,6 @@ protocol OnboardingTelemetryProtocol: AnyObject { with action: OnboardingMultipleChoiceAction ) func sendDismissOnboardingTelemetry(from cardName: String) + func sendGoToSettingsButtonTappedTelemetry() + func sendDismissButtonTappedTelemetry() } diff --git a/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift b/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift index a5041feb9b045..32f7d2ccb7533 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift @@ -328,12 +328,14 @@ extension IntroViewController: OnboardingCardDelegate { introViewModel.chosenOptions.insert(.setAsDefaultBrowser) introViewModel.updateOnboardingUserActivationEvent() registerForNotification() + viewModel.telemetryUtility.sendGoToSettingsButtonTappedTelemetry() DefaultApplicationHelper().openSettings() case .openInstructionsPopup: /// Setting default browser card action opens an instruction pop up instead of /// setting a default browser action. TBD if the above code even still fires. introViewModel.chosenOptions.insert(.setAsDefaultBrowser) introViewModel.updateOnboardingUserActivationEvent() + registerForNotification() presentDefaultBrowserPopup( windowUUID: windowUUID, from: cardName, @@ -344,6 +346,7 @@ extension IntroViewController: OnboardingCardDelegate { from: cardName, selector: #selector(dismissPrivacyPolicyViewController)) case .openIosFxSettings: + viewModel.telemetryUtility.sendGoToSettingsButtonTappedTelemetry() DefaultApplicationHelper().openSettings() advance(numberOfPages: 1, from: cardName) { self.showNextPageCompletionForLastCard() diff --git a/firefox-ios/Client/Glean/probes/metrics.yaml b/firefox-ios/Client/Glean/probes/metrics.yaml index 995cb9adf87ed..a83b8e12c780c 100755 --- a/firefox-ios/Client/Glean/probes/metrics.yaml +++ b/firefox-ios/Client/Glean/probes/metrics.yaml @@ -332,35 +332,6 @@ context_menu: # Default browser card metrics default_browser_card: - dismiss_pressed: - type: counter - description: | - Counts the number of times default browser card is dismissed. - bugs: - - https://github.com/mozilla-mobile/firefox-ios/issues/7151 - data_reviews: - - https://github.com/mozilla-mobile/firefox-ios/pull/7245 - - https://github.com/mozilla-mobile/firefox-ios/pull/9673 - - https://github.com/mozilla-mobile/firefox-ios/pull/12334 - - https://github.com/mozilla-mobile/firefox-ios/pull/14102 - notification_emails: - - fx-ios-data-stewards@mozilla.com - expires: "2026-06-01" - go_to_settings_pressed: - type: counter - description: | - Counts the number of times the Go To Settings button on - default browser card is clicked. - bugs: - - https://github.com/mozilla-mobile/firefox-ios/issues/7151 - data_reviews: - - https://github.com/mozilla-mobile/firefox-ios/pull/7245 - - https://github.com/mozilla-mobile/firefox-ios/pull/9673 - - https://github.com/mozilla-mobile/firefox-ios/pull/12334 - - https://github.com/mozilla-mobile/firefox-ios/pull/14102 - notification_emails: - - fx-ios-data-stewards@mozilla.com - expires: "2026-06-01" evergreen_impression: type: event description: | @@ -374,38 +345,6 @@ default_browser_card: - fx-ios-data-stewards@mozilla.com expires: "2026-06-01" -# Default browser onboarding metrics -default_browser_onboarding: - dismiss_pressed: - type: counter - description: | - Counts the number of times default browser onboarding is dismissed. - bugs: - - https://github.com/mozilla-mobile/firefox-ios/issues/7870 - data_reviews: - - https://github.com/mozilla-mobile/firefox-ios/pull/7245 - - https://github.com/mozilla-mobile/firefox-ios/pull/9673 - - https://github.com/mozilla-mobile/firefox-ios/pull/12334 - - https://github.com/mozilla-mobile/firefox-ios/pull/14102 - notification_emails: - - fx-ios-data-stewards@mozilla.com - expires: "2026-06-01" - go_to_settings_pressed: - type: counter - description: | - Counts the number of times the Go To Settings button on - default browser onboarding is clicked. - bugs: - - https://github.com/mozilla-mobile/firefox-ios/issues/7870 - data_reviews: - - https://github.com/mozilla-mobile/firefox-ios/pull/7245 - - https://github.com/mozilla-mobile/firefox-ios/pull/9673 - - https://github.com/mozilla-mobile/firefox-ios/pull/12334 - - https://github.com/mozilla-mobile/firefox-ios/pull/14102 - notification_emails: - - fx-ios-data-stewards@mozilla.com - expires: "2026-06-01" - app: default_browser: type: boolean diff --git a/firefox-ios/Client/Glean/probes/onboarding.yaml b/firefox-ios/Client/Glean/probes/onboarding.yaml index 9d10279d31212..866f1ea3d3c89 100644 --- a/firefox-ios/Client/Glean/probes/onboarding.yaml +++ b/firefox-ios/Client/Glean/probes/onboarding.yaml @@ -422,3 +422,50 @@ onboarding: - fx-ios-data-stewards@mozilla.com expires: "2026-01-01" +# Default Browser Sheet +onboarding.default_browser_sheet: + go_to_settings_button_tapped: + type: event + description: | + Records when the Go To Settings button on default browser onboarding + is clicked. This applies to both legacy and modern onboarding flows. + extra_keys: + onboarding_variant: + type: string + description: | + The onboarding variant (modern, legacy, or japan). + bugs: + - https://github.com/mozilla-mobile/firefox-ios/issues/7870 + - https://github.com/mozilla-mobile/firefox-ios/issues/31366 + data_reviews: + - https://github.com/mozilla-mobile/firefox-ios/pull/7245 + - https://github.com/mozilla-mobile/firefox-ios/pull/9673 + - https://github.com/mozilla-mobile/firefox-ios/pull/12334 + - https://github.com/mozilla-mobile/firefox-ios/pull/14102 + - https://github.com/mozilla-mobile/firefox-ios/pull/31375 + notification_emails: + - fx-ios-data-stewards@mozilla.com + expires: "2026-06-01" + dismiss_button_tapped: + type: event + description: | + Records when default browser onboarding is dismissed. + This applies to both legacy and modern onboarding flows. + extra_keys: + onboarding_variant: + type: string + description: | + The onboarding variant (modern, legacy, or japan). + bugs: + - https://github.com/mozilla-mobile/firefox-ios/issues/7870 + - https://github.com/mozilla-mobile/firefox-ios/issues/31366 + data_reviews: + - https://github.com/mozilla-mobile/firefox-ios/pull/7245 + - https://github.com/mozilla-mobile/firefox-ios/pull/9673 + - https://github.com/mozilla-mobile/firefox-ios/pull/12334 + - https://github.com/mozilla-mobile/firefox-ios/pull/14102 + - https://github.com/mozilla-mobile/firefox-ios/pull/31375 + notification_emails: + - fx-ios-data-stewards@mozilla.com + expires: "2026-06-01" + diff --git a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift index a9de5971a8c2d..af51991a491df 100644 --- a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift +++ b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift @@ -529,8 +529,6 @@ extension TelemetryWrapper { case onboarding = "onboarding" case upgradeOnboarding = "upgrade-onboarding" // MARK: New Upgrade screen - case dismissDefaultBrowserCard = "default-browser-card" - case goToSettingsDefaultBrowserCard = "default-browser-card-go-to-settings" case dismissDefaultBrowserOnboarding = "default-browser-onboarding" case goToSettingsDefaultBrowserOnboarding = "default-browser-onboarding-go-to-settings" case homeTabBannerEvergreen = "home-tab-banner-evergreen" @@ -1119,22 +1117,24 @@ extension TelemetryWrapper { extras: extras) } // MARK: Default Browser - case (.action, .tap, .dismissDefaultBrowserCard, _, _): - GleanMetrics.DefaultBrowserCard.dismissPressed.add() - case (.action, .tap, .goToSettingsDefaultBrowserCard, _, _): - GleanMetrics.DefaultBrowserCard.goToSettingsPressed.add() case (.action, .open, .asDefaultBrowser, _, _): GleanMetrics.App.openedAsDefaultBrowser.add() case(.action, .tap, .engagementNotification, _, _): GleanMetrics.Onboarding.engagementNotificationTapped.record() case(.action, .cancel, .engagementNotification, _, _): GleanMetrics.Onboarding.engagementNotificationCancel.record() - case (.action, .tap, .dismissDefaultBrowserOnboarding, _, _): - GleanMetrics.DefaultBrowserOnboarding.dismissPressed.add() - case (.action, .tap, .goToSettingsDefaultBrowserOnboarding, _, _): - GleanMetrics.DefaultBrowserOnboarding.goToSettingsPressed.add() case (.information, .view, .homeTabBannerEvergreen, _, _): GleanMetrics.DefaultBrowserCard.evergreenImpression.record() + case (.action, .tap, .dismissDefaultBrowserOnboarding, _, _): + let extras = GleanMetrics.OnboardingDefaultBrowserSheet.DismissButtonTappedExtra( + onboardingVariant: "legacy" + ) + GleanMetrics.OnboardingDefaultBrowserSheet.dismissButtonTapped.record(extras) + case (.action, .tap, .goToSettingsDefaultBrowserOnboarding, _, _): + let extras = GleanMetrics.OnboardingDefaultBrowserSheet.GoToSettingsButtonTappedExtra( + onboardingVariant: "legacy" + ) + GleanMetrics.OnboardingDefaultBrowserSheet.goToSettingsButtonTapped.record(extras) // MARK: Downloads case(.action, .tap, .downloadNowButton, _, _): GleanMetrics.Downloads.downloadNowButtonTapped.record() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingServiceTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingServiceTests.swift index 7bdf6025783de..afe65d2442938 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingServiceTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingServiceTests.swift @@ -84,6 +84,56 @@ class MockSearchBarLocationSaver: SearchBarLocationSaverProtocol { } } +class MockOnboardingTelemetryUtility: OnboardingTelemetryProtocol { + var sendCardViewTelemetryCalled = false + var sendButtonActionTelemetryCalled = false + var sendMultipleChoiceButtonActionTelemetryCalled = false + var sendDismissOnboardingTelemetryCalled = false + var sendGoToSettingsButtonTappedTelemetryCalled = false + var sendDismissButtonTappedTelemetryCalled = false + + var cardName: String? + var action: OnboardingActions? + var primaryButton: Bool? + var multipleChoiceAction: OnboardingMultipleChoiceAction? + + func sendCardViewTelemetry(from cardName: String) { + sendCardViewTelemetryCalled = true + self.cardName = cardName + } + + func sendButtonActionTelemetry(from cardName: String, + with action: OnboardingActions, + and primaryButton: Bool) { + sendButtonActionTelemetryCalled = true + self.cardName = cardName + self.action = action + self.primaryButton = primaryButton + } + + func sendMultipleChoiceButtonActionTelemetry( + from cardName: String, + with action: OnboardingMultipleChoiceAction + ) { + sendMultipleChoiceButtonActionTelemetryCalled = true + self.cardName = cardName + self.multipleChoiceAction = action + } + + func sendDismissOnboardingTelemetry(from cardName: String) { + sendDismissOnboardingTelemetryCalled = true + self.cardName = cardName + } + + func sendGoToSettingsButtonTappedTelemetry() { + sendGoToSettingsButtonTappedTelemetryCalled = true + } + + func sendDismissButtonTappedTelemetry() { + sendDismissButtonTappedTelemetryCalled = true + } +} + class MockActivityEventHelper: ActivityEventHelper { var chosenOption: [IntroViewModel.OnboardingOptions] = [] var updateOnboardingUserActivationEventCalled = false @@ -107,6 +157,7 @@ final class OnboardingServiceTests: XCTestCase { var mockSearchBarLocationSaver: MockSearchBarLocationSaver! var mockProfile: MockProfile! var mockThemeManager: MockThemeManager! + var mockTelemetryUtility: MockOnboardingTelemetryUtility! override func setUp() async throws { try await super.setUp() @@ -120,6 +171,7 @@ final class OnboardingServiceTests: XCTestCase { mockSearchBarLocationSaver = MockSearchBarLocationSaver() mockProfile = MockProfile(databasePrefix: "OnboardingServiceTests") mockThemeManager = MockThemeManager() + mockTelemetryUtility = MockOnboardingTelemetryUtility() sut = OnboardingService( userDefaults: mockUserDefaults, @@ -134,6 +186,7 @@ final class OnboardingServiceTests: XCTestCase { notificationCenter: mockNotificationCenter, searchBarLocationSaver: mockSearchBarLocationSaver ) + sut.telemetryUtility = mockTelemetryUtility DependencyHelperMock().bootstrapDependencies() } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift index 5ebdd2c54f73c..98a48a0dea2c5 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift @@ -16,11 +16,12 @@ final class OnboardingTelemetryUtilityTests: XCTestCase { try await super.setUp() DependencyHelperMock().bootstrapDependencies() mockGleanWrapper = MockGleanWrapper() + Self.setupTelemetry(with: MockProfile()) } override func tearDown() async throws { mockGleanWrapper = nil - DependencyHelperMock().reset() + Self.tearDownTelemetry() try await super.tearDown() } @@ -269,6 +270,110 @@ final class OnboardingTelemetryUtilityTests: XCTestCase { XCTAssertEqual(savedExtras.onboardingVariant, "japan") } + // MARK: - Go To Settings Button Tapped Telemetry + func testSendGoToSettingsButtonTappedTelemetry_RecordsEvent() throws { + let subject = createTelemetryUtility(for: .freshInstall) + let event = GleanMetrics.OnboardingDefaultBrowserSheet.goToSettingsButtonTapped + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.GoToSettingsButtonTappedExtra + + subject.sendGoToSettingsButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + let savedMetric = try XCTUnwrap(mockGleanWrapper.savedEvents.first as? EventMetricType) + + XCTAssertEqual(mockGleanWrapper.recordEventCalled, 1) + XCTAssertEqual(savedExtras.onboardingVariant, "legacy") + XCTAssert(savedMetric === event, "Received \(savedMetric) instead of \(event)") + } + + func testSendGoToSettingsButtonTappedTelemetry_MultipleCalls_RecordsMultipleEvents() throws { + let subject = createTelemetryUtility(for: .freshInstall) + + subject.sendGoToSettingsButtonTappedTelemetry() + subject.sendGoToSettingsButtonTappedTelemetry() + subject.sendGoToSettingsButtonTappedTelemetry() + + XCTAssertEqual(mockGleanWrapper.recordEventCalled, 3) + XCTAssertEqual(mockGleanWrapper.savedExtras.count, 3) + } + + func testSendGoToSettingsButtonTappedTelemetry_WorksWithLegacyOnboarding() throws { + let subject = createTelemetryUtility(for: .upgrade) + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.GoToSettingsButtonTappedExtra + + subject.sendGoToSettingsButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + XCTAssertEqual(savedExtras.onboardingVariant, "legacy") + } + + func testSendGoToSettingsButtonTappedTelemetry_WorksWithModernOnboarding() throws { + let subject = createModernTelemetryUtility(for: .freshInstall) + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.GoToSettingsButtonTappedExtra + + subject.sendGoToSettingsButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + XCTAssertEqual(savedExtras.onboardingVariant, "modern") + } + + // MARK: - Dismiss Button Tapped Telemetry + func testSendDismissButtonTappedTelemetry_RecordsEvent() throws { + let subject = createTelemetryUtility(for: .freshInstall) + let event = GleanMetrics.OnboardingDefaultBrowserSheet.dismissButtonTapped + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.DismissButtonTappedExtra + + subject.sendDismissButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + let savedMetric = try XCTUnwrap(mockGleanWrapper.savedEvents.first as? EventMetricType) + + XCTAssertEqual(mockGleanWrapper.recordEventCalled, 1) + XCTAssertEqual(savedExtras.onboardingVariant, "legacy") + XCTAssert(savedMetric === event, "Received \(savedMetric) instead of \(event)") + } + + func testSendDismissButtonTappedTelemetry_MultipleCalls_RecordsMultipleEvents() throws { + let subject = createTelemetryUtility(for: .freshInstall) + + subject.sendDismissButtonTappedTelemetry() + subject.sendDismissButtonTappedTelemetry() + subject.sendDismissButtonTappedTelemetry() + + XCTAssertEqual(mockGleanWrapper.recordEventCalled, 3) + XCTAssertEqual(mockGleanWrapper.savedExtras.count, 3) + } + + func testSendDismissButtonTappedTelemetry_WorksWithLegacyOnboarding() throws { + let subject = createTelemetryUtility(for: .upgrade) + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.DismissButtonTappedExtra + + subject.sendDismissButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + XCTAssertEqual(savedExtras.onboardingVariant, "legacy") + } + + func testSendDismissButtonTappedTelemetry_WorksWithModernOnboarding() throws { + let subject = createModernTelemetryUtility(for: .freshInstall) + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.DismissButtonTappedExtra + + subject.sendDismissButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + XCTAssertEqual(savedExtras.onboardingVariant, "modern") + } + + func testSendDismissButtonTappedTelemetry_WorksWithJapanOnboarding() throws { + let subject = createModernTelemetryUtility(for: .freshInstall, variant: .japan) + typealias EventExtrasType = GleanMetrics.OnboardingDefaultBrowserSheet.DismissButtonTappedExtra + + subject.sendDismissButtonTappedTelemetry() + + let savedExtras = try XCTUnwrap(mockGleanWrapper.savedExtras.first as? EventExtrasType) + XCTAssertEqual(savedExtras.onboardingVariant, "japan") + } + // MARK: Private private func createTelemetryUtility( for onboardingType: OnboardingType,