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" 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 about: Create a performance issue if the app is slow or it uses too much memory, disk space, battery, or network data
title: "" title: ""
labels: "eng:performance" labels: "performance"
assignees: '' assignees: ''
--- ---

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

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

@ -8,7 +8,7 @@ tasks:
- $let: - $let:
taskgraph: taskgraph:
branch: taskgraph branch: taskgraph
revision: 9daff451cfbe82c5c70237a7b3dbcf4fd3238299 revision: d85f4e4213706ec7737c7257f681977fdf20eb60
trustDomain: mobile trustDomain: mobile
in: in:
$let: $let:
@ -104,7 +104,6 @@ tasks:
tasks_for in ["action", "cron"] tasks_for in ["action", "cron"]
|| (tasks_for == "github-pull-request" && pullRequestAction in ["opened", "reopened", "synchronize"]) || (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-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: then:
$let: $let:
level: level:
@ -249,9 +248,9 @@ tasks:
command: command:
- /usr/local/bin/run-task - /usr/local/bin/run-task
- '--mobile-checkout=/builds/worker/checkouts/src' - '--mobile-checkout=/builds/worker/checkouts/vcs'
- '--taskgraph-checkout=/builds/worker/checkouts/taskgraph' - '--taskgraph-checkout=/builds/worker/checkouts/taskgraph'
- '--task-cwd=/builds/worker/checkouts/src' - '--task-cwd=/builds/worker/checkouts/vcs'
- '--' - '--'
- bash - bash
- -cx - -cx
@ -293,7 +292,7 @@ tasks:
expires: {$fromNow: '1 year'} expires: {$fromNow: '1 year'}
'public/docker-contexts': 'public/docker-contexts':
type: 'directory' 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 # This needs to be at least the deadline of the
# decision task + the docker-image task deadlines. # decision task + the docker-image task deadlines.
# It is set to a week to allow for some time for # 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 // 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 // that will compromise the accuracy of the results. Unfortunately, I couldn't get everything
// working so I had to suppress some things. // working so I had to suppress some things.
// 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 // - ACTIVITY-MISSING: we're supposed to use the test instrumentation runner,
// that we're unable to launch the activity. My understanding is that this runner will use an // "androidx.benchmark.junit4.AndroidBenchmarkRunner". However, when I do so, I get an error
// "IsolationActivity" to reduce the impact of other work on the device from affecting the benchmark // that we're unable to launch the activity. My understanding is that this runner will use an
// and to opt into a lower-max CPU frequency on unrooted devices that support it // "IsolationActivity" to reduce the impact of other work on the device from affecting the benchmark
// - UNLOCKED: ./gradlew lockClocks, which locks the CPU frequency, fails on my device. See // and to opt into a lower-max CPU frequency on unrooted devices that support it
// https://issuetracker.google.com/issues/176836267 for potential workarounds. // - UNLOCKED: ./gradlew lockClocks, which locks the CPU frequency, fails on my device. See
testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'ACTIVITY-MISSING,UNLOCKED' // 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 { plugins {
id "com.jetbrains.python.envs" version "0.0.26" id "com.jetbrains.python.envs" version "0.0.26"
id "com.google.protobuf" version "0.8.17" id "com.google.protobuf" version "0.8.17"
@ -42,6 +44,7 @@ android {
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
resValue "bool", "IS_DEBUG", "false" resValue "bool", "IS_DEBUG", "false"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "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. // This should be the "public" base URL of AMO.
buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\"" buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\""
buildConfigField "String", "AMO_COLLECTION_NAME", "\"7dfae8669acc4312a65e8ba5553036\"" buildConfigField "String", "AMO_COLLECTION_NAME", "\"7dfae8669acc4312a65e8ba5553036\""
@ -83,6 +86,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs) 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")) { if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
@ -271,6 +278,7 @@ android {
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = Versions.androidx_compose kotlinCompilerExtensionVersion = Versions.androidx_compose
} }
} }
// ------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------
@ -447,6 +455,12 @@ configurations {
jnaForTest jnaForTest
} }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
}
}
dependencies { dependencies {
jnaForTest Deps.jna jnaForTest Deps.jna
testImplementation files(configurations.jnaForTest.copyRecursive().files) testImplementation files(configurations.jnaForTest.copyRecursive().files)
@ -631,8 +645,14 @@ dependencies {
} }
protobuf { protobuf {
// Mac M1 workaround until we can bump the version. Dependent on A-S.
// See https://github.com/mozilla-mobile/fenix/issues/22321
protoc { 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 // Generates the java Protobuf-lite code for the Protobufs in this project. See
@ -824,3 +844,11 @@ ext.updateExtensionVersion = { task, extDir ->
expand(values) 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 - android-probes@mozilla.com
- erichards@mozilla.com - erichards@mozilla.com
expires: never 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: synced_tab_opened:
type: event type: event
description: | description: |
@ -623,47 +599,6 @@ toolbar_settings:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-02-01" 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: context_menu:
item_tapped: item_tapped:
type: event type: event
@ -1320,9 +1255,10 @@ metrics:
expires: "2022-02-01" expires: "2022-02-01"
inactive_tabs_count: inactive_tabs_count:
type: quantity type: quantity
lifetime: application
description: | 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: send_in_pings:
- metrics - metrics
bugs: bugs:
@ -1445,6 +1381,22 @@ customize_home:
notification_emails: notification_emails:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-09-20" 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: preferences:
studies_enabled: studies_enabled:
@ -1815,6 +1767,19 @@ preferences:
notification_emails: notification_emails:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-02-01" 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: search.default_engine:
code: code:
@ -2676,12 +2641,12 @@ history:
recent_searches_tapped: recent_searches_tapped:
type: event type: event
description: | 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: extra_keys:
page_number: page_number:
description: | description: |
The page number in the homescreen carousel that the recent searches The page number in the homescreen carousel that the recently visited
card was on. item was on.
bugs: bugs:
- https://github.com/mozilla-mobile/fenix/issues/22172 - https://github.com/mozilla-mobile/fenix/issues/22172
data_reviews: data_reviews:
@ -2691,7 +2656,58 @@ history:
notification_emails: notification_emails:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-11-01" 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: reader_mode:
available: available:
@ -3149,6 +3165,45 @@ tabs_tray:
notification_emails: notification_emails:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-11-01" 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: collections:
renamed: renamed:
@ -4315,6 +4370,23 @@ first_session:
- android-probes@mozilla.com - android-probes@mozilla.com
- erichards@mozilla.com - erichards@mozilla.com
expires: never 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: timestamp:
send_in_pings: send_in_pings:
- first-session - first-session
@ -5584,6 +5656,19 @@ home_screen:
notification_emails: notification_emails:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-04-01" 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: customize_home_clicked:
type: event type: event
description: A user clicked on Customize home from the home screen menu. description: A user clicked on Customize home from the home screen menu.
@ -5912,14 +5997,14 @@ recent_searches:
type: event type: event
description: | description: |
A user has deleted a search term group from the 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 long-press menu "Remove" option. This removes
the item from the homescreen, but does not delete the item from the homescreen, but does not delete
the item from history. the item from history.
bugs: bugs:
- https://github.com/mozilla-mobile/fenix/issues/22175 - https://github.com/mozilla-mobile/fenix/issues/22175
data_reviews: data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/TBD - https://github.com/mozilla-mobile/fenix/pull/22176#issuecomment-956421788
data_sensitivity: data_sensitivity:
- interaction - interaction
notification_emails: notification_emails:
@ -6059,3 +6144,80 @@ credit_cards:
notification_emails: notification_emails:
- android-probes@mozilla.com - android-probes@mozilla.com
expires: "2022-09-01" 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> <html>
<body> <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> <script>
(function() { (function() {
document.getElementById("link").click() document.getElementById("link").click()

@ -14,7 +14,7 @@ import mozilla.components.service.fxa.ServerConfig
object FxaServer { object FxaServer {
private const val CLIENT_ID = "a2270f727f45f648" 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") @Suppress("UNUSED_PARAMETER")
fun config(context: Context): ServerConfig { 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") @Suppress("MagicNumber")
val waitingTime: Long = TimeUnit.SECONDS.toMillis(15) val waitingTime: Long = TimeUnit.SECONDS.toMillis(15)
val waitingTimeShort: Long = TimeUnit.SECONDS.toMillis(1) 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) data class TestAsset(val url: Uri, val content: String, val title: String)
@ -70,7 +72,7 @@ object TestAssetHelper {
fun getDownloadAsset(server: MockWebServer): TestAsset { fun getDownloadAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/download.html").toString().toUri()!! val url = server.url("pages/download.html").toString().toUri()!!
val content = "Page content: Globe.svg" val content = "Page content: $downloadFileName"
return TestAsset(url, content, "") return TestAsset(url, content, "")
} }

@ -16,7 +16,6 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.preference.PreferenceManager
import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingRegistry
@ -33,6 +32,7 @@ import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import java.io.File
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mozilla.components.support.ktx.android.content.appName import mozilla.components.support.ktx.android.content.appName
import org.hamcrest.CoreMatchers 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.ext.waitNotNull
import org.mozilla.fenix.helpers.idlingresource.NetworkConnectionIdlingResource import org.mozilla.fenix.helpers.idlingresource.NetworkConnectionIdlingResource
import org.mozilla.fenix.ui.robots.mDevice import org.mozilla.fenix.ui.robots.mDevice
import java.io.File
object TestHelper { object TestHelper {
@ -71,13 +70,6 @@ object TestHelper {
).perform(longClick()) ).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) { fun restartApp(activity: HomeActivityIntentTestRule) {
with(activity) { with(activity) {
finishActivity() finishActivity()

@ -6,7 +6,7 @@ import androidx.test.espresso.IdlingResource
import mozilla.components.feature.addons.ui.AddonInstallationDialogFragment import mozilla.components.feature.addons.ui.AddonInstallationDialogFragment
class AddonsInstallingIdlingResource( class AddonsInstallingIdlingResource(
val fragmentManager: FragmentManager private val fragmentManager: FragmentManager
) : ) :
IdlingResource { IdlingResource {
private var resourceCallback: IdlingResource.ResourceCallback? = null 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 @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 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_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( private val failureMsgStrictMode = getErrorMessage(
shortName = "StrictMode suppression", shortName = "StrictMode suppression",

@ -11,8 +11,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.locale.LocaleTestRule
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before 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.TestAssetHelper
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull 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.bookmarksMenu
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.mDevice import org.mozilla.fenix.ui.robots.mDevice
import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.swipeToBottom import org.mozilla.fenix.ui.robots.swipeToBottom
import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.locale.LocaleTestRule
class MenuScreenShotTest : ScreenshotTest() { class MenuScreenShotTest : ScreenshotTest() {
private lateinit var mockWebServer: MockWebServer 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 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 tapOnTabCounter() = onView(withId(R.id.counter_text)).click()
fun settingsAccountPreferences() = onView(withText(R.string.preferences_sync)).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.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.bookmarkStorage import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
@ -166,9 +167,11 @@ class BookmarksTest {
addNewFolderName(bookmarksFolderName) addNewFolderName(bookmarksFolderName)
navigateUp() navigateUp()
verifyKeyboardHidden() verifyKeyboardHidden()
verifyBookmarkFolderIsNotCreated(bookmarksFolderName)
} }
} }
@SmokeTest
@Test @Test
fun editBookmarkTest() { fun editBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -277,6 +280,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun deleteBookmarkTest() { fun deleteBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -296,6 +300,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun undoDeleteBookmarkTest() { fun undoDeleteBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -320,8 +325,9 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun multiSelectionToolbarItemsTest() { fun bookmarksMultiSelectionToolbarItemsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen { browserScreen {
@ -345,6 +351,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun openSelectionInNewTabTest() { fun openSelectionInNewTabTest() {
val settings = activityTestRule.activity.applicationContext.settings() val settings = activityTestRule.activity.applicationContext.settings()
@ -375,6 +382,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun openSelectionInPrivateTabTest() { fun openSelectionInPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -398,6 +406,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun deleteMultipleSelectionTest() { fun deleteMultipleSelectionTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -427,6 +436,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun undoDeleteMultipleSelectionTest() { fun undoDeleteMultipleSelectionTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -515,6 +525,7 @@ class BookmarksTest {
} }
} }
@SmokeTest
@Test @Test
fun changeBookmarkParentFolderTest() { fun changeBookmarkParentFolderTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -609,8 +620,37 @@ class BookmarksTest {
IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!) IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!)
}.clickEdit { }.clickEdit {
clickDeleteInEditModeButton() 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() confirmDeletion()
verifyDeleteSnackBarText() verifyDeleteSnackBarText()
clickUndoDeleteButton()
verifyFolderTitle("My Folder")
} }
} }
} }

@ -9,12 +9,13 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
@ -34,13 +35,17 @@ class CollectionTest {
private lateinit var mockWebServer: MockWebServer private lateinit var mockWebServer: MockWebServer
private val firstCollectionName = "testcollection_1" private val firstCollectionName = "testcollection_1"
private val secondCollectionName = "testcollection_2" private val secondCollectionName = "testcollection_2"
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule @get:Rule
val activityTestRule = HomeActivityTestRule() val activityTestRule = HomeActivityTestRule()
@Before @Before
fun setUp() { 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 { mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher() dispatcher = AndroidAssetDispatcher()
@ -51,6 +56,9 @@ class CollectionTest {
@After @After
fun tearDown() { fun tearDown() {
mockWebServer.shutdown() mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
} }
@Test @Test
@ -81,7 +89,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun verifyAddTabButtonOfCollectionMenu() { fun verifyAddTabButtonOfCollectionMenu() {
val firstWebPage = getGenericAsset(mockWebServer, 1) val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2) val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -108,7 +115,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun renameCollectionTest() { fun renameCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1) val webPage = getGenericAsset(mockWebServer, 1)
@ -269,4 +275,29 @@ class CollectionTest {
verifyMenuButton() 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 okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.downloadRobot import org.mozilla.fenix.ui.robots.downloadRobot
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar 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 { class ContextMenusTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer private lateinit var mockWebServer: MockWebServer
@ -42,6 +43,7 @@ class ContextMenusTest {
@Before @Before
fun setUp() { fun setUp() {
activityIntentTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
mockWebServer = MockWebServer().apply { mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher() dispatcher = AndroidAssetDispatcher()
start() start()
@ -53,6 +55,7 @@ class ContextMenusTest {
mockWebServer.shutdown() mockWebServer.shutdown()
} }
@SmokeTest
@Test @Test
fun verifyContextOpenLinkNewTab() { fun verifyContextOpenLinkNewTab() {
val pageLinks = val pageLinks =
@ -67,7 +70,7 @@ class ContextMenusTest {
verifyLinkContextMenuItems(genericURL.url) verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInNewTab() clickContextOpenLinkInNewTab()
verifySnackBarText("New tab opened") verifySnackBarText("New tab opened")
snackBarButtonClick("Switch") snackBarButtonClick()
verifyUrl(genericURL.url.toString()) verifyUrl(genericURL.url.toString())
}.openTabDrawer { }.openTabDrawer {
verifyNormalModeSelected() verifyNormalModeSelected()
@ -76,6 +79,7 @@ class ContextMenusTest {
} }
} }
@SmokeTest
@Test @Test
fun verifyContextOpenLinkPrivateTab() { fun verifyContextOpenLinkPrivateTab() {
val pageLinks = val pageLinks =
@ -90,7 +94,7 @@ class ContextMenusTest {
verifyLinkContextMenuItems(genericURL.url) verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInPrivateTab() clickContextOpenLinkInPrivateTab()
verifySnackBarText("New private tab opened") verifySnackBarText("New private tab opened")
snackBarButtonClick("Switch") snackBarButtonClick()
verifyUrl(genericURL.url.toString()) verifyUrl(genericURL.url.toString())
}.openTabDrawer { }.openTabDrawer {
verifyPrivateModeSelected() verifyPrivateModeSelected()
@ -98,7 +102,6 @@ class ContextMenusTest {
} }
} }
@Ignore("Test failures: https://github.com/mozilla-mobile/fenix/issues/12473")
@Test @Test
fun verifyContextCopyLink() { fun verifyContextCopyLink() {
val pageLinks = val pageLinks =
@ -135,7 +138,6 @@ class ContextMenusTest {
} }
} }
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12367")
@Test @Test
fun verifyContextOpenImageNewTab() { fun verifyContextOpenImageNewTab() {
val pageLinks = val pageLinks =
@ -150,13 +152,12 @@ class ContextMenusTest {
verifyLinkImageContextMenuItems(imageResource.url) verifyLinkImageContextMenuItems(imageResource.url)
clickContextOpenImageNewTab() clickContextOpenImageNewTab()
verifySnackBarText("New tab opened") verifySnackBarText("New tab opened")
snackBarButtonClick("Switch") snackBarButtonClick()
verifyUrl(imageResource.url.toString()) verifyUrl(imageResource.url.toString())
} }
} }
@Test @Test
@Ignore("Disabled Google Keyboard Clipboard overlay blocks the address bar: https://github.com/mozilla-mobile/fenix/issues/10586")
fun verifyContextCopyImageLocation() { fun verifyContextCopyImageLocation() {
val pageLinks = val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4) TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -177,7 +178,6 @@ class ContextMenusTest {
} }
@Test @Test
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12309")
fun verifyContextSaveImage() { fun verifyContextSaveImage() {
val pageLinks = val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4) TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -202,7 +202,6 @@ class ContextMenusTest {
} }
@Test @Test
@Ignore("Intermittent: https://github.com/mozilla-mobile/fenix/issues/12309")
fun verifyContextMixedVariations() { fun verifyContextMixedVariations() {
val pageLinks = val pageLinks =
TestAssetHelper.getGenericAsset(mockWebServer, 4) TestAssetHelper.getGenericAsset(mockWebServer, 4)
@ -224,4 +223,48 @@ class ContextMenusTest {
verifyNoLinkImageContextMenuItems(imageResource.url) 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 @Test
fun openSettings() { fun openSettings() {
robot.openSettings { robot.openSettings {
verifyBasicsHeading() verifyGeneralHeading()
verifyAdvancedHeading() verifyAdvancedHeading()
} }
} }

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

@ -13,9 +13,11 @@ import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
@ -36,6 +38,7 @@ class HistoryTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping. /* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mockWebServer: MockWebServer private lateinit var mockWebServer: MockWebServer
private var historyListIdlingResource: RecyclerViewIdlingResource? = null private var historyListIdlingResource: RecyclerViewIdlingResource? = null
private var recentlyClosedTabsListIdlingResource: RecyclerViewIdlingResource? = null
@get:Rule @get:Rule
val activityTestRule = HomeActivityTestRule() val activityTestRule = HomeActivityTestRule()
@ -65,6 +68,10 @@ class HistoryTest {
if (historyListIdlingResource != null) { if (historyListIdlingResource != null) {
IdlingRegistry.getInstance().unregister(historyListIdlingResource!!) IdlingRegistry.getInstance().unregister(historyListIdlingResource!!)
} }
if (recentlyClosedTabsListIdlingResource != null) {
IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!)
}
} }
@Test @Test
@ -78,6 +85,7 @@ class HistoryTest {
} }
} }
@Ignore("Failing, see https://github.com/mozilla-mobile/fenix/issues/22304")
@Test @Test
// Test running on beta/release builds in CI: // Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds // caution when making changes to it, so they don't block the builds
@ -120,6 +128,7 @@ class HistoryTest {
} }
} }
@SmokeTest
@Test @Test
fun deleteAllHistoryTest() { fun deleteAllHistoryTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -142,8 +151,9 @@ class HistoryTest {
} }
} }
@SmokeTest
@Test @Test
fun multiSelectionToolbarItemsTest() { fun historyMultiSelectionToolbarItemsTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar { navigationToolbar {
@ -278,4 +288,28 @@ class HistoryTest {
verifyShareTabUrl() 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() verifyWelcomeHeader()
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
verifyBasicsHeading() verifyGeneralHeading()
}.goBack { }.goBack {
verifyExistingTopSitesList() verifyExistingTopSitesList()
} }

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

@ -1,22 +1,21 @@
package org.mozilla.fenix.ui 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 /* 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, 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 okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.navigationToolbar
@ -80,7 +79,7 @@ class SettingsAddonsTest {
val addonName = "uBlock Origin" val addonName = "uBlock Origin"
navigationToolbar {} navigationToolbar {}
.openNewTabAndEnterToBrowser(defaultWebPage.url) {} .enterURLAndEnterToBrowser(defaultWebPage.url) {}
.openThreeDotMenu {} .openThreeDotMenu {}
.openAddonsManagerMenu { .openAddonsManagerMenu {
addonsListIdlingResource = addonsListIdlingResource =

@ -10,15 +10,17 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.FenixApplication 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.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestAssetHelper.getLoremIpsumAsset 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.checkTextSizeOnWebsite
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.navigationToolbar
@ -33,6 +35,7 @@ class SettingsBasicsTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer private lateinit var mockWebServer: MockWebServer
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule @get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule() val activityIntentTestRule = HomeActivityIntentTestRule()
@ -43,13 +46,16 @@ class SettingsBasicsTest {
dispatcher = AndroidAssetDispatcher() dispatcher = AndroidAssetDispatcher()
start() start()
} }
val settings = activityIntentTestRule.activity.settings()
settings.shouldShowJumpBackInCFR = false featureSettingsHelper.setJumpBackCFREnabled(false)
} }
@After @After
fun tearDown() { fun tearDown() {
mockWebServer.shutdown() mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
} }
private fun getUiTheme(): Boolean { private fun getUiTheme(): Boolean {
@ -64,33 +70,33 @@ class SettingsBasicsTest {
} }
@Test @Test
// Walks through settings menu and sub-menus to ensure all items are present fun settingsGeneralItemsTests() {
fun settingsMenuBasicsItemsTests() { homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifySettingsToolbar()
verifyGeneralHeading()
verifySearchButton()
verifyTabsButton()
verifyHomepageButton()
verifyCustomizeButton()
verifyLoginsAndPasswordsButton()
verifyCreditCardsButton()
verifyAccessibilityButton()
verifyLanguageButton()
verifySetAsDefaultBrowserButton()
}
}
@Test
fun searchSettingsItemsTest() {
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
verifyBasicsHeading()
verifySearchEngineButton()
verifyDefaultBrowserItem()
verifyTabsItem()
// drill down to submenu
}.openSearchSubMenu { }.openSearchSubMenu {
verifySearchToolbar()
verifyDefaultSearchEngineHeader() verifyDefaultSearchEngineHeader()
verifySearchEngineList() 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 @Test
fun changeThemeSetting() { fun changeThemeSetting() {
// Goes through the settings and changes the default search engine, then verifies it changes. // 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. // 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 fenixApp = activityIntentTestRule.activity.applicationContext as FenixApplication
val webpage = getLoremIpsumAsset(mockWebServer).url 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%. // This value will represent the text size percentage the webpage will scale to. The default value is 100%.
val textSizePercentage = 180 val textSizePercentage = 180
@ -184,4 +187,97 @@ class SettingsBasicsTest {
verifyMenuItemsAreDisabled() 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.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -449,10 +450,11 @@ class SettingsPrivacyTest {
confirmDeletionAndAssertSnackbar() confirmDeletionAndAssertSnackbar()
} }
settingsScreen { settingsScreen {
verifyBasicsHeading() verifyGeneralHeading()
} }
} }
@SmokeTest
@Test @Test
fun deleteTabsDataTest() { fun deleteTabsDataTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -472,16 +474,17 @@ class SettingsPrivacyTest {
confirmDeletionAndAssertSnackbar() confirmDeletionAndAssertSnackbar()
} }
settingsScreen { settingsScreen {
verifyBasicsHeading() verifyGeneralHeading()
}.openSettingsSubMenuDeleteBrowsingData { }.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsDetails("0") verifyOpenTabsDetails("0")
}.goBack { }.goBack {
}.goBack { }.goBack {
}.openTabDrawer { }.openTabDrawer {
verifyNoTabsOpened() verifyNoOpenTabsInNormalBrowsing()
} }
} }
@SmokeTest
@Test @Test
fun deleteDeleteBrowsingHistoryDataTest() { fun deleteDeleteBrowsingHistoryDataTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -505,7 +508,7 @@ class SettingsPrivacyTest {
confirmDeletionAndAssertSnackbar() confirmDeletionAndAssertSnackbar()
verifyBrowsingHistoryDetails("0") verifyBrowsingHistoryDetails("0")
}.goBack { }.goBack {
verifyBasicsHeading() verifyGeneralHeading()
}.goBack { }.goBack {
} }
navigationToolbar { 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.junit.Test
import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP 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.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper 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
import org.mozilla.fenix.helpers.TestHelper.appName import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.assertExternalAppOpens import org.mozilla.fenix.helpers.TestHelper.assertExternalAppOpens
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.TestHelper.deleteDownloadFromStorage import org.mozilla.fenix.helpers.TestHelper.deleteDownloadFromStorage
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled 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.returnToBrowser
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource 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 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 * These tests will verify different functionalities of the app as a way to quickly detect regressions in main areas
*/ */
@Suppress("ForbiddenComment") @Suppress("ForbiddenComment")
@SmokeTest
class SmokeTest { class SmokeTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer private lateinit var mockWebServer: MockWebServer
@ -68,22 +73,12 @@ class SmokeTest {
private var addonsListIdlingResource: RecyclerViewIdlingResource? = null private var addonsListIdlingResource: RecyclerViewIdlingResource? = null
private var recentlyClosedTabsListIdlingResource: RecyclerViewIdlingResource? = null private var recentlyClosedTabsListIdlingResource: RecyclerViewIdlingResource? = null
private var readerViewNotification: ViewVisibilityIdlingResource? = null private var readerViewNotification: ViewVisibilityIdlingResource? = null
private val downloadFileName = "Globe.svg"
private val collectionName = "First Collection" private val collectionName = "First Collection"
private var bookmarksListIdlingResource: RecyclerViewIdlingResource? = null private var bookmarksListIdlingResource: RecyclerViewIdlingResource? = null
private var localeListIdlingResource: RecyclerViewIdlingResource? = null private var localeListIdlingResource: RecyclerViewIdlingResource? = null
private val customMenuItem = "TestMenuItem" 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 lateinit var browserStore: BrowserStore
private val featureSettingsHelper = FeatureSettingsHelper()
@get:Rule @get:Rule
val activityTestRule = AndroidComposeTestRule( val activityTestRule = AndroidComposeTestRule(
@ -108,7 +103,9 @@ class SmokeTest {
// So we are initializing this here instead of in all related tests. // So we are initializing this here instead of in all related tests.
browserStore = activityTestRule.activity.components.core.store 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 { mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher() dispatcher = AndroidAssetDispatcher()
start() start()
@ -144,6 +141,9 @@ class SmokeTest {
if (localeListIdlingResource != null) { if (localeListIdlingResource != null) {
IdlingRegistry.getInstance().unregister(localeListIdlingResource) IdlingRegistry.getInstance().unregister(localeListIdlingResource)
} }
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
} }
// Verifies the first run onboarding screen // Verifies the first run onboarding screen
@ -311,9 +311,6 @@ class SmokeTest {
@Test @Test
// Verifies the Add to top sites option in a tab's 3 dot menu // Verifies the Add to top sites option in a tab's 3 dot menu
fun openMainMenuAddTopSiteTest() { fun openMainMenuAddTopSiteTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar { navigationToolbar {
@ -350,6 +347,8 @@ class SmokeTest {
clickAddShortcutButton() clickAddShortcutButton()
clickAddAutomaticallyButton() clickAddAutomaticallyButton()
}.openHomeScreenShortcut("Test Page") { }.openHomeScreenShortcut("Test Page") {
verifyUrl(website.url.toString())
verifyTabCounter("1")
} }
} }
@ -418,8 +417,10 @@ class SmokeTest {
navigationToolbar { navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu { }.openThreeDotMenu {
}.sharePage { }.clickShareButton {
verifyShareAppsLayout() verifyShareTabLayout()
verifySendToDeviceTitle()
verifyShareALinkTitle()
} }
} }
@ -447,7 +448,7 @@ class SmokeTest {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
}.openEnhancedTrackingProtectionSubMenu { }.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionOptions() verifyEnhancedTrackingProtectionOptionsEnabled()
selectTrackingProtectionOption("Custom") selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings() verifyCustomTrackingProtectionSettings()
}.goBackToHomeScreen {} }.goBackToHomeScreen {}
@ -471,80 +472,48 @@ class SmokeTest {
@Test @Test
// Verifies changing the default engine from the Search Shortcut menu // Verifies changing the default engine from the Search Shortcut menu
fun verifySearchEngineCanBeChangedTemporarilyUsingShortcuts() { fun selectSearchEnginesShortcutTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val enginesList = listOf("DuckDuckGo", "Google", "Amazon.com", "Wikipedia", "Bing", "eBay")
homeScreen { for (searchEngine in enginesList) {
}.openSearch { homeScreen {
verifyKeyboardVisibility() }.openSearch {
clickSearchEngineShortcutButton() verifyKeyboardVisibility()
verifySearchEngineList(activityTestRule) clickSearchEngineShortcutButton()
changeDefaultSearchEngine(activityTestRule, "Amazon.com") verifySearchEngineList(activityTestRule)
verifySearchEngineIcon("Amazon.com") changeDefaultSearchEngine(activityTestRule, searchEngine)
}.goToSearchEngine { verifySearchEngineIcon(searchEngine)
mDevice.waitForIdle() }.submitQuery("mozilla ") {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { verifyUrl(searchEngine)
}.openTabDrawer { }.goToHomescreen { }
}.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")
} }
} }
@Test @Test
// Ads a new search engine from the list of custom engines // Ads a new search engine from the list of custom engines
fun addPredefinedSearchEngineTest() { fun addPredefinedSearchEngineTest() {
val searchEngine = "Reddit"
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
}.openSearchSubMenu { }.openSearchSubMenu {
openAddSearchEngineMenu() openAddSearchEngineMenu()
verifyAddSearchEngineList() verifyAddSearchEngineList()
addNewSearchEngine("YouTube") addNewSearchEngine(searchEngine)
verifyEngineListContains("YouTube") verifyEngineListContains(searchEngine)
}.goBack { }.goBack {
}.goBack { }.goBack {
}.openSearch { }.openSearch {
verifyKeyboardVisibility() verifyKeyboardVisibility()
clickSearchEngineShortcutButton() clickSearchEngineShortcutButton()
mDevice.waitForIdle() verifyEnginesListShortcutContains(activityTestRule, searchEngine)
activityTestRule.waitForIdle() changeDefaultSearchEngine(activityTestRule, searchEngine)
verifyEnginesListShortcutContains(activityTestRule, "YouTube") }.submitQuery("mozilla ") {
verifyUrl(searchEngine)
} }
} }
@Ignore("Started failing: https://github.com/mozilla-mobile/fenix/issues/21540")
@Test @Test
// Verifies setting as default a customized search engine name and URL // Verifies setting as default a customized search engine name and URL
fun editCustomSearchEngineTest() { fun editCustomSearchEngineTest() {
@ -576,7 +545,6 @@ class SmokeTest {
} }
} }
@Ignore("Strated failing on Nighlty task: https://github.com/mozilla-mobile/fenix/issues/21620")
@Test @Test
// Test running on beta/release builds in CI: // Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds // caution when making changes to it, so they don't block the builds
@ -713,8 +681,8 @@ class SmokeTest {
} }
@Test @Test
// This test verifies the Recently Closed Tabs List and items // Verifies that a recently closed item is properly opened
fun verifyRecentlyClosedTabsListTest() { fun openRecentlyClosedItemTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1) val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen { homeScreen {
@ -731,39 +699,13 @@ class SmokeTest {
IdlingRegistry.getInstance().register(recentlyClosedTabsListIdlingResource!!) IdlingRegistry.getInstance().register(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsMenuView() verifyRecentlyClosedTabsMenuView()
IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!) IdlingRegistry.getInstance().unregister(recentlyClosedTabsListIdlingResource!!)
verifyRecentlyClosedTabsPageTitle("Test_Page_1") }.clickRecentlyClosedItem("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 {
verifyUrl(website.url.toString()) verifyUrl(website.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
} }
} }
@Test @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() { fun deleteRecentlyClosedTabsItemTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1) val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -821,10 +763,11 @@ class SmokeTest {
} }
@Test @Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun createFirstCollectionTest() { fun createFirstCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings() // disabling these features to have better visibility of Collections
settings.shouldShowJumpBackInCFR = false featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setPocketEnabled(false)
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2) val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
@ -836,6 +779,7 @@ class SmokeTest {
}.submitQuery(secondWebPage.url.toString()) { }.submitQuery(secondWebPage.url.toString()) {
mDevice.waitForIdle() mDevice.waitForIdle()
}.goToHomescreen { }.goToHomescreen {
swipeToBottom()
}.clickSaveTabsToCollectionButton { }.clickSaveTabsToCollectionButton {
longClickTab(firstWebPage.title) longClickTab(firstWebPage.title)
selectTab(secondWebPage.title) selectTab(secondWebPage.title)
@ -855,10 +799,11 @@ class SmokeTest {
} }
@Test @Test
@Ignore("https://github.com/mozilla-mobile/fenix/issues/21397")
fun verifyExpandedCollectionItemsTest() { fun verifyExpandedCollectionItemsTest() {
val settings = activityTestRule.activity.applicationContext.settings() // disabling these features to have better visibility of Collections
settings.shouldShowJumpBackInCFR = false featureSettingsHelper.setRecentTabsFeatureEnabled(false)
featureSettingsHelper.setPocketEnabled(false)
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar { navigationToolbar {
@ -873,8 +818,27 @@ class SmokeTest {
verifyCollectionIcon() verifyCollectionIcon()
}.expandCollection(collectionName) { }.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title) verifyTabSavedInCollection(webPage.title)
verifyCollectionTabLogo() verifyCollectionTabLogo(true)
verifyCollectionTabUrl() 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) verifyShareCollectionButtonIsVisible(true)
verifyCollectionMenuIsVisible(true) verifyCollectionMenuIsVisible(true)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, true) verifyCollectionItemRemoveButtonIsVisible(webPage.title, true)
@ -884,6 +848,9 @@ class SmokeTest {
verifyTabSavedInCollection(webPage.title, false) verifyTabSavedInCollection(webPage.title, false)
verifyShareCollectionButtonIsVisible(false) verifyShareCollectionButtonIsVisible(false)
verifyCollectionMenuIsVisible(false) verifyCollectionMenuIsVisible(false)
verifyCollectionTabLogo(false)
verifyCollectionTabUrl(false)
verifyCollectionItemRemoveButtonIsVisible(webPage.title, false)
} }
} }
@ -911,25 +878,28 @@ class SmokeTest {
@Test @Test
fun shareCollectionTest() { fun shareCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings() val firstWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 1)
settings.shouldShowJumpBackInCFR = false val secondWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val sharingApp = "Gmail"
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val urlString = "${secondWebsite.url}\n\n${firstWebsite.url}"
navigationToolbar { navigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) { }.enterURLAndEnterToBrowser(firstWebsite.url) {
verifyPageContent(firstWebsite.content)
}.openTabDrawer { }.openTabDrawer {
createCollection(webPage.title, collectionName) createCollection(firstWebsite.title, collectionName)
snackBarButtonClick("VIEW") }.openNewTab {
} }.submitQuery(secondWebsite.url.toString()) {
verifyPageContent(secondWebsite.content)
homeScreen { }.openThreeDotMenu {
}.openSaveToCollection {
}.selectExistingCollection(collectionName) {
}.goToHomescreen {
}.expandCollection(collectionName) { }.expandCollection(collectionName) {
clickShareCollectionButton() }.clickShareCollectionButton {
} verifyShareTabsOverlay(firstWebsite.title, secondWebsite.title)
selectAppToShareWith(sharingApp)
homeScreen { verifySharedTabsIntent(urlString, collectionName)
verifyShareTabsOverlay()
} }
} }
@ -937,8 +907,6 @@ class SmokeTest {
// Test running on beta/release builds in CI: // Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds // caution when making changes to it, so they don't block the builds
fun deleteCollectionTest() { fun deleteCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar { navigationToolbar {
@ -977,6 +945,18 @@ class SmokeTest {
verifyFolderTitle("My Folder") verifyFolderTitle("My Folder")
IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!) IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!)
}.openThreeDotMenu("Test_Page_1") { }.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 { }.clickEdit {
clickParentFolderSelector() clickParentFolderSelector()
selectFolder("My Folder") selectFolder("My Folder")
@ -990,51 +970,62 @@ class SmokeTest {
}.clickDelete { }.clickDelete {
confirmDeletion() confirmDeletion()
verifyDeleteSnackBarText() verifyDeleteSnackBarText()
verifyBookmarkIsDeleted("My Folder")
verifyBookmarkIsDeleted("My Folder 2")
verifyBookmarkIsDeleted("Test_Page_1")
navigateUp() navigateUp()
} }
browserScreen { browserScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
verifyBookmarksButton() verifyAddBookmarkButton()
} }
} }
@Test @Test
fun shareTabsFromTabsTrayTest() { 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 { homeScreen {
}.openNavigationToolbar { }.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) { }.enterURLAndEnterToBrowser(firstWebsite.url) {
mDevice.waitForIdle() verifyPageContent(firstWebsite.content)
}.openTabDrawer {
}.openNewTab {
}.submitQuery(secondWebsite.url.toString()) {
verifyPageContent(secondWebsite.content)
}.openTabDrawer { }.openTabDrawer {
verifyNormalModeSelected()
verifyExistingTabList()
verifyExistingOpenTabs("Test_Page_1") verifyExistingOpenTabs("Test_Page_1")
verifyTabTrayOverflowMenu(true) verifyExistingOpenTabs("Test_Page_2")
}.openTabsListThreeDotMenu { }.openTabsListThreeDotMenu {
verifyShareAllTabsButton() verifyShareAllTabsButton()
clickShareAllTabsButton() }.clickShareAllTabsButton {
verifyShareTabsOverlay() verifyShareTabsOverlay(firstWebsiteTitle, secondWebsiteTitle)
selectAppToShareWith(sharingApp)
verifySharedTabsIntent(
sharedUrlsString,
"$firstWebsiteTitle, $secondWebsiteTitle"
)
} }
} }
@Test @Test
fun emptyTabsTrayViewPrivateBrowsingTest() { fun emptyTabsTrayViewPrivateBrowsingTest() {
homeScreen { navigationToolbar {
}.dismissOnboarding() }.openTabTray {
homeScreen {
}.openTabDrawer {
}.toggleToPrivateTabs() { }.toggleToPrivateTabs() {
verifyPrivateModeSelected() verifyNormalBrowsingButtonIsSelected(false)
verifyNormalBrowsingButtonIsDisplayed() verifyPrivateBrowsingButtonIsSelected(true)
verifyNoTabsOpened() verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInPrivateBrowsing()
verifyPrivateBrowsingNewTabButton()
verifyTabTrayOverflowMenu(true) verifyTabTrayOverflowMenu(true)
verifyNewTabButton() verifyEmptyTabsTrayMenuButtons()
}.openTabsListThreeDotMenu {
verifyTabSettingsButton()
verifyRecentlyClosedTabsButton()
} }
} }
@ -1050,14 +1041,19 @@ class SmokeTest {
}.enterURLAndEnterToBrowser(website.url) { }.enterURLAndEnterToBrowser(website.url) {
mDevice.waitForIdle() mDevice.waitForIdle()
}.openTabDrawer { }.openTabDrawer {
verifyPrivateModeSelected() verifyNormalBrowsingButtonIsSelected(false)
verifyNormalBrowsingButtonIsDisplayed() verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyTabTrayOverflowMenu(true)
verifyTabsTrayCounter()
verifyExistingTabList() verifyExistingTabList()
verifyExistingOpenTabs("Test_Page_1") verifyExistingOpenTabs(website.title)
verifyCloseTabsButton("Test_Page_1") verifyCloseTabsButton(website.title)
verifyOpenedTabThumbnail() verifyOpenedTabThumbnail()
verifyTabTrayOverflowMenu(true) verifyPrivateBrowsingNewTabButton()
verifyNewTabButton() }.openTab(website.title) {
verifyUrl(website.url.toString())
verifyTabCounter("1")
} }
} }
@ -1100,7 +1096,6 @@ class SmokeTest {
} }
@Test @Test
@Ignore("To be re-enabled later. See https://github.com/mozilla-mobile/fenix/issues/20716")
fun mainMenuInstallPWATest() { fun mainMenuInstallPWATest() {
val pwaPage = "https://mozilla-mobile.github.io/testapp/" val pwaPage = "https://mozilla-mobile.github.io/testapp/"
@ -1166,7 +1161,7 @@ class SmokeTest {
}.openTabCrashReporter { }.openTabCrashReporter {
}.clickTabCrashedCloseButton { }.clickTabCrashedCloseButton {
}.openTabDrawer { }.openTabDrawer {
verifyNoTabsOpened() verifyNoOpenTabsInNormalBrowsing()
} }
} }
@ -1275,7 +1270,7 @@ class SmokeTest {
assertPlaybackState(browserStore, MediaSession.PlaybackState.PLAYING) assertPlaybackState(browserStore, MediaSession.PlaybackState.PLAYING)
}.openTabDrawer { }.openTabDrawer {
verifyTabMediaControlButtonState("Pause") verifyTabMediaControlButtonState("Pause")
clickTabMediaControlButton() clickTabMediaControlButton("Pause")
verifyTabMediaControlButtonState("Play") verifyTabMediaControlButtonState("Play")
}.openTab(audioTestPage.title) { }.openTab(audioTestPage.title) {
assertPlaybackState(browserStore, MediaSession.PlaybackState.PAUSED) assertPlaybackState(browserStore, MediaSession.PlaybackState.PAUSED)
@ -1357,8 +1352,6 @@ class SmokeTest {
@Test @Test
fun goToHomeScreenBottomToolbarTest() { fun goToHomeScreenBottomToolbarTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1) val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar { navigationToolbar {
@ -1371,9 +1364,6 @@ class SmokeTest {
@Test @Test
fun goToHomeScreenTopToolbarTest() { fun goToHomeScreenTopToolbarTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.shouldShowJumpBackInCFR = false
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1) val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen { homeScreen {
@ -1435,29 +1425,7 @@ class SmokeTest {
}.openTabsSubMenu { }.openTabsSubMenu {
verifyTabViewOptions() verifyTabViewOptions()
verifyCloseTabsOptions() verifyCloseTabsOptions()
verifyStartOnHomeOptions() verifyMoveOldTabsToInactiveOptions()
}
}
@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()
} }
} }
} }

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

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

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

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

@ -12,7 +12,6 @@ import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.widget.EditText import android.widget.EditText
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents 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.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility 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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility 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
import androidx.test.uiautomator.By.text import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.mediasession.MediaSession import mozilla.components.concept.engine.mediasession.MediaSession
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Assert.fail import org.junit.Assert.fail
import org.mozilla.fenix.R 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.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.packageName 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.click
import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.helpers.ext.waitNotNull
@ -103,16 +102,23 @@ class BrowserRobot {
} }
fun verifyTabCounter(expectedText: String) { fun verifyTabCounter(expectedText: String) {
onView(withId(R.id.counter_text)) val counter =
.check((matches(withText(containsString(expectedText))))) mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/counter_text")
.text(expectedText)
)
assertTrue(counter.waitForExists(waitingTime))
} }
fun verifySnackBarText(expectedText: String) { fun verifySnackBarText(expectedText: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(expectedText)))
mDevice.waitNotNull(Until.findObject(text(expectedText)), waitingTime)
onView(withText(expectedText)).check( assertTrue(
matches(isCompletelyDisplayed()) mDevice.findObject(
UiSelector()
.textContains(expectedText)
).waitForExists(waitingTime)
) )
} }
@ -154,29 +160,19 @@ class BrowserRobot {
) )
} }
fun verifyNavURLBar() = assertNavURLBar()
fun verifyNavURLBarHidden() = assertNavURLBarHidden() fun verifyNavURLBarHidden() = assertNavURLBarHidden()
fun verifySecureConnectionLockIcon() = assertSecureConnectionLockIcon() fun verifySecureConnectionLockIcon() = assertSecureConnectionLockIcon()
fun verifyEnhancedTrackingProtectionSwitch() = assertEnhancedTrackingProtectionSwitch()
fun verifyEnhancedTrackingOptions() {
onView(withId(R.id.mozac_browser_toolbar_security_indicator)).click()
verifyEnhancedTrackingProtectionSwitch()
}
fun verifyMenuButton() = assertMenuButton() fun verifyMenuButton() = assertMenuButton()
fun verifyNavURLBarItems() { fun verifyNavURLBarItems() {
verifyEnhancedTrackingOptions() navURLBar().waitForExists(waitingTime)
pressBack()
waitingTime
verifySecureConnectionLockIcon()
verifyTabCounter("1")
verifyNavURLBar()
verifyMenuButton() verifyMenuButton()
verifyTabCounter("1")
verifySearchBar()
verifySecureConnectionLockIcon()
verifyHomeScreenButton()
} }
fun verifyNoLinkImageContextMenuItems(containsURL: Uri) { fun verifyNoLinkImageContextMenuItems(containsURL: Uri) {
@ -199,6 +195,10 @@ class BrowserRobot {
) )
} }
fun verifyHomeScreenButton() = assertHomeScreenButton()
fun verifySearchBar() = assertSearchBar()
fun dismissContentContextMenu(containsURL: Uri) { fun dismissContentContextMenu(containsURL: Uri) {
onView(withText(containsURL.toString())) onView(withText(containsURL.toString()))
.inRoot(isDialog()) .inRoot(isDialog())
@ -314,11 +314,31 @@ class BrowserRobot {
} }
fun longClickMatchingText(expectedText: String) { fun longClickMatchingText(expectedText: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) try {
mDevice.waitNotNull(Until.findObject(text(expectedText)), waitingTime) 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)) // Refresh the page in case the first long click didn't succeed
element.click(LONG_CLICK_DURATION) 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) { fun longClickAndCopyText(expectedText: String, selectAll: Boolean = false) {
@ -374,10 +394,45 @@ class BrowserRobot {
} }
} }
fun snackBarButtonClick(expectedText: String) { fun longClickAndSearchText(searchButton: String, expectedText: String) {
onView(allOf(withId(R.id.snackbar_btn), withText(expectedText))).check( var currentTries = 0
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)) while (currentTries++ < 3) {
).perform(ViewActions.click()) 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() { fun verifySaveLoginPromptIsShown() {
@ -405,12 +460,38 @@ class BrowserRobot {
.resourceId("password") .resourceId("password")
.className(EditText::class.java) .className(EditText::class.java)
) )
passwordField.waitForExists(waitingTime) try {
passwordField.click() passwordField.waitForExists(waitingTime)
passwordField.clearTextField() mDevice.findObject(
passwordField.text = password By
// wait until the password is hidden .res("password")
assertTrue(mDevice.findObject(UiSelector().text(password)).waitUntilGone(waitingTime)) .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() { fun clickMediaPlayerPlayButton() {
@ -497,7 +578,10 @@ class BrowserRobot {
} }
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition { 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() tabsCounter().click()
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/tab_layout"))) mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/tab_layout")))
@ -550,6 +634,14 @@ class BrowserRobot {
HomeScreenRobot().interact() HomeScreenRobot().interact()
return HomeScreenRobot.Transition() 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")) 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() { private fun assertHomeScreenButton() =
withText(R.id.trackingProtectionSwitch) homeScreenButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.matches(withEffectiveVisibility(Visibility.VISIBLE))
}
private fun assertProtectionSettingsButton() { private fun assertSearchBar() = searchBar().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withId(R.id.protection_settings))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) private fun assertNavURLBar() = assertTrue(navURLBar().waitForExists(waitingTime))
}
private fun assertNavURLBarHidden() = assertTrue(navURLBar().waitUntilGone(waitingTime))
private fun assertSecureConnectionLockIcon() { private fun assertSecureConnectionLockIcon() {
onView(withId(R.id.mozac_browser_toolbar_security_indicator)) onView(withId(R.id.mozac_browser_toolbar_security_indicator))
@ -586,7 +677,7 @@ private fun assertMenuButton() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .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() = private fun mediaPlayerPlayButton() =
mDevice.findObject( mDevice.findObject(

@ -78,12 +78,20 @@ class CollectionRobot {
.check(doesNotExist()) .check(doesNotExist())
} }
fun verifyCollectionTabUrl() { fun verifyCollectionTabUrl(visible: Boolean) {
onView(withId(R.id.caption)).check(matches(isDisplayed())) onView(withId(R.id.caption))
.check(
if (visible) matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
else doesNotExist()
)
} }
fun verifyCollectionTabLogo() { fun verifyCollectionTabLogo(visible: Boolean) {
onView(withId(R.id.favicon)).check(matches(isDisplayed())) onView(withId(R.id.favicon))
.check(
if (visible) matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
else doesNotExist()
)
} }
fun verifyShareCollectionButtonIsVisible(visible: Boolean) { fun verifyShareCollectionButtonIsVisible(visible: Boolean) {
@ -94,8 +102,6 @@ class CollectionRobot {
) )
} }
fun clickShareCollectionButton() = onView(withId(R.id.collection_share_button)).click()
fun verifyCollectionMenuIsVisible(visible: Boolean) { fun verifyCollectionMenuIsVisible(visible: Boolean) {
collectionThreeDotButton() collectionThreeDotButton()
.check( .check(
@ -240,6 +246,13 @@ class CollectionRobot {
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() 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.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.RootMatchers.isDialog 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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
@ -132,6 +133,8 @@ private fun assertDownloadNotificationPopup() {
mDevice.waitNotNull(Until.findObjects(By.text("Open")), TestAssetHelper.waitingTime) mDevice.waitNotNull(Until.findObjects(By.text("Open")), TestAssetHelper.waitingTime)
onView(withId(R.id.download_dialog_title)) onView(withId(R.id.download_dialog_title))
.check(matches(withText(CoreMatchers.containsString("Download completed")))) .check(matches(withText(CoreMatchers.containsString("Download completed"))))
onView(withId(R.id.download_dialog_filename))
.check(matches(ViewMatchers.isCompletelyDisplayed()))
} }
private fun closePromptButton() = private fun closePromptButton() =

@ -9,7 +9,9 @@ package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers 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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
@ -19,6 +21,7 @@ import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertTrue
import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.not
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.TestHelper.packageName
@ -39,6 +42,8 @@ class EnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionDetailsStatus(status: String) = fun verifyEnhancedTrackingProtectionDetailsStatus(status: String) =
assertEnhancedTrackingProtectionDetailsStatus(status) assertEnhancedTrackingProtectionDetailsStatus(status)
fun verifyETPSwitchVisibility(visible: Boolean) = assertETPSwitchVisibility(visible)
fun verifyTrackingCookiesBlocked() = assertTrackingCookiesBlocked() fun verifyTrackingCookiesBlocked() = assertTrackingCookiesBlocked()
fun verifyFingerprintersBlocked() = assertFingerprintersBlocked() fun verifyFingerprintersBlocked() = assertFingerprintersBlocked()
@ -69,6 +74,7 @@ class EnhancedTrackingProtectionRobot {
fun openEnhancedTrackingProtectionSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition { fun openEnhancedTrackingProtectionSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
openEnhancedTrackingProtectionSheet().waitForExists(waitingTime) openEnhancedTrackingProtectionSheet().waitForExists(waitingTime)
openEnhancedTrackingProtectionSheet().click() openEnhancedTrackingProtectionSheet().click()
assertSecuritySheetIsCompletelyDisplayed()
EnhancedTrackingProtectionRobot().interact() EnhancedTrackingProtectionRobot().interact()
return Transition() return Transition()
@ -83,7 +89,7 @@ class EnhancedTrackingProtectionRobot {
} }
fun disableEnhancedTrackingProtectionFromSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition { fun disableEnhancedTrackingProtectionFromSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
disableEnhancedTrackingProtection().click() enhancedTrackingProtectionSwitch().click()
EnhancedTrackingProtectionRobot().interact() EnhancedTrackingProtectionRobot().interact()
return Transition() return Transition()
@ -113,6 +119,16 @@ fun enhancedTrackingProtection(interact: EnhancedTrackingProtectionRobot.() -> U
return EnhancedTrackingProtectionRobot.Transition() 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) { private fun assertEnhancedTrackingProtectionSheetStatus(status: String, state: Boolean) {
mDevice.waitNotNull(Until.findObjects(By.textContains(status))) mDevice.waitNotNull(Until.findObjects(By.textContains(status)))
onView(ViewMatchers.withResourceName("switch_widget")).check( onView(ViewMatchers.withResourceName("switch_widget")).check(
@ -131,7 +147,7 @@ private fun assertEnhancedTrackingProtectionDetailsStatus(status: String) {
private fun openEnhancedTrackingProtectionSheet() = private fun openEnhancedTrackingProtectionSheet() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_security_indicator")) mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_security_indicator"))
private fun disableEnhancedTrackingProtection() = private fun enhancedTrackingProtectionSwitch() =
onView(ViewMatchers.withResourceName("switch_widget")) onView(ViewMatchers.withResourceName("switch_widget"))
private fun trackingProtectionSettingsButton() = private fun trackingProtectionSettingsButton() =
@ -169,3 +185,10 @@ private fun assertTrackingContentBlocked() {
} }
private fun trackingContentBlockListButton() = onView(withId(R.id.tracking_content)) 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 android.widget.EditText
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click 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.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant 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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withHint 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.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.text
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiScrollable
@ -116,6 +116,11 @@ class HomeScreenRobot {
fun verifyExistingTopSitesTabs(title: String) = assertExistingTopSitesTabs(title) fun verifyExistingTopSitesTabs(title: String) = assertExistingTopSitesTabs(title)
fun verifyTopSiteContextMenuItems() = assertTopSiteContextMenuItems() fun verifyTopSiteContextMenuItems() = assertTopSiteContextMenuItems()
fun verifyJumpBackInSectionIsDisplayed() = assertJumpBackInSectionIsDisplayed()
fun verifyJumpBackInSectionIsNotDisplayed() = assertJumpBackInSectionIsNotDisplayed()
fun verifyRecentBookmarksSectionIsDisplayed() = assertRecentBookmarksSectionIsDisplayed()
fun verifyRecentBookmarksSectionIsNotDisplayed() = assertRecentBookmarksSectionIsNotDisplayed()
// Collections elements // Collections elements
fun verifyCollectionIsDisplayed(title: String, collectionExists: Boolean = true) { fun verifyCollectionIsDisplayed(title: String, collectionExists: Boolean = true) {
if (collectionExists) { if (collectionExists) {
@ -129,8 +134,6 @@ class HomeScreenRobot {
fun verifyCollectionIcon() = onView(withId(R.id.collection_icon)).check(matches(isDisplayed())) fun verifyCollectionIcon() = onView(withId(R.id.collection_icon)).check(matches(isDisplayed()))
fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun togglePrivateBrowsingModeOnOff() { fun togglePrivateBrowsingModeOnOff() {
onView(ViewMatchers.withResourceName("privateBrowsingButton")) onView(ViewMatchers.withResourceName("privateBrowsingButton"))
.perform(click()) .perform(click())
@ -146,10 +149,13 @@ class HomeScreenRobot {
mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime) mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime)
} }
fun snackBarButtonClick(expectedText: String) { fun clickUndoCollectionDeletion(expectedText: String) {
onView(allOf(withId(R.id.snackbar_btn), withText(expectedText))).check( onView(
matches(withEffectiveVisibility(Visibility.VISIBLE)) allOf(
).perform(click()) withId(R.id.snackbar_btn),
withText(expectedText)
)
).click()
} }
class Transition { class Transition {
@ -187,8 +193,7 @@ class HomeScreenRobot {
} }
fun openSearch(interact: SearchRobot.() -> Unit): SearchRobot.Transition { fun openSearch(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar")) navigationToolbar().waitForExists(waitingTime)
.waitForExists(waitingTime)
navigationToolbar().click() navigationToolbar().click()
SearchRobot().interact() SearchRobot().interact()
@ -317,13 +322,13 @@ class HomeScreenRobot {
} }
fun expandCollection(title: String, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition { fun expandCollection(title: String, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
try { // Depending on the screen dimensions collections might report as visible on screen
mDevice.waitNotNull(findObject(text(title)), waitingTime) // but actually have the bottom toolbar above so interactions with collections might fail.
collectionTitle(title).click() // As a quick solution we'll try scrolling to the element below collection on the homescreen
} catch (e: NoMatchingViewException) { // so that they are displayed above in their entirety.
scrollToElementByText(title) scrollToElementByText(appContext.getString(R.string.pocket_stories_header_1))
collectionTitle(title).click()
} collectionTitle(title).click()
CollectionRobot().interact() CollectionRobot().interact()
return CollectionRobot.Transition() return CollectionRobot.Transition()
@ -578,12 +583,15 @@ private fun assertTopSiteContextMenuItems() {
) )
} }
private fun assertShareTabsOverlay() { private fun assertJumpBackInSectionIsDisplayed() = jumpBackInSection().check(matches(isDisplayed()))
onView(withId(R.id.shared_site_list)).check(matches(isDisplayed()))
onView(withId(R.id.share_tab_title)).check(matches(isDisplayed())) private fun assertJumpBackInSectionIsNotDisplayed() = jumpBackInSection().check(doesNotExist())
onView(withId(R.id.share_tab_favicon)).check(matches(isDisplayed()))
onView(withId(R.id.share_tab_url)).check(matches(isDisplayed())) private fun assertRecentBookmarksSectionIsDisplayed() =
} recentBookmarksSection().check(matches(isDisplayed()))
private fun assertRecentBookmarksSectionIsNotDisplayed() =
recentBookmarksSection().check(doesNotExist())
private fun privateBrowsingButton() = onView(withId(R.id.privateBrowsingButton)) 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 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 { private fun startBrowsingButton(): UiObject {
val startBrowsingButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/finish_button")) val startBrowsingButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/finish_button"))
homeScreenList() homeScreenList()

@ -7,6 +7,7 @@
package org.mozilla.fenix.ui.robots package org.mozilla.fenix.ui.robots
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingRegistry
@ -150,25 +151,6 @@ class NavigationToolbarRobot {
return TabDrawerRobot.Transition() 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 { fun visitLinkFromClipboard(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull( mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/mozac_browser_toolbar_clear_view")), Until.findObject(By.res("org.mozilla.fenix.debug:id/mozac_browser_toolbar_clear_view")),
@ -181,10 +163,15 @@ class NavigationToolbarRobot {
waitingTime waitingTime
) )
mDevice.waitNotNull( // On Android 12 or above we don't SHOW the URL unless the user requests to do so.
Until.findObject(By.res("org.mozilla.fenix.debug:id/clipboard_url")), // See for mor information https://github.com/mozilla-mobile/fenix/issues/22271
waitingTime 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() fillLinkButton().click()
BrowserRobot().interact() BrowserRobot().interact()

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

@ -11,6 +11,7 @@ import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.ComposeTestRule 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.onAllNodesWithText
import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithTag
@ -56,7 +57,7 @@ class SearchRobot {
fun verifySearchView() = assertSearchView() fun verifySearchView() = assertSearchView()
fun verifyBrowserToolbar() = assertBrowserToolbarEditView() fun verifyBrowserToolbar() = assertBrowserToolbarEditView()
fun verifyScanButton() = assertScanButton() fun verifyScanButton() = assertScanButton()
fun verifySearchEngineButton() = assertSearchEngineButton() fun verifySearchEngineButton() = assertSearchButton()
fun verifySearchWithText() = assertSearchWithText() fun verifySearchWithText() = assertSearchWithText()
fun verifySearchEngineResults(rule: ComposeTestRule, searchEngineName: String, count: Int) = fun verifySearchEngineResults(rule: ComposeTestRule, searchEngineName: String, count: Int) =
assertSearchEngineResults(rule, searchEngineName, count) assertSearchEngineResults(rule, searchEngineName, count)
@ -103,7 +104,12 @@ class SearchRobot {
} }
fun typeSearch(searchTerm: String) { fun typeSearch(searchTerm: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view")
).waitForExists(waitingTime)
browserToolbarEditView().setText(searchTerm) browserToolbarEditView().setText(searchTerm)
mDevice.waitForIdle() mDevice.waitForIdle()
} }
@ -208,7 +214,7 @@ class SearchRobot {
fun submitQuery(query: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun submitQuery(query: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
sessionLoadedIdlingResource = SessionLoadedIdlingResource() sessionLoadedIdlingResource = SessionLoadedIdlingResource()
mDevice.waitForIdle() searchWrapper().waitForExists(waitingTime)
browserToolbarEditView().setText(query) browserToolbarEditView().setText(query)
mDevice.pressEnter() mDevice.pressEnter()
@ -317,7 +323,7 @@ private fun assertScanButton() =
).waitForExists(waitingTime) ).waitForExists(waitingTime)
) )
private fun assertSearchEngineButton() = private fun assertSearchButton() =
assertTrue( assertTrue(
mDevice.findObject( mDevice.findObject(
UiSelector().resourceId("$packageName:id/search_engines_shortcut_button") UiSelector().resourceId("$packageName:id/search_engines_shortcut_button")
@ -346,16 +352,13 @@ fun searchScreen(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
return SearchRobot.Transition() return SearchRobot.Transition()
} }
private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean) = { private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean): () -> Unit = {
mDevice.waitNotNull( searchWrapper().waitForExists(waitingTime)
Until.findObject(
By.text("Search Engine")
),
waitingTime
)
assertEquals( assertEquals(
"Keyboard not shown",
isExpectedToBeVisible, isExpectedToBeVisible,
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice
.executeShellCommand("dumpsys input_method | grep mInputShown") .executeShellCommand("dumpsys input_method | grep mInputShown")
.contains("mInputShown=true") .contains("mInputShown=true")
) )
@ -383,25 +386,35 @@ private fun ComposeTestRule.assertSearchEngineList() {
onNodeWithText("Wikipedia") onNodeWithText("Wikipedia")
.assertExists() .assertExists()
.assertIsDisplayed() .assertIsDisplayed()
onNodeWithText("eBay")
.assertExists()
.assertIsDisplayed()
} }
@OptIn(ExperimentalTestApi::class) @OptIn(ExperimentalTestApi::class)
private fun assertEngineListShortcutContains(rule: ComposeTestRule, searchEngineName: String) { private fun assertEngineListShortcutContains(rule: ComposeTestRule, searchEngineName: String) {
rule.waitForIdle() try {
rule.waitForIdle()
mDevice.waitForObjects( } catch (e: ComposeNotIdleException) {
mDevice.pressBack()
navigationToolbar {
}.clickUrlbar {
clickSearchEngineShortcutButton()
}
} finally {
mDevice.findObject( mDevice.findObject(
UiSelector().textContains("Google") UiSelector().textContains("Google")
) ).waitForExists(waitingTime)
)
rule.onNodeWithTag("mozac.awesomebar.suggestions") rule.onNodeWithTag("mozac.awesomebar.suggestions")
.performScrollToIndex(5) .performScrollToIndex(5)
rule.onNodeWithText(searchEngineName) rule.onNodeWithText(searchEngineName)
.assertExists() .assertExists()
.assertIsDisplayed() .assertIsDisplayed()
.assertHasClickAction() .assertHasClickAction()
}
} }
private fun ComposeTestRule.selectDefaultSearchEngine(searchEngine: String) { private fun ComposeTestRule.selectDefaultSearchEngine(searchEngine: String) {
@ -434,5 +447,3 @@ private fun assertPastedToolbarText(expectedText: String) {
) )
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) ).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
import androidx.test.uiautomator.By.textContains import androidx.test.uiautomator.By.textContains
import androidx.test.uiautomator.UiDevice 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 androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers import org.hamcrest.CoreMatchers
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_PLAY_SERVICES import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_PLAY_SERVICES
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.appName import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled 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.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.assertIsEnabled import org.mozilla.fenix.helpers.assertIsEnabled
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
@ -45,15 +50,17 @@ import org.mozilla.fenix.ui.robots.SettingsRobot.Companion.DEFAULT_APPS_SETTINGS
class SettingsRobot { class SettingsRobot {
// BASICS SECTION // BASICS SECTION
fun verifyBasicsHeading() = assertGeneralHeading() fun verifyGeneralHeading() = assertGeneralHeading()
fun verifySearchEngineButton() = assertSearchEngineButton() fun verifySearchButton() = assertSearchButton()
fun verifyThemeButton() = assertCustomizeButton() fun verifyCustomizeButton() = assertCustomizeButton()
fun verifyThemeSelected() = assertThemeSelected() fun verifyThemeSelected() = assertThemeSelected()
fun verifyAccessibilityButton() = assertAccessibilityButton() fun verifyAccessibilityButton() = assertAccessibilityButton()
fun verifySetAsDefaultBrowserButton() = assertSetAsDefaultBrowserButton() fun verifySetAsDefaultBrowserButton() = assertSetAsDefaultBrowserButton()
fun verifyDefaultBrowserItem() = assertDefaultBrowserItem() fun verifyTabsButton() = assertTabsButton()
fun verifyTabsItem() = assertTabsItem() fun verifyHomepageButton() = assertHomepageButton()
fun verifyCreditCardsButton() = assertCreditCardsButton()
fun verifyLanguageButton() = assertLanguageButton()
fun verifyDefaultBrowserIsDisaled() = assertDefaultBrowserIsDisabled() fun verifyDefaultBrowserIsDisaled() = assertDefaultBrowserIsDisabled()
fun clickDefaultBrowserSwitch() = toggleDefaultBrowserSwitch() fun clickDefaultBrowserSwitch() = toggleDefaultBrowserSwitch()
fun verifyAndroidDefaultAppsMenuAppears() = assertAndroidDefaultAppsMenuAppears() fun verifyAndroidDefaultAppsMenuAppears() = assertAndroidDefaultAppsMenuAppears()
@ -62,7 +69,7 @@ class SettingsRobot {
fun verifyPrivacyHeading() = assertPrivacyHeading() fun verifyPrivacyHeading() = assertPrivacyHeading()
fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton() fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton()
fun verifyLoginsButton() = assertLoginsButton() fun verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
fun verifyEnhancedTrackingProtectionValue(state: String) = fun verifyEnhancedTrackingProtectionValue(state: String) =
assertEnhancedTrackingProtectionValue(state) assertEnhancedTrackingProtectionValue(state)
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton() fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
@ -76,6 +83,7 @@ class SettingsRobot {
fun verifyOpenLinksInAppsButton() = assertOpenLinksInAppsButton() fun verifyOpenLinksInAppsButton() = assertOpenLinksInAppsButton()
fun verifyOpenLinksInAppsSwitchDefault() = assertOpenLinksInAppsValue() fun verifyOpenLinksInAppsSwitchDefault() = assertOpenLinksInAppsValue()
fun verifySettingsView() = assertSettingsView() fun verifySettingsView() = assertSettingsView()
fun verifySettingsToolbar() = assertSettingsToolbar()
// ADVANCED SECTION // ADVANCED SECTION
fun verifyAdvancedHeading() = assertAdvancedHeading() fun verifyAdvancedHeading() = assertAdvancedHeading()
@ -88,8 +96,8 @@ class SettingsRobot {
// ABOUT SECTION // ABOUT SECTION
fun verifyAboutHeading() = assertAboutHeading() fun verifyAboutHeading() = assertAboutHeading()
fun verifyRateOnGooglePlay() = assertRateOnGooglePlay() fun verifyRateOnGooglePlay() = assertTrue(rateOnGooglePlayHeading().waitForExists(waitingTime))
fun verifyAboutFirefoxPreview() = assertAboutFirefoxPreview() fun verifyAboutFirefoxPreview() = assertTrue(aboutFirefoxHeading().waitForExists(waitingTime))
fun verifyGooglePlayRedirect() = assertGooglePlayRedirect() fun verifyGooglePlayRedirect() = assertGooglePlayRedirect()
class Transition { class Transition {
@ -113,7 +121,7 @@ class SettingsRobot {
fun openAboutFirefoxPreview(interact: SettingsSubMenuAboutRobot.() -> Unit): fun openAboutFirefoxPreview(interact: SettingsSubMenuAboutRobot.() -> Unit):
SettingsSubMenuAboutRobot.Transition { SettingsSubMenuAboutRobot.Transition {
assertAboutFirefoxPreview().click() aboutFirefoxHeading().click()
SettingsSubMenuAboutRobot().interact() SettingsSubMenuAboutRobot().interact()
return SettingsSubMenuAboutRobot.Transition() return SettingsSubMenuAboutRobot.Transition()
@ -147,6 +155,15 @@ class SettingsRobot {
return SettingsSubMenuTabsRobot.Transition() 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 { fun openAccessibilitySubMenu(interact: SettingsSubMenuAccessibilityRobot.() -> Unit): SettingsSubMenuAccessibilityRobot.Transition {
scrollToElementByText("Accessibility") scrollToElementByText("Accessibility")
@ -286,18 +303,37 @@ private fun assertSettingsView() {
} }
// GENERAL SECTION // 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() { private fun assertGeneralHeading() {
scrollToElementByText("General") scrollToElementByText("General")
onView(withText("General")) onView(withText("General"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
private fun assertSearchEngineButton() { private fun assertSearchButton() {
mDevice.wait(Until.findObject(By.text("Search")), waitingTime) mDevice.wait(Until.findObject(By.text("Search")), waitingTime)
onView(withText("Search")) onView(withText(R.string.preferences_search))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .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")) private fun assertCustomizeButton() = onView(withText("Customize"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
@ -328,15 +364,9 @@ private fun assertAndroidDefaultAppsMenuAppears() {
intended(IntentMatchers.hasAction(DEFAULT_APPS_SETTINGS_ACTION)) intended(IntentMatchers.hasAction(DEFAULT_APPS_SETTINGS_ACTION))
} }
private fun assertDefaultBrowserItem() { private fun assertTabsButton() {
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() {
mDevice.wait(Until.findObject(By.text("Tabs")), waitingTime) mDevice.wait(Until.findObject(By.text("Tabs")), waitingTime)
onView(withText("Tabs")) onView(withText(R.string.preferences_tabs))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
@ -361,9 +391,9 @@ private fun assertEnhancedTrackingProtectionValue(state: String) {
onView(withText(state)).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) onView(withText(state)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
private fun assertLoginsButton() { private fun assertLoginsAndPasswordsButton() {
scrollToElementByText("Logins and passwords") scrollToElementByText("Logins and passwords")
onView(withText("Logins and passwords")) onView(withText(R.string.preferences_passwords_logins_and_passwords))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
@ -470,24 +500,32 @@ private fun assertAboutHeading(): ViewInteraction {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
private fun assertRateOnGooglePlay(): ViewInteraction { private fun rateOnGooglePlayHeading(): UiObject {
onView(withId(R.id.recycler_view)) val rateOnGooglePlay = mDevice.findObject(UiSelector().text("Rate on Google Play"))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(hasDescendant(withText("Rate on Google Play")))) scrollToElementByText("Rate on Google Play")
return onView(withText("Rate on Google Play")) if (!rateOnGooglePlay.exists()) {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) settingsList().swipeUp(2)
rateOnGooglePlay.waitForExists(waitingTime)
}
return rateOnGooglePlay
} }
private fun assertAboutFirefoxPreview(): ViewInteraction { private fun aboutFirefoxHeading(): UiObject {
onView(withId(R.id.recycler_view)) val aboutFirefoxHeading = mDevice.findObject(UiSelector().text("About $appName"))
.perform(RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(hasDescendant(withText("About $appName")))) scrollToElementByText("About $appName")
return onView(withText("About $appName")) if (!aboutFirefoxHeading.exists()) {
.check(matches(isDisplayed())) settingsList().swipeUp(2)
aboutFirefoxHeading.waitForExists(waitingTime)
}
return aboutFirefoxHeading
} }
fun swipeToBottom() = onView(withId(R.id.recycler_view)).perform(ViewActions.swipeUp()) fun swipeToBottom() = onView(withId(R.id.recycler_view)).perform(ViewActions.swipeUp())
fun clickRateButtonGooglePlay() { fun clickRateButtonGooglePlay() {
assertRateOnGooglePlay().click() rateOnGooglePlayHeading().click()
} }
private fun assertGooglePlayRedirect() { private fun assertGooglePlayRedirect() {
@ -502,3 +540,6 @@ private fun addonsManagerButton() = onView(withText(R.string.preferences_addons)
private fun goBackButton() = private fun goBackButton() =
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up"))) 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) { private fun navigateBackToAboutPage(itemToInteract: () -> Unit) {
browserScreen { navigationToolbar {
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
}.openAboutFirefoxPreview { }.openAboutFirefoxPreview {
@ -161,8 +156,7 @@ private fun assertSupport() {
} }
private fun assertCrashes() { private fun assertCrashes() {
navigationToolbar {
browserScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
}.openAboutFirefoxPreview { }.openAboutFirefoxPreview {

@ -6,16 +6,19 @@ package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches 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.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import junit.framework.TestCase.assertTrue
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.R 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. * Implementation of Robot Pattern for the settings Enhanced Tracking Protection Exceptions sub menu.
@ -24,11 +27,11 @@ class SettingsSubMenuEnhancedTrackingProtectionExceptionsRobot {
fun verifyNavigationToolBarHeader() = assertNavigationToolBarHeader() 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() { fun verifyEnhancedTrackingProtectionProtectionExceptionsSubMenuItems() {
verifyDefault() verifyDefault()
@ -63,13 +66,25 @@ private fun assertNavigationToolBarHeader() {
} }
private fun assertExceptionDefault() = 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() = 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) = private fun assertExceptionURL(url: String) =
onView(allOf(withText(url))) assertTrue(
mDevice.findObject(
UiSelector().textContains(url.replace("http://", "https://"))
).waitForExists(waitingTime)
)
private fun disableExceptionsButton() = 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.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.helpers.TestHelper.appName import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.assertIsChecked import org.mozilla.fenix.helpers.assertIsChecked
@ -48,7 +47,7 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionTextWithSwitchWidget() = assertEnhancedTrackingProtectionTextWithSwitchWidget() fun verifyEnhancedTrackingProtectionTextWithSwitchWidget() = assertEnhancedTrackingProtectionTextWithSwitchWidget()
fun verifyEnhancedTrackingProtectionOptions() = assertEnhancedTrackingProtectionOptions() fun verifyEnhancedTrackingProtectionOptionsEnabled(enabled: Boolean = true) = assertEnhancedTrackingProtectionOptionsState(enabled)
fun verifyTrackingProtectionSwitchEnabled() = assertTrackingProtectionSwitchEnabled() fun verifyTrackingProtectionSwitchEnabled() = assertTrackingProtectionSwitchEnabled()
@ -63,7 +62,7 @@ class SettingsSubMenuEnhancedTrackingProtectionRobot {
verifyEnhancedTrackingProtectionTextWithSwitchWidget() verifyEnhancedTrackingProtectionTextWithSwitchWidget()
verifyTrackingProtectionSwitchEnabled() verifyTrackingProtectionSwitchEnabled()
verifyRadioButtonDefaults() verifyRadioButtonDefaults()
verifyEnhancedTrackingProtectionOptions() verifyEnhancedTrackingProtectionOptionsEnabled()
} }
fun verifyCustomTrackingProtectionSettings() = assertCustomTrackingProtectionSettings() fun verifyCustomTrackingProtectionSettings() = assertCustomTrackingProtectionSettings()
@ -153,48 +152,24 @@ private fun assertEnhancedTrackingProtectionTextWithSwitchWidget() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
private fun assertEnhancedTrackingProtectionOptions() { private fun assertEnhancedTrackingProtectionOptionsState(enabled: Boolean) {
onView(withText("Standard (default)")) onView(withText("Standard (default)"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(isEnabled(enabled)))
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))))
onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_standard_description_4)) 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")) 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)) 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")) onView(withText("Custom"))
.check(matches(not(isEnabled(true)))) .check(matches(isEnabled(enabled)))
val customText = onView(withText(org.mozilla.fenix.R.string.preference_enhanced_tracking_protection_custom_description_2))
"Choose which trackers and scripts to block." .check(matches(isEnabled(enabled)))
onView(withText(customText))
.check(matches(not(isEnabled(true))))
} }
private fun assertTrackingProtectionSwitchEnabled() { private fun assertTrackingProtectionSwitchEnabled() {
@ -248,7 +223,7 @@ private fun assertCustomTrackingProtectionSettings() {
private fun cookiesCheckbox() = onView(withText("Cookies")) 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")) 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.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView 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.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers 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.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers import org.hamcrest.CoreMatchers
@ -30,12 +29,14 @@ import org.hamcrest.Matchers.allOf
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime 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.click
/** /**
* Implementation of Robot Pattern for the settings search sub menu. * Implementation of Robot Pattern for the settings search sub menu.
*/ */
class SettingsSubMenuSearchRobot { class SettingsSubMenuSearchRobot {
fun verifySearchToolbar() = assertSearchToolbar()
fun verifyDefaultSearchEngineHeader() = assertDefaultSearchEngineHeader() fun verifyDefaultSearchEngineHeader() = assertDefaultSearchEngineHeader()
fun verifySearchEngineList() = assertSearchEngineList() fun verifySearchEngineList() = assertSearchEngineList()
fun verifyShowSearchSuggestions() = assertShowSearchSuggestions() fun verifyShowSearchSuggestions() = assertShowSearchSuggestions()
@ -73,12 +74,56 @@ class SettingsSubMenuSearchRobot {
fun selectAddCustomSearchEngine() = onView(withText("Other")).click() fun selectAddCustomSearchEngine() = onView(withText("Other")).click()
fun typeCustomEngineDetails(engineName: String, engineURL: String) { fun typeCustomEngineDetails(engineName: String, engineURL: String) {
onView(withId(R.id.edit_engine_name)) mDevice.findObject(By.res("$packageName:id/edit_engine_name")).clear()
.perform(clearText()) mDevice.findObject(By.res("$packageName:id/edit_engine_name")).setText(engineName)
.perform(typeText(engineName)) mDevice.findObject(By.res("$packageName:id/edit_search_string")).clear()
onView(withId(R.id.edit_search_string)) mDevice.findObject(By.res("$packageName:id/edit_search_string")).setText(engineURL)
.perform(clearText())
.perform(typeText(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) { 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() = private fun assertDefaultSearchEngineHeader() =
onView(withText("Default search engine")) onView(withText("Default search engine"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .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.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.click
/** /**
* Implementation of Robot Pattern for the settings Tabs sub menu. * Implementation of Robot Pattern for the settings Tabs sub menu.
@ -28,12 +27,7 @@ class SettingsSubMenuTabsRobot {
fun verifyCloseTabsOptions() = assertCloseTabsOptions() fun verifyCloseTabsOptions() = assertCloseTabsOptions()
fun verifyStartOnHomeOptions() = assertStartOnHomeOptions() fun verifyMoveOldTabsToInactiveOptions() = assertMoveOldTabsToInactiveOptions()
fun clickAlwaysStartOnHomeToggle() {
scrollToElementByText("Move old tabs to inactive")
alwaysStartOnHomeToggle().click()
}
class Transition { class Transition {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@ -57,11 +51,15 @@ private fun assertTabViewOptions() {
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
searchTermTabGroupsToggle() searchTermTabGroupsToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
searchGroupsDescription()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
} }
private fun assertCloseTabsOptions() { private fun assertCloseTabsOptions() {
closeTabsHeading() closeTabsHeading()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
neverToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
afterOneDayToggle() afterOneDayToggle()
.check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
afterOneWeekToggle() afterOneWeekToggle()
@ -70,14 +68,10 @@ private fun assertCloseTabsOptions() {
.check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
} }
private fun assertStartOnHomeOptions() { private fun assertMoveOldTabsToInactiveOptions() {
// Scroll to ensure all the items are visible. moveOldTabsToInactiveHeading()
scrollToElementByText("Never")
startOnHomeHeading()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
afterFourHoursToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
alwaysStartOnHomeToggle() moveOldTabsToInactiveToggle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .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 searchTermTabGroupsToggle() = onView(withText("Search groups"))
private fun searchGroupsDescription() = onView(withText("Group related sites together"))
private fun closeTabsHeading() = onView(withText("Close tabs")) private fun closeTabsHeading() = onView(withText("Close tabs"))
private fun manuallyToggle() = onView(withText("Manually")) private fun manuallyToggle() = onView(withText("Manually"))
private fun neverToggle() = onView(withText("Never"))
private fun afterOneDayToggle() = onView(withText("After one day")) private fun afterOneDayToggle() = onView(withText("After one day"))
private fun afterOneWeekToggle() = onView(withText("After one week")) private fun afterOneWeekToggle() = onView(withText("After one week"))
private fun afterOneMonthToggle() = onView(withText("After one month")) private fun afterOneMonthToggle() = onView(withText("After one month"))
private fun startOnHomeHeading() = onView(withText("Start on home")) private fun moveOldTabsToInactiveHeading() = onView(withText("Move old tabs to inactive"))
private fun afterFourHoursToggle() = onView(withText("After four hours"))
private fun alwaysStartOnHomeToggle() = onView(withText("Always"))
private fun neverStartOnHomeToggle() = onView(withText("Never")) private fun moveOldTabsToInactiveToggle() =
onView(withText(R.string.preferences_inactive_tabs_title))
private fun goBackButton() = private fun goBackButton() =
onView(allOf(ViewMatchers.withContentDescription("Navigate up"))) 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.content.Context
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.GeneralLocation 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.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches 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
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId 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
import androidx.test.uiautomator.Until.findObject import androidx.test.uiautomator.Until.findObject
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import junit.framework.AssertionFailedError
import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertTrue
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anyOf import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime 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.TestHelper.packageName
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.clickAtLocationInView import org.mozilla.fenix.helpers.clickAtLocationInView
import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.idlingresource.BottomSheetBehaviorStateIdlingResource 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.BottomSheetBehaviorHalfExpandedMaxRatioMatcher
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorStateMatcher import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorStateMatcher
@ -69,17 +68,27 @@ class TabDrawerRobot {
} }
fun verifyNormalBrowsingButtonIsDisplayed() = assertNormalBrowsingButton() 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 verifyExistingOpenTabs(title: String) = assertExistingOpenTabs(title)
fun verifyCloseTabsButton(title: String) = assertCloseTabsButton(title) fun verifyCloseTabsButton(title: String) = assertCloseTabsButton(title)
fun verifyExistingTabList() = assertExistingTabList() fun verifyExistingTabList() = assertExistingTabList()
fun verifyNoTabsOpened() = assertNoTabsOpenedText() fun verifyNoOpenTabsInNormalBrowsing() = assertNoOpenTabsInNormalBrowsing()
fun verifyNoOpenTabsInPrivateBrowsing() = assertNoOpenTabsInPrivateBrowsing()
fun verifyPrivateModeSelected() = assertPrivateModeSelected() fun verifyPrivateModeSelected() = assertPrivateModeSelected()
fun verifyNormalModeSelected() = assertNormalModeSelected() fun verifyNormalModeSelected() = assertNormalModeSelected()
fun verifyNewTabButton() = assertNewTabButton() fun verifyNormalBrowsingNewTabButton() = assertNormalBrowsingNewTabButton()
fun verifyPrivateBrowsingNewTabButton() = assertPrivateBrowsingNewTabButton()
fun verifyEmptyTabsTrayMenuButtons() = assertEmptyTabsTrayMenuButtons()
fun verifySelectTabsButton() = assertSelectTabsButton() fun verifySelectTabsButton() = assertSelectTabsButton()
fun verifyTabTrayOverflowMenu(visibility: Boolean) = assertTabTrayOverflowButton(visibility) fun verifyTabTrayOverflowMenu(visibility: Boolean) = assertTabTrayOverflowButton(visibility)
fun verifyTabsTrayCounter() = assertTabsTrayCounter()
fun verifyTabTrayIsOpened() = assertTabTrayDoesExist() fun verifyTabTrayIsOpened() = assertTabTrayDoesExist()
fun verifyTabTrayIsClosed() = assertTabTrayDoesNotExist() fun verifyTabTrayIsClosed() = assertTabTrayDoesNotExist()
@ -104,7 +113,12 @@ class TabDrawerRobot {
fun swipeTabRight(title: String) { fun swipeTabRight(title: String) {
var retries = 0 // number of retries before failing, will stop at 2 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()) tab(title).perform(ViewActions.swipeRight())
retries++ retries++
} }
@ -112,57 +126,105 @@ class TabDrawerRobot {
fun swipeTabLeft(title: String) { fun swipeTabLeft(title: String) {
var retries = 0 // number of retries before failing, will stop at 2 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()) tab(title).perform(ViewActions.swipeLeft())
retries++ 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) { fun verifySnackBarText(expectedText: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) assertTrue(
mDevice.waitNotNull(findObject(By.text(expectedText)), TestAssetHelper.waitingTime) mDevice.findObject(
UiSelector().text(expectedText)
).waitForExists(waitingTime)
)
} }
fun snackBarButtonClick(expectedText: String) { fun snackBarButtonClick(expectedText: String) {
mDevice.findObject( val snackBarButton =
UiSelector().resourceId("$packageName:id/snackbar_btn") mDevice.findObject(
).waitForExists(waitingTime) UiSelector()
onView(allOf(withId(R.id.snackbar_btn), withText(expectedText))).check( .resourceId("$packageName:id/snackbar_btn")
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)) .text(expectedText)
).perform(click()) )
snackBarButton.waitForExists(waitingTime)
snackBarButton.click()
} }
fun verifyTabMediaControlButtonState(action: String) { fun verifyTabMediaControlButtonState(action: String) {
mDevice.waitForIdle() try {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_empty_view")
).waitUntilGone(waitingTime)
mDevice.findObject( mDevice.findObject(
UiSelector() UiSelector().resourceId("$packageName:id/tab_tray_grid_item")
.resourceId("$packageName:id/play_pause_button") ).waitForExists(waitingTime)
).waitForExists(waitingTime)
assertTrue(
mDevice.findObject( mDevice.findObject(
UiSelector().descriptionContains(action) UiSelector()
.resourceId("$packageName:id/play_pause_button")
.descriptionContains(action)
).waitForExists(waitingTime) ).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() { fun clickSelectTabs() {
threeDotMenu().click() threeDotMenu().click()
@ -280,7 +342,11 @@ class TabDrawerRobot {
fun openTab(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun openTab(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(findObject(text(title))) mDevice.waitNotNull(findObject(text(title)))
tab(title).click() mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_title")
.textContains(title)
).click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -374,17 +440,19 @@ fun tabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
return 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) = private fun assertCloseTabsButton(title: String) =
onView( assertTrue(
allOf( mDevice.findObject(
withId(R.id.mozac_browser_tabstray_close), UiSelector()
withContentDescription("Close tab $title") .resourceId("$packageName:id/mozac_browser_tabstray_close")
) .descriptionContains("Close tab $title")
).waitForExists(waitingTime)
) )
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun normalBrowsingButton() = onView( private fun normalBrowsingButton() = onView(
anyOf( anyOf(
@ -394,6 +462,7 @@ private fun normalBrowsingButton() = onView(
) )
private fun privateBrowsingButton() = onView(withContentDescription("Private tabs")) 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 newTabButton() = mDevice.findObject(UiSelector().resourceId("$packageName:id/new_tab_button"))
private fun threeDotMenu() = onView(withId(R.id.tab_tray_overflow)) private fun threeDotMenu() = onView(withId(R.id.tab_tray_overflow))
@ -406,30 +475,69 @@ private fun assertExistingOpenTabs(title: String) {
) )
.waitForExists(waitingTime) .waitForExists(waitingTime)
tab(title).check(matches(isDisplayed())) assertTrue(
} catch (e: NoMatchingViewException) { mDevice.findObject(
onView(withId(R.id.tabsTray)).perform( UiSelector()
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>( .resourceId("$packageName:id/mozac_browser_tabstray_title")
allOf( .textContains(title)
withId(R.id.mozac_browser_tabstray_title), ).waitForExists(waitingTime)
withText(title) )
) } catch (e: AssertionError) {
) println("The tab wasn't found")
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) 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() = private fun assertExistingTabList() {
onView(allOf(withId(R.id.tab_item))) mDevice.findObject(
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) UiSelector().resourceId("$packageName:id/tabsTray")
).waitForExists(waitingTime)
private fun assertNoTabsOpenedText() = assertTrue(
onView(withId(R.id.tab_tray_empty_view)) mDevice.findObject(
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) UiSelector().resourceId("$packageName:id/tab_item")
).waitForExists(waitingTime)
)
}
private fun assertNewTabButton() = private fun assertNoOpenTabsInNormalBrowsing() =
onView(withId(R.id.new_tab_button)) onView(
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) 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() = private fun assertSelectTabsButton() =
onView(withText("Select tabs")) onView(withText("Select tabs"))
@ -447,6 +555,19 @@ private fun assertTabTrayOverflowButton(visible: Boolean) =
onView(withId(R.id.tab_tray_overflow)) onView(withId(R.id.tab_tray_overflow))
.check(matches(withEffectiveVisibility(visibleOrGone(visible)))) .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() { private fun assertTabTrayDoesExist() {
onView(withId(R.id.tab_wrapper)) onView(withId(R.id.tab_wrapper))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
@ -471,9 +592,24 @@ private fun assertNormalBrowsingButton() {
normalBrowsingButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) 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() { private fun assertTabThumbnail() {
onView(withId(R.id.mozac_browser_tabstray_thumbnail)) assertTrue(
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_tabstray_thumbnail")
).waitForExists(waitingTime)
)
} }
private fun tab(title: String) = 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 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) = private fun visibleOrGone(visibility: Boolean) =
if (visibility) ViewMatchers.Visibility.VISIBLE else ViewMatchers.Visibility.GONE 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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By 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.TestHelper.packageName
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.share.ShareFragment
/** /**
* Implementation of Robot Pattern for the three dot (main) menu. * Implementation of Robot Pattern for the three dot (main) menu.
*/ */
@Suppress("ForbiddenComment") @Suppress("ForbiddenComment")
class ThreeDotMenuMainRobot { class ThreeDotMenuMainRobot {
fun verifyTabSettingsButton() = assertTabSettingsButton()
fun verifyRecentlyClosedTabsButton() = assertRecentlyClosedTabsButton()
fun verifyShareAllTabsButton() = assertShareAllTabsButton() fun verifyShareAllTabsButton() = assertShareAllTabsButton()
fun clickShareAllTabsButton() = shareAllTabsButton().click()
fun verifySettingsButton() = assertSettingsButton() fun verifySettingsButton() = assertSettingsButton()
fun verifyCustomizeHomeButton() = assertCustomizeHomeButton()
fun verifyAddOnsButton() = assertAddOnsButton() fun verifyAddOnsButton() = assertAddOnsButton()
fun verifyHistoryButton() = assertHistoryButton() fun verifyHistoryButton() = assertHistoryButton()
fun verifyBookmarksButton() = assertBookmarksButton() fun verifyBookmarksButton() = assertBookmarksButton()
@ -67,19 +63,11 @@ class ThreeDotMenuMainRobot {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeUp()) 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 verifyShareTabButton() = assertShareTabButton()
fun verifySaveCollection() = assertSaveCollectionButton() fun verifySaveCollection() = assertSaveCollectionButton()
fun verifySelectTabs() = assertSelectTabsButton() fun verifySelectTabs() = assertSelectTabsButton()
fun verifyFindInPageButton() = assertFindInPageButton() fun verifyFindInPageButton() = assertFindInPageButton()
fun verifyShareScrim() = assertShareScrim()
fun verifySendToDeviceTitle() = assertSendToDeviceTitle()
fun verifyShareALinkTitle() = assertShareALinkTitle()
fun verifyWhatsNewButton() = assertWhatsNewButton() fun verifyWhatsNewButton() = assertWhatsNewButton()
fun verifyAddToTopSitesButton() = assertAddToTopSitesButton() fun verifyAddToTopSitesButton() = assertAddToTopSitesButton()
fun verifyAddToMobileHome() = assertAddToMobileHome() fun verifyAddToMobileHome() = assertAddToMobileHome()
@ -87,7 +75,7 @@ class ThreeDotMenuMainRobot {
fun verifyDownloadsButton() = assertDownloadsButton() fun verifyDownloadsButton() = assertDownloadsButton()
fun verifyShareTabsOverlay() = assertShareTabsOverlay() fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun verifySignInToSyncButton() = assertSignInToSyncButton() fun verifySignInToSyncButton() = assertSignInToSyncButton()
fun verifyNewTabButton() = assertNewTabButton() fun verifyNewTabButton() = assertNormalBrowsingNewTabButton()
fun verifyReportSiteIssueButton() = assertReportSiteIssueButton() fun verifyReportSiteIssueButton() = assertReportSiteIssueButton()
fun verifyDesktopSiteModeEnabled(state: Boolean) { fun verifyDesktopSiteModeEnabled(state: Boolean) {
@ -187,13 +175,6 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun sharePage(interact: LibrarySubMenusMultipleSelectionToolbarRobot.() -> Unit): LibrarySubMenusMultipleSelectionToolbarRobot.Transition {
shareButton().click()
LibrarySubMenusMultipleSelectionToolbarRobot().interact()
return LibrarySubMenusMultipleSelectionToolbarRobot.Transition()
}
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
helpButton().click() helpButton().click()
@ -202,6 +183,26 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition() 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 { fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
forwardButton().click() forwardButton().click()
@ -216,6 +217,14 @@ class ThreeDotMenuMainRobot {
return BrowserRobot.Transition() 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 { fun close(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
// Close three dot // Close three dot
mDevice.pressBack() mDevice.pressBack()
@ -345,6 +354,13 @@ class ThreeDotMenuMainRobot {
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun clickShareAllTabsButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
shareAllTabsButton().click()
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
}
} }
} }
private fun threeDotMenuRecyclerView() = private fun threeDotMenuRecyclerView() =
@ -357,6 +373,17 @@ private fun threeDotMenuRecyclerViewExists() {
private fun settingsButton() = mDevice.findObject(UiSelector().text("Settings")) private fun settingsButton() = mDevice.findObject(UiSelector().text("Settings"))
private fun assertSettingsButton() = assertTrue(settingsButton().waitForExists(waitingTime)) 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 addOnsButton() = onView(allOf(withText("Add-ons")))
private fun assertAddOnsButton() { private fun assertAddOnsButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown()) 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 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( private fun whatsNewButton() = onView(
allOf( allOf(
withText("Whats New"), withText("Whats New"),
@ -523,26 +534,6 @@ private fun clickAddonsManagerButton() {
addOnsButton().check(matches(isCompletelyDisplayed())).click() 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() = private fun shareAllTabsButton() =
onView(allOf(withText("Share all tabs"))).inRoot(RootMatchers.isPlatformPopup()) 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> <intent-filter>
<action android:name="android.service.autofill.AutofillService"/> <action android:name="android.service.autofill.AutofillService"/>
</intent-filter> </intent-filter>
<meta-data
android:name="android.autofill"
android:resource="@xml/autofill_configuration" />
</service> </service>
<service android:name=".media.MediaSessionService" <service android:name=".media.MediaSessionService"

@ -149,6 +149,7 @@ class AppRequestInterceptor(
ErrorType.ERROR_NO_INTERNET, ErrorType.ERROR_NO_INTERNET,
ErrorType.ERROR_UNKNOWN_PROTOCOL -> RiskLevel.Low ErrorType.ERROR_UNKNOWN_PROTOCOL -> RiskLevel.Low
ErrorType.ERROR_HTTPS_ONLY,
ErrorType.ERROR_SECURITY_BAD_CERT, ErrorType.ERROR_SECURITY_BAD_CERT,
ErrorType.ERROR_SECURITY_SSL, ErrorType.ERROR_SECURITY_SSL,
ErrorType.ERROR_PORT_BLOCKED -> RiskLevel.Medium 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.Event
import org.mozilla.fenix.components.metrics.MozillaProductDetector import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.perf.MarkersLifecycleCallbacks import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.tabstray.ext.inactiveTabs
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
/** /**
@ -198,7 +197,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService()) visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService())
registerActivityLifecycleCallbacks(visibilityLifecycleCallback) registerActivityLifecycleCallbacks(visibilityLifecycleCallback)
registerActivityLifecycleCallbacks(MarkersLifecycleCallbacks(components.core.engine)) registerActivityLifecycleCallbacks(MarkersActivityLifecycleCallbacks(components.core.engine))
// Storage maintenance disabled, for now, as it was interfering with background migrations. // Storage maintenance disabled, for now, as it was interfering with background migrations.
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context. // See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
@ -642,7 +641,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
tabViewSetting.set(settings.getTabViewPingString()) tabViewSetting.set(settings.getTabViewPingString())
closeTabSetting.set(settings.getTabTimeoutPingString()) closeTabSetting.set(settings.getTabTimeoutPingString())
inactiveTabsCount.set(browserStore.state.inactiveTabs.size.toLong())
val installSourcePackage = if (SDK_INT >= Build.VERSION_CODES.R) { val installSourcePackage = if (SDK_INT >= Build.VERSION_CODES.R) {
packageManager.getInstallSourceInfo(packageName).installingPackageName packageManager.getInstallSourceInfo(packageName).installingPackageName
@ -686,6 +684,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
voiceSearchEnabled.set(settings.shouldShowVoiceSearch) voiceSearchEnabled.set(settings.shouldShowVoiceSearch)
openLinksInAppEnabled.set(settings.openLinksInExternalApp) openLinksInAppEnabled.set(settings.openLinksInExternalApp)
signedInSync.set(settings.signedInFxaAccount) signedInSync.set(settings.signedInFxaAccount)
searchTermGroupsEnabled.set(settings.searchTermTabGroupsAreEnabled)
val syncedItems = SyncEnginesStorage(applicationContext).getStatus().entries.filter { val syncedItems = SyncEnginesStorage(applicationContext).getStatus().entries.filter {
it.value it.value
@ -739,6 +738,14 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
@VisibleForTesting @VisibleForTesting
internal fun reportHomeScreenMetrics(settings: Settings) { 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 { components.analytics.experiments.register(object : NimbusInterface.Observer {
override fun onUpdatesApplied(updated: List<EnrolledExperiment>) { override fun onUpdatesApplied(updated: List<EnrolledExperiment>) {
CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature) CustomizeHome.jumpBackIn.set(settings.showRecentTabsFeature)

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

@ -20,7 +20,7 @@ import org.mozilla.fenix.components.getType
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings 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.perf.StartupTimeline
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor
@ -47,7 +47,7 @@ class IntentReceiverActivity : Activity() {
processIntent(intent) processIntent(intent)
components.core.engine.profiler?.addMarker( 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. StartupTimeline.onActivityCreateEndIntentReceiver() // DO NOT MOVE ANYTHING BELOW HERE.
} }

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

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

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

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

@ -12,14 +12,12 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.databinding.FragmentCreateCollectionBinding import org.mozilla.fenix.databinding.FragmentCreateCollectionBinding
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
@ExperimentalCoroutinesApi
class CollectionCreationFragment : DialogFragment() { class CollectionCreationFragment : DialogFragment() {
private lateinit var collectionCreationView: CollectionCreationView private lateinit var collectionCreationView: CollectionCreationView
private lateinit var collectionCreationStore: CollectionCreationStore 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.Action
import mozilla.components.lib.state.State import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store import mozilla.components.lib.state.Store
import org.mozilla.fenix.collections.CollectionCreationAction.StepChanged
import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.home.Tab 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 SyncConfig(supportedEngines, PeriodicSyncConfig(periodMinutes = 240)) // four hours
private val creditCardKeyProvider by lazyMonitored { creditCardsStorage.value.crypto } private val creditCardKeyProvider by lazyMonitored { creditCardsStorage.value.crypto }
private val passwordKeyProvider by lazyMonitored { passwordsStorage.value.crypto }
init { init {
// Make the "history", "bookmark", "passwords", "tabs", "credit cards" stores // Make the "history", "bookmark", "passwords", "tabs", "credit cards" stores
// accessible to workers spawned by the sync manager. // accessible to workers spawned by the sync manager.
GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage) GlobalSyncableStoreProvider.configureStore(SyncEngine.History to historyStorage)
GlobalSyncableStoreProvider.configureStore(SyncEngine.Bookmarks to bookmarkStorage) 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(SyncEngine.Tabs to remoteTabsStorage)
GlobalSyncableStoreProvider.configureStore( GlobalSyncableStoreProvider.configureStore(
storePair = SyncEngine.CreditCards to creditCardsStorage, storePair = SyncEngine.CreditCards to creditCardsStorage,

@ -74,7 +74,8 @@ class Components(private val context: Context) {
core.store, core.store,
core.webAppShortcutManager, core.webAppShortcutManager,
core.topSitesStorage, core.topSitesStorage,
core.bookmarksStorage core.bookmarksStorage,
core.historyStorage
) )
} }
@ -179,6 +180,7 @@ class Components(private val context: Context) {
val appStartReasonProvider by lazyMonitored { AppStartReasonProvider() } val appStartReasonProvider by lazyMonitored { AppStartReasonProvider() }
val startupActivityLog by lazyMonitored { StartupActivityLog() } val startupActivityLog by lazyMonitored { StartupActivityLog() }
val startupStateProvider by lazyMonitored { StartupStateProvider(startupActivityLog, appStartReasonProvider) } 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.webcompat.reporter.WebCompatReporterFeature
import mozilla.components.feature.webnotifications.WebNotificationFeature import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.lib.dataprotect.SecureAbove22Preferences import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.lib.dataprotect.generateEncryptionKey
import mozilla.components.service.digitalassetlinks.RelationChecker import mozilla.components.service.digitalassetlinks.RelationChecker
import mozilla.components.service.digitalassetlinks.local.StatementApi import mozilla.components.service.digitalassetlinks.local.StatementApi
import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker 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.Mockable
import org.mozilla.fenix.utils.getUndoDelay import org.mozilla.fenix.utils.getUndoDelay
import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntime
import java.lang.IllegalStateException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -218,7 +216,15 @@ class Core(
) )
BrowserStore( 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 { ).apply {
// Install the "icons" WebExtension to automatically load icons for every visited website. // Install the "icons" WebExtension to automatically load icons for every visited website.
icons.install(engine, this) icons.install(engine, this)
@ -294,7 +300,7 @@ class Core(
// We can fully initialize GeckoEngine without initialized our storage. // We can fully initialize GeckoEngine without initialized our storage.
val lazyHistoryStorage = lazyMonitored { PlacesHistoryStorage(context, crashReporter) } val lazyHistoryStorage = lazyMonitored { PlacesHistoryStorage(context, crashReporter) }
val lazyBookmarksStorage = lazyMonitored { PlacesBookmarksStorage(context) } val lazyBookmarksStorage = lazyMonitored { PlacesBookmarksStorage(context) }
val lazyPasswordsStorage = lazyMonitored { SyncableLoginsStorage(context, passwordsEncryptionKey) } val lazyPasswordsStorage = lazyMonitored { SyncableLoginsStorage(context, lazySecurePrefs) }
val lazyAutofillStorage = lazyMonitored { AutofillCreditCardsAddressesStorage(context, lazySecurePrefs) } val lazyAutofillStorage = lazyMonitored { AutofillCreditCardsAddressesStorage(context, lazySecurePrefs) }
/** /**
@ -361,6 +367,13 @@ class Core(
SupportUtils.TC_URL SupportUtils.TC_URL
) )
) )
defaultTopSites.add(
Pair(
context.getString(R.string.default_top_site_meituan),
SupportUtils.MEITUAN_URL
)
)
} else { } else {
defaultTopSites.add( defaultTopSites.add(
Pair( Pair(
@ -418,23 +431,6 @@ class Core(
// Temporary. See https://github.com/mozilla-mobile/fenix/issues/19155 // Temporary. See https://github.com/mozilla-mobile/fenix/issues/19155
private val lazySecurePrefs = lazyMonitored { getSecureAbove22Preferences() } 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()) val trackingProtectionPolicyFactory = TrackingProtectionPolicyFactory(context.settings())
/** /**

@ -78,7 +78,7 @@ class FenixSnackbar private constructor(
companion object { companion object {
const val LENGTH_LONG = Snackbar.LENGTH_LONG const val LENGTH_LONG = Snackbar.LENGTH_LONG
const val LENGTH_SHORT = Snackbar.LENGTH_SHORT 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 const val LENGTH_INDEFINITE = Snackbar.LENGTH_INDEFINITE
private const val minTextSize = 12 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.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.BookmarksStorage import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.app.links.AppLinksUseCases import mozilla.components.feature.app.links.AppLinksUseCases
import mozilla.components.feature.contextmenu.ContextMenuUseCases import mozilla.components.feature.contextmenu.ContextMenuUseCases
import mozilla.components.feature.downloads.DownloadsUseCases import mozilla.components.feature.downloads.DownloadsUseCases
@ -38,7 +39,8 @@ class UseCases(
private val store: BrowserStore, private val store: BrowserStore,
private val shortcutManager: WebAppShortcutManager, private val shortcutManager: WebAppShortcutManager,
private val topSitesStorage: TopSitesStorage, 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. * Use cases that provide engine interactions for a given browser session.
@ -63,7 +65,8 @@ class UseCases(
val searchUseCases by lazyMonitored { val searchUseCases by lazyMonitored {
SearchUseCases( SearchUseCases(
store, store,
tabsUseCases tabsUseCases,
sessionUseCases
) )
} }
@ -97,5 +100,5 @@ class UseCases(
/** /**
* Use cases that provide bookmark management. * 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 androidx.annotation.WorkerThread
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarksStorage import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.concept.storage.HistoryStorage
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
* Use cases that allow for modifying and retrieving bookmarks. * 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) { 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( 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. * 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 @WorkerThread
suspend operator fun invoke( suspend operator fun invoke(
count: Int = DEFAULT_BOOKMARKS_TO_RETRIEVE, count: Int = DEFAULT_BOOKMARKS_TO_RETRIEVE,
maxAgeInMs: Long = TimeUnit.DAYS.toMillis(DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE) maxAgeInMs: Long = TimeUnit.DAYS.toMillis(DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE)
): List<BookmarkNode> { ): List<RecentBookmark> {
return storage.getRecentBookmarks( val currentTime = System.currentTimeMillis()
count,
maxAgeInMs // 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 addBookmark by lazy { AddBookmarksUseCase(bookmarksStorage) }
val retrieveRecentBookmarks by lazy { RetrieveRecentBookmarksUseCase(storage) } val retrieveRecentBookmarks by lazy {
RetrieveRecentBookmarksUseCase(
bookmarksStorage,
historyStorage
)
}
companion object { companion object {
// Number of recent bookmarks to retrieve.
const val DEFAULT_BOOKMARKS_TO_RETRIEVE = 4 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 const val DEFAULT_BOOKMARKS_DAYS_AGE_TO_RETRIEVE = 10L
} }
} }

@ -4,6 +4,7 @@
package org.mozilla.fenix.components.history package org.mozilla.fenix.components.history
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.storage.VisitInfo import mozilla.components.concept.storage.VisitInfo
import mozilla.components.concept.storage.VisitType import mozilla.components.concept.storage.VisitType
@ -37,6 +38,26 @@ class DefaultPagedHistoryProvider(
private val historyStorage: PlacesHistoryStorage, private val historyStorage: PlacesHistoryStorage,
private val showHistorySearchGroups: Boolean = FeatureFlags.showHistorySearchGroups, private val showHistorySearchGroups: Boolean = FeatureFlags.showHistorySearchGroups,
) : PagedHistoryProvider { ) : 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 @Volatile private var historyGroups: List<History.Group>? = null
@ -52,7 +73,7 @@ class DefaultPagedHistoryProvider(
val history: List<History> val history: List<History>
if (showHistorySearchGroups) { 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. // in the case of a pull to refresh.
if (historyGroups == null || offset == 0L) { if (historyGroups == null || offset == 0L) {
historyGroups = historyStorage.getHistoryMetadataSince(Long.MIN_VALUE) historyGroups = historyStorage.getHistoryMetadataSince(Long.MIN_VALUE)
@ -75,15 +96,7 @@ class DefaultPagedHistoryProvider(
.getVisitsPaginated( .getVisitsPaginated(
offset, offset,
numberOfItems, numberOfItems,
excludeTypes = listOf( excludeTypes = excludedVisitTypes
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
) )
.mapIndexed(transformVisitInfoToHistoryItem(offset.toInt())) .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. * 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( val history = historyStorage.getDetailedVisits(
start = historyMetadata.visitedAt - BUFFER_TIME, start = historyMetadata.visitedAt - BUFFER_TIME,
end = historyMetadata.visitedAt + BUFFER_TIME, end = historyMetadata.visitedAt + BUFFER_TIME,
excludeTypes = listOf( excludeTypes = excludedVisitTypes
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
) )
return history return history
.filter { it.url == historyMetadata.url } .filter { it.url == historyMetadata.url }
.minByOrNull { abs(historyMetadata.visitedAt - it.visitTime) } .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") @Suppress("MagicNumber")
private suspend fun getHistoryAndSearchGroups( private suspend fun getHistoryAndSearchGroups(
offset: Long, offset: Long,
@ -134,18 +150,26 @@ class DefaultPagedHistoryProvider(
.getVisitsPaginated( .getVisitsPaginated(
offset, offset,
numberOfItems, numberOfItems,
excludeTypes = listOf( excludeTypes = excludedVisitTypes
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
) )
.mapIndexed(transformVisitInfoToHistoryItem(offset.toInt())) .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 // 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 // additional buffer time to the most recent visit to account for a history group
// appearing as the most recent item. // appearing as the most recent item.
@ -155,8 +179,10 @@ class DefaultPagedHistoryProvider(
// items. // items.
val historyGroupsInOffset = if (history.isNotEmpty()) { val historyGroupsInOffset = if (history.isNotEmpty()) {
historyGroups?.filter { historyGroups?.filter {
history.last().visitedAt <= it.visitedAt - visitedAtBuffer && it.items.any { item ->
it.visitedAt - visitedAtBuffer <= (history.first().visitedAt + visitedAtBuffer) (history.last().visitedAt - visitedAtBuffer) <= item.visitedAt &&
item.visitedAt <= (history.first().visitedAt + visitedAtBuffer)
}
} ?: emptyList() } ?: emptyList()
} else { } else {
emptyList() emptyList()
@ -174,11 +200,12 @@ class DefaultPagedHistoryProvider(
// url, but we don't have a use case for this currently in the history view. // url, but we don't have a use case for this currently in the history view.
result.addAll( result.addAll(
historyGroupsInOffset.map { group -> 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 { 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.Autoplay
import org.mozilla.fenix.GleanMetrics.Collections import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.ContextMenu import org.mozilla.fenix.GleanMetrics.ContextMenu
import org.mozilla.fenix.GleanMetrics.CrashReporter
import org.mozilla.fenix.GleanMetrics.ErrorPage import org.mozilla.fenix.GleanMetrics.ErrorPage
import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.History 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.Preferences
import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp
import org.mozilla.fenix.GleanMetrics.SearchShortcuts import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchTerms
import org.mozilla.fenix.GleanMetrics.TabsTray import org.mozilla.fenix.GleanMetrics.TabsTray
import org.mozilla.fenix.GleanMetrics.ToolbarSettings import org.mozilla.fenix.GleanMetrics.ToolbarSettings
import org.mozilla.fenix.GleanMetrics.TopSites import org.mozilla.fenix.GleanMetrics.TopSites
@ -85,6 +85,10 @@ sealed class Event {
data class HistoryRecentSearchesTapped(val source: String) : Event() { data class HistoryRecentSearchesTapped(val source: String) : Event() {
override val extras = mapOf(History.recentSearchesTappedKeys.pageNumber to source) 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 ReaderModeAvailable : Event()
object ReaderModeOpened : Event() object ReaderModeOpened : Event()
object ReaderModeClosed : Event() object ReaderModeClosed : Event()
@ -211,12 +215,16 @@ sealed class Event {
object TabsTrayCloseAllInactiveTabs : Event() object TabsTrayCloseAllInactiveTabs : Event()
data class TabsTrayCloseInactiveTab(val amountClosed: Int = 1) : Event() data class TabsTrayCloseInactiveTab(val amountClosed: Int = 1) : Event()
object TabsTrayOpenInactiveTab : Event() object TabsTrayOpenInactiveTab : Event()
object TabsTrayInactiveTabsCFRGotoSettings : Event()
object TabsTrayInactiveTabsCFRDismissed : Event()
object TabsTrayInactiveTabsCFRIsVisible : Event()
object InactiveTabsSurveyOpened : Event() object InactiveTabsSurveyOpened : Event()
data class InactiveTabsOffSurvey(val feedback: String) : Event() { data class InactiveTabsOffSurvey(val feedback: String) : Event() {
override val extras: Map<Preferences.turnOffInactiveTabsSurveyKeys, String> override val extras: Map<Preferences.turnOffInactiveTabsSurveyKeys, String>
get() = mapOf(Preferences.turnOffInactiveTabsSurveyKeys.feedback to feedback.lowercase(Locale.ROOT)) get() = mapOf(Preferences.turnOffInactiveTabsSurveyKeys.feedback to feedback.lowercase(Locale.ROOT))
} }
data class InactiveTabsCountUpdate(val count: Int) : Event()
object ProgressiveWebAppOpenFromHomescreenTap : Event() object ProgressiveWebAppOpenFromHomescreenTap : Event()
object ProgressiveWebAppInstallAsShortcut : Event() object ProgressiveWebAppInstallAsShortcut : Event()
@ -254,6 +262,7 @@ sealed class Event {
// Home menu interaction // Home menu interaction
object HomeMenuSettingsItemClicked : Event() object HomeMenuSettingsItemClicked : Event()
object HomeScreenDisplayed : Event() object HomeScreenDisplayed : Event()
object HomeScreenViewCount : Event()
object HomeScreenCustomizedHomeClicked : Event() object HomeScreenCustomizedHomeClicked : Event()
// Browser Toolbar // Browser Toolbar
@ -618,14 +627,8 @@ sealed class Event {
} }
} }
object CrashReporterOpened : Event()
data class AddonInstalled(val addonId: String) : 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() { data class BrowserMenuItemTapped(val item: Item) : Event() {
enum class Item { enum class Item {
SETTINGS, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB, 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)) 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() object AutoPlaySettingVisited : Event()
data class AutoPlaySettingChanged(val setting: AutoplaySetting) : 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)) 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 sealed class Search
internal open val extras: Map<*, String>? internal open val extras: Map<*, String>?

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

@ -19,7 +19,6 @@ import org.mozilla.fenix.GleanMetrics.BrowserSearch
import org.mozilla.fenix.GleanMetrics.Collections import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.ContextMenu import org.mozilla.fenix.GleanMetrics.ContextMenu
import org.mozilla.fenix.GleanMetrics.ContextualMenu import org.mozilla.fenix.GleanMetrics.ContextualMenu
import org.mozilla.fenix.GleanMetrics.CrashReporter
import org.mozilla.fenix.GleanMetrics.CreditCards import org.mozilla.fenix.GleanMetrics.CreditCards
import org.mozilla.fenix.GleanMetrics.CustomTab import org.mozilla.fenix.GleanMetrics.CustomTab
import org.mozilla.fenix.GleanMetrics.CustomizeHome 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.RecentSearches
import org.mozilla.fenix.GleanMetrics.RecentTabs import org.mozilla.fenix.GleanMetrics.RecentTabs
import org.mozilla.fenix.GleanMetrics.SearchShortcuts import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchTerms
import org.mozilla.fenix.GleanMetrics.SearchWidget import org.mozilla.fenix.GleanMetrics.SearchWidget
import org.mozilla.fenix.GleanMetrics.SetDefaultNewtabExperiment import org.mozilla.fenix.GleanMetrics.SetDefaultNewtabExperiment
import org.mozilla.fenix.GleanMetrics.SetDefaultSettingExperiment import org.mozilla.fenix.GleanMetrics.SetDefaultSettingExperiment
@ -157,13 +157,6 @@ private val Event.wrapper: EventWrapper<*>?
{ ContextMenu.itemTapped.record(it) }, { ContextMenu.itemTapped.record(it) },
{ ContextMenu.itemTappedKeys.valueOf(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( is Event.BrowserMenuItemTapped -> EventWrapper(
{ Events.browserMenuAction.record(it) }, { Events.browserMenuAction.record(it) },
{ Events.browserMenuActionKeys.valueOf(it) } { Events.browserMenuActionKeys.valueOf(it) }
@ -319,6 +312,18 @@ private val Event.wrapper: EventWrapper<*>?
{ History.recentSearchesTapped.record(it) }, { History.recentSearchesTapped.record(it) },
{ History.recentSearchesTappedKeys.valueOf(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>( is Event.CollectionRenamed -> EventWrapper<NoExtraKeys>(
{ Collections.renamed.record(it) } { Collections.renamed.record(it) }
) )
@ -543,10 +548,6 @@ private val Event.wrapper: EventWrapper<*>?
is Event.VoiceSearchTapped -> EventWrapper<NoExtraKeys>( is Event.VoiceSearchTapped -> EventWrapper<NoExtraKeys>(
{ VoiceSearch.tapped.record(it) } { VoiceSearch.tapped.record(it) }
) )
is Event.TabCounterMenuItemTapped -> EventWrapper(
{ Events.tabCounterMenuAction.record(it) },
{ Events.tabCounterMenuActionKeys.valueOf(it) }
)
is Event.OnboardingPrivacyNotice -> EventWrapper<NoExtraKeys>( is Event.OnboardingPrivacyNotice -> EventWrapper<NoExtraKeys>(
{ Onboarding.privacyNotice.record(it) } { Onboarding.privacyNotice.record(it) }
) )
@ -651,6 +652,18 @@ private val Event.wrapper: EventWrapper<*>?
{ Preferences.turnOffInactiveTabsSurvey.record(it) }, { Preferences.turnOffInactiveTabsSurvey.record(it) },
{ Preferences.turnOffInactiveTabsSurveyKeys.valueOf(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>( is Event.AutoPlaySettingVisited -> EventWrapper<NoExtraKeys>(
{ Autoplay.visitedSetting.record(it) } { Autoplay.visitedSetting.record(it) }
) )
@ -766,6 +779,9 @@ private val Event.wrapper: EventWrapper<*>?
is Event.HomeScreenDisplayed -> EventWrapper<NoExtraKeys>( is Event.HomeScreenDisplayed -> EventWrapper<NoExtraKeys>(
{ HomeScreen.homeScreenDisplayed.record(it) } { HomeScreen.homeScreenDisplayed.record(it) }
) )
is Event.HomeScreenViewCount -> EventWrapper<NoExtraKeys>(
{ HomeScreen.homeScreenViewCount.add() }
)
is Event.HomeScreenCustomizedHomeClicked -> EventWrapper<NoExtraKeys>( is Event.HomeScreenCustomizedHomeClicked -> EventWrapper<NoExtraKeys>(
{ HomeScreen.customizeHomeClicked.record(it) } { HomeScreen.customizeHomeClicked.record(it) }
) )
@ -880,6 +896,20 @@ private val Event.wrapper: EventWrapper<*>?
is Event.CreditCardManagementCardTapped -> EventWrapper<NoExtraKeys>( is Event.CreditCardManagementCardTapped -> EventWrapper<NoExtraKeys>(
{ CreditCards.managementCardTapped.record(it) } { 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: // Don't record other events in Glean:
is Event.AddBookmark -> null is Event.AddBookmark -> null
@ -942,8 +972,3 @@ class GleanMetricsService(
return event.wrapper != null 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 appContext: Context,
private val experiments: NimbusApi private val experiments: NimbusApi
) { ) {
suspend fun startTests() { fun startTests() {
// The Android Keystore is used to secure the shared prefs only on API 23+ // The Android Keystore is used to secure the shared prefs only on API 23+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// These tests should run only if the experiment is live // 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.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext 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.InvalidRecordException
import mozilla.components.service.sync.logins.LoginsStorageException 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.FennecLoginsMPImporter
import mozilla.components.support.migration.FennecProfile import mozilla.components.support.migration.FennecProfile
import org.mozilla.fenix.R 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 { CoroutineScope(IO).launch {
logins.map { it.toLogin() }.forEach { try {
try { context.components.core.passwordsStorage.importLoginsAsync(logins)
context.components.core.passwordsStorage.add(it) } catch (e: InvalidRecordException) {
} catch (e: InvalidRecordException) { // This record was invalid and we couldn't save this login
// This record was invalid and we couldn't save this login context.components.analytics.crashReporter.submitCaughtException(e)
context.components.analytics.crashReporter.submitCaughtException(e) } catch (e: LoginsStorageException) {
} catch (e: IdCollisionException) { // Some other error occurred
// Nonempty ID was provided context.components.analytics.crashReporter.submitCaughtException(e)
context.components.analytics.crashReporter.submitCaughtException(e)
} catch (e: LoginsStorageException) {
// Some other error occurred
context.components.analytics.crashReporter.submitCaughtException(e)
}
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
// Step 3: Dismiss this dialog and show the success dialog // Step 3: Dismiss this dialog and show the success dialog

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

@ -14,7 +14,6 @@ import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.browser.state.state.CustomTabSessionState
@ -35,7 +34,6 @@ import org.mozilla.fenix.utils.ToolbarPopupWindow
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import mozilla.components.browser.toolbar.behavior.ToolbarPosition as MozacToolbarPosition import mozilla.components.browser.toolbar.behavior.ToolbarPosition as MozacToolbarPosition
@ExperimentalCoroutinesApi
@SuppressWarnings("LargeClass") @SuppressWarnings("LargeClass")
class BrowserToolbarView( class BrowserToolbarView(
private val container: ViewGroup, private val container: ViewGroup,
@ -131,7 +129,7 @@ class BrowserToolbarView(
trackingProtection = primaryTextColor, trackingProtection = primaryTextColor,
highlight = ContextCompat.getColor( highlight = ContextCompat.getColor(
context, 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.core.content.ContextCompat.getColor
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull 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. * @param bookmarksStorage Used to check if a page is bookmarked.
*/ */
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions") @Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
@ExperimentalCoroutinesApi
open class DefaultToolbarMenu( open class DefaultToolbarMenu(
private val context: Context, private val context: Context,
private val store: BrowserStore, private val store: BrowserStore,
@ -183,7 +181,7 @@ open class DefaultToolbarMenu(
iconTintColorResource = primaryTextColor(), iconTintColorResource = primaryTextColor(),
highlight = BrowserMenuHighlight.LowPriority( highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_install_on_homescreen), 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 = { isHighlighted = {
!context.settings().installPwaOpened !context.settings().installPwaOpened
@ -252,7 +250,7 @@ open class DefaultToolbarMenu(
iconTintColorResource = primaryTextColor(), iconTintColorResource = primaryTextColor(),
highlight = BrowserMenuHighlight.LowPriority( highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_open_app_link), 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 } isHighlighted = { !context.settings().openInAppOpened }
) { ) {

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

@ -7,7 +7,6 @@ package org.mozilla.fenix.components.toolbar
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider
import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs 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.ext.settings
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
@ExperimentalCoroutinesApi
abstract class ToolbarIntegration( abstract class ToolbarIntegration(
context: Context, context: Context,
toolbar: BrowserToolbar, toolbar: BrowserToolbar,
@ -77,7 +75,6 @@ abstract class ToolbarIntegration(
} }
} }
@ExperimentalCoroutinesApi
class DefaultToolbarIntegration( class DefaultToolbarIntegration(
context: Context, context: Context,
toolbar: BrowserToolbar, 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.height
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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. * @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 * 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. * 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 @Composable
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -35,7 +42,9 @@ fun Image(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
private: Boolean = false, private: Boolean = false,
targetSize: Dp = 100.dp, targetSize: Dp = 100.dp,
contentDescription: String? = null contentDescription: String? = null,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
) { ) {
ImageLoader( ImageLoader(
url = url, url = url,
@ -48,6 +57,8 @@ fun Image(
painter = painter, painter = painter,
modifier = modifier, modifier = modifier,
contentDescription = contentDescription, contentDescription = contentDescription,
alignment = alignment,
contentScale = contentScale
) )
} }

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

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

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

Loading…
Cancel
Save