Merge pull request #431 from fork-maintainers/upstream-sync

Upstream sync with Mozilla Firefox v96.3.0
fork-history iceraven-1.15.0
interfect 2 years ago committed by GitHub
commit 28893ceabb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,7 +2,7 @@
name: "⌛ Performance issue"
about: Create a performance issue if the app is slow or it uses too much memory, disk space, battery, or network data
title: ""
labels: "eng:performance"
labels: "performance"
assignees: ''
---

@ -107,7 +107,7 @@ jobs:
run-ui:
runs-on: macos-11
if: ${{ false }} # disable for now'
if: ${{ false }}
timeout-minutes: 60
strategy:
@ -118,12 +118,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Run subset of UI Tests
uses: reactivecircus/android-emulator-runner@v2
uses: reactivecircus/android-emulator-runner@v2.21.0
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: pixel_3a
profile: pixel_2
script:
"JAVA_HOME=$JAVA_HOME_11_X64 && ./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=\
org.mozilla.fenix.ui.NavigationToolbarTest#visitURLTest"

@ -1,3 +1,7 @@
queue_rules:
- name: default
conditions:
- status-success=pr-complete
pull_request_rules:
- name: Resolve conflict
conditions:
@ -14,9 +18,10 @@ pull_request_rules:
review:
type: APPROVE
message: MickeyMoz 💪
merge:
queue:
method: rebase
strict: smart
name: default
rebase_fallback: none
- name: L10N - Auto Merge
conditions:
- author=mozilla-l10n-automation-bot
@ -26,9 +31,10 @@ pull_request_rules:
review:
type: APPROVE
message: LGTM 😎
merge:
queue:
method: rebase
strict: smart
name: default
rebase_fallback: none
- name: Release automation (Old)
conditions:
- base~=releases[_/].*
@ -52,9 +58,10 @@ pull_request_rules:
review:
type: APPROVE
message: 🚢
merge:
queue:
method: rebase
strict: smart
name: default
rebase_fallback: none
delete_head_branch:
force: false
- name: Release automation (New)
@ -81,9 +88,10 @@ pull_request_rules:
review:
type: APPROVE
message: 🚢
merge:
queue:
method: rebase
strict: smart
name: default
rebase_fallback: none
delete_head_branch:
force: false
- name: Needs landing - Rebase
@ -95,9 +103,10 @@ pull_request_rules:
- label!=pr:work-in-progress
- label!=pr:do-not-land
actions:
merge:
queue:
method: rebase
strict: smart
name: default
rebase_fallback: none
- name: Needs landing - Squash
conditions:
- check-success=pr-complete
@ -107,6 +116,7 @@ pull_request_rules:
- label!=pr:work-in-progress
- label!=pr:do-not-land
actions:
merge:
queue:
method: squash
strict: smart
name: default
rebase_fallback: none

@ -8,7 +8,7 @@ tasks:
- $let:
taskgraph:
branch: taskgraph
revision: 9daff451cfbe82c5c70237a7b3dbcf4fd3238299
revision: d85f4e4213706ec7737c7257f681977fdf20eb60
trustDomain: mobile
in:
$let:
@ -104,7 +104,6 @@ tasks:
tasks_for in ["action", "cron"]
|| (tasks_for == "github-pull-request" && pullRequestAction in ["opened", "reopened", "synchronize"])
|| (tasks_for == "github-push" && head_branch[:10] != "refs/tags/") && (head_branch != "staging.tmp") && (head_branch != "trying.tmp") && (head_branch[:8] != "mergify/")
|| (tasks_for == "github-release" && releaseAction == "published" && (ownerEmail != "mozilla-release-automation-bot@users.noreply.github.com") && (ownerEmail != "mozilla-release-automation-bot-staging@users.noreply.github.com"))
then:
$let:
level:
@ -249,9 +248,9 @@ tasks:
command:
- /usr/local/bin/run-task
- '--mobile-checkout=/builds/worker/checkouts/src'
- '--mobile-checkout=/builds/worker/checkouts/vcs'
- '--taskgraph-checkout=/builds/worker/checkouts/taskgraph'
- '--task-cwd=/builds/worker/checkouts/src'
- '--task-cwd=/builds/worker/checkouts/vcs'
- '--'
- bash
- -cx
@ -293,7 +292,7 @@ tasks:
expires: {$fromNow: '1 year'}
'public/docker-contexts':
type: 'directory'
path: '/builds/worker/checkouts/src/docker-contexts'
path: '/builds/worker/checkouts/vcs/docker-contexts'
# This needs to be at least the deadline of the
# decision task + the docker-image task deadlines.
# It is set to a week to allow for some time for

@ -53,14 +53,29 @@ ext.maybeConfigForJetpackBenchmark = { android ->
// WARNING: the benchmark framework warns you if you're running the test in a configuration
// that will compromise the accuracy of the results. Unfortunately, I couldn't get everything
// working so I had to suppress some things.
//
// - ACTIVITY-MISSING: we're supposed to use the test instrumentation runner,
// "androidx.benchmark.junit4.AndroidBenchmarkRunner". However, when I do so, I get an error
// that we're unable to launch the activity. My understanding is that this runner will use an
// "IsolationActivity" to reduce the impact of other work on the device from affecting the benchmark
// and to opt into a lower-max CPU frequency on unrooted devices that support it
// - UNLOCKED: ./gradlew lockClocks, which locks the CPU frequency, fails on my device. See
// https://issuetracker.google.com/issues/176836267 for potential workarounds.
testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'ACTIVITY-MISSING,UNLOCKED'
testInstrumentationRunnerArguments = [
// - ACTIVITY-MISSING: we're supposed to use the test instrumentation runner,
// "androidx.benchmark.junit4.AndroidBenchmarkRunner". However, when I do so, I get an error
// that we're unable to launch the activity. My understanding is that this runner will use an
// "IsolationActivity" to reduce the impact of other work on the device from affecting the benchmark
// and to opt into a lower-max CPU frequency on unrooted devices that support it
// - UNLOCKED: ./gradlew lockClocks, which locks the CPU frequency, fails on my device. See
// https://issuetracker.google.com/issues/176836267 for potential workarounds.
'androidx.benchmark.suppressErrors' : 'ACTIVITY-MISSING,UNLOCKED',
// The tests don't always output a JSON file with the data. To make sure it does, we have to
// set androidx.benchmark.output.enable to true.
'androidx.benchmark.output.enable' : 'true',
// We set the the output directory simply for simplicity since the benchmark_runner.py script
// can't know the name of the phone in the /build/outputs/ directory. The system defaults to
// {phone_name} which can be troublesome finding in some case.
//
// NOTE: Jetpack Benchmark outputs to Logcat too. However, the output in the logcat is
// the min of the several repeats, for more statistics. Therefore, to get more stats,
// we refer to the JSON file.
additionalTestOutputDir : '/storage/emulated/0/benchmark'
]
}
}

@ -1,3 +1,5 @@
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"
@ -42,6 +44,7 @@ android {
testInstrumentationRunnerArguments clearPackageData: 'true'
resValue "bool", "IS_DEBUG", "false"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "false"
buildConfigField "String", "GIT_HASH", "\"\"" // see override in release builds for why it's blank.
// This should be the "public" base URL of AMO.
buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\""
buildConfigField "String", "AMO_COLLECTION_NAME", "\"7dfae8669acc4312a65e8ba5553036\""
@ -83,6 +86,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs)
// Changing the build config can cause files that depend on BuildConfig.java to recompile
// so we only set the git hash in release builds to avoid possible recompilation in debug builds.
buildConfigField "String", "GIT_HASH", "\"${Config.getGitHash()}\""
if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
signingConfig signingConfigs.debug
}
@ -271,6 +278,7 @@ android {
composeOptions {
kotlinCompilerExtensionVersion = Versions.androidx_compose
}
}
// -------------------------------------------------------------------------------------------------
@ -447,6 +455,12 @@ configurations {
jnaForTest
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
}
}
dependencies {
jnaForTest Deps.jna
testImplementation files(configurations.jnaForTest.copyRecursive().files)
@ -631,8 +645,14 @@ dependencies {
}
protobuf {
// Mac M1 workaround until we can bump the version. Dependent on A-S.
// See https://github.com/mozilla-mobile/fenix/issues/22321
protoc {
artifact = Deps.protobuf_compiler
if (osdetector.os == "osx") {
artifact = "${Deps.protobuf_compiler}:osx-x86_64"
} else {
artifact = Deps.protobuf_compiler
}
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
@ -824,3 +844,11 @@ ext.updateExtensionVersion = { task, extDir ->
expand(values)
}
}
android.applicationVariants.all { variant ->
tasks.register("apkSize${variant.name.capitalize()}", ApkSizeTask) {
variantName = variant.name
apks = variant.outputs.collect { output -> output.outputFile.name }
dependsOn "package${variant.name.capitalize()}"
}
}

File diff suppressed because it is too large Load Diff

@ -302,30 +302,6 @@ events:
- android-probes@mozilla.com
- erichards@mozilla.com
expires: never
tab_counter_menu_action:
type: event
description:
A tab counter menu item was tapped
extra_keys:
item:
description: |
A string containing the name of the item the user tapped. These items
are:
New tab, New private tab, Close tab
bugs:
- https://github.com/mozilla-mobile/fenix/issues/11442
- https://github.com/mozilla-mobile/fenix/issues/19923
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11533
- https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877
- https://github.com/mozilla-mobile/fenix/pull/18143
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2021-12-01"
synced_tab_opened:
type: event
description: |
@ -623,47 +599,6 @@ toolbar_settings:
- android-probes@mozilla.com
expires: "2022-02-01"
crash_reporter:
opened:
type: event
description: |
The crash reporter was displayed
bugs:
- https://github.com/mozilla-mobile/fenix/issues/1040
- https://github.com/mozilla-mobile/fenix/issues/19923
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708
- https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877
- https://github.com/mozilla-mobile/fenix/pull/18143
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2021-12-01"
closed:
type: event
description: |
The crash reporter was closed
extra_keys:
crash_submitted:
description: |
A boolean that tells us whether or not the user submitted a crash
report
bugs:
- https://github.com/mozilla-mobile/fenix/issues/1040
- https://github.com/mozilla-mobile/fenix/issues/19923
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708
- https://github.com/mozilla-mobile/fenix/pull/13958#issuecomment-676857877
- https://github.com/mozilla-mobile/fenix/pull/18143
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2021-12-01"
context_menu:
item_tapped:
type: event
@ -1320,9 +1255,10 @@ metrics:
expires: "2022-02-01"
inactive_tabs_count:
type: quantity
lifetime: application
description: |
How many inactive tabs does the user have.
How many inactive tabs does the user have, checked when the user opens
the tabs tray.
Value will be 0 if the feature is disabled.
send_in_pings:
- metrics
bugs:
@ -1445,6 +1381,22 @@ customize_home:
notification_emails:
- android-probes@mozilla.com
expires: "2022-09-20"
opening_screen:
type: string
description: |
What opening screen preference the user has selected
under "Customize Home".
"homepage," "last tab," or "homepage after 4 hours"
default: "homepage after 4 hours"
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22145
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22333
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
preferences:
studies_enabled:
@ -1815,6 +1767,19 @@ preferences:
notification_emails:
- android-probes@mozilla.com
expires: "2022-02-01"
search_term_groups_enabled:
type: boolean
description: |
Is search term group in tabs tray on?
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22057
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22058
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
search.default_engine:
code:
@ -2676,12 +2641,12 @@ history:
recent_searches_tapped:
type: event
description: |
User has tapped on a recent searches card in home.
User has tapped on an item in the "Recently visited" section on home.
extra_keys:
page_number:
description: |
The page number in the homescreen carousel that the recent searches
card was on.
The page number in the homescreen carousel that the recently visited
item was on.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22172
data_reviews:
@ -2691,7 +2656,58 @@ history:
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
search_term_group_tapped:
type: event
description: |
A user tapped on a search term group in history
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22299
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22300
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
search_term_group_open_tab:
type: event
description: |
A user opens a tab from the search term group in history.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22147
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22368#issuecomment-964223263
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
search_term_group_remove_tab:
type: event
description: |
A user closes a single tab in the search term group in history.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22147
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22368#issuecomment-964223263
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
search_term_group_remove_all:
type: event
description: |
A user closes all tabs in the search term group in history.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22147
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22368#issuecomment-964223263
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
reader_mode:
available:
@ -3149,6 +3165,45 @@ tabs_tray:
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
inactive_tabs_cfr_settings:
type: event
description: |
A user has opened settings via the inactive tabs CFR.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22298
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
inactive_tabs_cfr_dismissed:
type: event
description: |
A user has dismissed the inactive tabs CFR.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22298
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
inactive_tabs_cfr_visible:
type: event
description: |
An indication of whether the inactive tabs CFR is visible.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22298
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
collections:
renamed:
@ -4315,6 +4370,23 @@ first_session:
- android-probes@mozilla.com
- erichards@mozilla.com
expires: never
distribution_id:
type: string
description: |
A string containing the distribution identifier. This is currently used
to identify installs from Mozilla Online.
send_in_pings:
- first-session
bugs:
- https://github.com/mozilla-mobile/fenix/issues/20376
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22543#issuecomment-977456848
data_sensitivity:
- technical
notification_emails:
- android-probes@mozilla.com
- rxu@mozilla.com
expires: never
timestamp:
send_in_pings:
- first-session
@ -5584,6 +5656,19 @@ home_screen:
notification_emails:
- android-probes@mozilla.com
expires: "2022-04-01"
home_screen_view_count:
type: counter
description: |
The number of times the home screen was displayed to the user.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22146
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22377
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
customize_home_clicked:
type: event
description: A user clicked on Customize home from the home screen menu.
@ -5912,14 +5997,14 @@ recent_searches:
type: event
description: |
A user has deleted a search term group from the
"Recent searches" section on the homescreen using
"Recently visited" section on the homescreen using
the long-press menu "Remove" option. This removes
the item from the homescreen, but does not delete
the item from history.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22175
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/TBD
- https://github.com/mozilla-mobile/fenix/pull/22176#issuecomment-956421788
data_sensitivity:
- interaction
notification_emails:
@ -6059,3 +6144,80 @@ credit_cards:
notification_emails:
- android-probes@mozilla.com
expires: "2022-09-01"
search_terms:
number_of_search_term_group:
type: event
description: |
Number of search term group when tabs tray is opened.
extra_keys:
count:
description: |
The number of tabs per search group
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22057
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22058
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
average_tabs_per_group:
type: event
description: |
Number of search term tabs per group when tabs tray is opened.
extra_keys:
count:
description: |
The average number of tabs per search group
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22057
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22058
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
jump_back_in_group_tapped:
type: event
description: |
User tapped on the jump back in search term group.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22057
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22058
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-11-01"
group_size_distribution:
type: custom_distribution
description: |
The distribution of search term tab group sizes. Rather than reporting
individual sizes directly as integers, it is currently desired to
report the sizes according to certain size ranges.
The "buckets" for reporting group sizes will be mapped as follows:
* 2 tabs -> 1
* 3-5 tabs -> 2
* 6-10 tabs -> 3
* 11+ tabs -> 4
Where the reported number will be 1, 2, 3, or 4, accordingly.
As an example, say a user has three groups of sizes 3, 6, and 15. The
app will report 2, 3, and 4 when this metric is tracked.
range_min: 1
range_max: 4
bucket_count: 5
histogram_type: linear
unit: tab_group_size_code
bugs:
- https://github.com/mozilla-mobile/fenix/issues/22410
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22479
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: "2022-12-01"

@ -1,6 +1,6 @@
<html>
<body>
<a id="link" href="../resources/Globe.svg" download>Page content: Globe.svg</a>
<a id="link" href="../resources/tAJwqaWjJsXS8AhzSninBMCfIZbHBGgcc001lx5DIdDwIcfEgQ6vE5Gb5VgAled17DFZ2A7ZDOHA0NpQPHXXFHPSD4wzCkRWiaOorNI574zLtv4Hjiz6O6T7onmUTGgUQ2YQoiQFyrCrPv8ZB9Kvmt.svg" download>Page content: tAJwqaWjJsXS8AhzSninBMCfIZbHBGgcc001lx5DIdDwIcfEgQ6vE5Gb5VgAled17DFZ2A7ZDOHA0NpQPHXXFHPSD4wzCkRWiaOorNI574zLtv4Hjiz6O6T7onmUTGgUQ2YQoiQFyrCrPv8ZB9Kvmt.svg</a>
<script>
(function() {
document.getElementById("link").click()

@ -14,7 +14,7 @@ import mozilla.components.service.fxa.ServerConfig
object FxaServer {
private const val CLIENT_ID = "a2270f727f45f648"
const val REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel"
private const val REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel"
@Suppress("UNUSED_PARAMETER")
fun config(context: Context): ServerConfig {

@ -0,0 +1,9 @@
package org.mozilla.fenix.customannotations
/**
* A custom annotation to mark the smoke tests corresponding to the ones in TestRail:
* https://testrail.stage.mozaws.net/index.php?/suites/view/3192
*/
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class SmokeTest

@ -0,0 +1,40 @@
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
fun setPocketEnabled(enabled: Boolean) {
settings.showPocketRecommendationsFeature = enabled
}
fun setJumpBackCFREnabled(enabled: Boolean) {
settings.shouldShowJumpBackInCFR = enabled
}
fun setRecentTabsFeatureEnabled(enabled: Boolean) {
settings.showRecentTabsFeature = enabled
}
fun setStrictETPEnabled() {
settings.setStrictETP()
}
// 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
}
}

@ -17,6 +17,8 @@ object TestAssetHelper {
@Suppress("MagicNumber")
val waitingTime: Long = TimeUnit.SECONDS.toMillis(15)
val waitingTimeShort: Long = TimeUnit.SECONDS.toMillis(1)
// A long enough file name to not fit on a single line in the UI.
const val downloadFileName = "tAJwqaWjJsXS8AhzSninBMCfIZbHBGgcc001lx5DIdDwIcfEgQ6vE5Gb5VgAled17DFZ2A7ZDOHA0NpQPHXXFHPSD4wzCkRWiaOorNI574zLtv4Hjiz6O6T7onmUTGgUQ2YQoiQFyrCrPv8ZB9Kvmt.svg"
data class TestAsset(val url: Uri, val content: String, val title: String)
@ -70,7 +72,7 @@ object TestAssetHelper {
fun getDownloadAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/download.html").toString().toUri()!!
val content = "Page content: Globe.svg"
val content = "Page content: $downloadFileName"
return TestAsset(url, content, "")
}

@ -16,7 +16,6 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.browser.customtabs.CustomTabsIntent
import androidx.preference.PreferenceManager
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
@ -33,6 +32,7 @@ import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import java.io.File
import kotlinx.coroutines.runBlocking
import mozilla.components.support.ktx.android.content.appName
import org.hamcrest.CoreMatchers
@ -43,7 +43,6 @@ import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.idlingresource.NetworkConnectionIdlingResource
import org.mozilla.fenix.ui.robots.mDevice
import java.io.File
object TestHelper {
@ -71,13 +70,6 @@ object TestHelper {
).perform(longClick())
}
fun setPreference(context: Context, pref: String, value: Int) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = preferences.edit()
editor.putInt(pref, value)
editor.apply()
}
fun restartApp(activity: HomeActivityIntentTestRule) {
with(activity) {
finishActivity()

@ -6,7 +6,7 @@ import androidx.test.espresso.IdlingResource
import mozilla.components.feature.addons.ui.AddonInstallationDialogFragment
class AddonsInstallingIdlingResource(
val fragmentManager: FragmentManager
private val fragmentManager: FragmentManager
) :
IdlingResource {
private var resourceCallback: IdlingResource.ResourceCallback? = null

@ -25,7 +25,7 @@ private const val EXPECTED_SUPPRESSION_COUNT = 19
@Suppress("TopLevelPropertyNaming") // it's silly this would have a different naming convention b/c no const
private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to local runs: increment these together
private const val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = 4
private const val EXPECTED_NUMBER_OF_INFLATION = 12
private const val EXPECTED_NUMBER_OF_INFLATION = 13
private val failureMsgStrictMode = getErrorMessage(
shortName = "StrictMode suppression",

@ -11,8 +11,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.locale.LocaleTestRule
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
@ -25,11 +23,13 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.bookmarksMenu
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.mDevice
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.swipeToBottom
import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.locale.LocaleTestRule
class MenuScreenShotTest : ScreenshotTest() {
private lateinit var mockWebServer: MockWebServer
@ -194,8 +194,6 @@ fun editBookmarkFolder() = onView(withText(R.string.bookmark_menu_edit_button)).
fun deleteBookmarkFolder() = onView(withText(R.string.bookmark_menu_delete_button)).click()
fun saveToCollectionButton() = onView(withId(R.id.save_tab_group_button)).click()
fun tapOnTabCounter() = onView(withId(R.id.counter_text)).click()
fun settingsAccountPreferences() = onView(withText(R.string.preferences_sync)).click()

@ -15,6 +15,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
@ -166,9 +167,11 @@ class BookmarksTest {
addNewFolderName(bookmarksFolderName)
navigateUp()
verifyKeyboardHidden()
verifyBookmarkFolderIsNotCreated(bookmarksFolderName)
}
}
@SmokeTest
@Test
fun editBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -277,6 +280,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun deleteBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -296,6 +300,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun undoDeleteBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -320,8 +325,9 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun multiSelectionToolbarItemsTest() {
fun bookmarksMultiSelectionToolbarItemsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen {
@ -345,6 +351,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun openSelectionInNewTabTest() {
val settings = activityTestRule.activity.applicationContext.settings()
@ -375,6 +382,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun openSelectionInPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -398,6 +406,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun deleteMultipleSelectionTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -427,6 +436,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun undoDeleteMultipleSelectionTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -515,6 +525,7 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun changeBookmarkParentFolderTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -609,8 +620,37 @@ class BookmarksTest {
IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!)
}.clickEdit {
clickDeleteInEditModeButton()
cancelDeletion()
clickDeleteInEditModeButton()
confirmDeletion()
verifyDeleteSnackBarText()
verifyBookmarkIsDeleted("Test_Page_1")
}
}
@SmokeTest
@Test
fun undoDeleteBookmarkFolderTest() {
browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
bookmarksListIdlingResource =
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1)
IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!)
createFolder("My Folder")
verifyFolderTitle("My Folder")
IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!)
}.openThreeDotMenu("My Folder") {
}.clickDelete {
cancelFolderDeletion()
verifyFolderTitle("My Folder")
}.openThreeDotMenu("My Folder") {
}.clickDelete {
confirmDeletion()
verifyDeleteSnackBarText()
clickUndoDeleteButton()
verifyFolderTitle("My Folder")
}
}
}

@ -9,12 +9,13 @@ 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.ext.settings
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
@ -34,13 +35,17 @@ class CollectionTest {
private lateinit var mockWebServer: MockWebServer
private val firstCollectionName = "testcollection_1"
private val secondCollectionName = "testcollection_2"
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = HomeActivityTestRule()
@Before
fun setUp() {
activityTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
// disabling these features to have better visibility of Collections
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setJumpBackCFREnabled(false)
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
@ -51,6 +56,9 @@ class CollectionTest {
@After
fun tearDown() {
mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
@Test
@ -81,7 +89,6 @@ class CollectionTest {
}
@Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun verifyAddTabButtonOfCollectionMenu() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -108,7 +115,6 @@ class CollectionTest {
}
@Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun renameCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
@ -269,4 +275,29 @@ class CollectionTest {
verifyMenuButton()
}
}
@SmokeTest
@Test
fun undoDeleteCollectionTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openTabDrawer {
createCollection(webPage.title, firstCollectionName)
snackBarButtonClick("VIEW")
}
homeScreen {
}.expandCollection(firstCollectionName) {
clickCollectionThreeDotButton()
selectDeleteCollection()
}
homeScreen {
verifySnackBarText("Collection deleted")
clickUndoCollectionDeletion("UNDO")
verifyCollectionIsDisplayed(firstCollectionName, true)
}
}
}

@ -9,13 +9,15 @@ 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.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.downloadRobot
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
@ -32,7 +34,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
*
*/
@Ignore("Test failures: https://github.com/mozilla-mobile/fenix/issues/18421")
class ContextMenusTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
@ -42,6 +43,7 @@ class ContextMenusTest {
@Before
fun setUp() {
activityIntentTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
@ -53,6 +55,7 @@ class ContextMenusTest {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun verifyContextOpenLinkNewTab() {
val pageLinks =
@ -67,7 +70,7 @@ class ContextMenusTest {
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInNewTab()
verifySnackBarText("New tab opened")
snackBarButtonClick("Switch")
snackBarButtonClick()
verifyUrl(genericURL.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
@ -76,6 +79,7 @@ class ContextMenusTest {
}
}
@SmokeTest
@Test
fun verifyContextOpenLinkPrivateTab() {
val pageLinks =
@ -90,7 +94,7 @@ class ContextMenusTest {
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInPrivateTab()
verifySnackBarText("New private tab opened")
snackBarButtonClick("Switch")
snackBarButtonClick()
verifyUrl(genericURL.url.toString())
}.openTabDrawer {
verifyPrivateModeSelected()
@ -98,7 +102,6 @@ class ContextMenusTest {
}
}
@Ignore("Test failures: https://github.com/mozilla-mobile/fenix/issues/12473")
@Test
fun verifyContextCopyLink() {
val pageLinks =
@ -135,7 +138,6 @@ class ContextMenusTest {
}
}
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12367")
@Test
fun verifyContextOpenImageNewTab() {
val pageLinks =
@ -150,13 +152,12 @@ class ContextMenusTest {
verifyLinkImageContextMenuItems(imageResource.url)
clickContextOpenImageNewTab()
verifySnackBarText("New tab opened")
snackBarButtonClick("Switch")
snackBarButtonClick()
verifyUrl(imageResource.url.toString())
}
}
@Test
@Ignore("Disabled Google Keyboard Clipboard overlay blocks the address bar: https://github.com/mozilla-mobile/fenix/issues/10586")
fun verifyContextCopyImageLocation() {
val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -177,7 +178,6 @@ class ContextMenusTest {
}
@Test
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12309")
fun verifyContextSaveImage() {
val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -202,7 +202,6 @@ class ContextMenusTest {
}
@Test
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12309")
fun verifyContextMixedVariations() {
val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -224,4 +223,48 @@ class ContextMenusTest {
verifyNoLinkImageContextMenuItems(imageResource.url)
}
}
@SmokeTest
@Test
fun shareSelectedTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickMatchingText(genericURL.content)
}.clickShareSelectedText {
verifyAndroidShareLayout()
}
}
@SmokeTest
@Test
fun selectAndSearchTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickAndSearchText("Search", "content")
mDevice.waitForIdle()
verifyTabCounter("2")
verifyUrl("google")
}
}
@SmokeTest
@Test
fun privateSelectAndSearchTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickAndSearchText("Private Search", "content")
mDevice.waitForIdle()
verifyTabCounter("2")
verifyUrl("google")
}
}
}

@ -99,7 +99,7 @@ class DeepLinkTest {
@Test
fun openSettings() {
robot.openSettings {
verifyBasicsHeading()
verifyGeneralHeading()
verifyAdvancedHeading()
}
}

@ -15,6 +15,7 @@ import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.downloadFileName
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.ui.robots.downloadRobot
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -31,7 +32,6 @@ import org.mozilla.fenix.ui.robots.notificationShade
class DownloadTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
@ -56,7 +56,7 @@ class DownloadTest {
fun tearDown() {
mockWebServer.shutdown()
TestHelper.deleteDownloadFromStorage("Globe.svg")
TestHelper.deleteDownloadFromStorage(downloadFileName)
}
@Test

@ -13,9 +13,11 @@ 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
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
@ -36,6 +38,7 @@ class HistoryTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mockWebServer: MockWebServer
private var historyListIdlingResource: RecyclerViewIdlingResource? = null
private var recentlyClosedTabsListIdlingResource: RecyclerViewIdlingResource? = null
@get:Rule
val activityTestRule = HomeActivityTestRule()
@ -65,6 +68,10 @@ class HistoryTest {
if (historyListIdlingResource != null) {
IdlingRegistry.getInstance().unregister(historyListIdlingResource!!)
}
if (recentlyClosedTabsListIdlingResource != null) {
IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!)
}
}
@Test
@ -78,6 +85,7 @@ class HistoryTest {
}
}
@Ignore("Failing, see https://github.com/mozilla-mobile/fenix/issues/22304")
@Test
// Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds
@ -120,6 +128,7 @@ class HistoryTest {
}
}
@SmokeTest
@Test
fun deleteAllHistoryTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -142,8 +151,9 @@ class HistoryTest {
}
}
@SmokeTest
@Test
fun multiSelectionToolbarItemsTest() {
fun historyMultiSelectionToolbarItemsTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -278,4 +288,28 @@ class HistoryTest {
verifyShareTabUrl()
}
}
@Test
// This test verifies the Recently Closed Tabs List and items
fun verifyRecentlyClosedTabsListTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
mDevice.waitForIdle()
}.openTabDrawer {
closeTab()
}.openTabDrawer {
}.openRecentlyClosedTabs {
waitForListToExist()
recentlyClosedTabsListIdlingResource =
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.recently_closed_list), 1)
IdlingRegistry.getInstance().register(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsMenuView()
IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsPageTitle("Test_Page_1")
verifyRecentlyClosedTabsUrl(website.url)
}
}
}

@ -93,7 +93,7 @@ class HomeScreenTest {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openSettings {
verifyBasicsHeading()
verifyGeneralHeading()
}.goBack {
verifyExistingTopSitesList()
}

@ -8,6 +8,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen
@ -40,6 +41,7 @@ class SearchTest {
}
}
@SmokeTest
@Ignore("This test cannot run on virtual devices due to camera permissions being required")
@Test
fun scanButtonTest() {

@ -1,22 +1,21 @@
package org.mozilla.fenix.ui
import android.view.View
import androidx.test.espresso.IdlingRegistry
import org.mozilla.fenix.helpers.TestAssetHelper
/* 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.view.View
import androidx.test.espresso.IdlingRegistry
import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -80,7 +79,7 @@ class SettingsAddonsTest {
val addonName = "uBlock Origin"
navigationToolbar {}
.openNewTabAndEnterToBrowser(defaultWebPage.url) {}
.enterURLAndEnterToBrowser(defaultWebPage.url) {}
.openThreeDotMenu {}
.openAddonsManagerMenu {
addonsListIdlingResource =

@ -10,15 +10,17 @@ 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.FenixApplication
import org.mozilla.fenix.ext.settings
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.helpers.TestAssetHelper.getLoremIpsumAsset
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.checkTextSizeOnWebsite
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -33,6 +35,7 @@ class SettingsBasicsTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule()
@ -43,13 +46,16 @@ class SettingsBasicsTest {
dispatcher = AndroidAssetDispatcher()
start()
}
val settings = activityIntentTestRule.activity.settings()
settings.shouldShowJumpBackInCFR = false
featureSettingsHelper.setJumpBackCFREnabled(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
private fun getUiTheme(): Boolean {
@ -64,33 +70,33 @@ class SettingsBasicsTest {
}
@Test
// Walks through settings menu and sub-menus to ensure all items are present
fun settingsMenuBasicsItemsTests() {
fun settingsGeneralItemsTests() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifySettingsToolbar()
verifyGeneralHeading()
verifySearchButton()
verifyTabsButton()
verifyHomepageButton()
verifyCustomizeButton()
verifyLoginsAndPasswordsButton()
verifyCreditCardsButton()
verifyAccessibilityButton()
verifyLanguageButton()
verifySetAsDefaultBrowserButton()
}
}
@Test
fun searchSettingsItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifyBasicsHeading()
verifySearchEngineButton()
verifyDefaultBrowserItem()
verifyTabsItem()
// drill down to submenu
}.openSearchSubMenu {
verifySearchToolbar()
verifyDefaultSearchEngineHeader()
verifySearchEngineList()
verifyShowSearchSuggestions()
verifyShowSearchShortcuts()
verifyShowClipboardSuggestions()
verifySearchBrowsingHistory()
verifySearchBookmarks()
}.goBack {
}.openCustomizeSubMenu {
verifyThemes()
}.goBack {
}.openAccessibilitySubMenu {
verifyAutomaticFontSizingMenuItems()
}.goBack {
// drill down to submenu
}
}
@ -137,7 +143,6 @@ class SettingsBasicsTest {
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/19016")
@Test
fun changeThemeSetting() {
// Goes through the settings and changes the default search engine, then verifies it changes.
@ -158,8 +163,6 @@ class SettingsBasicsTest {
// Goes through the settings and changes the default text on a webpage, then verifies if the text has changed.
val fenixApp = activityIntentTestRule.activity.applicationContext as FenixApplication
val webpage = getLoremIpsumAsset(mockWebServer).url
val settings = fenixApp.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
// This value will represent the text size percentage the webpage will scale to. The default value is 100%.
val textSizePercentage = 180
@ -184,4 +187,97 @@ class SettingsBasicsTest {
verifyMenuItemsAreDisabled()
}
}
@SmokeTest
@Test
fun jumpBackInOptionTest() {
val genericURL = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
mDevice.waitForIdle()
}.goToHomescreen {
verifyJumpBackInSectionIsDisplayed()
}.openThreeDotMenu {
}.openCustomizeHome {
clickJumpBackInButton()
}.goBack {
verifyJumpBackInSectionIsNotDisplayed()
}
}
@SmokeTest
@Test
fun recentBookmarksOptionTest() {
val genericURL = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
}.bookmarkPage {
}.goToHomescreen {
verifyRecentBookmarksSectionIsDisplayed()
}.openThreeDotMenu {
}.openCustomizeHome {
clickRecentBookmarksButton()
}.goBack {
verifyRecentBookmarksSectionIsNotDisplayed()
}
}
@SmokeTest
@Test
fun startOnHomepageTest() {
val genericURL = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openHomepageSubMenu {
clickStartOnHomepageButton()
}
restartApp(activityIntentTestRule)
homeScreen {
verifyHomeScreen()
}
}
@SmokeTest
@Test
fun startOnLastTabTest() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHomepageSubMenu {
clickStartOnHomepageButton()
}
restartApp(activityIntentTestRule)
homeScreen {
verifyHomeScreen()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
mDevice.waitForIdle()
}.goToHomescreen {
}.openThreeDotMenu {
}.openCustomizeHome {
clickStartOnLastTabButton()
}
restartApp(activityIntentTestRule)
browserScreen {
verifyUrl(firstWebPage.url.toString())
}
}
}

@ -11,6 +11,7 @@ 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.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -449,10 +450,11 @@ class SettingsPrivacyTest {
confirmDeletionAndAssertSnackbar()
}
settingsScreen {
verifyBasicsHeading()
verifyGeneralHeading()
}
}
@SmokeTest
@Test
fun deleteTabsDataTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -472,16 +474,17 @@ class SettingsPrivacyTest {
confirmDeletionAndAssertSnackbar()
}
settingsScreen {
verifyBasicsHeading()
verifyGeneralHeading()
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsDetails("0")
}.goBack {
}.goBack {
}.openTabDrawer {
verifyNoTabsOpened()
verifyNoOpenTabsInNormalBrowsing()
}
}
@SmokeTest
@Test
fun deleteDeleteBrowsingHistoryDataTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -505,7 +508,7 @@ class SettingsPrivacyTest {
confirmDeletionAndAssertSnackbar()
verifyBrowsingHistoryDetails("0")
}.goBack {
verifyBasicsHeading()
verifyGeneralHeading()
}.goBack {
}
navigationToolbar {

@ -1,65 +0,0 @@
/* 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.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
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
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying basic functionality of history
*
*/
class ShareButtonTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun ShareButtonAppearanceTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
// - Visit a URL, wait until it's loaded
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
}
// From the 3-dot menu next to the Select share menu
navigationToolbar {
}.openThreeDotMenu {
clickShareButton()
verifyShareScrim()
verifySendToDeviceTitle()
verifyShareALinkTitle()
}
}
}

@ -22,20 +22,22 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.downloadFileName
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.assertExternalAppOpens
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.TestHelper.deleteDownloadFromStorage
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.returnToBrowser
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
@ -57,10 +59,13 @@ import org.mozilla.fenix.ui.util.ROMANIAN_LANGUAGE_HEADER
import org.mozilla.fenix.ui.util.STRING_ONBOARDING_TRACKING_PROTECTION_HEADER
/**
* Test Suite that contains tests defined as part of the Smoke and Sanity check defined in Test rail.
* Test Suite that contains a part of the Smoke and Sanity tests defined in TestRail:
* https://testrail.stage.mozaws.net/index.php?/suites/view/3192
* Other smoke tests have been marked with the @SmokeTest annotation throughout the ui package in order to limit this class expansion.
* These tests will verify different functionalities of the app as a way to quickly detect regressions in main areas
*/
@Suppress("ForbiddenComment")
@SmokeTest
class SmokeTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
@ -68,22 +73,12 @@ class SmokeTest {
private var addonsListIdlingResource: RecyclerViewIdlingResource? = null
private var recentlyClosedTabsListIdlingResource: RecyclerViewIdlingResource? = null
private var readerViewNotification: ViewVisibilityIdlingResource? = null
private val downloadFileName = "Globe.svg"
private val collectionName = "First Collection"
private var bookmarksListIdlingResource: RecyclerViewIdlingResource? = null
private var localeListIdlingResource: RecyclerViewIdlingResource? = null
private val customMenuItem = "TestMenuItem"
// This finds the dialog fragment child of the homeFragment, otherwise the awesomeBar would return null
private fun getAwesomebarView(): View? {
val homeFragment = activityTestRule.activity.supportFragmentManager.primaryNavigationFragment
val searchDialogFragment = homeFragment?.childFragmentManager?.fragments?.first {
it.javaClass.simpleName == "SearchDialogFragment"
}
return searchDialogFragment?.view?.findViewById(R.id.awesome_bar)
}
private lateinit var browserStore: BrowserStore
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = AndroidComposeTestRule(
@ -108,7 +103,9 @@ class SmokeTest {
// So we are initializing this here instead of in all related tests.
browserStore = activityTestRule.activity.components.core.store
activityTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
// disabling the new homepage pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
@ -144,6 +141,9 @@ class SmokeTest {
if (localeListIdlingResource != null) {
IdlingRegistry.getInstance().unregister(localeListIdlingResource)
}
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
// Verifies the first run onboarding screen
@ -311,9 +311,6 @@ class SmokeTest {
@Test
// Verifies the Add to top sites option in a tab's 3 dot menu
fun openMainMenuAddTopSiteTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -350,6 +347,8 @@ class SmokeTest {
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut("Test Page") {
verifyUrl(website.url.toString())
verifyTabCounter("1")
}
}
@ -418,8 +417,10 @@ class SmokeTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.sharePage {
verifyShareAppsLayout()
}.clickShareButton {
verifyShareTabLayout()
verifySendToDeviceTitle()
verifyShareALinkTitle()
}
}
@ -447,7 +448,7 @@ class SmokeTest {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionOptions()
verifyEnhancedTrackingProtectionOptionsEnabled()
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
}.goBackToHomeScreen {}
@ -471,80 +472,48 @@ class SmokeTest {
@Test
// Verifies changing the default engine from the Search Shortcut menu
fun verifySearchEngineCanBeChangedTemporarilyUsingShortcuts() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.openSearch {
verifyKeyboardVisibility()
clickSearchEngineShortcutButton()
verifySearchEngineList(activityTestRule)
changeDefaultSearchEngine(activityTestRule, "Amazon.com")
verifySearchEngineIcon("Amazon.com")
}.goToSearchEngine {
mDevice.waitForIdle()
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
}.openNewTab {
clickSearchEngineShortcutButton()
mDevice.waitForIdle()
changeDefaultSearchEngine(activityTestRule, "Bing")
verifySearchEngineIcon("Bing")
}.goToSearchEngine {
mDevice.waitForIdle()
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
}.openNewTab {
clickSearchEngineShortcutButton()
mDevice.waitForIdle()
changeDefaultSearchEngine(activityTestRule, "DuckDuckGo")
verifySearchEngineIcon("DuckDuckGo")
}.goToSearchEngine {
mDevice.waitForIdle()
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
}.openNewTab {
clickSearchEngineShortcutButton()
changeDefaultSearchEngine(activityTestRule, "Wikipedia")
verifySearchEngineIcon("Wikipedia")
}.goToSearchEngine {
mDevice.waitForIdle()
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
// Checking whether the next search will be with default or not
}.openNewTab {
}.goToSearchEngine {
mDevice.waitForIdle()
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openNavigationToolbar {
}.clickUrlbar {
verifyDefaultSearchEngine("Google")
fun selectSearchEnginesShortcutTest() {
val enginesList = listOf("DuckDuckGo", "Google", "Amazon.com", "Wikipedia", "Bing", "eBay")
for (searchEngine in enginesList) {
homeScreen {
}.openSearch {
verifyKeyboardVisibility()
clickSearchEngineShortcutButton()
verifySearchEngineList(activityTestRule)
changeDefaultSearchEngine(activityTestRule, searchEngine)
verifySearchEngineIcon(searchEngine)
}.submitQuery("mozilla ") {
verifyUrl(searchEngine)
}.goToHomescreen { }
}
}
@Test
// Ads a new search engine from the list of custom engines
fun addPredefinedSearchEngineTest() {
val searchEngine = "Reddit"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
openAddSearchEngineMenu()
verifyAddSearchEngineList()
addNewSearchEngine("YouTube")
verifyEngineListContains("YouTube")
addNewSearchEngine(searchEngine)
verifyEngineListContains(searchEngine)
}.goBack {
}.goBack {
}.openSearch {
verifyKeyboardVisibility()
clickSearchEngineShortcutButton()
mDevice.waitForIdle()
activityTestRule.waitForIdle()
verifyEnginesListShortcutContains(activityTestRule, "YouTube")
verifyEnginesListShortcutContains(activityTestRule, searchEngine)
changeDefaultSearchEngine(activityTestRule, searchEngine)
}.submitQuery("mozilla ") {
verifyUrl(searchEngine)
}
}
@Ignore("Started failing: https://github.com/mozilla-mobile/fenix/issues/21540")
@Test
// Verifies setting as default a customized search engine name and URL
fun editCustomSearchEngineTest() {
@ -576,7 +545,6 @@ class SmokeTest {
}
}
@Ignore("Strated failing on Nighlty task: https://github.com/mozilla-mobile/fenix/issues/21620")
@Test
// Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds
@ -713,8 +681,8 @@ class SmokeTest {
}
@Test
// This test verifies the Recently Closed Tabs List and items
fun verifyRecentlyClosedTabsListTest() {
// Verifies that a recently closed item is properly opened
fun openRecentlyClosedItemTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
@ -731,39 +699,13 @@ class SmokeTest {
IdlingRegistry.getInstance().register(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsMenuView()
IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsPageTitle("Test_Page_1")
verifyRecentlyClosedTabsUrl(website.url)
}
}
@Test
// Verifies the Open in a new tab option from the Recently Closed Tabs overflow menu
fun openRecentlyClosedTabsInNewTabTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
mDevice.waitForIdle()
}.openTabDrawer {
closeTab()
}.openTabDrawer {
}.openRecentlyClosedTabs {
waitForListToExist()
recentlyClosedTabsListIdlingResource =
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.recently_closed_list), 1)
IdlingRegistry.getInstance().register(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsMenuView()
IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!)
}.clickOpenInNewTab {
}.clickRecentlyClosedItem("Test_Page_1") {
verifyUrl(website.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
}
}
@Test
// Verifies the delete button from the Recently Closed Tabs
// Verifies that tapping the "x" button removes a recently closed item from the list
fun deleteRecentlyClosedTabsItemTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -821,10 +763,11 @@ class SmokeTest {
}
@Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun createFirstCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
// disabling these features to have better visibility of Collections
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setPocketEnabled(false)
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
@ -836,6 +779,7 @@ class SmokeTest {
}.submitQuery(secondWebPage.url.toString()) {
mDevice.waitForIdle()
}.goToHomescreen {
swipeToBottom()
}.clickSaveTabsToCollectionButton {
longClickTab(firstWebPage.title)
selectTab(secondWebPage.title)
@ -855,10 +799,11 @@ class SmokeTest {
}
@Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun verifyExpandedCollectionItemsTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
// disabling these features to have better visibility of Collections
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setPocketEnabled(false)
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -873,8 +818,27 @@ class SmokeTest {
verifyCollectionIcon()
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title)
verifyCollectionTabLogo()
verifyCollectionTabUrl()
verifyCollectionTabLogo(true)
verifyCollectionTabUrl(true)
verifyShareCollectionButtonIsVisible(true)
verifyCollectionMenuIsVisible(true)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, true)
}.collapseCollection(collectionName) {}
collectionRobot {
verifyTabSavedInCollection(webPage.title, false)
verifyShareCollectionButtonIsVisible(false)
verifyCollectionMenuIsVisible(false)
verifyCollectionTabLogo(false)
verifyCollectionTabUrl(false)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, false)
}
homeScreen {
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title)
verifyCollectionTabLogo(true)
verifyCollectionTabUrl(true)
verifyShareCollectionButtonIsVisible(true)
verifyCollectionMenuIsVisible(true)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, true)
@ -884,6 +848,9 @@ class SmokeTest {
verifyTabSavedInCollection(webPage.title, false)
verifyShareCollectionButtonIsVisible(false)
verifyCollectionMenuIsVisible(false)
verifyCollectionTabLogo(false)
verifyCollectionTabUrl(false)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, false)
}
}
@ -911,25 +878,28 @@ class SmokeTest {
@Test
fun shareCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val firstWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val sharingApp = "Gmail"
val urlString = "${secondWebsite.url}\n\n${firstWebsite.url}"
navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.enterURLAndEnterToBrowser(firstWebsite.url) {
verifyPageContent(firstWebsite.content)
}.openTabDrawer {
createCollection(webPage.title, collectionName)
snackBarButtonClick("VIEW")
}
homeScreen {
createCollection(firstWebsite.title, collectionName)
}.openNewTab {
}.submitQuery(secondWebsite.url.toString()) {
verifyPageContent(secondWebsite.content)
}.openThreeDotMenu {
}.openSaveToCollection {
}.selectExistingCollection(collectionName) {
}.goToHomescreen {
}.expandCollection(collectionName) {
clickShareCollectionButton()
}
homeScreen {
verifyShareTabsOverlay()
}.clickShareCollectionButton {
verifyShareTabsOverlay(firstWebsite.title, secondWebsite.title)
selectAppToShareWith(sharingApp)
verifySharedTabsIntent(urlString, collectionName)
}
}
@ -937,8 +907,6 @@ class SmokeTest {
// Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds
fun deleteCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -977,6 +945,18 @@ class SmokeTest {
verifyFolderTitle("My Folder")
IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!)
}.openThreeDotMenu("Test_Page_1") {
}.clickEdit {
clickParentFolderSelector()
selectFolder("My Folder")
navigateUp()
saveEditBookmark()
createFolder("My Folder 2")
bookmarksListIdlingResource =
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1)
IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!)
verifyFolderTitle("My Folder 2")
IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!)
}.openThreeDotMenu("My Folder 2") {
}.clickEdit {
clickParentFolderSelector()
selectFolder("My Folder")
@ -990,51 +970,62 @@ class SmokeTest {
}.clickDelete {
confirmDeletion()
verifyDeleteSnackBarText()
verifyBookmarkIsDeleted("My Folder")
verifyBookmarkIsDeleted("My Folder 2")
verifyBookmarkIsDeleted("Test_Page_1")
navigateUp()
}
browserScreen {
}.openThreeDotMenu {
verifyBookmarksButton()
verifyAddBookmarkButton()
}
}
@Test
fun shareTabsFromTabsTrayTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val firstWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val firstWebsiteTitle = firstWebsite.title
val secondWebsiteTitle = secondWebsite.title
val sharingApp = "Gmail"
val sharedUrlsString = "${firstWebsite.url}\n\n${secondWebsite.url}"
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
mDevice.waitForIdle()
}.enterURLAndEnterToBrowser(firstWebsite.url) {
verifyPageContent(firstWebsite.content)
}.openTabDrawer {
}.openNewTab {
}.submitQuery(secondWebsite.url.toString()) {
verifyPageContent(secondWebsite.content)
}.openTabDrawer {
verifyNormalModeSelected()
verifyExistingTabList()
verifyExistingOpenTabs("Test_Page_1")
verifyTabTrayOverflowMenu(true)
verifyExistingOpenTabs("Test_Page_2")
}.openTabsListThreeDotMenu {
verifyShareAllTabsButton()
clickShareAllTabsButton()
verifyShareTabsOverlay()
}.clickShareAllTabsButton {
verifyShareTabsOverlay(firstWebsiteTitle, secondWebsiteTitle)
selectAppToShareWith(sharingApp)
verifySharedTabsIntent(
sharedUrlsString,
"$firstWebsiteTitle, $secondWebsiteTitle"
)
}
}
@Test
fun emptyTabsTrayViewPrivateBrowsingTest() {
homeScreen {
}.dismissOnboarding()
homeScreen {
}.openTabDrawer {
navigationToolbar {
}.openTabTray {
}.toggleToPrivateTabs() {
verifyPrivateModeSelected()
verifyNormalBrowsingButtonIsDisplayed()
verifyNoTabsOpened()
verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInPrivateBrowsing()
verifyPrivateBrowsingNewTabButton()
verifyTabTrayOverflowMenu(true)
verifyNewTabButton()
}.openTabsListThreeDotMenu {
verifyTabSettingsButton()
verifyRecentlyClosedTabsButton()
verifyEmptyTabsTrayMenuButtons()
}
}
@ -1050,14 +1041,19 @@ class SmokeTest {
}.enterURLAndEnterToBrowser(website.url) {
mDevice.waitForIdle()
}.openTabDrawer {
verifyPrivateModeSelected()
verifyNormalBrowsingButtonIsDisplayed()
verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyTabTrayOverflowMenu(true)
verifyTabsTrayCounter()
verifyExistingTabList()
verifyExistingOpenTabs("Test_Page_1")
verifyCloseTabsButton("Test_Page_1")
verifyExistingOpenTabs(website.title)
verifyCloseTabsButton(website.title)
verifyOpenedTabThumbnail()
verifyTabTrayOverflowMenu(true)
verifyNewTabButton()
verifyPrivateBrowsingNewTabButton()
}.openTab(website.title) {
verifyUrl(website.url.toString())
verifyTabCounter("1")
}
}
@ -1100,7 +1096,6 @@ class SmokeTest {
}
@Test
@Ignore("To be re-enabled later. See https://github.com/mozilla-mobile/fenix/issues/20716")
fun mainMenuInstallPWATest() {
val pwaPage = "https://mozilla-mobile.github.io/testapp/"
@ -1166,7 +1161,7 @@ class SmokeTest {
}.openTabCrashReporter {
}.clickTabCrashedCloseButton {
}.openTabDrawer {
verifyNoTabsOpened()
verifyNoOpenTabsInNormalBrowsing()
}
}
@ -1275,7 +1270,7 @@ class SmokeTest {
assertPlaybackState(browserStore, MediaSession.PlaybackState.PLAYING)
}.openTabDrawer {
verifyTabMediaControlButtonState("Pause")
clickTabMediaControlButton()
clickTabMediaControlButton("Pause")
verifyTabMediaControlButtonState("Play")
}.openTab(audioTestPage.title) {
assertPlaybackState(browserStore, MediaSession.PlaybackState.PAUSED)
@ -1357,8 +1352,6 @@ class SmokeTest {
@Test
fun goToHomeScreenBottomToolbarTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -1371,9 +1364,6 @@ class SmokeTest {
@Test
fun goToHomeScreenTopToolbarTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
@ -1435,29 +1425,7 @@ class SmokeTest {
}.openTabsSubMenu {
verifyTabViewOptions()
verifyCloseTabsOptions()
verifyStartOnHomeOptions()
}
}
@Test
fun alwaysStartOnHomeTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openTabsSubMenu {
clickAlwaysStartOnHomeToggle()
}
restartApp(activityTestRule.activityRule)
homeScreen {
verifyHomeScreen()
verifyMoveOldTabsToInactiveOptions()
}
}
}

@ -9,8 +9,9 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.enhancedTrackingProtection
@ -33,6 +34,7 @@ import org.mozilla.fenix.ui.robots.settingsSubMenuEnhancedTrackingProtection
class StrictEnhancedTrackingProtectionTest {
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule
val activityTestRule = HomeActivityTestRule()
@ -43,15 +45,14 @@ class StrictEnhancedTrackingProtectionTest {
dispatcher = AndroidAssetDispatcher()
start()
}
val settings = activityTestRule.activity.settings()
settings.setStrictETP()
settings.shouldShowJumpBackInCFR = false
featureSettingsHelper.setStrictETPEnabled()
featureSettingsHelper.setJumpBackCFREnabled(false)
}
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
@Test
@ -63,13 +64,46 @@ class StrictEnhancedTrackingProtectionTest {
verifyEnhancedTrackingProtectionValue("On")
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionHeader()
verifyEnhancedTrackingProtectionOptions()
verifyEnhancedTrackingProtectionOptionsEnabled()
verifyTrackingProtectionSwitchEnabled()
}.openExceptions {
verifyDefault()
}
}
@SmokeTest
@Test
fun testETPOffGlobally() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
switchEnhancedTrackingProtectionToggle()
verifyEnhancedTrackingProtectionOptionsEnabled(false)
}.goBack {
}.goBack { }
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) { }
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyETPSwitchVisibility(false)
}.closeEnhancedTrackingProtectionSheet {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
switchEnhancedTrackingProtectionToggle()
verifyEnhancedTrackingProtectionOptionsEnabled(true)
}.goBack {
}.goBackToBrowser { }
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyETPSwitchVisibility(true)
}
}
@Test
fun testStrictVisitProtectionSheet() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -79,9 +113,12 @@ class StrictEnhancedTrackingProtectionTest {
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
@ -97,9 +134,12 @@ class StrictEnhancedTrackingProtectionTest {
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
@ -107,13 +147,13 @@ class StrictEnhancedTrackingProtectionTest {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}.openProtectionSettings {
verifyEnhancedTrackingProtectionHeader()
verifyEnhancedTrackingProtectionOptions()
verifyEnhancedTrackingProtectionOptionsEnabled()
verifyTrackingProtectionSwitchEnabled()
}
settingsSubMenuEnhancedTrackingProtection {
}.openExceptions {
verifyListedURL(trackingProtectionTest.url.toString())
verifyListedURL(trackingProtectionTest.url.host.toString())
}.disableExceptions {
verifyDefault()
}
@ -128,9 +168,12 @@ class StrictEnhancedTrackingProtectionTest {
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)

@ -10,10 +10,11 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
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.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.browserScreen
@ -41,6 +42,7 @@ import org.mozilla.fenix.ui.robots.notificationShade
class TabbedBrowsingTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
@get:Rule
@ -48,7 +50,9 @@ class TabbedBrowsingTest {
@Before
fun setUp() {
activityTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
// disabling the new homepage pop-up that interferes with the tests.
featureSettingsHelper.setJumpBackCFREnabled(false)
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
@ -58,6 +62,7 @@ class TabbedBrowsingTest {
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
@Test
@ -65,7 +70,7 @@ class TabbedBrowsingTest {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
verifyTabCounter("1")
}.openTabDrawer {
@ -73,7 +78,7 @@ class TabbedBrowsingTest {
verifyExistingOpenTabs("Test_Page_1")
closeTab()
}.openTabDrawer {
verifyNoTabsOpened()
verifyNoOpenTabsInNormalBrowsing()
}.openNewTab {
}.submitQuery(defaultWebPage.url.toString()) {
mDevice.waitForIdle()
@ -91,14 +96,14 @@ class TabbedBrowsingTest {
homeScreen {}.togglePrivateBrowsingMode()
navigationToolbar {
}.openNewTabAndEnterToBrowser(defaultWebPage.url) {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
verifyTabCounter("1")
}.openTabDrawer {
verifyExistingTabList()
verifyPrivateModeSelected()
}.toggleToNormalTabs {
verifyNoTabsOpened()
verifyNoOpenTabsInNormalBrowsing()
}.toggleToPrivateTabs {
verifyExistingTabList()
}
@ -141,40 +146,55 @@ class TabbedBrowsingTest {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.openNewTabAndEnterToBrowser(genericURL.url) {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
closeTabViaXButton("Test_Page_1")
verifySnackBarText("Tab closed")
snackBarButtonClick("UNDO")
closeTab()
}
mDevice.waitForIdle()
browserScreen {
homeScreen {
verifyNoTabsOpened()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
swipeTabRight("Test_Page_1")
verifySnackBarText("Tab closed")
snackBarButtonClick("UNDO")
}
homeScreen {
verifyNoTabsOpened()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
swipeTabLeft("Test_Page_1")
}
homeScreen {
verifyNoTabsOpened()
}
}
mDevice.waitForIdle()
@Ignore("Currently failing, will need some investigation, see https://github.com/mozilla-mobile/fenix/issues/22640")
@Test
fun verifyUndoSnackBarTest() {
// disabling these features because they interfere with the snackbar visibility
featureSettingsHelper.setPocketEnabled(false)
featureSettingsHelper.setRecentTabsFeatureEnabled(false)
browserScreen {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
swipeTabLeft("Test_Page_1")
closeTab()
verifySnackBarText("Tab closed")
snackBarButtonClick("UNDO")
}
mDevice.waitForIdle()
browserScreen {
verifyTabCounter("1")
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
}.closeTabDrawer { }
}
}
@Test
@ -183,40 +203,53 @@ class TabbedBrowsingTest {
homeScreen { }.togglePrivateBrowsingMode()
navigationToolbar {
}.openNewTabAndEnterToBrowser(genericURL.url) {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
verifyCloseTabsButton("Test_Page_1")
closeTabViaXButton("Test_Page_1")
verifySnackBarText("Private tab closed")
snackBarButtonClick("UNDO")
closeTab()
}
mDevice.waitForIdle()
browserScreen {
homeScreen {
verifyNoTabsOpened()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
swipeTabRight("Test_Page_1")
verifySnackBarText("Private tab closed")
snackBarButtonClick("UNDO")
}
homeScreen {
verifyNoTabsOpened()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
swipeTabLeft("Test_Page_1")
}
homeScreen {
verifyNoTabsOpened()
}
}
mDevice.waitForIdle()
@Test
fun verifyPrivateTabUndoSnackBarTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen {
homeScreen { }.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
swipeTabLeft("Test_Page_1")
verifyCloseTabsButton("Test_Page_1")
closeTab()
verifySnackBarText("Private tab closed")
snackBarButtonClick("UNDO")
}
mDevice.waitForIdle()
browserScreen {
verifyTabCounter("1")
}.openTabDrawer {
verifyExistingOpenTabs("Test_Page_1")
verifyPrivateModeSelected()
}
}
@ -244,7 +277,7 @@ class TabbedBrowsingTest {
navigationToolbar {
}.openTabTray {
verifyNoTabsOpened()
verifyNoOpenTabsInNormalBrowsing()
// With no tabs opened the state should be STATE_COLLAPSED.
verifyBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
// Need to ensure the halfExpandedRatio is very small so that when in STATE_HALF_EXPANDED
@ -267,13 +300,13 @@ class TabbedBrowsingTest {
fun verifyEmptyTabTray() {
navigationToolbar {
}.openTabTray {
verifyNoTabsOpened()
verifyNewTabButton()
verifyTabTrayOverflowMenu(true)
}.toggleToPrivateTabs {
verifyNoTabsOpened()
verifyNewTabButton()
verifyNormalBrowsingButtonIsSelected(true)
verifyPrivateBrowsingButtonIsSelected(false)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInNormalBrowsing()
verifyNormalBrowsingNewTabButton()
verifyTabTrayOverflowMenu(true)
verifyEmptyTabsTrayMenuButtons()
}
}
@ -284,14 +317,19 @@ class TabbedBrowsingTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabDrawer {
verifyExistingTabList()
verifyNewTabButton()
verifyNormalBrowsingButtonIsSelected(true)
verifyPrivateBrowsingButtonIsSelected(false)
verifySyncedTabsButtonIsSelected(false)
verifyTabTrayOverflowMenu(true)
verifyTabsTrayCounter()
verifyExistingTabList()
verifyNormalBrowsingNewTabButton()
verifyOpenedTabThumbnail()
verifyExistingOpenTabs(defaultWebPage.title)
verifyCloseTabsButton(defaultWebPage.title)
}.openNewTab {
verifySearchBarEmpty()
verifyKeyboardVisibility()
}.openTab(defaultWebPage.title) {
verifyUrl(defaultWebPage.url.toString())
verifyTabCounter("1")
}
}

@ -54,11 +54,16 @@ class ThreeDotMenuMainTest {
verifyDesktopSite()
verifyWhatsNewButton()
verifyHelpButton()
verifyCustomizeHomeButton()
verifySettingsButton()
}.openSettings {
verifySettingsView()
}.goBack {
}.openThreeDotMenu {
}.openCustomizeHome {
verifyHomePageView()
}.goBack {
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.openTabDrawer {

@ -32,6 +32,7 @@ import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
@ -67,11 +68,15 @@ class BookmarksRobot {
assertFolderTitle(title)
}
fun verifyBookmarkFolderIsNotCreated(title: String) = assertBookmarkFolderIsNotCreated(title)
fun verifyBookmarkTitle(title: String) {
mDevice.findObject(UiSelector().text(title)).waitForExists(waitingTime)
assertBookmarkTitle(title)
}
fun verifyBookmarkIsDeleted(expectedTitle: String) = assertBookmarkIsDeleted(expectedTitle)
fun verifyDeleteSnackBarText() = assertSnackBarText("Deleted")
fun verifyUndoDeleteSnackBarButton() = assertUndoDeleteSnackBarButton()
@ -201,6 +206,12 @@ class BookmarksRobot {
fun longTapDesktopFolder(title: String) = onView(withText(title)).perform(longClick())
fun cancelDeletion() {
val cancelButton = mDevice.findObject(UiSelector().textContains("CANCEL"))
cancelButton.waitForExists(waitingTime)
cancelButton.click()
}
fun confirmDeletion() {
onView(withText(R.string.delete_browsing_data_prompt_allow))
.inRoot(RootMatchers.isDialog())
@ -318,6 +329,20 @@ private fun assertCloseButton() = closeButton().check(matches(withEffectiveVisib
private fun assertEmptyBookmarksList() =
onView(withId(R.id.bookmarks_empty_view)).check(matches(withText("No bookmarks here")))
private fun assertBookmarkFolderIsNotCreated(title: String) {
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/bookmarks_wrapper")
).waitForExists(waitingTime)
assertFalse(
mDevice.findObject(
UiSelector()
.textContains(title)
).waitForExists(waitingTime)
)
}
private fun assertBookmarkFavicon(forUrl: Uri) = bookmarkFavicon(forUrl.toString()).check(
matches(
withEffectiveVisibility(
@ -335,6 +360,20 @@ private fun assertFolderTitle(expectedTitle: String) =
private fun assertBookmarkTitle(expectedTitle: String) =
onView(withText(expectedTitle)).check(matches(isDisplayed()))
private fun assertBookmarkIsDeleted(expectedTitle: String) {
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/bookmarks_wrapper")
).waitForExists(waitingTime)
assertFalse(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/title")
.textContains(expectedTitle)
).waitForExists(waitingTime)
)
}
private fun assertUndoDeleteSnackBarButton() =
snackBarUndoButton().check(matches(withText("UNDO")))

@ -12,7 +12,6 @@ import android.net.Uri
import android.os.SystemClock
import android.widget.EditText
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
@ -21,7 +20,6 @@ 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.Visibility
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
@ -31,13 +29,13 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
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.hamcrest.CoreMatchers.containsString
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.mozilla.fenix.R
@ -46,6 +44,7 @@ import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
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
@ -103,16 +102,23 @@ class BrowserRobot {
}
fun verifyTabCounter(expectedText: String) {
onView(withId(R.id.counter_text))
.check((matches(withText(containsString(expectedText)))))
val counter =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/counter_text")
.text(expectedText)
)
assertTrue(counter.waitForExists(waitingTime))
}
fun verifySnackBarText(expectedText: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull(Until.findObject(text(expectedText)), waitingTime)
mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(expectedText)))
onView(withText(expectedText)).check(
matches(isCompletelyDisplayed())
assertTrue(
mDevice.findObject(
UiSelector()
.textContains(expectedText)
).waitForExists(waitingTime)
)
}
@ -154,29 +160,19 @@ class BrowserRobot {
)
}
fun verifyNavURLBar() = assertNavURLBar()
fun verifyNavURLBarHidden() = assertNavURLBarHidden()
fun verifySecureConnectionLockIcon() = assertSecureConnectionLockIcon()
fun verifyEnhancedTrackingProtectionSwitch() = assertEnhancedTrackingProtectionSwitch()
fun verifyEnhancedTrackingOptions() {
onView(withId(R.id.mozac_browser_toolbar_security_indicator)).click()
verifyEnhancedTrackingProtectionSwitch()
}
fun verifyMenuButton() = assertMenuButton()
fun verifyNavURLBarItems() {
verifyEnhancedTrackingOptions()
pressBack()
waitingTime
verifySecureConnectionLockIcon()
verifyTabCounter("1")
verifyNavURLBar()
navURLBar().waitForExists(waitingTime)
verifyMenuButton()
verifyTabCounter("1")
verifySearchBar()
verifySecureConnectionLockIcon()
verifyHomeScreenButton()
}
fun verifyNoLinkImageContextMenuItems(containsURL: Uri) {
@ -199,6 +195,10 @@ class BrowserRobot {
)
}
fun verifyHomeScreenButton() = assertHomeScreenButton()
fun verifySearchBar() = assertSearchBar()
fun dismissContentContextMenu(containsURL: Uri) {
onView(withText(containsURL.toString()))
.inRoot(isDialog())
@ -314,11 +314,31 @@ class BrowserRobot {
}
fun longClickMatchingText(expectedText: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull(Until.findObject(text(expectedText)), waitingTime)
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)
val element = mDevice.findObject(text(expectedText))
element.click(LONG_CLICK_DURATION)
// Refresh the page in case the first long click didn't succeed
navigationToolbar {
}.openThreeDotMenu {
}.refreshPage {
mDevice.waitForIdle()
}
// 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) {
@ -374,10 +394,45 @@ class BrowserRobot {
}
}
fun snackBarButtonClick(expectedText: String) {
onView(allOf(withId(R.id.snackbar_btn), withText(expectedText))).check(
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
).perform(ViewActions.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()
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()
}
}
}
}
fun snackBarButtonClick() {
val switchButton =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/snackbar_btn")
)
switchButton.waitForExists(waitingTime)
switchButton.clickAndWaitForNewWindow(waitingTime)
}
fun verifySaveLoginPromptIsShown() {
@ -405,12 +460,38 @@ class BrowserRobot {
.resourceId("password")
.className(EditText::class.java)
)
passwordField.waitForExists(waitingTime)
passwordField.click()
passwordField.clearTextField()
passwordField.text = password
// wait until the password is hidden
assertTrue(mDevice.findObject(UiSelector().text(password)).waitUntilGone(waitingTime))
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)
// 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))
}
}
fun clickMediaPlayerPlayButton() {
@ -497,7 +578,10 @@ class BrowserRobot {
}
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.desc("Tabs")))
mDevice.findObject(
UiSelector().descriptionContains("open tab. Tap to switch tabs.")
).waitForExists(waitingTime)
tabsCounter().click()
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/tab_layout")))
@ -550,6 +634,14 @@ class BrowserRobot {
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun clickShareSelectedText(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
val shareTextButton = org.mozilla.fenix.ui.robots.mDevice.findObject(By.textContains("Share"))
shareTextButton.click()
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
}
}
}
@ -560,19 +652,18 @@ fun browserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
fun navURLBar() = mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
private fun assertNavURLBar() = assertTrue(navURLBar().waitForExists(waitingTime))
fun searchBar() = onView(withId(R.id.mozac_browser_toolbar_url_view))
private fun assertNavURLBarHidden() = assertTrue(navURLBar().waitUntilGone(waitingTime))
fun homeScreenButton() = onView(withContentDescription(R.string.browser_toolbar_home))
private fun assertEnhancedTrackingProtectionSwitch() {
withText(R.id.trackingProtectionSwitch)
.matches(withEffectiveVisibility(Visibility.VISIBLE))
}
private fun assertHomeScreenButton() =
homeScreenButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertProtectionSettingsButton() {
onView(withId(R.id.protection_settings))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertSearchBar() = searchBar().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertNavURLBar() = assertTrue(navURLBar().waitForExists(waitingTime))
private fun assertNavURLBarHidden() = assertTrue(navURLBar().waitUntilGone(waitingTime))
private fun assertSecureConnectionLockIcon() {
onView(withId(R.id.mozac_browser_toolbar_security_indicator))
@ -586,7 +677,7 @@ private fun assertMenuButton() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun tabsCounter() = mDevice.findObject(By.desc("Tabs"))
private fun tabsCounter() = mDevice.findObject(By.res("$packageName:id/counter_root"))
private fun mediaPlayerPlayButton() =
mDevice.findObject(

@ -78,12 +78,20 @@ class CollectionRobot {
.check(doesNotExist())
}
fun verifyCollectionTabUrl() {
onView(withId(R.id.caption)).check(matches(isDisplayed()))
fun verifyCollectionTabUrl(visible: Boolean) {
onView(withId(R.id.caption))
.check(
if (visible) matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
else doesNotExist()
)
}
fun verifyCollectionTabLogo() {
onView(withId(R.id.favicon)).check(matches(isDisplayed()))
fun verifyCollectionTabLogo(visible: Boolean) {
onView(withId(R.id.favicon))
.check(
if (visible) matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
else doesNotExist()
)
}
fun verifyShareCollectionButtonIsVisible(visible: Boolean) {
@ -94,8 +102,6 @@ class CollectionRobot {
)
}
fun clickShareCollectionButton() = onView(withId(R.id.collection_share_button)).click()
fun verifyCollectionMenuIsVisible(visible: Boolean) {
collectionThreeDotButton()
.check(
@ -240,6 +246,13 @@ class CollectionRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickShareCollectionButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
shareCollectionButton().click()
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
}
}
}

@ -12,6 +12,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -132,6 +133,8 @@ private fun assertDownloadNotificationPopup() {
mDevice.waitNotNull(Until.findObjects(By.text("Open")), TestAssetHelper.waitingTime)
onView(withId(R.id.download_dialog_title))
.check(matches(withText(CoreMatchers.containsString("Download completed"))))
onView(withId(R.id.download_dialog_filename))
.check(matches(ViewMatchers.isCompletelyDisplayed()))
}
private fun closePromptButton() =

@ -9,7 +9,9 @@ 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.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
@ -19,6 +21,7 @@ import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import junit.framework.TestCase.assertTrue
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.not
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.packageName
@ -39,6 +42,8 @@ class EnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionDetailsStatus(status: String) =
assertEnhancedTrackingProtectionDetailsStatus(status)
fun verifyETPSwitchVisibility(visible: Boolean) = assertETPSwitchVisibility(visible)
fun verifyTrackingCookiesBlocked() = assertTrackingCookiesBlocked()
fun verifyFingerprintersBlocked() = assertFingerprintersBlocked()
@ -69,6 +74,7 @@ class EnhancedTrackingProtectionRobot {
fun openEnhancedTrackingProtectionSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
openEnhancedTrackingProtectionSheet().waitForExists(waitingTime)
openEnhancedTrackingProtectionSheet().click()
assertSecuritySheetIsCompletelyDisplayed()
EnhancedTrackingProtectionRobot().interact()
return Transition()
@ -83,7 +89,7 @@ class EnhancedTrackingProtectionRobot {
}
fun disableEnhancedTrackingProtectionFromSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
disableEnhancedTrackingProtection().click()
enhancedTrackingProtectionSwitch().click()
EnhancedTrackingProtectionRobot().interact()
return Transition()
@ -113,6 +119,16 @@ fun enhancedTrackingProtection(interact: EnhancedTrackingProtectionRobot.() -> U
return EnhancedTrackingProtectionRobot.Transition()
}
private fun assertETPSwitchVisibility(visible: Boolean) {
if (visible) {
enhancedTrackingProtectionSwitch()
.check(matches(isDisplayed()))
} else {
enhancedTrackingProtectionSwitch()
.check(matches(not(isDisplayed())))
}
}
private fun assertEnhancedTrackingProtectionSheetStatus(status: String, state: Boolean) {
mDevice.waitNotNull(Until.findObjects(By.textContains(status)))
onView(ViewMatchers.withResourceName("switch_widget")).check(
@ -131,7 +147,7 @@ private fun assertEnhancedTrackingProtectionDetailsStatus(status: String) {
private fun openEnhancedTrackingProtectionSheet() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_security_indicator"))
private fun disableEnhancedTrackingProtection() =
private fun enhancedTrackingProtectionSwitch() =
onView(ViewMatchers.withResourceName("switch_widget"))
private fun trackingProtectionSettingsButton() =
@ -169,3 +185,10 @@ private fun assertTrackingContentBlocked() {
}
private fun trackingContentBlockListButton() = onView(withId(R.id.tracking_content))
private fun assertSecuritySheetIsCompletelyDisplayed() {
mDevice.findObject(UiSelector().description("Quick settings sheet"))
.waitForExists(waitingTime)
onView(withContentDescription("Quick settings sheet"))
.check(matches(isCompletelyDisplayed()))
}

@ -10,14 +10,15 @@ import android.graphics.Bitmap
import android.widget.EditText
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withHint
@ -25,7 +26,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable
@ -116,6 +116,11 @@ class HomeScreenRobot {
fun verifyExistingTopSitesTabs(title: String) = assertExistingTopSitesTabs(title)
fun verifyTopSiteContextMenuItems() = assertTopSiteContextMenuItems()
fun verifyJumpBackInSectionIsDisplayed() = assertJumpBackInSectionIsDisplayed()
fun verifyJumpBackInSectionIsNotDisplayed() = assertJumpBackInSectionIsNotDisplayed()
fun verifyRecentBookmarksSectionIsDisplayed() = assertRecentBookmarksSectionIsDisplayed()
fun verifyRecentBookmarksSectionIsNotDisplayed() = assertRecentBookmarksSectionIsNotDisplayed()
// Collections elements
fun verifyCollectionIsDisplayed(title: String, collectionExists: Boolean = true) {
if (collectionExists) {
@ -129,8 +134,6 @@ class HomeScreenRobot {
fun verifyCollectionIcon() = onView(withId(R.id.collection_icon)).check(matches(isDisplayed()))
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun togglePrivateBrowsingModeOnOff() {
onView(ViewMatchers.withResourceName("privateBrowsingButton"))
.perform(click())
@ -146,10 +149,13 @@ class HomeScreenRobot {
mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime)
}
fun snackBarButtonClick(expectedText: String) {
onView(allOf(withId(R.id.snackbar_btn), withText(expectedText))).check(
matches(withEffectiveVisibility(Visibility.VISIBLE))
).perform(click())
fun clickUndoCollectionDeletion(expectedText: String) {
onView(
allOf(
withId(R.id.snackbar_btn),
withText(expectedText)
)
).click()
}
class Transition {
@ -187,8 +193,7 @@ class HomeScreenRobot {
}
fun openSearch(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
.waitForExists(waitingTime)
navigationToolbar().waitForExists(waitingTime)
navigationToolbar().click()
SearchRobot().interact()
@ -317,13 +322,13 @@ class HomeScreenRobot {
}
fun expandCollection(title: String, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
try {
mDevice.waitNotNull(findObject(text(title)), waitingTime)
collectionTitle(title).click()
} catch (e: NoMatchingViewException) {
scrollToElementByText(title)
collectionTitle(title).click()
}
// Depending on the screen dimensions collections might report as visible on screen
// but actually have the bottom toolbar above so interactions with collections might fail.
// As a quick solution we'll try scrolling to the element below collection on the homescreen
// so that they are displayed above in their entirety.
scrollToElementByText(appContext.getString(R.string.pocket_stories_header_1))
collectionTitle(title).click()
CollectionRobot().interact()
return CollectionRobot.Transition()
@ -578,12 +583,15 @@ private fun assertTopSiteContextMenuItems() {
)
}
private fun assertShareTabsOverlay() {
onView(withId(R.id.shared_site_list)).check(matches(isDisplayed()))
onView(withId(R.id.share_tab_title)).check(matches(isDisplayed()))
onView(withId(R.id.share_tab_favicon)).check(matches(isDisplayed()))
onView(withId(R.id.share_tab_url)).check(matches(isDisplayed()))
}
private fun assertJumpBackInSectionIsDisplayed() = jumpBackInSection().check(matches(isDisplayed()))
private fun assertJumpBackInSectionIsNotDisplayed() = jumpBackInSection().check(doesNotExist())
private fun assertRecentBookmarksSectionIsDisplayed() =
recentBookmarksSection().check(matches(isDisplayed()))
private fun assertRecentBookmarksSectionIsNotDisplayed() =
recentBookmarksSection().check(doesNotExist())
private fun privateBrowsingButton() = onView(withId(R.id.privateBrowsingButton))
@ -591,6 +599,22 @@ private fun saveTabsToCollectionButton() = onView(withId(R.id.add_tabs_to_collec
private fun tabsCounter() = onView(withId(R.id.tab_button))
private fun jumpBackInSection() =
onView(
allOf(
withText(R.string.recent_tabs_header),
hasSibling(withText(R.string.recent_tabs_show_all))
)
)
private fun recentBookmarksSection() =
onView(
allOf(
withText(R.string.recent_bookmarks_title),
hasSibling(withText(R.string.recently_saved_show_all))
)
)
private fun startBrowsingButton(): UiObject {
val startBrowsingButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/finish_button"))
homeScreenList()

@ -7,6 +7,7 @@
package org.mozilla.fenix.ui.robots
import android.net.Uri
import android.os.Build
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
@ -150,25 +151,6 @@ class NavigationToolbarRobot {
return TabDrawerRobot.Transition()
}
fun openNewTabAndEnterToBrowser(
url: Uri,
interact: BrowserRobot.() -> Unit
): BrowserRobot.Transition {
sessionLoadedIdlingResource = SessionLoadedIdlingResource()
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/toolbar")), waitingTime)
urlBar().click()
awesomeBar().setText(url.toString())
mDevice.pressEnter()
runWithIdleRes(sessionLoadedIdlingResource) {
onView(ViewMatchers.withResourceName("browserLayout"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun visitLinkFromClipboard(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/mozac_browser_toolbar_clear_view")),
@ -181,10 +163,15 @@ class NavigationToolbarRobot {
waitingTime
)
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/clipboard_url")),
waitingTime
)
// On Android 12 or above we don't SHOW the URL unless the user requests to do so.
// 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")),
waitingTime
)
}
fillLinkButton().click()
BrowserRobot().interact()

@ -7,7 +7,6 @@ package org.mozilla.fenix.ui.robots
import android.net.Uri
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.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -44,8 +43,9 @@ class RecentlyClosedTabsRobot {
fun clickDeleteRecentlyClosedTabs() = recentlyClosedTabsDeleteButton().click()
class Transition {
fun clickOpenInNewTab(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
recentlyClosedTabsPageTitle().click()
fun clickRecentlyClosedItem(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
recentlyClosedTabsPageTitle(title).click()
mDevice.waitForIdle()
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -65,16 +65,16 @@ private fun assertRecentlyClosedTabsMenuView() {
)
}
private fun assertEmptyRecentlyClosedTabsList() =
private fun assertEmptyRecentlyClosedTabsList() {
mDevice.waitForIdle()
onView(
allOf(
withId(R.id.recently_closed_empty_view),
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)
)
)
.check(
matches(withText("No recently closed tabs here"))
withText(R.string.recently_closed_empty_message)
)
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertPageUrl(expectedUrl: Uri) = onView(
allOf(
@ -88,21 +88,16 @@ private fun assertPageUrl(expectedUrl: Uri) = onView(
matches(withText(Matchers.containsString(expectedUrl.toString())))
)
private fun recentlyClosedTabsPageTitle() = onView(
private fun recentlyClosedTabsPageTitle(title: String) = onView(
allOf(
withId(R.id.title),
withText("Test_Page_1")
withText(title)
)
)
private fun assertRecentlyClosedTabsPageTitle(title: String) {
recentlyClosedTabsPageTitle()
.check(
matches(withEffectiveVisibility(Visibility.VISIBLE))
)
.check(
matches(withText(title))
)
recentlyClosedTabsPageTitle(title)
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun recentlyClosedTabsDeleteButton() =

@ -11,6 +11,7 @@ import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.android.ComposeNotIdleException
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithTag
@ -56,7 +57,7 @@ class SearchRobot {
fun verifySearchView() = assertSearchView()
fun verifyBrowserToolbar() = assertBrowserToolbarEditView()
fun verifyScanButton() = assertScanButton()
fun verifySearchEngineButton() = assertSearchEngineButton()
fun verifySearchEngineButton() = assertSearchButton()
fun verifySearchWithText() = assertSearchWithText()
fun verifySearchEngineResults(rule: ComposeTestRule, searchEngineName: String, count: Int) =
assertSearchEngineResults(rule, searchEngineName, count)
@ -103,7 +104,12 @@ class SearchRobot {
}
fun typeSearch(searchTerm: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view")
).waitForExists(waitingTime)
browserToolbarEditView().setText(searchTerm)
mDevice.waitForIdle()
}
@ -208,7 +214,7 @@ class SearchRobot {
fun submitQuery(query: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
sessionLoadedIdlingResource = SessionLoadedIdlingResource()
mDevice.waitForIdle()
searchWrapper().waitForExists(waitingTime)
browserToolbarEditView().setText(query)
mDevice.pressEnter()
@ -317,7 +323,7 @@ private fun assertScanButton() =
).waitForExists(waitingTime)
)
private fun assertSearchEngineButton() =
private fun assertSearchButton() =
assertTrue(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/search_engines_shortcut_button")
@ -346,16 +352,13 @@ fun searchScreen(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
return SearchRobot.Transition()
}
private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean) = {
mDevice.waitNotNull(
Until.findObject(
By.text("Search Engine")
),
waitingTime
)
private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean): () -> Unit = {
searchWrapper().waitForExists(waitingTime)
assertEquals(
"Keyboard not shown",
isExpectedToBeVisible,
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice
.executeShellCommand("dumpsys input_method | grep mInputShown")
.contains("mInputShown=true")
)
@ -383,25 +386,35 @@ private fun ComposeTestRule.assertSearchEngineList() {
onNodeWithText("Wikipedia")
.assertExists()
.assertIsDisplayed()
onNodeWithText("eBay")
.assertExists()
.assertIsDisplayed()
}
@OptIn(ExperimentalTestApi::class)
private fun assertEngineListShortcutContains(rule: ComposeTestRule, searchEngineName: String) {
rule.waitForIdle()
mDevice.waitForObjects(
try {
rule.waitForIdle()
} catch (e: ComposeNotIdleException) {
mDevice.pressBack()
navigationToolbar {
}.clickUrlbar {
clickSearchEngineShortcutButton()
}
} finally {
mDevice.findObject(
UiSelector().textContains("Google")
)
)
).waitForExists(waitingTime)
rule.onNodeWithTag("mozac.awesomebar.suggestions")
.performScrollToIndex(5)
rule.onNodeWithTag("mozac.awesomebar.suggestions")
.performScrollToIndex(5)
rule.onNodeWithText(searchEngineName)
.assertExists()
.assertIsDisplayed()
.assertHasClickAction()
rule.onNodeWithText(searchEngineName)
.assertExists()
.assertIsDisplayed()
.assertHasClickAction()
}
}
private fun ComposeTestRule.selectDefaultSearchEngine(searchEngine: String) {
@ -434,5 +447,3 @@ private fun assertPastedToolbarText(expectedText: String) {
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))

@ -27,13 +27,18 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.textContains
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_PLAY_SERVICES
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.assertIsEnabled
import org.mozilla.fenix.helpers.click
@ -45,15 +50,17 @@ import org.mozilla.fenix.ui.robots.SettingsRobot.Companion.DEFAULT_APPS_SETTINGS
class SettingsRobot {
// BASICS SECTION
fun verifyBasicsHeading() = assertGeneralHeading()
fun verifyGeneralHeading() = assertGeneralHeading()
fun verifySearchEngineButton() = assertSearchEngineButton()
fun verifyThemeButton() = assertCustomizeButton()
fun verifySearchButton() = assertSearchButton()
fun verifyCustomizeButton() = assertCustomizeButton()
fun verifyThemeSelected() = assertThemeSelected()
fun verifyAccessibilityButton() = assertAccessibilityButton()
fun verifySetAsDefaultBrowserButton() = assertSetAsDefaultBrowserButton()
fun verifyDefaultBrowserItem() = assertDefaultBrowserItem()
fun verifyTabsItem() = assertTabsItem()
fun verifyTabsButton() = assertTabsButton()
fun verifyHomepageButton() = assertHomepageButton()
fun verifyCreditCardsButton() = assertCreditCardsButton()
fun verifyLanguageButton() = assertLanguageButton()
fun verifyDefaultBrowserIsDisaled() = assertDefaultBrowserIsDisabled()
fun clickDefaultBrowserSwitch() = toggleDefaultBrowserSwitch()
fun verifyAndroidDefaultAppsMenuAppears() = assertAndroidDefaultAppsMenuAppears()
@ -62,7 +69,7 @@ class SettingsRobot {
fun verifyPrivacyHeading() = assertPrivacyHeading()
fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton()
fun verifyLoginsButton() = assertLoginsButton()
fun verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
fun verifyEnhancedTrackingProtectionValue(state: String) =
assertEnhancedTrackingProtectionValue(state)
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
@ -76,6 +83,7 @@ class SettingsRobot {
fun verifyOpenLinksInAppsButton() = assertOpenLinksInAppsButton()
fun verifyOpenLinksInAppsSwitchDefault() = assertOpenLinksInAppsValue()
fun verifySettingsView() = assertSettingsView()
fun verifySettingsToolbar() = assertSettingsToolbar()
// ADVANCED SECTION
fun verifyAdvancedHeading() = assertAdvancedHeading()
@ -88,8 +96,8 @@ class SettingsRobot {
// ABOUT SECTION
fun verifyAboutHeading() = assertAboutHeading()
fun verifyRateOnGooglePlay() = assertRateOnGooglePlay()
fun verifyAboutFirefoxPreview() = assertAboutFirefoxPreview()
fun verifyRateOnGooglePlay() = assertTrue(rateOnGooglePlayHeading().waitForExists(waitingTime))
fun verifyAboutFirefoxPreview() = assertTrue(aboutFirefoxHeading().waitForExists(waitingTime))
fun verifyGooglePlayRedirect() = assertGooglePlayRedirect()
class Transition {
@ -113,7 +121,7 @@ class SettingsRobot {
fun openAboutFirefoxPreview(interact: SettingsSubMenuAboutRobot.() -> Unit):
SettingsSubMenuAboutRobot.Transition {
assertAboutFirefoxPreview().click()
aboutFirefoxHeading().click()
SettingsSubMenuAboutRobot().interact()
return SettingsSubMenuAboutRobot.Transition()
@ -147,6 +155,15 @@ class SettingsRobot {
return SettingsSubMenuTabsRobot.Transition()
}
fun openHomepageSubMenu(interact: SettingsSubMenuHomepageRobot.() -> Unit): SettingsSubMenuHomepageRobot.Transition {
mDevice.findObject(UiSelector().textContains("Homepage")).waitForExists(waitingTime)
onView(withText(R.string.preferences_home_2)).click()
SettingsSubMenuHomepageRobot().interact()
return SettingsSubMenuHomepageRobot.Transition()
}
fun openAccessibilitySubMenu(interact: SettingsSubMenuAccessibilityRobot.() -> Unit): SettingsSubMenuAccessibilityRobot.Transition {
scrollToElementByText("Accessibility")
@ -286,18 +303,37 @@ private fun assertSettingsView() {
}
// GENERAL SECTION
private fun assertSettingsToolbar() =
onView(
CoreMatchers.allOf(
withId(R.id.navigationToolbar),
hasDescendant(ViewMatchers.withContentDescription(R.string.action_bar_up_description)),
hasDescendant(withText(R.string.settings))
)
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertGeneralHeading() {
scrollToElementByText("General")
onView(withText("General"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertSearchEngineButton() {
private fun assertSearchButton() {
mDevice.wait(Until.findObject(By.text("Search")), waitingTime)
onView(withText("Search"))
onView(withText(R.string.preferences_search))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertHomepageButton() =
onView(withText(R.string.preferences_home_2)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertCreditCardsButton() =
onView(withText(R.string.preferences_credit_cards)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertLanguageButton() =
onView(withText(R.string.preferences_language)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertCustomizeButton() = onView(withText("Customize"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
@ -328,15 +364,9 @@ private fun assertAndroidDefaultAppsMenuAppears() {
intended(IntentMatchers.hasAction(DEFAULT_APPS_SETTINGS_ACTION))
}
private fun assertDefaultBrowserItem() {
mDevice.wait(Until.findObject(By.text("Set as default browser")), waitingTime)
onView(withText("Set as default browser"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertTabsItem() {
private fun assertTabsButton() {
mDevice.wait(Until.findObject(By.text("Tabs")), waitingTime)
onView(withText("Tabs"))
onView(withText(R.string.preferences_tabs))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
@ -361,9 +391,9 @@ private fun assertEnhancedTrackingProtectionValue(state: String) {
onView(withText(state)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertLoginsButton() {
private fun assertLoginsAndPasswordsButton() {
scrollToElementByText("Logins and passwords")
onView(withText("Logins and passwords"))
onView(withText(R.string.preferences_passwords_logins_and_passwords))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
@ -470,24 +500,32 @@ private fun assertAboutHeading(): ViewInteraction {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertRateOnGooglePlay(): ViewInteraction {
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(hasDescendant(withText("Rate on Google Play"))))
return onView(withText("Rate on Google Play"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun rateOnGooglePlayHeading(): UiObject {
val rateOnGooglePlay = mDevice.findObject(UiSelector().text("Rate on Google Play"))
scrollToElementByText("Rate on Google Play")
if (!rateOnGooglePlay.exists()) {
settingsList().swipeUp(2)
rateOnGooglePlay.waitForExists(waitingTime)
}
return rateOnGooglePlay
}
private fun assertAboutFirefoxPreview(): ViewInteraction {
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(hasDescendant(withText("About $appName"))))
return onView(withText("About $appName"))
.check(matches(isDisplayed()))
private fun aboutFirefoxHeading(): UiObject {
val aboutFirefoxHeading = mDevice.findObject(UiSelector().text("About $appName"))
scrollToElementByText("About $appName")
if (!aboutFirefoxHeading.exists()) {
settingsList().swipeUp(2)
aboutFirefoxHeading.waitForExists(waitingTime)
}
return aboutFirefoxHeading
}
fun swipeToBottom() = onView(withId(R.id.recycler_view)).perform(ViewActions.swipeUp())
fun clickRateButtonGooglePlay() {
assertRateOnGooglePlay().click()
rateOnGooglePlayHeading().click()
}
private fun assertGooglePlayRedirect() {
@ -502,3 +540,6 @@ private fun addonsManagerButton() = onView(withText(R.string.preferences_addons)
private fun goBackButton() =
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))
private fun settingsList() =
UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))

@ -67,12 +67,7 @@ private fun assertFirefoxPreviewPage() {
}
private fun navigateBackToAboutPage(itemToInteract: () -> Unit) {
browserScreen {
}.openTabDrawer {
closeTab()
}
homeScreen {
navigationToolbar {
}.openThreeDotMenu {
}.openSettings {
}.openAboutFirefoxPreview {
@ -161,8 +156,7 @@ private fun assertSupport() {
}
private fun assertCrashes() {
browserScreen {
navigationToolbar {
}.openThreeDotMenu {
}.openSettings {
}.openAboutFirefoxPreview {

@ -6,16 +6,19 @@ 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.Visibility
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.espresso.matcher.ViewMatchers.Visibility
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import junit.framework.TestCase.assertTrue
import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.click
/**
* Implementation of Robot Pattern for the settings Enhanced Tracking Protection Exceptions sub menu.
@ -24,11 +27,11 @@ class SettingsSubMenuEnhancedTrackingProtectionExceptionsRobot {
fun verifyNavigationToolBarHeader() = assertNavigationToolBarHeader()
fun verifyDefault() = assertExceptionDefault()!!
fun verifyDefault() = assertExceptionDefault()
fun verifyExceptionLearnMoreText() = assertExceptionLearnMoreText()!!
fun verifyExceptionLearnMoreText() = assertExceptionLearnMoreText()
fun verifyListedURL(url: String) = assertExceptionURL(url)!!
fun verifyListedURL(url: String) = assertExceptionURL(url)
fun verifyEnhancedTrackingProtectionProtectionExceptionsSubMenuItems() {
verifyDefault()
@ -63,13 +66,25 @@ private fun assertNavigationToolBarHeader() {
}
private fun assertExceptionDefault() =
onView(allOf(withText(R.string.exceptions_empty_message_description)))
assertTrue(
mDevice.findObject(
UiSelector().text("Exceptions let you disable tracking protection for selected sites.")
).waitForExists(waitingTime)
)
private fun assertExceptionLearnMoreText() =
onView(allOf(withText(R.string.exceptions_empty_message_learn_more_link)))
assertTrue(
mDevice.findObject(
UiSelector().text("Learn more")
).waitForExists(waitingTime)
)
private fun assertExceptionURL(url: String) =
onView(allOf(withText(url)))
assertTrue(
mDevice.findObject(
UiSelector().textContains(url.replace("http://", "https://"))
).waitForExists(waitingTime)
)
private fun disableExceptionsButton() =
onView(allOf(withId(R.id.removeAllExceptions)))
onView(withId(R.id.removeAllExceptions)).click()

@ -25,7 +25,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.assertIsChecked
@ -48,7 +47,7 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionTextWithSwitchWidget() = assertEnhancedTrackingProtectionTextWithSwitchWidget()
fun verifyEnhancedTrackingProtectionOptions() = assertEnhancedTrackingProtectionOptions()
fun verifyEnhancedTrackingProtectionOptionsEnabled(enabled: Boolean = true) = assertEnhancedTrackingProtectionOptionsState(enabled)
fun verifyTrackingProtectionSwitchEnabled() = assertTrackingProtectionSwitchEnabled()
@ -63,7 +62,7 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
verifyEnhancedTrackingProtectionTextWithSwitchWidget()
verifyTrackingProtectionSwitchEnabled()
verifyRadioButtonDefaults()
verifyEnhancedTrackingProtectionOptions()
verifyEnhancedTrackingProtectionOptionsEnabled()
}
fun verifyCustomTrackingProtectionSettings() = assertCustomTrackingProtectionSettings()
@ -153,48 +152,24 @@ private fun assertEnhancedTrackingProtectionTextWithSwitchWidget() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertEnhancedTrackingProtectionOptions() {
private fun assertEnhancedTrackingProtectionOptionsState(enabled: Boolean) {
onView(withText("Standard (default)"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_standard_description_4))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Strict"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_strict_description_3))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText("Custom"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
val customText =
"Choose which trackers and scripts to block."
onView(withText(customText))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertEnhancedTrackingProtectionOptionsGrayedOut() {
onView(withText("Standard (default)"))
.check(matches(not(isEnabled(true))))
.check(matches(isEnabled(enabled)))
onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_standard_description_4))
.check(matches(not(isEnabled(true))))
.check(matches(isEnabled(enabled)))
onView(withText("Strict"))
.check(matches(not(isEnabled(true))))
.check(matches(isEnabled(enabled)))
onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_strict_description_3))
.check(matches(not(isEnabled(true))))
.check(matches(isEnabled(enabled)))
onView(withText("Custom"))
.check(matches(not(isEnabled(true))))
.check(matches(isEnabled(enabled)))
val customText =
"Choose which trackers and scripts to block."
onView(withText(customText))
.check(matches(not(isEnabled(true))))
onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_custom_description_2))
.check(matches(isEnabled(enabled)))
}
private fun assertTrackingProtectionSwitchEnabled() {
@ -248,7 +223,7 @@ private fun assertCustomTrackingProtectionSettings() {
private fun cookiesCheckbox() = onView(withText("Cookies"))
private fun cookiesDropDownMenuDefault() = onView(withText("All cookies (will cause websites to break)"))
private fun cookiesDropDownMenuDefault() = onView(withText("Cross-site and social media trackers"))
private fun trackingContentCheckbox() = onView(withText("Tracking content"))

@ -0,0 +1,111 @@
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.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
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 org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.click
/**
* Implementation of Robot Pattern for the settings Homepage sub menu.
*/
class SettingsSubMenuHomepageRobot {
fun verifyHomePageView() {
assertMostVisitedTopSitesButton()
assertJumpBackInButton()
assertRecentBookmarksButton()
assertRecentSearchesButton()
assertPocketButton()
assertOpeningScreenHeading()
assertHomepageButton()
assertLastTabButton()
assertHomepageAfterFourHoursButton()
}
fun clickJumpBackInButton() = jumpBackInButton().click()
fun clickRecentBookmarksButton() = recentBookmarksButton().click()
fun clickStartOnHomepageButton() = homepageButton().click()
fun clickStartOnLastTabButton() = lastTabButton().click()
class Transition {
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
goBackButton().click()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
}
}
private fun mostVisitedTopSitesButton() =
onView(allOf(withText(R.string.top_sites_toggle_top_recent_sites_3)))
private fun jumpBackInButton() =
onView(allOf(withText(R.string.customize_toggle_jump_back_in)))
private fun recentBookmarksButton() =
onView(allOf(withText(R.string.customize_toggle_recent_bookmarks)))
private fun recentSearchesButton() =
onView(allOf(withText(R.string.customize_toggle_recently_visited)))
private fun pocketButton() =
onView(allOf(withText(R.string.customize_toggle_pocket)))
private fun openingScreenHeading() = onView(withText(R.string.preferences_opening_screen))
private fun homepageButton() =
onView(
allOf(
withId(R.id.title),
withText(R.string.opening_screen_homepage),
hasSibling(withId(R.id.radio_button))
)
)
private fun lastTabButton() =
onView(
allOf(
withId(R.id.title),
withText(R.string.opening_screen_last_tab),
hasSibling(withId(R.id.radio_button))
)
)
private fun homepageAfterFourHoursButton() =
onView(
allOf(
withId(R.id.title),
withText(R.string.opening_screen_after_four_hours_of_inactivity),
hasSibling(withId(R.id.radio_button))
)
)
private fun goBackButton() = onView(allOf(withContentDescription(R.string.action_bar_up_description)))
private fun assertMostVisitedTopSitesButton() =
mostVisitedTopSitesButton().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 assertPocketButton() =
pocketButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertOpeningScreenHeading() =
openingScreenHeading().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertHomepageButton() =
homepageButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertLastTabButton() =
lastTabButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertHomepageAfterFourHoursButton() =
homepageAfterFourHoursButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))

@ -8,9 +8,7 @@ package org.mozilla.fenix.ui.robots
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.clearText
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
@ -23,6 +21,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers
@ -30,12 +29,14 @@ import org.hamcrest.Matchers.allOf
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
/**
* Implementation of Robot Pattern for the settings search sub menu.
*/
class SettingsSubMenuSearchRobot {
fun verifySearchToolbar() = assertSearchToolbar()
fun verifyDefaultSearchEngineHeader() = assertDefaultSearchEngineHeader()
fun verifySearchEngineList() = assertSearchEngineList()
fun verifyShowSearchSuggestions() = assertShowSearchSuggestions()
@ -73,12 +74,56 @@ class SettingsSubMenuSearchRobot {
fun selectAddCustomSearchEngine() = onView(withText("Other")).click()
fun typeCustomEngineDetails(engineName: String, engineURL: String) {
onView(withId(R.id.edit_engine_name))
.perform(clearText())
.perform(typeText(engineName))
onView(withId(R.id.edit_search_string))
.perform(clearText())
.perform(typeText(engineURL))
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).setText(engineName)
mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
mDevice.findObject(By.res("$packageName:id/edit_search_string")).setText(engineURL)
try {
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/edit_engine_name")
.text(engineName)
).waitForExists(waitingTime)
)
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/edit_search_string")
.text(engineURL)
).waitForExists(waitingTime)
)
} catch (e: AssertionError) {
println("The name or the search string were not set properly")
// Lets again set both name and search string
goBackButton().click()
openAddSearchEngineMenu()
selectAddCustomSearchEngine()
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
mDevice.findObject(By.res("$packageName:id/edit_engine_name")).setText(engineName)
mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
mDevice.findObject(By.res("$packageName:id/edit_search_string")).setText(engineURL)
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/edit_engine_name")
.text(engineName)
).waitForExists(waitingTime)
)
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/edit_search_string")
.text(engineURL)
).waitForExists(waitingTime)
)
}
}
fun openEngineOverflowMenu(searchEngineName: String) {
@ -112,6 +157,15 @@ class SettingsSubMenuSearchRobot {
}
}
private fun assertSearchToolbar() =
onView(
allOf(
withId(R.id.navigationToolbar),
hasDescendant(withContentDescription(R.string.action_bar_up_description)),
hasDescendant(withText(R.string.preferences_search))
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertDefaultSearchEngineHeader() =
onView(withText("Default search engine"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))

@ -16,8 +16,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.R
/**
* Implementation of Robot Pattern for the settings Tabs sub menu.
@ -28,12 +27,7 @@ class SettingsSubMenuTabsRobot {
fun verifyCloseTabsOptions() = assertCloseTabsOptions()
fun verifyStartOnHomeOptions() = assertStartOnHomeOptions()
fun clickAlwaysStartOnHomeToggle() {
scrollToElementByText("Move old tabs to inactive")
alwaysStartOnHomeToggle().click()
}
fun verifyMoveOldTabsToInactiveOptions() = assertMoveOldTabsToInactiveOptions()
class Transition {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@ -57,11 +51,15 @@ private fun assertTabViewOptions() {
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
searchTermTabGroupsToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
searchGroupsDescription()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertCloseTabsOptions() {
closeTabsHeading()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
neverToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
afterOneDayToggle()
.check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
afterOneWeekToggle()
@ -70,14 +68,10 @@ private fun assertCloseTabsOptions() {
.check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertStartOnHomeOptions() {
// Scroll to ensure all the items are visible.
scrollToElementByText("Never")
startOnHomeHeading()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
afterFourHoursToggle()
private fun assertMoveOldTabsToInactiveOptions() {
moveOldTabsToInactiveHeading()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
alwaysStartOnHomeToggle()
moveOldTabsToInactiveToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
@ -89,23 +83,24 @@ private fun gridToggle() = onView(withText("Grid"))
private fun searchTermTabGroupsToggle() = onView(withText("Search groups"))
private fun searchGroupsDescription() = onView(withText("Group related sites together"))
private fun closeTabsHeading() = onView(withText("Close tabs"))
private fun manuallyToggle() = onView(withText("Manually"))
private fun neverToggle() = onView(withText("Never"))
private fun afterOneDayToggle() = onView(withText("After one day"))
private fun afterOneWeekToggle() = onView(withText("After one week"))
private fun afterOneMonthToggle() = onView(withText("After one month"))
private fun startOnHomeHeading() = onView(withText("Start on home"))
private fun afterFourHoursToggle() = onView(withText("After four hours"))
private fun alwaysStartOnHomeToggle() = onView(withText("Always"))
private fun moveOldTabsToInactiveHeading() = onView(withText("Move old tabs to inactive"))
private fun neverStartOnHomeToggle() = onView(withText("Never"))
private fun moveOldTabsToInactiveToggle() =
onView(withText(R.string.preferences_inactive_tabs_title))
private fun goBackButton() =
onView(allOf(ViewMatchers.withContentDescription("Navigate up")))

@ -0,0 +1,91 @@
package org.mozilla.fenix.ui.robots
import android.content.Intent
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
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.withId
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.Matchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.ext.waitNotNull
class ShareOverlayRobot {
// This function verifies the share layout when more than one tab is shared - a list of tabs is shown
fun verifyShareTabsOverlay(vararg tabsTitles: String) {
onView(withId(R.id.shared_site_list))
.check(matches(isDisplayed()))
for (tabs in tabsTitles) {
onView(withText(tabs))
.check(
matches(
allOf(
hasSibling(withId(R.id.share_tab_favicon)),
hasSibling(withId(R.id.share_tab_url))
)
)
)
}
}
// This function verifies the share layout when a single tab is shared - no tab info shown
fun verifyShareTabLayout() = assertShareTabLayout()
// this verifies the Android sharing layout - not customized for sharing tabs
fun verifyAndroidShareLayout() {
mDevice.waitNotNull(Until.findObject(By.res("android:id/resolver_list")))
}
fun selectAppToShareWith(appName: String) =
mDevice.findObject(UiSelector().text(appName)).clickAndWaitForNewWindow()
fun verifySendToDeviceTitle() = assertSendToDeviceTitle()
fun verifyShareALinkTitle() = assertShareALinkTitle()
fun verifySharedTabsIntent(text: String, subject: String) {
Intents.intended(
allOf(
IntentMatchers.hasExtra(Intent.EXTRA_TEXT, text),
IntentMatchers.hasExtra(Intent.EXTRA_SUBJECT, subject)
)
)
}
class Transition
}
private fun shareTabsLayout() = onView(withResourceName("shareWrapper"))
private fun assertShareTabLayout() =
shareTabsLayout().check(matches(isDisplayed()))
private fun sendToDeviceTitle() =
onView(
allOf(
withText("SEND TO DEVICE"),
withResourceName("accountHeaderText")
)
)
private fun assertSendToDeviceTitle() = sendToDeviceTitle()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun shareALinkTitle() =
onView(
allOf(
withText("ALL ACTIONS"),
withResourceName("apps_link_header")
)
)
private fun assertShareALinkTitle() = shareALinkTitle()

@ -8,11 +8,9 @@ package org.mozilla.fenix.ui.robots
import android.content.Context
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.GeneralLocation
@ -21,9 +19,8 @@ import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers
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
@ -36,19 +33,21 @@ import androidx.test.uiautomator.UiSelector
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.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
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.clickAtLocationInView
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.idlingresource.BottomSheetBehaviorStateIdlingResource
import org.mozilla.fenix.helpers.isSelected
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorHalfExpandedMaxRatioMatcher
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorStateMatcher
@ -69,17 +68,27 @@ class TabDrawerRobot {
}
fun verifyNormalBrowsingButtonIsDisplayed() = assertNormalBrowsingButton()
fun verifyNormalBrowsingButtonIsSelected(isSelected: Boolean) =
assertNormalBrowsingButtonIsSelected(isSelected)
fun verifyPrivateBrowsingButtonIsSelected(isSelected: Boolean) =
assertPrivateBrowsingButtonIsSelected(isSelected)
fun verifySyncedTabsButtonIsSelected(isSelected: Boolean) =
assertSyncedTabsButtonIsSelected(isSelected)
fun verifyExistingOpenTabs(title: String) = assertExistingOpenTabs(title)
fun verifyCloseTabsButton(title: String) = assertCloseTabsButton(title)
fun verifyExistingTabList() = assertExistingTabList()
fun verifyNoTabsOpened() = assertNoTabsOpenedText()
fun verifyNoOpenTabsInNormalBrowsing() = assertNoOpenTabsInNormalBrowsing()
fun verifyNoOpenTabsInPrivateBrowsing() = assertNoOpenTabsInPrivateBrowsing()
fun verifyPrivateModeSelected() = assertPrivateModeSelected()
fun verifyNormalModeSelected() = assertNormalModeSelected()
fun verifyNewTabButton() = assertNewTabButton()
fun verifyNormalBrowsingNewTabButton() = assertNormalBrowsingNewTabButton()
fun verifyPrivateBrowsingNewTabButton() = assertPrivateBrowsingNewTabButton()
fun verifyEmptyTabsTrayMenuButtons() = assertEmptyTabsTrayMenuButtons()
fun verifySelectTabsButton() = assertSelectTabsButton()
fun verifyTabTrayOverflowMenu(visibility: Boolean) = assertTabTrayOverflowButton(visibility)
fun verifyTabsTrayCounter() = assertTabsTrayCounter()
fun verifyTabTrayIsOpened() = assertTabTrayDoesExist()
fun verifyTabTrayIsClosed() = assertTabTrayDoesNotExist()
@ -104,7 +113,12 @@ class TabDrawerRobot {
fun swipeTabRight(title: String) {
var retries = 0 // number of retries before failing, will stop at 2
while (mDevice.findObject(UiSelector().text(title)).exists() && retries < 3) {
while (!mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.text(title)
).waitUntilGone(waitingTimeShort) && retries < 2
) {
tab(title).perform(ViewActions.swipeRight())
retries++
}
@ -112,57 +126,105 @@ class TabDrawerRobot {
fun swipeTabLeft(title: String) {
var retries = 0 // number of retries before failing, will stop at 2
while (mDevice.findObject(UiSelector().text(title)).exists() && retries < 3) {
while (!mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.text(title)
).waitUntilGone(waitingTimeShort) && retries < 2
) {
tab(title).perform(ViewActions.swipeLeft())
retries++
}
}
fun closeTabViaXButton(title: String) {
mDevice.findObject(UiSelector().text(title)).waitForExists(waitingTime)
var retries = 0 // number of retries before failing, will stop at 2
do {
val closeButton = onView(
allOf(
withId(R.id.mozac_browser_tabstray_close),
withContentDescription("Close tab $title")
)
)
closeButton.perform(click())
retries++
} while (mDevice.findObject(UiSelector().text(title)).exists() && retries < 3)
}
fun verifySnackBarText(expectedText: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull(findObject(By.text(expectedText)), TestAssetHelper.waitingTime)
assertTrue(
mDevice.findObject(
UiSelector().text(expectedText)
).waitForExists(waitingTime)
)
}
fun snackBarButtonClick(expectedText: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/snackbar_btn")
).waitForExists(waitingTime)
onView(allOf(withId(R.id.snackbar_btn), withText(expectedText))).check(
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
).perform(click())
val snackBarButton =
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/snackbar_btn")
.text(expectedText)
)
snackBarButton.waitForExists(waitingTime)
snackBarButton.click()
}
fun verifyTabMediaControlButtonState(action: String) {
mDevice.waitForIdle()
try {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_empty_view")
).waitUntilGone(waitingTime)
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/play_pause_button")
).waitForExists(waitingTime)
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_grid_item")
).waitForExists(waitingTime)
assertTrue(
mDevice.findObject(
UiSelector().descriptionContains(action)
UiSelector()
.resourceId("$packageName:id/play_pause_button")
.descriptionContains(action)
).waitForExists(waitingTime)
)
assertTrue(
mDevice.findObject(UiSelector().descriptionContains(action)).waitForExists(waitingTime)
)
} catch (e: AssertionFailedError) {
// In some cases the tab media button isn't updated after performing an action on it
println("Failed to update the state of the tab media button")
// Let's dismiss the tabs tray and try again
mDevice.pressBack()
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/toolbar")
).waitForExists(waitingTime)
browserScreen {
}.openTabDrawer {
// Click again the tab media button
tabMediaControlButton().click()
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_empty_view")
).waitUntilGone(waitingTime)
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_grid_item")
).waitForExists(waitingTime)
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/play_pause_button")
.descriptionContains(action)
).waitForExists(waitingTime)
assertTrue(
mDevice.findObject(UiSelector().descriptionContains(action)).waitForExists(waitingTime)
)
}
}
}
fun clickTabMediaControlButton() = tabMediaControlButton().click()
fun clickTabMediaControlButton(action: String) {
mDevice.waitNotNull(
Until.findObjects(
By
.res("$packageName:id/play_pause_button")
.descContains(action)
),
waitingTime
)
tabMediaControlButton().click()
}
fun clickSelectTabs() {
threeDotMenu().click()
@ -280,7 +342,11 @@ class TabDrawerRobot {
fun openTab(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(findObject(text(title)))
tab(title).click()
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.textContains(title)
).click()
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -374,17 +440,19 @@ fun tabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
return TabDrawerRobot.Transition()
}
private fun tabMediaControlButton() = onView(withId(R.id.play_pause_button))
private fun tabMediaControlButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/play_pause_button"))
private fun closeTabButton() = onView(withId(R.id.mozac_browser_tabstray_close))
private fun closeTabButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_tabstray_close"))
private fun assertCloseTabsButton(title: String) =
onView(
allOf(
withId(R.id.mozac_browser_tabstray_close),
withContentDescription("Close tab $title")
)
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_close")
.descriptionContains("Close tab $title")
).waitForExists(waitingTime)
)
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun normalBrowsingButton() = onView(
anyOf(
@ -394,6 +462,7 @@ private fun normalBrowsingButton() = onView(
)
private fun privateBrowsingButton() = onView(withContentDescription("Private tabs"))
private fun syncedTabsButton() = onView(withContentDescription("Synced tabs"))
private fun newTabButton() = mDevice.findObject(UiSelector().resourceId("$packageName:id/new_tab_button"))
private fun threeDotMenu() = onView(withId(R.id.tab_tray_overflow))
@ -406,30 +475,69 @@ private fun assertExistingOpenTabs(title: String) {
)
.waitForExists(waitingTime)
tab(title).check(matches(isDisplayed()))
} catch (e: NoMatchingViewException) {
onView(withId(R.id.tabsTray)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
allOf(
withId(R.id.mozac_browser_tabstray_title),
withText(title)
)
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.textContains(title)
).waitForExists(waitingTime)
)
} catch (e: AssertionError) {
println("The tab wasn't found")
mDevice.findObject(UiSelector().resourceId("$packageName:id/tabsTray")).swipeUp(2)
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.textContains(title)
).waitForExists(waitingTime)
)
}
}
private fun assertExistingTabList() =
onView(allOf(withId(R.id.tab_item)))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertExistingTabList() {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tabsTray")
).waitForExists(waitingTime)
private fun assertNoTabsOpenedText() =
onView(withId(R.id.tab_tray_empty_view))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
assertTrue(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_item")
).waitForExists(waitingTime)
)
}
private fun assertNewTabButton() =
onView(withId(R.id.new_tab_button))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertNoOpenTabsInNormalBrowsing() =
onView(
allOf(
withId(R.id.tab_tray_empty_view),
withText(R.string.no_open_tabs_description)
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertNoOpenTabsInPrivateBrowsing() =
onView(
allOf(
withId(R.id.tab_tray_empty_view),
withText(R.string.no_private_tabs_description)
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertNormalBrowsingNewTabButton() =
onView(
allOf(
withId(R.id.new_tab_button),
withContentDescription(R.string.add_tab)
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertPrivateBrowsingNewTabButton() =
onView(
allOf(
withId(R.id.new_tab_button),
withContentDescription(R.string.add_private_tab)
)
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertSelectTabsButton() =
onView(withText("Select tabs"))
@ -447,6 +555,19 @@ private fun assertTabTrayOverflowButton(visible: Boolean) =
onView(withId(R.id.tab_tray_overflow))
.check(matches(withEffectiveVisibility(visibleOrGone(visible))))
private fun assertTabsTrayCounter() =
tabsTrayCounterBox().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertEmptyTabsTrayMenuButtons() {
threeDotMenu().click()
tabsSettingsButton()
.inRoot(RootMatchers.isPlatformPopup())
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
recentlyClosedTabsButton()
.inRoot(RootMatchers.isPlatformPopup())
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertTabTrayDoesExist() {
onView(withId(R.id.tab_wrapper))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
@ -471,9 +592,24 @@ private fun assertNormalBrowsingButton() {
normalBrowsingButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertNormalBrowsingButtonIsSelected(isSelected: Boolean) {
normalBrowsingButton().check(matches(isSelected(isSelected)))
}
private fun assertPrivateBrowsingButtonIsSelected(isSelected: Boolean) {
privateBrowsingButton().check(matches(isSelected(isSelected)))
}
private fun assertSyncedTabsButtonIsSelected(isSelected: Boolean) {
syncedTabsButton().check(matches(isSelected(isSelected)))
}
private fun assertTabThumbnail() {
onView(withId(R.id.mozac_browser_tabstray_thumbnail))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
assertTrue(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_tabstray_thumbnail")
).waitForExists(waitingTime)
)
}
private fun tab(title: String) =
@ -486,6 +622,24 @@ private fun tab(title: String) =
private fun tabsCounter() = onView(withId(R.id.tab_button))
private fun tabsTrayCounterBox() = onView(withId(R.id.counter_box))
private fun tabsSettingsButton() =
onView(
allOf(
withId(R.id.simple_text),
withText(R.string.tab_tray_menu_tab_settings)
)
)
private fun recentlyClosedTabsButton() =
onView(
allOf(
withId(R.id.simple_text),
withText(R.string.tab_tray_menu_recently_closed)
)
)
private fun visibleOrGone(visibility: Boolean) =
if (visibility) ViewMatchers.Visibility.VISIBLE else ViewMatchers.Visibility.GONE

@ -21,7 +21,6 @@ import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
@ -37,18 +36,15 @@ import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.share.ShareFragment
/**
* Implementation of Robot Pattern for the three dot (main) menu.
*/
@Suppress("ForbiddenComment")
class ThreeDotMenuMainRobot {
fun verifyTabSettingsButton() = assertTabSettingsButton()
fun verifyRecentlyClosedTabsButton() = assertRecentlyClosedTabsButton()
fun verifyShareAllTabsButton() = assertShareAllTabsButton()
fun clickShareAllTabsButton() = shareAllTabsButton().click()
fun verifySettingsButton() = assertSettingsButton()
fun verifyCustomizeHomeButton() = assertCustomizeHomeButton()
fun verifyAddOnsButton() = assertAddOnsButton()
fun verifyHistoryButton() = assertHistoryButton()
fun verifyBookmarksButton() = assertBookmarksButton()
@ -67,19 +63,11 @@ class ThreeDotMenuMainRobot {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeUp())
}
fun clickShareButton() {
shareButton().click()
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
}
fun verifyShareTabButton() = assertShareTabButton()
fun verifySaveCollection() = assertSaveCollectionButton()
fun verifySelectTabs() = assertSelectTabsButton()
fun verifyFindInPageButton() = assertFindInPageButton()
fun verifyShareScrim() = assertShareScrim()
fun verifySendToDeviceTitle() = assertSendToDeviceTitle()
fun verifyShareALinkTitle() = assertShareALinkTitle()
fun verifyWhatsNewButton() = assertWhatsNewButton()
fun verifyAddToTopSitesButton() = assertAddToTopSitesButton()
fun verifyAddToMobileHome() = assertAddToMobileHome()
@ -87,7 +75,7 @@ class ThreeDotMenuMainRobot {
fun verifyDownloadsButton() = assertDownloadsButton()
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun verifySignInToSyncButton() = assertSignInToSyncButton()
fun verifyNewTabButton() = assertNewTabButton()
fun verifyNewTabButton() = assertNormalBrowsingNewTabButton()
fun verifyReportSiteIssueButton() = assertReportSiteIssueButton()
fun verifyDesktopSiteModeEnabled(state: Boolean) {
@ -187,13 +175,6 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition()
}
fun sharePage(interact: LibrarySubMenusMultipleSelectionToolbarRobot.() -> Unit): LibrarySubMenusMultipleSelectionToolbarRobot.Transition {
shareButton().click()
LibrarySubMenusMultipleSelectionToolbarRobot().interact()
return LibrarySubMenusMultipleSelectionToolbarRobot.Transition()
}
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
helpButton().click()
@ -202,6 +183,26 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition()
}
fun openCustomizeHome(interact: SettingsSubMenuHomepageRobot.() -> Unit): SettingsSubMenuHomepageRobot.Transition {
mDevice.wait(
Until
.findObject(
By.textContains("$packageName:id/browser_menu_customize_home")
),
waitingTime
)
customizeHomeButton().click()
mDevice.findObject(
UiSelector().resourceId("$packageName:id/recycler_view")
).waitForExists(waitingTime)
SettingsSubMenuHomepageRobot().interact()
return SettingsSubMenuHomepageRobot.Transition()
}
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
forwardButton().click()
@ -216,6 +217,14 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition()
}
fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
shareButton().click()
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
}
fun close(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
// Close three dot
mDevice.pressBack()
@ -345,6 +354,13 @@ class ThreeDotMenuMainRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickShareAllTabsButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
shareAllTabsButton().click()
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
}
}
}
private fun threeDotMenuRecyclerView() =
@ -357,6 +373,17 @@ private fun threeDotMenuRecyclerViewExists() {
private fun settingsButton() = mDevice.findObject(UiSelector().text("Settings"))
private fun assertSettingsButton() = assertTrue(settingsButton().waitForExists(waitingTime))
private fun customizeHomeButton() =
onView(
allOf(
withId(R.id.text),
withText(R.string.browser_menu_customize_home)
)
)
private fun assertCustomizeHomeButton() =
customizeHomeButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun addOnsButton() = onView(allOf(withText("Add-ons")))
private fun assertAddOnsButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
@ -424,22 +451,6 @@ private fun findInPageButton() = onView(allOf(withText("Find in page")))
private fun assertFindInPageButton() = findInPageButton()
private fun shareScrim() = onView(withResourceName("closeSharingScrim"))
private fun assertShareScrim() =
shareScrim().check(matches(ViewMatchers.withAlpha(ShareFragment.SHOW_PAGE_ALPHA)))
private fun SendToDeviceTitle() =
onView(allOf(withText("SEND TO DEVICE"), withResourceName("accountHeaderText")))
private fun assertSendToDeviceTitle() = SendToDeviceTitle()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun shareALinkTitle() =
onView(allOf(withText("ALL ACTIONS"), withResourceName("apps_link_header")))
private fun assertShareALinkTitle() = shareALinkTitle()
private fun whatsNewButton() = onView(
allOf(
withText("Whats New"),
@ -523,26 +534,6 @@ private fun clickAddonsManagerButton() {
addOnsButton().check(matches(isCompletelyDisplayed())).click()
}
private fun tabSettingsButton() =
onView(allOf(withText("Tab settings"))).inRoot(RootMatchers.isPlatformPopup())
private fun assertTabSettingsButton() {
tabSettingsButton()
.check(
matches(isDisplayed())
)
}
private fun recentlyClosedTabsButton() =
onView(allOf(withText("Recently closed tabs"))).inRoot(RootMatchers.isPlatformPopup())
private fun assertRecentlyClosedTabsButton() {
recentlyClosedTabsButton()
.check(
matches(isDisplayed())
)
}
private fun shareAllTabsButton() =
onView(allOf(withText("Share all tabs"))).inRoot(RootMatchers.isPlatformPopup())
@ -553,4 +544,4 @@ private fun assertShareAllTabsButton() {
)
}
private fun assertNewTabButton() = onView(withText("New tab")).check(matches(isDisplayed()))
private fun assertNormalBrowsingNewTabButton() = onView(withText("New tab")).check(matches(isDisplayed()))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

@ -265,6 +265,9 @@
<intent-filter>
<action android:name="android.service.autofill.AutofillService"/>
</intent-filter>
<meta-data
android:name="android.autofill"
android:resource="@xml/autofill_configuration" />
</service>
<service android:name=".media.MediaSessionService"

@ -149,6 +149,7 @@ class AppRequestInterceptor(
ErrorType.ERROR_NO_INTERNET,
ErrorType.ERROR_UNKNOWN_PROTOCOL -> RiskLevel.Low
ErrorType.ERROR_HTTPS_ONLY,
ErrorType.ERROR_SECURITY_BAD_CERT,
ErrorType.ERROR_SECURITY_SSL,
ErrorType.ERROR_PORT_BLOCKED -> RiskLevel.Medium

@ -80,8 +80,7 @@ import org.mozilla.fenix.components.Core
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.perf.MarkersLifecycleCallbacks
import org.mozilla.fenix.tabstray.ext.inactiveTabs
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.utils.Settings
/**
@ -198,7 +197,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService())
registerActivityLifecycleCallbacks(visibilityLifecycleCallback)
registerActivityLifecycleCallbacks(MarkersLifecycleCallbacks(components.core.engine))
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.
@ -642,7 +641,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
tabViewSetting.set(settings.getTabViewPingString())
closeTabSetting.set(settings.getTabTimeoutPingString())
inactiveTabsCount.set(browserStore.state.inactiveTabs.size.toLong())
val installSourcePackage = if (SDK_INT >= Build.VERSION_CODES.R) {
packageManager.getInstallSourceInfo(packageName).installingPackageName
@ -686,6 +684,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
voiceSearchEnabled.set(settings.shouldShowVoiceSearch)
openLinksInAppEnabled.set(settings.openLinksInExternalApp)
signedInSync.set(settings.signedInFxaAccount)
searchTermGroupsEnabled.set(settings.searchTermTabGroupsAreEnabled)
val syncedItems = SyncEnginesStorage(applicationContext).getStatus().entries.filter {
it.value
@ -739,6 +738,14 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
@VisibleForTesting
internal fun reportHomeScreenMetrics(settings: Settings) {
CustomizeHome.openingScreen.set(
when {
settings.alwaysOpenTheHomepageWhenOpeningTheApp -> "homepage"
settings.alwaysOpenTheLastTabWhenOpeningTheApp -> "last tab"
settings.openHomepageAfterFourHoursOfInactivity -> "homepage after four hours"
else -> ""
}
)
components.analytics.experiments.register(object : NimbusInterface.Observer {
override fun onUpdatesApplied(updated: List<EnrolledExperiment>) {
CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature)

@ -35,7 +35,6 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
@ -98,7 +97,8 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker
import org.mozilla.fenix.perf.MarkersLifecycleCallbacks
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.PerformanceInflater
import org.mozilla.fenix.perf.ProfilerMarkers
@ -187,6 +187,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
val startTimeProfiler = components.core.engine.profiler?.getProfilerTime()
components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager)
MarkersFragmentLifecycleCallbacks.register(supportFragmentManager, components.core.engine)
// There is disk read violations on some devices such as samsung and pixel for android 9/10
components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
// Theme setup should always be called before super.onCreate
@ -228,7 +230,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
if (!shouldStartOnHome() &&
shouldNavigateBrowserFragmentOnCouldStart(savedInstanceState)
shouldNavigateBrowserFragmentOnColdStart(savedInstanceState)
) {
navigateToBrowserOnColdStart()
} else if (FeatureFlags.showStartOnHomeSettings) {
@ -274,7 +276,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
components.core.engine.profiler?.addMarker(
MarkersLifecycleCallbacks.MARKER_NAME, startTimeProfiler, "HomeActivity.onCreate"
MarkersActivityLifecycleCallbacks.MARKER_NAME, startTimeProfiler, "HomeActivity.onCreate"
)
StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
}
@ -339,7 +341,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
ProfilerMarkers.homeActivityOnStart(binding.rootContainer, components.core.engine.profiler)
components.core.engine.profiler?.addMarker(
MarkersLifecycleCallbacks.MARKER_NAME, startProfilerTime, "HomeActivity.onStart"
MarkersActivityLifecycleCallbacks.MARKER_NAME, startProfilerTime, "HomeActivity.onStart"
) // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL.
}
@ -913,7 +915,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
}
fun updateSecureWindowFlags(mode: BrowsingMode = browsingModeManager.mode) {
private fun updateSecureWindowFlags(mode: BrowsingMode = browsingModeManager.mode) {
if (mode == BrowsingMode.Private && !settings().allowScreenshotsInPrivateMode) {
window.addFlags(FLAG_SECURE)
} else {
@ -941,7 +943,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
isVisuallyComplete = true
}
private fun captureSnapshotTelemetryMetrics() = CoroutineScope(Dispatchers.IO).launch {
private fun captureSnapshotTelemetryMetrics() = CoroutineScope(IO).launch {
// PWA
val recentlyUsedPwaCount = components.core.webAppShortcutManager.recentlyUsedWebAppsCount(
activeThresholdMs = PWA_RECENTLY_USED_THRESHOLD
@ -997,7 +999,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
@VisibleForTesting
internal fun getSettings(): Settings = settings()
private fun shouldNavigateBrowserFragmentOnCouldStart(savedInstanceState: Bundle?): Boolean {
private fun shouldNavigateBrowserFragmentOnColdStart(savedInstanceState: Bundle?): Boolean {
return isActivityColdStarted(intent, savedInstanceState) &&
!externalSourceIntentProcessors.any {
it.process(

@ -20,7 +20,7 @@ import org.mozilla.fenix.components.getType
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.MarkersLifecycleCallbacks
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor
@ -47,7 +47,7 @@ class IntentReceiverActivity : Activity() {
processIntent(intent)
components.core.engine.profiler?.addMarker(
MarkersLifecycleCallbacks.MARKER_NAME, startTimeProfiler, "IntentReceiverActivity.onCreate"
MarkersActivityLifecycleCallbacks.MARKER_NAME, startTimeProfiler, "IntentReceiverActivity.onCreate"
)
StartupTimeline.onActivityCreateEndIntentReceiver() // DO NOT MOVE ANYTHING BELOW HERE.
}

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
@ -53,7 +52,6 @@ class WebExtensionActionPopupFragment : AddonPopupBaseFragment(), EngineSession.
showToolbar(title)
}
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

@ -32,7 +32,6 @@ import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
@ -134,6 +133,7 @@ import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor
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
@ -142,7 +142,6 @@ import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolb
* This class only contains shared code focused on the main browsing content.
* UI code specific to the app or to custom tabs can be found in the subclasses.
*/
@ExperimentalCoroutinesApi
@Suppress("TooManyFunctions", "LargeClass")
abstract class BaseBrowserFragment :
Fragment(),
@ -212,6 +211,9 @@ abstract class BaseBrowserFragment :
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// DO NOT ADD ANYTHING ABOVE THIS getProfilerTime CALL!
val profilerStartTime = requireComponents.core.engine.profiler?.getProfilerTime()
customTabSessionId = requireArguments().getString(EXTRA_SESSION_ID)
// Diagnostic breadcrumb for "Display already aquired" crash:
@ -234,10 +236,17 @@ abstract class BaseBrowserFragment :
)
}
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
requireComponents.core.engine.profiler?.addMarker(
MarkersFragmentLifecycleCallbacks.MARKER_NAME, profilerStartTime, "BaseBrowserFragment.onCreateView",
)
return binding.root
}
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// DO NOT ADD ANYTHING ABOVE THIS getProfilerTime CALL!
val profilerStartTime = requireComponents.core.engine.profiler?.getProfilerTime()
initializeUI(view)
if (customTabSessionId == null) {
@ -254,6 +263,11 @@ abstract class BaseBrowserFragment :
}
requireContext().accessibilityManager.addAccessibilityStateChangeListener(this)
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
requireComponents.core.engine.profiler?.addMarker(
MarkersFragmentLifecycleCallbacks.MARKER_NAME, profilerStartTime, "BaseBrowserFragment.onViewCreated",
)
}
private fun initializeUI(view: View) {
@ -1033,10 +1047,6 @@ abstract class BaseBrowserFragment :
components.useCases.sessionUseCases.reload()
}
hideToolbar()
components.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)?.let {
updateThemeForSession(it)
}
}
@CallSuper

@ -13,7 +13,6 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TabSessionState
@ -44,7 +43,6 @@ import org.mozilla.fenix.theme.ThemeManager
/**
* Fragment used for browsing the web within the main app.
*/
@ExperimentalCoroutinesApi
@Suppress("TooManyFunctions", "LargeClass")
class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {

@ -10,7 +10,6 @@ import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.NavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
@ -30,7 +29,6 @@ import org.mozilla.fenix.utils.Settings
/**
* Displays an [InfoBanner] when a user visits a website that can be opened in an installed native app.
*/
@ExperimentalCoroutinesApi
@Suppress("LongParameterList")
class OpenInAppOnboardingObserver(
private val context: Context,

@ -12,14 +12,12 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.databinding.FragmentCreateCollectionBinding
import org.mozilla.fenix.ext.requireComponents
@ExperimentalCoroutinesApi
class CollectionCreationFragment : DialogFragment() {
private lateinit var collectionCreationView: CollectionCreationView
private lateinit var collectionCreationStore: CollectionCreationStore

@ -13,7 +13,6 @@ import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.collections.CollectionCreationAction.StepChanged
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.home.Tab

@ -0,0 +1,22 @@
/* 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.components
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.Store
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.AppStoreReducer
/**
* A [Store] that holds the [AppState] for the app and reduces [AppAction]s
* dispatched to the store.
*
* This store is not persisted to disk and is scoped to the life-cycle of the application.
*/
class AppStore(
initialState: AppState = AppState(),
middlewares: List<Middleware<AppState, AppAction>> = emptyList()
) : Store<AppState, AppAction>(initialState, AppStoreReducer::reduce, middlewares)

@ -106,13 +106,17 @@ class BackgroundServices(
SyncConfig(supportedEngines, PeriodicSyncConfig(periodMinutes = 240)) // four hours
private val creditCardKeyProvider by lazyMonitored { creditCardsStorage.value.crypto }
private val passwordKeyProvider by lazyMonitored { passwordsStorage.value.crypto }
init {
// Make the "history", "bookmark", "passwords", "tabs", "credit cards" stores
// accessible to workers spawned by the sync manager.
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Passwords to passwordsStorage)
GlobalSyncableStoreProvider.configureStore(
storePair = SyncEngine.Passwords to passwordsStorage,
keyProvider = lazy { passwordKeyProvider }
)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Tabs to remoteTabsStorage)
GlobalSyncableStoreProvider.configureStore(
storePair = SyncEngine.CreditCards to creditCardsStorage,

@ -74,7 +74,8 @@ class Components(private val context: Context) {
core.store,
core.webAppShortcutManager,
core.topSitesStorage,
core.bookmarksStorage
core.bookmarksStorage,
core.historyStorage
)
}
@ -179,6 +180,7 @@ class Components(private val context: Context) {
val appStartReasonProvider by lazyMonitored { AppStartReasonProvider() }
val startupActivityLog by lazyMonitored { StartupActivityLog() }
val startupStateProvider by lazyMonitored { StartupStateProvider(startupActivityLog, appStartReasonProvider) }
val appStore by lazyMonitored { AppStore() }
}
/**

@ -53,7 +53,6 @@ import mozilla.components.feature.webcompat.WebCompatFeature
import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.lib.dataprotect.generateEncryptionKey
import mozilla.components.service.digitalassetlinks.RelationChecker
import mozilla.components.service.digitalassetlinks.local.StatementApi
import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker
@ -87,7 +86,6 @@ import org.mozilla.fenix.telemetry.TelemetryMiddleware
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.getUndoDelay
import org.mozilla.geckoview.GeckoRuntime
import java.lang.IllegalStateException
import java.util.concurrent.TimeUnit
/**
@ -218,7 +216,15 @@ class Core(
)
BrowserStore(
middleware = middlewareList + EngineMiddleware.create(engine)
middleware = middlewareList + EngineMiddleware.create(
engine,
// We are disabling automatic suspending of engine sessions under memory pressure
// in Nightly as a test. Instead we solely rely on GeckoView and the Android system
// to reclaim memory when needed.
// https://github.com/mozilla-mobile/fenix/issues/12731
// https://github.com/mozilla-mobile/android-components/issues/11300
trimMemoryAutomatically = Config.channel.isReleaseOrBeta
)
).apply {
// Install the "icons" WebExtension to automatically load icons for every visited website.
icons.install(engine, this)
@ -294,7 +300,7 @@ class Core(
// We can fully initialize GeckoEngine without initialized our storage.
val lazyHistoryStorage = lazyMonitored { PlacesHistoryStorage(context, crashReporter) }
val lazyBookmarksStorage = lazyMonitored { PlacesBookmarksStorage(context) }
val lazyPasswordsStorage = lazyMonitored { SyncableLoginsStorage(context, passwordsEncryptionKey) }
val lazyPasswordsStorage = lazyMonitored { SyncableLoginsStorage(context, lazySecurePrefs) }
val lazyAutofillStorage = lazyMonitored { AutofillCreditCardsAddressesStorage(context, lazySecurePrefs) }
/**
@ -361,6 +367,13 @@ class Core(
SupportUtils.TC_URL
)
)
defaultTopSites.add(
Pair(
context.getString(R.string.default_top_site_meituan),
SupportUtils.MEITUAN_URL
)
)
} else {
defaultTopSites.add(
Pair(
@ -418,23 +431,6 @@ class Core(
// Temporary. See https://github.com/mozilla-mobile/fenix/issues/19155
private val lazySecurePrefs = lazyMonitored { getSecureAbove22Preferences() }
private val passwordsEncryptionKey by lazyMonitored {
getSecureAbove22Preferences().getString(PASSWORDS_KEY)
?: generateEncryptionKey(KEY_STRENGTH).also {
if (context.settings().passwordsEncryptionKeyGenerated) {
// We already had previously generated an encryption key, but we have lost it
crashReporter.submitCaughtException(
IllegalStateException(
"Passwords encryption key for passwords storage was lost and we generated a new one"
)
)
}
context.settings().recordPasswordsEncryptionKeyGenerated()
getSecureAbove22Preferences().putString(PASSWORDS_KEY, it)
}
}
val trackingProtectionPolicyFactory = TrackingProtectionPolicyFactory(context.settings())
/**

@ -78,7 +78,7 @@ class FenixSnackbar private constructor(
companion object {
const val LENGTH_LONG = Snackbar.LENGTH_LONG
const val LENGTH_SHORT = Snackbar.LENGTH_SHORT
const val LENGTH_ACCESSIBLE = 15000 /* 15 seconds in ms */
private const val LENGTH_ACCESSIBLE = 15000 /* 15 seconds in ms */
const val LENGTH_INDEFINITE = Snackbar.LENGTH_INDEFINITE
private const val minTextSize = 12

@ -1,29 +0,0 @@
/* 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.components
import android.annotation.TargetApi
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.os.Build
import org.mozilla.gecko.search.SearchWidgetProvider
/**
* Handles the creation of the pinning search widget dialog.
*/
object SearchWidgetCreator {
/**
* Attempts to display a prompt requesting the user pin the search widget
* Returns true if the prompt is displayed successfully, and false otherwise.
*/
@TargetApi(Build.VERSION_CODES.O)
fun createSearchWidget(context: Context): Boolean {
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, SearchWidgetProvider::class.java)
return appWidgetManager.requestPinAppWidget(myProvider, null, null)
}
}

@ -8,6 +8,7 @@ import android.content.Context
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.app.links.AppLinksUseCases
import mozilla.components.feature.contextmenu.ContextMenuUseCases
import mozilla.components.feature.downloads.DownloadsUseCases
@ -38,7 +39,8 @@ class UseCases(
private val store: BrowserStore,
private val shortcutManager: WebAppShortcutManager,
private val topSitesStorage: TopSitesStorage,
private val bookmarksStorage: BookmarksStorage
private val bookmarksStorage: BookmarksStorage,
private val historyStorage: HistoryStorage
) {
/**
* Use cases that provide engine interactions for a given browser session.
@ -63,7 +65,8 @@ class UseCases(
val searchUseCases by lazyMonitored {
SearchUseCases(
store,
tabsUseCases
tabsUseCases,
sessionUseCases
)
}
@ -97,5 +100,5 @@ class UseCases(
/**
* Use cases that provide bookmark management.
*/
val bookmarksUseCases by lazyMonitored { BookmarksUseCase(bookmarksStorage) }
val bookmarksUseCases by lazyMonitored { BookmarksUseCase(bookmarksStorage, historyStorage) }
}

@ -0,0 +1,15 @@
/* 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.components.appstate
import mozilla.components.lib.state.Action
import org.mozilla.fenix.components.AppStore
/**
* [Action] implementation related to [AppStore].
*/
sealed class AppAction : Action {
data class UpdateInactiveExpanded(val expanded: Boolean) : AppAction()
}

@ -0,0 +1,17 @@
/* 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.components.appstate
import mozilla.components.lib.state.State
/**
* Value type that represents the state of the tabs tray.
*
* @property inactiveTabsExpanded A flag to know if the Inactive Tabs section of the Tabs Tray
* should be expanded when the tray is opened.
*/
data class AppState(
val inactiveTabsExpanded: Boolean = false
) : State

@ -0,0 +1,17 @@
/* 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.components.appstate
import org.mozilla.fenix.components.AppStore
/**
* Reducer for [AppStore].
*/
internal object AppStoreReducer {
fun reduce(state: AppState, action: AppAction): AppState = when (action) {
is AppAction.UpdateInactiveExpanded ->
state.copy(inactiveTabsExpanded = action.expanded)
}
}

@ -6,14 +6,18 @@ package org.mozilla.fenix.components.bookmarks
import androidx.annotation.WorkerThread
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.concept.storage.HistoryStorage
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import java.util.concurrent.TimeUnit
/**
* Use cases that allow for modifying and retrieving bookmarks.
*/
class BookmarksUseCase(storage: BookmarksStorage) {
class BookmarksUseCase(
bookmarksStorage: BookmarksStorage,
historyStorage: HistoryStorage,
) {
class AddBookmarksUseCase internal constructor(private val storage: BookmarksStorage) {
@ -40,29 +44,62 @@ class BookmarksUseCase(storage: BookmarksStorage) {
}
}
/**
* Uses for retrieving recently added bookmarks.
*
* @param bookmarksStorage [BookmarksStorage] to retrieve the bookmark data.
* @param historyStorage Optional [HistoryStorage] to retrieve the preview image of a visited
* page associated with a bookmark.
*/
class RetrieveRecentBookmarksUseCase internal constructor(
private val storage: BookmarksStorage
private val bookmarksStorage: BookmarksStorage,
private val historyStorage: HistoryStorage? = null
) {
/**
* Retrieves a list of recently added bookmarks, if any, up to maximum.
*
* @param count The number of recent bookmarks to return.
* @param maxAgeInMs The maximum age (ms) of a recently added bookmark to return.
* @return a list of [RecentBookmark] that were added no older than specify by [maxAgeInMs],
* if any, up to a number specified by [count].
*/
@WorkerThread
suspend operator fun invoke(
count: Int = DEFAULT_BOOKMARKS_TO_RETRIEVE,
maxAgeInMs: Long = TimeUnit.DAYS.toMillis(DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE)
): List<BookmarkNode> {
return storage.getRecentBookmarks(
count,
maxAgeInMs
): List<RecentBookmark> {
val currentTime = System.currentTimeMillis()
// Fetch visit information within the time range of now and the specified maximum age.
val history = historyStorage?.getDetailedVisits(
start = currentTime - maxAgeInMs,
end = currentTime
)
return bookmarksStorage
.getRecentBookmarks(count, maxAgeInMs)
.map { bookmark ->
RecentBookmark(
title = bookmark.title,
url = bookmark.url,
previewImageUrl = history?.find { bookmark.url == it.url }?.previewImageUrl
)
}
}
}
val addBookmark by lazy { AddBookmarksUseCase(storage) }
val retrieveRecentBookmarks by lazy { RetrieveRecentBookmarksUseCase(storage) }
val addBookmark by lazy { AddBookmarksUseCase(bookmarksStorage) }
val retrieveRecentBookmarks by lazy {
RetrieveRecentBookmarksUseCase(
bookmarksStorage,
historyStorage
)
}
companion object {
// Number of recent bookmarks to retrieve.
const val DEFAULT_BOOKMARKS_TO_RETRIEVE = 4
// The maximum age in days of a recent bookmarks to retrieve.
const val DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE = 10L
}
}

@ -4,6 +4,7 @@
package org.mozilla.fenix.components.history
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.storage.VisitInfo
import mozilla.components.concept.storage.VisitType
@ -37,6 +38,26 @@ class DefaultPagedHistoryProvider(
private val historyStorage: PlacesHistoryStorage,
private val showHistorySearchGroups: Boolean = FeatureFlags.showHistorySearchGroups,
) : PagedHistoryProvider {
/**
* Types of visits we currently do not display in the History UI.
*/
private val excludedVisitTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_PERMANENT,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
)
/**
* All types of visits that aren't redirects. This is used for fetching only redirecting visits
* from the store so that we can filter them out.
*/
private val notRedirectTypes = VisitType.values().filterNot {
it == VisitType.REDIRECT_PERMANENT || it == VisitType.REDIRECT_TEMPORARY
}
@Volatile private var historyGroups: List<History.Group>? = null
@ -52,7 +73,7 @@ class DefaultPagedHistoryProvider(
val history: List<History>
if (showHistorySearchGroups) {
// We need to refetch all the history metadata if the offset resets back at 0
// We need to re-fetch all the history metadata if the offset resets back at 0
// in the case of a pull to refresh.
if (historyGroups == null || offset == 0L) {
historyGroups = historyStorage.getHistoryMetadataSince(Long.MIN_VALUE)
@ -75,15 +96,7 @@ class DefaultPagedHistoryProvider(
.getVisitsPaginated(
offset,
numberOfItems,
excludeTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
excludeTypes = excludedVisitTypes
)
.mapIndexed(transformVisitInfoToHistoryItem(offset.toInt()))
}
@ -92,38 +105,41 @@ class DefaultPagedHistoryProvider(
}
}
/**
* Removes [group] and any corresponding history visits.
*/
suspend fun deleteMetadataSearchGroup(group: History.Group) {
for (historyMetadata in group.items) {
getMatchingHistory(historyMetadata)?.let {
historyStorage.deleteVisit(
url = it.url,
timestamp = it.visitTime
)
}
}
historyStorage.deleteHistoryMetadata(
searchTerm = group.title
)
// Force a re-fetch of the groups next time we go through #getHistory.
historyGroups = null
}
/**
* Returns the [History.Regular] corresponding to the given [History.Metadata] item.
*
* @param historyMetadata The [History.Metadata] to match.
* @return the [History.Regular] corresponding to the given [History.Metadata] item or null.
*/
suspend fun getMatchingHistory(historyMetadata: History.Metadata): VisitInfo? {
private suspend fun getMatchingHistory(historyMetadata: History.Metadata): VisitInfo? {
val history = historyStorage.getDetailedVisits(
start = historyMetadata.visitedAt - BUFFER_TIME,
end = historyMetadata.visitedAt + BUFFER_TIME,
excludeTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
excludeTypes = excludedVisitTypes
)
return history
.filter { it.url == historyMetadata.url }
.minByOrNull { abs(historyMetadata.visitedAt - it.visitTime) }
}
/**
* Clears the history groups to refetch the most history metadata after any changes.
*/
fun clearHistoryGroups() {
historyGroups = null
}
@Suppress("MagicNumber")
private suspend fun getHistoryAndSearchGroups(
offset: Long,
@ -134,18 +150,26 @@ class DefaultPagedHistoryProvider(
.getVisitsPaginated(
offset,
numberOfItems,
excludeTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
excludeTypes = excludedVisitTypes
)
.mapIndexed(transformVisitInfoToHistoryItem(offset.toInt()))
// We'll use this list to filter out redirects from metadata groups below.
val redirectsInThePage = if (history.isNotEmpty()) {
historyStorage.getDetailedVisits(
start = history.last().visitedAt,
end = history.first().visitedAt,
excludeTypes = notRedirectTypes
).map { it.url }
} else {
// Edge-case this doesn't cover: if we only had redirects in the current page,
// we'd end up with an empty 'history' list since the redirects would have been
// filtered out above. One possible solution would be to look at redirects in all of
// history, but that's potentially quite expensive on large profiles, and introduces
// other problems (e.g. pages that were redirects a month ago may not be redirects today).
emptyList()
}
// History metadata items are recorded after their associated visited info, we add an
// additional buffer time to the most recent visit to account for a history group
// appearing as the most recent item.
@ -155,8 +179,10 @@ class DefaultPagedHistoryProvider(
// items.
val historyGroupsInOffset = if (history.isNotEmpty()) {
historyGroups?.filter {
history.last().visitedAt <= it.visitedAt - visitedAtBuffer &&
it.visitedAt - visitedAtBuffer <= (history.first().visitedAt + visitedAtBuffer)
it.items.any { item ->
(history.last().visitedAt - visitedAtBuffer) <= item.visitedAt &&
item.visitedAt <= (history.first().visitedAt + visitedAtBuffer)
}
} ?: emptyList()
} else {
emptyList()
@ -174,11 +200,12 @@ class DefaultPagedHistoryProvider(
// url, but we don't have a use case for this currently in the history view.
result.addAll(
historyGroupsInOffset.map { group ->
group.copy(items = group.items.distinctBy { it.url })
group.copy(items = group.items.distinctBy { it.url }.filterNot { redirectsInThePage.contains(it.url) })
}
)
return result.sortedByDescending { it.visitedAt }
return result.removeConsecutiveDuplicates()
.sortedByDescending { it.visitedAt }
}
private fun transformVisitInfoToHistoryItem(offset: Int): (id: Int, visit: VisitInfo) -> History.Regular {
@ -196,3 +223,18 @@ class DefaultPagedHistoryProvider(
}
}
}
@VisibleForTesting
internal fun List<History>.removeConsecutiveDuplicates(): List<History> {
var previousURL = ""
return filter {
var isNotDuplicate = true
previousURL = if (it is History.Regular) {
isNotDuplicate = it.url != previousURL
it.url
} else {
""
}
isNotDuplicate
}
}

@ -14,7 +14,6 @@ import org.mozilla.fenix.GleanMetrics.AppTheme
import org.mozilla.fenix.GleanMetrics.Autoplay
import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.ContextMenu
import org.mozilla.fenix.GleanMetrics.CrashReporter
import org.mozilla.fenix.GleanMetrics.ErrorPage
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.History
@ -24,6 +23,7 @@ import org.mozilla.fenix.GleanMetrics.Pocket
import org.mozilla.fenix.GleanMetrics.Preferences
import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchTerms
import org.mozilla.fenix.GleanMetrics.TabsTray
import org.mozilla.fenix.GleanMetrics.ToolbarSettings
import org.mozilla.fenix.GleanMetrics.TopSites
@ -85,6 +85,10 @@ sealed class Event {
data class HistoryRecentSearchesTapped(val source: String) : Event() {
override val extras = mapOf(History.recentSearchesTappedKeys.pageNumber to source)
}
object HistorySearchTermGroupTapped : Event()
object HistorySearchTermGroupOpenTab : Event()
object HistorySearchTermGroupRemoveTab : Event()
object HistorySearchTermGroupRemoveAll : Event()
object ReaderModeAvailable : Event()
object ReaderModeOpened : Event()
object ReaderModeClosed : Event()
@ -211,12 +215,16 @@ sealed class Event {
object TabsTrayCloseAllInactiveTabs : Event()
data class TabsTrayCloseInactiveTab(val amountClosed: Int = 1) : Event()
object TabsTrayOpenInactiveTab : Event()
object TabsTrayInactiveTabsCFRGotoSettings : Event()
object TabsTrayInactiveTabsCFRDismissed : Event()
object TabsTrayInactiveTabsCFRIsVisible : Event()
object InactiveTabsSurveyOpened : Event()
data class InactiveTabsOffSurvey(val feedback: String) : Event() {
override val extras: Map<Preferences.turnOffInactiveTabsSurveyKeys, String>
get() = mapOf(Preferences.turnOffInactiveTabsSurveyKeys.feedback to feedback.lowercase(Locale.ROOT))
}
data class InactiveTabsCountUpdate(val count: Int) : Event()
object ProgressiveWebAppOpenFromHomescreenTap : Event()
object ProgressiveWebAppInstallAsShortcut : Event()
@ -254,6 +262,7 @@ sealed class Event {
// Home menu interaction
object HomeMenuSettingsItemClicked : Event()
object HomeScreenDisplayed : Event()
object HomeScreenViewCount : Event()
object HomeScreenCustomizedHomeClicked : Event()
// Browser Toolbar
@ -618,14 +627,8 @@ sealed class Event {
}
}
object CrashReporterOpened : Event()
data class AddonInstalled(val addonId: String) : Event()
data class CrashReporterClosed(val crashSubmitted: Boolean) : Event() {
override val extras: Map<CrashReporter.closedKeys, String>?
get() = mapOf(CrashReporter.closedKeys.crashSubmitted to crashSubmitted.toString())
}
data class BrowserMenuItemTapped(val item: Item) : Event() {
enum class Item {
SETTINGS, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB,
@ -639,15 +642,6 @@ sealed class Event {
get() = mapOf(Events.browserMenuActionKeys.item to item.toString().lowercase(Locale.ROOT))
}
data class TabCounterMenuItemTapped(val item: Item) : Event() {
enum class Item {
NEW_TAB, NEW_PRIVATE_TAB, CLOSE_TAB
}
override val extras: Map<Events.tabCounterMenuActionKeys, String>?
get() = mapOf(Events.tabCounterMenuActionKeys.item to item.toString().lowercase(Locale.ROOT))
}
object AutoPlaySettingVisited : Event()
data class AutoPlaySettingChanged(val setting: AutoplaySetting) : Event() {
@ -666,6 +660,20 @@ sealed class Event {
get() = mapOf(Events.tabViewChangedKeys.type to type.toString().lowercase(Locale.ROOT))
}
data class SearchTermGroupCount(val count: Int) : Event() {
override val extras: Map<SearchTerms.numberOfSearchTermGroupKeys, String>
get() = hashMapOf(SearchTerms.numberOfSearchTermGroupKeys.count to count.toString())
}
data class AverageTabsPerSearchTermGroup(val averageSize: Double) : Event() {
override val extras: Map<SearchTerms.averageTabsPerGroupKeys, String>
get() = hashMapOf(SearchTerms.averageTabsPerGroupKeys.count to averageSize.toString())
}
data class SearchTermGroupSizeDistribution(val groupSizes: List<Long>) : Event()
object JumpBackInGroupTapped : Event()
sealed class Search
internal open val extras: Map<*, String>?

@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.Config
import org.mozilla.fenix.GleanMetrics.FirstSession
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.ext.settings
@ -60,6 +61,12 @@ class FirstSessionPing(private val context: Context) {
FirstSession.adgroup.set(it.adjustAdGroup)
FirstSession.creative.set(it.adjustCreative)
FirstSession.network.set(it.adjustNetwork)
FirstSession.distributionId.set(
when (Config.channel.isMozillaOnline) {
true -> "MozillaOnline"
false -> "Mozilla"
}
)
FirstSession.timestamp.set()
}

@ -19,7 +19,6 @@ import org.mozilla.fenix.GleanMetrics.BrowserSearch
import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.ContextMenu
import org.mozilla.fenix.GleanMetrics.ContextualMenu
import org.mozilla.fenix.GleanMetrics.CrashReporter
import org.mozilla.fenix.GleanMetrics.CreditCards
import org.mozilla.fenix.GleanMetrics.CustomTab
import org.mozilla.fenix.GleanMetrics.CustomizeHome
@ -44,6 +43,7 @@ import org.mozilla.fenix.GleanMetrics.RecentBookmarks
import org.mozilla.fenix.GleanMetrics.RecentSearches
import org.mozilla.fenix.GleanMetrics.RecentTabs
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchTerms
import org.mozilla.fenix.GleanMetrics.SearchWidget
import org.mozilla.fenix.GleanMetrics.SetDefaultNewtabExperiment
import org.mozilla.fenix.GleanMetrics.SetDefaultSettingExperiment
@ -157,13 +157,6 @@ private val Event.wrapper: EventWrapper<*>?
{ ContextMenu.itemTapped.record(it) },
{ ContextMenu.itemTappedKeys.valueOf(it) }
)
is Event.CrashReporterOpened -> EventWrapper<NoExtraKeys>(
{ CrashReporter.opened.record(it) }
)
is Event.CrashReporterClosed -> EventWrapper(
{ CrashReporter.closed.record(it) },
{ CrashReporter.closedKeys.valueOf(it) }
)
is Event.BrowserMenuItemTapped -> EventWrapper(
{ Events.browserMenuAction.record(it) },
{ Events.browserMenuActionKeys.valueOf(it) }
@ -319,6 +312,18 @@ private val Event.wrapper: EventWrapper<*>?
{ History.recentSearchesTapped.record(it) },
{ History.recentSearchesTappedKeys.valueOf(it) }
)
is Event.HistorySearchTermGroupTapped -> EventWrapper<NoExtraKeys>(
{ History.searchTermGroupTapped.record(it) }
)
is Event.HistorySearchTermGroupOpenTab -> EventWrapper<NoExtraKeys>(
{ History.searchTermGroupOpenTab.record(it) }
)
is Event.HistorySearchTermGroupRemoveTab -> EventWrapper<NoExtraKeys>(
{ History.searchTermGroupRemoveTab.record(it) }
)
is Event.HistorySearchTermGroupRemoveAll -> EventWrapper<NoExtraKeys>(
{ History.searchTermGroupRemoveAll.record(it) }
)
is Event.CollectionRenamed -> EventWrapper<NoExtraKeys>(
{ Collections.renamed.record(it) }
)
@ -543,10 +548,6 @@ private val Event.wrapper: EventWrapper<*>?
is Event.VoiceSearchTapped -> EventWrapper<NoExtraKeys>(
{ VoiceSearch.tapped.record(it) }
)
is Event.TabCounterMenuItemTapped -> EventWrapper(
{ Events.tabCounterMenuAction.record(it) },
{ Events.tabCounterMenuActionKeys.valueOf(it) }
)
is Event.OnboardingPrivacyNotice -> EventWrapper<NoExtraKeys>(
{ Onboarding.privacyNotice.record(it) }
)
@ -651,6 +652,18 @@ private val Event.wrapper: EventWrapper<*>?
{ Preferences.turnOffInactiveTabsSurvey.record(it) },
{ Preferences.turnOffInactiveTabsSurveyKeys.valueOf(it) }
)
is Event.InactiveTabsCountUpdate -> EventWrapper<NoExtraKeys>(
{ Metrics.inactiveTabsCount.set(this.count.toLong()) },
)
is Event.TabsTrayInactiveTabsCFRGotoSettings -> EventWrapper<NoExtraKeys>(
{ TabsTray.inactiveTabsCfrSettings.record(it) }
)
is Event.TabsTrayInactiveTabsCFRDismissed -> EventWrapper<NoExtraKeys>(
{ TabsTray.inactiveTabsCfrDismissed.record(it) }
)
is Event.TabsTrayInactiveTabsCFRIsVisible -> EventWrapper<NoExtraKeys>(
{ TabsTray.inactiveTabsCfrVisible.record(it) }
)
is Event.AutoPlaySettingVisited -> EventWrapper<NoExtraKeys>(
{ Autoplay.visitedSetting.record(it) }
)
@ -766,6 +779,9 @@ private val Event.wrapper: EventWrapper<*>?
is Event.HomeScreenDisplayed -> EventWrapper<NoExtraKeys>(
{ HomeScreen.homeScreenDisplayed.record(it) }
)
is Event.HomeScreenViewCount -> EventWrapper<NoExtraKeys>(
{ HomeScreen.homeScreenViewCount.add() }
)
is Event.HomeScreenCustomizedHomeClicked -> EventWrapper<NoExtraKeys>(
{ HomeScreen.customizeHomeClicked.record(it) }
)
@ -880,6 +896,20 @@ private val Event.wrapper: EventWrapper<*>?
is Event.CreditCardManagementCardTapped -> EventWrapper<NoExtraKeys>(
{ CreditCards.managementCardTapped.record(it) }
)
is Event.SearchTermGroupCount -> EventWrapper(
{ SearchTerms.numberOfSearchTermGroup.record(it) },
{ SearchTerms.numberOfSearchTermGroupKeys.valueOf(it) }
)
is Event.AverageTabsPerSearchTermGroup -> EventWrapper(
{ SearchTerms.averageTabsPerGroup.record(it) },
{ SearchTerms.averageTabsPerGroupKeys.valueOf(it) }
)
is Event.SearchTermGroupSizeDistribution -> EventWrapper<NoExtraKeys>(
{ SearchTerms.groupSizeDistribution.accumulateSamples(this.groupSizes.toLongArray()) },
)
is Event.JumpBackInGroupTapped -> EventWrapper<NoExtraKeys>(
{ SearchTerms.jumpBackInGroupTapped.record(it) }
)
// Don't record other events in Glean:
is Event.AddBookmark -> null
@ -942,8 +972,3 @@ class GleanMetricsService(
return event.wrapper != null
}
}
// Helper function for making our booleans fit into the string list formatting
fun Boolean.toStringList(): List<String> {
return listOf(this.toString())
}

@ -20,7 +20,7 @@ class SecurePrefsTelemetry(
private val appContext: Context,
private val experiments: NimbusApi
) {
suspend fun startTests() {
fun startTests() {
// The Android Keystore is used to secure the shared prefs only on API 23+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// These tests should run only if the experiment is live

@ -19,11 +19,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.service.sync.logins.IdCollisionException
import mozilla.components.concept.storage.Login
import mozilla.components.service.sync.logins.InvalidRecordException
import mozilla.components.service.sync.logins.LoginsStorageException
import mozilla.components.service.sync.logins.ServerPassword
import mozilla.components.service.sync.logins.toLogin
import mozilla.components.support.migration.FennecLoginsMPImporter
import mozilla.components.support.migration.FennecProfile
import org.mozilla.fenix.R
@ -186,21 +184,16 @@ class MasterPasswordTipProvider(
}
}
private fun saveLogins(logins: List<ServerPassword>, dialog: AlertDialog) {
private fun saveLogins(logins: List<Login>, dialog: AlertDialog) {
CoroutineScope(IO).launch {
logins.map { it.toLogin() }.forEach {
try {
context.components.core.passwordsStorage.add(it)
} catch (e: InvalidRecordException) {
// This record was invalid and we couldn't save this login
context.components.analytics.crashReporter.submitCaughtException(e)
} catch (e: IdCollisionException) {
// Nonempty ID was provided
context.components.analytics.crashReporter.submitCaughtException(e)
} catch (e: LoginsStorageException) {
// Some other error occurred
context.components.analytics.crashReporter.submitCaughtException(e)
}
try {
context.components.core.passwordsStorage.importLoginsAsync(logins)
} catch (e: InvalidRecordException) {
// This record was invalid and we couldn't save this login
context.components.analytics.crashReporter.submitCaughtException(e)
} catch (e: LoginsStorageException) {
// Some other error occurred
context.components.analytics.crashReporter.submitCaughtException(e)
}
withContext(Dispatchers.Main) {
// Step 3: Dismiss this dialog and show the success dialog

@ -126,9 +126,6 @@ class DefaultBrowserToolbarController(
override fun handleTabCounterItemInteraction(item: TabCounterMenu.Item) {
when (item) {
is TabCounterMenu.Item.CloseTab -> {
metrics.track(
Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB)
)
store.state.selectedTab?.let {
// When closing the last tab we must show the undo snackbar in the home fragment
if (store.state.getNormalOrPrivateTabs(it.content.private).count() == 1) {
@ -143,20 +140,12 @@ class DefaultBrowserToolbarController(
}
}
is TabCounterMenu.Item.NewTab -> {
metrics.track(
Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)
)
activity.browsingModeManager.mode = BrowsingMode.Normal
navController.navigate(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)
)
}
is TabCounterMenu.Item.NewPrivateTab -> {
metrics.track(
Event.TabCounterMenuItemTapped(
Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB
)
)
activity.browsingModeManager.mode = BrowsingMode.Private
navController.navigate(
BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)

@ -14,7 +14,6 @@ import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.CustomTabSessionState
@ -35,7 +34,6 @@ import org.mozilla.fenix.utils.ToolbarPopupWindow
import java.lang.ref.WeakReference
import mozilla.components.browser.toolbar.behavior.ToolbarPosition as MozacToolbarPosition
@ExperimentalCoroutinesApi
@SuppressWarnings("LargeClass")
class BrowserToolbarView(
private val container: ViewGroup,
@ -131,7 +129,7 @@ class BrowserToolbarView(
trackingProtection = primaryTextColor,
highlight = ContextCompat.getColor(
context,
R.color.whats_new_notification_color
R.color.fx_mobile_icon_color_notice
)
)

@ -11,7 +11,6 @@ import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.core.content.ContextCompat.getColor
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
@ -54,7 +53,6 @@ import org.mozilla.fenix.utils.BrowsersCache
* @param bookmarksStorage Used to check if a page is bookmarked.
*/
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ExperimentalCoroutinesApi
open class DefaultToolbarMenu(
private val context: Context,
private val store: BrowserStore,
@ -183,7 +181,7 @@ open class DefaultToolbarMenu(
iconTintColorResource = primaryTextColor(),
highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_install_on_homescreen),
notificationTint = getColor(context, R.color.whats_new_notification_color)
notificationTint = getColor(context, R.color.fx_mobile_icon_color_notice)
),
isHighlighted = {
!context.settings().installPwaOpened
@ -252,7 +250,7 @@ open class DefaultToolbarMenu(
iconTintColorResource = primaryTextColor(),
highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_open_app_link),
notificationTint = getColor(context, R.color.whats_new_notification_color)
notificationTint = getColor(context, R.color.fx_mobile_icon_color_notice)
),
isHighlighted = { !context.settings().openInAppOpened }
) {

@ -6,7 +6,6 @@ package org.mozilla.fenix.components.toolbar
import android.view.View
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
@ -16,7 +15,6 @@ import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
@ExperimentalCoroutinesApi
class MenuPresenter(
private val menuToolbar: BrowserToolbar,
private val store: BrowserStore,

@ -7,7 +7,6 @@ package org.mozilla.fenix.components.toolbar
import android.content.Context
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs
@ -28,7 +27,6 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
@ExperimentalCoroutinesApi
abstract class ToolbarIntegration(
context: Context,
toolbar: BrowserToolbar,
@ -77,7 +75,6 @@ abstract class ToolbarIntegration(
}
}
@ExperimentalCoroutinesApi
class DefaultToolbarIntegration(
context: Context,
toolbar: BrowserToolbar,

@ -0,0 +1,75 @@
/* 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.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.icons.compose.Loader
import mozilla.components.browser.icons.compose.Placeholder
import mozilla.components.browser.icons.compose.WithIcon
import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.components.components
/**
* Load and display the favicon of a particular website.
*
* @param url Website [URL] for which the favicon will be shown.
* @param size [Dp] height and width of the image to be loaded.
* @param isPrivate Whether or not a private request (like in private browsing) should be used to
* download the icon (if needed).
*/
@Composable
fun Favicon(
url: String,
size: Dp,
isPrivate: Boolean = false
) {
components.core.icons.Loader(
url = url,
isPrivate = isPrivate,
size = size.toIconRequestSize()
) {
Placeholder {
Box(
modifier = Modifier.background(
color = when (isSystemInDarkTheme()) {
true -> PhotonColors.DarkGrey30
false -> PhotonColors.LightGrey30
}
)
)
}
WithIcon { icon ->
Image(
painter = icon.painter,
contentDescription = null,
modifier = Modifier
.size(size)
.clip(RoundedCornerShape(2.dp)),
contentScale = ContentScale.Fit
)
}
}
}
@Composable
private fun Dp.toIconRequestSize() = when {
value <= dimensionResource(IconRequest.Size.DEFAULT.dimen).value -> IconRequest.Size.DEFAULT
value <= dimensionResource(IconRequest.Size.LAUNCHER.dimen).value -> IconRequest.Size.LAUNCHER
else -> IconRequest.Size.LAUNCHER_ADAPTIVE
}

@ -7,7 +7,10 @@ package org.mozilla.fenix.compose
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -27,6 +30,10 @@ import org.mozilla.fenix.components.components
* @param contentDescription Localized text used by accessibility services to describe what this image represents.
* This should always be provided unless this image is used for decorative purposes, and does not represent
* a meaningful action that a user can take.
* @param alignment Optional alignment parameter used to place the [Painter] in the given
* bounds defined by the width and height.
* @param contentScale Optional scale parameter used to determine the aspect ratio scaling to be used
* if the bounds are a different size from the intrinsic size of the [Painter].
*/
@Composable
@Suppress("LongParameterList")
@ -35,7 +42,9 @@ fun Image(
modifier: Modifier = Modifier,
private: Boolean = false,
targetSize: Dp = 100.dp,
contentDescription: String? = null
contentDescription: String? = null,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
) {
ImageLoader(
url = url,
@ -48,6 +57,8 @@ fun Image(
painter = painter,
modifier = modifier,
contentDescription = contentDescription,
alignment = alignment,
contentScale = contentScale
)
}

@ -110,7 +110,7 @@ private fun ListItemTabSurface(
Card(
modifier = modifier,
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.surface,
backgroundColor = FirefoxTheme.colors.layer2,
elevation = 6.dp
) {
Row(

@ -49,7 +49,7 @@ fun ListItemTabLargePlaceholder(
.size(328.dp, 116.dp)
.clickable { onClick() },
shape = RoundedCornerShape(8.dp),
backgroundColor = FirefoxTheme.colors.surface,
backgroundColor = FirefoxTheme.colors.layer2,
elevation = 6.dp,
) {
Column(

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

Loading…
Cancel
Save