/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @file:Suppress("DEPRECATION") package org.mozilla.fenix.helpers import android.content.Intent import android.view.ViewConfiguration.getLongPressTimeout import androidx.test.espresso.intent.rule.IntentsTestRule import androidx.test.rule.ActivityTestRule import androidx.test.uiautomator.UiSelector import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.helpers.FeatureSettingsHelper.Companion.settings import org.mozilla.fenix.helpers.TestHelper.appContext import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.onboarding.FenixOnboarding /** * A [org.junit.Rule] to handle shared test set up for tests on [HomeActivity]. * * @param initialTouchMode See [ActivityTestRule] * @param launchActivity See [ActivityTestRule] */ class HomeActivityTestRule( initialTouchMode: Boolean = false, launchActivity: Boolean = true, private val skipOnboarding: Boolean = false, ) : ActivityTestRule(HomeActivity::class.java, initialTouchMode, launchActivity), FeatureSettingsHelper by FeatureSettingsHelperDelegate() { // Using a secondary constructor allows us to easily delegate the settings to FeatureSettingsHelperDelegate. // Otherwise if wanting to use the same names we would have to override these settings in the primary // constructor and in that elide the FeatureSettingsHelperDelegate. constructor( initialTouchMode: Boolean = false, launchActivity: Boolean = true, skipOnboarding: Boolean = false, isHomeOnboardingDialogEnabled: Boolean = settings.showHomeOnboardingDialog && FenixOnboarding(appContext).userHasBeenOnboarded(), isPocketEnabled: Boolean = settings.showPocketRecommendationsFeature, isJumpBackInCFREnabled: Boolean = settings.shouldShowJumpBackInCFR, isRecentTabsFeatureEnabled: Boolean = settings.showRecentTabsFeature, isRecentlyVisitedFeatureEnabled: Boolean = settings.historyMetadataUIFeature, isPWAsPromptEnabled: Boolean = !settings.userKnowsAboutPwas, isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR, isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding, isDeleteSitePermissionsEnabled: Boolean = settings.deleteSitePermissions, etpPolicy: ETPPolicy = getETPPolicy(settings), ) : this(initialTouchMode, launchActivity, skipOnboarding) { this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled this.isPocketEnabled = isPocketEnabled this.isJumpBackInCFREnabled = isJumpBackInCFREnabled this.isRecentTabsFeatureEnabled = isRecentTabsFeatureEnabled this.isRecentlyVisitedFeatureEnabled = isRecentlyVisitedFeatureEnabled this.isPWAsPromptEnabled = isPWAsPromptEnabled this.isTCPCFREnabled = isTCPCFREnabled this.isWallpaperOnboardingEnabled = isWallpaperOnboardingEnabled this.isDeleteSitePermissionsEnabled = isDeleteSitePermissionsEnabled this.etpPolicy = etpPolicy } /** * Update settings after the activity was created. */ fun applySettingsExceptions(settings: (FeatureSettingsHelper) -> Unit) { FeatureSettingsHelperDelegate().also { settings(it) applyFlagUpdates() } } private val longTapUserPreference = getLongPressTimeout() override fun beforeActivityLaunched() { super.beforeActivityLaunched() setLongTapTimeout(3000) applyFlagUpdates() if (skipOnboarding) { skipOnboardingBeforeLaunch() } } override fun afterActivityFinished() { super.afterActivityFinished() setLongTapTimeout(longTapUserPreference) resetAllFeatureFlags() closeNotificationShade() } companion object { /** * Create a new instance of [HomeActivityTestRule] which by default will disable specific * app features that would otherwise negatively impact most tests. * * The disabled features are: * - the Jump back in CFR, * - the Total Cookie Protection CFR, * - the PWA prompt dialog, * - the wallpaper onboarding. */ fun withDefaultSettingsOverrides( initialTouchMode: Boolean = false, launchActivity: Boolean = true, skipOnboarding: Boolean = false, ) = HomeActivityTestRule( initialTouchMode = initialTouchMode, launchActivity = launchActivity, skipOnboarding = skipOnboarding, isJumpBackInCFREnabled = false, isPWAsPromptEnabled = false, isTCPCFREnabled = false, isWallpaperOnboardingEnabled = false, ) } } /** * A [org.junit.Rule] to handle shared test set up for tests on [HomeActivity]. This adds * functionality for using the Espresso-intents api, and extends from ActivityTestRule. * * @param initialTouchMode See [IntentsTestRule] * @param launchActivity See [IntentsTestRule] */ class HomeActivityIntentTestRule internal constructor( initialTouchMode: Boolean = false, launchActivity: Boolean = true, private val skipOnboarding: Boolean = false, ) : IntentsTestRule(HomeActivity::class.java, initialTouchMode, launchActivity), FeatureSettingsHelper by FeatureSettingsHelperDelegate() { // Using a secondary constructor allows us to easily delegate the settings to FeatureSettingsHelperDelegate. // Otherwise if wanting to use the same names we would have to override these settings in the primary // constructor and in that elide the FeatureSettingsHelperDelegate. constructor( initialTouchMode: Boolean = false, launchActivity: Boolean = true, skipOnboarding: Boolean = false, isHomeOnboardingDialogEnabled: Boolean = settings.showHomeOnboardingDialog && FenixOnboarding(appContext).userHasBeenOnboarded(), isPocketEnabled: Boolean = settings.showPocketRecommendationsFeature, isJumpBackInCFREnabled: Boolean = settings.shouldShowJumpBackInCFR, isRecentTabsFeatureEnabled: Boolean = settings.showRecentTabsFeature, isRecentlyVisitedFeatureEnabled: Boolean = settings.historyMetadataUIFeature, isPWAsPromptEnabled: Boolean = !settings.userKnowsAboutPwas, isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR, isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding, isDeleteSitePermissionsEnabled: Boolean = settings.deleteSitePermissions, etpPolicy: ETPPolicy = getETPPolicy(settings), ) : this(initialTouchMode, launchActivity, skipOnboarding) { this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled this.isPocketEnabled = isPocketEnabled this.isJumpBackInCFREnabled = isJumpBackInCFREnabled this.isRecentTabsFeatureEnabled = isRecentTabsFeatureEnabled this.isRecentlyVisitedFeatureEnabled = isRecentlyVisitedFeatureEnabled this.isPWAsPromptEnabled = isPWAsPromptEnabled this.isTCPCFREnabled = isTCPCFREnabled this.isWallpaperOnboardingEnabled = isWallpaperOnboardingEnabled this.isDeleteSitePermissionsEnabled = isDeleteSitePermissionsEnabled this.etpPolicy = etpPolicy } private val longTapUserPreference = getLongPressTimeout() private lateinit var intent: Intent /** * Update settings after the activity was created. */ fun applySettingsExceptions(settings: (FeatureSettingsHelper) -> Unit) { FeatureSettingsHelperDelegate().apply { settings(this) applyFlagUpdates() } } override fun getActivityIntent(): Intent? { return if (this::intent.isInitialized) { this.intent } else { super.getActivityIntent() } } fun withIntent(intent: Intent): HomeActivityIntentTestRule { this.intent = intent return this } override fun beforeActivityLaunched() { super.beforeActivityLaunched() setLongTapTimeout(3000) applyFlagUpdates() if (skipOnboarding) { skipOnboardingBeforeLaunch() } } override fun afterActivityFinished() { super.afterActivityFinished() setLongTapTimeout(longTapUserPreference) closeNotificationShade() resetAllFeatureFlags() } /** * Update the settings values from when this rule was first instantiated to account for any changes * done while running the tests. * Useful in the scenario about the activity being restarted which would otherwise set the initial * settings and override any changes made in the meantime. */ fun updateCachedSettings() { isHomeOnboardingDialogEnabled = settings.showHomeOnboardingDialog && FenixOnboarding(appContext).userHasBeenOnboarded() isPocketEnabled = settings.showPocketRecommendationsFeature isJumpBackInCFREnabled = settings.shouldShowJumpBackInCFR isRecentTabsFeatureEnabled = settings.showRecentTabsFeature isRecentlyVisitedFeatureEnabled = settings.historyMetadataUIFeature isPWAsPromptEnabled = !settings.userKnowsAboutPwas isTCPCFREnabled = settings.shouldShowTotalCookieProtectionCFR isWallpaperOnboardingEnabled = settings.showWallpaperOnboarding isDeleteSitePermissionsEnabled = settings.deleteSitePermissions etpPolicy = getETPPolicy(settings) } companion object { /** * Create a new instance of [HomeActivityIntentTestRule] which by default will disable specific * app features that would otherwise negatively impact most tests. * * The disabled features are: * - the Jump back in CFR, * - the Total Cookie Protection CFR, * - the PWA prompt dialog, * - the wallpaper onboarding. */ fun withDefaultSettingsOverrides( initialTouchMode: Boolean = false, launchActivity: Boolean = true, skipOnboarding: Boolean = false, ) = HomeActivityIntentTestRule( initialTouchMode = initialTouchMode, launchActivity = launchActivity, skipOnboarding = skipOnboarding, isJumpBackInCFREnabled = false, isPWAsPromptEnabled = false, isTCPCFREnabled = false, isWallpaperOnboardingEnabled = false, ) } } // changing the device preference for Touch and Hold delay, to avoid long-clicks instead of a single-click fun setLongTapTimeout(delay: Int) { // Issue: https://github.com/mozilla-mobile/fenix/issues/25132 var attempts = 0 while (attempts++ < 3) { try { mDevice.executeShellCommand("settings put secure long_press_timeout $delay") break } catch (e: RuntimeException) { e.printStackTrace() } } } private fun skipOnboardingBeforeLaunch() { // The production code isn't aware that we're using // this API so it can be fragile. FenixOnboarding(appContext).finish() } private fun closeNotificationShade() { if (mDevice.findObject( UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"), ).exists() ) { mDevice.pressHome() } }