Merge branch 'fenix/108.1.1' into fork

pull/543/head
akliuxingyuan 1 year ago
commit 6ec7bb2bf6

@ -36,4 +36,4 @@ jobs:
type: decision-task
treeherder-symbol: legacy-api-ui
target-tasks-method: legacy_api_ui_tests
when: [{hour: 10, minute: 30}]
when: [] # temporarily unscheduled

@ -1,9 +1,4 @@
[*.{kt,kts}]
# Disabling rules that were added in the latest versions of ktlint
# tracking here: https://github.com/mozilla-mobile/fenix/issues/4861
ktlint_disabled_rules=import-ordering
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_allow_trailing_comma=true

@ -1,4 +1,12 @@
---
growth-data:
description: A feature measuring campaign growth data
hasExposure: true
exposureDescription: ""
variables:
enabled:
type: boolean
description: "If true, the feature is active"
homescreen:
description: The homescreen that the user goes to when they press home or new tab.
hasExposure: true

@ -1,3 +1,5 @@
# .git-blame-ignore-revs
# 26901: Fix issues from ktlint baseline
# For #27667 - Remove import-ordering from the list of disabled ktlint rules (#27680)
9654b4dfb122b54b04369fe80a2f9c95811478e8
# For #26844: Fix ktlint issues and remove them from baseline. (#26901)
ffcef5ff2e3f78b6972dd16551f3f653b7035ccc

@ -1,7 +1,7 @@
queue_rules:
- name: default
conditions:
- status-success=pr-complete
- status-success=complete-pr
pull_request_rules:
- name: Resolve conflict
conditions:
@ -110,7 +110,7 @@ pull_request_rules:
- name: Needs landing - Squash
conditions:
- check-success=pr-complete
- label=pr:needs-landing-squashed
- label=pr:needs-landing-squashed
- "#approved-reviews-by>=1"
- -draft
- label!=pr:work-in-progress

@ -14,7 +14,10 @@ tasks:
then: '${tasks_for}@noreply.mozilla.org'
else:
$if: 'tasks_for == "github-push"'
then: '${event.pusher.email}'
then:
$if: 'event.pusher.email'
then: '${event.pusher.email}'
else: '${event.pusher.name}@users.noreply.github.com'
else:
$if: 'tasks_for == "github-pull-request"'
then: '${event.pull_request.user.login}@users.noreply.github.com'

@ -2,7 +2,7 @@ import org.mozilla.fenix.gradle.tasks.ApkSizeTask
plugins {
id "com.jetbrains.python.envs" version "0.0.26"
id "com.google.protobuf" version "0.8.17"
id "com.google.protobuf" version "0.8.19"
}
apply plugin: 'com.android.application'
@ -183,7 +183,6 @@ android {
buildConfigField "String", "AMO_COLLECTION_USER", "\"16201230\""
buildConfigField "String", "AMO_COLLECTION_NAME", "\"What-I-want-on-Fenix\""
}
}
buildFeatures {
@ -272,7 +271,7 @@ android.applicationVariants.all { variant ->
// Generate version codes for builds
// -------------------------------------------------------------------------------------------------
def isDebug = variant.buildType.resValues['IS_DEBUG']?.value ?: false
def isDebug = variant.buildType.resValues['bool/IS_DEBUG']?.value ?: false
def useReleaseVersioning = variant.buildType.buildConfigFields['USE_RELEASE_VERSIONING']?.value ?: false
println("----------------------------------------------")
@ -403,6 +402,21 @@ android.applicationVariants.all { variant ->
println("--")
}
// -------------------------------------------------------------------------------------------------
// Glean: Read custom server URL from local.properties of a local file if it exists
// -------------------------------------------------------------------------------------------------
print("Glean custom server URL: ")
if (gradle.hasProperty("localProperties.glean.custom.server.url")) {
def url=gradle.getProperty("localProperties.glean.custom.server.url")
buildConfigField 'String', 'GLEAN_CUSTOM_URL', url
println "(Added from local.properties file)"
} else {
buildConfigField 'String', 'GLEAN_CUSTOM_URL', 'null'
println("--")
}
// -------------------------------------------------------------------------------------------------
// BuildConfig: Set flag for official builds; similar to MOZILLA_OFFICIAL in mozilla-central.
// -------------------------------------------------------------------------------------------------
@ -487,7 +501,7 @@ configurations {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
}
}
@ -598,6 +612,7 @@ dependencies {
debugImplementation Deps.leakcanary
forkDebugImplementation Deps.leakcanary
implementation Deps.androidx_annotation
implementation Deps.androidx_compose_ui
implementation Deps.androidx_compose_ui_tooling
implementation Deps.androidx_compose_foundation
@ -623,6 +638,15 @@ dependencies {
implementation Deps.protobuf_javalite
implementation Deps.google_material
implementation Deps.adjust
implementation Deps.installreferrer // Required by Adjust
implementation Deps.google_ads_id // Required for the Google Advertising ID
// Required for in-app reviews
implementation Deps.google_play_review
implementation Deps.google_play_review_ktx
androidTestImplementation Deps.uiautomator
androidTestImplementation "tools.fastlane:screengrab:2.0.0"
// This Falcon version is added to maven central now required for Screengrab

File diff suppressed because it is too large Load Diff

@ -43,3 +43,22 @@ topsites-impression:
- https://github.com/mozilla-mobile/fenix/pull/23945
notification_emails:
- android-probes@mozilla.com
spoc:
description: |
Contains data identifying with which Pocket sponsored story the user
interacted with and the type of interaction: story impression or click.
include_client_id: false
reasons:
impression: |
A sponsored story had more than 50% of it's content visible
on the screen.
click: |
A sponsored story was clicked by the user.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/27549
- https://mozilla-hub.atlassian.net/browse/FNXV2-21791
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27550#issuecomment-1295027631
notification_emails:
- android-probes@mozilla.com

@ -0,0 +1,8 @@
<html>
<body>
<h2>Cross-site cookies storage access test</h2>
<h3>anti-tracker-test.com</h3>
<h4>different site, cross-origin iframe</h4>
<iframe width=500 height=1000 src="https://mozilla-mobile.github.io/testapp/anti-tracker-test_set_storage_with_sa_api.html"></iframe>
</body>
</html>

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>Html_Control_Form</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<p>Misc Link Types</p>
<section>
<a href="https://duckduckgo.com/">External link</a>
</section>
<section>
<a href="mailto://example@example.com">Email link</a>
</section>
<section>
<a href="tel://1234567890">Telephone link</a>
</section>
</html>

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<title>Html_Control_Form</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<p>Calendar Form</p>
<section>
<input type="date" id="calendar">
<button onclick="printDate()" id="submitDate"> Submit date </button>
<div id="displayDate"></div>
</section>
<p>Clock Form</p>
<section>
<input type="time" id="clock">
<button onclick="printTime()" id="submitTime"> Submit time </button>
<div id="displayTime"></div>
</section>
<p>Color Form</p>
<section>
<input type="color" id="colorPicker">
<button onclick="printColor()" id="submitColor"> Submit color </button>
<div id="displayColor"></div>
</section>
<p>Drop-down Form</p>
<select id="dropDown">
<option type="text" text="The Only Ones">The Only Ones</option>
<option type="text" text="The National">The National</option>
</select>
<button onclick="printOption()" id="submitOption"> Submit drop down option </button>
<div id="displayOption"></div>
<script>
function printOption() {
let dropDown = document.querySelector("#dropDown");
let displayOption = document.querySelector("#displayOption");
displayOption.innerHTML = "Selected option is: " + dropDown.value;
}
</script>
<script>
function printDate() {
let calendar = document.querySelector("#calendar");
let displayDate = document.querySelector("#displayDate");
displayDate.innerHTML = "Selected date is: " + calendar.value;
}
</script>
<script>
function printTime() {
let time = document.querySelector("#clock");
let displayTime = document.querySelector("#displayTime");
displayTime.innerHTML = "Selected time is: " + time.value;
}
</script>
<script>
function printColor() {
let colorPicker = document.querySelector("#colorPicker");
let displayColor = document.querySelector("#displayColor");
displayColor.innerHTML = "Selected color is: " + colorPicker.value;
}
</script>
</html>

@ -5,8 +5,8 @@
package org.mozilla.fenix.components
import android.content.Context
import mozilla.components.service.fxa.ServerConfig.Server
import mozilla.components.service.fxa.ServerConfig
import mozilla.components.service.fxa.ServerConfig.Server
/**
* Utility to configure Firefox Account stage servers.

@ -24,8 +24,8 @@ import mozilla.components.service.glean.net.ConceptFetchHttpUploader
import mozilla.components.service.glean.testing.GleanTestLocalServer
import okhttp3.mockwebserver.RecordedRequest
import org.json.JSONObject
import org.junit.Assert.assertTrue
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
@ -36,9 +36,9 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MockWebServerHelper
import java.util.concurrent.TimeUnit
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.util.concurrent.TimeUnit
import java.util.zip.GZIPInputStream
/**

@ -5,11 +5,6 @@
package org.mozilla.fenix.helpers
import android.graphics.Bitmap
/* 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/. */
import android.graphics.Color
import org.junit.Assert.assertEquals

@ -21,4 +21,10 @@ object Constants {
const val LONG_CLICK_DURATION: Long = 5000
const val LISTS_MAXSWIPES: Int = 3
const val RETRY_COUNT = 3
val searchEngineCodes = mapOf(
"Google" to "client=firefox-b-m",
"Bing" to "firefox&pc=MOZB&form=MOZMBA",
"DuckDuckGo" to "t=fpas",
)
}

@ -4,72 +4,84 @@
package org.mozilla.fenix.helpers
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import org.mozilla.fenix.ext.settings
class FeatureSettingsHelper {
private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
private val settings = context.settings()
// saving default values of feature flags
private var isPocketEnabled: Boolean = settings.showPocketRecommendationsFeature
private var isJumpBackInCFREnabled: Boolean = settings.shouldShowJumpBackInCFR
private var isRecentTabsFeatureEnabled: Boolean = settings.showRecentTabsFeature
private var isRecentlyVisitedFeatureEnabled: Boolean = settings.historyMetadataUIFeature
private var isUserKnowsAboutPwasTrue: Boolean = settings.userKnowsAboutPwas
private var isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR
private var isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding
fun setPocketEnabled(enabled: Boolean) {
settings.showPocketRecommendationsFeature = enabled
}
/**
* Helper for querying the status and modifying various features and settings in the application.
*/
interface FeatureSettingsHelper {
/**
* Whether the onboarding for existing users should be shown or not.
* It should appear only once on the first visit to homescreen.
*/
var isHomeOnboardingDialogEnabled: Boolean
fun setJumpBackCFREnabled(enabled: Boolean) {
settings.shouldShowJumpBackInCFR = enabled
}
/**
* Whether the Pocket stories feature is enabled or not.
*/
var isPocketEnabled: Boolean
fun setShowWallpaperOnboarding(enabled: Boolean) {
settings.showWallpaperOnboarding = enabled
}
/**
* Whether the "Jump back in" CFR should be shown or not.
* It should appear on the first visit to homescreen given that there is a tab opened.
*/
var isJumpBackInCFREnabled: Boolean
fun setRecentTabsFeatureEnabled(enabled: Boolean) {
settings.showRecentTabsFeature = enabled
}
/**
* Whether the onboarding dialog for choosing wallpapers should be shown or not.
*/
var isWallpaperOnboardingEnabled: Boolean
fun setRecentlyVisitedFeatureEnabled(enabled: Boolean) {
settings.historyMetadataUIFeature = enabled
}
/**
* Whether the "Jump back in" homescreen section is enabled or not.
* It shows the last visited tab on this device and on other synced devices.
*/
var isRecentTabsFeatureEnabled: Boolean
fun setStrictETPEnabled() {
settings.setStrictETP()
}
/**
* Whether the "Recently visited" homescreen section is enabled or not.
* It can show up to 9 history highlights and history groups.
*/
var isRecentlyVisitedFeatureEnabled: Boolean
fun disablePwaCFR(disable: Boolean) {
settings.userKnowsAboutPwas = disable
}
/**
* Whether the onboarding dialog for PWAs should be shown or not.
* It can show the first time a website that can be installed as a PWA is accessed.
*/
var isPWAsPromptEnabled: Boolean
fun deleteSitePermissions(delete: Boolean) {
settings.deleteSitePermissions = delete
}
/**
* Whether the "Site permissions" option is checked in the "Delete browsing data" screen or not.
*/
var isDeleteSitePermissionsEnabled: Boolean
/**
* Enable or disable showing the TCP CFR when accessing a webpage for the first time.
*/
fun setTCPCFREnabled(shouldShowCFR: Boolean) {
settings.shouldShowTotalCookieProtectionCFR = shouldShowCFR
}
var isTCPCFREnabled: Boolean
/**
* The current "Enhanced Tracking Protection" policy.
* @see ETPPolicy
*/
var etpPolicy: ETPPolicy
// Important:
// Use this after each test if you have modified these feature settings
// to make sure the app goes back to the default state
fun resetAllFeatureFlags() {
settings.showPocketRecommendationsFeature = isPocketEnabled
settings.shouldShowJumpBackInCFR = isJumpBackInCFREnabled
settings.showRecentTabsFeature = isRecentTabsFeatureEnabled
settings.historyMetadataUIFeature = isRecentlyVisitedFeatureEnabled
settings.userKnowsAboutPwas = isUserKnowsAboutPwasTrue
settings.shouldShowTotalCookieProtectionCFR = isTCPCFREnabled
settings.showWallpaperOnboarding = isWallpaperOnboardingEnabled
fun applyFlagUpdates()
fun resetAllFeatureFlags()
companion object {
val settings = InstrumentationRegistry.getInstrumentation().targetContext.settings()
}
}
/**
* All "Enhanced Tracking Protection" modes.
*/
enum class ETPPolicy {
STANDARD,
STRICT,
CUSTOM,
;
}

@ -0,0 +1,159 @@
/* 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/. */
package org.mozilla.fenix.helpers
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.helpers.ETPPolicy.CUSTOM
import org.mozilla.fenix.helpers.ETPPolicy.STANDARD
import org.mozilla.fenix.helpers.ETPPolicy.STRICT
import org.mozilla.fenix.helpers.FeatureSettingsHelper.Companion.settings
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.utils.Settings
/**
* Helper for querying the status and modifying various features and settings in the application.
*/
class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
/**
* The current feature flags used inside the app before the tests start.
* These will be restored when the tests end.
*/
private val initialFeatureFlags = FeatureFlags(
isHomeOnboardingDialogEnabled = settings.showHomeOnboardingDialog,
homeOnboardingDialogVersion = getHomeOnboardingVersion(),
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),
)
/**
* The current feature flags updated in tests.
*/
private var updatedFeatureFlags = initialFeatureFlags.copy()
override var isHomeOnboardingDialogEnabled: Boolean
get() = updatedFeatureFlags.isHomeOnboardingDialogEnabled &&
FenixOnboarding(appContext).userHasBeenOnboarded()
set(value) {
updatedFeatureFlags.isHomeOnboardingDialogEnabled = value
updatedFeatureFlags.homeOnboardingDialogVersion = when (value) {
true -> FenixOnboarding.CURRENT_ONBOARDING_VERSION
false -> 0
}
}
override var isPocketEnabled: Boolean by updatedFeatureFlags::isPocketEnabled
override var isJumpBackInCFREnabled: Boolean by updatedFeatureFlags::isJumpBackInCFREnabled
override var isWallpaperOnboardingEnabled: Boolean by updatedFeatureFlags::isWallpaperOnboardingEnabled
override var isRecentTabsFeatureEnabled: Boolean by updatedFeatureFlags::isRecentTabsFeatureEnabled
override var isRecentlyVisitedFeatureEnabled: Boolean by updatedFeatureFlags::isRecentlyVisitedFeatureEnabled
override var isPWAsPromptEnabled: Boolean by updatedFeatureFlags::isPWAsPromptEnabled
override var isTCPCFREnabled: Boolean by updatedFeatureFlags::isTCPCFREnabled
override var etpPolicy: ETPPolicy by updatedFeatureFlags::etpPolicy
override fun applyFlagUpdates() {
applyFeatureFlags(updatedFeatureFlags)
}
override fun resetAllFeatureFlags() {
applyFeatureFlags(initialFeatureFlags)
}
override var isDeleteSitePermissionsEnabled: Boolean by updatedFeatureFlags::isDeleteSitePermissionsEnabled
private fun applyFeatureFlags(featureFlags: FeatureFlags) {
settings.showHomeOnboardingDialog = featureFlags.isHomeOnboardingDialogEnabled
setHomeOnboardingVersion(featureFlags.homeOnboardingDialogVersion)
settings.showPocketRecommendationsFeature = featureFlags.isPocketEnabled
settings.shouldShowJumpBackInCFR = featureFlags.isJumpBackInCFREnabled
settings.showRecentTabsFeature = featureFlags.isRecentTabsFeatureEnabled
settings.historyMetadataUIFeature = featureFlags.isRecentlyVisitedFeatureEnabled
settings.userKnowsAboutPwas = !featureFlags.isPWAsPromptEnabled
settings.shouldShowTotalCookieProtectionCFR = featureFlags.isTCPCFREnabled
settings.showWallpaperOnboarding = featureFlags.isWallpaperOnboardingEnabled
settings.deleteSitePermissions = featureFlags.isDeleteSitePermissionsEnabled
setETPPolicy(featureFlags.etpPolicy)
}
}
private data class FeatureFlags(
var isHomeOnboardingDialogEnabled: Boolean,
var homeOnboardingDialogVersion: Int,
var isPocketEnabled: Boolean,
var isJumpBackInCFREnabled: Boolean,
var isRecentTabsFeatureEnabled: Boolean,
var isRecentlyVisitedFeatureEnabled: Boolean,
var isPWAsPromptEnabled: Boolean,
var isTCPCFREnabled: Boolean,
var isWallpaperOnboardingEnabled: Boolean,
var isDeleteSitePermissionsEnabled: Boolean,
var etpPolicy: ETPPolicy,
)
internal fun getETPPolicy(settings: Settings): ETPPolicy {
return when {
settings.useStrictTrackingProtection -> STRICT
settings.useCustomTrackingProtection -> CUSTOM
else -> STANDARD
}
}
private fun setETPPolicy(policy: ETPPolicy) {
when (policy) {
STRICT -> settings.setStrictETP()
// The following two cases update ETP in the same way "setStrictETP" does.
STANDARD -> {
settings.preferences.edit()
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_strict_default),
false,
)
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_option),
false,
)
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
true,
)
.commit()
}
CUSTOM -> {
settings.preferences.edit()
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_strict_default),
false,
)
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
true,
)
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_option),
true,
)
.commit()
}
}
}
private fun getHomeOnboardingVersion(): Int {
return FenixOnboarding(appContext)
.preferences
.getInt(FenixOnboarding.LAST_VERSION_ONBOARDING_KEY, 0)
}
private fun setHomeOnboardingVersion(version: Int) {
FenixOnboarding(appContext)
.preferences.edit()
.putInt(FenixOnboarding.LAST_VERSION_ONBOARDING_KEY, version)
.commit()
}

@ -6,12 +6,12 @@
package org.mozilla.fenix.helpers
import android.app.Activity
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
@ -27,34 +27,91 @@ class HomeActivityTestRule(
initialTouchMode: Boolean = false,
launchActivity: Boolean = true,
private val skipOnboarding: Boolean = false,
) :
ActivityTestRule<HomeActivity>(HomeActivity::class.java, initialTouchMode, launchActivity) {
) : ActivityTestRule<HomeActivity>(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
}
/**
* Helper for updating various app settings that could interfere with the tests.
* Tests that use [HomeActivityTestRule] are expected to rely on this [FeatureSettingsHelper]
* instead of instantiating their own.
*
* The main benefit this brings is better ordering of operations with the settings cleanup
* automatically happening just before the [Activity] under test finishes which may as opposed to
* cleanup happening earlier and modifying the app behavior.
* Update settings after the activity was created.
*/
val featureSettingsHelper = FeatureSettingsHelper()
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)
featureSettingsHelper.resetAllFeatureFlags()
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,
)
}
}
/**
@ -65,17 +122,59 @@ class HomeActivityTestRule(
* @param launchActivity See [IntentsTestRule]
*/
class HomeActivityIntentTestRule(
class HomeActivityIntentTestRule internal constructor(
initialTouchMode: Boolean = false,
launchActivity: Boolean = true,
private val skipOnboarding: Boolean = false,
) :
IntentsTestRule<HomeActivity>(HomeActivity::class.java, initialTouchMode, launchActivity) {
) : IntentsTestRule<HomeActivity>(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()
/**
* Update settings after the activity was created.
*/
fun applySettingsExceptions(settings: (FeatureSettingsHelper) -> Unit) {
FeatureSettingsHelperDelegate().apply {
settings(this)
applyFlagUpdates()
}
}
override fun beforeActivityLaunched() {
super.beforeActivityLaunched()
setLongTapTimeout(3000)
applyFlagUpdates()
if (skipOnboarding) { skipOnboardingBeforeLaunch() }
}
@ -83,6 +182,53 @@ class HomeActivityIntentTestRule(
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,
)
}
}

@ -12,9 +12,9 @@ import android.os.SystemClock
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import org.junit.rules.ExternalResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import java.util.Date
import kotlin.random.Random
import org.mozilla.fenix.helpers.TestHelper.mDevice
private const val mockProviderName = LocationManager.GPS_PROVIDER

@ -7,14 +7,14 @@ package org.mozilla.fenix.helpers
import android.os.Handler
import android.os.Looper
import androidx.test.platform.app.InstrumentationRegistry
import java.io.IOException
import java.io.InputStream
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import okio.Buffer
import okio.source
import java.io.IOException
import java.io.InputStream
/**
* A [MockWebServer] [Dispatcher] that will return a generic search results page in the body of

@ -99,6 +99,18 @@ object TestAssetHelper {
return TestAsset(url, "", "")
}
fun getHTMLControlsFormAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/htmlControls.html").toString().toUri()!!
return TestAsset(url, "", "")
}
fun getExternalLinksAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/externalLinks.html").toString().toUri()!!
return TestAsset(url, "", "")
}
fun getAudioPageAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/audioMediaPage.html").toString().toUri()!!
val title = "Audio_Test_Page"

@ -9,6 +9,8 @@ package org.mozilla.fenix.helpers
import android.app.ActivityManager
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -95,6 +97,7 @@ object TestHelper {
fun restartApp(activity: HomeActivityIntentTestRule) {
with(activity) {
updateCachedSettings()
finishActivity()
mDevice.waitForIdle()
launchActivity(null)
@ -417,4 +420,14 @@ object TestHelper {
Configuration::class.java,
).invoke(am, config)
}
/**
* Creates clipboard data.
*/
fun setTextToClipBoard(context: Context, message: String) {
val clipBoard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText("label", message)
clipBoard.setPrimaryClip(clipData)
}
}

@ -5,13 +5,13 @@
package org.mozilla.fenix.helpers.matchers
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.view.View
import android.widget.ImageView
import androidx.test.espresso.matcher.BoundedMatcher
import org.hamcrest.Description
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.StateListDrawable
import android.graphics.drawable.Drawable
class BitmapDrawableMatcher(private val bitmap: Bitmap, private val name: String) :
BoundedMatcher<View, ImageView>(ImageView::class.java) {

@ -33,7 +33,7 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
*
* Say no to main thread IO! 🙅
*/
private const val EXPECTED_SUPPRESSION_COUNT = 17
private const val EXPECTED_SUPPRESSION_COUNT = 19
/**
* The number of times we call the `runBlocking` coroutine method on the main thread during this

@ -14,8 +14,8 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.homeScreen
import tools.fastlane.screengrab.Screengrab
@ -43,7 +43,6 @@ class DefaultHomeScreenTest : ScreenshotTest() {
@Test
fun showDefaultHomeScreen() {
homeScreen {
swipeToBottom()
verifyAccountsSignInButton()
Screengrab.screenshot("HomeScreenRobot_home-screen-scroll")
TestAssetHelper.waitingTime

@ -43,8 +43,7 @@ class MenuScreenShotTest : ScreenshotTest() {
val localeTestRule = LocaleTestRule()
@get:Rule
var mActivityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = mActivityTestRule.featureSettingsHelper
var mActivityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
@ -53,8 +52,6 @@ class MenuScreenShotTest : ScreenshotTest() {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setTCPCFREnabled(false)
}
@After

@ -13,7 +13,6 @@ import mozilla.appservices.places.BookmarkRoot
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
@ -48,8 +47,7 @@ class BookmarksTest {
}
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField
@ -62,8 +60,6 @@ class BookmarksTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
@ -105,7 +101,7 @@ class BookmarksTest {
verifyFolderTitle("Bookmarks Menu")
verifyFolderTitle("Bookmarks Toolbar")
verifyFolderTitle("Other Bookmarks")
verifySignInToSyncButton()
verifySyncSignInButton()
}
}.clickSingInToSyncButton {
verifyTurnOnSyncToolbarTitle()
@ -222,7 +218,7 @@ class BookmarksTest {
clickClearButton()
longClickToolbar()
clickPasteText()
verifyPastedToolbarText(defaultWebPage.url.toString())
verifyTypedToolbarText(defaultWebPage.url.toString())
}
}
@ -264,6 +260,83 @@ class BookmarksTest {
}
}
@Test
fun openAllInTabsTest() {
val webPages = listOf(
TestAssetHelper.getGenericAsset(mockWebServer, 1),
TestAssetHelper.getGenericAsset(mockWebServer, 2),
TestAssetHelper.getGenericAsset(mockWebServer, 3),
TestAssetHelper.getGenericAsset(mockWebServer, 4),
)
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
createFolder("root")
createFolder("sub", "root")
createFolder("empty", "root")
}.closeMenu {
}
browserScreen {
createBookmark(webPages[0].url)
createBookmark(webPages[1].url, "root")
createBookmark(webPages[2].url, "root")
createBookmark(webPages[3].url, "sub")
}.openTabDrawer {
closeTab()
}
browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
}.openThreeDotMenu("root") {
}.clickOpenAllInTabs {
verifyTabTrayIsOpened()
verifyNormalModeSelected()
verifyExistingOpenTabs("Test_Page_2", "Test_Page_3", "Test_Page_4")
// Bookmark that is not under the root folder should not be opened
verifyNoExistingOpenTabs("Test_Page_1")
}
}
@Test
fun openAllInPrivateTabsTest() {
val webPages = listOf(
TestAssetHelper.getGenericAsset(mockWebServer, 1),
TestAssetHelper.getGenericAsset(mockWebServer, 2),
)
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
createFolder("root")
createFolder("sub", "root")
createFolder("empty", "root")
}.closeMenu {
}
browserScreen {
createBookmark(webPages[0].url, "root")
createBookmark(webPages[1].url, "sub")
}.openTabDrawer {
closeTab()
}
browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
}.openThreeDotMenu("root") {
}.clickOpenAllInPrivateTabs {
verifyTabTrayIsOpened()
verifyPrivateModeSelected()
verifyExistingOpenTabs("Test_Page_1", "Test_Page_2")
}
}
@Test
fun openBookmarkInPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -354,7 +427,6 @@ class BookmarksTest {
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openSelectionInNewTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -384,7 +456,6 @@ class BookmarksTest {
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openSelectionInPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -5,7 +5,6 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
@ -26,20 +25,12 @@ class BrowsingErrorPagesTest {
private val harmfulSiteWarning = getStringResource(R.string.mozac_browser_errorpages_safe_harmful_uri_title)
@get: Rule
val mActivityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = mActivityTestRule.featureSettingsHelper
val mActivityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
// disabling the jump-back-in pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
}
@SmokeTest
@Test
fun blockMalwarePageTest() {

@ -15,7 +15,6 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.ui.robots.browserScreen
@ -35,23 +34,21 @@ class CollectionTest {
private val firstCollectionName = "testcollection_1"
private val secondCollectionName = "testcollection_2"
private val collectionName = "First Collection"
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val composeTestRule = AndroidComposeTestRule(
HomeActivityIntentTestRule(),
{ it.activity },
)
@Before
fun setUp() {
// disabling these features to have better visibility of Collections,
// and to avoid multiple matches on tab items
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setRecentlyVisitedFeatureEnabled(false)
HomeActivityIntentTestRule(
isPocketEnabled = false,
isJumpBackInCFREnabled = false,
isRecentTabsFeatureEnabled = false,
isRecentlyVisitedFeatureEnabled = false,
),
) { it.activity }
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
@ -62,9 +59,6 @@ class CollectionTest {
@After
fun tearDown() {
mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
@SmokeTest

@ -9,13 +9,11 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
@ -42,14 +40,12 @@ class ContextMenusTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField
val retryTestRule = RetryTestRule(3)
private val featureSettingsHelper = FeatureSettingsHelper()
@Before
fun setUp() {
activityIntentTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
@ -58,19 +54,15 @@ class ContextMenusTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyContextOpenLinkNewTab() {
val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -80,7 +72,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("Link 1")
longClickLink("Link 1")
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInNewTab()
verifySnackBarText("New tab opened")
@ -95,7 +87,6 @@ class ContextMenusTest {
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyContextOpenLinkPrivateTab() {
val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -105,7 +96,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("Link 2")
longClickLink("Link 2")
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInPrivateTab()
verifySnackBarText("New private tab opened")
@ -127,7 +118,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("Link 3")
longClickLink("Link 3")
verifyLinkContextMenuItems(genericURL.url)
clickContextCopyLink()
verifySnackBarText("Link copied to clipboard")
@ -147,7 +138,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("Link 1")
longClickLink("Link 1")
verifyLinkContextMenuItems(genericURL.url)
clickContextShareLink(genericURL.url) // verify share intent is matched with associated URL
}
@ -163,7 +154,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("test_link_image")
longClickLink("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
clickContextOpenImageNewTab()
verifySnackBarText("New tab opened")
@ -182,7 +173,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("test_link_image")
longClickLink("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
clickContextCopyImageLocation()
verifySnackBarText("Link copied to clipboard")
@ -202,7 +193,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("test_link_image")
longClickLink("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
clickContextSaveImage()
}
@ -215,7 +206,6 @@ class ContextMenusTest {
}
}
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun verifyContextMixedVariations() {
val pageLinks =
@ -228,13 +218,13 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickMatchingText("Link 1")
longClickLink("Link 1")
verifyLinkContextMenuItems(genericURL.url)
dismissContentContextMenu(genericURL.url)
longClickMatchingText("test_link_image")
longClickLink("test_link_image")
verifyLinkImageContextMenuItems(imageResource.url)
dismissContentContextMenu(imageResource.url)
longClickMatchingText("test_no_link_image")
longClickLink("test_no_link_image")
verifyNoLinkImageContextMenuItems(imageResource.url)
}
}
@ -246,7 +236,7 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickMatchingText(genericURL.content)
longClickLink(genericURL.content)
}.clickShareSelectedText {
verifyAndroidShareLayout()
}

@ -0,0 +1,70 @@
/* 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/. */
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying the new Cookie protection & homescreen feature hints.
* Note: This involves setting the feature flags On for CFRs which are disabled elsewhere.
*
*/
class ContextualHintsTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule(
isJumpBackInCFREnabled = true,
isTCPCFREnabled = true,
isPocketEnabled = false,
isRecentlyVisitedFeatureEnabled = false,
)
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun jumpBackInCFRTest() {
val genericPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyCookiesProtectionHint()
// One back press to dismiss the TCP hint
mDevice.pressBack()
}.goToHomescreen {
verifyJumpBackInMessage()
}
}
@Test
fun cookieProtectionHintTest() {
val genericPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyCookiesProtectionHint()
}
}
}

@ -12,7 +12,6 @@ import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.getStringResource
@ -22,21 +21,20 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
class CrashReportingTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
private val tabCrashMessage = getStringResource(R.string.tab_crash_title_2)
@get:Rule
val activityTestRule = AndroidComposeTestRule(
HomeActivityIntentTestRule(),
{ it.activity },
)
HomeActivityIntentTestRule(
isPocketEnabled = false,
isJumpBackInCFREnabled = false,
isWallpaperOnboardingEnabled = false,
isTCPCFREnabled = false,
),
) { it.activity }
@Before
fun setUp() {
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
@ -47,7 +45,6 @@ class CrashReportingTest {
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
@Test
@ -77,7 +74,6 @@ class CrashReportingTest {
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25029")
@SmokeTest
@Test
fun useAppWhileTabIsCrashedTest() {
@ -110,7 +106,6 @@ class CrashReportingTest {
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun privateBrowsingUseAppWhileTabIsCrashedTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
@ -124,7 +119,6 @@ class CrashReportingTest {
}.openNewTab {
}.submitQuery(secondWebPage.url.toString()) {
waitForPageToLoad()
verifyPageContent("Page content: 2")
}
navigationToolbar {

@ -14,7 +14,7 @@ import org.junit.Test
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.FeatureSettingsHelperDelegate
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
@ -47,7 +47,7 @@ class CustomTabsTest {
false,
)
private val featureSettingsHelper = FeatureSettingsHelper()
private val featureSettingsHelper = FeatureSettingsHelperDelegate()
@Before
fun setUp() {
@ -57,7 +57,9 @@ class CustomTabsTest {
start()
}
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.apply {
isTCPCFREnabled = false
}.applyFlagUpdates()
}
@After
@ -145,7 +147,7 @@ class CustomTabsTest {
clickClearButton()
longClickToolbar()
clickPasteText()
verifyPastedToolbarText(customTabPage.url.toString())
verifyTypedToolbarText(customTabPage.url.toString())
}
}

@ -5,14 +5,11 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -27,10 +24,9 @@ class DownloadFileTypesTest(fileName: String) {
/* Remote test page managed by Mozilla Mobile QA team at https://github.com/mozilla-mobile/testapp */
private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
private var downloadFile: String = fileName
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = HomeActivityIntentTestRule()
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
companion object {
// Creating test data. The test will take each file name as a parameter and run it individually.
@ -48,21 +44,6 @@ class DownloadFileTypesTest(fileName: String) {
)
}
@Before
fun setUp() {
// disabling the jump-back-in pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
// disable the TCP CFR that appears when loading webpages and interferes with the tests.
featureSettingsHelper.setTCPCFREnabled(false)
// disabling the PWA CFR on 3rd visit
featureSettingsHelper.disablePwaCFR(true)
}
@After
fun tearDown() {
featureSettingsHelper.resetAllFeatureFlags()
}
@SmokeTest
@Test
fun downloadMultipleFileTypesTest() {

@ -4,9 +4,7 @@
package org.mozilla.fenix.ui
import android.os.Build
import androidx.core.net.toUri
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.permission.PermissionRequester
import androidx.test.uiautomator.UiDevice
@ -19,7 +17,6 @@ import org.junit.rules.TestRule
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.deleteDownloadFromStorage
import org.mozilla.fenix.ui.robots.browserScreen
@ -37,14 +34,13 @@ import org.mozilla.fenix.ui.robots.notificationShade
**/
class DownloadTest {
private lateinit var mDevice: UiDevice
private val featureSettingsHelper = FeatureSettingsHelper()
/* Remote test page managed by Mozilla Mobile QA team at https://github.com/mozilla-mobile/testapp */
private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
private var downloadFile: String = ""
@get:Rule
val activityTestRule = HomeActivityIntentTestRule()
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
// Making sure to grant storage access for this test running on API 28
@get: Rule
@ -65,12 +61,6 @@ class DownloadTest {
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// disabling the jump-back-in pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
// disable the TCP CFR that appears when loading webpages and interferes with the tests.
featureSettingsHelper.setTCPCFREnabled(false)
// disabling the PWA CFR on 3rd visit
featureSettingsHelper.disablePwaCFR(true)
// clear all existing notifications
notificationShade {
mDevice.openNotification()
@ -80,13 +70,11 @@ class DownloadTest {
@After
fun tearDown() {
featureSettingsHelper.resetAllFeatureFlags()
notificationShade {
cancelAllShownNotifications()
}
}
@Ignore("Failing with frequent ANR: https://github.com/mozilla-mobile/fenix/issues/25926")
@Test
fun testDownloadPrompt() {
downloadFile = "web_icon.png"
@ -139,7 +127,6 @@ class DownloadTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
fun pauseResumeCancelDownloadTest() {
@ -171,6 +158,7 @@ class DownloadTest {
- downloads appear in the list
- deleting a download from device storage, removes it from the Downloads Menu too
*/
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/27220")
@SmokeTest
@Test
fun manageDownloadsInDownloadsMenuTest() {

@ -4,6 +4,7 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
@ -11,9 +12,12 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.ui.robots.enhancedTrackingProtection
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -32,12 +36,15 @@ import org.mozilla.fenix.ui.robots.settingsSubMenuEnhancedTrackingProtection
* - Verifying Enhanced Tracking Protection site exceptions
*/
class StrictEnhancedTrackingProtectionTest {
class EnhancedTrackingProtectionTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule(
isJumpBackInCFREnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
)
@Before
fun setUp() {
@ -45,10 +52,6 @@ class StrictEnhancedTrackingProtectionTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setStrictETPEnabled()
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
}
@After
@ -83,8 +86,8 @@ class StrictEnhancedTrackingProtectionTest {
}.openEnhancedTrackingProtectionSubMenu {
switchEnhancedTrackingProtectionToggle()
verifyEnhancedTrackingProtectionOptionsEnabled(false)
}.goBack {
}.goBack { }
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) { }
@ -106,8 +109,8 @@ class StrictEnhancedTrackingProtectionTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun testStrictVisitProtectionSheet() {
appContext.settings().setStrictETP()
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
@ -127,9 +130,9 @@ class StrictEnhancedTrackingProtectionTest {
}
}
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun testStrictVisitDisableExceptionToggle() {
appContext.settings().setStrictETP()
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
@ -162,9 +165,10 @@ class StrictEnhancedTrackingProtectionTest {
}
}
@Ignore("Permanent failure: https://github.com/mozilla-mobile/fenix/issues/27312")
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun testStrictVisitSheetDetails() {
appContext.settings().setStrictETP()
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
@ -192,4 +196,71 @@ class StrictEnhancedTrackingProtectionTest {
viewTrackingContentBlockList()
}
}
@Ignore("Permanent failure: https://github.com/mozilla-mobile/fenix/issues/27312")
@SmokeTest
@Test
fun customTrackingProtectionSettingsTest() {
val genericWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingPage = TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionOptionsEnabled()
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
}.goBackToHomeScreen {}
navigationToolbar {
// browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.openDetails {
verifyTrackingCookiesBlocked()
verifyCryptominersBlocked()
verifyFingerprintersBlocked()
verifyTrackingContentBlocked()
viewTrackingContentBlockList()
}
}
@SmokeTest
@Test
fun blockCookiesStorageAccessTest() {
// With Standard TrackingProtection settings
val page = mockWebServer.url("pages/cross-site-cookies.html").toString().toUri()
val originSite = "https://mozilla-mobile.github.io"
val currentSite = "http://localhost:${mockWebServer.port}"
navigationToolbar {
}.enterURLAndEnterToBrowser(page) {
}.clickRequestStorageAccessButton {
verifyCrossOriginCookiesPermissionPrompt(originSite, currentSite)
}.clickPagePermissionButton(allow = false) {
verifyPageContent("access denied")
}
}
@SmokeTest
@Test
fun allowCookiesStorageAccessTest() {
// With Standard TrackingProtection settings
val page = mockWebServer.url("pages/cross-site-cookies.html").toString().toUri()
val originSite = "https://mozilla-mobile.github.io"
val currentSite = "http://localhost:${mockWebServer.port}"
navigationToolbar {
}.enterURLAndEnterToBrowser(page) {
}.clickRequestStorageAccessButton {
verifyCrossOriginCookiesPermissionPrompt(originSite, currentSite)
}.clickPagePermissionButton(allow = true) {
verifyPageContent("access granted")
}
}
}

@ -13,7 +13,6 @@ import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
@ -40,8 +39,7 @@ class HistoryTest {
private lateinit var mDevice: UiDevice
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
@ -53,8 +51,6 @@ class HistoryTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
@ -221,7 +217,6 @@ class HistoryTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openHistoryInNewTabTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -251,7 +246,6 @@ class HistoryTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openHistoryInPrivateTabTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -336,9 +330,8 @@ class HistoryTest {
}
}
@Test
// This test verifies the Recently Closed Tabs List and items
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
@Test
fun verifyRecentlyClosedTabsListTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -36,8 +36,7 @@ class HomeScreenTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField
@ -51,8 +50,6 @@ class HomeScreenTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
@ -144,8 +141,10 @@ class HomeScreenTest {
@Test
fun dismissOnboardingUsingHelpTest() {
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
activityTestRule.applySettingsExceptions {
it.isJumpBackInCFREnabled = false
it.isWallpaperOnboardingEnabled = false
}
homeScreen {
verifyWelcomeHeader()
@ -172,8 +171,10 @@ class HomeScreenTest {
@Test
fun verifyPocketHomepageStoriesTest() {
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setRecentlyVisitedFeatureEnabled(false)
activityTestRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
@ -193,8 +194,6 @@ class HomeScreenTest {
@Test
fun verifyCustomizeHomepageTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {

@ -11,7 +11,6 @@ import mozilla.components.concept.engine.mediasession.MediaSession
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.components
@ -37,7 +36,7 @@ class MediaNotificationTest {
private lateinit var mDevice: UiDevice
@get:Rule
val activityTestRule = HomeActivityTestRule()
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
private lateinit var browserStore: BrowserStore
@Rule
@ -62,7 +61,6 @@ class MediaNotificationTest {
mockWebServer.shutdown()
}
@Ignore("Failing with ANR: https://github.com/mozilla-mobile/fenix/issues/15754")
@Test
fun videoPlaybackSystemNotificationTest() {
val videoTestPage = TestAssetHelper.getVideoPageAsset(mockWebServer)
@ -96,7 +94,6 @@ class MediaNotificationTest {
mDevice.pressBack()
}
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun mediaSystemNotificationInPrivateModeTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)

@ -34,8 +34,7 @@ class NavigationToolbarTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
@ -44,9 +43,6 @@ class NavigationToolbarTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.disablePwaCFR(true)
}
@After

@ -6,8 +6,6 @@ package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
@ -27,14 +25,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
class NoNetworkAccessStartupTests {
@get:Rule
val activityTestRule = HomeActivityTestRule(launchActivity = false)
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
@Before
fun setUp() {
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
}
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides(launchActivity = false)
@After
fun tearDown() {
@ -61,7 +52,6 @@ class NoNetworkAccessStartupTests {
// Based on STR from https://github.com/mozilla-mobile/fenix/issues/16886
@Test
fun networkInterruptedFromBrowserToHomeTest() {
featureSettingsHelper.setJumpBackCFREnabled(false)
val url = "example.com"
activityTestRule.launchActivity(null)
@ -93,7 +83,6 @@ class NoNetworkAccessStartupTests {
}.refreshPage { }
}
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun testSignInPageWithNoNetworkConnection() {
setNetworkEnabled(false)

@ -0,0 +1,53 @@
/* 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/. */
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.homeScreen
/**
* Tests for verifying the new onboarding features.
* Note: This involves setting the feature flag On for the onboarding dialog
*
*/
class OnboardingFeaturesTest {
@get:Rule
val activityTestRule = AndroidComposeTestRule(
HomeActivityTestRule(isHomeOnboardingDialogEnabled = true),
) { it.activity }
@SmokeTest
@Test
fun upgradingUsersOnboardingScreensTest() {
homeScreen {
verifyUpgradingUserOnboardingFirstScreen(activityTestRule)
clickGetStartedButton(activityTestRule)
verifyUpgradingUserOnboardingSecondScreen(activityTestRule)
clickSkipButton(activityTestRule)
verifyHomeScreen()
}
}
@Test
fun upgradingUsersOnboardingSignInButtonTest() {
homeScreen {
verifyUpgradingUserOnboardingFirstScreen(activityTestRule)
clickGetStartedButton(activityTestRule)
verifyUpgradingUserOnboardingSecondScreen(activityTestRule)
}.clickUpgradingUserOnboardingSignInButton(activityTestRule) {
verifyTurnOnSyncMenu()
mDevice.pressBack()
}
homeScreen {
verifyHomeScreen()
}
}
}

@ -1,14 +1,11 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.Constants.PackageName.GMAIL_APP
import org.mozilla.fenix.helpers.Constants.PackageName.PHONE_APP
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.assertNativeAppOpens
import org.mozilla.fenix.ui.robots.customTabScreen
@ -16,8 +13,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.pwaScreen
class PwaTest {
private val featureSettingsHelper = FeatureSettingsHelper()
/* Updated externalLinks.html to v2.0,
changed the hypertext reference to mozilla-mobile.github.io/testapp/downloads for "External link"
*/
@ -27,18 +22,7 @@ class PwaTest {
private val shortcutTitle = "TEST_APP"
@get:Rule
val activityTestRule = HomeActivityIntentTestRule()
@Before
fun setUp() {
featureSettingsHelper.disablePwaCFR(true)
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
fun tearDown() {
featureSettingsHelper.resetAllFeatureFlags()
}
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@SmokeTest
@Test

@ -37,7 +37,7 @@ class ReaderViewTest {
private val estimatedReadingTime = "1 - 2 minutes"
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField

@ -6,11 +6,10 @@ package org.mozilla.fenix.ui
import android.content.Context
import android.hardware.camera2.CameraManager
import android.os.Build
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.core.net.toUri
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.filters.SdkSuppress
import androidx.test.espresso.Espresso.pressBack
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.icons.generator.DefaultIconGenerator
import mozilla.components.feature.search.ext.createSearchEngine
@ -23,16 +22,18 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.Constants.PackageName.ANDROID_SETTINGS
import org.mozilla.fenix.helpers.Constants.searchEngineCodes
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.SearchDispatcher
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.assertNativeAppOpens
import org.mozilla.fenix.helpers.TestHelper.denyPermission
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.grantPermission
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.setCustomSearchEngine
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.multipleSelectionToolbar
@ -48,13 +49,19 @@ import org.mozilla.fenix.ui.robots.multipleSelectionToolbar
class SearchTest {
lateinit var searchMockServer: MockWebServer
lateinit var queryString: String
@get:Rule
val activityTestRule = AndroidComposeTestRule(
HomeActivityTestRule(),
{ it.activity },
)
private val featureSettingsHelper = activityTestRule.activityRule.featureSettingsHelper
HomeActivityTestRule(
skipOnboarding = true,
isPocketEnabled = false,
isJumpBackInCFREnabled = false,
isRecentTabsFeatureEnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
),
) { it.activity }
@Before
fun setUp() {
@ -62,10 +69,6 @@ class SearchTest {
dispatcher = SearchDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
}
@After
@ -84,7 +87,6 @@ class SearchTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
fun scanButtonDenyPermissionTest() {
@ -106,7 +108,6 @@ class SearchTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
fun scanButtonAllowPermissionTest() {
@ -122,64 +123,50 @@ class SearchTest {
}
@Test
fun shortcutButtonTest() {
val searchEngineURL = "bing.com/search?q=mozilla%20firefox"
fun setDefaultSearchEngineFromShortcutsTest() {
queryString = "firefox"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
enableShowSearchShortcuts()
toggleShowSearchShortcuts()
}.goBack {
}.goBack {
}.openSearch {
verifySearchBarEmpty()
clickSearchEngineButton(activityTestRule, "Bing")
typeSearch("mozilla")
verifySearchEngineResults(activityTestRule, "mozilla firefox", "Bing")
clickSearchEngineResult(activityTestRule, "mozilla firefox")
scrollToSearchEngineSettings(activityTestRule)
}.clickSearchEngineSettings(activityTestRule) {
changeDefaultSearchEngine("DuckDuckGo")
}
browserScreen {
waitForPageToLoad()
verifyUrl(searchEngineURL)
}
}
exitMenu()
@Test
fun shortcutSearchEngineSettingsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
enableShowSearchShortcuts()
}.goBack {
}.goBack {
}.openSearch {
scrollToSearchEngineSettings(activityTestRule)
clickSearchEngineSettings(activityTestRule)
verifySearchSettings()
}.submitQuery(queryString) {
verifyUrl("duckduckgo.com/?q=firefox")
}
}
@Test
fun clearSearchTest() {
queryString = "test"
homeScreen {
}.openSearch {
typeSearch("test")
typeSearch(queryString)
clickClearButton()
verifySearchBarEmpty()
}
}
@Ignore("Failure caused by bugs: https://github.com/mozilla-mobile/fenix/issues/23818")
@SmokeTest
@Test
fun searchGroupShowsInRecentlyVisitedTest() {
val firstPage = searchMockServer.url("generic1.html").toString()
val secondPage = searchMockServer.url("generic2.html").toString()
queryString = "test search"
// setting our custom mockWebServer search URL
val searchString = "http://localhost:${searchMockServer.port}/searchResults.html?search={searchTerms}"
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
@ -190,31 +177,81 @@ class SearchTest {
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery("test search") {
longClickMatchingText("Link 1")
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
longClickMatchingText("Link 2")
snackBarButtonClick()
waitForPageToLoad()
pressBack()
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
}.goToHomescreen {
verifyJumpBackInSectionIsDisplayed()
verifyCurrentSearchGroupIsDisplayed(true, "test search", 3)
verifyRecentlyVisitedSearchGroupDisplayed(false, "test search", 3)
}.openTabDrawer {
}.openTabFromGroup(firstPage) {
snackBarButtonClick()
waitForPageToLoad()
}.openTabDrawer {
}.openTabFromGroup(secondPage) {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}
}
@Test
fun verifySearchGroupHistoryWithNoDuplicatesTest() {
val firstPageUrl = getGenericAsset(searchMockServer, 1).url
val secondPageUrl = getGenericAsset(searchMockServer, 2).url
val originPageUrl =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search=test%20search".toUri()
queryString = "test search"
// setting our custom mockWebServer search URL
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
icon = DefaultIconGenerator().generate(appContext, IconRequest(searchString)).bitmap,
)
setCustomSearchEngine(customSearchEngine)
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
pressBack()
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
pressBack()
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
pressBack()
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, "test search", 3)
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
verifyTestPageUrl(firstPageUrl)
verifyTestPageUrl(secondPageUrl)
verifyTestPageUrl(originPageUrl)
}
}
@SmokeTest
@Ignore("Failing due to known bug, see https://github.com/mozilla-mobile/fenix/issues/23818")
@Test
fun noCurrentSearchGroupFromPrivateBrowsingTest() {
fun searchGroupGeneratedInTheSameTabTest() {
queryString = "test search"
// setting our custom mockWebServer search URL
val searchString = "http://localhost:${searchMockServer.port}/searchResults.html?search={searchTerms}"
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
@ -225,27 +262,26 @@ class SearchTest {
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery("test search") {
longClickMatchingText("Link 1")
clickContextOpenLinkInPrivateTab()
longClickMatchingText("Link 2")
clickContextOpenLinkInPrivateTab()
}.goToHomescreen {
verifyCurrentSearchGroupIsDisplayed(false, "test search", 3)
}.openThreeDotMenu {
}.openHistory {
verifyHistoryItemExists(false, "3 sites")
}.submitQuery(queryString) {
clickLinkMatchingText("Link 1")
waitForPageToLoad()
pressBack()
clickLinkMatchingText("Link 2")
waitForPageToLoad()
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}
}
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun noRecentlyVisitedSearchGroupInPrivateBrowsingTest() {
val firstPage = searchMockServer.url("generic1.html").toString()
val secondPage = searchMockServer.url("generic2.html").toString()
fun noSearchGroupFromPrivateBrowsingTest() {
queryString = "test search"
// setting our custom mockWebServer search URL
val searchString = "http://localhost:${searchMockServer.port}/searchResults.html?search={searchTerms}"
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
@ -255,35 +291,37 @@ class SearchTest {
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.togglePrivateBrowsingMode()
homeScreen {
}.openSearch {
}.submitQuery("test search") {
longClickMatchingText("Link 1")
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInPrivateTab()
longClickMatchingText("Link 2")
longClickLink("Link 2")
clickContextOpenLinkInPrivateTab()
}.openTabDrawer {
}.openTab(firstPage) {
}.toggleToPrivateTabs {
}.openTabWithIndex(0) {
}.openTabDrawer {
}.openTab(secondPage) {
}.openTabWithIndex(1) {
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
homeScreen {
}.togglePrivateBrowsingMode()
verifyRecentlyVisitedSearchGroupDisplayed(false, "test search", 3)
togglePrivateBrowsingModeOnOff()
verifyRecentlyVisitedSearchGroupDisplayed(false, queryString, 3)
}.openThreeDotMenu {
}.openHistory {
verifyHistoryItemExists(false, "3 sites")
}
}
@Ignore("Failure caused by bugs: https://github.com/mozilla-mobile/fenix/issues/23818")
@SmokeTest
@Test
fun deleteItemsFromSearchGroupsHistoryTest() {
val firstPage = searchMockServer.url("generic1.html").toString()
val secondPage = searchMockServer.url("generic2.html").toString()
fun deleteItemsFromSearchGroupHistoryTest() {
queryString = "test search"
val firstPageUrl = getGenericAsset(searchMockServer, 1).url
val secondPageUrl = getGenericAsset(searchMockServer, 2).url
// setting our custom mockWebServer search URL
val searchString = "http://localhost:${searchMockServer.port}/searchResults.html?search={searchTerms}"
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
@ -294,22 +332,23 @@ class SearchTest {
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery("test search") {
longClickMatchingText("Link 1")
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
longClickMatchingText("Link 2")
snackBarButtonClick()
waitForPageToLoad()
mDevice.pressBack()
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
}.openTabDrawer {
}.openTabFromGroup(firstPage) {
}.openTabDrawer {
}.openTabFromGroup(secondPage) {
snackBarButtonClick()
waitForPageToLoad()
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, "test search", 3)
}.openRecentlyVisitedSearchGroupHistoryList("test search") {
clickDeleteHistoryButton(firstPage)
longTapSelectItem(secondPage.toUri())
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
clickDeleteHistoryButton(firstPageUrl.toString())
longTapSelectItem(secondPageUrl)
multipleSelectionToolbar {
openActionBarOverflowOrOptionsMenu(activityTestRule.activity)
clickMultiSelectionDelete()
@ -318,7 +357,204 @@ class SearchTest {
}
homeScreen {
// checking that the group is removed when only 1 item is left
verifyRecentlyVisitedSearchGroupDisplayed(false, "test search", 1)
verifyRecentlyVisitedSearchGroupDisplayed(false, queryString, 1)
}
}
@Test
fun deleteSearchGroupFromHistoryTest() {
queryString = "test search"
val firstPageUrl = getGenericAsset(searchMockServer, 1).url
// setting our custom mockWebServer search URL
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
icon = DefaultIconGenerator().generate(appContext, IconRequest(searchString)).bitmap,
)
setCustomSearchEngine(customSearchEngine)
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
mDevice.pressBack()
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
clickDeleteAllHistoryButton()
confirmDeleteAllHistory()
verifyDeleteSnackbarText("Group deleted")
verifyHistoryItemExists(false, firstPageUrl.toString())
}.goBack {}
homeScreen {
verifyRecentlyVisitedSearchGroupDisplayed(false, queryString, 3)
}.openThreeDotMenu {
}.openHistory {
verifySearchGroupDisplayed(false, queryString, 3)
verifyEmptyHistoryView()
}
}
@Test
fun reopenTabsFromSearchGroupTest() {
val firstPageUrl = getGenericAsset(searchMockServer, 1).url
val secondPageUrl = getGenericAsset(searchMockServer, 2).url
queryString = "test search"
// setting our custom mockWebServer search URL
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
icon = DefaultIconGenerator().generate(appContext, IconRequest(searchString)).bitmap,
)
setCustomSearchEngine(customSearchEngine)
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
mDevice.pressBack()
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
}.openWebsite(firstPageUrl) {
verifyUrl(firstPageUrl.toString())
}.goToHomescreen {
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
longTapSelectItem(firstPageUrl)
longTapSelectItem(secondPageUrl)
openActionBarOverflowOrOptionsMenu(activityTestRule.activity)
}
multipleSelectionToolbar {
}.clickOpenNewTab {
verifyNormalModeSelected()
}.closeTabDrawer {}
openActionBarOverflowOrOptionsMenu(activityTestRule.activity)
multipleSelectionToolbar {
}.clickOpenPrivateTab {
verifyPrivateModeSelected()
}
}
@Test
fun sharePageFromASearchGroupTest() {
val firstPageUrl = getGenericAsset(searchMockServer, 1).url
queryString = "test search"
// setting our custom mockWebServer search URL
val searchString =
"http://localhost:${searchMockServer.port}/pages/searchResults.html?search={searchTerms}"
val customSearchEngine = createSearchEngine(
name = "TestSearchEngine",
url = searchString,
icon = DefaultIconGenerator().generate(appContext, IconRequest(searchString)).bitmap,
)
setCustomSearchEngine(customSearchEngine)
// Performs a search and opens 2 dummy search results links to create a search group
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
longClickLink("Link 1")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
mDevice.pressBack()
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
waitForPageToLoad()
}.openTabDrawer {
}.openTabsListThreeDotMenu {
}.closeAllTabs {
verifyRecentlyVisitedSearchGroupDisplayed(true, queryString, 3)
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
longTapSelectItem(firstPageUrl)
}
multipleSelectionToolbar {
clickShareHistoryButton()
verifyShareOverlay()
verifyShareTabFavicon()
verifyShareTabTitle()
verifyShareTabUrl()
}
}
// Default search code for Google-US
@Test
fun defaultSearchCodeGoogleUS() {
queryString = "firefox"
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
verifyUrl(searchEngineCodes["Google"]!!)
}
}
// Default search code for Bing-US
@Test
fun defaultSearchCodeBingUS() {
queryString = "firefox"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
changeDefaultSearchEngine("Bing")
}
exitMenu()
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
verifyUrl(searchEngineCodes["Bing"]!!)
}
}
// Default search code for DuckDuckGo-US
@Test
fun defaultSearchCodeDuckDuckGoUS() {
queryString = "firefox"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
changeDefaultSearchEngine("DuckDuckGo")
}
exitMenu()
homeScreen {
}.openSearch {
}.submitQuery(queryString) {
verifyUrl(searchEngineCodes["DuckDuckGo"]!!)
}
}
}

@ -10,11 +10,9 @@ import androidx.test.uiautomator.UiSelector
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -31,7 +29,6 @@ class SettingsAboutTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
@ -52,7 +49,6 @@ class SettingsAboutTest {
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
// Walks through settings menu and sub-menus to ensure all items are present
@ -72,7 +68,9 @@ class SettingsAboutTest {
// ABOUT
@Test
fun verifyRateOnGooglePlayRedirect() {
featureSettingsHelper.setTCPCFREnabled(false)
activityIntentTestRule.applySettingsExceptions {
it.isTCPCFREnabled = false
}
homeScreen {
}.openThreeDotMenu {
@ -85,10 +83,12 @@ class SettingsAboutTest {
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25355")
@Test
fun verifyAboutFirefoxPreview() {
featureSettingsHelper.setJumpBackCFREnabled(false)
activityIntentTestRule.applySettingsExceptions {
it.isJumpBackInCFREnabled = false
it.isTCPCFREnabled = false
}
homeScreen {
}.openThreeDotMenu {
}.openSettings {

@ -14,7 +14,6 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.getEnhancedTrackingProtectionAsset
@ -31,10 +30,9 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
*/
class SettingsAddonsTest {
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = HomeActivityIntentTestRule()
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
@ -42,15 +40,11 @@ class SettingsAddonsTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
// Walks through settings add-ons menu to ensure all items are present

@ -13,7 +13,6 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
@ -30,10 +29,12 @@ class SettingsAdvancedTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
val activityIntentTestRule = HomeActivityIntentTestRule(
isPocketEnabled = false,
isTCPCFREnabled = false,
)
@Before
fun setUp() {
@ -42,15 +43,11 @@ class SettingsAdvancedTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
// Walks through settings menu and sub-menus to ensure all items are present

@ -15,7 +15,6 @@ import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
@ -23,8 +22,8 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getLoremIpsumAsset
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.runWithSystemLocaleChanged
import org.mozilla.fenix.helpers.TestHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.TestHelper.runWithSystemLocaleChanged
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_CREDIT_CARD_NUMBER
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_EXPIRATION_MONTH
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_EXPIRATION_YEAR
@ -47,7 +46,6 @@ import java.util.Locale
class SettingsBasicsTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
object CreditCard {
const val MOCK_CREDIT_CARD_NUMBER = "5555555555554444"
@ -58,7 +56,7 @@ class SettingsBasicsTest {
}
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
@ -66,18 +64,11 @@ class SettingsBasicsTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
private fun getUiTheme(): Boolean {

@ -7,10 +7,10 @@ package org.mozilla.fenix.ui
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule

@ -12,7 +12,6 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
@ -27,10 +26,9 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
*/
class SettingsHomepageTest {
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule(skipOnboarding = true)
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Rule
@JvmField
@ -42,17 +40,83 @@ class SettingsHomepageTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun verifyHomepageSettingsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHomepageSubMenu {
verifyHomePageView()
}
}
@Test
fun verifyShortcutOptionTest() {
// en-US defaults
val defaultTopSites = arrayOf(
"Top Articles",
"Wikipedia",
"Google",
)
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
homeScreen {
defaultTopSites.forEach { item ->
verifyExistingTopSitesTabs(item)
}
}.openThreeDotMenu {
}.openCustomizeHome {
clickShortcutsButton()
}.goBack {
defaultTopSites.forEach { item ->
verifyNotExistingTopSitesList(item)
}
}
}
@Test
fun verifyRecentlyVisitedOptionTest() {
activityIntentTestRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
}
val genericURL = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.goToHomescreen {
verifyRecentlyVisitedSectionIsDisplayed()
}.openThreeDotMenu {
}.openCustomizeHome {
clickRecentlyVisited()
}.goBack {
verifyRecentlyVisitedSectionIsNotDisplayed()
}
}
@Test
fun verifyPocketOptionTest() {
activityIntentTestRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
val genericURL = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.goToHomescreen {
verifyPocketSectionIsDisplayed()
}.openThreeDotMenu {
}.openCustomizeHome {
clickPocketButton()
}.goBack {
verifyPocketSectionIsNotDisplayed()
}
}
@SmokeTest

@ -13,13 +13,11 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset
@ -48,10 +46,9 @@ class SettingsPrivacyTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val pageShortcutName = generateRandomString(5)
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = HomeActivityIntentTestRule(skipOnboarding = true)
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
@ -61,11 +58,6 @@ class SettingsPrivacyTest {
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
featureSettingsHelper.disablePwaCFR(true)
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
val autofillManager: AutofillManager =
appContext.getSystemService(AutofillManager::class.java)
@ -76,7 +68,6 @@ class SettingsPrivacyTest {
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
// Walks through settings privacy menu and sub-menus to ensure all items are present
@ -358,7 +349,6 @@ class SettingsPrivacyTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun launchPageShortcutInPrivateModeTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -389,7 +379,6 @@ class SettingsPrivacyTest {
}
}
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun launchLinksInPrivateToggleOffStateDoesntChangeTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -624,9 +613,13 @@ class SettingsPrivacyTest {
@SmokeTest
@Test
fun deleteCookiesTest() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val cookiesTestPage = getStorageTestAsset(mockWebServer, "storage_write.html").url
// Browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(cookiesTestPage) {
verifyPageContent("No cookies set")
clickSetCookiesButton()

@ -7,24 +7,28 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.SearchDispatcher
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.setTextToClipBoard
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.util.ARABIC_LANGUAGE_HEADER
class SettingsSearchTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var searchMockServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = AndroidComposeTestRule(
HomeActivityIntentTestRule(),
HomeActivityIntentTestRule.withDefaultSettingsOverrides(),
) { it.activity }
@Before
@ -33,17 +37,11 @@ class SettingsSearchTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
featureSettingsHelper.setTCPCFREnabled(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
@Test
@ -55,6 +53,11 @@ class SettingsSearchTest {
verifySearchToolbar()
verifyDefaultSearchEngineHeader()
verifySearchEngineList()
verifyShowSearchSuggestions()
verifyShowSearchShortcuts()
verifySearchBrowsingHistory()
verifySearchBookmarks()
verifyShowClipboardSuggestionsDefault()
}
}
@ -73,7 +76,27 @@ class SettingsSearchTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun toggleSearchAutocomplete() {
homeScreen {
}.openSearch {
typeSearch("mo")
verifyTypedToolbarText("monster.com")
typeSearch("moz")
verifyTypedToolbarText("mozilla.org")
}.dismissSearchBar {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
toggleAutocomplete()
}.goBack {
}.goBack {
}.openSearch {
typeSearch("moz")
verifyTypedToolbarText("moz")
}
}
@Test
fun toggleSearchBookmarksAndHistoryTest() {
// Bookmarks 2 websites, toggles the bookmarks and history search settings off,
// then verifies if the websites do not show in the suggestions.
@ -101,8 +124,9 @@ class SettingsSearchTest {
}.openSearch {
typeSearch("test")
expandSearchSuggestionsList()
verifySearchEngineSuggestionResults(activityTestRule, "Test_Page_1")
verifySearchEngineSuggestionResults(activityTestRule, "Test_Page_2")
verifyFirefoxSuggestResults(activityTestRule, "Firefox Suggest")
verifyFirefoxSuggestResults(activityTestRule, "Test_Page_1")
verifyFirefoxSuggestResults(activityTestRule, "Test_Page_2")
}.dismissSearchBar {
}.openThreeDotMenu {
}.openSettings {
@ -118,6 +142,7 @@ class SettingsSearchTest {
}.openSearch {
typeSearch("test")
expandSearchSuggestionsList()
verifyNoSuggestionsAreDisplayed(activityTestRule, "Firefox Suggest")
verifyNoSuggestionsAreDisplayed(activityTestRule, "Test_Page_1")
verifyNoSuggestionsAreDisplayed(activityTestRule, "Test_Page_2")
}
@ -200,7 +225,48 @@ class SettingsSearchTest {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
disableShowSearchSuggestions()
toggleShowSearchSuggestions()
}.goBack {
}.goBack {
}.openSearch {
typeSearch("mozilla")
verifyNoSuggestionsAreDisplayed(activityTestRule, "mozilla firefox")
}
}
// Tests the "Don't allow" option from private mode search suggestions onboarding dialog
@Test
fun blockSearchSuggestionsInPrivateModeOnboardingTest() {
homeScreen {
togglePrivateBrowsingModeOnOff()
}.openSearch {
typeSearch("mozilla")
verifyAllowSuggestionsInPrivateModeDialog()
denySuggestionsInPrivateMode()
verifyNoSuggestionsAreDisplayed(activityTestRule, "mozilla firefox")
}.dismissSearchBar {
togglePrivateBrowsingModeOnOff()
}.openSearch {
typeSearch("mozilla")
verifySearchEngineSuggestionResults(activityTestRule, "mozilla firefox")
}
}
// Tests the "Allow" option from private mode search suggestions onboarding dialog
@Test
fun allowSearchSuggestionsInPrivateModeOnboardingTest() {
homeScreen {
togglePrivateBrowsingModeOnOff()
}.openSearch {
typeSearch("mozilla")
verifyAllowSuggestionsInPrivateModeDialog()
allowSuggestionsInPrivateMode()
verifySearchEngineSuggestionResults(activityTestRule, "mozilla firefox")
}.dismissSearchBar {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
toggleShowSuggestionsInPrivateSessions()
}.goBack {
}.goBack {
}.openSearch {
@ -228,4 +294,194 @@ class SettingsSearchTest {
verifyVoiceSearchButtonVisibility(false)
}
}
@Test
fun toggleShowClipboardSuggestionsTest() {
val link = "https://www.mozilla.org/en-US/"
setTextToClipBoard(appContext, link)
homeScreen {
}.openNavigationToolbar {
verifyClipboardSuggestionsAreDisplayed(link, true)
}.visitLinkFromClipboard {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
verifyShowClipboardSuggestionsDefault()
toggleClipboardSuggestion()
exitMenu()
}
homeScreen {
}.openNavigationToolbar {
verifyClipboardSuggestionsAreDisplayed(link, false)
}
}
// Expected for en-us defaults
@Test
fun undoDeleteSearchEngineTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
verifyEngineListContains("Bing")
openEngineOverflowMenu("Bing")
clickDeleteSearchEngine()
clickUndoSnackBarButton()
verifyEngineListContains("Bing")
}
}
// Expected for en-us defaults
@Test
fun deleteDefaultSearchEngineTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
verifyEngineListContains("Google")
verifyDefaultSearchEngine("Google")
openEngineOverflowMenu("Google")
clickDeleteSearchEngine()
verifyEngineListDoesNotContain("Google")
verifyDefaultSearchEngine("Bing")
}
}
// Expected for en-us defaults
@Test
fun deleteAllSearchEnginesTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
deleteMultipleSearchEngines(
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
)
verifyDefaultSearchEngine("Wikipedia")
verifyThreeDotButtonIsNotDisplayed("Wikipedia")
openAddSearchEngineMenu()
verifyAddSearchEngineListContains(
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
)
}
}
// Expected for en-us defaults
@Test
fun changeSearchEnginesBasedOnTextTest() {
homeScreen {
}.openSearch {
typeSearch("D")
verifySearchEnginePrompt(activityTestRule, "DuckDuckGo")
clickSearchEnginePrompt(activityTestRule, "DuckDuckGo")
}.submitQuery("firefox") {
verifyUrl("duckduckgo.com/?q=firefox")
}
}
// Expected for app language set to Arabic
@Test
fun verifySearchEnginesWithRTLLocale() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
toggleShowSearchShortcuts()
}.goBack {
}.openLanguageSubMenu {
TestHelper.registerAndCleanupIdlingResources(
RecyclerViewIdlingResource(
activityTestRule.activity.findViewById(R.id.locale_list),
2,
),
) {
selectLanguage("Arabic")
verifyLanguageHeaderIsTranslated(ARABIC_LANGUAGE_HEADER)
}
}
exitMenu()
homeScreen {
}.openSearch {
verifyTranslatedFocusedNavigationToolbar("ابحث أو أدخِل عنوانا")
verifySearchEngineShortcuts(
activityTestRule,
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
/* Disabled Arabic Wikipedia verification
until https://github.com/mozilla-mobile/fenix/issues/12236 gets fixed
"ويكيبيديا (ar)"
*/
)
}
}
// Expected for en-us defaults
@Test
fun toggleSearchEnginesShortcutListTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
verifyShowSearchEnginesToggleState(false)
toggleShowSearchShortcuts()
verifyShowSearchEnginesToggleState(true)
}
exitMenu()
homeScreen {
}.openSearch {
verifySearchEngineShortcuts(
activityTestRule,
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
"Wikipedia",
)
}.clickSearchEngineSettings(activityTestRule) {
toggleShowSearchShortcuts()
verifyShowSearchEnginesToggleState(false)
}
exitMenu()
homeScreen {
}.openSearch {
verifySearchEngineShortcutsAreNotDisplayed(
activityTestRule,
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
"Wikipedia",
)
clickSearchEngineShortcutButton()
verifySearchEngineShortcuts(
activityTestRule,
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
"Wikipedia",
)
}
}
}

@ -7,10 +7,10 @@ package org.mozilla.fenix.ui
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule

@ -7,10 +7,10 @@ package org.mozilla.fenix.ui
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule

@ -13,7 +13,6 @@ import androidx.core.net.toUri
import androidx.test.filters.SdkSuppress
import androidx.test.rule.GrantPermissionRule
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
@ -36,8 +35,12 @@ class SitePermissionsTest {
private val micManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule(
isJumpBackInCFREnabled = false,
isPWAsPromptEnabled = false,
isTCPCFREnabled = false,
isDeleteSitePermissionsEnabled = true,
)
@get:Rule
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
@ -52,15 +55,6 @@ class SitePermissionsTest {
@get: Rule
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
// disabling the new homepage pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.deleteSitePermissions(true)
featureSettingsHelper.disablePwaCFR(true)
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
@ -80,7 +74,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
fun rememberBlockAudioVideoPermissionChoiceTest() {
@ -104,7 +97,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
fun rememberAllowAudioVideoPermissionChoiceTest() {
@ -128,7 +120,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@Test
fun microphonePermissionChoiceOnEachRequestTest() {
assumeTrue(micManager.microphones.isNotEmpty())
@ -146,7 +137,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@Test
fun rememberBlockMicrophonePermissionChoiceTest() {
assumeTrue(micManager.microphones.isNotEmpty())
@ -168,7 +158,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@Test
fun rememberAllowMicrophonePermissionChoiceTest() {
assumeTrue(micManager.microphones.isNotEmpty())
@ -190,7 +179,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@Test
fun cameraPermissionChoiceOnEachRequestTest() {
assumeTrue(cameraManager.cameraIdList.isNotEmpty())
@ -208,7 +196,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@Test
fun rememberBlockCameraPermissionChoiceTest() {
assumeTrue(cameraManager.cameraIdList.isNotEmpty())
@ -230,7 +217,6 @@ class SitePermissionsTest {
}
}
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@Test
fun rememberAllowCameraPermissionChoiceTest() {
assumeTrue(cameraManager.cameraIdList.isNotEmpty())

@ -25,8 +25,8 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP
import org.mozilla.fenix.helpers.FeatureSettingsHelperDelegate
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.RetryTestRule
@ -36,12 +36,11 @@ import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.assertNativeAppOpens
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.TestHelper.generateRandomString
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.TestHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.customTabScreen
import org.mozilla.fenix.ui.robots.enhancedTrackingProtection
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.notificationShade
@ -62,22 +61,22 @@ class SmokeTest {
private lateinit var mockWebServer: MockWebServer
private val customMenuItem = "TestMenuItem"
private lateinit var browserStore: BrowserStore
private val featureSettingsHelper = FeatureSettingsHelper()
private val featureSettingsHelper = FeatureSettingsHelperDelegate()
@get:Rule
@get:Rule(order = 0)
val activityTestRule = AndroidComposeTestRule(
HomeActivityIntentTestRule(),
{ it.activity },
)
@get: Rule
@get: Rule(order = 1)
val intentReceiverActivityTestRule = ActivityTestRule(
IntentReceiverActivity::class.java,
true,
false,
)
@Rule
@Rule(order = 2)
@JvmField
val retryTestRule = RetryTestRule(3)
@ -88,9 +87,11 @@ class SmokeTest {
browserStore = activityTestRule.activity.components.core.store
// disabling the new homepage pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
featureSettingsHelper.apply {
isJumpBackInCFREnabled = false
isTCPCFREnabled = false
isWallpaperOnboardingEnabled = false
}.applyFlagUpdates()
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
@ -159,7 +160,6 @@ class SmokeTest {
}
}
@Test
/* Verifies the nav bar:
- opening a web page
- the existence of nav bar items
@ -167,7 +167,7 @@ class SmokeTest {
- the tab drawer button
- opening a new search and dismissing the nav bar
*/
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
@Test
fun verifyBasicNavigationToolbarFunctionality() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -188,13 +188,13 @@ class SmokeTest {
}
// Verifies the list of items in a tab's 3 dot menu
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/26711")
@Test
fun verifyPageMainMenuItemsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad()
}.openThreeDotMenu {
verifyPageThreeDotMainMenuItems()
}
@ -345,17 +345,16 @@ class SmokeTest {
// Device or AVD requires a Google Services Android OS installation with Play Store installed
// Verifies the Open in app button when an app is installed
@Ignore("Failing with frequent ANR: https://github.com/mozilla-mobile/fenix/issues/25926")
@Test
fun mainMenuOpenInAppTest() {
val playStoreUrl = "play.google.com/store/apps/details?id=org.mozilla.fenix"
val youtubeURL = "https://m.youtube.com/user/mozilla?cbrd=1"
navigationToolbar {
}.enterURLAndEnterToBrowser(playStoreUrl.toUri()) {
}.enterURLAndEnterToBrowser(youtubeURL.toUri()) {
verifyNotificationDotOnMainMenu()
}.openThreeDotMenu {
}.clickOpenInApp {
assertNativeAppOpens(Constants.PackageName.GOOGLE_PLAY_SERVICES, playStoreUrl)
assertNativeAppOpens(YOUTUBE_APP, youtubeURL)
}
}
@ -403,38 +402,6 @@ class SmokeTest {
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25345")
@Test
fun customTrackingProtectionSettingsTest() {
val genericWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingPage = TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionOptionsEnabled()
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
}.goBackToHomeScreen {}
navigationToolbar {
// browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.openDetails {
verifyTrackingCookiesBlocked()
verifyCryptominersBlocked()
verifyFingerprintersBlocked()
verifyTrackingContentBlocked()
viewTrackingContentBlockList()
}
}
// Verifies changing the default engine from the Search Shortcut menu
@Test
fun selectSearchEnginesShortcutTest() {
@ -504,9 +471,8 @@ class SmokeTest {
}
}
@Test
// Verifies that a recently closed item is properly opened
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
@Test
fun openRecentlyClosedItemTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -529,9 +495,8 @@ class SmokeTest {
}
}
@Test
// Verifies that tapping the "x" button removes a recently closed item from the list
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
@Test
fun deleteRecentlyClosedTabsItemTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -678,7 +643,6 @@ class SmokeTest {
// caution when making changes to it, so they don't block the builds
@Test
fun noHistoryInPrivateBrowsingTest() {
FeatureSettingsHelper().setTCPCFREnabled(false)
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
@ -816,7 +780,6 @@ class SmokeTest {
}
}
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun audioPlaybackSystemNotificationTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)
@ -884,7 +847,6 @@ class SmokeTest {
mDevice.pressBack()
}
@Ignore("Failing: https://github.com/mozilla-mobile/fenix/issues/26884")
@Test
fun copyTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -900,11 +862,10 @@ class SmokeTest {
clickClearButton()
longClickToolbar()
clickPasteText()
verifyPastedToolbarText("content")
verifyTypedToolbarText("content")
}
}
@Ignore("Failing: https://github.com/mozilla-mobile/fenix/issues/26884")
@Test
fun selectAllAndCopyTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -920,7 +881,8 @@ class SmokeTest {
clickClearButton()
longClickToolbar()
clickPasteText()
verifyPastedToolbarText("Page content: 1")
// with Select all, some white space is copied over, so we need to include that too
verifyTypedToolbarText(" Page content: 1 ")
}
}

@ -45,8 +45,7 @@ class TabbedBrowsingTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
@get:Rule
val activityTestRule = HomeActivityTestRule()
private val featureSettingsHelper = activityTestRule.featureSettingsHelper
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField
@ -54,11 +53,6 @@ class TabbedBrowsingTest {
@Before
fun setUp() {
// disabling the new homepage pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
@ -72,7 +66,6 @@ class TabbedBrowsingTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openNewTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -97,7 +90,6 @@ class TabbedBrowsingTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openNewPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -118,7 +110,6 @@ class TabbedBrowsingTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun closeAllTabsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -183,11 +174,12 @@ class TabbedBrowsingTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyUndoSnackBarTest() {
// disabling these features because they interfere with the snackbar visibility
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
activityTestRule.applySettingsExceptions {
it.isPocketEnabled = false
it.isRecentTabsFeatureEnabled = false
}
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -7,10 +7,8 @@ package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen
@ -26,11 +24,10 @@ class ThreeDotMenuMainTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
activityTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
@ -43,7 +40,6 @@ class ThreeDotMenuMainTest {
}
// Verifies the list of items in the homescreen's 3 dot main menu
@Ignore("Failing with frequent ANR: https://bugzilla.mozilla.org/show_bug.cgi?id=1764605")
@Test
fun homeThreeDotMenuItemsTest() {
homeScreen {
@ -52,7 +48,8 @@ class ThreeDotMenuMainTest {
verifyHistoryButton()
verifyDownloadsButton()
verifyAddOnsButton()
verifySyncSignInButton()
// Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788
// verifySyncSignInButton()
verifyDesktopSite()
verifyWhatsNewButton()
verifyHelpButton()

@ -14,7 +14,6 @@ import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
@ -36,10 +35,9 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
class TopSitesTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule(skipOnboarding = true)
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@get:Rule
val retryTestRule = RetryTestRule(3)
@ -51,16 +49,11 @@ class TopSitesTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.setJumpBackCFREnabled(false)
featureSettingsHelper.setTCPCFREnabled(false)
featureSettingsHelper.setShowWallpaperOnboarding(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
@SmokeTest
@ -225,7 +218,6 @@ class TopSitesTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad()
verifyPageContent(defaultWebPage.content)
}
}

@ -0,0 +1,187 @@
/* 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/. */
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.assertNativeAppOpens
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying basic interactions with web control elements
*
*/
class WebControlsTest {
private lateinit var mockWebServer: MockWebServer
private val hour = 10
private val minute = 10
private val colorHexValue = "#5b2067"
private val emailLink = "mailto://example@example.com"
private val phoneLink = "tel://1234567890"
@get:Rule
val activityTestRule = HomeActivityTestRule(
isJumpBackInCFREnabled = false,
isTCPCFREnabled = false,
)
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun cancelCalendarFormTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Calendar Form")
clickFormViewButton("CANCEL")
clickSubmitDateButton()
verifyNoDateIsSelected()
}
}
@Test
fun setAndClearCalendarFormTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Calendar Form")
selectDate()
clickFormViewButton("OK")
clickSubmitDateButton()
verifySelectedDate()
clickForm("Calendar Form")
clickFormViewButton("CLEAR")
clickSubmitDateButton()
verifyNoDateIsSelected()
}
}
@Test
fun cancelClockFormTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Clock Form")
clickFormViewButton("CANCEL")
clickSubmitTimeButton()
verifyNoTimeIsSelected(hour, minute)
}
}
@Test
fun setAndClearClockFormTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Clock Form")
selectTime(hour, minute)
clickFormViewButton("OK")
clickSubmitTimeButton()
verifySelectedTime(hour, minute)
clickForm("Clock Form")
clickFormViewButton("CLEAR")
clickSubmitTimeButton()
verifyNoTimeIsSelected(hour, minute)
}
}
@Test
fun cancelColorFormTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Color Form")
selectColor(colorHexValue)
clickFormViewButton("CANCEL")
clickSubmitColorButton()
verifyColorIsNotSelected(colorHexValue)
}
}
@Test
fun setColorFormTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Color Form")
selectColor(colorHexValue)
clickFormViewButton("SET")
clickSubmitColorButton()
verifySelectedColor(colorHexValue)
}
}
@Test
fun verifyDropdownMenuTest() {
val htmlControlsPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(htmlControlsPage.url) {
clickForm("Drop-down Form")
selectDropDownOption("The National")
clickSubmitDropDownButton()
verifySelectedDropDownOption("The National")
}
}
@Test
fun externalLinkTest() {
val externalLinksPage = TestAssetHelper.getExternalLinksAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickLinkMatchingText("External link")
verifyUrl("duckduckgo")
}
}
@Test
fun emailLinkTest() {
val externalLinksPage = TestAssetHelper.getExternalLinksAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickLinkMatchingText("Email link")
assertNativeAppOpens(Constants.PackageName.GMAIL_APP, emailLink)
}
}
@Test
fun telephoneLinkTest() {
val externalLinksPage = TestAssetHelper.getExternalLinksAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickLinkMatchingText("Telephone link")
assertNativeAppOpens(Constants.PackageName.PHONE_APP, phoneLink)
}
}
}

@ -138,8 +138,8 @@ class BookmarksRobot {
mDevice.waitNotNull(Until.findObject(By.text(childFolderName)), waitingTime)
}
fun verifySignInToSyncButton() =
signInToSyncButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
fun verifySyncSignInButton() =
syncSignInButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
fun verifyDeleteFolderConfirmationMessage() = assertDeleteFolderConfirmationMessage()
@ -150,12 +150,21 @@ class BookmarksRobot {
.click()
}
fun createFolder(name: String) {
fun createFolder(name: String, parent: String? = null) {
clickAddFolderButton()
addNewFolderName(name)
if (!parent.isNullOrBlank()) {
setParentFolder(parent)
}
saveNewFolder()
}
fun setParentFolder(parentName: String) {
clickParentFolderSelector()
selectFolder(parentName)
navigateUp()
}
fun clickAddFolderButton() {
mDevice.waitNotNull(
Until.findObject(By.desc("Add folder")),
@ -244,7 +253,7 @@ class BookmarksRobot {
}
fun clickSingInToSyncButton(interact: SettingsTurnOnSyncRobot.() -> Unit): SettingsTurnOnSyncRobot.Transition {
signInToSyncButton().click()
syncSignInButton().click()
SettingsTurnOnSyncRobot().interact()
return SettingsTurnOnSyncRobot.Transition()
@ -308,7 +317,7 @@ private fun saveBookmarkButton() = onView(withId(R.id.save_bookmark_button))
private fun deleteInEditModeButton() = onView(withId(R.id.delete_bookmark_button))
private fun signInToSyncButton() = onView(withId(R.id.bookmark_folders_sign_in))
private fun syncSignInButton() = onView(withId(R.id.bookmark_folders_sign_in))
private fun assertBookmarksView() {
onView(

@ -11,15 +11,17 @@ import android.content.Intent
import android.net.Uri
import android.os.SystemClock
import android.util.Log
import android.widget.EditText
import android.widget.TimePicker
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.BundleMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
@ -27,6 +29,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
@ -34,6 +37,7 @@ import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.mediasession.MediaSession
import org.hamcrest.CoreMatchers.allOf
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.mozilla.fenix.R
@ -43,11 +47,13 @@ import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.waitForObjects
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import java.time.LocalDate
class BrowserRobot {
private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
@ -334,134 +340,54 @@ class BrowserRobot {
menuSaveImage.click()
}
fun createBookmark(url: Uri) {
fun createBookmark(url: Uri, folder: String? = null) {
navigationToolbar {
}.enterURLAndEnterToBrowser(url) {
// needs to wait for the right url to load before saving a bookmark
verifyUrl(url.toString())
}.openThreeDotMenu {
}.bookmarkPage { }
}
fun clickLinkMatchingText(expectedText: String) {
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTimeLong)
val element = mDevice.findObject(UiSelector().textContains(expectedText))
element.click()
}
fun longClickMatchingText(expectedText: String) {
try {
mDevice.waitForWindowUpdate(packageName, waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
val link = mDevice.findObject(By.textContains(expectedText))
link.click(LONG_CLICK_DURATION)
} catch (e: NullPointerException) {
println(e)
// Refresh the page in case the first long click didn't succeed
navigationToolbar {
}.openThreeDotMenu {
}.refreshPage {
mDevice.waitForIdle()
}.bookmarkPage {
}.takeIf { !folder.isNullOrBlank() }?.let {
it.openThreeDotMenu {
}.editBookmarkPage {
setParentFolder(folder!!)
saveEditBookmark()
}
// Long click again the desired text
mDevice.waitForWindowUpdate(packageName, waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
val link = mDevice.findObject(By.textContains(expectedText))
link.click(LONG_CLICK_DURATION)
}
}
fun longClickAndCopyText(expectedText: String, selectAll: Boolean = false) {
try {
// Long click desired text
mDevice.waitForWindowUpdate(packageName, waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
val link = mDevice.findObject(By.textContains(expectedText))
link.click(LONG_CLICK_DURATION)
// Click Select all from the text selection toolbar
if (selectAll) {
mDevice.findObject(UiSelector().textContains("Select all")).waitForExists(waitingTime)
val selectAllText = mDevice.findObject(By.textContains("Select all"))
selectAllText.click()
}
fun clickLinkMatchingText(expectedText: String) =
clickPageObject(webPageItemContainingText(expectedText))
// Click Copy from the text selection toolbar
mDevice.findObject(UiSelector().textContains("Copy")).waitForExists(waitingTime)
val copyText = mDevice.findObject(By.textContains("Copy"))
copyText.click()
} catch (e: NullPointerException) {
println("Failed to long click desired text: ${e.localizedMessage}")
// Refresh the page in case the first long click didn't succeed
navigationToolbar {
}.openThreeDotMenu {
}.refreshPage {
mDevice.waitForIdle()
}
fun longClickLink(expectedText: String) =
longClickPageObject(webPageItemWithText(expectedText))
// Long click again the desired text
mDevice.waitForWindowUpdate(packageName, waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
val link = mDevice.findObject(By.textContains(expectedText))
link.click(LONG_CLICK_DURATION)
// Click again Select all from the text selection toolbar
if (selectAll) {
mDevice.findObject(UiSelector().textContains("Select all")).waitForExists(waitingTime)
val selectAllText = mDevice.findObject(By.textContains("Select all"))
selectAllText.click()
}
fun longClickMatchingText(expectedText: String) =
longClickPageObject(webPageItemContainingText(expectedText))
fun longClickAndCopyText(expectedText: String, selectAll: Boolean = false) {
longClickPageObject(webPageItemContainingText(expectedText))
// Click again Copy from the text selection toolbar
mDevice.findObject(UiSelector().textContains("Copy")).waitForExists(waitingTime)
val copyText = mDevice.findObject(By.textContains("Copy"))
copyText.click()
// Click Select all from the text selection toolbar
if (selectAll) {
mDevice.findObject(UiSelector().textContains("Select all")).waitForExists(waitingTime)
val selectAllText = mDevice.findObject(By.textContains("Select all"))
selectAllText.click()
}
// Click Copy from the text selection toolbar
mDevice.findObject(UiSelector().textContains("Copy")).waitForExists(waitingTime)
val copyText = mDevice.findObject(By.textContains("Copy"))
copyText.click()
}
fun longClickAndSearchText(searchButton: String, expectedText: String) {
var currentTries = 0
while (currentTries++ < 3) {
try {
// Long click desired text
mDevice.waitForWindowUpdate(packageName, waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)
val link = mDevice.findObject(By.textContains(expectedText))
link.click(LONG_CLICK_DURATION)
// Click search from the text selection toolbar
mDevice.findObject(UiSelector().textContains(searchButton)).waitForExists(waitingTime)
val searchText = mDevice.findObject(By.textContains(searchButton))
searchText.click()
longClickPageObject(webPageItemContainingText(expectedText))
break
} catch (e: NullPointerException) {
println("Failed to long click desired text: ${e.localizedMessage}")
// Refresh the page in case the first long click didn't succeed
navigationToolbar {
}.openThreeDotMenu {
}.refreshPage {
mDevice.waitForIdle()
}
}
}
// Click search from the text selection toolbar
mDevice.findObject(UiSelector().textContains(searchButton)).waitForExists(waitingTime)
val searchText = mDevice.findObject(By.textContains(searchButton))
searchText.click()
}
fun snackBarButtonClick() {
@ -474,18 +400,10 @@ class BrowserRobot {
switchButton.clickAndWaitForNewWindow(waitingTime)
}
fun verifySaveLoginPromptIsShown() {
mDevice.findObject(UiSelector().text("test@example.com")).waitForExists(waitingTime)
val submitButton = mDevice.findObject(By.res("submit"))
submitButton.clickAndWait(Until.newWindow(), waitingTime)
// Click save to save the login
mDevice.waitNotNull(Until.findObjects(text("Save")))
}
fun verifySaveLoginPromptIsShown() = clickPageObject(webPageItemWithResourceId("submit"))
fun verifyUpdateLoginPromptIsShown() {
val submitButton = mDevice.findObject(By.res("submit"))
submitButton.clickAndWait(Until.newWindow(), waitingTime)
clickPageObject(webPageItemWithResourceId("submit"))
mDevice.waitNotNull(Until.findObjects(text("Update")))
}
@ -499,49 +417,13 @@ class BrowserRobot {
}
fun enterPassword(password: String) {
val passwordField = mDevice.findObject(
UiSelector()
.resourceId("password")
.className(EditText::class.java),
)
try {
passwordField.waitForExists(waitingTime)
mDevice.findObject(
By
.res("password")
.clazz(EditText::class.java),
).click()
passwordField.clearTextField()
passwordField.text = password
// wait until the password is hidden
assertTrue(mDevice.findObject(UiSelector().text(password)).waitUntilGone(waitingTime))
} catch (e: UiObjectNotFoundException) {
println(e)
clickPageObject(webPageItemWithResourceId("password"))
setPageObjectText(webPageItemWithResourceId("password"), password)
// Lets refresh the page and try again
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
mDevice.waitForIdle()
}
} finally {
passwordField.waitForExists(waitingTime)
mDevice.findObject(
By
.res("password")
.clazz(EditText::class.java),
).click()
passwordField.clearTextField()
passwordField.text = password
// wait until the password is hidden
assertTrue(mDevice.findObject(UiSelector().text(password)).waitUntilGone(waitingTime))
}
assertTrue(mDevice.findObject(UiSelector().text(password)).waitUntilGone(waitingTime))
}
fun clickMediaPlayerPlayButton() {
mediaPlayerPlayButton().waitForExists(waitingTime)
mediaPlayerPlayButton().click()
}
fun clickMediaPlayerPlayButton() = clickPageObject(webPageItemWithText("Play"))
/**
* Get the current playback state of the currently selected tab.
@ -605,26 +487,17 @@ class BrowserRobot {
}
fun fillAndSubmitLoginCredentials(userName: String, password: String) {
var currentTries = 0
while (currentTries++ < 3) {
try {
mDevice.waitForIdle(waitingTime)
userNameTextBox.setText(userName)
passwordTextBox.setText(password)
submitLoginButton.click()
mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")))
mDevice.waitForIdle(waitingTime)
setPageObjectText(webPageItemWithResourceId("username"), userName)
setPageObjectText(webPageItemWithResourceId("password"), password)
clickPageObject(webPageItemWithResourceId("submit"))
break
} catch (e: UiObjectNotFoundException) {
Log.e("BROWSER_ROBOT", "Failed to find locator: ${e.localizedMessage}")
}
}
mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")))
}
fun clearUserNameLoginCredential() {
mDevice.waitForObjects(userNameTextBox)
userNameTextBox.clearTextField()
mDevice.waitForObjects(webPageItemWithResourceId("username"))
webPageItemWithResourceId("username").clearTextField()
mDevice.waitForIdle(waitingTime)
}
@ -637,25 +510,19 @@ class BrowserRobot {
mDevice.waitForObjects(suggestedLogins)
break
} catch (e: UiObjectNotFoundException) {
userNameTextBox.click()
clickPageObject(webPageItemWithResourceId("username"))
}
}
}
fun clickStreetAddressTextBox() {
streetAddressTextBox().waitForExists(waitingTime)
streetAddressTextBox().click()
}
fun clickStreetAddressTextBox() = clickPageObject(webPageItemWithResourceId("streetAddress"))
fun clickSelectAddressButton() {
selectAddressButton.waitForExists(waitingTime)
selectAddressButton.clickAndWaitForNewWindow(waitingTime)
}
fun clickCardNumberTextBox() {
creditCardNumberTextBox().waitForExists(waitingTime)
creditCardNumberTextBox().click()
}
fun clickCardNumberTextBox() = clickPageObject(webPageItemWithResourceId("cardNumber"))
fun clickSelectCreditCardButton() {
selectCreditCardButton.waitForExists(waitingTime)
@ -699,8 +566,8 @@ class BrowserRobot {
// Sometimes the assertion of the pre-filled logins fails so we are re-trying after refreshing the page
while (currentTries++ < 3) {
try {
mDevice.waitForObjects(userNameTextBox)
assertTrue(userNameTextBox.text.equals(userName))
mDevice.waitForObjects(webPageItemWithResourceId("username"))
assertTrue(webPageItemWithResourceId("username").text.equals(userName))
break
} catch (e: AssertionError) {
@ -714,18 +581,24 @@ class BrowserRobot {
}
}
}
mDevice.waitForObjects(userNameTextBox)
assertTrue(userNameTextBox.text.equals(userName))
mDevice.waitForObjects(webPageItemWithResourceId("username"))
assertTrue(webPageItemWithResourceId("username").text.equals(userName))
}
fun verifyAutofilledAddress(streetAddress: String) {
mDevice.waitForObjects(streetAddressTextBox(streetAddress))
assertTrue(streetAddressTextBox(streetAddress).waitForExists(waitingTime))
mDevice.waitForObjects(webPageItemContainingTextAndResourceId("streetAddress", streetAddress))
assertTrue(
webPageItemContainingTextAndResourceId("streetAddress", streetAddress)
.waitForExists(waitingTime),
)
}
fun verifyAutofilledCreditCard(creditCardNumber: String) {
mDevice.waitForObjects(creditCardNumberTextBox(creditCardNumber))
assertTrue(creditCardNumberTextBox(creditCardNumber).waitForExists(waitingTime))
mDevice.waitForObjects(webPageItemContainingTextAndResourceId("cardNumber", creditCardNumber))
assertTrue(
webPageItemContainingTextAndResourceId("cardNumber", creditCardNumber)
.waitForExists(waitingTime),
)
}
fun verifyPrefilledPWALoginCredentials(userName: String, shortcutTitle: String) {
@ -734,9 +607,9 @@ class BrowserRobot {
var currentTries = 0
while (currentTries++ < 3) {
try {
assertTrue(submitLoginButton.waitForExists(waitingTime))
submitLoginButton.click()
assertTrue(userNameTextBox.text.equals(userName))
assertTrue(webPageItemWithResourceId("submit").waitForExists(waitingTime))
webPageItemWithResourceId("submit").click()
assertTrue(webPageItemWithResourceId("username").text.equals(userName))
break
} catch (e: AssertionError) {
addToHomeScreen {
@ -779,10 +652,236 @@ class BrowserRobot {
}
}
fun clickSetCookiesButton() {
val setCookiesButton = mDevice.findObject(UiSelector().resourceId("setCookies"))
setCookiesButton.waitForExists(waitingTimeLong)
setCookiesButton.click()
fun clickSetCookiesButton() = clickPageObject(webPageItemWithResourceId("setCookies"))
fun verifyCookiesProtectionHint() {
val hintMessage =
mDevice.findObject(
UiSelector()
.textContains(getStringResource(R.string.tcp_cfr_message)),
)
assertTrue(hintMessage.waitForExists(waitingTime))
}
fun clickForm(formType: String) {
when (formType) {
"Calendar Form" -> {
clickPageObject(webPageItemWithResourceId("calendar"))
mDevice.waitForIdle(waitingTime)
}
"Clock Form" -> {
clickPageObject(webPageItemWithResourceId("clock"))
mDevice.waitForIdle(waitingTime)
}
"Color Form" -> {
clickPageObject(webPageItemWithResourceId("colorPicker"))
mDevice.waitForIdle(waitingTime)
}
"Drop-down Form" -> {
clickPageObject(webPageItemWithResourceId("dropDown"))
mDevice.waitForIdle(waitingTime)
}
}
}
fun clickFormViewButton(button: String) = mDevice.findObject(UiSelector().textContains(button)).click()
fun selectDate() {
mDevice.findObject(UiSelector().resourceId("android:id/month_view")).waitForExists(waitingTime)
mDevice.findObject(
UiSelector()
.textContains("$currentDay")
.descriptionContains("$currentDay $currentMonth $currentYear"),
).click()
}
fun selectTime(hour: Int, minute: Int) =
onView(
isAssignableFrom(TimePicker::class.java),
).inRoot(
isDialog(),
).perform(PickerActions.setTime(hour, minute))
fun selectColor(hexValue: String) {
mDevice.findObject(
UiSelector()
.textContains("Choose a color")
.resourceId("$packageName:id/alertTitle"),
).waitForExists(waitingTime)
val colorSelection =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/color_item")
.descriptionContains(hexValue),
)
colorSelection.click()
}
fun selectDropDownOption(optionName: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/customPanel"),
).waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(optionName)).click()
}
fun clickSubmitDateButton() = clickPageObject(webPageItemWithResourceId("submitDate"))
fun clickSubmitTimeButton() = clickPageObject(webPageItemWithResourceId("submitTime"))
fun clickSubmitColorButton() = clickPageObject(webPageItemWithResourceId("submitColor"))
fun clickSubmitDropDownButton() = clickPageObject(webPageItemWithResourceId("submitOption"))
fun verifySelectedDate() {
for (i in 1..RETRY_COUNT) {
try {
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected date is: $currentDate"),
).waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected time isn't displayed ${e.localizedMessage}")
clickForm("Calendar Form")
selectDate()
clickFormViewButton("OK")
clickSubmitDateButton()
}
}
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected date is: $currentDate"),
).waitForExists(waitingTime),
)
}
fun verifyNoDateIsSelected() {
assertFalse(
mDevice.findObject(
UiSelector()
.text("Selected date is: $currentDate"),
).waitForExists(waitingTime),
)
}
fun verifySelectedTime(hour: Int, minute: Int) {
for (i in 1..RETRY_COUNT) {
try {
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected time is: $hour:$minute"),
).waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected time isn't displayed ${e.localizedMessage}")
clickForm("Clock Form")
clickFormViewButton("CLEAR")
clickForm("Clock Form")
selectTime(hour, minute)
clickFormViewButton("OK")
clickSubmitTimeButton()
}
}
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected time is: $hour:$minute"),
).waitForExists(waitingTime),
)
}
fun verifySelectedColor(hexValue: String) {
for (i in 1..RETRY_COUNT) {
try {
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected color is: $hexValue"),
).waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected color isn't displayed ${e.localizedMessage}")
clickForm("Color Form")
selectColor(hexValue)
clickFormViewButton("SET")
clickSubmitColorButton()
}
}
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected color is: $hexValue"),
).waitForExists(waitingTime),
)
}
fun verifySelectedDropDownOption(optionName: String) {
for (i in 1..RETRY_COUNT) {
try {
mDevice.findObject(
UiSelector()
.textContains("Submit drop down option")
.resourceId("submitOption"),
).waitForExists(waitingTime)
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected option is: $optionName"),
).waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected option isn't displayed ${e.localizedMessage}")
clickForm("Drop-down Form")
selectDropDownOption(optionName)
clickSubmitDropDownButton()
}
}
assertTrue(
mDevice.findObject(
UiSelector()
.text("Selected option is: $optionName"),
).waitForExists(waitingTime),
)
}
fun verifyNoTimeIsSelected(hour: Int, minute: Int) {
assertFalse(
mDevice.findObject(
UiSelector()
.text("Selected date is: $hour:$minute"),
).waitForExists(waitingTime),
)
}
fun verifyColorIsNotSelected(hexValue: String) {
assertFalse(
mDevice.findObject(
UiSelector()
.text("Selected date is: $hexValue"),
).waitForExists(waitingTime),
)
}
class Transition {
@ -812,12 +911,22 @@ class BrowserRobot {
}
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
mDevice.findObject(
UiSelector().descriptionContains("Tap to switch tabs."),
).waitForExists(waitingTime)
mDevice.waitForObjects(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_toolbar_browser_actions"),
),
waitingTime,
)
tabsCounter().click()
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/tab_layout")))
mDevice.waitForObjects(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/new_tab_button"),
),
waitingTime,
)
TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
@ -850,7 +959,12 @@ class BrowserRobot {
assertTrue(
mDevice.findObject(UiSelector().resourceId("$packageName:id/homeLayout"))
.waitForExists(waitingTime),
.waitForExists(waitingTime) ||
mDevice.findObject(
UiSelector().text(
getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
),
).waitForExists(waitingTime),
)
HomeScreenRobot().interact()
@ -888,13 +1002,12 @@ class BrowserRobot {
}
fun clickDownloadLink(title: String, interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
val downloadLink = mDevice.findObject(UiSelector().textContains(title))
assertTrue(
"$title download link not found",
downloadLink.waitForExists(waitingTime),
webPageItemContainingText(title).waitForExists(waitingTime),
)
downloadLink.click()
clickPageObject(webPageItemContainingText(title))
DownloadRobot().interact()
return DownloadRobot.Transition()
@ -902,8 +1015,7 @@ class BrowserRobot {
fun clickStartCameraButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
// Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
cameraButton.waitForExists(waitingTime)
cameraButton.click()
clickPageObject(webPageItemWithText("Open camera"))
SitePermissionsRobot().interact()
return SitePermissionsRobot.Transition()
@ -911,8 +1023,7 @@ class BrowserRobot {
fun clickStartMicrophoneButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
// Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
microphoneButton.waitForExists(waitingTime)
microphoneButton.click()
clickPageObject(webPageItemWithText("Open microphone"))
SitePermissionsRobot().interact()
return SitePermissionsRobot.Transition()
@ -920,8 +1031,7 @@ class BrowserRobot {
fun clickStartAudioVideoButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
// Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
audioVideoButton.waitForExists(waitingTime)
audioVideoButton.click()
clickPageObject(webPageItemWithText("Camera & Microphone"))
SitePermissionsRobot().interact()
return SitePermissionsRobot.Transition()
@ -929,8 +1039,7 @@ class BrowserRobot {
fun clickOpenNotificationButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
// Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
notificationButton.waitForExists(waitingTime)
notificationButton.click()
clickPageObject(webPageItemWithText("Open notifications dialogue"))
mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains("Allow to send notifications?")))
SitePermissionsRobot().interact()
@ -939,8 +1048,14 @@ class BrowserRobot {
fun clickGetLocationButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
// Test page used for testing permissions located at https://mozilla-mobile.github.io/testapp/permissions
getLocationButton.waitForExists(waitingTime)
getLocationButton.click()
clickPageObject(webPageItemWithText("Get Location"))
SitePermissionsRobot().interact()
return SitePermissionsRobot.Transition()
}
fun clickRequestStorageAccessButton(interact: SitePermissionsRobot.() -> Unit): SitePermissionsRobot.Transition {
clickPageObject(webPageItemContainingText("requestStorageAccess()"))
SitePermissionsRobot().interact()
return SitePermissionsRobot.Transition()
@ -988,14 +1103,8 @@ private fun assertMenuButton() {
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun tabsCounter() = mDevice.findObject(By.res("$packageName:id/counter_root"))
private fun mediaPlayerPlayButton() =
mDevice.findObject(
UiSelector()
.className("android.widget.Button")
.text("Play"),
)
private fun tabsCounter() =
mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_browser_actions"))
private var progressBar =
mDevice.findObject(
@ -1013,24 +1122,6 @@ private fun addressSuggestion(streetName: String) =
.textContains(streetName),
)
private fun streetAddressTextBox(streetAddress: String = "") =
mDevice.findObject(
UiSelector()
.resourceId("streetAddress")
.textContains(streetAddress)
.className("android.widget.EditText")
.packageName("$packageName"),
)
private fun creditCardNumberTextBox(creditCardNumber: String = "") =
mDevice.findObject(
UiSelector()
.resourceId("cardNumber")
.textContains(creditCardNumber)
.className("android.widget.EditText")
.packageName("$packageName"),
)
private fun creditCardSuggestion(creditCardNumber: String) =
mDevice.findObject(
UiSelector()
@ -1041,41 +1132,92 @@ private fun creditCardSuggestion(creditCardNumber: String) =
private fun siteSecurityToolbarButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_security_indicator"))
// Permissions test page elements & prompts
// Test page used located at https://mozilla-mobile.github.io/testapp/permissions
private val cameraButton = mDevice.findObject(UiSelector().text("Open camera"))
private fun clickPageObject(webPageItem: UiObject) {
for (i in 1..RETRY_COUNT) {
try {
webPageItem.also {
it.waitForExists(waitingTime)
it.click()
}
private val microphoneButton = mDevice.findObject(UiSelector().text("Open microphone"))
break
} catch (e: UiObjectNotFoundException) {
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
progressBar.waitUntilGone(waitingTime)
}
}
}
}
}
private val audioVideoButton = mDevice.findObject(UiSelector().text("Camera & Microphone"))
fun longClickPageObject(webPageItem: UiObject) {
for (i in 1..RETRY_COUNT) {
try {
webPageItem.also {
it.waitForExists(waitingTime)
it.longClick()
}
private val notificationButton = mDevice.findObject(UiSelector().text("Open notifications dialogue"))
break
} catch (e: UiObjectNotFoundException) {
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
}
progressBar.waitUntilGone(waitingTime)
}
}
}
}
private val getLocationButton = mDevice.findObject(UiSelector().text("Get Location"))
private fun webPageItemContainingText(itemText: String) =
mDevice.findObject(UiSelector().textContains(itemText))
// Login form test page elements
// Test page used located at https://mozilla-mobile.github.io/testapp/loginForm
val userNameTextBox =
mDevice.findObject(
UiSelector()
.resourceId("username")
.className("android.widget.EditText")
.packageName("$packageName"),
)
private fun webPageItemWithText(itemText: String) =
mDevice.findObject(UiSelector().text(itemText))
private val submitLoginButton =
mDevice.findObject(
UiSelector()
.resourceId("submit")
.textContains("Submit Query")
.className("android.widget.Button")
.packageName("$packageName"),
)
private fun webPageItemWithResourceId(resourceId: String) =
mDevice.findObject(UiSelector().resourceId(resourceId))
val passwordTextBox =
private fun webPageItemContainingTextAndResourceId(resourceId: String, itemText: String) =
mDevice.findObject(
UiSelector()
.resourceId("password")
.className("android.widget.EditText")
.packageName("$packageName"),
.resourceId(resourceId)
.textContains(itemText),
)
private fun setPageObjectText(webPageItem: UiObject, text: String) {
for (i in 1..RETRY_COUNT) {
try {
webPageItem.also {
it.waitForExists(waitingTime)
it.setText(text)
}
break
} catch (e: UiObjectNotFoundException) {
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
progressBar.waitUntilGone(waitingTime)
}
}
}
}
}
private val currentDate = LocalDate.now()
private val currentDay = currentDate.dayOfMonth
private val currentMonth = currentDate.month
private val currentYear = currentDate.year

@ -11,11 +11,13 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.UiSelector
import junit.framework.TestCase.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -92,49 +94,40 @@ class CustomTabRobot {
}
fun fillAndSubmitLoginCredentials(userName: String, password: String) {
var currentTries = 0
while (currentTries++ < 3) {
try {
mDevice.waitForIdle(waitingTime)
userNameTextBox.setText(userName)
passwordTextBox.setText(password)
submitLoginButton.click()
mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")))
break
} catch (e: UiObjectNotFoundException) {
customTabScreen {
}.openMainMenu {
refreshButton().click()
waitForPageToLoad()
}
}
}
mDevice.waitForIdle(waitingTime)
setPageObjectText(webPageItemWithResourceId("username"), userName)
setPageObjectText(webPageItemWithResourceId("password"), password)
clickPageObject(webPageItemWithResourceId("submit"))
mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")))
}
fun clickLinkMatchingText(expectedText: String) {
var currentTries = 0
while (currentTries++ < 3) {
fun clickLinkMatchingText(expectedText: String) = clickPageObject(webPageItemContainingText(expectedText))
fun waitForPageToLoad() = progressBar.waitUntilGone(waitingTime)
fun clickPageObject(webPageItem: UiObject) {
for (i in 1..RETRY_COUNT) {
try {
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains(expectedText))
.waitForExists(waitingTime)
webPageItem.also {
it.waitForExists(waitingTime)
it.click()
}
val element = mDevice.findObject(UiSelector().textContains(expectedText))
element.click()
break
} catch (e: UiObjectNotFoundException) {
customTabScreen {
}.openMainMenu {
refreshButton().click()
waitForPageToLoad()
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
progressBar.waitUntilGone(waitingTime)
}
}
}
}
}
fun waitForPageToLoad() = progressBar.waitUntilGone(waitingTime)
class Transition {
fun openMainMenu(interact: CustomTabRobot.() -> Unit): Transition {
mainMenuButton().waitForExists(waitingTime)
@ -176,16 +169,36 @@ private fun closeButton() = onView(withContentDescription("Return to previous ap
private fun customTabToolbar() = mDevice.findObject(By.res("$packageName:id/toolbar"))
private fun setPageObjectText(webPageItem: UiObject, text: String) {
for (i in 1..RETRY_COUNT) {
try {
webPageItem.also {
it.waitForExists(waitingTime)
it.setText(text)
}
break
} catch (e: UiObjectNotFoundException) {
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
progressBar.waitUntilGone(waitingTime)
}
}
}
}
}
private val progressBar =
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_progress"),
)
private val submitLoginButton =
mDevice.findObject(
UiSelector()
.resourceId("submit")
.textContains("Submit Query")
.className("android.widget.Button")
.packageName("$packageName"),
)
private fun webPageItemContainingText(itemText: String) =
mDevice.findObject(UiSelector().textContains(itemText))
private fun webPageItemWithResourceId(resourceId: String) =
mDevice.findObject(UiSelector().resourceId(resourceId))

@ -28,8 +28,8 @@ import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.assertExternalAppOpens
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull

@ -11,6 +11,7 @@ import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
@ -23,6 +24,7 @@ import org.hamcrest.Matchers.allOf
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -61,7 +63,7 @@ class HistoryRobot {
fun verifyFirstTestPageTitle(title: String) = assertTestPageTitle(title)
fun verifyTestPageUrl(expectedUrl: Uri) = assertPageUrl(expectedUrl)
fun verifyTestPageUrl(expectedUrl: Uri) = pageUrl(expectedUrl.toString()).check(matches(isDisplayed()))
fun verifyCopySnackBarText() = assertCopySnackBarText()
@ -99,9 +101,34 @@ class HistoryRobot {
snackBarUndoButton().click()
}
fun verifySearchGroupDisplayed(shouldBeDisplayed: Boolean, searchTerm: String, groupSize: Int) {
// checks if the search group exists in the Recently visited section
if (shouldBeDisplayed) {
assertTrue(
mDevice.findObject(UiSelector().text(searchTerm))
.getFromParent(UiSelector().text("$groupSize sites"))
.waitForExists(TestAssetHelper.waitingTimeShort),
)
} else {
assertFalse(
mDevice.findObject(UiSelector().text(searchTerm))
.getFromParent(UiSelector().text("$groupSize sites"))
.waitForExists(TestAssetHelper.waitingTimeShort),
)
}
}
class Transition {
fun goBackToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.pressBack()
fun goBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
onView(withContentDescription("Navigate up")).click()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun openWebsite(url: Uri, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
assertHistoryListExists()
onView(withText(url.toString())).click()
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -116,10 +143,10 @@ fun historyMenu(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
private fun testPageTitle() = onView(allOf(withId(R.id.title), withText("Test_Page_1")))
private fun pageUrl() = onView(withId(R.id.url))
private fun pageUrl(url: String) = onView(allOf(withId(R.id.url), withText(url)))
private fun deleteButton(title: String) =
onView(allOf(withId(R.id.overflow_menu), hasSibling(withText(title))))
onView(allOf(withContentDescription("Delete"), hasSibling(withText(title))))
private fun deleteButton() = onView(withId(R.id.history_delete))
@ -142,7 +169,7 @@ private fun assertEmptyHistoryView() =
.check(matches(withText("No history here")))
private fun assertHistoryListExists() =
mDevice.findObject(UiSelector().resourceId("R.id.history_list")).waitForExists(waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/history_list")).waitForExists(waitingTime)
private fun assertHistoryItemExists(shouldExist: Boolean, item: String) {
if (shouldExist) {
@ -159,10 +186,6 @@ private fun assertTestPageTitle(title: String) = testPageTitle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(withText(title)))
private fun assertPageUrl(expectedUrl: Uri) = pageUrl()
.check(matches(ViewMatchers.isCompletelyDisplayed()))
.check(matches(withText(Matchers.containsString(expectedUrl.toString()))))
private fun assertDeleteConfirmationMessage() {
assertTrue(deleteHistoryPromptTitle().waitForExists(waitingTime))
assertTrue(deleteHistoryPromptSummary().waitForExists(waitingTime))

@ -11,6 +11,7 @@ import android.widget.EditText
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
@ -37,7 +38,6 @@ import mozilla.components.browser.state.state.searchEngines
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers
import org.junit.Assert
import org.junit.Assert.assertFalse
@ -56,7 +56,6 @@ import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.matchers.hasItem
import org.mozilla.fenix.helpers.withBitmapDrawable
import org.mozilla.fenix.ui.util.STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER
import org.mozilla.fenix.ui.util.STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER
@ -134,6 +133,44 @@ class HomeScreenRobot {
fun verifyPrivacyNoticeButton() = assertPrivacyNoticeButton()
fun verifyStartBrowsingButton() = assertStartBrowsingButton()
// Upgrading users onboarding dialog
fun verifyUpgradingUserOnboardingFirstScreen(testRule: ComposeTestRule) {
testRule.also {
it.onNodeWithText(getStringResource(R.string.onboarding_home_welcome_title_2))
.assertIsDisplayed()
it.onNodeWithText(getStringResource(R.string.onboarding_home_welcome_description))
.assertIsDisplayed()
it.onNodeWithText(getStringResource(R.string.onboarding_home_get_started_button))
.assertIsDisplayed()
}
}
fun clickGetStartedButton(testRule: ComposeTestRule) =
testRule.onNodeWithText(getStringResource(R.string.onboarding_home_get_started_button)).performClick()
fun verifyUpgradingUserOnboardingSecondScreen(testRule: ComposeTestRule) {
testRule.also {
it.onNodeWithText(getStringResource(R.string.onboarding_home_sync_title_3))
.assertIsDisplayed()
it.onNodeWithText(getStringResource(R.string.onboarding_home_sync_description))
.assertIsDisplayed()
it.onNodeWithText(getStringResource(R.string.onboarding_home_sign_in_button))
.assertIsDisplayed()
it.onNodeWithText(getStringResource(R.string.onboarding_home_skip_button))
.assertIsDisplayed()
}
}
fun clickSkipButton(testRule: ComposeTestRule) =
testRule
.onNodeWithText(getStringResource(R.string.onboarding_home_skip_button))
.performClick()
fun verifyPrivateSessionMessage() = assertPrivateSessionMessage()
fun verifyExistingTopSitesList() = assertExistingTopSitesList()
@ -145,42 +182,27 @@ class HomeScreenRobot {
fun verifyJumpBackInSectionIsDisplayed() = assertJumpBackInSectionIsDisplayed()
fun verifyJumpBackInSectionIsNotDisplayed() = assertJumpBackInSectionIsNotDisplayed()
fun verifyRecentlyVisitedSectionIsDisplayed() = assertRecentlyVisitedSectionIsDisplayed()
fun verifyRecentlyVisitedSectionIsNotDisplayed() = assertRecentlyVisitedSectionIsNotDisplayed()
fun verifyRecentBookmarksSectionIsDisplayed() = assertRecentBookmarksSectionIsDisplayed()
fun verifyRecentBookmarksSectionIsNotDisplayed() = assertRecentBookmarksSectionIsNotDisplayed()
fun verifyPocketSectionIsDisplayed() = assertPocketSectionIsDisplayed()
fun verifyPocketSectionIsNotDisplayed() = assertPocketSectionIsNotDisplayed()
fun verifyRecentlyVisitedSearchGroupDisplayed(shouldBeDisplayed: Boolean, searchTerm: String, groupSize: Int) {
// checks if the search group exists in the Recently visited section
if (shouldBeDisplayed) {
recentlyVisitedList.waitForExists(waitingTime)
scrollToElementByText("Recently visited")
recentlyVisitedList.getChildByText(UiSelector().text(searchTerm), searchTerm, true)
.waitForExists(waitingTimeShort)
assertTrue(
mDevice.findObject(UiSelector().text(searchTerm))
.getFromParent(UiSelector().text("$groupSize sites"))
.waitForExists(waitingTimeShort),
)
} else {
assertFalse(
assertTrue(
mDevice.findObject(UiSelector().text(searchTerm))
.getFromParent(UiSelector().text("$groupSize sites"))
.waitForExists(waitingTimeShort),
)
}
}
fun verifyCurrentSearchGroupIsDisplayed(shouldBeDisplayed: Boolean, searchTerm: String, groupSize: Int = 0) {
// checks search group in the Jump back in section
if (shouldBeDisplayed) {
assertTrue(
mDevice.findObject(UiSelector().text("""Your search for "$searchTerm""""))
.getFromParent(UiSelector().textContains("$groupSize sites"))
.waitForExists(waitingTimeShort),
)
} else {
assertFalse(
mDevice.findObject(UiSelector().text("""Your search for "$searchTerm""""))
.waitForExists(waitingTimeShort),
.waitUntilGone(waitingTimeShort),
)
}
}
@ -292,7 +314,7 @@ class HomeScreenRobot {
fun getSponsoredShortcutTitle(position: Int): String {
val sponsoredShortcut = mDevice.findObject(
UiSelector()
.className("android.widget.FrameLayout")
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
@ -302,6 +324,16 @@ class HomeScreenRobot {
return sponsoredShortcut
}
fun verifyJumpBackInMessage() {
assertTrue(
mDevice.findObject(
UiSelector().text(
getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
),
).waitForExists(waitingTime),
)
}
class Transition {
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
@ -352,6 +384,16 @@ class HomeScreenRobot {
return SearchRobot.Transition()
}
fun clickUpgradingUserOnboardingSignInButton(
testRule: ComposeTestRule,
interact: SyncSignInRobot.() -> Unit,
): SyncSignInRobot.Transition {
testRule.onNodeWithText("Sign in").performClick()
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
}
fun togglePrivateBrowsingMode() {
mDevice.findObject(UiSelector().resourceId("$packageName:id/privateBrowsingButton"))
.waitForExists(
@ -519,7 +561,8 @@ class HomeScreenRobot {
}
fun openRecentlyVisitedSearchGroupHistoryList(title: String, interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
val searchGroup = recentlyVisitedList.getChildByText(UiSelector().text(title), title, true)
scrollToElementByText("Recently visited")
val searchGroup = mDevice.findObject(UiSelector().text(title))
searchGroup.waitForExists(waitingTimeShort)
searchGroup.click()
@ -645,9 +688,11 @@ private fun assertStartSyncHeader() {
onView(allOf(withText(R.string.onboarding_account_sign_in_header)))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertAccountsSignInButton() =
private fun assertAccountsSignInButton() {
scrollToElementByText(getStringResource(R.string.onboarding_firefox_account_sign_in))
onView(ViewMatchers.withResourceName("fxa_sign_in_button"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertChooseThemeHeader() {
scrollToElementByText("Choose your theme")
@ -798,7 +843,7 @@ private fun assertSponsoredSubtitleIsDisplayed(position: Int) =
assertTrue(
mDevice.findObject(
UiSelector()
.className("android.widget.FrameLayout")
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
@ -810,7 +855,7 @@ private fun assertSponsoredShortcutTitle(sponsoredShortcutTitle: String, positio
assertTrue(
mDevice.findObject(
UiSelector()
.className("android.widget.FrameLayout")
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
@ -820,11 +865,15 @@ private fun assertSponsoredShortcutTitle(sponsoredShortcutTitle: String, positio
}
private fun assertNotExistingTopSitesList(title: String) {
mDevice.findObject(UiSelector().text(title))
.waitUntilGone(waitingTime)
mDevice.findObject(UiSelector().text(title)).waitUntilGone(waitingTime)
onView(allOf(withId(R.id.top_sites_list)))
.check(matches(not(hasItem(hasDescendant(withText(title))))))
assertFalse(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/top_site_title")
.textContains(title),
).waitForExists(waitingTime),
)
}
private fun assertSponsoredTopSitesNotDisplayed() {
@ -852,12 +901,20 @@ private fun assertJumpBackInSectionIsDisplayed() = assertTrue(jumpBackInSection(
private fun assertJumpBackInSectionIsNotDisplayed() = assertFalse(jumpBackInSection().waitForExists(waitingTimeShort))
private fun assertRecentlyVisitedSectionIsDisplayed() = assertTrue(recentlyVisitedSection().waitForExists(waitingTime))
private fun assertRecentlyVisitedSectionIsNotDisplayed() = assertFalse(recentlyVisitedSection().waitForExists(waitingTime))
private fun assertRecentBookmarksSectionIsDisplayed() =
assertTrue(recentBookmarksSection().waitForExists(waitingTime))
private fun assertRecentBookmarksSectionIsNotDisplayed() =
assertFalse(recentBookmarksSection().waitForExists(waitingTimeShort))
private fun assertPocketSectionIsDisplayed() = assertTrue(pocketSection().waitForExists(waitingTime))
private fun assertPocketSectionIsNotDisplayed() = assertFalse(pocketSection().waitForExists(waitingTime))
private fun privateBrowsingButton() = onView(withId(R.id.privateBrowsingButton))
private fun saveTabsToCollectionButton() = onView(withId(R.id.add_tabs_to_collections_button))
@ -867,9 +924,15 @@ private fun tabsCounter() = onView(withId(R.id.tab_button))
private fun jumpBackInSection() =
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.recent_tabs_header)))
private fun recentlyVisitedSection() =
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.history_metadata_header_2)))
private fun recentBookmarksSection() =
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.recently_saved_title)))
private fun pocketSection() =
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.pocket_stories_header_1)))
private fun startBrowsingButton(): UiObject {
val startBrowsingButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/finish_button"))
homeScreenList()

@ -9,22 +9,22 @@ import android.widget.TextView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.hamcrest.Matchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
/*
* Implementation of Robot Pattern for the multiple selection toolbar of History and Bookmarks menus.

@ -22,18 +22,19 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.not
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
@ -54,8 +55,6 @@ class NavigationToolbarRobot {
fun verifyCloseReaderViewDetected(visible: Boolean = false) =
assertCloseReaderViewDetected(visible)
fun typeSearchTerm(searchTerm: String) = awesomeBar().setText(searchTerm)
fun toggleReaderView() {
mDevice.findObject(
UiSelector()
@ -66,6 +65,33 @@ class NavigationToolbarRobot {
readerViewToggle().click()
}
fun verifyClipboardSuggestionsAreDisplayed(link: String, shouldBeDisplayed: Boolean) {
when (shouldBeDisplayed) {
true -> {
assertTrue(
mDevice.findObject(UiSelector().resourceId("$packageName:id/fill_link_from_clipboard"))
.waitForExists(waitingTime),
)
assertTrue(
mDevice.findObject(UiSelector().resourceId("$packageName:id/clipboard_url").text(link))
.waitForExists(waitingTime),
)
}
false -> {
assertFalse(
mDevice.findObject(UiSelector().resourceId("$packageName:id/fill_link_from_clipboard"))
.waitForExists(waitingTime),
)
assertFalse(
mDevice.findObject(UiSelector().resourceId("$packageName:id/clipboard_url").text(link))
.waitForExists(waitingTime),
)
}
}
}
class Transition {
private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
@ -98,12 +124,15 @@ class NavigationToolbarRobot {
mDevice.pressEnter()
runWithIdleRes(sessionLoadedIdlingResource) {
onView(
anyOf(
withResourceName("browserLayout"),
withResourceName("download_button"),
),
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
assertTrue(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/browserLayout"),
).waitForExists(waitingTime) || mDevice.findObject(
UiSelector().resourceId("$packageName:id/download_button"),
).waitForExists(waitingTime) || mDevice.findObject(
UiSelector().text(getStringResource(R.string.tcp_cfr_message)),
).waitForExists(waitingTime),
)
}
BrowserRobot().interact()
@ -149,14 +178,12 @@ class NavigationToolbarRobot {
}
fun visitLinkFromClipboard(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/mozac_browser_toolbar_clear_view")),
waitingTime,
)
clearAddressBar().click()
if (clearAddressBar().waitForExists(waitingTimeShort)) {
clearAddressBar().click()
}
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/clipboard_title")),
Until.findObject(By.res("$packageName:id/clipboard_title")),
waitingTime,
)
@ -164,7 +191,7 @@ class NavigationToolbarRobot {
// See for mor information https://github.com/mozilla-mobile/fenix/issues/22271
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/clipboard_url")),
Until.findObject(By.res("$packageName:id/clipboard_url")),
waitingTime,
)
}
@ -284,7 +311,9 @@ private fun threeDotButton() = onView(withId(R.id.mozac_browser_toolbar_menu))
private fun tabTrayButton() = onView(withId(R.id.tab_button))
private fun fillLinkButton() = onView(withId(R.id.fill_link_from_clipboard))
private fun clearAddressBar() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_clear_view"))
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_clear_view"),
)
private fun goBackButton() = mDevice.pressBack()
private fun readerViewToggle() =
onView(withParent(withId(R.id.mozac_browser_toolbar_page_actions)))

@ -13,12 +13,12 @@ import androidx.test.uiautomator.Until
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.ext.waitNotNull
import java.lang.AssertionError
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
class NotificationRobot {

@ -8,17 +8,16 @@ package org.mozilla.fenix.ui.robots
import android.os.Build
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertAny
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.android.ComposeNotIdleException
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
import androidx.compose.ui.test.performScrollToNode
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.assertion.ViewAssertions.matches
@ -28,12 +27,9 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withSubstring
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.allOf
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -41,16 +37,16 @@ import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.Constants.SPEECH_RECOGNITION
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.waitForObjects
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
/**
* Implementation of Robot Pattern for the search fragment.
@ -94,14 +90,95 @@ class SearchRobot {
}
fun verifySearchEngineButton() = assertSearchButton()
fun verifySearchWithText() = assertSearchWithText()
fun verifySearchEngineResults(rule: ComposeTestRule, searchSuggestion: String, searchEngineName: String) =
assertSearchEngineResults(rule, searchSuggestion, searchEngineName)
fun verifySearchEngineSuggestionResults(rule: ComposeTestRule, searchSuggestion: String) =
assertSearchEngineSuggestionResults(rule, searchSuggestion)
fun verifyNoSuggestionsAreDisplayed(rule: ComposeTestRule, searchSuggestion: String) =
assertNoSuggestionsAreDisplayed(rule, searchSuggestion)
fun verifySearchSettings() = assertSearchSettings()
fun verifySearchEngineSuggestionResults(rule: ComposeTestRule, searchSuggestion: String) {
rule.waitForIdle()
for (i in 1..RETRY_COUNT) {
try {
assertTrue(
mDevice.findObject(UiSelector().textContains(searchSuggestion))
.waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
throw e
} else {
expandSearchSuggestionsList()
}
}
}
}
fun verifyFirefoxSuggestResults(rule: ComposeTestRule, searchSuggestion: String) {
rule.waitForIdle()
for (i in 1..RETRY_COUNT) {
try {
rule.onNodeWithTag("mozac.awesomebar.suggestions")
.performScrollToNode(hasText(searchSuggestion))
.assertExists()
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
throw e
} else {
expandSearchSuggestionsList()
}
}
}
}
fun verifyNoSuggestionsAreDisplayed(rule: ComposeTestRule, searchSuggestion: String) {
rule.waitForIdle()
assertFalse(
mDevice.findObject(UiSelector().textContains(searchSuggestion))
.waitForExists(waitingTime),
)
}
fun verifyAllowSuggestionsInPrivateModeDialog() {
assertTrue(
mDevice.findObject(
UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_title)),
).waitForExists(waitingTime),
)
assertTrue(
mDevice.findObject(
UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_text)),
).exists(),
)
assertTrue(
mDevice.findObject(
UiSelector().text("Learn more"),
).exists(),
)
assertTrue(
mDevice.findObject(
UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_allow_button)),
).exists(),
)
assertTrue(
mDevice.findObject(
UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_do_not_allow_button)),
).exists(),
)
}
fun denySuggestionsInPrivateMode() {
mDevice.findObject(
UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_do_not_allow_button)),
).click()
}
fun allowSuggestionsInPrivateMode() {
mDevice.findObject(
UiSelector().text(getStringResource(R.string.search_suggestions_onboarding_allow_button)),
).click()
}
fun verifySearchEnginePrompt(rule: ComposeTestRule, searchEngineName: String) =
assertSearchEnginePrompt(rule, searchEngineName)
fun verifySearchBarEmpty() = assertSearchBarEmpty()
fun verifyKeyboardVisibility() = assertKeyboardVisibility(isExpectedToBeVisible = true)
@ -157,33 +234,6 @@ class SearchRobot {
mDevice.waitForIdle()
}
fun clickSearchEngineButton(rule: ComposeTestRule, searchEngineName: String) {
rule.waitForIdle()
mDevice.waitForObjects(
mDevice.findObject(
UiSelector().textContains(searchEngineName),
),
)
rule.onNodeWithText(searchEngineName)
.assertExists()
.assertHasClickAction()
.performClick()
}
fun clickSearchEngineResult(rule: ComposeTestRule, searchSuggestion: String) {
mDevice.waitNotNull(
Until.findObjects(By.text(searchSuggestion)),
waitingTime,
)
rule.onNodeWithText(searchSuggestion)
.assertIsDisplayed()
.assertHasClickAction()
.performClick()
}
@OptIn(ExperimentalTestApi::class)
fun scrollToSearchEngineSettings(rule: ComposeTestRule) {
// Soft keyboard is visible on screen on view access; hide it
@ -198,13 +248,6 @@ class SearchRobot {
.performScrollToIndex(5)
}
fun clickSearchEngineSettings(rule: ComposeTestRule) {
rule.onNodeWithText("Search engine settings")
.assertIsDisplayed()
.assertHasClickAction()
.performClick()
}
fun clickClearButton() {
clearButton().click()
}
@ -225,11 +268,53 @@ class SearchRobot {
pasteText.click()
}
fun clickSearchEnginePrompt(rule: ComposeTestRule, searchEngineName: String) =
rule.onNodeWithText("Search $searchEngineName").performClick()
fun expandSearchSuggestionsList() {
onView(allOf(withId(R.id.search_wrapper))).perform(
closeSoftKeyboard(),
)
awesomeBar.swipeUp(2)
}
fun verifyPastedToolbarText(expectedText: String) = assertPastedToolbarText(expectedText)
fun verifyTranslatedFocusedNavigationToolbar(toolbarHintString: String) =
assertTranslatedFocusedNavigationToolbar(toolbarHintString)
fun verifySearchEngineShortcuts(rule: ComposeTestRule, vararg searchEngines: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/awesome_bar"),
).swipeUp(1)
for (searchEngine in searchEngines) {
rule.waitForIdle()
rule.onNodeWithText(searchEngine).assertIsDisplayed()
}
}
fun verifySearchEngineShortcutsAreNotDisplayed(rule: ComposeTestRule, vararg searchEngines: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/pill_wrapper_divider"),
).waitForExists(waitingTime)
for (searchEngine in searchEngines) {
rule.waitForIdle()
rule.onNodeWithText(searchEngine).assertDoesNotExist()
}
}
fun verifyTypedToolbarText(expectedText: String) {
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view"))
.waitForExists(waitingTime)
onView(
allOf(
withText(expectedText),
withId(R.id.mozac_browser_toolbar_edit_url_view),
),
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
class Transition {
private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
@ -276,9 +361,17 @@ class SearchRobot {
return BrowserRobot.Transition()
}
fun goToSearchEngine(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
NavigationToolbarRobot().interact()
return NavigationToolbarRobot.Transition()
fun clickSearchEngineSettings(
rule: ComposeTestRule,
interact: SettingsSubMenuSearchRobot.() -> Unit,
): SettingsSubMenuSearchRobot.Transition {
rule.onNodeWithText("Search engine settings")
.assertIsDisplayed()
.assertHasClickAction()
.performClick()
SettingsSubMenuSearchRobot().interact()
return SettingsSubMenuSearchRobot.Transition()
}
}
}
@ -300,35 +393,12 @@ private fun clearButton() =
private fun searchWrapper() = mDevice.findObject(UiSelector().resourceId("$packageName:id/search_wrapper"))
private fun assertSearchEngineResults(rule: ComposeTestRule, searchSuggestion: String, searchEngineName: String) {
rule.waitUntil(waitingTime, waitForSearchSuggestions(rule, searchSuggestion, searchEngineName))
rule.onNodeWithText(searchSuggestion).assertIsDisplayed()
}
private fun waitForSearchSuggestions(rule: ComposeTestRule, searchSuggestion: String, searchEngineName: String): () -> Boolean =
{
rule.waitForIdle()
mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(searchSuggestion)))
rule.onAllNodesWithTag("mozac.awesomebar.suggestion").assertAny(hasText(searchSuggestion) and hasText(searchEngineName))
mDevice.findObject(UiSelector().textContains(searchSuggestion)).waitForExists(waitingTime)
}
private fun assertSearchEngineSuggestionResults(rule: ComposeTestRule, searchResult: String) {
rule.waitForIdle()
assertTrue(
mDevice.findObject(UiSelector().textContains(searchResult))
.waitForExists(waitingTime),
)
}
private fun assertNoSuggestionsAreDisplayed(rule: ComposeTestRule, searchTerm: String) {
private fun assertSearchEnginePrompt(rule: ComposeTestRule, searchEngineName: String) {
rule.waitForIdle()
assertFalse(
mDevice.findObject(UiSelector().textContains(searchTerm))
.waitForExists(waitingTime),
)
rule.onNodeWithText("Search $searchEngineName").assertIsDisplayed()
rule.onNodeWithText(
getStringResource(R.string.search_engine_suggestions_description),
).assertIsDisplayed()
}
private fun assertSearchView() =
@ -357,14 +427,6 @@ private fun assertSearchButton() =
).waitForExists(waitingTime),
)
private fun assertSearchWithText() =
onView(allOf(withText("THIS TIME, SEARCH WITH:")))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertSearchSettings() =
onView(allOf(withText("Default search engine")))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertSearchBarEmpty() =
assertTrue(
mDevice.findObject(
@ -462,25 +524,16 @@ private fun assertDefaultSearchEngine(expectedText: String) =
).waitForExists(waitingTime),
)
private fun assertPastedToolbarText(expectedText: String) {
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
.waitForExists(waitingTime)
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view"))
.waitForExists(waitingTime)
onView(
allOf(
withSubstring(expectedText),
withId(R.id.mozac_browser_toolbar_edit_url_view),
),
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertTranslatedFocusedNavigationToolbar(toolbarHintString: String) =
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view")
.textContains(toolbarHintString),
).waitForExists(waitingTime),
)
private val awesomeBar =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"))
private val voiceSearchButton = mDevice.findObject(UiSelector().description("Voice search"))
private val searchSuggestionsList =
UiScrollable(
UiSelector().className("android.widget.ScrollView"),
)

@ -44,6 +44,7 @@ import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_PLAY_SERVICES
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.getStringResource
@ -575,7 +576,21 @@ private fun rateOnGooglePlayHeading(): UiObject {
}
private fun aboutFirefoxHeading(): UiObject {
settingsList().scrollToEnd(LISTS_MAXSWIPES)
for (i in 1..RETRY_COUNT) {
try {
settingsList().scrollToEnd(LISTS_MAXSWIPES)
assertTrue(
mDevice.findObject(UiSelector().text("About $appName"))
.waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
throw e
}
}
}
return mDevice.findObject(UiSelector().text("About $appName"))
}

@ -6,22 +6,24 @@ package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.endsWith
import org.hamcrest.Matchers
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.click
@ -30,25 +32,46 @@ import org.mozilla.fenix.helpers.click
*/
class SettingsSubMenuHomepageRobot {
fun verifyHomePageView() {
assertMostVisitedTopSitesButton()
fun verifyHomePageView(
shortcutsSwitchEnabled: Boolean = true,
sponsoredShortcutsCheckBox: Boolean = true,
jumpBackInSwitchEnabled: Boolean = true,
recentBookmarksSwitchEnabled: Boolean = true,
recentlyVisitedSwitchEnabled: Boolean = true,
pocketSwitchEnabled: Boolean = true,
sponsoredStoriesCheckBox: Boolean = true,
) {
assertShortcutsButton()
assertShortcutsSwitchState(shortcutsSwitchEnabled)
assertSponsoredShortcutsButton()
assertSponsoredShortcutsCheckBox(sponsoredShortcutsCheckBox)
assertJumpBackInButton()
assertJumpBackInSwitchState(jumpBackInSwitchEnabled)
assertRecentBookmarksButton()
assertRecentSearchesButton()
assertRecentBookmarksSwitchState(recentBookmarksSwitchEnabled)
assertRecentlyVisitedButton()
assertRecentlyVisitedSwitchState(recentlyVisitedSwitchEnabled)
assertPocketButton()
assertPocketSwitchState(pocketSwitchEnabled)
assertSponsoredStoriesButton()
assertSponsoredStoriesCheckBox(sponsoredStoriesCheckBox)
assertOpeningScreenHeading()
assertHomepageButton()
assertLastTabButton()
assertHomepageAfterFourHoursButton()
}
fun clickSponsoredShortcuts() = sponsoredShortcuts().click()
fun clickShortcutsButton() = shortcutsButton().click()
fun clickSponsoredShortcuts() = sponsoredShortcutsButton().click()
fun clickJumpBackInButton() = jumpBackInButton().click()
fun clickRecentlyVisited() = recentlyVisitedButton().click()
fun clickRecentBookmarksButton() = recentBookmarksButton().click()
fun clickRecentSearchesButton() = recentSearchesButton().click()
fun clickRecentSearchesButton() = recentlyVisitedButton().click()
fun clickPocketButton() = pocketButton().click()
@ -69,37 +92,7 @@ class SettingsSubMenuHomepageRobot {
).waitForExists(waitingTimeShort),
)
fun verifySponsoredShortcutsCheckBox(checked: Boolean) {
if (checked) {
sponsoredShortcuts()
.check(
matches(
hasSibling(
withChild(
allOf(
withClassName(endsWith("CheckBox")),
isChecked(),
),
),
),
),
)
} else {
sponsoredShortcuts()
.check(
matches(
hasSibling(
withChild(
allOf(
withClassName(endsWith("CheckBox")),
isNotChecked(),
),
),
),
),
)
}
}
fun verifySponsoredShortcutsCheckBox(checked: Boolean) = assertSponsoredShortcutsCheckBox(checked)
class Transition {
@ -121,10 +114,10 @@ class SettingsSubMenuHomepageRobot {
}
}
private fun mostVisitedTopSitesButton() =
private fun shortcutsButton() =
onView(allOf(withText(R.string.top_sites_toggle_top_recent_sites_4)))
private fun sponsoredShortcuts() =
private fun sponsoredShortcutsButton() =
onView(allOf(withText(R.string.customize_toggle_contile)))
private fun jumpBackInButton() =
@ -133,11 +126,14 @@ private fun jumpBackInButton() =
private fun recentBookmarksButton() =
onView(allOf(withText(R.string.customize_toggle_recent_bookmarks)))
private fun recentSearchesButton() =
private fun recentlyVisitedButton() =
onView(allOf(withText(R.string.customize_toggle_recently_visited)))
private fun pocketButton() =
onView(allOf(withText(R.string.customize_toggle_pocket)))
onView(allOf(withText(R.string.customize_toggle_pocket_2)))
private fun sponsoredStoriesButton() =
onView(allOf(withText(R.string.customize_toggle_pocket_sponsored)))
private fun openingScreenHeading() = onView(withText(R.string.preferences_opening_screen))
@ -170,16 +166,20 @@ private fun homepageAfterFourHoursButton() =
private fun goBackButton() = onView(allOf(withContentDescription(R.string.action_bar_up_description)))
private fun assertMostVisitedTopSitesButton() =
mostVisitedTopSitesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertShortcutsButton() =
shortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertSponsoredShortcutsButton() =
sponsoredShortcutsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertJumpBackInButton() =
jumpBackInButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertRecentBookmarksButton() =
recentBookmarksButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertRecentSearchesButton() =
recentSearchesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertRecentlyVisitedButton() =
recentlyVisitedButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertPocketButton() =
pocketButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertSponsoredStoriesButton() =
sponsoredStoriesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertOpeningScreenHeading() =
openingScreenHeading().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertHomepageButton() =
@ -189,4 +189,208 @@ private fun assertLastTabButton() =
private fun assertHomepageAfterFourHoursButton() =
homepageAfterFourHoursButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
fun assertShortcutsSwitchState(enabled: Boolean) {
if (enabled) {
shortcutsButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isChecked(),
),
),
),
)
} else {
shortcutsButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isNotChecked(),
),
),
),
)
}
}
fun assertSponsoredShortcutsCheckBox(checked: Boolean) {
if (checked) {
sponsoredShortcutsButton()
.check(
matches(
hasSibling(
ViewMatchers.withChild(
allOf(
withClassName(CoreMatchers.endsWith("CheckBox")),
isChecked(),
),
),
),
),
)
} else {
sponsoredShortcutsButton()
.check(
matches(
hasSibling(
ViewMatchers.withChild(
allOf(
withClassName(CoreMatchers.endsWith("CheckBox")),
isNotChecked(),
),
),
),
),
)
}
}
fun assertJumpBackInSwitchState(enabled: Boolean) {
if (enabled) {
jumpBackInButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isChecked(),
),
),
),
)
} else {
jumpBackInButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isNotChecked(),
),
),
),
)
}
}
fun assertRecentBookmarksSwitchState(enabled: Boolean) {
if (enabled) {
recentBookmarksButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isChecked(),
),
),
),
)
} else {
recentBookmarksButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isNotChecked(),
),
),
),
)
}
}
fun assertRecentlyVisitedSwitchState(enabled: Boolean) {
if (enabled) {
recentlyVisitedButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isChecked(),
),
),
),
)
} else {
recentlyVisitedButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isNotChecked(),
),
),
),
)
}
}
fun assertPocketSwitchState(enabled: Boolean) {
if (enabled) {
pocketButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isChecked(),
),
),
),
)
} else {
pocketButton()
.check(
matches(
TestHelper.hasCousin(
Matchers.allOf(
withClassName(Matchers.endsWith("Switch")),
isNotChecked(),
),
),
),
)
}
}
fun assertSponsoredStoriesCheckBox(checked: Boolean) {
if (checked) {
sponsoredStoriesButton()
.check(
matches(
hasSibling(
ViewMatchers.withChild(
allOf(
withClassName(CoreMatchers.endsWith("CheckBox")),
isChecked(),
),
),
),
),
)
} else {
sponsoredStoriesButton()
.check(
matches(
hasSibling(
ViewMatchers.withChild(
allOf(
withClassName(CoreMatchers.endsWith("CheckBox")),
isNotChecked(),
),
),
),
),
)
}
}
private val wallpapersMenuButton = onView(withText("Wallpapers"))

@ -110,5 +110,5 @@ private fun assertDefaultValueAutofillLogins(context: Context) = onView(
private fun assertDefaultValueExceptions() = onView(ViewMatchers.withText("Exceptions"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertDefaultValueSyncLogins() = onView(ViewMatchers.withText("Sign in to Sync"))
private fun assertDefaultValueSyncLogins() = onView(ViewMatchers.withText("Sync and save data"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))

@ -15,6 +15,7 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -23,13 +24,18 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.endsWith
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.hasCousin
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.isChecked
/**
* Implementation of Robot Pattern for the settings search sub menu.
@ -38,18 +44,90 @@ class SettingsSubMenuSearchRobot {
fun verifySearchToolbar() = assertSearchToolbar()
fun verifyDefaultSearchEngineHeader() = assertDefaultSearchEngineHeader()
fun verifySearchEngineList() = assertSearchEngineList()
fun verifyShowSearchSuggestions() = assertShowSearchSuggestions()
fun verifyShowSearchSuggestions() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search suggestions")),
),
)
onView(withText("Show search suggestions"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
fun verifyShowSearchShortcuts() = assertShowSearchShortcuts()
fun verifyShowClipboardSuggestions() = assertShowClipboardSuggestions()
fun verifySearchBrowsingHistory() = assertSearchBrowsingHistory()
fun verifySearchBookmarks() = assertSearchBookmarks()
fun verifyShowClipboardSuggestionsDefault() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(getStringResource(R.string.preferences_show_clipboard_suggestions))),
),
)
onView(withText(getStringResource(R.string.preferences_show_clipboard_suggestions)))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(hasCousin(allOf(withClassName(endsWith("Switch")), isChecked(true)))))
}
fun toggleClipboardSuggestion() {
onView(withText(getStringResource(R.string.preferences_show_clipboard_suggestions)))
.click()
}
fun verifySearchBrowsingHistory() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search browsing history")),
),
)
searchHistoryToggle
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
fun verifySearchBookmarks() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search bookmarks")),
),
)
searchBookmarksToggle
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
fun changeDefaultSearchEngine(searchEngineName: String) =
selectSearchEngine(searchEngineName)
fun disableShowSearchSuggestions() = toggleShowSearchSuggestions()
fun toggleAutocomplete() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(getStringResource(R.string.preferences_enable_autocomplete_urls))),
),
)
onView(withText(getStringResource(R.string.preferences_enable_autocomplete_urls)))
.click()
}
fun toggleShowSearchSuggestions() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search suggestions")),
),
)
fun enableShowSearchShortcuts() = toggleShowSearchShortcuts()
onView(withText("Show search suggestions"))
.perform(click())
}
fun toggleShowSearchShortcuts() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search engines")),
),
)
onView(withText("Show search engines"))
.perform(click())
}
fun toggleVoiceSearch() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
@ -78,12 +156,34 @@ class SettingsSubMenuSearchRobot {
searchBookmarksToggle.click()
}
fun toggleShowSuggestionsInPrivateSessions() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(getStringResource(R.string.preferences_show_search_suggestions_in_private))),
),
)
onView(withText(getStringResource(R.string.preferences_show_search_suggestions_in_private)))
.click()
}
fun openAddSearchEngineMenu() = addSearchEngineButton().click()
fun verifyAddSearchEngineList() = assertAddSearchEngineList()
fun verifyEngineListContains(searchEngineName: String) = assertEngineListContains(searchEngineName)
fun verifyEngineListDoesNotContain(searchEngineName: String) = assertEngineListDoesNotContain(searchEngineName)
fun verifyDefaultSearchEngine(searchEngineName: String) = assertDefaultSearchEngine(searchEngineName)
fun verifyThreeDotButtonIsNotDisplayed(searchEngineName: String) = assertThreeDotButtonIsNotDisplayed(searchEngineName)
fun verifyAddSearchEngineListContains(vararg searchEngines: String) {
for (searchEngine in searchEngines) {
assertEngineListContains(searchEngine)
}
}
fun saveNewSearchEngine() {
addSearchEngineSaveButton().click()
assertTrue(
@ -160,8 +260,26 @@ class SettingsSubMenuSearchRobot {
threeDotMenu(searchEngineName).click()
}
fun deleteMultipleSearchEngines(vararg searchEngines: String) {
for (searchEngine in searchEngines) {
openEngineOverflowMenu(searchEngine)
clickDeleteSearchEngine()
}
}
fun clickEdit() = onView(withText("Edit")).click()
fun clickDeleteSearchEngine() =
mDevice.findObject(
UiSelector().textContains(getStringResource(R.string.search_engine_delete)),
).click()
fun clickUndoSnackBarButton() =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/snackbar_btn"),
).click()
fun saveEditSearchEngine() {
onView(withId(R.id.save_button)).click()
assertTrue(
@ -171,6 +289,8 @@ class SettingsSubMenuSearchRobot {
)
}
fun verifyShowSearchEnginesToggleState(enabled: Boolean) = assertShowSearchEnginesToggle(enabled)
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
mDevice.waitForIdle()
@ -215,16 +335,6 @@ private fun assertSearchEngineList() {
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertShowSearchSuggestions() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search suggestions")),
),
)
onView(withText("Show search suggestions"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertShowSearchShortcuts() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
@ -235,38 +345,8 @@ private fun assertShowSearchShortcuts() {
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertShowClipboardSuggestions() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show clipboard suggestions")),
),
)
onView(withText("Show clipboard suggestions"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertSearchBrowsingHistory() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search browsing history")),
),
)
searchHistoryToggle
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private val searchHistoryToggle = onView(withText("Search browsing history"))
private fun assertSearchBookmarks() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Search bookmarks")),
),
)
searchBookmarksToggle
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private val searchBookmarksToggle = onView(withText("Search bookmarks"))
private fun selectSearchEngine(searchEngine: String) {
@ -275,28 +355,6 @@ private fun selectSearchEngine(searchEngine: String) {
.perform(click())
}
private fun toggleShowSearchSuggestions() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search suggestions")),
),
)
onView(withText("Show search suggestions"))
.perform(click())
}
private fun toggleShowSearchShortcuts() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search engines")),
),
)
onView(withText("Show search engines"))
.perform(click())
}
private fun goBackButton() =
onView(CoreMatchers.allOf(withContentDescription("Navigate up")))
@ -314,6 +372,48 @@ private fun assertEngineListContains(searchEngineName: String) {
onView(withId(R.id.search_engine_group)).check(matches(hasDescendant(withText(searchEngineName))))
}
private fun assertDefaultSearchEngine(searchEngineName: String) =
onView(
allOf(
withId(R.id.radio_button),
withParent(withChild(withText(searchEngineName))),
),
).check(matches(isChecked(true)))
private fun assertEngineListDoesNotContain(searchEngineName: String) {
onView(withId(R.id.search_engine_group)).check(matches(not(hasDescendant(withText(searchEngineName)))))
}
private fun assertThreeDotButtonIsNotDisplayed(searchEngineName: String) =
threeDotMenu(searchEngineName).check(matches(not(isDisplayed())))
private fun assertShowSearchEnginesToggle(enabled: Boolean) =
if (enabled) {
onView(withText(R.string.preferences_show_search_engines))
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
ViewMatchers.isChecked(),
),
),
),
)
} else {
onView(withText(R.string.preferences_show_search_engines))
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
ViewMatchers.isNotChecked(),
),
),
),
)
}
private fun threeDotMenu(searchEngineName: String) =
onView(
allOf(

@ -17,9 +17,9 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.assertIsChecked
import org.mozilla.fenix.helpers.click

@ -6,6 +6,7 @@ package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.uiautomator.UiSelector
@ -82,6 +83,16 @@ class SitePermissionsRobot {
}
}
fun verifyCrossOriginCookiesPermissionPrompt(originSite: String, currentSite: String) {
mDevice.findObject(UiSelector().text("Allow $originSite to use its cookies on $currentSite?"))
.waitForExists(waitingTime)
onView(ViewMatchers.withText("Allow $originSite to use its cookies on $currentSite?")).check(matches(isDisplayed()))
onView(ViewMatchers.withText("You may want to block access if it's not clear why $originSite needs this data.")).check(matches(isDisplayed()))
onView(ViewMatchers.withText("Learn more")).check(matches(isDisplayed()))
onView(ViewMatchers.withText("Block")).check(matches(isDisplayed()))
onView(ViewMatchers.withText("Allow")).check(matches(isDisplayed()))
}
fun selectRememberPermissionDecision() {
onView(withId(R.id.do_not_ask_again))
.check(matches(isDisplayed()))

@ -33,17 +33,18 @@ import androidx.test.uiautomator.Until
import androidx.test.uiautomator.Until.findObject
import com.google.android.material.bottomsheet.BottomSheetBehavior
import junit.framework.AssertionFailedError
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.Matcher
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.clickAtLocationInView
@ -78,6 +79,7 @@ class TabDrawerRobot {
assertSyncedTabsButtonIsSelected(isSelected)
fun verifyExistingOpenTabs(vararg titles: String) = assertExistingOpenTabs(*titles)
fun verifyNoExistingOpenTabs(vararg titles: String) = assertNoExistingOpenTabs(*titles)
fun verifyCloseTabsButton(title: String) = assertCloseTabsButton(title)
fun verifyExistingTabList() = assertExistingTabList()
@ -338,19 +340,19 @@ class TabDrawerRobot {
return BrowserRobot.Transition()
}
fun openTabFromGroup(
title: String,
// Temporary method to use indexes instead of tab titles, until the compose migration is complete
fun openTabWithIndex(
tabPosition: Int,
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
val tab = UiScrollable(UiSelector().resourceId("$packageName:id/tab_group_list"))
.setAsHorizontalList()
.getChildByText(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.textContains(title),
title,
true,
)
val tab = mDevice.findObject(
UiSelector()
.className("androidx.compose.ui.platform.ComposeView")
.index(tabPosition),
)
UiScrollable(UiSelector().resourceId("$packageName:id/tray_list_item")).scrollIntoView(tab)
tab.waitForExists(waitingTime)
tab.click()
BrowserRobot().interact()
@ -451,7 +453,7 @@ private fun tabMediaControlButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/play_pause_button"))
private fun closeTabButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_tabstray_close"))
mDevice.findObject(UiSelector().descriptionContains("Close tab"))
private fun assertCloseTabsButton(title: String) =
assertTrue(
@ -490,6 +492,14 @@ private fun assertExistingOpenTabs(vararg tabTitles: String) {
}
}
private fun assertNoExistingOpenTabs(vararg tabTitles: String) {
for (title in tabTitles) {
assertFalse(
tabItem(title).waitForExists(waitingTimeLong),
)
}
}
private fun assertExistingTabList() {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tabsTray"),
@ -497,7 +507,7 @@ private fun assertExistingTabList() {
assertTrue(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_item"),
UiSelector().resourceId("$packageName:id/tray_list_item"),
).waitForExists(waitingTime),
)
}
@ -623,8 +633,7 @@ private fun tab(title: String) =
private fun tabItem(title: String) =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/tab_item")
.childSelector(UiSelector().text(title)),
.textContains(title),
)
private fun tabsCounter() = onView(withId(R.id.tab_button))

@ -52,6 +52,20 @@ class ThreeDotMenuBookmarksRobot {
return TabDrawerRobot.Transition()
}
fun clickOpenAllInTabs(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
openAllInTabsButton().click()
TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
}
fun clickOpenAllInPrivateTabs(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
openAllInPrivateTabsButton().click()
TabDrawerRobot().interact()
return TabDrawerRobot.Transition()
}
fun clickDelete(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
deleteButton().click()
@ -71,4 +85,8 @@ private fun openInNewTabButton() = onView(withText("Open in new tab"))
private fun openInPrivateTabButton() = onView(withText("Open in private tab"))
private fun openAllInTabsButton() = onView(withText("Open all in new tabs"))
private fun openAllInPrivateTabsButton() = onView(withText("Open all in private tabs"))
private fun deleteButton() = onView(withText("Delete"))

@ -50,7 +50,7 @@ class ThreeDotMenuMainRobot {
fun verifyAddOnsButton() = assertAddOnsButton()
fun verifyHistoryButton() = assertHistoryButton()
fun verifyBookmarksButton() = assertBookmarksButton()
fun verifySyncSignInButton() = assertSignInToSyncButton()
fun verifySyncSignInButton() = assertSyncSignInButton()
fun verifyHelpButton() = assertHelpButton()
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
fun verifyForwardButton() = assertForwardButton()
@ -77,7 +77,6 @@ class ThreeDotMenuMainRobot {
fun verifyDesktopSite() = assertDesktopSite()
fun verifyDownloadsButton() = assertDownloadsButton()
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun verifySignInToSyncButton() = assertSignInToSyncButton()
fun verifyNewTabButton() = assertNormalBrowsingNewTabButton()
fun verifyReportSiteIssueButton() = assertReportSiteIssueButton()
@ -97,7 +96,7 @@ class ThreeDotMenuMainRobot {
verifyHistoryButton()
verifyDownloadsButton()
verifyAddOnsButton()
verifySignInToSyncButton()
verifySyncSignInButton()
threeDotMenuRecyclerView().perform(swipeUp())
verifyFindInPageButton()
verifyDesktopSite()
@ -173,8 +172,8 @@ class ThreeDotMenuMainRobot {
fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
threeDotMenuRecyclerView().perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Sign in to sync")), waitingTime)
signInToSyncButton().click()
mDevice.waitNotNull(Until.findObject(By.text("Sync and save data")), waitingTime)
syncSignInButton().click()
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
@ -208,6 +207,14 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition()
}
fun editBookmarkPage(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
editBookmarkButton().click()
BookmarksRobot().interact()
return BookmarksRobot.Transition()
}
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
helpButton().click()
@ -440,8 +447,8 @@ private fun bookmarksButton() = onView(allOf(withText(R.string.library_bookmarks
private fun assertBookmarksButton() = bookmarksButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun signInToSyncButton() = onView(withText("Sign in to sync"))
private fun assertSignInToSyncButton() = signInToSyncButton().check(matches(isDisplayed()))
private fun syncSignInButton() = onView(withText("Sync and save data"))
private fun assertSyncSignInButton() = syncSignInButton().check(matches(isDisplayed()))
private fun helpButton() = onView(allOf(withText(R.string.browser_menu_help)))
private fun assertHelpButton() = helpButton()

@ -8,6 +8,7 @@ const val STRING_ONBOARDING_ACCOUNT_SIGN_IN_HEADER = "Pick up where you left off
const val STRING_ONBOARDING_TRACKING_PROTECTION_HEADER = "Privacy protection by default"
const val STRING_ONBOARDING_TOOLBAR_PLACEMENT_HEADER = "Pick your toolbar placement"
const val FRENCH_LANGUAGE_HEADER = "Langues"
const val ROMANIAN_LANGUAGE_HEADER = "Limbă"
const val ARABIC_LANGUAGE_HEADER = "اللغة"
const val FR_SETTINGS = "Paramètres"
const val FRENCH_SYSTEM_LOCALE_OPTION = "Utiliser la langue de lappareil"
const val ROMANIAN_LANGUAGE_HEADER = "Limbă"

@ -19,6 +19,9 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<!-- Needed for Google Play policy https://support.google.com/googleplay/android-developer/answer/6048248 -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
<!-- Needed to prompt the user to give permission to install a downloaded apk -->
<uses-permission-sdk-23 android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

@ -12,6 +12,14 @@ import mozilla.components.support.locale.LocaleManager.getSystemDefault
* A single source for setting feature flags that are mostly based on build type.
*/
object FeatureFlags {
/**
* Enables custom extension collection feature,
* This feature does not only depend on this flag. It requires the AMO collection override to
* be enabled which is behind the Secret Settings.
* */
val customExtensionCollectionFeature = Config.channel.isNightlyOrDebug || Config.channel.isBeta
/**
* Pull-to-refresh allows you to pull the web content down far enough to have the page to
* reload.
@ -58,11 +66,6 @@ object FeatureFlags {
*/
const val inactiveTabs = true
/**
* Allows tabs to be dragged around as long as tab groups are disabled
*/
val tabReorderingFeature = Config.channel.isNightlyOrDebug
/**
* Show Pocket recommended stories on home.
*/
@ -84,11 +87,6 @@ object FeatureFlags {
*/
const val showHomeOnboarding = true
/**
* Enables history improvement features.
*/
const val historyImprovementFeatures = true
/**
* Enables the Task Continuity enhancements.
*/
@ -118,4 +116,16 @@ object FeatureFlags {
* Enables the wallpaper v2 enhancements.
*/
const val wallpaperV2Enabled = true
/**
* Enables the save to PDF feature.
*/
const val saveToPDF = true
/**
* Enables storage maintenance feature.
*
* Feature flag tracking: https://github.com/mozilla-mobile/fenix/issues/27759
* */
val storageMaintenanceFeature = Config.channel.isNightlyOrDebug
}

@ -14,6 +14,7 @@ import android.util.Log.INFO
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Configuration.Builder
@ -28,6 +29,7 @@ import mozilla.appservices.Megazord
import mozilla.components.browser.state.action.SystemAction
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.storage.sync.GlobalPlacesDependencyProvider
import mozilla.components.concept.base.crash.Breadcrumb
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.concept.engine.webextension.isUnsupported
@ -75,8 +77,10 @@ import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.containsQueryParameters
import org.mozilla.fenix.ext.getCustomGleanServerUrlIfAvailable
import org.mozilla.fenix.ext.isCustomEngine
import org.mozilla.fenix.ext.isKnownSearchDomain
import org.mozilla.fenix.ext.setCustomEndpointIfAvailable
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
@ -93,7 +97,7 @@ import org.mozilla.fenix.telemetry.TelemetryLifecycleObserver
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
import org.mozilla.fenix.wallpapers.WallpaperManager
import org.mozilla.fenix.wallpapers.Wallpaper
import java.util.UUID
import java.util.concurrent.TimeUnit
@ -154,14 +158,24 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
logger.debug("Initializing Glean (uploadEnabled=$telemetryEnabled})")
// for performance reasons, this is only available in Nightly or Debug builds
val customEndpoint = if (Config.channel.isNightlyOrDebug) {
// for testing, if custom glean server url is set in the secret menu, use it to initialize Glean
getCustomGleanServerUrlIfAvailable(this)
} else {
null
}
val configuration = Configuration(
channel = BuildConfig.BUILD_TYPE,
httpClient = ConceptFetchHttpUploader(
lazy(LazyThreadSafetyMode.NONE) { components.core.client },
),
)
Glean.initialize(
applicationContext = this,
configuration = Configuration(
channel = BuildConfig.BUILD_TYPE,
httpClient = ConceptFetchHttpUploader(
lazy(LazyThreadSafetyMode.NONE) { components.core.client },
),
),
configuration = configuration.setCustomEndpointIfAvailable(customEndpoint),
uploadEnabled = telemetryEnabled,
buildInfo = GleanBuildInfo.buildInfo,
)
@ -198,6 +212,14 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
components.core.engine.warmUp()
}
initializeWebExtensionSupport()
if (FeatureFlags.storageMaintenanceFeature) {
// Make sure to call this function before registering a storage worker
// (e.g. components.core.historyStorage.registerStorageMaintenanceWorker())
// as the storage maintenance worker needs a places storage globally when
// it is needed while the app is not running and WorkManager wakes up the app
// for the periodic task.
GlobalPlacesDependencyProvider.initialize(components.core.historyStorage)
}
restoreBrowserState()
restoreDownloads()
@ -217,12 +239,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
registerActivityLifecycleCallbacks(visibilityLifecycleCallback)
registerActivityLifecycleCallbacks(MarkersActivityLifecycleCallbacks(components.core.engine))
// Storage maintenance disabled, for now, as it was interfering with background migrations.
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
// if ((System.currentTimeMillis() - settings().lastPlacesStorageMaintenance) > ONE_DAY_MILLIS) {
// runStorageMaintenance()
// }
components.appStartReasonProvider.registerInAppOnCreate(this)
components.startupActivityLog.registerInAppOnCreate(this)
initVisualCompletenessQueueAndQueueTasks()
@ -331,6 +347,18 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
}
fun queueStorageMaintenance() {
if (FeatureFlags.storageMaintenanceFeature) {
queue.runIfReadyOrQueue {
// Make sure GlobalPlacesDependencyProvider.initialize(components.core.historyStorage)
// is called before this call. When app is not running and WorkManager wakes up
// the app for the periodic task, it will require a globally provided places storage
// to run the maintenance on.
components.core.historyStorage.registerStorageMaintenanceWorker()
}
}
}
initQueue()
// We init these items in the visual completeness queue to avoid them initing in the critical
@ -339,6 +367,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
queueMetrics()
queueReviewPrompt()
queueRestoreLocale()
queueStorageMaintenance()
}
private fun startMetricsIfEnabled() {
@ -351,20 +380,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
}
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
// To re-enable this, we need to do so in a way that won't interfere with any startup operations
// which acquire reserved+ sqlite lock. Currently, Fennec migrations need to write to storage
// on startup, and since they run in a background service we can't simply order these operations.
// @OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
// private fun runStorageMaintenance() {
// GlobalScope.launch(Dispatchers.IO) {
// // Bookmarks and history storage sit on top of the same db file so we only need to
// // run maintenance on one - arbitrarily using bookmarks.
// // components.core.bookmarksStorage.runMaintenance()
// }
// settings().lastPlacesStorageMaintenance = System.currentTimeMillis()
// }
protected open fun setupLeakCanary() {
// no-op, LeakCanary is disabled by default
}
@ -711,7 +726,19 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
installSource.set(installSourcePackage.orEmpty())
defaultWallpaper.set(WallpaperManager.isDefaultTheCurrentWallpaper(settings))
val isDefaultTheCurrentWallpaper =
Wallpaper.nameIsDefault(settings.currentWallpaperName)
defaultWallpaper.set(isDefaultTheCurrentWallpaper)
@Suppress("TooGenericExceptionCaught")
try {
notificationsAllowed.set(
NotificationManagerCompat.from(applicationContext).areNotificationsEnabled(),
)
} catch (e: Exception) {
Logger.warn("Failed to check if notifications are enabled", e)
}
}
with(AndroidAutofill) {

@ -25,7 +25,7 @@ import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.annotation.CallSuper
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PROTECTED
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
import androidx.appcompat.app.ActionBar
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope
@ -78,6 +78,7 @@ import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.databinding.ActivityHomeBinding
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
@ -370,6 +371,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
Events.defaultBrowserChanged.record(NoExtras())
}
// We attempt to send metrics onResume so that the start of new user sessions is not
// missed. Previously, this was done in FenixApplication::onCreate, but it was decided
// that we should not rely on the application being killed between user sessions.
components.appStore.dispatch(AppAction.ResumedMetricsAction)
DefaultBrowserNotificationWorker.setDefaultBrowserNotificationIfNeeded(applicationContext)
}
}

@ -10,10 +10,10 @@ import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.CustomTabListAction
import mozilla.components.browser.state.state.createCustomTab
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.browser.state.state.EngineState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.createCustomTab
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.window.WindowRequest

@ -40,9 +40,9 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.databinding.FragmentAddOnsManagementBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.theme.ThemeManager
import java.lang.ref.WeakReference
import java.util.concurrent.CancellationException

@ -8,6 +8,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
@ -17,7 +18,9 @@ import androidx.navigation.fragment.findNavController
import com.google.android.material.switchmaterial.SwitchMaterial
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.webextension.EnableSource
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.ui.translateName
import org.mozilla.fenix.HomeActivity
@ -32,7 +35,8 @@ import org.mozilla.fenix.ext.showToolbar
*/
@Suppress("LargeClass", "TooManyFunctions")
class InstalledAddonDetailsFragment : Fragment() {
private lateinit var addon: Addon
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal lateinit var addon: Addon
private var _binding: FragmentInstalledAddOnDetailsBinding? = null
private val binding get() = _binding!!
@ -124,8 +128,8 @@ class InstalledAddonDetailsFragment : Fragment() {
switch.isClickable = false
binding.removeAddOn.isEnabled = false
if (isChecked) {
addonManager.enableAddon(
addon,
enableAddon(
addonManager,
onSuccess = {
runIfFragmentIsAttached {
this.addon = it
@ -207,6 +211,28 @@ class InstalledAddonDetailsFragment : Fragment() {
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun enableAddon(
addonManager: AddonManager,
onSuccess: (Addon) -> Unit,
onError: (Throwable) -> Unit,
) {
// If the addon is migrated from Fennec and supported in Fenix, for the addon to be enabled,
// we need to also request the addon to be enabled as supported by the app
if (addon.isSupported() && addon.isDisabledAsUnsupported()) {
addonManager.enableAddon(
addon,
EnableSource.APP_SUPPORT,
{ enabledAddon ->
addonManager.enableAddon(enabledAddon, EnableSource.USER, onSuccess, onError)
},
onError,
)
} else {
addonManager.enableAddon(addon, EnableSource.USER, onSuccess, onError)
}
}
private fun bindSettings() {
binding.settings.apply {
isVisible = shouldSettingsBeVisible()

@ -39,7 +39,7 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.appservices.places.uniffi.PlacesException
import mozilla.appservices.places.uniffi.PlacesApiException
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findCustomTab
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
@ -54,6 +54,7 @@ import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.concept.engine.permission.SitePermissions
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.accounts.FxaCapability
import mozilla.components.feature.accounts.FxaWebChannelFeature
@ -68,31 +69,38 @@ import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
import mozilla.components.feature.privatemode.feature.SecureWindowFeature
import mozilla.components.feature.prompts.PromptFeature
import mozilla.components.feature.prompts.PromptFeature.Companion.PIN_REQUEST
import mozilla.components.feature.prompts.address.AddressDelegate
import mozilla.components.feature.prompts.creditcard.CreditCardDelegate
import mozilla.components.feature.prompts.login.LoginDelegate
import mozilla.components.feature.prompts.share.ShareDelegate
import mozilla.components.feature.readerview.ReaderViewFeature
import mozilla.components.feature.search.SearchFeature
import mozilla.components.feature.session.FullScreenFeature
import mozilla.components.feature.session.PictureInPictureFeature
import mozilla.components.feature.session.ScreenOrientationFeature
import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SwipeRefreshFeature
import mozilla.components.concept.engine.permission.SitePermissions
import mozilla.components.feature.prompts.address.AddressDelegate
import mozilla.components.feature.prompts.creditcard.CreditCardDelegate
import mozilla.components.feature.prompts.login.LoginDelegate
import mozilla.components.feature.session.ScreenOrientationFeature
import mozilla.components.feature.session.behavior.EngineViewBrowserToolbarBehavior
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
import mozilla.components.feature.webauthn.WebAuthnFeature
import mozilla.components.lib.state.ext.consumeFlow
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.service.sync.autofill.DefaultCreditCardValidationDelegate
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.view.enterToImmersiveMode
import mozilla.components.support.ktx.android.view.exitImmersiveMode
import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.kotlin.getOrigin
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.MediaState
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.NavGraphDirections
@ -109,6 +117,10 @@ import org.mozilla.fenix.components.toolbar.BrowserToolbarView
import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController
import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor
import org.mozilla.fenix.crashes.CrashContentIntegration
import org.mozilla.fenix.databinding.FragmentBrowserBinding
import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.downloads.DynamicDownloadDialog
import org.mozilla.fenix.ext.accessibilityManager
@ -118,32 +130,19 @@ import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.hideToolbar
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.secure
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeScreenViewModel
import org.mozilla.fenix.home.SharedViewModel
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.biometric.BiometricPromptFeature
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.allowUndo
import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration
import java.lang.ref.WeakReference
import mozilla.components.feature.session.behavior.EngineViewBrowserToolbarBehavior
import mozilla.components.feature.webauthn.WebAuthnFeature
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.service.sync.autofill.DefaultCreditCardValidationDelegate
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.ktx.android.view.enterToImmersiveMode
import mozilla.components.support.ktx.kotlin.getOrigin
import org.mozilla.fenix.GleanMetrics.Downloads
import org.mozilla.fenix.GleanMetrics.MediaState
import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor
import org.mozilla.fenix.crashes.CrashContentIntegration
import org.mozilla.fenix.databinding.FragmentBrowserBinding
import org.mozilla.fenix.ext.secure
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.settings.biometric.BiometricPromptFeature
import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition
/**
@ -529,10 +528,6 @@ abstract class BaseBrowserFragment :
dynamicDownloadDialog.show()
browserToolbarView.expand()
if (downloadState.contentType == "application/pdf") {
Downloads.pdfDownloadCount.add()
}
}
}
@ -1321,7 +1316,7 @@ abstract class BaseBrowserFragment :
.show()
}
}
} catch (e: PlacesException.UrlParseFailed) {
} catch (e: PlacesApiException.UrlParseFailed) {
withContext(Main) {
view?.let {
FenixSnackbar.make(

@ -38,7 +38,6 @@ class OpenInAppOnboardingObserver(
private val settings: Settings,
private val appLinksUseCases: AppLinksUseCases,
private val container: ViewGroup,
@VisibleForTesting
internal val shouldScrollWithTopToolbar: Boolean = false,
) : LifecycleAwareFeature {
private var scope: CoroutineScope? = null

@ -106,8 +106,8 @@ class SwipeGestureLayout @JvmOverloads constructor(
listeners.add(listener)
}
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
return when (event?.actionMasked) {
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
handledInitialScroll = false
gestureDetector.onTouchEvent(event)
@ -117,8 +117,8 @@ class SwipeGestureLayout @JvmOverloads constructor(
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return when (event?.actionMasked) {
override fun onTouchEvent(event: MotionEvent): Boolean {
return when (event.actionMasked) {
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
gestureDetector.onTouchEvent(event)
// If the active listener is not null here, then we haven't detected a fling

@ -6,7 +6,6 @@ package org.mozilla.fenix.browser.infobanner
import android.content.Context
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
/**
@ -19,7 +18,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
class DynamicInfoBanner(
private val context: Context,
container: ViewGroup,
@VisibleForTesting
internal val shouldScrollWithTopToolbar: Boolean = false,
message: String,
dismissText: String,

@ -32,10 +32,8 @@ open class InfoBanner(
private val dismissText: String,
private val actionText: String? = null,
private val dismissByHiding: Boolean = false,
@VisibleForTesting
internal val dismissAction: (() -> Unit)? = null,
@VisibleForTesting
internal val actionToPerform: (() -> Unit)? = null,
private val actionToPerform: (() -> Unit)? = null,
) {
@SuppressLint("InflateParams")
@VisibleForTesting

@ -10,10 +10,10 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.crash.sentry.SentryService
import mozilla.components.lib.crash.service.CrashReporterService
import mozilla.components.lib.crash.service.GleanCrashReporterService
import mozilla.components.lib.crash.service.MozillaSocorroService
import mozilla.components.lib.crash.sentry.SentryService
import mozilla.components.service.nimbus.NimbusApi
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
@ -22,15 +22,17 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ReleaseChannel
import org.mozilla.fenix.components.metrics.AdjustMetricsService
import org.mozilla.fenix.components.metrics.DefaultMetricsStorage
import org.mozilla.fenix.components.metrics.GleanMetricsService
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.experiments.createNimbus
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.CustomAttributeProvider
import org.mozilla.fenix.gleanplumb.OnDiskMessageMetadataStorage
import org.mozilla.fenix.gleanplumb.NimbusMessagingStorage
import org.mozilla.fenix.gleanplumb.OnDiskMessageMetadataStorage
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION
@ -119,7 +121,15 @@ class Analytics(
MetricController.create(
listOf(
GleanMetricsService(context),
AdjustMetricsService(context as Application),
AdjustMetricsService(
application = context as Application,
storage = DefaultMetricsStorage(
context = context,
settings = context.settings(),
checkDefaultBrowser = { BrowsersCache.all(context).isDefaultBrowser },
),
crashReporter = crashReporter,
),
),
isDataTelemetryEnabled = { context.settings().isTelemetryEnabled },
isMarketingDataTelemetryEnabled = { context.settings().isMarketingTelemetryEnabled },

@ -7,7 +7,7 @@ package org.mozilla.fenix.components
import android.content.Context
import android.os.Build
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
@ -183,6 +183,7 @@ class BackgroundServices(
// Enable push if it's configured.
push.feature?.let { autoPushFeature ->
FxaPushSupportFeature(context, accountManager, autoPushFeature, crashReporter)
.initialize()
}
SendTabFeature(accountManager) { device, tabs ->

@ -4,13 +4,11 @@
package org.mozilla.fenix.components
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.Intent
import android.os.StrictMode
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import com.google.android.play.core.review.ReviewManagerFactory
import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
@ -21,19 +19,20 @@ import mozilla.components.support.base.worker.Frequency
import io.github.forkmaintainers.iceraven.components.PagedAddonCollectionProvider
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.autofill.AutofillConfirmActivity
import org.mozilla.fenix.autofill.AutofillSearchActivity
import org.mozilla.fenix.autofill.AutofillUnlockActivity
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.metrics.MetricsMiddleware
import org.mozilla.fenix.datastore.pocketStoriesSelectedCategoriesDataStore
import org.mozilla.fenix.ext.asRecentTabs
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.filterState
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.state.MessagingMiddleware
import org.mozilla.fenix.ext.sort
import org.mozilla.fenix.gleanplumb.state.MessagingMiddleware
import org.mozilla.fenix.home.PocketUpdatesMiddleware
import org.mozilla.fenix.home.blocklist.BlocklistHandler
import org.mozilla.fenix.home.blocklist.BlocklistMiddleware
@ -44,7 +43,6 @@ import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wallpapers.WallpaperManager
import org.mozilla.fenix.wifi.WifiConnectionMonitor
import java.util.concurrent.TimeUnit
@ -104,12 +102,32 @@ class Components(private val context: Context) {
}
val addonCollectionProvider by lazyMonitored {
PagedAddonCollectionProvider(
context,
core.client,
serverURL = BuildConfig.AMO_SERVER_URL,
maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE,
)
// Check if we have a customized (overridden) AMO collection (supported in Nightly & Beta)
if (FeatureFlags.customExtensionCollectionFeature && context.settings().amoCollectionOverrideConfigured()) {
AddonCollectionProvider(
context,
core.client,
collectionUser = context.settings().overrideAmoUser,
collectionName = context.settings().overrideAmoCollection,
)
}
// Use build config otherwise
else if (!BuildConfig.AMO_COLLECTION_USER.isNullOrEmpty() &&
!BuildConfig.AMO_COLLECTION_NAME.isNullOrEmpty()
) {
AddonCollectionProvider(
context,
core.client,
serverURL = BuildConfig.AMO_SERVER_URL,
collectionUser = BuildConfig.AMO_COLLECTION_USER,
collectionName = BuildConfig.AMO_COLLECTION_NAME,
maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE,
)
}
// Fall back to defaults
else {
AddonCollectionProvider(context, core.client, maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE)
}
}
@Suppress("MagicNumber")
@ -122,11 +140,6 @@ class Components(private val context: Context) {
DefaultSupportedAddonsChecker(
context,
Frequency(12, TimeUnit.HOURS),
onNotificationClickIntent = Intent(context, HomeActivity::class.java).apply {
action = Intent.ACTION_VIEW
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
data = "${BuildConfig.DEEP_LINK_SCHEME}://settings_addon_manager".toUri()
},
)
}
@ -146,14 +159,6 @@ class Components(private val context: Context) {
val wifiConnectionMonitor by lazyMonitored { WifiConnectionMonitor(context as Application) }
val strictMode by lazyMonitored { StrictModeManager(Config, this) }
val wallpaperManager by lazyMonitored {
strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
WallpaperManager(
appStore,
)
}
}
val settings by lazyMonitored { Settings(context) }
val reviewPromptController by lazyMonitored {
@ -163,6 +168,7 @@ class Components(private val context: Context) {
)
}
@delegate:SuppressLint("NewApi")
val autofillConfiguration by lazyMonitored {
AutofillConfiguration(
storage = core.passwordsStorage,
@ -206,6 +212,7 @@ class Components(private val context: Context) {
context.pocketStoriesSelectedCategoriesDataStore,
),
MessagingMiddleware(messagingStorage = analytics.messagingStorage),
MetricsMiddleware(metrics = analytics.metrics),
),
)
}

@ -92,6 +92,7 @@ import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.advanced.getSelectedLocale
import org.mozilla.fenix.share.SaveToPDFMiddleware
import org.mozilla.fenix.telemetry.TelemetryMiddleware
import org.mozilla.fenix.utils.getUndoDelay
import org.mozilla.geckoview.GeckoRuntime
@ -229,7 +230,7 @@ class Core(
RecentlyClosedMiddleware(recentlyClosedTabsStorage, RECENTLY_CLOSED_MAX),
DownloadMiddleware(context, DownloadService::class.java),
ReaderViewMiddleware(),
TelemetryMiddleware(context.settings()),
TelemetryMiddleware(context.settings(), metrics),
ThumbnailsMiddleware(thumbnailStorage),
UndoMiddleware(context.getUndoDelay()),
RegionMiddleware(context, locationService),
@ -244,6 +245,7 @@ class Core(
LastMediaAccessMiddleware(),
HistoryMetadataMiddleware(historyMetadataService),
SessionPrioritizationMiddleware(),
SaveToPDFMiddleware(context),
)
BrowserStore(

@ -5,7 +5,6 @@
package org.mozilla.fenix.components
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.paging.DataSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -16,8 +15,8 @@ import kotlin.coroutines.CoroutineContext
class PermissionStorage(
private val context: Context,
@VisibleForTesting internal val dispatcher: CoroutineContext = Dispatchers.IO,
@VisibleForTesting internal val permissionsStorage: SitePermissionsStorage =
private val dispatcher: CoroutineContext = Dispatchers.IO,
internal val permissionsStorage: SitePermissionsStorage =
context.components.core.geckoSitePermissionsStorage,
) {

@ -7,8 +7,8 @@ package org.mozilla.fenix.components
import android.content.Context
import androidx.core.net.toUri
import mozilla.components.feature.push.AutoPushFeature
import mozilla.components.feature.push.PushConfig
import mozilla.components.feature.push.Protocol
import mozilla.components.feature.push.PushConfig
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.R

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save