From 60d503d74ecfb0e3dd6508ae264da8d3a5bb803c Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Mon, 6 Nov 2023 16:14:32 +0200 Subject: [PATCH 1/8] Use one instance for the shopping CFR --- .../Browser/BrowserViewController.swift | 1 + ...BrowserViewController+URLBarDelegate.swift | 39 +++++++++++++------ .../ContextualHintEligibilityUtility.swift | 1 + 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index d3ab6af8c668..34d79b5b67fe 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -61,6 +61,7 @@ class BrowserViewController: UIViewController, var overlayManager: OverlayModeManager var appAuthenticator: AppAuthenticationProtocol var contextHintVC: ContextualHintViewController + var shoppingContextHintVC: ContextualHintViewController? private var backgroundTabLoader: DefaultBackgroundTabLoader // popover rotation handling diff --git a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift index dcf75d3647c8..896f152824e6 100644 --- a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift +++ b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift @@ -135,21 +135,38 @@ extension BrowserViewController: URLBarDelegate { return } - let contextualViewProvider = ContextualHintViewProvider(forHintType: .shoppingExperience, - with: profile) - - let contextHintVC = ContextualHintViewController(with: contextualViewProvider) + if shoppingContextHintVC == nil { + configureShoppingContextVC(at: sourceView) + } else { + shoppingContextHintVC = nil + } + } - contextHintVC.configure( + private func configureShoppingContextVC(at sourceView: UIView) { + let viewProvider = ContextualHintViewProvider( + forHintType: .shoppingExperience, + with: profile + ) + shoppingContextHintVC = ContextualHintViewController( + with: viewProvider + ) + + guard let shoppingContextHintVC else { return } + shoppingContextHintVC.configure( anchor: sourceView, withArrowDirection: isBottomSearchBar ? .down : .up, andDelegate: self, - presentedUsing: { - self.present(contextHintVC, animated: true) - TelemetryWrapper.recordEvent(category: .action, - method: .navigate, - object: .shoppingButton, - value: .shoppingCFRsDisplayed) + presentedUsing: { [unowned self] in + self.present(shoppingContextHintVC, animated: true) + TelemetryWrapper.recordEvent( + category: .action, + method: .navigate, + object: .shoppingButton, + value: .shoppingCFRsDisplayed + ) + }, + actionOnDismiss: { + self.shoppingContextHintVC = nil }, andActionForButton: { [weak self] in guard let self else { return } diff --git a/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift b/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift index 74b54f36f2bc..f876872d3d11 100644 --- a/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift +++ b/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift @@ -113,6 +113,7 @@ struct ContextualHintEligibilityUtility: ContextualHintEligibilityUtilityProtoco if cfrCounter <= 2, !hasOptedIn, hasTimePassed { // - Display CFR-1 profile.prefs.setInt(cfrCounter + 1, forKey: PrefsKeys.ContextualHints.shoppingOnboardingCFRsCounterKey.rawValue) + profile.prefs.setTimestamp(Date.now(), forKey: PrefsKeys.FakespotLastCFRTimestamp) return true } else if cfrCounter < 4, hasOptedIn, hasTimePassed { // - Display CFR-2 From a945f5bea2e18983e6282adb0ec4d8819f5a6156 Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Tue, 7 Nov 2023 15:24:20 +0200 Subject: [PATCH 2/8] Rename contextHintVC and add new unit test --- Client/Frontend/Browser/BrowserViewController.swift | 12 ++++++------ .../ContextualHintEligibilityUtilityTests.swift | 13 +++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index 34d79b5b67fe..54ba15c1ad2c 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -60,7 +60,7 @@ class BrowserViewController: UIViewController, var passBookHelper: OpenPassBookHelper? var overlayManager: OverlayModeManager var appAuthenticator: AppAuthenticationProtocol - var contextHintVC: ContextualHintViewController + var toolbarContextHintVC: ContextualHintViewController var shoppingContextHintVC: ContextualHintViewController? private var backgroundTabLoader: DefaultBackgroundTabLoader @@ -188,7 +188,7 @@ class BrowserViewController: UIViewController, self.overlayManager = DefaultOverlayModeManager() let contextualViewProvider = ContextualHintViewProvider(forHintType: .toolbarLocation, with: profile) - self.contextHintVC = ContextualHintViewController(with: contextualViewProvider) + self.toolbarContextHintVC = ContextualHintViewController(with: contextualViewProvider) self.backgroundTabLoader = DefaultBackgroundTabLoader(tabQueue: profile.queue) super.init(nibName: nil, bundle: nil) didInit() @@ -634,11 +634,11 @@ class BrowserViewController: UIViewController, } private func prepareURLOnboardingContextualHint() { - guard contextHintVC.shouldPresentHint(), + guard toolbarContextHintVC.shouldPresentHint(), featureFlags.isFeatureEnabled(.isToolbarCFREnabled, checking: .buildOnly) else { return } - contextHintVC.configure( + toolbarContextHintVC.configure( anchor: urlBar, withArrowDirection: isBottomSearchBar ? .down : .up, andDelegate: self, @@ -649,9 +649,9 @@ class BrowserViewController: UIViewController, private func presentContextualHint() { if IntroScreenManager(prefs: profile.prefs).shouldShowIntroScreen { return } - present(contextHintVC, animated: true) + present(toolbarContextHintVC, animated: true) - UIAccessibility.post(notification: .layoutChanged, argument: contextHintVC) + UIAccessibility.post(notification: .layoutChanged, argument: toolbarContextHintVC) } override func viewWillDisappear(_ animated: Bool) { diff --git a/Tests/ClientTests/Frontend/ContextualHints/ContextualHintEligibilityUtilityTests.swift b/Tests/ClientTests/Frontend/ContextualHints/ContextualHintEligibilityUtilityTests.swift index e84f20256ce8..19742f0e5491 100644 --- a/Tests/ClientTests/Frontend/ContextualHints/ContextualHintEligibilityUtilityTests.swift +++ b/Tests/ClientTests/Frontend/ContextualHints/ContextualHintEligibilityUtilityTests.swift @@ -227,4 +227,17 @@ class ContextualHintEligibilityUtilityTests: XCTestCase { let result = subject.canPresent(.shoppingExperience) XCTAssertFalse(result) } + + func test_canPresentShoppingCFR_TwoConsecutiveCFRs_UserHasNotOptedIn_() { + let lastTimestamp: Timestamp = 1695719918000 // Date and time (GMT): Tuesday, 26 September 2023 09:18:38 + + profile.prefs.setBool(true, forKey: CFRPrefsKeys.shoppingOnboardingKey.rawValue) + profile.prefs.setTimestamp(lastTimestamp, forKey: PrefsKeys.FakespotLastCFRTimestamp) + profile.prefs.setBool(false, forKey: PrefsKeys.Shopping2023OptIn) + + let canPresentFirstCFR = subject.canPresent(.shoppingExperience) + XCTAssertTrue(canPresentFirstCFR) + let canPresentSecondCFR = subject.canPresent(.shoppingExperience) + XCTAssertFalse(canPresentSecondCFR) + } } From c31ca934525d73a653acdedb7074a47e90a3b072 Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Wed, 8 Nov 2023 12:42:49 +0200 Subject: [PATCH 3/8] Initialize shopping contextual hint in init of BVC --- .../Browser/BrowserViewController.swift | 5 ++++- ...BrowserViewController+URLBarDelegate.swift | 19 +------------------ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/Client/Frontend/Browser/BrowserViewController.swift b/Client/Frontend/Browser/BrowserViewController.swift index 54ba15c1ad2c..9763898ff7f1 100644 --- a/Client/Frontend/Browser/BrowserViewController.swift +++ b/Client/Frontend/Browser/BrowserViewController.swift @@ -61,7 +61,7 @@ class BrowserViewController: UIViewController, var overlayManager: OverlayModeManager var appAuthenticator: AppAuthenticationProtocol var toolbarContextHintVC: ContextualHintViewController - var shoppingContextHintVC: ContextualHintViewController? + let shoppingContextHintVC: ContextualHintViewController private var backgroundTabLoader: DefaultBackgroundTabLoader // popover rotation handling @@ -189,6 +189,9 @@ class BrowserViewController: UIViewController, let contextualViewProvider = ContextualHintViewProvider(forHintType: .toolbarLocation, with: profile) self.toolbarContextHintVC = ContextualHintViewController(with: contextualViewProvider) + let shoppingViewProvider = ContextualHintViewProvider(forHintType: .shoppingExperience, + with: profile) + shoppingContextHintVC = ContextualHintViewController(with: shoppingViewProvider) self.backgroundTabLoader = DefaultBackgroundTabLoader(tabQueue: profile.queue) super.init(nibName: nil, bundle: nil) didInit() diff --git a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift index 896f152824e6..cf468bff1302 100644 --- a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift +++ b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+URLBarDelegate.swift @@ -134,24 +134,10 @@ extension BrowserViewController: URLBarDelegate { didStartAtHome = false return } - - if shoppingContextHintVC == nil { - configureShoppingContextVC(at: sourceView) - } else { - shoppingContextHintVC = nil - } + configureShoppingContextVC(at: sourceView) } private func configureShoppingContextVC(at sourceView: UIView) { - let viewProvider = ContextualHintViewProvider( - forHintType: .shoppingExperience, - with: profile - ) - shoppingContextHintVC = ContextualHintViewController( - with: viewProvider - ) - - guard let shoppingContextHintVC else { return } shoppingContextHintVC.configure( anchor: sourceView, withArrowDirection: isBottomSearchBar ? .down : .up, @@ -165,9 +151,6 @@ extension BrowserViewController: URLBarDelegate { value: .shoppingCFRsDisplayed ) }, - actionOnDismiss: { - self.shoppingContextHintVC = nil - }, andActionForButton: { [weak self] in guard let self else { return } guard let productURL = self.urlBar.currentURL else { return } From 6a7856e6dfb21b728e334e49c55a64c0db853000 Mon Sep 17 00:00:00 2001 From: Winnie Teichmann <4530+thatswinnie@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:07:08 +0100 Subject: [PATCH 4/8] Fix crash that can happen when manually changing time on the device --- Shared/TimeConstants.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Shared/TimeConstants.swift b/Shared/TimeConstants.swift index 8ca59cb77fa3..87ef5349e870 100644 --- a/Shared/TimeConstants.swift +++ b/Shared/TimeConstants.swift @@ -150,6 +150,8 @@ extension Date { /// - Returns: `true` if the specified time in hours has passed since the lastTimestamp; `false` otherwise. public static func hasTimePassedBy(hours: Timestamp, lastTimestamp: Timestamp) -> Bool { + guard Date.now() > lastTimestamp else { return false } + let millisecondsInAnHour: Timestamp = 3_600_000 // Convert 1 hour to milliseconds let timeDifference = Date.now() - lastTimestamp return timeDifference >= hours * millisecondsInAnHour From 5c0f287c866009f190df1b2ff5e73885066c40eb Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Thu, 9 Nov 2023 15:22:39 +0200 Subject: [PATCH 5/8] Fix stackview layout by using UIButton configuration --- .../ContextualHintView.swift | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift index ac00c66158ec..bcda770ce22e 100644 --- a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift +++ b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift @@ -15,7 +15,7 @@ public class ContextualHintView: UIView, ThemeApplicable { static let closeButtonTrailing: CGFloat = 5 static let closeButtonTop: CGFloat = 23 static let closeButtonBottom: CGFloat = 12 - static let closeButtonInset = UIEdgeInsets(top: 0, left: 7.5, bottom: 15, right: 7.5) + static let closeButtonInset = NSDirectionalEdgeInsets(top: 0, leading: 7.5, bottom: 15, trailing: 7.5) static let descriptionTextSize: CGFloat = 17 static let stackViewLeading: CGFloat = 16 static let stackViewTopArrowTopConstraint: CGFloat = 16 @@ -27,10 +27,12 @@ public class ContextualHintView: UIView, ThemeApplicable { // MARK: - UI Elements private lazy var contentContainer: UIView = .build { _ in } - private lazy var closeButton: ActionButton = .build { button in + private lazy var closeButton: UIButton = .build { button in button.setImage(UIImage(named: StandardImageIdentifiers.Medium.cross)?.withRenderingMode(.alwaysTemplate), for: .normal) - button.contentEdgeInsets = UX.closeButtonInset + button.addTarget(self, action: #selector(self.didTapCloseButton), for: .touchUpInside) + button.configuration = .plain() + button.configuration?.contentInsets = UX.closeButtonInset } private lazy var descriptionLabel: UILabel = .build { label in @@ -39,10 +41,17 @@ public class ContextualHintView: UIView, ThemeApplicable { label.numberOfLines = 0 } - private lazy var actionButton: ActionButton = .build { button in + private lazy var actionButton: UIButton = .build { button in button.titleLabel?.textAlignment = .left button.titleLabel?.numberOfLines = 0 - button.buttonEdgeSpacing = 0 + button.addTarget(self, action: #selector(self.didTapActionButton), for: .touchUpInside) + button.configuration = .plain() + button.configuration?.contentInsets = NSDirectionalEdgeInsets( + top: 0, + leading: 0, + bottom: 0, + trailing: 0 + ) } private lazy var stackView: UIStackView = .build { stack in @@ -90,9 +99,6 @@ public class ContextualHintView: UIView, ThemeApplicable { if viewModel.isActionType { stackView.addArrangedSubview(actionButton) } setupConstraints() - - closeButton.touchUpAction = viewModel.closeButtonAction - actionButton.touchUpAction = viewModel.actionButtonAction } private func setupConstraints() { @@ -132,6 +138,16 @@ public class ContextualHintView: UIView, ThemeApplicable { layoutIfNeeded() } + @objc + private func didTapCloseButton(sender: UIButton) { + viewModel.closeButtonAction?(sender) + } + + @objc + private func didTapActionButton(sender: UIButton) { + viewModel.actionButtonAction?(sender) + } + public func applyTheme(theme: Theme) { closeButton.tintColor = theme.colors.textOnDark descriptionLabel.textColor = theme.colors.textOnDark From ce1bbd7f8ffe264a9cc2e0d19adc7490644d0815 Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Thu, 9 Nov 2023 17:16:20 +0200 Subject: [PATCH 6/8] Change UIButton to LinkButton --- .../ContextualHintView.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift index bcda770ce22e..067f3f283568 100644 --- a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift +++ b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift @@ -41,17 +41,11 @@ public class ContextualHintView: UIView, ThemeApplicable { label.numberOfLines = 0 } - private lazy var actionButton: UIButton = .build { button in + private lazy var actionButton: LinkButton = .build { button in button.titleLabel?.textAlignment = .left button.titleLabel?.numberOfLines = 0 button.addTarget(self, action: #selector(self.didTapActionButton), for: .touchUpInside) button.configuration = .plain() - button.configuration?.contentInsets = NSDirectionalEdgeInsets( - top: 0, - leading: 0, - bottom: 0, - trailing: 0 - ) } private lazy var stackView: UIStackView = .build { stack in @@ -84,6 +78,18 @@ public class ContextualHintView: UIView, ThemeApplicable { public func configure(viewModel: ContextualHintViewModel) { self.viewModel = viewModel + let actionButtonViewModel = LinkButtonViewModel( + title: viewModel.actionButtonTitle, + a11yIdentifier: "", + contentInsets: NSDirectionalEdgeInsets( + top: 0, + leading: 0, + bottom: 0, + trailing: 0 + ) + ) + actionButton.configure(viewModel: actionButtonViewModel) + closeButton.accessibilityLabel = viewModel.closeButtonA11yLabel descriptionLabel.text = viewModel.description From 5259d5268f0f9950b1bc8600d33ff59b2c1230ea Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Thu, 9 Nov 2023 17:32:13 +0200 Subject: [PATCH 7/8] Remove configuration from actionButton --- .../ComponentLibrary/ContextualHintView/ContextualHintView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift index 067f3f283568..ab9cbb86e2e9 100644 --- a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift +++ b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift @@ -45,7 +45,6 @@ public class ContextualHintView: UIView, ThemeApplicable { button.titleLabel?.textAlignment = .left button.titleLabel?.numberOfLines = 0 button.addTarget(self, action: #selector(self.didTapActionButton), for: .touchUpInside) - button.configuration = .plain() } private lazy var stackView: UIStackView = .build { stack in From 5ece51e4c1391d64070756844b3456eb7004d218 Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Thu, 9 Nov 2023 18:24:41 +0200 Subject: [PATCH 8/8] Add a11y id and move button insets to UX struct --- .../ContextualHintView/ContextualHintView.swift | 14 +++++--------- .../ContextualHintViewModel.swift | 5 ++++- Client/Application/AccessibilityIdentifiers.swift | 4 ++++ .../ContextualHintViewController.swift | 3 ++- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift index ab9cbb86e2e9..97bb5247f319 100644 --- a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift +++ b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintView.swift @@ -15,7 +15,8 @@ public class ContextualHintView: UIView, ThemeApplicable { static let closeButtonTrailing: CGFloat = 5 static let closeButtonTop: CGFloat = 23 static let closeButtonBottom: CGFloat = 12 - static let closeButtonInset = NSDirectionalEdgeInsets(top: 0, leading: 7.5, bottom: 15, trailing: 7.5) + static let closeButtonInsets = NSDirectionalEdgeInsets(top: 0, leading: 7.5, bottom: 15, trailing: 7.5) + static let actionButtonInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) static let descriptionTextSize: CGFloat = 17 static let stackViewLeading: CGFloat = 16 static let stackViewTopArrowTopConstraint: CGFloat = 16 @@ -32,7 +33,7 @@ public class ContextualHintView: UIView, ThemeApplicable { for: .normal) button.addTarget(self, action: #selector(self.didTapCloseButton), for: .touchUpInside) button.configuration = .plain() - button.configuration?.contentInsets = UX.closeButtonInset + button.configuration?.contentInsets = UX.closeButtonInsets } private lazy var descriptionLabel: UILabel = .build { label in @@ -79,13 +80,8 @@ public class ContextualHintView: UIView, ThemeApplicable { let actionButtonViewModel = LinkButtonViewModel( title: viewModel.actionButtonTitle, - a11yIdentifier: "", - contentInsets: NSDirectionalEdgeInsets( - top: 0, - leading: 0, - bottom: 0, - trailing: 0 - ) + a11yIdentifier: viewModel.actionButtonA11yId, + contentInsets: UX.actionButtonInsets ) actionButton.configure(viewModel: actionButtonViewModel) diff --git a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintViewModel.swift b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintViewModel.swift index c01a7e2ef2fc..4d69cf317e91 100644 --- a/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintViewModel.swift +++ b/BrowserKit/Sources/ComponentLibrary/ContextualHintView/ContextualHintViewModel.swift @@ -11,6 +11,7 @@ public struct ContextualHintViewModel { public var description: String public var arrowDirection: UIPopoverArrowDirection public var closeButtonA11yLabel: String + public var actionButtonA11yId: String public var closeButtonAction: ((UIButton) -> Void)? public var actionButtonAction: ((UIButton) -> Void)? @@ -19,11 +20,13 @@ public struct ContextualHintViewModel { actionButtonTitle: String, description: String, arrowDirection: UIPopoverArrowDirection, - closeButtonA11yLabel: String) { + closeButtonA11yLabel: String, + actionButtonA11yId: String) { self.isActionType = isActionType self.actionButtonTitle = actionButtonTitle self.description = description self.arrowDirection = arrowDirection self.closeButtonA11yLabel = closeButtonA11yLabel + self.actionButtonA11yId = actionButtonA11yId } } diff --git a/Client/Application/AccessibilityIdentifiers.swift b/Client/Application/AccessibilityIdentifiers.swift index 540ca5748f8e..a4f71623e482 100644 --- a/Client/Application/AccessibilityIdentifiers.swift +++ b/Client/Application/AccessibilityIdentifiers.swift @@ -44,6 +44,10 @@ public struct AccessibilityIdentifiers { } } + struct ContextualHints { + static let actionButton = "ContextualHints.ActionButton" + } + struct FirefoxHomepage { static let collectionView = "FxCollectionView" diff --git a/Client/Frontend/ContextualHint/ContextualHintViewController.swift b/Client/Frontend/ContextualHint/ContextualHintViewController.swift index e3da93a967da..65f05d5af974 100644 --- a/Client/Frontend/ContextualHint/ContextualHintViewController.swift +++ b/Client/Frontend/ContextualHint/ContextualHintViewController.swift @@ -143,7 +143,8 @@ class ContextualHintViewController: UIViewController, OnViewDismissable, Themeab actionButtonTitle: viewProvider.getCopyFor(.action), description: viewProvider.getCopyFor(.description), arrowDirection: arrowDirection, - closeButtonA11yLabel: .ContextualHints.ContextualHintsCloseAccessibility) + closeButtonA11yLabel: .ContextualHints.ContextualHintsCloseAccessibility, + actionButtonA11yId: AccessibilityIdentifiers.ContextualHints.actionButton) viewModel.closeButtonAction = { [weak self] _ in self?.dismissAnimated() }