Compare commits

...

No commits in common. 'fork' and 'iceraven-2.2.0' have entirely different histories.

@ -0,0 +1,142 @@
projects:
app:
upstream_dependencies:
- browser-domains
- browser-engine-gecko
- browser-errorpages
- browser-icons
- browser-menu
- browser-menu2
- browser-session-storage
- browser-state
- browser-storage-sync
- browser-tabstray
- browser-thumbnails
- browser-toolbar
- compose-awesomebar
- compose-cfr
- concept-awesomebar
- concept-base
- concept-engine
- concept-fetch
- concept-menu
- concept-push
- concept-storage
- concept-sync
- concept-tabstray
- concept-toolbar
- feature-accounts
- feature-accounts-push
- feature-addons
- feature-app-links
- feature-autofill
- feature-awesomebar
- feature-contextmenu
- feature-customtabs
- feature-downloads
- feature-findinpage
- feature-intent
- feature-logins
- feature-media
- feature-privatemode
- feature-prompts
- feature-push
- feature-pwa
- feature-qr
- feature-readerview
- feature-recentlyclosed
- feature-search
- feature-session
- feature-share
- feature-sitepermissions
- feature-syncedtabs
- feature-tab-collections
- feature-tabs
- feature-toolbar
- feature-top-sites
- feature-webauthn
- feature-webcompat
- feature-webcompat-reporter
- feature-webnotifications
- lib-crash
- lib-crash-sentry
- lib-dataprotect
- lib-publicsuffixlist
- lib-push-firebase
- lib-state
- service-contile
- service-digitalassetlinks
- service-firefox-accounts
- service-glean
- service-location
- service-nimbus
- service-pocket
- service-sync-autofill
- service-sync-logins
- support-base
- support-images
- support-ktx
- support-locale
- support-rusterrors
- support-rusthttp
- support-rustlog
- support-sync-telemetry
- support-test
- support-test-libstate
- support-utils
- support-webextensions
- ui-autocomplete
- ui-colors
- ui-icons
- ui-tabcounter
- ui-widgets
variants:
- apks:
- abi: arm64-v8a
fileName: app-fenix-arm64-v8a-debug.apk
- abi: armeabi-v7a
fileName: app-fenix-armeabi-v7a-debug.apk
- abi: x86
fileName: app-fenix-x86-debug.apk
- abi: x86_64
fileName: app-fenix-x86_64-debug.apk
build_type: debug
name: fenixDebug
- apks:
- abi: arm64-v8a
fileName: app-fenix-arm64-v8a-release-unsigned.apk
- abi: armeabi-v7a
fileName: app-fenix-armeabi-v7a-release-unsigned.apk
- abi: x86
fileName: app-fenix-x86-release-unsigned.apk
- abi: x86_64
fileName: app-fenix-x86_64-release-unsigned.apk
build_type: release
name: fenixRelease
- apks:
- abi: arm64-v8a
fileName: app-fenix-arm64-v8a-nightly-unsigned.apk
- abi: armeabi-v7a
fileName: app-fenix-armeabi-v7a-nightly-unsigned.apk
- abi: x86
fileName: app-fenix-x86-nightly-unsigned.apk
- abi: x86_64
fileName: app-fenix-x86_64-nightly-unsigned.apk
build_type: nightly
name: fenixNightly
- apks:
- abi: arm64-v8a
fileName: app-fenix-arm64-v8a-beta-unsigned.apk
- abi: armeabi-v7a
fileName: app-fenix-armeabi-v7a-beta-unsigned.apk
- abi: x86
fileName: app-fenix-x86-beta-unsigned.apk
- abi: x86_64
fileName: app-fenix-x86_64-beta-unsigned.apk
build_type: beta
name: fenixBeta
- apks:
- abi: noarch
fileName: app-debug-androidTest.apk
build_type: androidTest
name: androidTest

@ -1,41 +0,0 @@
# Definitions for jobs that run periodically. For details on the format, see
# `taskcluster/taskgraph/cron/schema.py`. For documentation, see
# `taskcluster/docs/cron.rst`.
---
jobs:
- name: nightly
job:
type: decision-task
treeherder-symbol: Nd
target-tasks-method: nightly
when:
- {hour: 5, minute: 0}
- {hour: 17, minute: 0}
- name: nightly-test
job:
type: decision-task
treeherder-symbol: Nt
target-tasks-method: nightly-test
when:
- {hour: 5, minute: 0}
- name: fennec-production
job:
type: decision-task
treeherder-symbol: fennec-production
target-tasks-method: fennec-production
when: [] # Force hook only
- name: screenshots
job:
type: decision-task
treeherder-symbol: screenshots-D
target-tasks-method: screenshots
when: [{weekday: 'Monday', hour: 10, minute: 0}]
- name: legacy-api-ui-tests
job:
type: decision-task
treeherder-symbol: legacy-api-ui
target-tasks-method: legacy_api_ui_tests
when:
- {hour: 11, minute: 0}
- {hour: 20, minute: 0}

31
.github/CODEOWNERS vendored

@ -1,31 +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/.
# This CODEOWNERS file defines individuals or teams that are responsible
# for code in this repository. Code owners are automatically requested
# for review when someone opens a pull request that modifies code that
# they own. Order is important; the last matching pattern takes the most
# precedence.
# A CODEOWNERS file uses a pattern that follows the same rules used in
# gitignore files. The pattern is followed by one or more GitHub usernames
# or team names using the standard @username or @org/team-name format.
# You can also refer to a user by an email address that has been added
# to their GitHub account, for example user@example.com.
# https://help.github.com/articles/about-codeowners/
# WARNING: if there is a single syntax error in this file, CODEOWNERS
# WILL NOT WORK AT ALL. Please be careful when editing this file.
#
# You can use the technique described in this blog post to validate
# the paths you specify in .gitignore:
# http://www.benjaminoakes.com/git/2018/08/10/Testing-changes-to-GitHub-CODEOWNERS/
# By default the Android Components team will be the owner for everything in
# the repo. Unless a later match takes precedence.
* @mozilla-mobile/ACT @mozilla-mobile/fenix
/.cron.yml @mozilla-mobile/releng @mozilla-mobile/fenix
/.taskcluster.yml @mozilla-mobile/releng @mozilla-mobile/fenix
/automation/ @mozilla-mobile/fenix
/taskcluster/ @mozilla-mobile/releng @mozilla-mobile/fenix
/.github/ @mozilla-mobile/fenix

@ -1,91 +0,0 @@
name: "\U0001F41E Bug report"
description: Create a report to help us improve.
title: "[Bug]: "
labels: ["\U0001F41E bug", "needs:triage"]
body:
- type: markdown
attributes:
value: |
- Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean.
- Have a look at ["I want to file an issue!"][info] for more information.
- Read the [Community Participation Guidelines][guidelines] and the [Bugzilla Etiquette guidelines][bugzilla] before filing an issue.
[info]: https://github.com/mozilla-mobile/fenix#i-want-to-file-an-issue
[guidelines]: https://www.mozilla.org/en-US/about/governance/policies/participation/
[bugzilla]: https://bugzilla.mozilla.org/page.cgi?id=etiquette.html
- type: textarea
attributes:
label: Steps to reproduce
description: Steps to reproduce the behaviour.
placeholder: |
1. Have a tab open..
2. Go to..
3. Click on..
4. Observe..
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
placeholder: A menu should open..
validations:
required: true
- type: textarea
attributes:
label: Actual behaviour
placeholder: The app closes unexpectedly..
validations:
required: true
- type: markdown
attributes:
value: |
# Device information
- type: input
attributes:
label: Device name
description: The name of the device model and manufacturer.
placeholder: Google Pixel 2
validations:
required: false
- type: input
attributes:
label: Android version
description: You can find the Android version information in the About section of your device's system settings.
placeholder: Android 10
validations:
required: true
- type: dropdown
attributes:
label: Firefox release type
description: You can find this information in Settings -> About Firefox.
options:
- Firefox Nightly
- Firefox Beta
- Firefox
validations:
required: true
- type: input
attributes:
label: Firefox version
description: You can find this information in Settings -> About Firefox.
placeholder: 89.0.10
validations:
required: true
- type: textarea
attributes:
label: Device logs
description: |
Device logs or crash information can greatly aid in debugging. You can find some details here on how to [retrieve device logs or crash IDs][log].
[log]: https://github.com/mozilla-mobile/fenix/wiki/Logging-Crash-Information
validations:
required: false
- type: textarea
attributes:
label: Additional information
description: |
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.
validations:
required: false

@ -1,19 +0,0 @@
---
name: "⭐️ Feature request"
about: Suggest an idea for this project
title: ''
labels: "🌟 feature request"
assignees: ''
---
[comment]: # (Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean)
[comment]: # (Every issue should have exactly one feature request described in it. Please do not file feedback list tickets as it is difficult to parse them and address their individual points)
[comment]: # (Feature Requests are better when theyre open-ended instead of demanding a specific solution e.g: “I want an easier way to do X” instead of “add Y”)
[comment]: # (Read https://github.com/mozilla-mobile/fenix#i-want-to-file-an-issue for more information)
### What is the user problem or growth opportunity you want to see solved?
### How do you know that this problem exists today? Why is this important?
### Who will benefit from it?

@ -1,19 +0,0 @@
---
name: "⌛ Performance issue"
about: Create a performance issue if the app is slow or it uses too much memory, disk space, battery, or network data
title: ""
labels: "performance"
assignees: ''
---
## Steps to reproduce
### Expected behavior
### Actual behavior
### Device information
* Android device: ?
* Fenix version: ?

@ -1,25 +0,0 @@
---
name: "\U0001F4BB Web content issue report"
about: Create an issue specifically about something wrong with web content while using Fenix
title: "[webcontent]"
labels: "\U0001F4BB bug"
assignees: ''
---
## Site with issue and any steps to reproduce
### Expected behavior
### Actual behavior
### Does toggling Tracking Protection fix the issue? (Press the shield icon in the toolbar while on the site to see toggle)
### Can you reproduce in Chrome (or other non-Mozilla browser)?
<!--- Note: If you can reproduce the same issue in Firefox for Android AND Chrome, this issue is very unlikely to be fixed by our teams. -->
<!-- If you cannot reproduce in Chrome and know how to do so, please consider filing a Bugzilla issue here for faster triage https://bugzilla.mozilla.org/enter_bug.cgi?product=GeckoView&component=General -->
### Device information
* Android device: ?
* Fenix version: ?

@ -1,24 +0,0 @@
---
name: "\U000026CF Investigative Spike"
about: Create an investigation spike
title: "[Spike]"
---
## Title
Brief description of what needs to be investigated, including the User story for which the spike is needed.
## Description
Description of what is being investigated, including:
Method of investigation (engineering research, prototype, etc.
Boundaries of investigation (time box to x hours, does not include UX, etc.)
## Deliverables
Description of deliverables, including:
Documentation of investigation results (within the spike ticket, or linked to it), including:
Findings
Recommendations
List of possible user stories to implement recommendations, including estimates
Next Steps
Reach out to Product to go over results of investigation.

@ -1,21 +0,0 @@
---
name: "\U0001F4C8 Telemetry User Story"
about: Create a telemetry user story, assigned to an Epic to track telemetry
title: "[Telemetry]"
labels: Feature:Telemetry
assignees: ''
---
## Description & Product Manager / Data Scientist User Story
## What questions will you answer with this data?
## Acceptance Criteria
- [ ] ENG files a [DS JIRA](https://jira.mozilla.com/projects/DO/issues/DO-228?filter=allopenissues) request outlining their methodology.
- [ ] DS sign off on instrumentation methodology addressing product questions.
- [ ] Event pings can be queried via re:dash
- [ ] Event pings can be queried via amplitude
- [ ] We are sending telemetry events for the actions listed in the requirements
- [ ] We have documented the telemetry
- [ ] We have asked a data steward to [review](https://github.com/mozilla/data-review/blob/master/request.md) the telemetry

@ -1,13 +0,0 @@
---
name: "\U0001F494 Intermittent UI Test Issue"
about: Create an issue to help log a UI test failure
labels: "eng:ui-test, intermittent-test"
title: "Intermittent UI test failure - <Classname.testName>"
assignees: ''
---
### Firebase Test Run:
Provide a Firebase test run report link here showcasing the problem
### Stacktrace:
### Build:

@ -1,13 +0,0 @@
---
name: "\U0001F6A8 Intermittent Unit Test Issue"
about: Create an issue to help log a Unit Test failure
labels: "eng:intermittent-test"
title: "Intermittent Unit Test failure - <Classname.testName>"
assignees: ''
---
### Test Run:
Provide a test run report link here showcasing the problem (e.g, Taskcluster), and a link to the source Github event
### Stacktrace:
### Build:

@ -1,18 +0,0 @@
---
name: "\U0001F469\U0001F3FB\U0001F4BB User Story"
about: Create a user story for an epic
title: ""
labels: ''
assignees: ''
---
## User Story
- As a user, I want … so I can do … (keep it problem-centric)
## Dependencies
- List dependencies on other issues/teams etc.
### Acceptance Criteria
- I can do … (e.g. add a bookmark)

@ -1,14 +0,0 @@
### Pull Request checklist
<!-- Before submitting the PR, please address each item -->
- [ ] **Tests**: This PR includes thorough tests or an explanation of why it does not
- [ ] **Screenshots**: This PR includes screenshots or GIFs of the changes made or an explanation of why it does not
- [ ] **Accessibility**: The code in this PR follows [accessibility best practices](https://github.com/mozilla-mobile/shared-docs/blob/master/android/accessibility_guide.md) or does not include any user facing features. In addition, it includes a screenshot of a successful [accessibility scan](https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&hl=en_US) to ensure no new defects are added to the product.
### To download an APK when reviewing a PR:
The PR runs an Android build check (`run-build`) that builds a `forkRelease` variant of the app. If it succeeds, then we upload the apks (signed with debug keys) via Github actions. We also generate a comment with some instructions and a link to help you find the downloads. You can also follow the instructions below:
1. Click Details next to "run-build (pull_request_target)" after it finishes with a green checkmark.
2. Click the "Artifacts" drop-down near the top right of the page.
3. The apk links should be present in the drop-down menu. You can click on the suitable CPU architecture to download a zipped apk file.
4. Unzip the file and install the apk.

@ -1,115 +0,0 @@
name: Android build PR
on:
pull_request:
branches:
- fork
jobs:
run-build:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Android SDK with pieces Gradle skips
run: ./automation/iceraven/install-sdk.sh
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Build forkRelease variant of app
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: app:assembleForkRelease -PversionName=${{ env.VERSION_NAME }}
run-testDebug:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run tests
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: testDebug
run-detekt:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run detekt
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: detekt
- name: Archive detekt results
uses: actions/upload-artifact@v2
with:
name: detekt report
path: build/reports/detekt.html
run-ktlint:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run ktlint
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: ktlint
run-lintDebug:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run lintDebug
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: lintDebug
- name: Archive lint results
uses: actions/upload-artifact@v2
with:
name: lintDebug report
path: app/build/reports/lint-results-debug.html

@ -1,143 +0,0 @@
name: Android build
on:
push:
branches:
- fork
jobs:
run-build:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Install Android SDK with pieces Gradle skips
run: ./automation/iceraven/install-sdk.sh
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Build forkRelease variant of app
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: app:assembleForkRelease -PversionName=${{ env.VERSION_NAME }}
- name: Create signed APKs
uses: abhijitvalluri/sign-apks@v0.8
with:
releaseDirectory: app/build/outputs/apk/forkRelease/
signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }}
alias: ${{ secrets.DEBUG_ALIAS }}
keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }}
- name: Archive arm64 apk
uses: actions/upload-artifact@v2
with:
name: app-arm64-v8a-forkRelease.apk
path: app/build/outputs/apk/forkRelease/app-arm64-v8a-forkRelease.apk
- name: Archive armeabi apk
uses: actions/upload-artifact@v2
with:
name: app-armeabi-v7a-forkRelease.apk
path: app/build/outputs/apk/forkRelease/app-armeabi-v7a-forkRelease.apk
- name: Archive x86 apk
uses: actions/upload-artifact@v2
with:
name: app-x86-forkRelease.apk
path: app/build/outputs/apk/forkRelease/app-x86-forkRelease.apk
- name: Archive x86_64 apk
uses: actions/upload-artifact@v2
with:
name: app-x86_64-forkRelease.apk
path: app/build/outputs/apk/forkRelease/app-x86_64-forkRelease.apk
run-testDebug:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run tests
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: testDebug
run-detekt:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run detekt
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: detekt
- name: Archive detekt results
uses: actions/upload-artifact@v2
with:
name: detekt report
path: build/reports/detekt.html
run-ktlint:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run ktlint
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: ktlint
run-lintDebug:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 11
- name: Run lintDebug
uses: eskatos/gradle-command-action@v1
with:
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: lintDebug
- name: Archive lint results
uses: actions/upload-artifact@v2
with:
name: lintDebug report
path: app/build/reports/lint-results-debug.html

@ -1,24 +0,0 @@
name: PR comment
on:
pull_request_target:
types: [opened]
branches:
- fork
jobs: # Disabled because we cannot build changes from fork PRs using this repo's secrets due to Github limitations. So, the built apk will be from wrong code, so this is pointless.
comment-on-pr:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')"
steps:
- name: Comment on PR with link to checks page
uses: mshick/add-pr-comment@v1
with:
message: |
### Download the built apks
You can download the apks built by Github actions **after** the CI checks pass.
Please go to the <a href="${{ github.event.pull_request.html_url }}/checks">checks page for this PR</a> to find the zipped apk files under the artifacts drop-down, as seen in the example screenshot below.
Note that you will have to click on the "Android build PR" tab on the left side to see the artifacts.
<img src="https://raw.githubusercontent.com/fork-maintainers/iceraven-browser/fork/.github/imgs/download-artifacts-screenshot.png" />
repo-token: ${{ secrets.GITHUB_TOKEN }}
allow-repeats: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

63
.github/stale.yml vendored

@ -1,63 +0,0 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 180
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- pin
- "feature request 🌟"
- "eng:disabled-test"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: wontfix
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
See: https://github.com/mozilla-mobile/fenix/issues/17373
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
# closeComment: >
# Your comment here.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
issues:
exemptLabels:
- pin
- "feature request 🌟"

@ -2,7 +2,7 @@
# 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/
name: "Sync Strings"
name: "Fenix - Sync Strings"
on:
schedule:

@ -2,7 +2,7 @@
# 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/
name: "Update Android-Components"
name: "Fenix - Update Android-Components"
on:
schedule:

@ -2,7 +2,7 @@
# 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/
name: "Update Nimbus Experiments"
name: "Fenix - Update Nimbus Experiments"
on:
schedule:
@ -15,14 +15,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout Main Branch"
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: fenix
ref: main
fetch-depth: 0
- name: "Update Experiments JSON"
id: update-experiments-json
uses: mozilla-mobile/update-experiments@v2
uses: mozilla-mobile/update-experiments@v3
with:
repo-path: fenix
output-path: app/src/main/res/raw/initial_experiments.json
@ -30,7 +30,7 @@ jobs:
app-name: fenix
branch: automation/update-nimbus-experiments
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v4
if: steps.update-experiments-json.outputs.changed == 1 && steps.update-experiments-json.outputs.changed-branch == 1
with:
token: ${{ secrets.GITHUB_TOKEN }}

@ -1,11 +1,13 @@
name: Release Automation
on:
push:
branches:
- fork
- iceraven
tags:
- '*'
- "*"
create:
jobs:
release-automation:
name: Build App
@ -14,18 +16,29 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'true'
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: 11
distribution: temurin
- name: Install Android SDK with pieces Gradle skips
run: ./automation/iceraven/install-sdk.sh
- name: Inspect memory
run: free -m
run: free -h
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Relpace strings
run: |
sed -i 's/Firefox/Iceraven/g' app/src/*/res/values*/*strings.xml
sed -i '/about_content/s/Mozilla/@forkmaintainers/' app/src/*/res/values*/*strings.xml
- name: Build forkRelease variant of app
uses: gradle/gradle-build-action@v2
env:
@ -33,17 +46,17 @@ jobs:
# See <https://stackoverflow.com/a/70010526> and <https://stackoverflow.com/a/70756876>
# The runner seems to have ~6 gigs of memory, so we make sure to stay under that.
# We have Java 11 so we don't have a perm size anymore.
GRADLE_OPTS: -Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=2g -Xms2g -Xmx5g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dev/stderr"
GRADLE_OPTS: -Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=1g -Xms2g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dev/stderr"
with:
gradle-home-cache-cleanup: true
gradle-executable: /usr/bin/time
arguments: -v ./gradlew app:assembleForkRelease -PversionName=${{ env.VERSION_NAME }} --stacktrace
arguments: -v ./gradlew app:assemblefenixForkRelease -x lintVitalFenixForkRelease -PversionName=${{ env.VERSION_NAME }} --stacktrace
- name: Create signed APKs
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: abhijitvalluri/sign-apks@v0.8
with:
releaseDirectory: app/build/outputs/apk/forkRelease/
releaseDirectory: app/build/outputs/apk/fenix/forkRelease/
signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }}
alias: ${{ secrets.DEBUG_ALIAS }}
keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }}
@ -53,25 +66,23 @@ jobs:
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
run: |
PREVIOUS_RELEASE_TAG=$(git tag --list iceraven-* --sort=-creatordate | tail -n+2 | head -n 1)
CURRENT_RELEASE_TAG=${{ github.event.ref }}
CURRENT_RELEASE_TAG_CAPITALIZE=${CURRENT_RELEASE_TAG^}
CURRENT_RELEASE_TAG_CAPITALIZE=$(echo $CURRENT_RELEASE_TAG_CAPITALIZE | tr '-' ' ')
echo "CURRENT_RELEASE_TAG_CAPITALIZE=${CURRENT_RELEASE_TAG_CAPITALIZE}" >> $GITHUB_ENV
FENIX_TAG=$(cat version.txt | tr -d '\n')
echo "## Release info\n" >> temp_changelog.md
echo '```' >> temp_changelog.md
echo "Iceraven: $(echo $CURRENT_RELEASE_TAG | tr -d 'iceraven-')" >> temp_changelog.md
echo "Fenix: ${FENIX_TAG}" >> temp_changelog.md
echo '```\n' >> temp_changelog.md
echo "## News\n" >> temp_changelog.md
echo "## Automated release of version ${{ github.event.ref }} browser" >>temp_changelog.md
if [[ "$(git log | grep $PREVIOUS_RELEASE_TAG | wc -l)" != "0" ]] ; then
# There's a path from the previous release to now
echo "<details>" >>temp_changelog.md
echo "<summary>Click to expand</summary>" >>temp_changelog.md
echo " " >>temp_changelog.md
echo "This is an automated release, consisting of the following changes:" >>temp_changelog.md
echo "### Change log (commit history since previous release)" >>temp_changelog.md
echo " " >>temp_changelog.md
git log ${{ github.event.ref }}...$PREVIOUS_RELEASE_TAG --pretty='format:%C(auto)%h (%as) %s' | head -n 1000 >>temp_changelog.md
echo " " >>temp_changelog.md
echo " " >>temp_changelog.md
echo "</details>" >>temp_changelog.md
else
echo "This is an automated release, not directly descended from the previous release." >>temp_changelog.md
fi
echo "**NOTE**: @fork-maintainers, you can edit these auto-generated release notes with a more user-friendly summary of the key changes, if needed." >>temp_changelog.md
echo " " >>temp_changelog.md
echo "## Change log\n"
echo "[${PREVIOUS_RELEASE_TAG}...${CURRENT_RELEASE_TAG}](https://github.com/${{ github.repository }}/compare/${PREVIOUS_RELEASE_TAG}...${CURRENT_RELEASE_TAG})" >> temp_changelog.md
- name: Create Release
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
@ -81,7 +92,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.event.ref }}
release_name: "Version ${{ github.event.ref }}"
release_name: "${{ env.CURRENT_RELEASE_TAG_CAPITALIZE }}"
draft: false
prerelease: false
body_path: temp_changelog.md
@ -93,11 +104,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/forkRelease/app-arm64-v8a-forkRelease.apk
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-arm64-v8a-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-arm64-v8a-forkRelease.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload armeabi apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
@ -105,11 +115,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/forkRelease/app-armeabi-v7a-forkRelease.apk
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-armeabi-v7a-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-armeabi-v7a-forkRelease.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload x86 apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
@ -117,11 +126,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/forkRelease/app-x86-forkRelease.apk
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-x86-forkRelease.apk
asset_content_type: application/vnd.android.package-archive
- name: Upload x86_64 apk
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
uses: actions/upload-release-asset@v1
@ -129,6 +137,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: app/build/outputs/apk/forkRelease/app-x86_64-forkRelease.apk
asset_path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86_64-forkRelease.apk
asset_name: ${{ github.event.ref }}-browser-x86_64-forkRelease.apk
asset_content_type: application/vnd.android.package-archive

@ -1,16 +0,0 @@
name: AssignTriageLabel
on:
issues:
types: [opened]
jobs:
assign:
name: Triage Issues
runs-on: ubuntu-latest
steps:
- name: Add Triage Label
uses: boek/AddTriageLabel@v1.2
with:
repotoken: ${{ secrets.GITHUB_TOKEN }}
labeltoadd: "needs:triage"

3
.gitignore vendored

@ -7,6 +7,7 @@
*.apk
*.ap_
*.aab
output-metadata.json
# Files for the ART/Dalvik VM
*.dex
@ -93,7 +94,7 @@ __pycache__/
venv/
# UI test artifacts
# UI test artifacts
.firebase_token*
results/
test_artifacts/

3
.gitmodules vendored

@ -0,0 +1,3 @@
[submodule "android-components"]
path = android-components
url = https://github.com/akliuxingyuan/android-components

@ -1,122 +0,0 @@
queue_rules:
- name: default
conditions:
- status-success=complete-pr
pull_request_rules:
- name: Resolve conflict
conditions:
- conflict
actions:
comment:
message: This pull request has conflicts when rebasing. Could you fix it @{{author}}? 🙏
- name: MickeyMoz - Auto Merge
conditions:
- author=MickeyMoz
- status-success=pr-complete
- files~=(Gecko.kt|AndroidComponents.kt)
actions:
review:
type: APPROVE
message: MickeyMoz 💪
queue:
method: rebase
name: default
rebase_fallback: none
- name: L10N - Auto Merge
conditions:
- author=mozilla-l10n-automation-bot
- status-success=pr-complete
- files~=(strings.xml|l10n.toml)
actions:
review:
type: APPROVE
message: LGTM 😎
queue:
method: rebase
name: default
rebase_fallback: none
- name: Release automation (Old)
conditions:
- base~=releases[_/].*
- author=github-actions[bot]
# Listing checks manually beause we do not have a "push complete" check yet.
- check-success=build-android-test-debug
- check-success=build-debug
- check-success=build-nightly-simulation
- check-success=lint-compare-locales
- check-success=lint-detekt
- check-success=lint-ktlint
- check-success=lint-lint
- check-success=signing-android-test-debug
- check-success=signing-debug
- check-success=signing-nightly-simulation
- check-success=test-debug
# TODO Temporarily disabled - should be renamed to -build
#- check-success=ui-test-x86-debug
- files~=(AndroidComponents.kt)
actions:
review:
type: APPROVE
message: 🚢
queue:
method: rebase
name: default
rebase_fallback: none
delete_head_branch:
force: false
- name: Release automation (New)
conditions:
- base~=releases[_/].*
- author=github-actions[bot]
# Listing checks manually beause we do not have a "push complete" check yet.
- check-success=build-android-test-beta
- check-success=build-android-test-debug
- check-success=build-beta-firebase
- check-success=build-debug
- check-success=build-nightly-simulation
- check-success=lint-compare-locales
- check-success=lint-detekt
- check-success=lint-ktlint
- check-success=lint-lint
- check-success=signing-android-test-beta
- check-success=signing-beta-firebase
- check-success=signing-nightly-simulation
- check-success=test-debug
- check-success=ui-test-x86-beta
- files~=(AndroidComponents.kt)
actions:
review:
type: APPROVE
message: 🚢
queue:
method: rebase
name: default
rebase_fallback: none
delete_head_branch:
force: false
- name: Needs landing - Rebase
conditions:
- check-success=pr-complete
- label=pr:needs-landing
- "#approved-reviews-by>=1"
- -draft
- label!=pr:work-in-progress
- label!=pr:do-not-land
actions:
queue:
method: rebase
name: default
rebase_fallback: none
- name: Needs landing - Squash
conditions:
- check-success=pr-complete
- label=pr:needs-landing-squashed
- "#approved-reviews-by>=1"
- -draft
- label!=pr:work-in-progress
- label!=pr:do-not-land
actions:
queue:
method: squash
name: default
rebase_fallback: none

@ -1,293 +0,0 @@
---
version: 1
reporting: checks-v1
policy:
# XXX We restrict taskcluster to collaborators so priviledged tests (like UI tests) can run on PRs
pullRequests: collaborators
tasks:
- $let:
trustDomain: mobile
# Github events have this stuff in different places...
ownerEmail:
$if: 'tasks_for in ["cron", "action"]'
then: '${tasks_for}@noreply.mozilla.org'
else:
$if: 'tasks_for == "github-push"'
then:
$if: 'event.pusher.email'
then: '${event.pusher.email}'
else: '${event.pusher.name}@users.noreply.github.com'
else:
$if: 'tasks_for == "github-pull-request"'
then: '${event.pull_request.user.login}@users.noreply.github.com'
baseRepoUrl:
$if: 'tasks_for == "github-push"'
then: '${event.repository.html_url}'
else:
$if: 'tasks_for == "github-pull-request"'
then: '${event.pull_request.base.repo.html_url}'
else:
$if: 'tasks_for in ["cron", "action"]'
then: '${repository.url}'
repoUrl:
$if: 'tasks_for == "github-push"'
then: '${event.repository.html_url}'
else:
$if: 'tasks_for == "github-pull-request"'
then: '${event.pull_request.head.repo.html_url}'
else:
$if: 'tasks_for in ["cron", "action"]'
then: '${repository.url}'
project:
$if: 'tasks_for == "github-push"'
then: '${event.repository.name}'
else:
$if: 'tasks_for == "github-pull-request"'
then: '${event.pull_request.head.repo.name}'
else:
$if: 'tasks_for in ["cron", "action"]'
then: '${repository.project}'
head_branch:
$if: 'tasks_for == "github-pull-request"'
then: ${event.pull_request.head.ref}
else:
$if: 'tasks_for == "github-push"'
then: ${event.ref}
else:
$if: 'tasks_for in ["action", "cron"]'
then: '${push.branch}'
head_sha:
$if: 'tasks_for == "github-push"'
then: '${event.after}'
else:
$if: 'tasks_for == "github-pull-request"'
then: '${event.pull_request.head.sha}'
else:
$if: 'tasks_for in ["action", "cron"]'
then: '${push.revision}'
ownTaskId:
$if: '"github" in tasks_for'
then: {$eval: as_slugid("decision_task")}
else:
$if: 'tasks_for in ["cron", "action"]'
then: '${ownTaskId}'
pullRequestAction:
$if: 'tasks_for == "github-pull-request"'
then: ${event.action}
else: 'UNDEFINED'
in:
$if: >
tasks_for in ["action", "cron"]
|| (tasks_for == "github-pull-request" && pullRequestAction in ["opened", "reopened", "synchronize"])
|| (tasks_for == "github-push" && head_branch[:10] != "refs/tags/") && (head_branch != "staging.tmp") && (head_branch != "trying.tmp") && (head_branch[:8] != "mergify/")
then:
$let:
level:
$if: 'tasks_for in ["github-push", "action", "cron"] && repoUrl == "https://github.com/mozilla-mobile/fenix"'
then: '3'
else: '1'
short_head_branch:
$if: 'head_branch[:11] == "refs/heads/"'
then: {$eval: 'head_branch[11:]'}
in:
taskId:
$if: 'tasks_for != "action"'
then: '${ownTaskId}'
taskGroupId:
$if: 'tasks_for == "action"'
then: '${action.taskGroupId}'
else: '${ownTaskId}' # same as taskId; this is how automation identifies a decision task
schedulerId: '${trustDomain}-level-${level}'
created: {$fromNow: ''}
deadline: {$fromNow: '1 day'}
expires: {$fromNow: '1 year 1 second'} # 1 second so artifacts expire first, despite rounding errors
metadata:
$merge:
- owner: "${ownerEmail}"
source: '${repoUrl}/raw/${head_sha}/.taskcluster.yml'
- $if: 'tasks_for in ["github-push", "github-pull-request"]'
then:
name: "Decision Task"
description: 'The task that creates all of the other tasks in the task graph'
else:
$if: 'tasks_for == "action"'
then:
name: "Action: ${action.title}"
description: |
${action.description}
Action triggered by clientID `${clientId}`
else:
name: "Decision Task for cron job ${cron.job_name}"
description: 'Created by a [cron task](https://firefox-ci-tc.services.mozilla.com/tasks/${cron.task_id})'
provisionerId: "${trustDomain}-${level}"
workerType: "decision-gcp"
tags:
$if: 'tasks_for in ["github-push", "github-pull-request"]'
then:
kind: decision-task
else:
$if: 'tasks_for == "action"'
then:
kind: 'action-callback'
else:
$if: 'tasks_for == "cron"'
then:
kind: cron-task
routes:
$flattenDeep:
- checks
- $if: 'level == "3" || repoUrl == "https://github.com/mozilla-releng/staging-fenix"'
then:
- tc-treeherder.v2.${project}.${head_sha}
# TODO Bug 1601928: Make this scope fork-friendly once ${project} is better defined. This will enable
# staging release promotion on forks.
- $if: 'tasks_for == "github-push"'
then:
- index.${trustDomain}.v2.${project}.branch.${short_head_branch}.latest.taskgraph.decision
- index.${trustDomain}.v2.${project}.branch.${short_head_branch}.revision.${head_sha}.taskgraph.decision
- index.${trustDomain}.v2.${project}.revision.${head_sha}.taskgraph.decision
- $if: 'tasks_for == "cron"'
then:
# cron context provides ${head_branch} as a short one
- index.${trustDomain}.v2.${project}.branch.${head_branch}.latest.taskgraph.decision-${cron.job_name}
- index.${trustDomain}.v2.${project}.branch.${head_branch}.revision.${head_sha}.taskgraph.decision-${cron.job_name}
- index.${trustDomain}.v2.${project}.branch.${head_branch}.revision.${head_sha}.taskgraph.cron.${ownTaskId}
scopes:
$if: 'tasks_for == "github-push"'
then:
# `https://` is 8 characters so, ${repoUrl[8:]} is the repository without the protocol.
- 'assume:repo:${repoUrl[8:]}:branch:${short_head_branch}'
else:
$if: 'tasks_for == "github-pull-request"'
then:
- 'assume:repo:github.com/${event.pull_request.base.repo.full_name}:pull-request'
else:
$if: 'tasks_for == "action"'
then:
# when all actions are hooks, we can calculate this directly rather than using a variable
- '${action.repo_scope}'
else:
- 'assume:repo:${repoUrl[8:]}:cron:${cron.job_name}'
requires: all-completed
priority: lowest
retries: 5
payload:
env:
# run-task uses these to check out the source; the inputs
# to `mach taskgraph decision` are all on the command line.
$merge:
- MOBILE_BASE_REPOSITORY: '${baseRepoUrl}'
MOBILE_HEAD_REPOSITORY: '${repoUrl}'
MOBILE_HEAD_REF: '${head_branch}'
MOBILE_HEAD_REV: '${head_sha}'
MOBILE_REPOSITORY_TYPE: git
MOBILE_PIP_REQUIREMENTS: taskcluster/requirements.txt
MOZ_AUTOMATION: "1"
REPOSITORIES: {$json: {mobile: "Fenix"}}
HG_STORE_PATH: /builds/worker/checkouts/hg-store
ANDROID_SDK_ROOT: /builds/worker/android-sdk
- $if: 'tasks_for in ["github-pull-request"]'
then:
MOBILE_PULL_REQUEST_NUMBER: '${event.pull_request.number}'
- $if: 'tasks_for == "action"'
then:
ACTION_TASK_GROUP_ID: '${action.taskGroupId}' # taskGroupId of the target task
ACTION_TASK_ID: {$json: {$eval: 'taskId'}} # taskId of the target task (JSON-encoded)
ACTION_INPUT: {$json: {$eval: 'input'}}
ACTION_CALLBACK: '${action.cb_name}'
features:
taskclusterProxy: true
chainOfTrust: true
# Note: This task is built server side without the context or tooling that
# exist in tree so we must hard code the hash
image: mozillareleases/taskgraph:decision-mobile-625975b642c148be4c6f1d8ee5cedf7399f5d0dd33d275ff69d5934e3082d4a9@sha256:bfb26700182486e1c6c52701baea6f386fa39e5e25417423c27845933605ad43
maxRunTime: 1800
command:
- /usr/local/bin/run-task
- '--mobile-checkout=/builds/worker/checkouts/src'
- '--task-cwd=/builds/worker/checkouts/src'
- '--'
- bash
- -cx
- $let:
extraArgs:
$if: 'tasks_for == "cron"'
then: '${cron.quoted_args}'
else: ''
in:
$if: 'tasks_for == "action"'
then: >
taskcluster/scripts/decision-install-sdk.sh &&
ln -s /builds/worker/artifacts artifacts &&
~/.local/bin/taskgraph action-callback
else: >
taskcluster/scripts/decision-install-sdk.sh &&
ln -s /builds/worker/artifacts artifacts &&
~/.local/bin/taskgraph decision
--pushlog-id='0'
--pushdate='0'
--project='${project}'
--message=""
--owner='${ownerEmail}'
--level='${level}'
--base-repository="$MOBILE_BASE_REPOSITORY"
--head-repository="$MOBILE_HEAD_REPOSITORY"
--head-ref="$MOBILE_HEAD_REF"
--head-rev="$MOBILE_HEAD_REV"
--repository-type="$MOBILE_REPOSITORY_TYPE"
--tasks-for='${tasks_for}'
${extraArgs}
artifacts:
'public':
type: 'directory'
path: '/builds/worker/artifacts'
expires:
$fromNow: '1 year'
'public/docker-contexts':
type: 'directory'
path: '/builds/worker/checkouts/src/docker-contexts'
# This needs to be at least the deadline of the
# decision task + the docker-image task deadlines.
# It is set to a week to allow for some time for
# debugging, but they are not useful long-term.
expires:
$fromNow: '7 day'
extra:
$merge:
- treeherder:
$merge:
- machine:
platform: gecko-decision
- $if: 'tasks_for in ["github-push", "github-pull-request"]'
then:
symbol: D
else:
$if: 'tasks_for == "action"'
then:
groupName: 'action-callback'
groupSymbol: AC
symbol: "${action.symbol}"
else:
groupSymbol: cron
symbol: "${cron.job_symbol}"
- $if: 'tasks_for == "action"'
then:
parent: '${action.taskGroupId}'
action:
name: '${action.name}'
context:
taskGroupId: '${action.taskGroupId}'
taskId: {$eval: 'taskId'}
input: {$eval: 'input'}
clientId: {$eval: 'clientId'}
- $if: 'tasks_for == "cron"'
then:
cron: {$json: {$eval: 'cron'}}
- tasks_for: '${tasks_for}'

@ -1,16 +0,0 @@
language: android
dist: trusty
script:
# Prepare SDK
- sudo mkdir -p /usr/local/android-sdk/licenses/
- sudo touch /usr/local/android-sdk/licenses/android-sdk-license
- echo "8933bad161af4178b1185d1a37fbf41ea5269c55" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license
- echo "d56f5187479451eabf01fb78af6dfcb131a6481e" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license
- echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license
# The build needs this but Gradle refuses to fetch it.
- sdkmanager "ndk;21.0.6113669"
# Run tests
- ./gradlew -q testDebug 2>&1
# Make sure a release build builds
- ./gradlew assembleForkRelease -PversionName="$(git describe --tags HEAD)"

@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

@ -1,4 +1,4 @@
# Iceraven Browser! [![Build Status](https://travis-ci.org/fork-maintainers/iceraven-browser.svg?branch=fork)](https://travis-ci.org/fork-maintainers/iceraven-browser) [![Release Automation](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/iceraven-build.yml/badge.svg)](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/iceraven-build.yml)
# Iceraven Browser! [![Release Automation](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/iceraven_build.yml/badge.svg)](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/iceraven_build.yml)
Definitely not brought to you by Mozilla!
@ -61,7 +61,7 @@ cd ..
2. Clone the project.
```sh
git clone https://github.com/fork-maintainers/iceraven-browser
git clone --recursive https://github.com/fork-maintainers/iceraven-browser
```
4. Go inside `iceraven-browser`. That's where the build is coordinated from.
@ -73,18 +73,18 @@ cd iceraven-browser
5. Configure the project. For your personal use you need to sign the apk file. The simplest way to do this is to use the debug key that is auto-generated by Android SDK. This is not a great idea for releasing, but acceptable for your personal use. You can configure it as follows:
```sh
echo "autosignReleaseWithDebugKey=" >>local.properties
echo "autosignReleaseWithDebugKey=" >> local.properties
```
6. Build the project. To build the Iceraven-branded release APKs, you can do:
```sh
./gradlew app:assembleForkRelease -PversionName="$(git describe --tags HEAD)"
./gradlew app:assemblefenixForkRelease -PversionName="$(git describe --tags HEAD)"
```
(If you don't use the `app:` prefix, you might get complaints about the build system being `unable to locate the objcopy executable`.)
The APKs will show up in `app/build/outputs/apk/forkRelease/`.
The APKs will show up in `app/build/outputs/apk/fenix/forkRelease/`.
## Getting Involved

@ -0,0 +1 @@
Subproject commit 557610ccc734506d89fab0e136fd2038913abb6c

@ -1,4 +1,12 @@
---
client-deduplication:
description: A feature to control the sending of the client-deduplication ping.
hasExposure: true
exposureDescription: ""
variables:
enabled:
type: boolean
description: "If true, the ping will be sent."
cookie-banners:
description: Features for cookie banner handling.
hasExposure: true
@ -60,6 +68,14 @@ mr2022:
sections-enabled:
type: json
description: This property provides a lookup table of whether or not the given section should be enabled.
nimbus-system:
description: "Configuration of the Nimbus System in Fenix.\n"
hasExposure: true
exposureDescription: ""
variables:
refresh-interval-foreground:
type: int
description: "The minimum interval in minutes between fetching experiment \nrecipes in the foreground.\n"
nimbus-validation:
description: A feature that does not correspond to an application feature suitable for showing that Nimbus is working. This should never be used in production.
hasExposure: true
@ -74,6 +90,14 @@ nimbus-validation:
settings-title:
type: string
description: The title of displayed in the Settings screen and app menu.
onboarding:
description: "A feature that configures the new user onboarding page. Note that onboarding is a **first run** feature, and should only be modified by first run experiments."
hasExposure: true
exposureDescription: ""
variables:
order:
type: json
description: Determines the order of the onboarding page panels
pre-permission-notification-prompt:
description: A feature that shows the pre-permission notification prompt.
hasExposure: true
@ -83,13 +107,16 @@ pre-permission-notification-prompt:
type: boolean
description: "if true, the pre-permission notification prompt is shown to the user."
re-engagement-notification:
description: A feature that shows the re-enagement notification if the user is inactive.
description: A feature that shows the re-engagement notification if the user is inactive.
hasExposure: true
exposureDescription: ""
variables:
enabled:
type: boolean
description: "If true, the re-engagement notification is shown to the inactive user."
type:
type: int
description: The type of re-engagement notification that is shown to the inactive user.
search-term-groups:
description: A feature allowing the grouping of URLs around the search term that it came from.
hasExposure: true

@ -33,6 +33,11 @@ android {
testBuildType project.property("testBuildType")
}
// This allows overriding the target activity for MozillaOnline builds, which happens
// as part of the defaultConfig below, and applies to all other configurations (Nightly,
// Beta, and Release.)
def targetActivity = "HomeActivity"
defaultConfig {
applicationId "io.github.forkmaintainers"
minSdkVersion Config.minSdkVersion
@ -66,17 +71,18 @@ android {
buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
def deepLinkSchemeValue = "fenix-dev"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
"deepLinkScheme": deepLinkSchemeValue,
"requestLegacyExternalStorage": true
]
// Build flag for "Mozilla Online" variants. See `Config.isMozillaOnline`.
if (project.hasProperty("mozillaOnline") || gradle.hasProperty("localProperties.mozillaOnline")) {
buildConfigField "boolean", "MOZILLA_ONLINE", "true"
targetActivity = "MozillaOnlineHomeActivity"
} else {
buildConfigField "boolean", "MOZILLA_ONLINE", "false"
}
manifestPlaceholders = [
"targetActivity": targetActivity,
"deepLinkScheme": deepLinkSchemeValue
]
}
def releaseTemplate = {
@ -107,19 +113,16 @@ android {
applicationIdSuffix ".fenix.debug"
resValue "bool", "IS_DEBUG", "true"
pseudoLocalesEnabled true
def deepLinkSchemeValue = "fenix-dev"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
"deepLinkScheme": deepLinkSchemeValue,
"requestLegacyExternalStorage": false
]
}
nightly releaseTemplate >> {
applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue, "requestLegacyExternalStorage": false]
manifestPlaceholders = [
"deepLinkScheme": deepLinkSchemeValue,
"targetActivity": targetActivity
]
}
beta releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
@ -136,7 +139,7 @@ android {
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
"requestLegacyExternalStorage": true
"targetActivity": targetActivity
]
}
release releaseTemplate >> {
@ -154,14 +157,13 @@ android {
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
"requestLegacyExternalStorage": true
"targetActivity": targetActivity,
]
}
forkDebug {
shrinkResources false
minifyEnabled false
applicationIdSuffix ".iceraven.debug"
resValue "bool", "IS_DEBUG", "true"
pseudoLocalesEnabled true
// Need to replicate default debug config features
signingConfig signingConfigs.debug
@ -171,6 +173,7 @@ android {
// Use custom default allowed addon list
buildConfigField "String", "AMO_COLLECTION_USER", "\"16201230\""
buildConfigField "String", "AMO_COLLECTION_NAME", "\"What-I-want-on-Fenix\""
resValue "bool", "IS_DEBUG", "true"
}
forkRelease releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
@ -178,6 +181,7 @@ android {
def deepLinkSchemeValue = "iceraven"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
"sharedUserId": "io.github.forkmaintainers.iceraven.sharedID",
"deepLinkScheme": deepLinkSchemeValue
]
// Use custom default allowed addon list
@ -190,7 +194,7 @@ android {
viewBinding true
}
aaptOptions {
androidResources {
// All JavaScript code used internally by GeckoView is packaged in a
// file called omni.ja. If this file is compressed in the APK,
// GeckoView must uncompress it before it can do anything else which
@ -210,6 +214,14 @@ android {
flavorDimensions "engine"
flavorDimensions "product"
productFlavors {
fenix {
dimension "product"
}
}
sourceSets {
androidTest {
resources.srcDirs += ['src/androidTest/resources']
@ -227,21 +239,21 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
lintOptions {
lint {
lintConfig file("lint.xml")
baseline file("lint-baseline.xml")
}
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
exclude 'META-INF/AL2.0'
exclude 'META-INF/LGPL2.1'
resources {
excludes += ['META-INF/atomicfu.kotlin_module', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
testOptions {
unitTests.returnDefaultValues = true
@ -264,6 +276,7 @@ android {
kotlinCompilerExtensionVersion = FenixVersions.androidx_compose_compiler
}
namespace 'org.mozilla.fenix'
}
android.applicationVariants.all { variant ->
@ -277,12 +290,11 @@ android.applicationVariants.all { variant ->
println("----------------------------------------------")
println("Variant name: " + variant.name)
println("Application ID: " + [variant.mergedFlavor.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
println("Application ID: " + [variant.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
println("Build type: " + variant.buildType.name)
println("Flavor: " + variant.flavorName)
println("Telemetry enabled: " + !isDebug)
if (useReleaseVersioning) {
// The Google Play Store does not allow multiple APKs for the same app that all have the
// same version code. Therefore we need to have different version codes for our ARM and x86
@ -435,7 +447,8 @@ android.applicationVariants.all { variant ->
print("Wallpaper URL: ")
try {
def token = new File("${rootDir}/.wallpaper_url").text.trim()
// def token = new File("${rootDir}/.wallpaper_url").text.trim()
def token = "https://assets.mozilla.net/mobile-wallpapers/android"
buildConfigField 'String', 'WALLPAPER_URL', '"' + token + '"'
println "(Added from .wallpaper_url file)"
} catch (FileNotFoundException ignored) {
@ -461,28 +474,28 @@ android.applicationVariants.all { variant ->
// Generate Kotlin code for the Fenix Glean metrics.
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
apply plugin: "org.mozilla.components.nimbus-gradle-plugin"
apply plugin: "org.mozilla.appservices.nimbus-gradle-plugin"
nimbus {
// The path to the Nimbus feature manifest file
manifestFile = "nimbus.fml.yaml"
// The fully qualified class name for the generated features.
// If the classname begins with a '.' this is taken as a suffix to the app's package name
destinationClass = ".nimbus.FxNimbus"
// Map from the variant name to the channel as experimenter and nimbus understand it.
// If nimbus's channels were accurately set up well for this project, then this
// shouldn't be needed.
channels = [
debug: "developer",
nightly: "nightly",
beta: "beta",
release: "release",
fenixDebug: "developer",
fenixNightly: "nightly",
fenixBeta: "beta",
fenixRelease: "release",
fenixForkRelease: "forkRelease"
]
// This is generated by the FML and should be checked into git.
// It will be fetched by Experimenter (the Nimbus experiment website)
// and used to inform experiment configuration.
experimenterManifest = ".experimenter.yaml"
}
configurations {
// There's an interaction between Gradle's resolution of dependencies with different types
// (@jar, @aar) for `implementation` and `testImplementation` and with Android Studio's built-in
@ -510,9 +523,8 @@ dependencies {
jnaForTest FenixDependencies.jna
testImplementation files(configurations.jnaForTest.copyRecursive().files)
implementation FenixDependencies.mozilla_browser_engine_gecko
implementation project(':browser-engine-gecko')
implementation FenixDependencies.kotlin_stdlib
implementation FenixDependencies.kotlin_coroutines
implementation FenixDependencies.kotlin_coroutines_android
testImplementation FenixDependencies.kotlin_coroutines_test
@ -520,96 +532,98 @@ dependencies {
implementation FenixDependencies.androidx_constraintlayout
implementation FenixDependencies.androidx_coordinatorlayout
implementation FenixDependencies.google_accompanist_drawablepainter
implementation FenixDependencies.google_accompanist_insets
implementation FenixDependencies.google_accompanist_pager
implementation FenixDependencies.sentry
implementation FenixDependencies.mozilla_compose_awesomebar
implementation FenixDependencies.mozilla_concept_awesomebar
implementation FenixDependencies.mozilla_concept_base
implementation FenixDependencies.mozilla_concept_engine
implementation FenixDependencies.mozilla_concept_menu
implementation FenixDependencies.mozilla_concept_push
implementation FenixDependencies.mozilla_concept_storage
implementation FenixDependencies.mozilla_concept_sync
implementation FenixDependencies.mozilla_concept_toolbar
implementation FenixDependencies.mozilla_concept_tabstray
implementation FenixDependencies.mozilla_browser_domains
implementation FenixDependencies.mozilla_browser_icons
implementation FenixDependencies.mozilla_browser_menu
implementation FenixDependencies.mozilla_browser_menu2
implementation FenixDependencies.mozilla_browser_session_storage
implementation FenixDependencies.mozilla_browser_state
implementation FenixDependencies.mozilla_browser_storage_sync
implementation FenixDependencies.mozilla_browser_tabstray
implementation FenixDependencies.mozilla_browser_thumbnails
implementation FenixDependencies.mozilla_browser_toolbar
implementation FenixDependencies.mozilla_feature_addons
implementation FenixDependencies.mozilla_feature_accounts
implementation FenixDependencies.mozilla_feature_app_links
implementation FenixDependencies.mozilla_feature_autofill
implementation FenixDependencies.mozilla_feature_awesomebar
implementation FenixDependencies.mozilla_feature_contextmenu
implementation FenixDependencies.mozilla_feature_customtabs
implementation FenixDependencies.mozilla_feature_downloads
implementation FenixDependencies.mozilla_feature_intent
implementation FenixDependencies.mozilla_feature_media
implementation FenixDependencies.mozilla_feature_prompts
implementation FenixDependencies.mozilla_feature_push
implementation FenixDependencies.mozilla_feature_privatemode
implementation FenixDependencies.mozilla_feature_pwa
implementation FenixDependencies.mozilla_feature_qr
implementation FenixDependencies.mozilla_feature_search
implementation FenixDependencies.mozilla_feature_session
implementation FenixDependencies.mozilla_feature_syncedtabs
implementation FenixDependencies.mozilla_feature_toolbar
implementation FenixDependencies.mozilla_feature_tabs
implementation FenixDependencies.mozilla_feature_findinpage
implementation FenixDependencies.mozilla_feature_logins
implementation FenixDependencies.mozilla_feature_site_permissions
implementation FenixDependencies.mozilla_feature_readerview
implementation FenixDependencies.mozilla_feature_tab_collections
implementation FenixDependencies.mozilla_feature_recentlyclosed
implementation FenixDependencies.mozilla_feature_top_sites
implementation FenixDependencies.mozilla_feature_share
implementation FenixDependencies.mozilla_feature_accounts_push
implementation FenixDependencies.mozilla_feature_webauthn
implementation FenixDependencies.mozilla_feature_webcompat
implementation FenixDependencies.mozilla_feature_webnotifications
implementation FenixDependencies.mozilla_feature_webcompat_reporter
implementation FenixDependencies.mozilla_service_pocket
implementation FenixDependencies.mozilla_service_contile
implementation FenixDependencies.mozilla_service_digitalassetlinks
implementation FenixDependencies.mozilla_service_sync_autofill
implementation FenixDependencies.mozilla_service_sync_logins
implementation FenixDependencies.mozilla_service_firefox_accounts
implementation(FenixDependencies.mozilla_service_glean)
implementation FenixDependencies.mozilla_service_location
implementation FenixDependencies.mozilla_service_nimbus
implementation FenixDependencies.mozilla_support_extensions
implementation FenixDependencies.mozilla_support_base
implementation FenixDependencies.mozilla_support_rusterrors
implementation FenixDependencies.mozilla_support_images
implementation FenixDependencies.mozilla_support_ktx
implementation FenixDependencies.mozilla_support_rustlog
implementation FenixDependencies.mozilla_support_utils
implementation FenixDependencies.mozilla_support_locale
implementation FenixDependencies.mozilla_ui_colors
implementation FenixDependencies.mozilla_ui_icons
implementation FenixDependencies.mozilla_lib_publicsuffixlist
implementation FenixDependencies.mozilla_ui_widgets
implementation FenixDependencies.mozilla_ui_tabcounter
implementation FenixDependencies.mozilla_lib_crash
implementation FenixDependencies.lib_crash_sentry
implementation FenixDependencies.mozilla_lib_state
implementation FenixDependencies.mozilla_lib_dataprotect
implementation project(':compose-awesomebar')
implementation project(':compose-cfr')
implementation project(':concept-awesomebar')
implementation project(':concept-base')
implementation project(':concept-engine')
implementation project(':concept-menu')
implementation project(':concept-push')
implementation project(':concept-storage')
implementation project(':concept-sync')
implementation project(':concept-toolbar')
implementation project(':concept-tabstray')
implementation project(':browser-domains')
implementation project(':browser-icons')
implementation project(':browser-menu')
implementation project(':browser-menu2')
implementation project(':browser-session-storage')
implementation project(':browser-state')
implementation project(':browser-storage-sync')
implementation project(':browser-tabstray')
implementation project(':browser-thumbnails')
implementation project(':browser-toolbar')
implementation project(':feature-addons')
implementation project(':feature-accounts')
implementation project(':feature-app-links')
implementation project(':feature-autofill')
implementation project(':feature-awesomebar')
implementation project(':feature-contextmenu')
implementation project(':feature-customtabs')
implementation project(':feature-downloads')
implementation project(':feature-intent')
implementation project(':feature-media')
implementation project(':feature-prompts')
implementation project(':feature-push')
implementation project(':feature-privatemode')
implementation project(':feature-pwa')
implementation project(':feature-qr')
implementation project(':feature-search')
implementation project(':feature-session')
implementation project(':feature-syncedtabs')
implementation project(':feature-toolbar')
implementation project(':feature-tabs')
implementation project(':feature-findinpage')
implementation project(':feature-logins')
implementation project(':feature-sitepermissions')
implementation project(':feature-readerview')
implementation project(':feature-tab-collections')
implementation project(':feature-recentlyclosed')
implementation project(':feature-top-sites')
implementation project(':feature-share')
implementation project(':feature-accounts-push')
implementation project(':feature-webauthn')
implementation project(':feature-webcompat')
implementation project(':feature-webnotifications')
implementation project(':feature-webcompat-reporter')
implementation project(':service-pocket')
implementation project(':service-contile')
implementation project(':service-digitalassetlinks')
implementation project(':service-sync-autofill')
implementation project(':service-sync-logins')
implementation project(':service-firefox-accounts')
implementation project(':service-glean')
implementation project(':service-location')
implementation project(':service-nimbus')
implementation project(':support-webextensions')
implementation project(':support-base')
implementation project(':support-rusterrors')
implementation project(':support-images')
implementation project(':support-ktx')
implementation project(':support-rustlog')
implementation project(':support-utils')
implementation project(':support-locale')
implementation project(':ui-colors')
implementation project(':ui-icons')
implementation project(':lib-publicsuffixlist')
implementation project(':ui-widgets')
implementation project(':ui-tabcounter')
implementation project(':lib-crash')
implementation project(':lib-crash-sentry')
implementation project(':lib-state')
implementation project(':lib-dataprotect')
debugImplementation FenixDependencies.leakcanary
implementation FenixDependencies.androidx_annotation
@ -641,7 +655,7 @@ dependencies {
androidTestImplementation FenixDependencies.uiautomator
androidTestImplementation "tools.fastlane:screengrab:2.0.0"
// This Falcon version is added to maven central now required for Screengrab
implementation 'com.jraska:falcon:2.2.0'
androidTestImplementation 'com.jraska:falcon:2.2.0'
androidTestImplementation FenixDependencies.androidx_compose_ui_test
@ -672,11 +686,12 @@ dependencies {
androidTestImplementation FenixDependencies.androidx_junit
androidTestImplementation FenixDependencies.androidx_test_extensions
androidTestImplementation FenixDependencies.androidx_tracing
androidTestImplementation FenixDependencies.androidx_work_testing
androidTestImplementation FenixDependencies.androidx_benchmark_junit4
androidTestImplementation FenixDependencies.mockwebserver
testImplementation FenixDependencies.mozilla_support_test
testImplementation FenixDependencies.mozilla_support_test_libstate
testImplementation project(':support-test')
testImplementation project(':support-test-libstate')
testImplementation FenixDependencies.androidx_junit
testImplementation FenixDependencies.androidx_test_extensions
testImplementation FenixDependencies.androidx_work_testing
@ -685,7 +700,7 @@ dependencies {
}
testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3'
implementation FenixDependencies.mozilla_support_rusthttp
implementation project(':support-rusthttp')
androidTestImplementation FenixDependencies.mockk_android
testImplementation FenixDependencies.mockk
@ -868,20 +883,6 @@ if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcd
apply from: "${topsrcdir}/substitute-local-geckoview.gradle"
}
def acSrcDir = null
if (gradle.hasProperty('localProperties.autoPublish.android-components.dir')) {
acSrcDir = gradle.getProperty('localProperties.autoPublish.android-components.dir')
} else if (gradle.hasProperty('localProperties.branchBuild.android-components.dir')) {
acSrcDir = gradle.getProperty('localProperties.branchBuild.android-components.dir')
}
if (acSrcDir) {
if (acSrcDir.startsWith("/")) {
apply from: "${acSrcDir}/substitute-local-ac.gradle"
} else {
apply from: "../${acSrcDir}/substitute-local-ac.gradle"
}
}
def appServicesSrcDir = null
if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
appServicesSrcDir = gradle.getProperty('localProperties.autoPublish.application-services.dir')
@ -901,26 +902,6 @@ if (gradle.hasProperty('localProperties.autoPublish.glean.dir')) {
apply from: "../${gleanSrcDir}/build-scripts/substitute-local-glean.gradle"
}
// Define a reusable task for updating the versions of our built-in web extensions. We automate this
// to make sure we never forget to update the version, either in local development or for releases.
// In both cases, we want to make sure the latest version of all extensions (including their latest
// changes) are installed on first start-up.
// We're using the A-C version here as we want to uplift all built-in extensions to A-C (Once that's
// done we can also remove the task below):
// https://github.com/mozilla-mobile/android-components/issues/7249
ext.updateExtensionVersion = { task, extDir ->
configure(task) {
from extDir
include 'manifest.template.json'
rename { 'manifest.json' }
into extDir
def values = ['version': AndroidComponents.VERSION + "." + new Date().format('MMddHHmmss')]
inputs.properties(values)
expand(values)
}
}
android.applicationVariants.all { variant ->
tasks.register("apkSize${variant.name.capitalize()}", ApkSizeTask) {
variantName = variant.name
@ -930,4 +911,4 @@ android.applicationVariants.all { variant ->
}
// Enable expiration by major version.
ext.gleanExpireByVersion = Config.majorVersion(project)
ext.gleanExpireByVersion = Config.majorVersion()

@ -0,0 +1,267 @@
---
features:
nimbus-system:
description: |
Configuration of the Nimbus System in Fenix.
variables:
refresh-interval-foreground:
description: |
The minimum interval in minutes between fetching experiment
recipes in the foreground.
type: Int
default: 60 # 1 hour
messaging:
description: |
Configuration for the messaging system.
In practice this is a set of growable lookup tables for the
message controller to piece together.
variables:
message-under-experiment:
description: Id or prefix of the message under experiment.
type: Option<String>
default: null
messages:
description: A growable collection of messages
type: Map<String, MessageData>
default: {}
triggers:
description: >
A collection of out the box trigger
expressions. Each entry maps to a
valid JEXL expression.
type: Map<String, String>
default: {}
styles:
description: >
A map of styles to configure message
appearance.
type: Map<String, StyleData>
default: {}
actions:
type: Map<String, String>
description: A growable map of action URLs.
default: {}
on-control:
type: ControlMessageBehavior
description: What should be displayed when a control message is selected.
default: show-next-message
notification-config:
description: Configuration of the notification worker for all notification messages.
type: NotificationConfig
default: {}
defaults:
- value:
triggers:
# Using attributes built into the Nimbus SDK
USER_RECENTLY_INSTALLED: days_since_install < 7
USER_RECENTLY_UPDATED: days_since_update < 7 && days_since_install != days_since_update
USER_TIER_ONE_COUNTRY: ('US' in locale || 'GB' in locale || 'CA' in locale || 'DE' in locale || 'FR' in locale)
USER_EN_SPEAKER: "'en' in locale"
USER_ES_SPEAKER: "'es' in locale"
USER_DE_SPEAKER: "'de' in locale"
USER_FR_SPEAKER: "'fr' in locale"
DEVICE_ANDROID: os == 'Android'
DEVICE_IOS: os == 'iOS'
ALWAYS: "true"
NEVER: "false"
DAY_1_AFTER_INSTALL: days_since_install == 1
DAY_2_AFTER_INSTALL: days_since_install == 2
DAY_3_AFTER_INSTALL: days_since_install == 3
DAY_4_AFTER_INSTALL: days_since_install == 4
DAY_5_AFTER_INSTALL: days_since_install == 5
# Using custom attributes for the browser
I_AM_DEFAULT_BROWSER: "is_default_browser"
I_AM_NOT_DEFAULT_BROWSER: "is_default_browser == false"
USER_ESTABLISHED_INSTALL: "number_of_app_launches >=4"
FUNNEL_PAID: "adjust_campaign != ''"
FUNNEL_ORGANIC: "adjust_campaign == ''"
# Using Glean events, specific to the browser
INACTIVE_1_DAY: "'app_launched'|eventLastSeen('Hours') >= 24"
INACTIVE_2_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 2"
INACTIVE_3_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 3"
INACTIVE_4_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 4"
INACTIVE_5_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 5"
# Has the user signed in the last 4 years
FXA_SIGNED_IN: "'sync_auth.sign_in'|eventLastSeen('Years', 0) <= 4"
FXA_NOT_SIGNED_IN: "'sync_auth.sign_in'|eventLastSeen('Years', 0) > 4"
# https://mozilla-hub.atlassian.net/wiki/spaces/FJT/pages/11469471/Core+Active
USER_INFREQUENT: "'app_launched'|eventCountNonZero('Days', 28) >= 1 && 'app_launched'|eventCountNonZero('Days', 28) < 7"
USER_CASUAL: "'app_launched'|eventCountNonZero('Days', 28) >= 7 && 'app_launched'|eventCountNonZero('Days', 28) < 14"
USER_REGULAR: "'app_launched'|eventCountNonZero('Days', 28) >= 14 && 'app_launched'|eventCountNonZero('Days', 28) < 21"
USER_CORE_ACTIVE: "'app_launched'|eventCountNonZero('Days', 28) >= 21"
LAUNCHED_ONCE_THIS_WEEK: "'app_launched'|eventSum('Days', 7) == 1"
actions:
ENABLE_PRIVATE_BROWSING: ://enable_private_browsing
INSTALL_SEARCH_WIDGET: ://install_search_widget
MAKE_DEFAULT_BROWSER: ://make_default_browser
VIEW_BOOKMARKS: ://urls_bookmarks
VIEW_COLLECTIONS: ://home_collections
VIEW_HISTORY: ://urls_history
VIEW_HOMESCREEN: ://home
OPEN_SETTINGS_ACCESSIBILITY: ://settings_accessibility
OPEN_SETTINGS_ADDON_MANAGER: ://settings_addon_manager
OPEN_SETTINGS_DELETE_BROWSING_DATA: ://settings_delete_browsing_data
OPEN_SETTINGS_LOGINS: ://settings_logins
OPEN_SETTINGS_NOTIFICATIONS: ://settings_notifications
OPEN_SETTINGS_PRIVACY: ://settings_privacy
OPEN_SETTINGS_SEARCH_ENGINE: ://settings_search_engine
OPEN_SETTINGS_TRACKING_PROTECTION: ://settings_tracking_protection
OPEN_SETTINGS_WALLPAPERS: ://settings_wallpapers
OPEN_SETTINGS: ://settings
TURN_ON_SYNC: ://turn_on_sync
styles:
DEFAULT:
priority: 50
max-display-count: 5
SURVEY:
priority: 55
max-display-count: 10
PERSISTENT:
priority: 50
max-display-count: 20
WARNING:
priority: 60
max-display-count: 10
URGENT:
priority: 100
max-display-count: 10
NOTIFICATION:
priority: 50
max-display-count: 1
messages:
default-browser:
text: default_browser_experiment_card_text
surface: homescreen
action: "MAKE_DEFAULT_BROWSER"
trigger: [ "I_AM_NOT_DEFAULT_BROWSER","USER_ESTABLISHED_INSTALL" ]
style: "PERSISTENT"
button-label: preferences_set_as_default_browser
default-browser-notification:
title: nimbus_notification_default_browser_title
text: nimbus_notification_default_browser_text
surface: notification
style: NOTIFICATION
trigger:
- I_AM_NOT_DEFAULT_BROWSER
- DAY_3_AFTER_INSTALL
action: MAKE_DEFAULT_BROWSER
- channel: developer
value:
styles:
DEFAULT:
priority: 50
max-display-count: 100
EXPIRES_QUICKLY:
priority: 100
max-display-count: 1
notification-config:
refresh-interval: 120 # minutes (2 hours)
objects:
MessageData:
description: >
An object to describe a message. It uses human
readable strings to describe the triggers, action and
style of the message as well as the text of the message
and call to action.
fields:
action:
type: Text
description: >
A URL of a page or a deeplink.
This may have substitution variables in.
# This should never be defaulted.
default: empty_string
title:
type: Option<Text>
description: "The title text displayed to the user"
default: null
text:
type: Text
description: "The message text displayed to the user"
# This should never be defaulted.
default: empty_string
is-control:
type: Boolean
description: "Indicates if this message is the control message, if true shouldn't be displayed"
default: false
button-label:
type: Option<Text>
description: >
The text on the button. If no text
is present, the whole message is clickable.
default: null
style:
type: String
description: >
The style as described in a
`StyleData` from the styles table.
default: DEFAULT
surface:
description:
The surface identifier for this message.
type: MessageSurfaceId
default: homescreen
trigger:
type: List<String>
description: >
A list of strings corresponding to
targeting expressions. The message will be
shown if all expressions `true`.
default: []
StyleData:
description: >
A group of properties (predominantly visual) to
describe the style of the message.
fields:
priority:
type: Int
description: >
The importance of this message.
0 is not very important, 100 is very important.
default: 50
max-display-count:
type: Int
description: >
How many sessions will this message be shown to the user
before it is expired.
default: 5
NotificationConfig:
description: Attributes controlling the global configuration of notification messages.
fields:
refresh-interval:
type: Int
description: >
How often, in minutes, the notification message worker will wake up and check for new
messages.
default: 240 # 4 hours
enums:
ControlMessageBehavior:
description: An enum to influence what should be displayed when a control message is selected.
variants:
show-next-message:
description: The next eligible message should be shown.
show-none:
description: The surface should show no message.
MessageSurfaceId:
description: The identity of a message surface
variants:
homescreen:
description: A banner in the homescreen.
notification:
description: A local notification in the background, like a push notification.

@ -184,33 +184,6 @@ events:
metadata:
tags:
- Toolbar
default_browser_notif_tapped:
type: event
description: |
User tapped on the default browser notification
bugs:
- https://github.com/mozilla-mobile/fenix/issues/19847
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/20311
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
expires: never
default_browser_notif_shown:
type: event
description: |
Default browser notification was shown to the user
bugs:
- https://github.com/mozilla-mobile/fenix/issues/27779
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27780
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 122
re_engagement_notif_tapped:
type: event
description: |
@ -285,6 +258,7 @@ events:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/17089
data_reviews:
@ -502,11 +476,12 @@ events:
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/19959#issuecomment-882539619
- https://github.com/mozilla-mobile/fenix/pull/24409
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410276888
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 113
expires: 118
save_to_pdf_tapped:
type: event
description: |
@ -540,6 +515,59 @@ events:
metadata:
tags:
- Sharing
share_to_app:
type: event
description: |
A user selected an app to share to from the share sheet.
extra_keys:
app_package:
description: |
Allowed package identifier for app or action (ex. 'org.mozilla.focus'
or 'com.android.bluetooth') the user selected. Any package identifiers
not in list are recorded as 'other'.
Currently allowed packages are:
'com.google.android.gm', 'com.microsoft.office.outlook',
'com.samsung.android.email.provider', 'com.facebook.katana',
'com.instagram.android', 'com.snapchat.android',
'com.google.android.keep', 'com.samsung.android.app.notes',
'com.microsoft.office.onenote', 'com.evernote',
'com.google.android.apps.messaging', 'com.facebook.orca',
'com.chating.messages.chat.fun', 'org.telegram.messenger',
'org.thoughtcrime.securesms', 'com.google.android.apps.dynamite',
'com.whatsapp', 'com.tencent.mm', 'com.Slack', 'com.discord',
'com.android.bluetooth', 'com.google.android.gms',
'org.mozilla.fenix.COPY_LINK_TO_CLIPBOARD
type: string
bugs:
- https://github.com/mozilla-mobile/fenix/issues/28033
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28118#issuecomment-1341620815
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: never
metadata:
tags:
- Sharing
form_data_failure:
type: event
description: |
Form data retrieval from a tab failed.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/28280
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/28280#issuecomment-1376040252
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- technical
expires: 121
metadata:
tags:
- Performance
onboarding:
syn_cfr_shown:
@ -1006,24 +1034,6 @@ unified_search:
- Search
- Shortcuts
experiments_default_browser:
toolbar_menu_clicked:
type: event
description: |
Set default browser was clicked from toolbar menu
bugs:
- https://github.com/mozilla-mobile/fenix/issues/18851
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/18982#pullrequestreview-635098629
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/21076#issuecomment-909237275
- https://github.com/mozilla-mobile/fenix/pull/23786#issuecomment-1042331298
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
toolbar_settings:
changed_position:
type: event
@ -1450,6 +1460,7 @@ metrics:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/1158
- https://github.com/mozilla-mobile/fenix/issues/6556
@ -1745,6 +1756,7 @@ metrics:
please see `has_open_tabs`.
send_in_pings:
- metrics
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/11479
data_reviews:
@ -1877,11 +1889,12 @@ metrics:
- https://github.com/mozilla-mobile/fenix/issues/25538
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/25539#issuecomment-1163393066
- https://github.com/mozilla-mobile/firefox-android/pull/1101
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
expires: 126
metadata:
tags:
- Wallpapers
@ -2028,12 +2041,14 @@ customize_home:
- https://github.com/mozilla-mobile/fenix/issues/24467
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24468
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410642835
data_sensitivity:
- interaction
lifetime: application
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
preference_toggled:
type: event
description: |
@ -2464,20 +2479,17 @@ preferences:
tags:
- Settings
open_links_in_app_enabled:
type: boolean
type: string
description: |
Whether or not the user has the open links in apps feature enabled.
default: false
The user has the open links in apps feature enabled.
"ask_before_opening", "always" or "never".
default: "never"
send_in_pings:
- metrics
bugs:
- https://github.com/mozilla-mobile/fenix/issues/11118
- https://bugzilla.mozilla.org/show_bug.cgi?id=1818085
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11446
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/23453#issuecomment-1024694220
- https://github.com/mozilla-mobile/firefox-android/pull/1054
data_sensitivity:
- interaction
notification_emails:
@ -2542,6 +2554,7 @@ search.default_engine:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/800
data_reviews:
@ -2571,6 +2584,7 @@ search.default_engine:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/800
data_reviews:
@ -3156,6 +3170,8 @@ sync_auth:
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
send_in_pings:
- client-deduplication
expires: never
metadata:
tags:
@ -3692,11 +3708,12 @@ history:
- https://github.com/mozilla-mobile/fenix/issues/23694
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23695
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410276888
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
expires: 118
search_result_tapped:
type: event
description: |
@ -3705,11 +3722,12 @@ history:
- https://github.com/mozilla-mobile/fenix/issues/23694
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23695
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410276888
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
expires: 118
recently_closed_tabs:
opened:
@ -4550,11 +4568,13 @@ tabs_tray:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: never
collections:
@ -4876,6 +4896,23 @@ collections:
metadata:
tags:
- Collections
placeholder_cancel:
type: event
description: |
User interacted with the `X` button from the homescreen no collections
placeholder.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1821032
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/1167
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 122
metadata:
tags:
- Collections
search_widget:
new_tab_button:
@ -5844,11 +5881,13 @@ top_sites:
- https://github.com/mozilla-mobile/fenix/issues/23893
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23945
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410663549
data_sensitivity:
- highly_sensitive
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Shortcuts
@ -5863,12 +5902,14 @@ top_sites:
- https://github.com/mozilla-mobile/fenix/issues/23893
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23945
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410663549
data_sensitivity:
- technical
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
unit: integer
metadata:
tags:
@ -5884,12 +5925,14 @@ top_sites:
- https://github.com/mozilla-mobile/fenix/issues/23893
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23945
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410663549
data_sensitivity:
- technical
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Shortcuts
@ -5905,12 +5948,14 @@ top_sites:
- https://github.com/mozilla-mobile/fenix/issues/23893
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23945
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410663549
data_sensitivity:
- technical
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Shortcuts
@ -5932,11 +5977,13 @@ top_sites:
- https://github.com/mozilla-mobile/fenix/issues/23893
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23945
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410663549
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Shortcuts
@ -5958,11 +6005,13 @@ top_sites:
- https://github.com/mozilla-mobile/fenix/issues/23893
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23945
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410663549
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Shortcuts
@ -6092,6 +6141,14 @@ pocket:
description: |
User tapped a Pocket sponsored story to be opened.
extra_keys:
spoc_id:
type: string
description: |
Id of the shown sponsored story.
Until version 112 this will be the `flight_id` allowing to
identify stories across different campaigns.
From version 112 this will be the story `id` identifying a
specific story irrespective of the campaign it is part of.
times_shown:
type: string
description: |
@ -6103,7 +6160,9 @@ pocket:
Uses the [row x column] matrix notation.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/25401
- https://bugzilla.mozilla.org/show_bug.cgi?id=1815160
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28796#issuecomment-1422818844
- https://github.com/mozilla-mobile/fenix/pull/25418#issuecomment-1163390855
- https://github.com/mozilla-mobile/fenix/pull/26184#issuecomment-1194744884
data_sensitivity:
@ -6142,6 +6201,14 @@ pocket:
A particular Pocket sponsored story was visible more than 50%
on the homescreen.
extra_keys:
spoc_id:
type: string
description: |
Id of the shown sponsored story.
Until version 112 this will be the `flight_id` allowing to
identify stories across different campaigns.
From version 112 this will be the story `id` identifying a
specific story irrespective of the campaign it is part of.
times_shown:
type: string
description: |
@ -6153,7 +6220,9 @@ pocket:
Uses the [row x column] matrix notation.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/25401
- https://bugzilla.mozilla.org/show_bug.cgi?id=1815160
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28796#issuecomment-1422818844
- https://github.com/mozilla-mobile/fenix/pull/25418#issuecomment-1163390855
- https://github.com/mozilla-mobile/fenix/pull/26184#issuecomment-1194744884
data_sensitivity:
@ -6388,6 +6457,7 @@ browser.search:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/6558
- https://github.com/mozilla-mobile/fenix/issues/28010
@ -6422,6 +6492,7 @@ browser.search:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/6558
- https://github.com/mozilla-mobile/fenix/issues/28010
@ -6447,6 +6518,7 @@ browser.search:
send_in_pings:
- metrics
- baseline
- client-deduplication
bugs:
- https://github.com/mozilla-mobile/fenix/issues/6557
data_reviews:
@ -7330,11 +7402,12 @@ progressive_web_app:
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/21076#issuecomment-909237275
- https://github.com/mozilla-mobile/fenix/pull/23783#issuecomment-1041863879
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410276888
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
expires: 118
metadata:
tags:
- PWA
@ -7350,11 +7423,29 @@ progressive_web_app:
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/21076#issuecomment-909237275
- https://github.com/mozilla-mobile/fenix/pull/23783#issuecomment-1041863879
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410276888
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 118
metadata:
tags:
- PWA
onboarding_cancel:
type: event
description: |
User interacts with the onboarding PWA dialog cancel button which takes
them to the website.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1821030
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/1164
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
expires: 122
metadata:
tags:
- PWA
@ -7555,26 +7646,33 @@ engine_tab:
description: |
Number of active tabs (with an engine session assigned) when the app
went to the background.
type: string
background_crashed_tabs:
description: |
Number of tabs marked as crashed when the app went to the background.
type: string
background_total_tabs:
description: |
Number of total tabs when the app went to the background.
type: string
foreground_active_tabs:
description: |
Number of active tabs (with an engine session assigned) when the
app came back to the foreground.
type: string
foreground_crashed_tabs:
description: |
Number of tabs marked as crashed when the app came back to the
foreground.
type: string
foreground_total_tabs:
description: |
Number of total tabs when the app came back to the foreground.
type: string
time_in_background:
description: |
Time (in milliseconds) the app was in the background.
type: string
metadata:
tags:
- Performance
@ -7722,6 +7820,23 @@ awesomebar:
metadata:
tags:
- Search
search_term_suggestion_clicked:
type: event
description: |
The search term suggestion in awesomebar was clicked.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/25816
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27810#issuecomment-1380720028
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Search
android_autofill:
supported:
type: boolean
@ -7892,11 +8007,13 @@ home_menu:
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/21076#issuecomment-909237275
- https://github.com/mozilla-mobile/fenix/pull/23786#issuecomment-1042331298
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410675626
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- Settings
@ -7913,11 +8030,13 @@ home_screen:
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/21076#issuecomment-909237275
- https://github.com/mozilla-mobile/fenix/pull/23786#issuecomment-1042331298
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410681521
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- HomeScreen
@ -7959,11 +8078,13 @@ start_on_home:
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/19885
- https://github.com/mozilla-mobile/fenix/pull/24982
- https://github.com/mozilla-mobile/firefox-android/pull/1101
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- HomeScreen
@ -7976,11 +8097,13 @@ start_on_home:
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/19885
- https://github.com/mozilla-mobile/fenix/pull/24982
- https://github.com/mozilla-mobile/firefox-android/pull/1101
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- HomeScreen
@ -7995,11 +8118,12 @@ recent_tabs:
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/20138
- https://github.com/mozilla-mobile/fenix/pull/24982
- https://github.com/mozilla-mobile/firefox-android/pull/1101
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
expires: 126
metadata:
tags:
- RecentTabs
@ -8012,28 +8136,13 @@ recent_tabs:
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/20138
- https://github.com/mozilla-mobile/fenix/pull/24982
- https://github.com/mozilla-mobile/firefox-android/pull/1101
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
metadata:
tags:
- RecentTabs
in_progress_media_tab_opened:
type: event
description: |
User has opened a recent media tab from homescreen.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/20393
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/20138
- https://github.com/mozilla-mobile/fenix/pull/24982
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
- kbrosnan@mozilla.com
expires: never
metadata:
tags:
- RecentTabs
@ -8178,11 +8287,12 @@ recent_searches:
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/22176#issuecomment-956421788
- https://github.com/mozilla-mobile/fenix/pull/23786#issuecomment-1042331298
- https://github.com/mozilla-mobile/fenix/pull/28709#issuecomment-1410276888
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 112
expires: 118
credit_cards:
saved:
@ -8595,12 +8705,14 @@ messaging:
bugs:
- https://github.com/mozilla-mobile/fenix/issues/24224
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/24224
- https://github.com/mozilla-mobile/fenix/pull/24426
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: never
message_dismissed:
type: event
description: |
@ -8613,11 +8725,13 @@ messaging:
- https://github.com/mozilla-mobile/fenix/issues/24224
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/24224
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: never
message_clicked:
type: event
description: |
@ -8633,11 +8747,13 @@ messaging:
- https://github.com/mozilla-mobile/fenix/issues/24224
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/24224
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: never
message_expired:
type: event
description: |
@ -8650,11 +8766,13 @@ messaging:
- https://github.com/mozilla-mobile/fenix/issues/24224
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/24224
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: never
malformed:
type: event
description: |
@ -8667,11 +8785,13 @@ messaging:
- https://github.com/mozilla-mobile/fenix/issues/24224
data_reviews:
- https://github.com/mozilla-mobile/fenix/issues/24224
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
- kbrosnan@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: never
wallpapers:
wallpaper_settings_opened:
@ -8682,11 +8802,12 @@ wallpapers:
- https://github.com/mozilla-mobile/fenix/issues/23381
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23382
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
no_lint:
- COMMON_PREFIX
metadata:
@ -8808,11 +8929,12 @@ recently_visited_homepage:
- https://github.com/mozilla-mobile/fenix/issues/23821
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23909
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
search_group_opened:
type: event
description: |
@ -8821,11 +8943,12 @@ recently_visited_homepage:
- https://github.com/mozilla-mobile/fenix/issues/23821
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/23909
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
recent_synced_tabs:
recent_synced_tab_shown:
@ -8837,11 +8960,12 @@ recent_synced_tabs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
recent_synced_tab_time_to_load:
type: timing_distribution
time_unit: millisecond
@ -8851,11 +8975,12 @@ recent_synced_tabs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
- https://github.com/mozilla-mobile/firefox-android/pull/1101
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 114
expires: 126
recent_synced_tab_opened:
type: labeled_counter
description: |
@ -8865,11 +8990,12 @@ recent_synced_tabs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
show_all_synced_tabs_clicked:
type: counter
description: |
@ -8878,11 +9004,12 @@ recent_synced_tabs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
latest_synced_tab_is_stale:
type: counter
description: |
@ -8892,11 +9019,12 @@ recent_synced_tabs:
- https://github.com/mozilla-mobile/fenix/issues/24549
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/24671
- https://github.com/mozilla-mobile/firefox-android/pull/1101
notification_emails:
- android-probes@mozilla.com
data_sensitivity:
- interaction
expires: 114
expires: 126
review_prompt:
prompt_attempt:
type: event
@ -8925,3 +9053,79 @@ review_prompt:
data_sensitivity:
- interaction
expires: 121
client_deduplication:
valid_advertising_id:
type: boolean
description: |
Whether or not we get a valid advertising ID from the device.
send_in_pings:
- client-deduplication
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1817029
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1813195#c11
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- fbertsch@mozilla.com
expires: 122
experiment_timeframe:
type: string
description: |
A string we use to identify which run of the experiment this is.
send_in_pings:
- client-deduplication
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1817029
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1813195#c11
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- fbertsch@mozilla.com
expires: 122
hashed_gaid:
type: string
lifetime: ping
description: |
A hashed and salted version of the Google Advertising ID from the device.
send_in_pings:
- client-deduplication
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1817029
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1813195#c11
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- fbertsch@mozilla.com
expires: 122
private_browsing_shortcut_cfr:
add_shortcut:
type: event
description: |
Pivate browsing CFR "Add shortcut" button pressed.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/4658
- https://bugzilla.mozilla.org/show_bug.cgi?id=1819909
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/1139
notification_emails:
- android-probes@mozilla.com
expires: 122
cancel:
type: event
description: |
Pivate browsing CFR "No thanks" button pressed.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/4658
- https://bugzilla.mozilla.org/show_bug.cgi?id=1819909
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/1139
notification_emails:
- android-probes@mozilla.com
expires: 122

@ -0,0 +1,272 @@
---
about:
description: Nimbus Feature Manifest for Fenix (Firefox Android)
kotlin:
package: org.mozilla.fenix
class: .nimbus.FxNimbus
channels:
- release
- beta
- nightly
- developer
- forkRelease
includes:
- messaging.fml.yaml
features:
homescreen:
description: The homescreen that the user goes to when they press home or new tab.
variables:
sections-enabled:
description: "This property provides a lookup table of whether or not the given section should be enabled.
If the section is enabled, it should be toggleable in the settings screen, and on by default."
type: Map<HomeScreenSection, Boolean>
default:
{
"top-sites": true,
"jump-back-in": true,
"recently-saved": true,
"recent-explorations": true,
"pocket": true,
"pocket-sponsored-stories": true,
}
defaults:
- channel: nightly
value: {
"sections-enabled": {
"top-sites": true,
"jump-back-in": true,
"recently-saved": true,
"recent-explorations": true,
"pocket": true,
}
}
nimbus-validation:
description: "A feature that does not correspond to an application feature suitable for showing
that Nimbus is working. This should never be used in production."
variables:
settings-title:
description: The title of displayed in the Settings screen and app menu.
type: Text
default: browser_menu_settings
settings-punctuation:
description: The emoji displayed in the Settings screen title.
type: String
default: ""
settings-icon:
description: The drawable displayed in the app menu for Settings
type: String
default: mozac_ic_settings
search-term-groups:
description: A feature allowing the grouping of URLs around the search term that it came from.
variables:
enabled:
description: If true, the feature shows up on the homescreen and on the new tab screen.
type: Boolean
default: false
defaults:
- channel: nightly
value:
enabled: true
- channel: developer
value:
enabled: true
mr2022:
description: Features for MR 2022.
variables:
sections-enabled:
description: "This property provides a lookup table of whether or not the given section should be enabled."
type: Map<MR2022Section, Boolean>
default:
{
"home-onboarding-dialog-existing-users": true,
"sync-cfr": true,
"wallpapers-selection-tool": true,
"jump-back-in-cfr": true,
"tcp-cfr": true,
"tcp-feature": true,
}
defaults:
- channel: developer
value: {
"sections-enabled": {
"home-onboarding-dialog-existing-users": true,
"sync-cfr": true,
"wallpapers-selection-tool": true,
"jump-back-in-cfr": true,
}
}
cookie-banners:
description: Features for cookie banner handling.
variables:
sections-enabled:
description: "This property provides a lookup table of whether or not the given section should be enabled."
type: Map<CookieBannersSection, Int>
default:
{
"feature-ui": 0,
"feature-setting-value": 0,
"dialog-re-engage-time": 4
}
defaults:
- channel: developer
value: {
"sections-enabled": {
"feature-ui": 1,
"feature-setting-value": 0,
"dialog-re-engage-time": 4
}
}
- channel: nightly
value: {
"sections-enabled": {
"feature-ui": 1,
"feature-setting-value": 0,
"dialog-re-engage-time": 4
}
}
unified-search:
description: A feature allowing user to easily search for specified results directly in the search bar.
variables:
enabled:
description: If true, the feature shows up in the search bar.
type: Boolean
default: false
defaults:
- channel: nightly
value:
enabled: false
client-deduplication:
description: A feature to control the sending of the client-deduplication ping.
variables:
enabled:
description: If true, the ping will be sent.
type: Boolean
default: false
defaults:
- channel: nightly
value:
enabled: false
- channel: developer
value:
enabled: false
client-deduplication:
description: A feature to control the sending of the client-deduplication ping.
variables:
enabled:
description: If true, the ping will be sent.
type: Boolean
default: false
defaults:
- channel: nightly
value:
enabled: false
- channel: developer
value:
enabled: false
growth-data:
description: A feature measuring campaign growth data
variables:
enabled:
description: If true, the feature is active
type: Boolean
default: false
defaults:
- channel: release
value:
enabled: true
re-engagement-notification:
description: A feature that shows the re-engagement notification if the user is inactive.
variables:
enabled:
description: If true, the re-engagement notification is shown to the inactive user.
type: Boolean
default: false
type:
description: The type of re-engagement notification that is shown to the inactive user.
type: Int
default: 0
pre-permission-notification-prompt:
description: A feature that shows the pre-permission notification prompt.
variables:
enabled:
description: if true, the pre-permission notification prompt is shown to the user.
type: Boolean
default: true
onboarding:
description: "A feature that configures the new user onboarding page.
Note that onboarding is a **first run** feature, and should only be modified by first run experiments."
variables:
order:
description: Determines the order of the onboarding page panels
type: List<OnboardingPanel>
default: ["themes", "toolbar-placement", "sync", "tcp", "privacy-notice"]
types:
objects: {}
enums:
HomeScreenSection:
description: The identifiers for the sections of the homescreen.
variants:
top-sites:
description: The frecency and pinned sites.
recently-saved:
description: The sites the user has bookmarked recently.
jump-back-in:
description: The tabs the user was looking immediately before being interrupted.
recent-explorations:
description: The tab groups
pocket:
description: The pocket section. This should only be available in the US.
pocket-sponsored-stories:
description: Subsection of the Pocket homescreen section which shows sponsored stories.
MR2022Section:
description: The identifiers for the sections of the MR 2022.
variants:
home-onboarding-dialog-existing-users:
description: Home onboarding dialog for upgraded users.
sync-cfr:
description: CFR for the first time you see a synced tab on the home screen.
wallpapers-selection-tool:
description: Wallpapers selection dialog tool for the home screen.
jump-back-in-cfr:
description: Jump back-in onboarding message.
tcp-cfr:
description: CFR for the first time you use the browse with Total Cookie Protection on the browser screen.
tcp-feature:
description: Controls the Total Cookie Protection feature.
CookieBannersSection:
description: The identifiers for the sections of the MR 2022.
variants:
feature-ui:
description: An integer either 0 or 1 indicating if the UI for cookie banner handling should be visible,
0 to hide the UI and 1 to show the UI. The actual UI is composed by cookie banner section
in the settings page, the toolbar section and the re-engagement dialog.
feature-setting-value:
description: An integer either 0 or 1 indicating if cookie banner setting should be enabled or disabled,
0 for setting the value to disabled, 1 for enabling the setting with the value reject_all.
dialog-re-engage-time:
description: An integer indicating the number of hours that needs to happen before
the re-engagement dialog shows again since the last seen, for example if set to 4
that means if the users has seen the dialog, it will see it 4 hours later.
OnboardingPanel:
description: The types of onboarding panels in the onboarding page
variants:
themes:
description: The themes onboarding panel where users pick themes
toolbar-placement:
description: The onboarding panel where users choose their toolbar placement (bottom or top)
sync:
description: The onboarding panel where users can sign in to sync
tcp:
description: The onboarding panel where users can choose their total cookie protection settings
privacy-notice:
description: The onboarding panel where users can tap to view our privacy notice.

@ -62,3 +62,21 @@ spoc:
- https://github.com/mozilla-mobile/fenix/pull/27550#issuecomment-1295027631
notification_emails:
- android-probes@mozilla.com
client-deduplication:
description: |
Contains data to help identify if client IDs are being regenerated
erroneously.
include_client_id: true
reasons:
active: |
The ping is being sent when the app is coming to the foreground.
inactive: |
The ping is being sent when the app is going to the background.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1817029
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1813195#c11
notification_emails:
- android-probes@mozilla.com
- fbertsch@mozilla.com

@ -1,26 +1,5 @@
-dontobfuscate
####################################################################################################
# Sentry
####################################################################################################
# Recommended config via https://docs.sentry.io/clients/java/modules/android/#manual-integration
# Since we don't obfuscate, we don't need to use their Gradle plugin to upload ProGuard mappings.
-keepattributes LineNumberTable,SourceFile
-dontwarn org.slf4j.**
-dontwarn javax.**
# Our addition: this class is saved to disk via Serializable, which ProGuard doesn't like.
# If we exclude this, upload silently fails (Sentry swallows a NPE so we don't crash).
# I filed https://github.com/getsentry/sentry-java/issues/572
#
# If Sentry ever mysteriously stops working after we upgrade it, this could be why.
-keep class io.sentry.event.Event { *; }
# Iceraven release build mysteriously cannot find a lot of Sentry.
# Luckily we do not really want it anyway.
-dontwarn io.sentry.**
####################################################################################################
# Android and GeckoView built-ins
####################################################################################################
@ -73,58 +52,6 @@
-keep class org.mozilla.fenix.**ViewModel { *; }
####################################################################################################
# Adjust
####################################################################################################
-keep public class com.adjust.sdk.** { *; }
-keep class com.google.android.gms.common.ConnectionResult {
int SUCCESS;
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
java.lang.String getId();
boolean isLimitAdTrackingEnabled();
}
-keep public class com.android.installreferrer.** { *; }
-keep class dalvik.system.VMRuntime {
java.lang.String getRuntime();
}
-keep class android.os.Build {
java.lang.String[] SUPPORTED_ABIS;
java.lang.String CPU_ABI;
}
-keep class android.content.res.Configuration {
android.os.LocaledList getLocales();
java.util.Locale locale;
}
-keep class android.os.LocaleList {
java.util.Locale get(int);
}
# Keep code generated from Glean Metrics
-keep class org.mozilla.fenix.GleanMetrics.** { *; }
# Keep motionlayout internal methods
# https://github.com/mozilla-mobile/fenix/issues/2094
-keep class androidx.constraintlayout.** { *; }
# Keep adjust relevant classes
-keep class com.adjust.sdk.** { *; }
-keep class com.google.android.gms.common.ConnectionResult {
int SUCCESS;
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
java.lang.String getId();
boolean isLimitAdTrackingEnabled();
}
-keep public class com.android.installreferrer.** { *; }
# Keep Android Lifecycle methods
# https://bugzilla.mozilla.org/show_bug.cgi?id=1596302
-keep class androidx.lifecycle.** { *; }

@ -5,12 +5,13 @@
</head>
<body>
<form>
<p>Street Address: <input id="streetAddress" type="text"></p>
<p>City: <input id="city" type="text"></p>
<p>Zip Code: <input id="zipCode" type="text"></p>
<p>Country: <input id="country" type="text"></p>
<p>Telephone: <input id="telephone" type="text"></p>
<p>Email: <input id="email" type="text"></p>
<p>Street Address: <input id="streetAddress" type="text"></p>
<p>City: <input id="city" type="text"></p>
<p>Zip Code: <input id="zipCode" type="text"></p>
<p>Country: <input id="country" type="text"></p>
<p>Telephone: <input id="telephone" type="text"></p>
<p>Email: <input id="email" type="text"></p>
<p>Apartment, suite, etc. <input id="apartment" type="text"></p>
</form>
</body>
</html>
</html>

@ -5,9 +5,13 @@
</head>
<body>
<form>
<p>Card information</p>
<p>Card Number: <input id="cardNumber" type="text" placeholder="1234 1234 1234 1234"></p>
<p>Name on card: <input id="nameOnCard"type="text" placeholder="Name on card"></p>
<p>Card information</p>
<p>Card Number: <input id="cardNumber" type="text" placeholder="1234 1234 1234 1234"></p>
<p>Name on card: <input id="nameOnCard"type="text" placeholder="Name on card"></p>
<p> Expiry date:
<input id="expiryMonthAndYear" inputmode="numerical" placeholder="MM / YYYY" type="text" />
</p>
<p><input type="submit" id="submit" value="Submit" aria-label="submit"/></p>
</form>
</body>
</html>
</html>

@ -23,7 +23,9 @@ import java.lang.ref.WeakReference
* deactivate the FxA web channel
* which is not supported on the staging servers.
*/
class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
class AppRequestInterceptor(
private val context: Context,
) : RequestInterceptor {
private var navController: WeakReference<NavController>? = null
@ -86,6 +88,8 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
errorType = improvedErrorType,
uri = uri,
htmlResource = riskLevel.htmlRes,
titleOverride = { type -> getErrorPageTitle(context, type) },
descriptionOverride = { type -> getErrorPageDescription(context, type) },
)
return RequestInterceptor.ErrorResponse(errorPageUri)
@ -125,6 +129,8 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
return null
}
// This method is the only difference from the production code.
// Otherwise the code should be kept identical
@Suppress("LongParameterList")
private fun interceptFxaRequest(
engineSession: EngineSession,
@ -160,6 +166,7 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
return when {
errorType == ErrorType.ERROR_UNKNOWN_HOST && !isConnected -> ErrorType.ERROR_NO_INTERNET
errorType == ErrorType.ERROR_HTTPS_ONLY -> ErrorType.ERROR_HTTPS_ONLY
else -> errorType
}
}
@ -201,6 +208,25 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
-> RiskLevel.High
}
private fun getErrorPageTitle(context: Context, type: ErrorType): String? {
return when (type) {
ErrorType.ERROR_HTTPS_ONLY -> context.getString(R.string.errorpage_httpsonly_title)
// Returning `null` will let the component use its default title for this error type
else -> null
}
}
private fun getErrorPageDescription(context: Context, type: ErrorType): String? {
return when (type) {
ErrorType.ERROR_HTTPS_ONLY ->
context.getString(R.string.errorpage_httpsonly_message_title) +
"<br><br>" +
context.getString(R.string.errorpage_httpsonly_message_summary)
// Returning `null` will let the component use its default description for this error type
else -> null
}
}
internal enum class RiskLevel(val htmlRes: String) {
Low(LOW_AND_MEDIUM_RISK_ERROR_PAGES),
Medium(LOW_AND_MEDIUM_RISK_ERROR_PAGES),

@ -67,6 +67,11 @@ interface FeatureSettingsHelper {
*/
var etpPolicy: ETPPolicy
/**
* Enable or disable cookie banner reduction dialog.
*/
var isCookieBannerReductionDialogEnabled: Boolean
fun applyFlagUpdates()
fun resetAllFeatureFlags()
@ -83,5 +88,4 @@ enum class ETPPolicy {
STANDARD,
STRICT,
CUSTOM,
;
}

@ -33,6 +33,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
isTCPCFREnabled = settings.shouldShowTotalCookieProtectionCFR,
isWallpaperOnboardingEnabled = settings.showWallpaperOnboarding,
isDeleteSitePermissionsEnabled = settings.deleteSitePermissions,
isCookieBannerReductionDialogEnabled = !settings.userOptOutOfReEngageCookieBannerDialog,
etpPolicy = getETPPolicy(settings),
)
@ -58,6 +59,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
override var isRecentlyVisitedFeatureEnabled: Boolean by updatedFeatureFlags::isRecentlyVisitedFeatureEnabled
override var isPWAsPromptEnabled: Boolean by updatedFeatureFlags::isPWAsPromptEnabled
override var isTCPCFREnabled: Boolean by updatedFeatureFlags::isTCPCFREnabled
override var isCookieBannerReductionDialogEnabled: Boolean by updatedFeatureFlags::isCookieBannerReductionDialogEnabled
override var etpPolicy: ETPPolicy by updatedFeatureFlags::etpPolicy
override fun applyFlagUpdates() {
@ -67,6 +69,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
override fun resetAllFeatureFlags() {
applyFeatureFlags(initialFeatureFlags)
}
override var isDeleteSitePermissionsEnabled: Boolean by updatedFeatureFlags::isDeleteSitePermissionsEnabled
private fun applyFeatureFlags(featureFlags: FeatureFlags) {
@ -80,6 +83,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
settings.shouldShowTotalCookieProtectionCFR = featureFlags.isTCPCFREnabled
settings.showWallpaperOnboarding = featureFlags.isWallpaperOnboardingEnabled
settings.deleteSitePermissions = featureFlags.isDeleteSitePermissionsEnabled
settings.userOptOutOfReEngageCookieBannerDialog = !featureFlags.isCookieBannerReductionDialogEnabled
setETPPolicy(featureFlags.etpPolicy)
}
}
@ -95,6 +99,7 @@ private data class FeatureFlags(
var isTCPCFREnabled: Boolean,
var isWallpaperOnboardingEnabled: Boolean,
var isDeleteSitePermissionsEnabled: Boolean,
var isCookieBannerReductionDialogEnabled: Boolean,
var etpPolicy: ETPPolicy,
)

@ -48,6 +48,7 @@ class HomeActivityTestRule(
isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR,
isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding,
isDeleteSitePermissionsEnabled: Boolean = settings.deleteSitePermissions,
isCookieBannerReductionDialogEnabled: Boolean = !settings.userOptOutOfReEngageCookieBannerDialog,
etpPolicy: ETPPolicy = getETPPolicy(settings),
) : this(initialTouchMode, launchActivity, skipOnboarding) {
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
@ -59,6 +60,7 @@ class HomeActivityTestRule(
this.isTCPCFREnabled = isTCPCFREnabled
this.isWallpaperOnboardingEnabled = isWallpaperOnboardingEnabled
this.isDeleteSitePermissionsEnabled = isDeleteSitePermissionsEnabled
this.isCookieBannerReductionDialogEnabled = isCookieBannerReductionDialogEnabled
this.etpPolicy = etpPolicy
}
@ -111,6 +113,7 @@ class HomeActivityTestRule(
isPWAsPromptEnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
isCookieBannerReductionDialogEnabled = false,
)
}
}
@ -146,6 +149,7 @@ class HomeActivityIntentTestRule internal constructor(
isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR,
isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding,
isDeleteSitePermissionsEnabled: Boolean = settings.deleteSitePermissions,
isCookieBannerReductionDialogEnabled: Boolean = !settings.userOptOutOfReEngageCookieBannerDialog,
etpPolicy: ETPPolicy = getETPPolicy(settings),
) : this(initialTouchMode, launchActivity, skipOnboarding) {
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
@ -157,6 +161,7 @@ class HomeActivityIntentTestRule internal constructor(
this.isTCPCFREnabled = isTCPCFREnabled
this.isWallpaperOnboardingEnabled = isWallpaperOnboardingEnabled
this.isDeleteSitePermissionsEnabled = isDeleteSitePermissionsEnabled
this.isCookieBannerReductionDialogEnabled = isCookieBannerReductionDialogEnabled
this.etpPolicy = etpPolicy
}
@ -218,6 +223,7 @@ class HomeActivityIntentTestRule internal constructor(
isTCPCFREnabled = settings.shouldShowTotalCookieProtectionCFR
isWallpaperOnboardingEnabled = settings.showWallpaperOnboarding
isDeleteSitePermissionsEnabled = settings.deleteSitePermissions
isCookieBannerReductionDialogEnabled = !settings.userOptOutOfReEngageCookieBannerDialog
etpPolicy = getETPPolicy(settings)
}
@ -244,6 +250,7 @@ class HomeActivityIntentTestRule internal constructor(
isPWAsPromptEnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
isCookieBannerReductionDialogEnabled = false,
)
}
}

@ -6,6 +6,7 @@ package org.mozilla.fenix.helpers
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiSelector
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -41,9 +42,15 @@ object MatcherHelper {
fun itemWithResIdAndText(resourceId: String, text: String) =
mDevice.findObject(UiSelector().resourceId(resourceId).text(text))
fun assertItemWithResIdExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
fun assertItemWithResIdExists(vararg appItems: UiObject, exists: Boolean = true) {
if (exists) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
} else {
for (appItem in appItems) {
assertFalse(appItem.waitForExists(waitingTime))
}
}
}
@ -53,9 +60,13 @@ object MatcherHelper {
}
}
fun assertItemWithDescriptionExists(vararg appItems: UiObject) {
fun assertItemWithDescriptionExists(vararg appItems: UiObject, exists: Boolean = true) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
if (exists) {
assertTrue(appItem.waitForExists(waitingTime))
} else {
assertFalse(appItem.waitForExists(waitingTime))
}
}
}
@ -77,9 +88,19 @@ object MatcherHelper {
}
}
fun assertItemWithResIdAndTextExists(vararg appItems: UiObject) {
fun assertItemWithResIdAndTextExists(vararg appItems: UiObject, exists: Boolean = true) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
if (exists) {
assertTrue(appItem.waitForExists(waitingTime))
} else {
assertFalse(appItem.waitForExists(waitingTime))
}
}
}
fun assertItemIsEnabledAndVisible(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime) && appItem.isEnabled)
}
}
}

@ -113,7 +113,7 @@ object TestHelper {
fun getPermissionAllowID(): String {
return when
(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
true -> "com.android.permissioncontroller"
false -> "com.android.packageinstaller"
}
@ -466,4 +466,24 @@ object TestHelper {
assertFalse("Light theme not selected", expected)
fun verifyDarkThemeApplied(expected: Boolean) = assertTrue("Dark theme not selected", expected)
/**
* Wrapper for tests to run only when certain conditions are met.
* For example: this method will avoid accidentally running a test on GV versions where the feature is disabled.
*/
fun runWithCondition(condition: Boolean, testBlock: () -> Unit) {
if (condition) {
testBlock()
}
}
fun putAppToBackground() {
mDevice.pressRecentApps()
mDevice.findObject(UiSelector().resourceId("$packageName:id/container")).waitUntilGone(waitingTime)
}
fun bringAppToForeground() {
mDevice.pressRecentApps()
mDevice.findObject(UiSelector().resourceId("$packageName:id/container")).waitForExists(waitingTime)
}
}

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

@ -26,27 +26,27 @@
},
"attrs": {
"hashes": [
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
"sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.5'",
"version": "==22.1.0"
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"blessings": {
"blessed": {
"hashes": [
"sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d",
"sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3",
"sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e"
"sha256:0c542922586a265e699188e52d5f5ac5ec0dd517e5a1041d90d2bbf23f906058",
"sha256:2cdd67f8746e048f00df47a2880f4d6acbcdb399031b604e34ba8f71d5787680"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.7"
"markers": "python_version >= '2.7'",
"version": "==1.20.0"
},
"certifi": {
"hashes": [
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
"sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
],
"version": "==2022.6.15"
"markers": "python_version >= '3.6'",
"version": "==2022.12.7"
},
"cffi": {
"hashes": [
@ -119,46 +119,56 @@
},
"charset-normalizer": {
"hashes": [
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.0"
"version": "==2.1.1"
},
"cryptography": {
"hashes": [
"sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59",
"sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596",
"sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3",
"sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5",
"sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab",
"sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884",
"sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82",
"sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b",
"sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441",
"sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa",
"sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d",
"sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b",
"sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a",
"sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6",
"sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157",
"sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280",
"sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282",
"sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67",
"sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8",
"sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046",
"sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327",
"sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"
],
"version": "==37.0.4"
"sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4",
"sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f",
"sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885",
"sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502",
"sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41",
"sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965",
"sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e",
"sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc",
"sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad",
"sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505",
"sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388",
"sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6",
"sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2",
"sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef",
"sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac",
"sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695",
"sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6",
"sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336",
"sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0",
"sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c",
"sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106",
"sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a",
"sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"
],
"index": "pypi",
"version": "==39.0.1"
},
"distro": {
"hashes": [
"sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39",
"sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b"
"sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8",
"sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"
],
"markers": "python_version >= '3.6'",
"version": "==1.7.0"
"version": "==1.8.0"
},
"exceptiongroup": {
"hashes": [
"sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e",
"sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"
],
"markers": "python_version < '3.11'",
"version": "==1.1.0"
},
"fxapom": {
"hashes": [
@ -170,11 +180,11 @@
},
"h11": {
"hashes": [
"sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06",
"sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.6'",
"version": "==0.13.0"
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"hawkauthlib": {
"hashes": [
@ -185,24 +195,26 @@
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"version": "==3.3"
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
],
"version": "==1.1.1"
"markers": "python_version >= '3.7'",
"version": "==2.0.0"
},
"mozdevice": {
"hashes": [
"sha256:074ba1ff99b18ccc1931538a161be2410d0f9cee122df852b3bc73e1000fbcad",
"sha256:a5a1e882a72df71165f6322def9b5e1d677d39d25f62157f3e0dc554b5ae04dc"
"sha256:8005df3c77bcb50e7b110ce75310ba11aaab44d550dbbe36d30e82f920ea551a",
"sha256:9a55047998fdffc00c0de2587fd818a5083520b60ce662036a2134515d485f84"
],
"version": "==4.0.3"
"version": "==4.1.0"
},
"mozdownload": {
"hashes": [
@ -214,9 +226,10 @@
},
"mozfile": {
"hashes": [
"sha256:e5dc835582ea150e35ecd57e9d86cb707d3aa3b2505679db7332326dd49fd6b8"
"sha256:3b0afcda2fa8b802ef657df80a56f21619008f61fcc14b756124028d7b7adf5c",
"sha256:92ca1a786abbdf5e6a7aada62d3a4e28f441ef069c7623223add45268e53c789"
],
"version": "==2.1.0"
"version": "==3.0.0"
},
"mozinfo": {
"hashes": [
@ -234,9 +247,10 @@
},
"mozlog": {
"hashes": [
"sha256:54b9a1e781ce31fc10079dc8aec509fff7feca83714edeae6c981e279ceb796f"
"sha256:080c0a7fdf01cc9a3c9dc8dc527ec8adfb447ca0cd05c5f5fe5c944ceeaec3ff",
"sha256:dc389dc861be3fe9ad1db561893a34015935bd203548685000c84ee5177ce05a"
],
"version": "==7.1.0"
"version": "==7.1.1"
},
"mozprocess": {
"hashes": [
@ -286,11 +300,11 @@
},
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
],
"markers": "python_version >= '3.6'",
"version": "==21.3"
"markers": "python_version >= '3.7'",
"version": "==23.0"
},
"pluggy": {
"hashes": [
@ -302,11 +316,11 @@
},
"progressbar2": {
"hashes": [
"sha256:14d3165a1781d053ffaa117daf27cc706128d2ec1d2977fdb05b6bb079888013",
"sha256:2562ba3e554433f08e81fb7b786208b19de135f3ca1c5da1787d9b05558e6247"
"sha256:1393922fcb64598944ad457569fbeb4b3ac189ef50b5adb9cef3284e87e394ce",
"sha256:1a8e201211f99a85df55f720b3b6da7fb5c8cdef56792c4547205be2de5ea606"
],
"markers": "python_version >= '3.7'",
"version": "==4.0.0"
"version": "==4.2.0"
},
"py": {
"hashes": [
@ -338,33 +352,18 @@
},
"pyjwt": {
"hashes": [
"sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf",
"sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba"
"sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd",
"sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"
],
"markers": "python_version >= '3.6'",
"version": "==2.4.0"
},
"pyopenssl": {
"hashes": [
"sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf",
"sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"
],
"version": "==22.0.0"
},
"pyparsing": {
"hashes": [
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
],
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9"
"markers": "python_version >= '3.7'",
"version": "==2.6.0"
},
"pypom": {
"hashes": [
"sha256:6f56888d25c1faf8fa0d53ef8a4ba1d7dc828e57212f7fe04e8cd21b5eaa924e",
"sha256:a65297125c498c4e9cee99de9b2ddf5b908d0041ee1fc1e90956d3750483fa3d"
"sha256:5da52cf447e62f43a0cfa47dfe52eb822eff07b2fdad759f930d1d227c15220b",
"sha256:8b4dc6d1a24580298bf5ad8ad6c586f33b73c326c10a4419f83aee1abb20077d"
],
"version": "==2.2.3"
"version": "==2.2.4"
},
"pysocks": {
"hashes": [
@ -408,11 +407,11 @@
},
"python-utils": {
"hashes": [
"sha256:3b1c8b706e40e91280eec5fa72ea730880a166cee99afa5555b863d55664478d",
"sha256:5cb9cf295018202fb4d6abdd694a33a7f08bc0ce1bf1eab8cce80ab2cde35c07"
"sha256:68198854fc276bc4b2403b261703c218e01ef564dcb072a7096ed9ea7aa5130c",
"sha256:8bfefc3430f1c48408fa0e5958eee51d39840a5a987c2181a579e99ab6fe5ca6"
],
"markers": "python_version >= '3.7'",
"version": "==3.3.3"
"version": "==3.5.2"
},
"redo": {
"hashes": [
@ -431,10 +430,19 @@
},
"selenium": {
"hashes": [
"sha256:61c8b02788b66f08f2e61d5295afc956991e394815e33573072e68402ed4e8d5"
"sha256:bd04eb41395605d9b2b65fe587f3fed21431da75512985c52772529e5e210c60",
"sha256:c48372905bffcc3b24bd55ab4683a07ee5e1f30fe918c59558ea5ee44cedf6c3"
],
"markers": "python_version ~= '3.7'",
"version": "==4.4.0"
"markers": "python_version >= '3.7'",
"version": "==4.8.2"
},
"setuptools": {
"hashes": [
"sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330",
"sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"
],
"markers": "python_version >= '3.7'",
"version": "==67.4.0"
},
"six": {
"hashes": [
@ -446,11 +454,11 @@
},
"sniffio": {
"hashes": [
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
],
"markers": "python_version >= '3.5'",
"version": "==1.2.0"
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"sortedcontainers": {
"hashes": [
@ -476,11 +484,11 @@
},
"trio": {
"hashes": [
"sha256:4dc0bf9d5cc78767fc4516325b6d80cc0968705a31d0eec2ecd7cdda466265b0",
"sha256:523f39b7b69eef73501cebfe1aafd400a9aad5b03543a0eded52952488ff1c13"
"sha256:ce68f1c5400a47b137c5a4de72c7c901bd4e7a24fbdebfe9b41de8c6c04eaacf",
"sha256:f1dd0780a89bfc880c7c7994519cb53f62aacb2c25ff487001c0052bd721cdf0"
],
"markers": "python_version >= '3.7'",
"version": "==0.21.0"
"version": "==0.22.0"
},
"trio-websocket": {
"hashes": [
@ -492,15 +500,21 @@
},
"urllib3": {
"extras": [
"secure",
"socks"
],
"hashes": [
"sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
"sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.26.14"
},
"wcwidth": {
"hashes": [
"sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc",
"sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"
"sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e",
"sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'",
"version": "==1.26.11"
"version": "==0.2.6"
},
"webob": {
"hashes": [
@ -512,119 +526,110 @@
},
"wsproto": {
"hashes": [
"sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b",
"sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"
"sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065",
"sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"
],
"markers": "python_version >= '3.7'",
"version": "==1.1.0"
"version": "==1.2.0"
},
"zope.component": {
"hashes": [
"sha256:32cbe426ba8fa7b62ce5b211f80f0718a0c749cc7ff09e3f4b43a57f7ccdf5e5",
"sha256:e955eb9f1e55d30e2d8097c8baa9ee012c356887eef3b0d43e6bfcd4868221e5"
"sha256:a508f9fef1b6f5286462d3340cd89ffab5c7899dca0401337239cb6ba7c6bb0a",
"sha256:cbd279e15a959f35a813b64ec4f1027c08b86fcca26f73250c0c912251df90dd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.0.1"
"version": "==5.1.0"
},
"zope.event": {
"hashes": [
"sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42",
"sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"
"sha256:73d9e3ef750cca14816a9c322c7250b0d7c9dbc337df5d1b807ff8d3d0b9e97c",
"sha256:81d98813046fc86cc4136e3698fee628a3282f9c320db18658c21749235fce80"
],
"version": "==4.5.0"
"version": "==4.6"
},
"zope.hookable": {
"hashes": [
"sha256:031e7672540685dc9d07565f2b968c6e21c7899c9391da58a23e63f229a8fdcd",
"sha256:14bac9afd00e9577227749b37dfc3b9fe4f4fb855923262fc016be47baa42712",
"sha256:16c0748438f716894cb61f3ed00adaa65359a5abe7a12bb44d7133d4b0cd3453",
"sha256:1da8d3386238983e97302b2cbdb510e126968e65e3cb8a8745da9744655001fc",
"sha256:24e4489ccf0dbd4c0e9d2d8b845b46994debb30cc22f96da76e973f9cb582799",
"sha256:3111fbc3ddae5d98655691ff81b1c71bd7ad0c3bcf2c2ef659fdc8e66f664603",
"sha256:34a84e15f2e6a3b2e372a67f7d845a372e3f0438319f26dfe9b7cb66ab75be44",
"sha256:35320d283a364c42dbf91694066bc1e7f07628bbd5ec87c1ae08143036e1de49",
"sha256:43855fbab9fb949fa14b5bbcf4c471e4d359428c2196061efe0d82fe3b387101",
"sha256:4711443b964bf7ace58780a3473f520151de8d441e57583b8d8cd8a231fb7656",
"sha256:49427208d9f65e5aa7df5c1be4dc818c51da34e261f4e52fe944a695ae90e3b3",
"sha256:4dbdfe522741effbb507d55354664c93c81db6db66ca124c981d1e3a41e5a31b",
"sha256:4dc9693fa28a238f063ba169110ef4199e0f86f9f976b57824c1d742eb21ad2e",
"sha256:64337672794b01d039e2380d11dd895ab17043530f27b5e3e0709a11fbe5b532",
"sha256:7067a450b15c8d41d00060890573ddc06f3414e62173dac55b38f6683920c20e",
"sha256:7091df7b4d6d897dd00e8717f0fa62b020e28aabe54a92005df22130ed204f6f",
"sha256:7b63de104e531109f606d4e3765b82fa3364e387d63b71a02460d321e24b33c8",
"sha256:7ef3128b5bcb97029c19df95126f57638afe6d2aca0ffede989149fa0bbd744a",
"sha256:800f93479202b05e46ba7696a2dd5411cfdd9d87c47a7d2b6fcda5394a5f9042",
"sha256:84dfb4f5189c8a3b70dfdf0d268840d5dce69048d4767120910f4665fa5ee2f3",
"sha256:8cf9982d43f6a732d041cf68b08fa969e5bcf090212ab519a637c8a393015eda",
"sha256:8fc3e6cd0486c6af48e3317c299def719b57538332a194e0b3bc6a772f4faa0e",
"sha256:a766186b97e493928dfac4a4b6c7508e47411e78c6f3be9846e8981eb91542d4",
"sha256:bfd1b1107a51ddfbd628c1ff884c6a67e92519b23012563b6082d438dbd16b9d",
"sha256:cbcbf6d31092caafb40ab26d07c065bf4f60f3ba7925d23d51a4e19469034544",
"sha256:cf27270212fa51005162d75cae83bb943c84ece7c7f8d30d68efebfad2f469d9",
"sha256:cf7f1f973aeced06ab1a245faa71e867cec6a053b807c2aad4c1728964aac071",
"sha256:de77a946ef020d08643647e417713ed753a2eed1f4495259c38a241c8eb31dbf",
"sha256:e3b01e7cf16b4a3257ee05e4c354737a4f64af302846826c46a296a7944b8da9",
"sha256:ff08276e555f2ef262fd03d872cca130e7ee376b87d7651a5595aae2fa5b2425"
"sha256:0054539ed839751b7f511193912cba393f0b8b5f7dfe9f3601c65b2d3b74e731",
"sha256:049ef54de127236e555d0864ad3b950b2b6e5048cdf1098cf340c6fb108104c7",
"sha256:06570ed57b22624c7673ff203801bbdece14d2d42dc5d9879c24ef5612c53456",
"sha256:0e9e5adc24954e157e084bee97362346470a06d0305cb095118367a8a776dce4",
"sha256:2e8fd79437c2007020d3faac41e13c49bcbaa6a0738e4142b996c656dcb5bb69",
"sha256:4313b3d282c1c26fcb69569b7988bc2de0b6dc59238ae7189b6b7b29503d47cb",
"sha256:448ca90d78bd3aef75fe5d55d19f5d05a217193738b7a8d5fd9e93ecf2c02c84",
"sha256:4b2fd781571336b0b7655826d9a052379a06b62af138085409b2e3fef1e6fb3d",
"sha256:5215355203b9583b7f2a8f06fa7df272562cc12bf5be1a960a45ea49c3294426",
"sha256:5cb0e4a23588435c6911bde300158d31e47c73c469fbf59d927e801e1cb457ef",
"sha256:71bff8f7c2e223f92a218b0909ccc6f612c075cc3b5ed164cf152f1537cae2ca",
"sha256:7241ab28df7288d9a8bf49339a0aabfbf035b93d6a2a843af13d13dfa735c46a",
"sha256:7269a0fbcd7c5901e255679f8dac835b628eab58d5490c38cf2b15508f181e64",
"sha256:7401bd6138e58231aef751c63718726259a7aa6875d746d8a87bba70271b9cff",
"sha256:761c9bf1b8df6e2b2d5ae87cda27b8e82c33e2f328750e039de4f6f7f35b73cd",
"sha256:78c51f04aabd3b77ba8d3b2c2abaff8b7598376fea7bd1af9929e90549f6dd4c",
"sha256:93cfda0663d4d3db6b1818619fbc14e3df2e703454983c841b3b95894d559f86",
"sha256:9af06ca83ff1ef9f94a98d08095dd8960fc5b71ffc7ed7db05988dc493e148a1",
"sha256:9cffa01d8ef1172492fd6df0113ff5432006129b9bd6e8265e1e4985362b973d",
"sha256:9d398b1de407a5908c8e5f55fb7a26fa177916b1203e697ef0b4c3389dd28e14",
"sha256:9f447ecaf7741257333f4b1cc215de633daaf147dbc87133638142ed88492617",
"sha256:9f5d425eb57dee785e4d32703e45c5d6cf2b9fa7ad37c10214593b5f62daa60b",
"sha256:9f7dd1b45cd13976f49ad21f48a8253628c74ad5eefe3f6e14d50f38cc45f613",
"sha256:9fd11381ec66a8569f999dbe11c94870ddf8aecd591300f203a927f18e938a24",
"sha256:acec917178af910959205f98f48bcd0a165bdcd6b4d8b3f4baf06fa393ac5ff5",
"sha256:b65e86a5cb8244d83eabd021f70968d4a80fac01edc99f6e35d29e5458a128bb",
"sha256:bad033b8adfe71f650fef2d4fc33452b3310a0e53139a530dbffbcf9fe08c8c8",
"sha256:c39ffe1b1ef7543e8efafdc6472d7b9ece8ed1ebe20be261522346463aa2c8c0",
"sha256:c79da9673a7d704f6ea2a4bbef6e5e161adbba9d8371476de28a0e3416510cc1",
"sha256:d06da931ac88ebb4c02ac89d0b6fdb2e4fff130901edf9c6e7ea0338a2edf6bd",
"sha256:d44229a0aa8d3587491f359d7326c55b5db6379f68656785dece792afbcfcbae",
"sha256:d5e50bfbcde1afe32f9cf7fa5e8ea42e218090ecb989c31164d708d0491134b7",
"sha256:d822b7ec71ebb5c96df000e2180127e94ba49258335ae796dc4b6201259b2502",
"sha256:eeb4042f9b1771a1dd8377cb1cb307c4a4f5821d1491becbdc69bc9de66d3918",
"sha256:fb601f00ac87e5aa582a81315ed96768ce3513280729d3f51f79312e2b8b94ac",
"sha256:fd49da3340339b8aeef31153ce898e93867ee5a7ffcf685e903ceae6717f0cc2"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.1.0"
"version": "==5.4"
},
"zope.interface": {
"hashes": [
"sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192",
"sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702",
"sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09",
"sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4",
"sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a",
"sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3",
"sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf",
"sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c",
"sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d",
"sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78",
"sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83",
"sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531",
"sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46",
"sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021",
"sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94",
"sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc",
"sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63",
"sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54",
"sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117",
"sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25",
"sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05",
"sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e",
"sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1",
"sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004",
"sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2",
"sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e",
"sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f",
"sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f",
"sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120",
"sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f",
"sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1",
"sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9",
"sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e",
"sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7",
"sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8",
"sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b",
"sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155",
"sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7",
"sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c",
"sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325",
"sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d",
"sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb",
"sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e",
"sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959",
"sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7",
"sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920",
"sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e",
"sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48",
"sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8",
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
"sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32",
"sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0",
"sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c",
"sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c",
"sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d",
"sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf",
"sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b",
"sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc",
"sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f",
"sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d",
"sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e",
"sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16",
"sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f",
"sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9",
"sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296",
"sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a",
"sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d",
"sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d",
"sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189",
"sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4",
"sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452",
"sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a",
"sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0",
"sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5",
"sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671",
"sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e",
"sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f",
"sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396",
"sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7",
"sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b",
"sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf",
"sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f",
"sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6",
"sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188",
"sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7",
"sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.4.0"
"version": "==5.5.2"
}
},
"develop": {}

@ -0,0 +1,111 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.searchScreen
class AddToHomeScreenTest {
private lateinit var mockWebServer: MockWebServer
private val downloadTestPage =
"https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
private val pdfFileName = "washington.pdf"
private val pdfFileURL = "storage.googleapis.com/mobile_test_assets/public/washington.pdf"
private val pdfFileContent = "Washington Crossing the Delaware"
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// Verifies the Add to home screen option in a tab's 3 dot menu
@SmokeTest
@Test
fun mainMenuAddToHomeScreenTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val shortcutTitle = TestHelper.generateRandomString(5)
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
clickCancelShortcutButton()
}
browserScreen {
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
verifyShortcutTextFieldTitle("Test_Page_1")
addShortcutName(shortcutTitle)
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(shortcutTitle) {
verifyUrl(website.url.toString())
verifyTabCounter("1")
}
}
@SmokeTest
@Test
fun addPrivateBrowsingShortcutTest() {
homeScreen {
}.dismissOnboarding()
homeScreen {
}.triggerPrivateBrowsingShortcutPrompt {
verifyNoThanksPrivateBrowsingShortcutButton()
verifyAddPrivateBrowsingShortcutButton()
clickAddPrivateBrowsingShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut("Private ${TestHelper.appName}") {}
searchScreen {
verifySearchView()
}.dismissSearchBar {
verifyCommonMythsLink()
}
}
@SmokeTest
@Test
fun addPDFToHomeScreenTest() {
navigationToolbar {
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
clickLinkMatchingText(pdfFileName)
verifyUrl(pdfFileURL)
verifyPageContent(pdfFileContent)
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
verifyShortcutTextFieldTitle(pdfFileName)
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(pdfFileName) {
verifyUrl(pdfFileURL)
}
}
}

@ -0,0 +1,406 @@
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class AddressAutofillTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun verifyAddressAutofillTest() {
val addressFormPage =
TestAssetHelper.getAddressFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
}.goBack {
}.goBack {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
clickSelectAddressButton()
clickAddressSuggestion("Harrison Street")
verifyAutofilledAddress("Harrison Street")
}
}
@SmokeTest
@Test
fun deleteSavedAddressTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
clickManageAddressesButton()
clickSavedAddress("Mozilla")
clickDeleteAddressButton()
clickCancelDeleteAddressButton()
clickDeleteAddressButton()
clickConfirmDeleteAddressButton()
verifyAddAddressButton()
}
}
@Test
fun verifyAddAddressViewTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
verifyAddAddressView()
}.goBackToAutofillSettings {
verifyAutofillToolbarTitle()
}
}
@Test
fun verifyEditAddressViewTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
clickManageAddressesButton()
clickSavedAddress("Mozilla")
verifyEditAddressView()
}
}
@Test
fun verifyAddressAutofillToggleTest() {
val addressFormPage =
TestAssetHelper.getAddressFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyAddressAutofillSection(true, false)
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
verifySelectAddressButtonExists(true)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickSaveAndAutofillAddressesOption()
verifyAddressAutofillSection(false, true)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
verifySelectAddressButtonExists(false)
}
}
@Test
fun verifyManageAddressesPromptOptionTest() {
val addressFormPage =
TestAssetHelper.getAddressFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyAddressAutofillSection(true, false)
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
clickSelectAddressButton()
}.clickManageAddressButton {
verifyAutofillToolbarTitle()
}.goBackToBrowser {
verifySaveLoginPromptIsNotDisplayed()
}
}
@Test
fun verifyAddressAutofillSelectionTest() {
val addressFormPage =
TestAssetHelper.getAddressFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyAddressAutofillSection(true, false)
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
clickManageAddressesButton()
clickAddAddressButton()
fillAndSaveAddress(
"Android",
"Test",
"Name",
"Fort Street",
"San Jose",
"Arizona",
"95141",
"United States",
"777-7777",
"fuu@bar.org",
)
verifyManageAddressesToolbarTitle()
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
clickSelectAddressButton()
clickAddressSuggestion("Harrison Street")
verifyAutofilledAddress("Harrison Street")
clearAddressForm()
clickStreetAddressTextBox()
clickSelectAddressButton()
clickAddressSuggestion("Fort Street")
verifyAutofilledAddress("Fort Street")
}
}
@Test
fun verifySavedAddressCanBeEditedTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyAddressAutofillSection(true, false)
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
clickManageAddressesButton()
clickSavedAddress("Mozilla")
fillAndSaveAddress(
"Android",
"Test",
"Name",
"Fort Street",
"San Jose",
"Arizona",
"95141",
"United States",
"777-7777",
"fuu@bar.org",
)
verifyManageAddressesToolbarTitle()
}
}
@Test
fun verifyStateFieldUpdatesInAccordanceWithCountryFieldTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyAddressAutofillSection(true, false)
clickAddAddressButton()
verifyCountryOption("United States")
verifyStateOption("Alabama")
verifyCountryOptions("Canada", "United States")
clickCountryOption("Canada")
verifyStateOption("Alberta")
}
}
@Test
fun verifyFormFieldCanBeFilledManuallyTest() {
val addressFormPage =
TestAssetHelper.getAddressFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
clickSelectAddressButton()
clickAddressSuggestion("Harrison Street")
verifyAutofilledAddress("Harrison Street")
setTextForApartmentTextBox("Ap. 07")
verifyManuallyFilledAddress("Ap. 07")
}
}
@Test
fun verifyAutofillAddressSectionTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyAddressAutofillSection(true, false)
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
verifyAddressAutofillSection(true, true)
clickManageAddressesButton()
verifyManageAddressesSection(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"US",
"555-5555",
"foo@bar.com",
)
}
}
}

@ -109,7 +109,7 @@ class CollectionTest {
homeScreen {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName, composeTestRule) {
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title)
verifyCollectionTabUrl(true, webPageUrl)
verifyShareCollectionButtonIsVisible(true)
@ -126,7 +126,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title)
verifyCollectionTabUrl(true, webPageUrl)
verifyShareCollectionButtonIsVisible(true)
@ -166,7 +167,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectOpenTabs(composeTestRule)
}
@ -194,7 +196,8 @@ class CollectionTest {
verifySnackBarText("Collection saved!")
}.openTabsListThreeDotMenu {
}.closeAllTabs {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
}.clickShareCollectionButton {
verifyShareTabsOverlay(firstWebsite.title, secondWebsite.title)
verifySharingWithSelectedApp(sharingApp, urlString, collectionName)
@ -216,7 +219,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectDeleteCollection(composeTestRule)
}
@ -248,7 +252,8 @@ class CollectionTest {
}.selectExistingCollection(collectionName) {
verifySnackBarText("Tab saved!")
}.goToHomescreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(firstWebPage.title)
verifyTabSavedInCollection(secondWebPage.title)
}
@ -270,7 +275,8 @@ class CollectionTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
}.goToHomescreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectAddTabToCollection(composeTestRule)
verifyTabsSelectedCounterText(1)
@ -291,10 +297,12 @@ class CollectionTest {
verifySnackBarText("Collection saved!")
}.closeTabDrawer {
}.goToHomescreen {
}.expandCollection(firstCollectionName, composeTestRule) {
verifyCollectionIsDisplayed(firstCollectionName)
}.expandCollection(firstCollectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectRenameCollection(composeTestRule)
}.typeCollectionNameAndSave(secondCollectionName) {}
homeScreen {
verifyCollectionIsDisplayed(secondCollectionName)
}
@ -334,7 +342,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
verifyTabSavedInCollection(webPage.title, true)
removeTabFromCollection(webPage.title)
verifyTabSavedInCollection(webPage.title, false)
@ -360,8 +369,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
swipeToBottom()
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
swipeTabLeft(testPage.title, composeTestRule)
verifyTabSavedInCollection(testPage.title, false)
}
@ -386,8 +395,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
swipeToBottom()
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
swipeTabRight(testPage.title, composeTestRule)
verifyTabSavedInCollection(testPage.title, false)
}
@ -422,14 +431,13 @@ class CollectionTest {
tabDrawer {
}.closeTabDrawer {
}.goToHomescreen {
}.expandCollection(collectionName, composeTestRule) {
}.expandCollection(collectionName) {
verifyTabSavedInCollection(firstWebPage.title)
verifyTabSavedInCollection(secondWebPage.title)
}
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun navigateBackInCollectionFlowTest() {
val webPage = getGenericAsset(mockWebServer, 1)
@ -463,7 +471,6 @@ class CollectionTest {
@SmokeTest
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun undoDeleteCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1)
@ -475,7 +482,8 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectDeleteCollection(composeTestRule)
}

@ -29,6 +29,7 @@ class ContextualHintsTest {
isTCPCFREnabled = true,
isPocketEnabled = false,
isRecentlyVisitedFeatureEnabled = false,
isCookieBannerReductionDialogEnabled = false,
)
@Before

@ -0,0 +1,106 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class CookieBannerReductionTest {
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@SmokeTest
@Test
fun verifyCookieBannerReductionTest() {
val webSite = "voetbal24.be"
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webSite.toUri()) {
waitForPageToLoad()
verifyCookieBannerExists(exists = true)
}.openThreeDotMenu {
}.openSettings {
}.openCookieBannerReductionSubMenu {
verifyCookieBannerView(isCookieBannerReductionChecked = false)
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(isCookieBannerReductionChecked = true)
}
exitMenu()
browserScreen {
verifyCookieBannerExists(exists = false)
}
TestHelper.restartApp(activityTestRule)
browserScreen {
verifyCookieBannerExists(exists = false)
}.openThreeDotMenu {
}.openSettings {
}.openCookieBannerReductionSubMenu {
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(false)
}
exitMenu()
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyCookieBannerExists(exists = false)
}
}
@SmokeTest
@Test
fun verifyCookieBannerReductionInPrivateBrowsingTest() {
val webSite = "voetbal24.be"
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(webSite.toUri()) {
waitForPageToLoad()
verifyCookieBannerExists(exists = true)
}.openThreeDotMenu {
}.openSettings {
}.openCookieBannerReductionSubMenu {
verifyCookieBannerView(isCookieBannerReductionChecked = false)
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(isCookieBannerReductionChecked = true)
exitMenu()
}
browserScreen {
verifyCookieBannerExists(exists = false)
}
restartApp(activityTestRule)
homeScreen {
}.openTabDrawer {
}.openTab("Voetbal24") {
verifyCookieBannerExists(exists = false)
}.openThreeDotMenu {
}.openSettings {
}.openCookieBannerReductionSubMenu {
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(false)
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyCookieBannerExists(exists = false)
}
}
}

@ -74,6 +74,7 @@ class CrashReportingTest {
}
}
@Ignore("Failure: https://bugzilla.mozilla.org/show_bug.cgi?id=1812075")
@SmokeTest
@Test
fun useAppWhileTabIsCrashedTest() {
@ -129,7 +130,7 @@ class CrashReportingTest {
verifyExistingOpenTabs(secondWebPage.title)
}.closeTabDrawer {
}.goToHomescreen {
verifyPrivateSessionMessage()
verifyCommonMythsLink()
}.openThreeDotMenu {
verifySettingsButton()
}

@ -0,0 +1,639 @@
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.bringAppToForeground
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.putAppToBackground
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import java.time.LocalDate
class CreditCardAutofillTest {
private lateinit var mockWebServer: MockWebServer
object MockCreditCard1 {
const val MOCK_CREDIT_CARD_NUMBER = "5555555555554444"
const val MOCK_LAST_CARD_DIGITS = "4444"
const val MOCK_NAME_ON_CARD = "Mastercard"
const val MOCK_EXPIRATION_MONTH = "February"
val MOCK_EXPIRATION_YEAR = (LocalDate.now().year + 1).toString()
val MOCK_EXPIRATION_MONTH_AND_YEAR = "02/${(LocalDate.now().year + 1)}"
}
object MockCreditCard2 {
const val MOCK_CREDIT_CARD_NUMBER = "2720994326581252"
const val MOCK_LAST_CARD_DIGITS = "1252"
const val MOCK_NAME_ON_CARD = "Mastercard"
const val MOCK_EXPIRATION_MONTH = "March"
val MOCK_EXPIRATION_YEAR = (LocalDate.now().year + 2).toString()
val MOCK_EXPIRATION_MONTH_AND_YEAR = "03/${(LocalDate.now().year + 2)}"
}
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun verifyCreditCardAutofillTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
// Opening Manage saved cards to dismiss here the Secure your credit prompt
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
}.goBackToAutofillSettings {
}.goBack {
}.goBack {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
clickSelectCreditCardButton()
clickCreditCardSuggestion(MockCreditCard1.MOCK_LAST_CARD_DIGITS)
verifyAutofilledCreditCard(MockCreditCard1.MOCK_CREDIT_CARD_NUMBER)
}
}
@SmokeTest
@Test
fun deleteSavedCreditCardUsingToolbarButtonTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
clickSavedCreditCard()
clickDeleteCreditCardToolbarButton()
clickConfirmDeleteCreditCardButton()
verifyAddCreditCardsButton()
}
}
@SmokeTest
@Test
fun cancelDeleteSavedCreditCardUsingToolbarButtonTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
clickSavedCreditCard()
clickDeleteCreditCardToolbarButton()
clickCancelDeleteCreditCardButton()
verifyEditCreditCardToolbarTitle()
}
}
@SmokeTest
@Test
fun deleteSavedCreditCardUsingMenuButtonTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
clickSavedCreditCard()
clickDeleteCreditCardMenuButton()
clickConfirmDeleteCreditCardButton()
verifyAddCreditCardsButton()
}
}
@SmokeTest
@Test
fun cancelDeleteSavedCreditCardUsingMenuButtonTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
clickSavedCreditCard()
clickDeleteCreditCardMenuButton()
clickCancelDeleteCreditCardButton()
verifyEditCreditCardToolbarTitle()
}
}
@Test
fun verifyCreditCardsSectionTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
}
}
@Test
fun verifyManageCreditCardsPromptOptionTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
clickSelectCreditCardButton()
}.clickManageCreditCardsButton {
}.goBackToBrowser {
verifySelectCreditCardPromptExists(false)
}
}
@Test
fun verifyCreditCardsAutofillToggleTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
verifySelectCreditCardPromptExists(true)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickSaveAndAutofillCreditCardsOption()
verifyCreditCardsAutofillSection(false, true)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
verifySelectCreditCardPromptExists(false)
}
}
@Test
fun verifyEditCardsViewTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
clickSavedCreditCard()
verifyEditCreditCardView(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
}.goBackToSavedCreditCards {
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
}
}
@Test
fun verifyEditedCardIsSavedTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
clickSavedCreditCard()
fillAndSaveCreditCard(
MockCreditCard2.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard2.MOCK_NAME_ON_CARD,
MockCreditCard2.MOCK_EXPIRATION_MONTH,
MockCreditCard2.MOCK_EXPIRATION_YEAR,
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
clickSelectCreditCardButton()
clickCreditCardSuggestion(MockCreditCard2.MOCK_LAST_CARD_DIGITS)
verifyAutofilledCreditCard(MockCreditCard2.MOCK_CREDIT_CARD_NUMBER)
}
}
@Test
fun verifyCreditCardCannotBeSavedWithoutCardNumberTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
clickSavedCreditCard()
clearCreditCardNumber()
clickSaveCreditCardToolbarButton()
verifyEditCreditCardToolbarTitle()
verifyCreditCardNumberErrorMessage()
}
}
@Test
fun verifyCreditCardCannotBeSavedWithoutNameOnCardTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
clickSavedCreditCard()
clearNameOnCreditCard()
clickSaveCreditCardToolbarButton()
verifyEditCreditCardToolbarTitle()
verifyNameOnCreditCardErrorMessage()
}
}
@Test
fun verifyMultipleCreditCardsCanBeSavedTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard2.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard2.MOCK_NAME_ON_CARD,
MockCreditCard2.MOCK_EXPIRATION_MONTH,
MockCreditCard2.MOCK_EXPIRATION_YEAR,
)
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
verifySavedCreditCardsSection(
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
MockCreditCard2.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
clickSelectCreditCardButton()
verifyCreditCardSuggestion(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
)
clickCreditCardSuggestion(MockCreditCard2.MOCK_LAST_CARD_DIGITS)
verifyAutofilledCreditCard(MockCreditCard2.MOCK_CREDIT_CARD_NUMBER)
}
}
@Test
fun verifyDoNotSaveCreditCardFromFormTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
clickCancelCreditCardPromptButton()
verifyUpdateOrSaveCreditCardPromptExists(exists = false)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
}
}
@Test
fun verifySaveCreditCardFromFormTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
clickUpdateOrSaveCreditCardPromptButton()
verifyUpdateOrSaveCreditCardPromptExists(exists = false)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, true)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
}
}
@Test
fun verifyCancelCreditCardUpdatePromptTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard2.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard2.MOCK_NAME_ON_CARD,
MockCreditCard2.MOCK_EXPIRATION_MONTH,
MockCreditCard2.MOCK_EXPIRATION_YEAR,
)
// Opening Manage saved cards to dismiss here the Secure your credit prompt
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
clickSelectCreditCardButton()
clickCreditCardSuggestion(MockCreditCard2.MOCK_LAST_CARD_DIGITS)
verifyAutofilledCreditCard(MockCreditCard2.MOCK_CREDIT_CARD_NUMBER)
changeCreditCardExpiryDate(MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR)
clickCreditCardFormSubmitButton()
clickCancelCreditCardPromptButton()
verifyUpdateOrSaveCreditCardPromptExists(false)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, true)
clickManageSavedCreditCardsButton()
verifySavedCreditCardsSection(
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
MockCreditCard2.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
}
}
@Test
fun verifyConfirmCreditCardUpdatePromptTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard2.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard2.MOCK_NAME_ON_CARD,
MockCreditCard2.MOCK_EXPIRATION_MONTH,
MockCreditCard2.MOCK_EXPIRATION_YEAR,
)
// Opening Manage saved cards to dismiss here the Secure your credit prompt
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCreditCardNumberTextBox()
clickSelectCreditCardButton()
clickCreditCardSuggestion(MockCreditCard2.MOCK_LAST_CARD_DIGITS)
verifyAutofilledCreditCard(MockCreditCard2.MOCK_CREDIT_CARD_NUMBER)
changeCreditCardExpiryDate(MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR)
clickCreditCardFormSubmitButton()
clickUpdateOrSaveCreditCardPromptButton()
verifyUpdateOrSaveCreditCardPromptExists(false)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, true)
clickManageSavedCreditCardsButton()
verifySavedCreditCardsSection(
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
}
}
@Test
fun verifySavedCreditCardsRedirectionToAutofillAfterInterruptionTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
verifySavedCreditCardsSection(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR,
)
putAppToBackground()
bringAppToForeground()
verifyAutofillToolbarTitle()
}
}
@Test
fun verifyEditCreditCardRedirectionToAutofillAfterInterruptionTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
verifyCreditCardsAutofillSection(true, false)
clickAddCreditCardButton()
fillAndSaveCreditCard(
MockCreditCard1.MOCK_CREDIT_CARD_NUMBER,
MockCreditCard1.MOCK_NAME_ON_CARD,
MockCreditCard1.MOCK_EXPIRATION_MONTH,
MockCreditCard1.MOCK_EXPIRATION_YEAR,
)
clickManageSavedCreditCardsButton()
clickSecuredCreditCardsLaterButton()
clickSavedCreditCard()
putAppToBackground()
bringAppToForeground()
verifyAutofillToolbarTitle()
}
}
}

@ -38,7 +38,7 @@ class CustomTabsTest {
private val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
@get:Rule
val activityTestRule = HomeActivityIntentTestRule()
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@get: Rule
val intentReceiverActivityTestRule = ActivityTestRule(
@ -56,10 +56,6 @@ class CustomTabsTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.apply {
isTCPCFREnabled = false
}.applyFlagUpdates()
}
@After
@ -117,7 +113,7 @@ class CustomTabsTest {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
verifySavedLoginFromPrompt("mozilla")
verifySavedLoginsSectionUsername("mozilla")
}
}

@ -53,7 +53,7 @@ class DownloadFileTypesTest(fileName: String) {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
}.closePrompt {
}.closeCompletedDownloadPrompt {
}.openThreeDotMenu {
}.openDownloadsManager {
waitForDownloadsListToExist()

@ -7,10 +7,12 @@ package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.assertExternalAppOpens
import org.mozilla.fenix.helpers.TestHelper.deleteDownloadedFileOnStorage
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.browserScreen
@ -30,6 +32,9 @@ class DownloadTest {
/* Remote test page managed by Mozilla Mobile QA team at https://github.com/mozilla-mobile/testapp */
private val downloadTestPage = "https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
private var downloadFile: String = ""
private val pdfFileName = "washington.pdf"
private val pdfFileURL = "storage.googleapis.com/mobile_test_assets/public/washington.pdf"
private val pdfFileContent = "Washington Crossing the Delaware"
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@ -95,7 +100,7 @@ class DownloadTest {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
}.closePrompt { }
}
mDevice.openNotification()
notificationShade {
verifySystemNotificationExists("Download completed")
@ -105,6 +110,11 @@ class DownloadTest {
@SmokeTest
@Test
fun pauseResumeCancelDownloadTest() {
// Clear the "Firefox Fenix default browser notification"
notificationShade {
cancelAllShownNotifications()
}
downloadFile = "1GB.zip"
navigationToolbar {
@ -162,6 +172,7 @@ class DownloadTest {
}
}
@Ignore("failing, see https://bugzilla.mozilla.org/show_bug.cgi?id=1821024")
@SmokeTest
@Test
fun openDownloadedFileTest() {
@ -184,4 +195,32 @@ class DownloadTest {
mDevice.pressBack()
}
}
@SmokeTest
@Test
fun openPDFInBrowserTest() {
navigationToolbar {
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
clickLinkMatchingText(pdfFileName)
verifyUrl(pdfFileURL)
verifyPageContent(pdfFileContent)
}
}
@SmokeTest
@Test
fun saveAndOpenPdfTest() {
navigationToolbar {
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
clickLinkMatchingText(pdfFileName)
verifyPageContent(pdfFileContent)
}.openThreeDotMenu {
}.clickShareButton {
}.clickSaveAsPDF {
verifyDownloadPrompt(pdfFileName)
}.clickDownload {
}.clickOpen("application/pdf") {
assertExternalAppOpens("com.google.android.apps.docs")
}
}
}

@ -64,7 +64,7 @@ class EnhancedTrackingProtectionTest {
}.openThreeDotMenu {
}.openSettings {
verifyEnhancedTrackingProtectionButton()
verifyEnhancedTrackingProtectionState("On")
verifyEnhancedTrackingProtectionState("Standard")
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionHeader()
verifyEnhancedTrackingProtectionOptionsEnabled()

@ -6,9 +6,7 @@ package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
@ -20,8 +18,6 @@ import org.mozilla.fenix.helpers.Constants.POCKET_RECOMMENDED_STORIES_UTM_PARAM
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -62,6 +58,7 @@ class HomeScreenTest {
mockWebServer.shutdown()
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1815275")
@Test
fun homeScreenItemsTest() {
homeScreen { }.dismissOnboarding()
@ -96,30 +93,9 @@ class HomeScreenTest {
homeScreen { }.togglePrivateBrowsingMode()
homeScreen {
verifyHomeScreen()
verifyNavigationToolbar()
verifyHomePrivateBrowsingButton()
verifyHomeMenuButton()
verifyHomeWordmark()
verifyTabButton()
verifyPrivateSessionMessage()
verifyNavigationToolbar()
verifyHomeComponent()
}
homeScreen {
// To deal with the race condition where multiple "add tab" buttons are present,
// we need to wait until previous HomeFragment View objects are gone.
mDevice.waitNotNull(Until.gone(By.text(privateSessionMessage)), waitingTime)
verifyHomeScreen()
verifyNavigationToolbar()
verifyHomePrivateBrowsingButton()
verifyHomeMenuButton()
verifyHomeWordmark()
verifyTabButton()
verifyPrivateSessionMessage()
verifyNavigationToolbar()
verifyHomeComponent()
verifyPrivateBrowsingHomeScreen()
}.openCommonMythsLink {
verifyUrl("common-myths-about-private-browsing")
}
}
@ -130,31 +106,45 @@ class HomeScreenTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
verifyPageContent(firstWebPage.content)
verifyUrl(firstWebPage.url.toString())
}.goToHomescreen {
verifyJumpBackInSectionIsDisplayed()
verifyJumpBackInItemTitle(firstWebPage.title)
verifyJumpBackInItemWithUrl(firstWebPage.url.toString())
verifyJumpBackInItemTitle(activityTestRule, firstWebPage.title)
verifyJumpBackInItemWithUrl(activityTestRule, firstWebPage.url.toString())
verifyJumpBackInShowAllButton()
}.clickJumpBackInShowAllButton {
verifyExistingOpenTabs(firstWebPage.title)
}.closeTabDrawer() {
}.closeTabDrawer {
}
homeScreen {
}.clickJumpBackInItemWithTitle(firstWebPage.title) {
verifyUrl(firstWebPage.url.toString())
clickLinkMatchingText("Link 1")
navigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
verifyPageContent(secondWebPage.content)
verifyUrl(secondWebPage.url.toString())
}.goToHomescreen {
verifyJumpBackInSectionIsDisplayed()
verifyJumpBackInItemTitle(secondWebPage.title)
verifyJumpBackInItemWithUrl(secondWebPage.url.toString())
verifyJumpBackInItemTitle(activityTestRule, secondWebPage.title)
verifyJumpBackInItemWithUrl(activityTestRule, secondWebPage.url.toString())
}.openTabDrawer {
closeTabWithTitle(secondWebPage.title)
}.closeTabDrawer {
}
homeScreen {
verifyJumpBackInSectionIsDisplayed()
verifyJumpBackInItemTitle(activityTestRule, firstWebPage.title)
verifyJumpBackInItemWithUrl(activityTestRule, firstWebPage.url.toString())
}.openTabDrawer {
closeTab()
}
homeScreen {
verifyJumpBackInSectionIsNotDisplayed()
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1815276")
@Test
fun verifyPocketHomepageStoriesTest() {
activityTestRule.activityRule.applySettingsExceptions {

@ -0,0 +1,667 @@
/* 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 android.os.Build
import android.view.autofill.AutofillManager
import androidx.core.net.toUri
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying:
* - the Logins and Passwords menu and sub-menus.
* - save login prompts.
* - saving logins based on the user's preferences.
*/
class LoginsTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule =
HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
val autofillManager: AutofillManager =
TestHelper.appContext.getSystemService(AutofillManager::class.java)
autofillManager.disableAutofillServices()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// Tests the Logins and passwords menu items and default values
@Test
fun loginsAndPasswordsSettingsItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
// Necessary to scroll a little bit for all screen sizes
scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
verifyAutofillInFirefoxToggle(true)
verifyAutofillLoginsInOtherAppsToggle(false)
}
}
// Tests only for initial state without signing in.
// For tests after signing in, see SyncIntegration test suite
@Test
fun savedLoginsMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
// Necessary to scroll a little bit for all screen sizes
scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that logins list is empty
verifyEmptySavedLoginsListView()
}
}
@Test
fun syncLoginsMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
// Necessary to scroll a little bit for all screen sizes
scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
}.openSyncLogins {
verifyReadyToScanOption()
verifyUseEmailOption()
}
}
@Test
fun saveLoginsAndPasswordsOptionsItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSaveLoginsAndPasswordsOptions {
verifySaveLoginsOptionsView()
}
}
@Test
fun saveLoginFromPromptTest() {
val saveLoginTest =
TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
clickSubmitLoginButton()
// Click save to save the login
saveLoginFromPrompt("Save")
}
browserScreen {
}.openThreeDotMenu {
}.openSettings {
scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that the login appears correctly
verifySavedLoginsSectionUsername("test@example.com")
}
}
@SmokeTest
@Test
fun openWebsiteForSavedLoginTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
val userName = "test"
val password = "pass"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials(userName, password)
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
viewSavedLoginDetails(userName)
}.goToSavedWebsite {
verifyUrl(originWebsite)
}
}
@Test
fun neverSaveLoginFromPromptTest() {
val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
clickSubmitLoginButton()
// Don't save the login, add to exceptions
saveLoginFromPrompt("Never save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that the login list is empty
verifyEmptySavedLoginsListView()
verifyNotSavedLoginFromPrompt()
}.goBack {
}.openLoginExceptions {
// Verify localhost was added to exceptions list
verifyLocalhostExceptionAdded()
}
}
@SmokeTest
@Test
fun updateSavedLoginTest() {
val saveLoginTest =
TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
clickSubmitLoginButton()
// Click Save to save the login
saveLoginFromPrompt("Save")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
enterPassword("test")
mDevice.waitForIdle()
clickSubmitLoginButton()
verifyUpdateLoginPromptIsShown()
// Click Update to change the saved password
saveLoginFromPrompt("Update")
}.openThreeDotMenu {
}.openSettings {
scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that the login appears correctly
verifySavedLoginsSectionUsername("test@example.com")
viewSavedLoginDetails("test@example.com")
revealPassword()
verifyPasswordSaved("test") // failing here locally
}
}
@SmokeTest
@Test
fun verifyMultipleLoginsSelectionsTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
fillAndSubmitLoginCredentials("firefox", "mozilla")
saveLoginFromPrompt("Save")
clearUserNameLoginCredential()
clickSuggestedLoginsButton()
verifySuggestedUserName("firefox")
verifySuggestedUserName("mozilla")
clickLoginSuggestion("mozilla")
clickShowPasswordButton()
verifyPrefilledLoginCredentials("mozilla", "firefox", true)
}
}
@Test
fun verifyEditLoginsViewTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
clickThreeDotButton(activityTestRule)
clickEditLoginButton()
setNewPassword("fenix")
saveEditedLogin()
revealPassword()
verifyPasswordSaved("fenix")
}
}
@Test
fun verifyEditedLoginsAreSavedTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
clickThreeDotButton(activityTestRule)
clickEditLoginButton()
setNewUserName("android")
setNewPassword("fenix")
saveEditedLogin()
}
exitMenu()
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
waitForPageToLoad()
clickShowPasswordButton()
verifyPrefilledLoginCredentials("android", "fenix", true)
}
}
@Test
fun verifyLoginWithNoUserNameCanBeSavedTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
clickThreeDotButton(activityTestRule)
clickEditLoginButton()
clickClearUserNameButton()
saveEditedLogin()
verifyLoginItemUsername("")
}
}
@Test
fun verifyLoginWithoutPasswordCanNotBeSavedTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
clickThreeDotButton(activityTestRule)
clickEditLoginButton()
clickClearPasswordButton()
verifyPasswordRequiredErrorMessage()
saveEditedLogin()
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Test
fun verifyEditModeDismissalDoesNotSaveLoginCredentialsTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
clickThreeDotButton(activityTestRule)
clickEditLoginButton()
setNewUserName("android")
setNewPassword("fenix")
clickGoBackButton()
verifyLoginItemUsername("mozilla")
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Test
fun verifyDeleteLoginButtonTest() {
val loginPage = TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.url) {
clickSubmitLoginButton()
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails("test@example.com")
clickThreeDotButton(activityTestRule)
clickDeleteLoginButton()
verifyLoginDeletionPrompt()
clickCancelDeleteLogin()
verifyLoginItemUsername("test@example.com")
viewSavedLoginDetails("test@example.com")
clickThreeDotButton(activityTestRule)
clickDeleteLoginButton()
verifyLoginDeletionPrompt()
clickConfirmDeleteLogin()
// The account remains displayed, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812431
// verifyNotSavedLoginFromPrompt()
}
}
@Test
fun verifyNeverSaveLoginOptionTest() {
val loginPage = TestAssetHelper.getSaveLoginAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSaveLoginsAndPasswordsOptions {
clickNeverSaveOption()
}.goBack {
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.url) {
clickSubmitLoginButton()
verifySaveLoginPromptIsNotDisplayed()
}
}
@Test
fun verifyAutofillToggleTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
verifyPrefilledLoginCredentials("mozilla", "firefox", true)
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
verifyAutofillInFirefoxToggle(true)
clickAutofillInFirefoxOption()
verifyAutofillInFirefoxToggle(false)
}.goBack {
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
verifyPrefilledLoginCredentials("mozilla", "firefox", false)
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1812995")
@Test
fun verifyLoginIsNotUpdatedTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
val originWebsite = "mozilla-mobile.github.io"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSaveLoginsAndPasswordsOptions {
verifySaveLoginsOptionsView()
verifyAskToSaveRadioButton(true)
verifyNeverSaveSaveRadioButton(false)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
clickShowPasswordButton()
fillAndSubmitLoginCredentials("mozilla", "fenix")
verifyUpdateLoginPromptIsShown()
saveLoginFromPrompt("Dont update")
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Test
fun verifySearchLoginsTest() {
val firstLoginPage = TestAssetHelper.getSaveLoginAsset(mockWebServer)
val secondLoginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(firstLoginPage.url) {
clickSubmitLoginButton()
saveLoginFromPrompt("Save")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
clickSearchLoginButton()
searchLogin("mozilla")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("mozilla")
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Test
fun verifyLastUsedLoginSortingOptionTest() {
val firstLoginPage = TestAssetHelper.getSaveLoginAsset(mockWebServer)
val secondLoginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(firstLoginPage.url) {
clickSubmitLoginButton()
saveLoginFromPrompt("Save")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
clickSavedLoginsChevronIcon()
verifyLoginsSortingOptions()
clickLastUsedSortingOption()
verifySortedLogin(activityTestRule, 0, originWebsite)
verifySortedLogin(activityTestRule, 1, firstLoginPage.url.authority.toString())
}.goBack {
}.openSavedLogins {
verifySortedLogin(activityTestRule, 0, originWebsite)
verifySortedLogin(activityTestRule, 1, firstLoginPage.url.authority.toString())
}
restartApp(activityTestRule)
browserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySortedLogin(activityTestRule, 0, originWebsite)
verifySortedLogin(activityTestRule, 1, firstLoginPage.url.authority.toString())
}
}
@Test
fun verifyAlphabeticalLoginSortingOptionTest() {
val firstLoginPage = TestAssetHelper.getSaveLoginAsset(mockWebServer)
val secondLoginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(firstLoginPage.url) {
clickSubmitLoginButton()
saveLoginFromPrompt("Save")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
verifySortedLogin(activityTestRule, 0, firstLoginPage.url.authority.toString())
verifySortedLogin(activityTestRule, 1, originWebsite)
}.goBack {
}.openSavedLogins {
verifySortedLogin(activityTestRule, 0, firstLoginPage.url.authority.toString())
verifySortedLogin(activityTestRule, 1, originWebsite)
}
restartApp(activityTestRule)
browserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySortedLogin(activityTestRule, 0, firstLoginPage.url.authority.toString())
verifySortedLogin(activityTestRule, 1, originWebsite)
}
}
@Test
fun verifyAddLoginManuallyTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
clickAddLoginButton()
verifyAddNewLoginView()
enterSiteCredential("mozilla")
verifyHostnameErrorMessage()
enterSiteCredential(loginPage)
verifyHostnameClearButtonEnabled()
setNewUserName("mozilla")
setNewPassword("firefox")
clickClearPasswordButton()
verifyPasswordErrorMessage()
setNewPassword("firefox")
verifyPasswordClearButtonEnabled()
saveEditedLogin()
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
clickUsernameTextField()
clickSuggestedLoginsButton()
verifySuggestedUserName("mozilla")
clickLoginSuggestion("mozilla")
clickShowPasswordButton()
verifyPrefilledLoginCredentials("mozilla", "firefox", true)
}
}
}

@ -34,6 +34,11 @@ import java.util.Locale
class NavigationToolbarTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val downloadTestPage =
"https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
private val pdfFileName = "washington.pdf"
private val pdfFileURL = "storage.googleapis.com/mobile_test_assets/public/washington.pdf"
private val pdfFileContent = "Washington Crossing the Delaware"
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
@get:Rule
@ -174,9 +179,46 @@ class NavigationToolbarTest {
verifyFindPrevInPageResult("2/3")
clickFindInPagePrevButton()
verifyFindPrevInPageResult("1/3")
}.closeFindInPageWithCloseButton {
verifyFindInPageBar(false)
}.openThreeDotMenu {
}.openFindInPage {
enterFindInPageQuery("3")
verifyFindNextInPageResult("1/1")
}.closeFindInPage { }
}.closeFindInPageWithBackButton {
verifyFindInPageBar(false)
}
}
@Test
fun pdfFindInPageTest() {
navigationToolbar {
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
clickLinkMatchingText(pdfFileName)
verifyUrl(pdfFileURL)
verifyPageContent(pdfFileContent)
}.openThreeDotMenu {
verifyThreeDotMenuExists()
verifyFindInPageButton()
}.openFindInPage {
verifyFindInPageNextButton()
verifyFindInPagePrevButton()
verifyFindInPageCloseButton()
enterFindInPageQuery("o")
verifyFindNextInPageResult("1/2")
clickFindInPageNextButton()
verifyFindNextInPageResult("2/2")
clickFindInPagePrevButton()
verifyFindPrevInPageResult("1/2")
}.closeFindInPageWithCloseButton {
verifyFindInPageBar(false)
}.openThreeDotMenu {
}.openFindInPage {
enterFindInPageQuery("l")
verifyFindNextInPageResult("1/1")
}.closeFindInPageWithBackButton {
verifyFindInPageBar(false)
}
}
@Test

@ -0,0 +1,119 @@
/* 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 android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.gleanplumb.CustomAttributeProvider
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.nimbus.Messaging
/**
* This test is to test the integrity of messages hardcoded in the FML.
*
* It tests if the trigger expressions are valid, all the fields are complete
* and a simple check if they are localized (don't contain `_`).
*/
class NimbusMessagingMessageTest {
private lateinit var feature: Messaging
private lateinit var mDevice: UiDevice
private lateinit var context: Context
private val storage
get() = context.components.analytics.messagingStorage
@get:Rule
val activityTestRule =
HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
context = TestHelper.appContext
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
feature = FxNimbus.features.messaging.value()
}
/**
* Check if all messages in the FML are internally consistent with the
* rest of the FML. This check is done in the `NimbusMessagingStorage`
* class.
*/
@Test
fun testAllMessageIntegrity() = runTest {
val messages = storage.getMessages()
val rawMessages = feature.messages
assertTrue(rawMessages.isNotEmpty())
if (messages.size != rawMessages.size) {
val expected = rawMessages.keys.toHashSet()
val observed = messages.map { it.id }.toHashSet()
val missing = expected - observed
fail("Problem with message(s) in FML: $missing")
}
assertEquals(messages.size, rawMessages.size)
}
/**
* Check if the messages' triggers are well formed JEXL.
*/
@Test
fun testAllMessageTriggers() = runTest {
val nimbus = context.components.analytics.experiments
val helper = nimbus.createMessageHelper(
CustomAttributeProvider.getCustomAttributes(context),
)
val messages = storage.getMessages()
messages.forEach { message ->
storage.isMessageEligible(message, helper)
if (storage.malFormedMap.isNotEmpty()) {
fail("${message.id} has a problem with its JEXL trigger: ${storage.malFormedMap.keys}")
}
}
}
private fun checkIsLocalized(string: String) {
assertFalse(string.isBlank())
// The check will almost always succeed, since the generated code
// will not compile if this is true, and there is no resource available.
assertFalse(string.matches(Regex("[a-z][_a-z\\d]*")))
}
/**
* Check that the messages are localized.
*/
@Test
fun testAllMessagesAreLocalized() {
feature.messages.values.forEach { message ->
message.buttonLabel?.let(::checkIsLocalized)
message.title?.let(::checkIsLocalized)
checkIsLocalized(message.text)
}
}
@Test
fun testIndividualMessagesAreValid() {
val expectedMessages = listOf(
"default-browser",
"default-browser-notification",
)
val rawMessages = feature.messages
for (id in expectedMessages) {
assertTrue(rawMessages.containsKey(id))
}
}
}

@ -0,0 +1,84 @@
/* 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 org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.experiments.nimbus.NimbusInterface
import org.mozilla.experiments.nimbus.internal.NimbusException
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.gleanplumb.CustomAttributeProvider
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.nimbus.Messaging
/**
* Test to instantiate Nimbus and automatically test all trigger expressions shipping with the app.
*
* We do this as a UI test to make sure that:
* - as much of the custom targeting and trigger attributes are recorded as possible.
* - we can run the Rust JEXL evaluator.
*/
class NimbusMessagingTriggerTest {
private lateinit var mDevice: UiDevice
private lateinit var feature: Messaging
private lateinit var nimbus: NimbusInterface
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
nimbus = TestHelper.appContext.components.analytics.experiments
feature = FxNimbus.features.messaging.value()
}
@Test
fun testAllMessageTriggersAreValid() {
val triggers = feature.triggers
val customAttributes = CustomAttributeProvider.getCustomAttributes(TestHelper.appContext)
val jexl = nimbus.createMessageHelper(customAttributes)
val failed = mutableMapOf<String, String>()
triggers.forEach { (key, expr) ->
try {
jexl.evalJexl(expr)
} catch (e: NimbusException) {
failed.put(key, expr)
}
}
if (failed.isNotEmpty()) {
Assert.fail("Expressions failed: $failed")
}
}
@Test
fun testBadTriggersAreDetected() {
val jexl = nimbus.createMessageHelper()
val triggers = mapOf(
"Syntax error" to "|'syntax error'|",
"Invalid identifier" to "invalid_identifier",
"Invalid transform" to "'string'|invalid_transform",
"Invalid interval" to "'string'|eventLastSeen('Invalid')",
)
triggers.forEach { (key, expr) ->
try {
jexl.evalJexl(expr)
Assert.fail("$key expression failed to error: $expr")
} catch (e: NimbusException) {
// NOOP
}
}
}
}

@ -6,6 +6,7 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
@ -64,7 +65,11 @@ class OnboardingTest {
)
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
verifySignInToSyncCard()
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
verifyPrivacyNoticeCard()
verifyStartBrowsingSection()
verifyNavigationToolbarItems("0")
@ -123,13 +128,13 @@ class OnboardingTest {
@Test
fun toolbarTapDoesntDismissOnboardingTest() {
homeScreen {
verifyWelcomeHeader()
verifyStartBrowsingButton()
}.openSearch {
verifyScanButton()
verifySearchEngineButton()
verifyKeyboardVisibility()
}.dismissSearchBar {
verifyWelcomeHeader()
verifyStartBrowsingButton()
}
}
@ -138,12 +143,12 @@ class OnboardingTest {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
verifyWelcomeHeader()
verifyStartBrowsingButton()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
verifyExistingTopSitesList()
verifyHomeScreen()
}
}
@ -193,11 +198,23 @@ class OnboardingTest {
@Test
fun privacyProtectionByDefaultCardTest() {
homeScreen {
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
clickStrictTrackingProtectionButton()
verifyPrivacyProtectionCard(isStandardChecked = false, isStrictChecked = true)
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = false,
isStrictChecked = true,
)
clickStandardTrackingProtectionButton()
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
}
}
@ -211,12 +228,13 @@ class OnboardingTest {
}
@Test
@Ignore("Failing due to changes from https://github.com/mozilla-mobile/firefox-android/pull/969")
fun youControlYourDataCardTest() {
homeScreen {
verifyPrivacyNoticeCard()
}.clickPrivacyNoticeButton {
verifyUrl(privacyNoticeLink)
}.goBack {
verifyCustomTabToolbarTitle("Firefox Privacy Notice")
}.goBackToOnboardingScreen {
verifyPrivacyNoticeCard()
}
}

@ -8,7 +8,11 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.Constants.PackageName.GMAIL_APP
import org.mozilla.fenix.helpers.Constants.PackageName.PHONE_APP
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.assertNativeAppOpens
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.addToHomeScreen
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.customTabScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.pwaScreen
@ -75,6 +79,7 @@ class PwaTest {
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(shortcutTitle) {
clickLinkMatchingText("Telephone link")
clickOpenInAppPromptButton()
assertNativeAppOpens(PHONE_APP, phoneLink)
}
}
@ -97,4 +102,40 @@ class PwaTest {
verifyPwaActivityInCurrentTask()
}
}
@SmokeTest
@Test
fun saveLoginsInPWATest() {
val pwaPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val shortcutTitle = "TEST_APP"
navigationToolbar {
}.enterURLAndEnterToBrowser(pwaPage.toUri()) {
verifyNotificationDotOnMainMenu()
}.openThreeDotMenu {
}.clickInstall {
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(shortcutTitle) {
mDevice.waitForIdle()
fillAndSubmitLoginCredentials("mozilla", "firefox")
verifySaveLoginPromptIsDisplayed()
saveLoginFromPrompt("Save")
TestHelper.openAppFromExternalLink(pwaPage)
browserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
verifySavedLoginsSectionUsername("mozilla")
}
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(shortcutTitle) {
verifyPrefilledPWALoginCredentials("mozilla", shortcutTitle)
}
}
}
}

@ -16,6 +16,7 @@ import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.ui.robots.clickAlwaysButton
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -61,7 +62,7 @@ class SettingsAdvancedTest {
verifyAdvancedHeading()
verifyAddons()
verifyOpenLinksInAppsButton()
verifyOpenLinksInAppsSwitchState(false)
verifyOpenLinksInAppsState("Never")
verifyRemoteDebug()
verifyLeakCanaryButton()
}
@ -76,9 +77,11 @@ class SettingsAdvancedTest {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifyOpenLinksInAppsSwitchState(false)
clickOpenLinksInAppsSwitch()
verifyOpenLinksInAppsSwitchState(true)
verifyOpenLinksInAppsButton()
verifyOpenLinksInAppsState("Never")
}.openOpenLinksInAppsMenu {
clickAlwaysButton()
}.goBack {
}.goBack {}
navigationToolbar {

@ -16,26 +16,18 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getLoremIpsumAsset
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.TestHelper.runWithSystemLocaleChanged
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_CREDIT_CARD_NUMBER
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_EXPIRATION_MONTH
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_EXPIRATION_YEAR
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_LAST_CARD_DIGITS
import org.mozilla.fenix.ui.SettingsBasicsTest.CreditCard.MOCK_NAME_ON_CARD
import org.mozilla.fenix.ui.robots.checkTextSizeOnWebsite
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.util.FRENCH_LANGUAGE_HEADER
import org.mozilla.fenix.ui.util.FRENCH_SYSTEM_LOCALE_OPTION
import org.mozilla.fenix.ui.util.FR_SETTINGS
import org.mozilla.fenix.ui.util.ROMANIAN_LANGUAGE_HEADER
import java.time.LocalDate
import java.util.Locale
/**
@ -46,14 +38,6 @@ class SettingsBasicsTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mockWebServer: MockWebServer
object CreditCard {
const val MOCK_CREDIT_CARD_NUMBER = "5555555555554444"
const val MOCK_LAST_CARD_DIGITS = "4444"
const val MOCK_NAME_ON_CARD = "Mastercard"
const val MOCK_EXPIRATION_MONTH = "February"
val MOCK_EXPIRATION_YEAR = (LocalDate.now().year + 1).toString()
}
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@ -119,116 +103,6 @@ class SettingsBasicsTest {
}
}
@SmokeTest
@Test
fun verifyAddressAutofillTest() {
val addressFormPage =
TestAssetHelper.getAddressFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
}.goBack {
}.goBack {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickStreetAddressTextBox()
clickSelectAddressButton()
clickAddressSuggestion("Harrison Street")
verifyAutofilledAddress("Harrison Street")
}
}
@SmokeTest
@Test
fun deleteSavedAddressTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddAddressButton()
fillAndSaveAddress(
"Mozilla",
"Fenix",
"Firefox",
"Harrison Street",
"San Francisco",
"Alaska",
"94105",
"United States",
"555-5555",
"foo@bar.com",
)
clickManageAddressesButton()
clickSavedAddress("Mozilla")
clickDeleteAddressButton()
clickCancelDeleteAddressButton()
clickDeleteAddressButton()
clickConfirmDeleteAddressButton()
verifyAddAddressButton()
}
}
@SmokeTest
@Test
fun verifyCreditCardAutofillTest() {
val creditCardFormPage = TestAssetHelper.getCreditCardFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(MOCK_CREDIT_CARD_NUMBER, MOCK_NAME_ON_CARD, MOCK_EXPIRATION_MONTH, MOCK_EXPIRATION_YEAR)
// Opening Manage saved cards to dismiss here the Secure your credit prompt
clickManageSavedCardsButton()
clickSecuredCreditCardsLaterButton()
}.goBackToAutofillSettings {
}.goBack {
}.goBack {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickCardNumberTextBox()
clickSelectCreditCardButton()
clickCreditCardSuggestion(MOCK_LAST_CARD_DIGITS)
verifyAutofilledCreditCard(MOCK_CREDIT_CARD_NUMBER)
}
}
@SmokeTest
@Test
fun deleteSavedCreditCardTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickAddCreditCardButton()
fillAndSaveCreditCard(MOCK_CREDIT_CARD_NUMBER, MOCK_NAME_ON_CARD, MOCK_EXPIRATION_MONTH, MOCK_EXPIRATION_YEAR)
clickManageSavedCardsButton()
clickSecuredCreditCardsLaterButton()
clickSavedCreditCard()
clickDeleteCreditCardButton()
clickConfirmDeleteCreditCardButton()
verifyAddCreditCardsButton()
}
}
@SmokeTest
@Test
fun switchLanguageTest() {

@ -0,0 +1,256 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.setNetworkEnabled
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.settingsScreen
/**
* Tests for verifying the Settings for:
* Delete Browsing Data
* Delete Browsing Data on quit
*
*/
class SettingsDeleteBrowsingDataTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun deleteBrowsingDataOptionStatesTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyAllCheckBoxesAreChecked()
switchBrowsingHistoryCheckBox()
switchCachedFilesCheckBox()
verifyOpenTabsCheckBox(true)
verifyBrowsingHistoryDetails(false)
verifyCookiesCheckBox(true)
verifyCachedFilesCheckBox(false)
verifySitePermissionsCheckBox(true)
verifyDownloadsCheckBox(true)
}
restartApp(activityTestRule)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsCheckBox(true)
verifyBrowsingHistoryDetails(false)
verifyCookiesCheckBox(true)
verifyCachedFilesCheckBox(false)
verifySitePermissionsCheckBox(true)
verifyDownloadsCheckBox(true)
switchOpenTabsCheckBox()
switchBrowsingHistoryCheckBox()
switchCookiesCheckBox()
switchCachedFilesCheckBox()
switchSitePermissionsCheckBox()
switchDownloadsCheckBox()
verifyOpenTabsCheckBox(false)
verifyBrowsingHistoryDetails(true)
verifyCookiesCheckBox(false)
verifyCachedFilesCheckBox(true)
verifySitePermissionsCheckBox(false)
verifyDownloadsCheckBox(false)
}
restartApp(activityTestRule)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsCheckBox(false)
verifyBrowsingHistoryDetails(true)
verifyCookiesCheckBox(false)
verifyCachedFilesCheckBox(true)
verifySitePermissionsCheckBox(false)
verifyDownloadsCheckBox(false)
}
}
@Test
fun deleteTabsDataWithNoOpenTabsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyAllCheckBoxesAreChecked()
selectOnlyOpenTabsCheckBox()
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
}
settingsScreen {
verifyGeneralHeading()
}
}
@SmokeTest
@Test
fun deleteTabsDataTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyAllCheckBoxesAreChecked()
selectOnlyOpenTabsCheckBox()
clickDeleteBrowsingDataButton()
clickDialogCancelButton()
verifyOpenTabsCheckBox(true)
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
}
settingsScreen {
verifyGeneralHeading()
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsDetails("0")
}.goBack {
}.goBack {
}.openTabDrawer {
verifyNoOpenTabsInNormalBrowsing()
}
}
@SmokeTest
@Test
fun deleteBrowsingHistoryAndSiteDataTest() {
val storageWritePage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageCheckPage = getStorageTestAsset(mockWebServer, "storage_check.html").url
navigationToolbar {
}.enterURLAndEnterToBrowser(storageWritePage) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage has value")
verifyPageContent("Local storage has value")
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyBrowsingHistoryDetails("2")
selectOnlyBrowsingHistoryCheckBox()
clickDeleteBrowsingDataButton()
clickDialogCancelButton()
verifyBrowsingHistoryDetails(true)
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
verifyBrowsingHistoryDetails("0")
exitMenu()
}
navigationToolbar {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()
mDevice.pressBack()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage empty")
verifyPageContent("Local storage empty")
}
}
@SmokeTest
@Test
fun deleteCookiesTest() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val cookiesTestPage = getStorageTestAsset(mockWebServer, "storage_write.html").url
// Browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(cookiesTestPage) {
verifyPageContent("No cookies set")
clickSetCookiesButton()
verifyPageContent("user=android")
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
selectOnlyCookiesCheckBox()
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyPageContent("No cookies set")
}
}
@SmokeTest
@Test
fun deleteCachedFilesTest() {
val pocketTopArticles = getStringResource(R.string.pocket_pinned_top_articles)
homeScreen {
verifyExistingTopSitesTabs(pocketTopArticles)
}.openTopSiteTabWithTitle(pocketTopArticles) {
waitForPageToLoad()
}.openTabDrawer {
}.openNewTab {
}.submitQuery("about:cache") {
// disabling wifi to prevent downloads in the background
setNetworkEnabled(enabled = false)
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
selectOnlyCachedFilesCheckBox()
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyNetworkCacheIsEmpty("memory")
verifyNetworkCacheIsEmpty("disk")
}
setNetworkEnabled(enabled = true)
}
}

@ -0,0 +1,196 @@
/* 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.core.net.toUri
import androidx.test.espresso.Espresso.pressBack
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class SettingsHTTPSOnlyModeTest {
private val httpPageUrl = "http://example.com/"
private val httpsPageUrl = "https://example.com/"
private val insecureHttpPage = "http.badssl.com"
// "HTTPs not supported" error page contents:
private val httpsOnlyErrorTitle = "Secure site not available"
private val httpsOnlyErrorMessage = "Most likely, the website simply does not support HTTPS."
private val httpsOnlyErrorMessage2 = "However, its also possible that an attacker is involved. If you continue to the website, you should not enter any sensitive info. If you continue, HTTPS-Only mode will be turned off temporarily for the site."
private val httpsOnlyContinueButton = "Continue to HTTP Site"
private val httpsOnlyBackButton = "Go Back (Recommended)"
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Test
fun httpsOnlyModeMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHttpsOnlyModeMenu {
verifyHttpsOnlyModeMenuHeader()
verifyHttpsOnlyModeSummary()
verifyHttpsOnlyModeIsEnabled(false)
verifyHttpsOnlyModeOptionsEnabled(false)
verifyHttpsOnlyOptionSelected(
allTabsOptionSelected = false,
privateTabsOptionSelected = false,
)
clickHttpsOnlyModeSwitch()
verifyHttpsOnlyModeIsEnabled(true)
verifyHttpsOnlyModeOptionsEnabled(true)
verifyHttpsOnlyOptionSelected(
allTabsOptionSelected = true,
privateTabsOptionSelected = false,
)
}.goBack {
verifySettingsToolbar()
}
}
@SmokeTest
@Test
fun httpsOnlyModeEnabledInNormalBrowsingTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHttpsOnlyModeMenu {
clickHttpsOnlyModeSwitch()
verifyHttpsOnlyOptionSelected(
allTabsOptionSelected = true,
privateTabsOptionSelected = false,
)
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(httpPageUrl.toUri()) {
waitForPageToLoad()
}.openNavigationToolbar {
verifyUrl(httpsPageUrl)
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
verifyPageContent(httpsOnlyErrorTitle)
verifyPageContent(httpsOnlyErrorMessage)
verifyPageContent(httpsOnlyErrorMessage2)
verifyPageContent(httpsOnlyBackButton)
clickLinkMatchingText(httpsOnlyBackButton)
verifyPageContent("Example Domain")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
clickLinkMatchingText(httpsOnlyContinueButton)
verifyPageContent("http.badssl.com")
}
}
@Test
fun httpsOnlyModeExceptionPersistsForCurrentSession() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHttpsOnlyModeMenu {
clickHttpsOnlyModeSwitch()
verifyHttpsOnlyOptionSelected(
allTabsOptionSelected = true,
privateTabsOptionSelected = false,
)
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
verifyPageContent(httpsOnlyErrorTitle)
clickLinkMatchingText(httpsOnlyContinueButton)
verifyPageContent("http.badssl.com")
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
verifyPageContent("http.badssl.com")
}
}
@Test
fun httpsOnlyModeEnabledOnlyInPrivateBrowsingTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHttpsOnlyModeMenu {
clickHttpsOnlyModeSwitch()
selectHttpsOnlyModeOption(
allTabsOptionSelected = false,
privateTabsOptionSelected = true,
)
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
verifyPageContent("http.badssl.com")
}.goToHomescreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(httpPageUrl.toUri()) {
waitForPageToLoad()
}.openNavigationToolbar {
verifyUrl(httpsPageUrl)
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
verifyPageContent(httpsOnlyErrorTitle)
verifyPageContent(httpsOnlyErrorMessage)
verifyPageContent(httpsOnlyErrorMessage2)
verifyPageContent(httpsOnlyBackButton)
clickLinkMatchingText(httpsOnlyBackButton)
verifyPageContent("Example Domain")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(insecureHttpPage.toUri()) {
clickLinkMatchingText(httpsOnlyContinueButton)
verifyPageContent("http.badssl.com")
}
}
@Test
fun turnOffHttpsOnlyModeTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHttpsOnlyModeMenu {
clickHttpsOnlyModeSwitch()
verifyHttpsOnlyOptionSelected(
allTabsOptionSelected = true,
privateTabsOptionSelected = false,
)
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(httpPageUrl.toUri()) {
waitForPageToLoad()
}.openNavigationToolbar {
verifyUrl(httpsPageUrl)
pressBack()
}
browserScreen {
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openHttpsOnlyModeMenu {
clickHttpsOnlyModeSwitch()
verifyHttpsOnlyModeIsEnabled(false)
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(httpPageUrl.toUri()) {
waitForPageToLoad()
}.openNavigationToolbar {
verifyUrl(httpPageUrl)
pressBack()
}
}
}

@ -1,736 +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 android.os.Build
import android.view.autofill.AutofillManager
import androidx.core.net.toUri
import androidx.test.filters.SdkSuppress
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.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getStorageTestAsset
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.generateRandomString
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.openAppFromExternalLink
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.setNetworkEnabled
import org.mozilla.fenix.ui.robots.addToHomeScreen
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.settingsScreen
/**
* Tests for verifying the main three dot menu options
*
*/
class SettingsPrivacyTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val pageShortcutName = generateRandomString(5)
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
appContext.settings().userOptOutOfReEngageCookieBannerDialog = true
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
val autofillManager: AutofillManager =
appContext.getSystemService(AutofillManager::class.java)
autofillManager.disableAutofillServices()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// Walks through settings privacy menu and sub-menus to ensure all items are present
@Test
fun settingsPrivacyItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
// PRIVACY
verifyPrivacyHeading()
// PRIVATE BROWSING
verifyPrivateBrowsingButton()
}.openPrivateBrowsingSubMenu {
verifyNavigationToolBarHeader()
}.goBack {
// HTTPS-Only Mode
verifyHTTPSOnlyModeButton()
verifyHTTPSOnlyModeState("Off")
// ENHANCED TRACKING PROTECTION
verifyEnhancedTrackingProtectionButton()
verifyEnhancedTrackingProtectionState("On")
}.openEnhancedTrackingProtectionSubMenu {
verifyNavigationToolBarHeader()
verifyEnhancedTrackingProtectionProtectionSubMenuItems()
// ENHANCED TRACKING PROTECTION EXCEPTION
}.openExceptions {
verifyNavigationToolBarHeader()
verifyEnhancedTrackingProtectionProtectionExceptionsSubMenuItems()
}.goBack {
}.goBack {
// SITE PERMISSIONS
verifySitePermissionsButton()
}.openSettingsSubMenuSitePermissions {
verifyNavigationToolBarHeader()
verifySitePermissionsSubMenuItems()
// SITE PERMISSIONS AUTOPLAY
}.openAutoPlay {
verifyNavigationToolBarHeader("Autoplay")
verifySitePermissionsAutoPlaySubMenuItems()
}.goBack {
// SITE PERMISSIONS CAMERA
}.openCamera {
verifyNavigationToolBarHeader("Camera")
verifySitePermissionsCommonSubMenuItems()
verifyToggleNameToON("3. Toggle Camera to ON")
}.goBack {
// SITE PERMISSIONS LOCATION
}.openLocation {
verifyNavigationToolBarHeader("Location")
verifySitePermissionsCommonSubMenuItems()
verifyToggleNameToON("3. Toggle Location to ON")
}.goBack {
// SITE PERMISSIONS MICROPHONE
}.openMicrophone {
verifyNavigationToolBarHeader("Microphone")
verifySitePermissionsCommonSubMenuItems()
verifyToggleNameToON("3. Toggle Microphone to ON")
}.goBack {
// SITE PERMISSIONS NOTIFICATION
}.openNotification {
verifyNavigationToolBarHeader("Notification")
verifySitePermissionsNotificationSubMenuItems()
}.goBack {
// SITE PERMISSIONS PERSISTENT STORAGE
}.openPersistentStorage {
verifyNavigationToolBarHeader("Persistent Storage")
verifySitePermissionsPersistentStorageSubMenuItems()
}.goBack {
// SITE PERMISSIONS EXCEPTIONS
}.openExceptions {
verifyNavigationToolBarHeader()
verifySitePermissionsExceptionSubMenuItems()
}.goBack {
}.goBack {
// DELETE BROWSING DATA
verifyDeleteBrowsingDataButton()
}.openSettingsSubMenuDeleteBrowsingData {
verifyNavigationToolBarHeader()
verifyDeleteBrowsingDataSubMenuItems()
}.goBack {
// DELETE BROWSING DATA ON QUIT
verifyDeleteBrowsingDataOnQuitButton()
verifyDeleteBrowsingDataOnQuitState("Off")
}.openSettingsSubMenuDeleteBrowsingDataOnQuit {
verifyNavigationToolBarHeader()
verifyDeleteBrowsingDataOnQuitSubMenuItems()
}.goBack {
// NOTIFICATIONS
verifyNotificationsButton()
}.openSettingsSubMenuNotifications {
verifySystemNotificationsView()
}.goBack {
// DATA COLLECTION
verifyDataCollectionButton()
}.openSettingsSubMenuDataCollection {
verifyNavigationToolBarHeader()
verifyDataCollectionSubMenuItems()
}.goBack {
}.goBack {
verifyHomeComponent()
}
}
// Tests only for initial state without signing in.
// For tests after singing in, see SyncIntegration test suite
@Test
fun loginsAndPasswordsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
// Necessary to scroll a little bit for all screen sizes
TestHelper.scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
verifyDefaultValueAutofillLogins(InstrumentationRegistry.getInstrumentation().targetContext)
verifyDefaultValueExceptions()
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that logins list is empty
// Issue #7272 nothing is shown
}.goBack {
}.openSyncLogins {
verifyReadyToScanOption()
verifyUseEmailOption()
}
}
@Test
fun saveLoginFromPromptTest() {
val saveLoginTest =
TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
verifySaveLoginPromptIsShown()
// Click save to save the login
saveLoginFromPrompt("Save")
}
browserScreen {
}.openThreeDotMenu {
}.openSettings {
TestHelper.scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that the login appears correctly
verifySavedLoginFromPrompt("test@example.com")
}
}
@Test
fun neverSaveLoginFromPromptTest() {
val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
verifySaveLoginPromptIsShown()
// Don't save the login, add to exceptions
saveLoginFromPrompt("Never save")
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
verifyDefaultView()
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that the login list is empty
verifyNotSavedLoginFromPrompt()
}.goBack {
}.openLoginExceptions {
// Verify localhost was added to exceptions list
verifyLocalhostExceptionAdded()
}
}
@Test
fun saveLoginsAndPasswordsOptions() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.saveLoginsAndPasswordsOptions {
verifySaveLoginsOptionsView()
}
}
@SmokeTest
@Test
fun openWebsiteForSavedLoginTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
val userName = "test"
val password = "pass"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials(userName, password)
saveLoginFromPrompt("Save")
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
viewSavedLoginDetails(userName)
}.goToSavedWebsite {
verifyUrl(originWebsite)
}
}
@SmokeTest
@Test
fun verifyMultipleLoginsSelectionsTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
fillAndSubmitLoginCredentials("mozilla", "firefox")
saveLoginFromPrompt("Save")
fillAndSubmitLoginCredentials("firefox", "mozilla")
saveLoginFromPrompt("Save")
clearUserNameLoginCredential()
clickSuggestedLoginsButton()
verifySuggestedUserName("firefox")
verifySuggestedUserName("mozilla")
clickLoginSuggestion("mozilla")
verifyPrefilledLoginCredentials("mozilla")
}
}
@Test
fun verifyPrivateBrowsingMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
verifyAddPrivateBrowsingShortcutButton()
verifyOpenLinksInPrivateTab()
verifyOpenLinksInPrivateTabOff()
}.goBack {
verifySettingsView()
}
}
@Test
fun openExternalLinksInPrivateTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
setOpenLinksInPrivateOn()
openAppFromExternalLink(firstWebPage.url.toString())
browserScreen {
verifyUrl(firstWebPage.url.toString())
}.openTabDrawer {
verifyPrivateModeSelected()
}.closeTabDrawer {
}.goToHomescreen { }
setOpenLinksInPrivateOff()
// We need to open a different link, otherwise it will open the same session
openAppFromExternalLink(secondWebPage.url.toString())
browserScreen {
verifyUrl(secondWebPage.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
}
}
@Test
fun launchPageShortcutInPrivateModeTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
setOpenLinksInPrivateOn()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openAddToHomeScreen {
addShortcutName(pageShortcutName)
clickAddShortcutButton()
clickAddAutomaticallyButton()
verifyShortcutAdded(pageShortcutName)
}
mDevice.waitForIdle()
// We need to close the existing tab here, to open a different session
restartApp(activityTestRule)
browserScreen {
}.openTabDrawer {
closeTab()
}
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(pageShortcutName) {
}.openTabDrawer {
verifyPrivateModeSelected()
}
}
@Test
fun launchLinksInPrivateToggleOffStateDoesntChangeTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
setOpenLinksInPrivateOn()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
addShortcutName(pageShortcutName)
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(pageShortcutName) {
}.goToHomescreen { }
setOpenLinksInPrivateOff()
restartApp(activityTestRule)
mDevice.waitForIdle()
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(pageShortcutName) {
}.openTabDrawer {
verifyNormalModeSelected()
}.closeTabDrawer {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
verifyOpenLinksInPrivateTabOff()
}
}
@Test
fun addPrivateBrowsingShortcut() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
cancelPrivateShortcutAddition()
addPrivateShortcutToHomescreen()
verifyPrivateBrowsingShortcutIcon()
}.openPrivateBrowsingShortcut {
verifySearchView()
}.openBrowser {
}.openTabDrawer {
verifyPrivateModeSelected()
}
}
// Verifies that you can go to System settings and change app's permissions from inside the app
@SmokeTest
@Test
@SdkSuppress(minSdkVersion = 29)
fun redirectToAppPermissionsSystemSettingsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuSitePermissions {
}.openCamera {
verifyBlockedByAndroid()
}.goBack {
}.openLocation {
verifyBlockedByAndroid()
}.goBack {
}.openMicrophone {
verifyBlockedByAndroid()
clickGoToSettingsButton()
openAppSystemPermissionsSettings()
switchAppPermissionSystemSetting("Camera", "Allow")
goBackToSystemAppPermissionSettings()
verifySystemGrantedPermission("Camera")
switchAppPermissionSystemSetting("Location", "Allow")
goBackToSystemAppPermissionSettings()
verifySystemGrantedPermission("Location")
switchAppPermissionSystemSetting("Microphone", "Allow")
goBackToSystemAppPermissionSettings()
verifySystemGrantedPermission("Microphone")
goBackToPermissionsSettingsSubMenu()
verifyUnblockedByAndroid()
}.goBack {
}.openLocation {
verifyUnblockedByAndroid()
}.goBack {
}.openCamera {
verifyUnblockedByAndroid()
}
}
@Test
fun deleteBrowsingDataOptionStatesTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyAllCheckBoxesAreChecked()
switchBrowsingHistoryCheckBox()
switchCachedFilesCheckBox()
verifyOpenTabsCheckBox(true)
verifyBrowsingHistoryDetails(false)
verifyCookiesCheckBox(true)
verifyCachedFilesCheckBox(false)
verifySitePermissionsCheckBox(true)
verifyDownloadsCheckBox(true)
}
restartApp(activityTestRule)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsCheckBox(true)
verifyBrowsingHistoryDetails(false)
verifyCookiesCheckBox(true)
verifyCachedFilesCheckBox(false)
verifySitePermissionsCheckBox(true)
verifyDownloadsCheckBox(true)
switchOpenTabsCheckBox()
switchBrowsingHistoryCheckBox()
switchCookiesCheckBox()
switchCachedFilesCheckBox()
switchSitePermissionsCheckBox()
switchDownloadsCheckBox()
verifyOpenTabsCheckBox(false)
verifyBrowsingHistoryDetails(true)
verifyCookiesCheckBox(false)
verifyCachedFilesCheckBox(true)
verifySitePermissionsCheckBox(false)
verifyDownloadsCheckBox(false)
}
restartApp(activityTestRule)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsCheckBox(false)
verifyBrowsingHistoryDetails(true)
verifyCookiesCheckBox(false)
verifyCachedFilesCheckBox(true)
verifySitePermissionsCheckBox(false)
verifyDownloadsCheckBox(false)
}
}
@Test
fun deleteTabsDataWithNoOpenTabsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyAllCheckBoxesAreChecked()
selectOnlyOpenTabsCheckBox()
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
}
settingsScreen {
verifyGeneralHeading()
}
}
@SmokeTest
@Test
fun deleteTabsDataTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyAllCheckBoxesAreChecked()
selectOnlyOpenTabsCheckBox()
clickDeleteBrowsingDataButton()
clickDialogCancelButton()
verifyOpenTabsCheckBox(true)
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
}
settingsScreen {
verifyGeneralHeading()
}.openSettingsSubMenuDeleteBrowsingData {
verifyOpenTabsDetails("0")
}.goBack {
}.goBack {
}.openTabDrawer {
verifyNoOpenTabsInNormalBrowsing()
}
}
@SmokeTest
@Test
fun deleteBrowsingHistoryAndSiteDataTest() {
val storageWritePage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageCheckPage = getStorageTestAsset(mockWebServer, "storage_check.html").url
navigationToolbar {
}.enterURLAndEnterToBrowser(storageWritePage) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage has value")
verifyPageContent("Local storage has value")
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyBrowsingHistoryDetails("2")
selectOnlyBrowsingHistoryCheckBox()
clickDeleteBrowsingDataButton()
clickDialogCancelButton()
verifyBrowsingHistoryDetails(true)
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
verifyBrowsingHistoryDetails("0")
exitMenu()
}
navigationToolbar {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()
mDevice.pressBack()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage empty")
verifyPageContent("Local storage empty")
}
}
@SmokeTest
@Test
fun deleteCookiesTest() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val cookiesTestPage = getStorageTestAsset(mockWebServer, "storage_write.html").url
// Browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(cookiesTestPage) {
verifyPageContent("No cookies set")
clickSetCookiesButton()
verifyPageContent("user=android")
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
selectOnlyCookiesCheckBox()
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyPageContent("No cookies set")
}
}
@SmokeTest
@Test
fun deleteCachedFilesTest() {
val pocketTopArticles = getStringResource(R.string.pocket_pinned_top_articles)
homeScreen {
verifyExistingTopSitesTabs(pocketTopArticles)
}.openTopSiteTabWithTitle(pocketTopArticles) {
waitForPageToLoad()
}.openTabDrawer {
}.openNewTab {
}.submitQuery("about:cache") {
// disabling wifi to prevent downloads in the background
setNetworkEnabled(enabled = false)
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
selectOnlyCachedFilesCheckBox()
clickDeleteBrowsingDataButton()
confirmDeletionAndAssertSnackbar()
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyNetworkCacheIsEmpty("memory")
verifyNetworkCacheIsEmpty("disk")
}
setNetworkEnabled(enabled = true)
}
@SmokeTest
@Test
fun saveLoginsInPWATest() {
val pwaPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val shortcutTitle = "TEST_APP"
navigationToolbar {
}.enterURLAndEnterToBrowser(pwaPage.toUri()) {
verifyNotificationDotOnMainMenu()
}.openThreeDotMenu {
}.clickInstall {
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(shortcutTitle) {
mDevice.waitForIdle()
fillAndSubmitLoginCredentials("mozilla", "firefox")
verifySaveLoginPromptIsDisplayed()
saveLoginFromPrompt("Save")
openAppFromExternalLink(pwaPage)
browserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
verifySavedLoginFromPrompt("mozilla")
}
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(shortcutTitle) {
verifyPrefilledPWALoginCredentials("mozilla", shortcutTitle)
}
}
}
}
private fun setOpenLinksInPrivateOn() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
verifyOpenLinksInPrivateTabEnabled()
clickOpenLinksInPrivateTabSwitch()
}.goBack {
}.goBack {
verifyHomeComponent()
}
}
private fun setOpenLinksInPrivateOff() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
clickOpenLinksInPrivateTabSwitch()
verifyOpenLinksInPrivateTabOff()
}.goBack {
}.goBack {
verifyHomeComponent()
}
}

@ -0,0 +1,190 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.addToHomeScreen
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class SettingsPrivateBrowsingTest {
private lateinit var mockWebServer: MockWebServer
private val pageShortcutName = TestHelper.generateRandomString(5)
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun verifyPrivateBrowsingMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
verifyAddPrivateBrowsingShortcutButton()
verifyOpenLinksInPrivateTab()
verifyOpenLinksInPrivateTabOff()
}.goBack {
verifySettingsView()
}
}
@Test
fun openExternalLinksInPrivateTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
setOpenLinksInPrivateOn()
TestHelper.openAppFromExternalLink(firstWebPage.url.toString())
browserScreen {
verifyUrl(firstWebPage.url.toString())
}.openTabDrawer {
verifyPrivateModeSelected()
}.closeTabDrawer {
}.goToHomescreen { }
setOpenLinksInPrivateOff()
// We need to open a different link, otherwise it will open the same session
TestHelper.openAppFromExternalLink(secondWebPage.url.toString())
browserScreen {
verifyUrl(secondWebPage.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
}
}
@Test
fun launchPageShortcutInPrivateModeTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
setOpenLinksInPrivateOn()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openAddToHomeScreen {
addShortcutName(pageShortcutName)
clickAddShortcutButton()
clickAddAutomaticallyButton()
verifyShortcutAdded(pageShortcutName)
}
mDevice.waitForIdle()
// We need to close the existing tab here, to open a different session
TestHelper.restartApp(activityTestRule)
browserScreen {
}.openTabDrawer {
closeTab()
}
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(pageShortcutName) {
}.openTabDrawer {
verifyPrivateModeSelected()
}
}
@Test
fun launchLinksInPrivateToggleOffStateDoesntChangeTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
setOpenLinksInPrivateOn()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
addShortcutName(pageShortcutName)
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(pageShortcutName) {
}.goToHomescreen { }
setOpenLinksInPrivateOff()
TestHelper.restartApp(activityTestRule)
mDevice.waitForIdle()
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(pageShortcutName) {
}.openTabDrawer {
verifyNormalModeSelected()
}.closeTabDrawer {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
verifyOpenLinksInPrivateTabOff()
}
}
@Test
fun addPrivateBrowsingShortcut() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
cancelPrivateShortcutAddition()
addPrivateShortcutToHomescreen()
verifyPrivateBrowsingShortcutIcon()
}.openPrivateBrowsingShortcut {
verifySearchView()
}.openBrowser {
}.openTabDrawer {
verifyPrivateModeSelected()
}
}
}
private fun setOpenLinksInPrivateOn() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
verifyOpenLinksInPrivateTabEnabled()
clickOpenLinksInPrivateTabSwitch()
}.goBack {
}.goBack {
verifyHomeComponent()
}
}
private fun setOpenLinksInPrivateOff() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openPrivateBrowsingSubMenu {
clickOpenLinksInPrivateTabSwitch()
verifyOpenLinksInPrivateTabOff()
}.goBack {
}.goBack {
verifyHomeComponent()
}
}

@ -9,6 +9,7 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
@ -17,6 +18,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.runWithCondition
import org.mozilla.fenix.helpers.TestHelper.setTextToClipBoard
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -383,23 +385,46 @@ class SettingsSearchTest {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
deleteMultipleSearchEngines(
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
)
verifyDefaultSearchEngine("Wikipedia")
verifyThreeDotButtonIsNotDisplayed("Wikipedia")
openAddSearchEngineMenu()
verifyAddSearchEngineListContains(
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
)
runWithCondition(!appContext.settings().showUnifiedSearchFeature) {
// If the feature is disabled run old steps.
deleteMultipleSearchEngines(
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
)
verifyDefaultSearchEngine("Wikipedia")
verifyThreeDotButtonIsNotDisplayed("Wikipedia")
openAddSearchEngineMenu()
verifyAddSearchEngineListContains(
"Google",
"Bing",
"Amazon.com",
"DuckDuckGo",
"eBay",
)
}
runWithCondition(appContext.settings().showUnifiedSearchFeature) {
// Run steps suitable for the enabled unified search feature.
deleteMultipleSearchEngines(
"Google",
"Bing",
"Amazon.com",
"eBay",
"Wikipedia",
)
verifyDefaultSearchEngine("DuckDuckGo")
verifyThreeDotButtonIsNotDisplayed("DuckDuckGo")
openAddSearchEngineMenu()
verifyAddSearchEngineListContains(
"Google",
"Bing",
"Amazon.com",
"eBay",
"Wikipedia",
)
}
}
}

@ -0,0 +1,52 @@
package org.mozilla.fenix.ui
import androidx.test.filters.SdkSuppress
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen
class SettingsSitePermissionsTest {
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
// Verifies that you can go to System settings and change app's permissions from inside the app
@SmokeTest
@Test
@SdkSuppress(minSdkVersion = 29)
fun redirectToAppPermissionsSystemSettingsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuSitePermissions {
}.openCamera {
verifyBlockedByAndroid()
}.goBack {
}.openLocation {
verifyBlockedByAndroid()
}.goBack {
}.openMicrophone {
verifyBlockedByAndroid()
clickGoToSettingsButton()
openAppSystemPermissionsSettings()
switchAppPermissionSystemSetting("Camera", "Allow")
goBackToSystemAppPermissionSettings()
verifySystemGrantedPermission("Camera")
switchAppPermissionSystemSetting("Location", "Allow")
goBackToSystemAppPermissionSettings()
verifySystemGrantedPermission("Location")
switchAppPermissionSystemSetting("Microphone", "Allow")
goBackToSystemAppPermissionSettings()
verifySystemGrantedPermission("Microphone")
goBackToPermissionsSettingsSubMenu()
verifyUnblockedByAndroid()
}.goBack {
}.openLocation {
verifyUnblockedByAndroid()
}.goBack {
}.openCamera {
verifyUnblockedByAndroid()
}
}
}

@ -14,6 +14,7 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.ui.robots.homeScreen
/**
* Tests for verifying the main three dot menu options
@ -27,7 +28,7 @@ class SettingsTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
@ -43,6 +44,111 @@ class SettingsTest {
mockWebServer.shutdown()
}
// Walks through settings privacy menu and sub-menus to ensure all items are present
@Test
fun settingsPrivacyItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
// PRIVACY
verifyPrivacyHeading()
// PRIVATE BROWSING
verifyPrivateBrowsingButton()
}.openPrivateBrowsingSubMenu {
verifyNavigationToolBarHeader()
}.goBack {
// HTTPS-Only Mode
verifyHTTPSOnlyModeButton()
verifyHTTPSOnlyModeState("Off")
// ENHANCED TRACKING PROTECTION
verifyEnhancedTrackingProtectionButton()
verifyEnhancedTrackingProtectionState("Standard")
}.openEnhancedTrackingProtectionSubMenu {
verifyNavigationToolBarHeader()
verifyEnhancedTrackingProtectionProtectionSubMenuItems()
// ENHANCED TRACKING PROTECTION EXCEPTION
}.openExceptions {
verifyNavigationToolBarHeader()
verifyEnhancedTrackingProtectionProtectionExceptionsSubMenuItems()
}.goBack {
}.goBack {
// SITE PERMISSIONS
verifySitePermissionsButton()
}.openSettingsSubMenuSitePermissions {
verifyNavigationToolBarHeader()
verifySitePermissionsSubMenuItems()
// SITE PERMISSIONS AUTOPLAY
}.openAutoPlay {
verifyNavigationToolBarHeader("Autoplay")
verifySitePermissionsAutoPlaySubMenuItems()
}.goBack {
// SITE PERMISSIONS CAMERA
}.openCamera {
verifyNavigationToolBarHeader("Camera")
verifySitePermissionsCommonSubMenuItems()
verifyToggleNameToON("3. Toggle Camera to ON")
}.goBack {
// SITE PERMISSIONS LOCATION
}.openLocation {
verifyNavigationToolBarHeader("Location")
verifySitePermissionsCommonSubMenuItems()
verifyToggleNameToON("3. Toggle Location to ON")
}.goBack {
// SITE PERMISSIONS MICROPHONE
}.openMicrophone {
verifyNavigationToolBarHeader("Microphone")
verifySitePermissionsCommonSubMenuItems()
verifyToggleNameToON("3. Toggle Microphone to ON")
}.goBack {
// SITE PERMISSIONS NOTIFICATION
}.openNotification {
verifyNavigationToolBarHeader("Notification")
verifySitePermissionsNotificationSubMenuItems()
}.goBack {
// SITE PERMISSIONS PERSISTENT STORAGE
}.openPersistentStorage {
verifyNavigationToolBarHeader("Persistent Storage")
verifySitePermissionsPersistentStorageSubMenuItems()
}.goBack {
// SITE PERMISSIONS EXCEPTIONS
}.openExceptions {
verifyNavigationToolBarHeader()
verifySitePermissionsExceptionSubMenuItems()
}.goBack {
}.goBack {
// DELETE BROWSING DATA
verifyDeleteBrowsingDataButton()
}.openSettingsSubMenuDeleteBrowsingData {
verifyNavigationToolBarHeader()
verifyDeleteBrowsingDataSubMenuItems()
}.goBack {
// DELETE BROWSING DATA ON QUIT
verifyDeleteBrowsingDataOnQuitButton()
verifyDeleteBrowsingDataOnQuitState("Off")
}.openSettingsSubMenuDeleteBrowsingDataOnQuit {
verifyNavigationToolBarHeader()
verifyDeleteBrowsingDataOnQuitSubMenuItems()
}.goBack {
// NOTIFICATIONS
verifyNotificationsButton()
}.openSettingsSubMenuNotifications {
verifySystemNotificationsView()
}.goBack {
// DATA COLLECTION
verifyDataCollectionButton()
}.openSettingsSubMenuDataCollection {
verifyNavigationToolBarHeader()
verifyDataCollectionSubMenuItems()
}.goBack {
}.goBack {
verifyHomeComponent()
}
}
// Walks through settings menu and sub-menus to ensure all items are present
@Ignore("This is a stub test, ignore for now")
@Test

@ -8,9 +8,7 @@ import android.Manifest
import android.content.Context
import android.hardware.camera2.CameraManager
import android.media.AudioManager
import android.os.Build
import androidx.core.net.toUri
import androidx.test.filters.SdkSuppress
import androidx.test.rule.GrantPermissionRule
import org.junit.Assume.assumeTrue
import org.junit.Rule
@ -55,7 +53,6 @@ class SitePermissionsTest {
@get: Rule
val retryTestRule = RetryTestRule(3)
@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P, codeName = "P")
@SmokeTest
@Test
fun audioVideoPermissionChoiceOnEachRequestTest() {

@ -25,16 +25,12 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP
import org.mozilla.fenix.helpers.FeatureSettingsHelperDelegate
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.assertNativeAppOpens
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.TestHelper.generateRandomString
import org.mozilla.fenix.helpers.TestHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
import org.mozilla.fenix.ui.robots.browserScreen
@ -58,11 +54,10 @@ class SmokeTest {
private lateinit var mockWebServer: MockWebServer
private val customMenuItem = "TestMenuItem"
private lateinit var browserStore: BrowserStore
private val featureSettingsHelper = FeatureSettingsHelperDelegate()
@get:Rule(order = 0)
val activityTestRule = AndroidComposeTestRule(
HomeActivityIntentTestRule(),
HomeActivityIntentTestRule.withDefaultSettingsOverrides(),
{ it.activity },
)
@ -83,13 +78,6 @@ class SmokeTest {
// So we are initializing this here instead of in all related tests.
browserStore = activityTestRule.activity.components.core.store
// disabling the new homepage pop-up that interferes with the tests.
featureSettingsHelper.apply {
isJumpBackInCFREnabled = false
isTCPCFREnabled = false
isWallpaperOnboardingEnabled = false
}.applyFlagUpdates()
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
@ -100,9 +88,6 @@ class SmokeTest {
@After
fun tearDown() {
mockWebServer.shutdown()
// resetting modified features enabled setting to default
featureSettingsHelper.resetAllFeatureFlags()
}
/* Verifies the nav bar:
@ -233,35 +218,6 @@ class SmokeTest {
}
}
// Verifies the Add to home screen option in a tab's 3 dot menu
@Test
fun mainMenuAddToHomeScreenTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val shortcutTitle = generateRandomString(5)
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
clickCancelShortcutButton()
}
browserScreen {
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
verifyShortcutTextFieldTitle("Test_Page_1")
addShortcutName(shortcutTitle)
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(shortcutTitle) {
verifyUrl(website.url.toString())
verifyTabCounter("1")
}
}
// Verifies the Add to collection option in a tab's 3 dot menu
@Test
fun openMainMenuAddToCollectionTest() {
@ -366,38 +322,6 @@ class SmokeTest {
}
}
// Saves a login, then changes it and verifies the update
@Test
fun updateSavedLoginTest() {
val saveLoginTest =
TestAssetHelper.getSaveLoginAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
verifySaveLoginPromptIsShown()
// Click Save to save the login
saveLoginFromPrompt("Save")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
enterPassword("test")
verifyUpdateLoginPromptIsShown()
// Click Update to change the saved password
saveLoginFromPrompt("Update")
}.openThreeDotMenu {
}.openSettings {
TestHelper.scrollToElementByText("Logins and passwords")
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
// Verify that the login appears correctly
verifySavedLoginFromPrompt("test@example.com")
viewSavedLoginDetails("test@example.com")
revealPassword()
verifyPasswordSaved("test") // failing here locally
}
}
// Verifies that a recently closed item is properly opened
@Test
fun openRecentlyClosedItemTest() {
@ -583,25 +507,6 @@ class SmokeTest {
}
}
@Test
fun addPrivateBrowsingShortcutTest() {
homeScreen {
}.dismissOnboarding()
homeScreen {
}.triggerPrivateBrowsingShortcutPrompt {
verifyNoThanksPrivateBrowsingShortcutButton()
verifyAddPrivateBrowsingShortcutButton()
clickAddPrivateBrowsingShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut("Private $appName") {}
searchScreen {
verifySearchView()
}.dismissSearchBar {
verifyPrivateSessionMessage()
}
}
@Test
fun mainMenuInstallPWATest() {
val pwaPage = "https://mozilla-mobile.github.io/testapp/"

@ -13,12 +13,10 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.defaultTopSitesList
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.getSponsoredShortcutTitle
import org.mozilla.fenix.ui.robots.homeScreen
@ -39,8 +37,8 @@ class SponsoredShortcutsTest {
@Before
fun setUp() {
TestHelper.appContext.settings().userOptOutOfReEngageCookieBannerDialog = true
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()

@ -352,7 +352,7 @@ class TabbedBrowsingTest {
verifyFocusedNavigationToolbar()
// dismiss search dialog
homeScreen { }.pressBack()
verifyPrivateSessionMessage()
verifyCommonMythsLink()
verifyNavigationToolbar()
}
navigationToolbar {

@ -170,6 +170,7 @@ class WebControlsTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickLinkMatchingText("Email link")
clickOpenInAppPromptButton()
assertNativeAppOpens(Constants.PackageName.GMAIL_APP, emailLink)
}
}
@ -181,6 +182,7 @@ class WebControlsTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickLinkMatchingText("Telephone link")
clickOpenInAppPromptButton()
assertNativeAppOpens(Constants.PackageName.PHONE_APP, phoneLink)
}
}

@ -45,6 +45,10 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.MatcherHelper
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
@ -401,18 +405,16 @@ class BrowserRobot {
switchButton.clickAndWaitForNewWindow(waitingTime)
}
fun verifySaveLoginPromptIsShown() = clickPageObject(webPageItemWithResourceId("submit"))
fun clickSubmitLoginButton() = clickPageObject(webPageItemWithResourceId("submit"))
fun verifyUpdateLoginPromptIsShown() {
clickPageObject(webPageItemWithResourceId("submit"))
mDevice.waitNotNull(Until.findObjects(text("Update")))
}
fun verifyUpdateLoginPromptIsShown() = mDevice.waitNotNull(Until.findObjects(text("Update")))
fun saveLoginFromPrompt(optionToSaveLogin: String) {
mDevice.waitForObjects(
mDevice.findObject(
UiSelector().resourceId("$packageName:id/feature_prompt_login_fragment"),
),
waitingTime,
)
mDevice.findObject(text(optionToSaveLogin)).click()
}
@ -493,7 +495,7 @@ class BrowserRobot {
setPageObjectText(webPageItemWithResourceId("password"), password)
clickPageObject(webPageItemWithResourceId("submit"))
mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")))
mDevice.waitForObjects(mDevice.findObject(UiSelector().resourceId("$packageName:id/save_confirm")), waitingTime)
}
fun clearUserNameLoginCredential() {
@ -502,6 +504,12 @@ class BrowserRobot {
mDevice.waitForIdle(waitingTime)
}
fun clickUsernameTextField() =
webPageItemWithResourceId("username").also {
it.waitForExists(waitingTime)
it.click()
}
fun clickSuggestedLoginsButton() {
var currentTries = 0
while (currentTries++ < 3) {
@ -518,18 +526,66 @@ class BrowserRobot {
fun clickStreetAddressTextBox() = clickPageObject(webPageItemWithResourceId("streetAddress"))
fun setTextForApartmentTextBox(apartment: String) =
webPageItemWithResourceId("apartment").setText(apartment)
fun clearAddressForm() {
webPageItemWithResourceId("streetAddress").clearTextField()
webPageItemWithResourceId("city").clearTextField()
webPageItemWithResourceId("zipCode").clearTextField()
webPageItemWithResourceId("country").clearTextField()
webPageItemWithResourceId("telephone").clearTextField()
webPageItemWithResourceId("email").clearTextField()
}
fun clickSelectAddressButton() {
selectAddressButton.waitForExists(waitingTime)
selectAddressButton.clickAndWaitForNewWindow(waitingTime)
}
fun clickCardNumberTextBox() = clickPageObject(webPageItemWithResourceId("cardNumber"))
fun verifySelectAddressButtonExists(exists: Boolean) = assertItemWithResIdExists(selectAddressButton, exists = exists)
fun clickCreditCardNumberTextBox() = clickPageObject(webPageItemWithResourceId("cardNumber"))
fun changeCreditCardExpiryDate(expiryDate: String) =
webPageItemWithResourceId("expiryMonthAndYear").setText(expiryDate)
fun clickCreditCardFormSubmitButton() =
webPageItemWithResourceId("submit").clickAndWaitForNewWindow(waitingTime)
fun fillAndSaveCreditCard(cardNumber: String, cardName: String, expiryMonthAndYear: String) {
webPageItemWithResourceId("cardNumber").setText(cardNumber)
webPageItemWithResourceId("nameOnCard").setText(cardName)
webPageItemWithResourceId("expiryMonthAndYear").setText(expiryMonthAndYear)
webPageItemWithResourceId("submit").clickAndWaitForNewWindow(waitingTime)
}
fun clickCancelCreditCardPromptButton() =
itemWithResId("$packageName:id/save_cancel").also {
it.waitForExists(waitingTime)
it.click()
}
fun clickUpdateOrSaveCreditCardPromptButton() =
itemWithResId("$packageName:id/save_confirm").also {
it.waitForExists(waitingTime)
it.click()
}
fun verifyUpdateOrSaveCreditCardPromptExists(exists: Boolean) =
assertItemWithResIdAndTextExists(
itemWithResId("$packageName:id/save_credit_card_header"),
exists = exists,
)
fun clickSelectCreditCardButton() {
selectCreditCardButton.waitForExists(waitingTime)
selectCreditCardButton.clickAndWaitForNewWindow(waitingTime)
}
fun verifySelectCreditCardPromptExists(exists: Boolean) =
assertItemWithResIdExists(selectCreditCardButton, exists = exists)
fun clickLoginSuggestion(userName: String) {
val loginSuggestion =
mDevice.findObject(
@ -551,6 +607,12 @@ class BrowserRobot {
creditCardSuggestion(creditCardNumber).click()
}
fun verifyCreditCardSuggestion(vararg creditCardNumbers: String) {
for (creditCardNumber in creditCardNumbers) {
assertTrue(creditCardSuggestion(creditCardNumber).waitForExists(waitingTime))
}
}
fun verifySuggestedUserName(userName: String) {
mDevice.findObject(
UiSelector()
@ -562,28 +624,41 @@ class BrowserRobot {
)
}
fun verifyPrefilledLoginCredentials(userName: String) {
var currentTries = 0
fun verifyPrefilledLoginCredentials(userName: String, password: String, credentialsArePrefilled: Boolean) {
// Sometimes the assertion of the pre-filled logins fails so we are re-trying after refreshing the page
while (currentTries++ < 3) {
for (i in 1..RETRY_COUNT) {
try {
mDevice.waitForObjects(webPageItemWithResourceId("username"))
assertTrue(webPageItemWithResourceId("username").text.equals(userName))
if (credentialsArePrefilled) {
mDevice.waitForObjects(webPageItemWithResourceId("username"))
assertTrue(webPageItemWithResourceId("username").text.equals(userName))
mDevice.waitForObjects(webPageItemWithResourceId("password"))
assertTrue(webPageItemWithResourceId("password").text.equals(password))
} else {
mDevice.waitForObjects(webPageItemWithResourceId("username"))
assertFalse(webPageItemWithResourceId("username").text.equals(userName))
mDevice.waitForObjects(webPageItemWithResourceId("password"))
assertFalse(webPageItemWithResourceId("password").text.equals(password))
}
break
} catch (e: AssertionError) {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
clearUserNameLoginCredential()
clickSuggestedLoginsButton()
verifySuggestedUserName(userName)
clickLoginSuggestion(userName)
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
clearUserNameLoginCredential()
clickSuggestedLoginsButton()
verifySuggestedUserName(userName)
clickLoginSuggestion(userName)
clickShowPasswordButton()
}
}
}
}
mDevice.waitForObjects(webPageItemWithResourceId("username"))
assertTrue(webPageItemWithResourceId("username").text.equals(userName))
}
fun verifyAutofilledAddress(streetAddress: String) {
@ -594,6 +669,14 @@ class BrowserRobot {
)
}
fun verifyManuallyFilledAddress(apartment: String) {
mDevice.waitForObjects(webPageItemContainingTextAndResourceId("apartment", apartment))
assertTrue(
webPageItemContainingTextAndResourceId("apartment", apartment)
.waitForExists(waitingTime),
)
}
fun verifyAutofilledCreditCard(creditCardNumber: String) {
mDevice.waitForObjects(webPageItemContainingTextAndResourceId("cardNumber", creditCardNumber))
assertTrue(
@ -628,6 +711,12 @@ class BrowserRobot {
)
}
fun verifySaveLoginPromptIsNotDisplayed() =
assertItemWithResIdExists(
itemWithResId("$packageName:id/feature_prompt_login_fragment"),
exists = false,
)
fun verifyTrackingProtectionWebContent(state: String) {
for (i in 1..RETRY_COUNT) {
try {
@ -885,6 +974,46 @@ class BrowserRobot {
)
}
fun verifyCookieBannerExists(exists: Boolean) {
for (i in 1..RETRY_COUNT) {
try {
assertItemWithResIdExists(cookieBanner, exists = exists)
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
waitForPageToLoad()
}
}
}
}
assertItemWithResIdExists(cookieBanner, exists = exists)
}
fun clickShowPasswordButton() =
itemWithResId("togglePassword").also {
it.waitForExists(waitingTime)
it.click()
}
fun clickOpenInAppPromptButton() =
itemWithResIdAndText("android:id/button1", "OPEN")
.also {
it.waitForExists(waitingTime)
it.click()
}
fun verifyFindInPageBar(exists: Boolean) =
assertItemWithResIdExists(
itemWithResId("$packageName:id/findInPageView"),
exists = exists,
)
class Transition {
private fun threeDotButton() = onView(
allOf(
@ -903,9 +1032,10 @@ class BrowserRobot {
}
fun openNavigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
.waitForExists(waitingTime)
navURLBar().waitForExists(waitingTime)
navURLBar().click()
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_url_view"))
.waitForExists(waitingTime)
NavigationToolbarRobot().interact()
return NavigationToolbarRobot.Transition()
@ -1079,6 +1209,20 @@ class BrowserRobot {
SiteSecurityRobot().interact()
return SiteSecurityRobot.Transition()
}
fun clickManageAddressButton(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
itemWithResId("$packageName:id/manage_addresses").clickAndWaitForNewWindow(waitingTime)
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
}
fun clickManageCreditCardsButton(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
itemWithResId("$packageName:id/manage_credit_cards").clickAndWaitForNewWindow(waitingTime)
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
}
}
}
@ -1210,7 +1354,8 @@ private fun setPageObjectText(webPageItem: UiObject, text: String) {
try {
webPageItem.also {
it.waitForExists(waitingTime)
it.setText(text)
it.clearTextField()
it.text = text
}
break
@ -1232,3 +1377,4 @@ private val currentDate = LocalDate.now()
private val currentDay = currentDate.dayOfMonth
private val currentMonth = currentDate.month
private val currentYear = currentDate.year
private val cookieBanner = itemWithResId("CybotCookiebotDialog")

@ -14,7 +14,6 @@ import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions.pressImeActionButton
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -25,8 +24,14 @@ import androidx.test.uiautomator.Until
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
@ -197,13 +202,9 @@ class CollectionRobot {
title: String,
interact: HomeScreenRobot.() -> Unit,
): HomeScreenRobot.Transition {
try {
collectionTitle(title).waitForExists(waitingTime)
collectionTitle(title).click()
} catch (e: NoMatchingViewException) {
scrollToElementByText(title)
collectionTitle(title).click()
}
assertItemContainingTextExists(itemContainingText(title))
itemContainingText(title).clickAndWaitForNewWindow(waitingTimeShort)
assertItemWithDescriptionExists(itemWithDescription(getStringResource(R.string.remove_tab_from_collection)), exists = false)
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()

@ -143,6 +143,13 @@ class CustomTabRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun goBackToOnboardingScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
mDevice.pressBack()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
}
}

@ -12,7 +12,6 @@ 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.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -82,6 +81,13 @@ class DownloadRobot {
return Transition()
}
fun closeCompletedDownloadPrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
closeCompletedDownloadButton().click()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun closePrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
closePromptButton().click()
@ -90,6 +96,7 @@ class DownloadRobot {
}
fun clickOpen(type: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
openDownloadButton().waitForExists(waitingTime)
openDownloadButton().click()
// verify open intent is matched with associated data type
@ -182,17 +189,18 @@ private fun assertDownloadNotificationPopup() {
)
}
private fun closeCompletedDownloadButton() =
onView(withId(R.id.download_dialog_close_button))
private fun closePromptButton() =
onView(withContentDescription("Close"))
onView(withId(R.id.close_button))
private fun downloadButton() =
onView(withText("Download"))
.inRoot(isDialog())
onView(withId(R.id.download_button))
.check(matches(isDisplayed()))
private fun openDownloadButton() =
onView(withId(R.id.download_dialog_action_button))
.check(matches(isDisplayed()))
mDevice.findObject(UiSelector().resourceId("$packageName:id/download_dialog_action_button"))
private fun downloadedFile(fileName: String) = onView(withText(fileName))

@ -58,12 +58,23 @@ class FindInPageRobot {
}
class Transition {
fun closeFindInPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
fun closeFindInPageWithCloseButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitForIdle()
findInPageCloseButton().click()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun closeFindInPageWithBackButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitForIdle()
// Will need to press back 2x, the first will only dismiss the keyboard
mDevice.pressBack()
mDevice.pressBack()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
}
}

@ -37,6 +37,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
@ -55,11 +56,13 @@ import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.MatcherHelper.assertCheckedItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
@ -74,6 +77,7 @@ import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.withBitmapDrawable
import org.mozilla.fenix.utils.Settings
/**
* Implementation of Robot Pattern for the home screen menu.
@ -89,6 +93,13 @@ class HomeScreenRobot {
fun verifyFocusedNavigationToolbar() = assertFocusedNavigationToolbar()
fun verifyHomeScreen() = assertItemWithResIdExists(homeScreen)
fun verifyPrivateBrowsingHomeScreen() {
verifyHomeScreenAppBarItems()
assertItemContainingTextExists(itemContainingText(privateSessionMessage))
verifyCommonMythsLink()
verifyNavigationToolbarItems()
}
fun verifyHomeScreenAppBarItems() =
assertItemWithResIdExists(homeScreen, privateBrowsingButton, homepageWordmark)
@ -151,9 +162,9 @@ class HomeScreenRobot {
assertItemWithResIdExists(signInButton)
}
fun verifyPrivacyProtectionCard(isStandardChecked: Boolean, isStrictChecked: Boolean) {
fun verifyPrivacyProtectionCard(settings: Settings, isStandardChecked: Boolean, isStrictChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_header_1))
assertItemContainingTextExists(privacyProtectionHeader, privacyProtectionDescription)
assertItemContainingTextExists(privacyProtectionHeader, privacyProtectionDescription(settings))
assertCheckedItemWithResIdExists(
standardTrackingProtectionToggle(isStandardChecked),
strictTrackingProtectionToggle(isStrictChecked),
@ -178,7 +189,7 @@ class HomeScreenRobot {
assertItemContainingTextExists(conclusionHeader)
}
fun verifyNavigationToolbarItems(numberOfOpenTabs: String) {
fun verifyNavigationToolbarItems(numberOfOpenTabs: String = "0") {
assertItemWithResIdExists(navigationToolbar, menuButton)
assertItemWithResIdAndTextExists(tabCounter(numberOfOpenTabs))
}
@ -262,7 +273,8 @@ class HomeScreenRobot {
.onNodeWithText(getStringResource(R.string.onboarding_home_skip_button))
.performClick()
fun verifyPrivateSessionMessage() = assertPrivateSessionMessage()
fun verifyCommonMythsLink() =
assertItemContainingTextExists(itemContainingText(getStringResource(R.string.private_browsing_common_myths)))
fun verifyExistingTopSitesList() = assertExistingTopSitesList()
fun verifyNotExistingTopSitesList(title: String) = assertNotExistingTopSitesList(title)
@ -286,10 +298,15 @@ class HomeScreenRobot {
}
fun verifyTopSiteContextMenuItems() = assertTopSiteContextMenuItems()
fun verifyJumpBackInSectionIsDisplayed() = assertJumpBackInSectionIsDisplayed()
fun verifyJumpBackInSectionIsDisplayed() {
scrollToElementByText(getStringResource(R.string.recent_tabs_header))
assertTrue(jumpBackInSection().waitForExists(waitingTime))
}
fun verifyJumpBackInSectionIsNotDisplayed() = assertJumpBackInSectionIsNotDisplayed()
fun verifyJumpBackInItemTitle(itemTitle: String) = assertJumpBackInItemTitle(itemTitle)
fun verifyJumpBackInItemWithUrl(itemUrl: String) = assertJumpBackInItemWithUrl(itemUrl)
fun verifyJumpBackInItemTitle(testRule: ComposeTestRule, itemTitle: String) =
assertJumpBackInItemTitle(testRule, itemTitle)
fun verifyJumpBackInItemWithUrl(testRule: ComposeTestRule, itemUrl: String) =
assertJumpBackInItemWithUrl(testRule, itemUrl)
fun verifyJumpBackInShowAllButton() = assertJumpBackInShowAllButton()
fun verifyRecentlyVisitedSectionIsDisplayed() = assertRecentlyVisitedSectionIsDisplayed()
fun verifyRecentlyVisitedSectionIsNotDisplayed() = assertRecentlyVisitedSectionIsNotDisplayed()
@ -319,10 +336,8 @@ class HomeScreenRobot {
// Collections elements
fun verifyCollectionIsDisplayed(title: String, collectionExists: Boolean = true) {
if (collectionExists) {
scrollToElementByText(title)
assertTrue(mDevice.findObject(UiSelector().text(title)).waitForExists(waitingTime))
} else {
scrollToElementByText("Collections")
assertTrue(mDevice.findObject(UiSelector().text(title)).waitUntilGone(waitingTime))
}
}
@ -718,13 +733,10 @@ class HomeScreenRobot {
return TabDrawerRobot.Transition()
}
fun expandCollection(title: String, rule: ComposeTestRule, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
homeScreenList().waitForExists(waitingTime)
homeScreenList().scrollToEnd(LISTS_MAXSWIPES)
collectionTitle(title, rule)
.assertIsDisplayed()
.performClick()
fun expandCollection(title: String, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
assertItemContainingTextExists(itemContainingText(title))
itemContainingText(title).clickAndWaitForNewWindow(waitingTimeShort)
assertItemWithDescriptionExists(itemWithDescription(getStringResource(R.string.remove_tab_from_collection)))
CollectionRobot().interact()
return CollectionRobot.Transition()
@ -817,11 +829,11 @@ class HomeScreenRobot {
return SyncSignInRobot.Transition()
}
fun clickPrivacyNoticeButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
fun clickPrivacyNoticeButton(interact: CustomTabRobot.() -> Unit): CustomTabRobot.Transition {
privacyNoticeButton.clickAndWaitForNewWindow(waitingTimeShort)
BrowserRobot().interact()
return BrowserRobot.Transition()
CustomTabRobot().interact()
return CustomTabRobot.Transition()
}
}
}
@ -888,16 +900,6 @@ private fun verifySearchEngineIcon(searchEngineName: String) {
verifySearchEngineIcon(defaultSearchEngine.icon, defaultSearchEngine.name)
}
private fun assertPrivateSessionMessage() =
assertTrue(
mDevice.findObject(
UiSelector()
.textContains(
getStringResource(R.string.private_browsing_common_myths),
),
).waitForExists(waitingTime),
)
private fun collectionTitle(title: String, rule: ComposeTestRule) =
rule.onNode(hasText(title))
@ -986,29 +988,13 @@ private fun assertTopSiteContextMenuItems() {
)
}
private fun assertJumpBackInSectionIsDisplayed() = assertTrue(jumpBackInSection().waitForExists(waitingTime))
private fun assertJumpBackInSectionIsNotDisplayed() = assertFalse(jumpBackInSection().waitForExists(waitingTimeShort))
private fun assertJumpBackInItemTitle(itemTitle: String) =
assertTrue(
mDevice
.findObject(
UiSelector()
.resourceId("recent.tab.title")
.textContains(itemTitle),
).waitForExists(waitingTime),
)
private fun assertJumpBackInItemTitle(testRule: ComposeTestRule, itemTitle: String) =
testRule.onNodeWithTag("recent.tab.title", useUnmergedTree = true).assert(hasText(itemTitle))
private fun assertJumpBackInItemWithUrl(itemUrl: String) =
assertTrue(
mDevice
.findObject(
UiSelector()
.resourceId("recent.tab.url")
.textContains(itemUrl),
).waitForExists(waitingTime),
)
private fun assertJumpBackInItemWithUrl(testRule: ComposeTestRule, itemUrl: String) =
testRule.onNodeWithTag("recent.tab.url", useUnmergedTree = true).assert(hasText(itemUrl))
private fun assertJumpBackInShowAllButton() =
assertTrue(
@ -1059,6 +1045,16 @@ private fun sponsoredShortcut(sponsoredShortcutTitle: String) =
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
private fun privacyProtectionDescription(settings: Settings): UiObject {
val isTCPPublic = settings.enabledTotalCookieProtectionCFR
val descriptionText = when (isTCPPublic) {
true -> R.string.onboarding_tracking_protection_description
false -> R.string.onboarding_tracking_protection_description_old
}
return itemContainingText(getStringResource(descriptionText))
}
private val homeScreen =
itemWithResId("$packageName:id/homeLayout")
private val privateBrowsingButton =
@ -1111,8 +1107,6 @@ private val signInButton =
itemWithResId("$packageName:id/fxa_sign_in_button")
private val privacyProtectionHeader =
itemContainingText(getStringResource(R.string.onboarding_tracking_protection_header))
private val privacyProtectionDescription =
itemContainingText(getStringResource(R.string.onboarding_tracking_protection_description))
private fun standardTrackingProtectionToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/tracking_protection_standard_option", isChecked)
private fun strictTrackingProtectionToggle(isChecked: Boolean) =

@ -44,6 +44,8 @@ import org.mozilla.fenix.helpers.ext.waitNotNull
* Implementation of Robot Pattern for the URL toolbar.
*/
class NavigationToolbarRobot {
fun verifyUrl(url: String) =
onView(withId(R.id.mozac_browser_toolbar_url_view)).check(matches(withText(url)))
fun verifyNoHistoryBookmarks() = assertNoHistoryBookmarks()

@ -22,11 +22,8 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
@ -39,21 +36,21 @@ import androidx.test.uiautomator.Until
import junit.framework.AssertionFailedError
import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.endsWith
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
import org.mozilla.fenix.helpers.Constants.PackageName.GOOGLE_PLAY_SERVICES
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.hasCousin
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.ui.robots.SettingsRobot.Companion.DEFAULT_APPS_SETTINGS_ACTION
@ -85,8 +82,8 @@ class SettingsRobot {
fun verifyHTTPSOnlyModeState(state: String) = assertHTTPSOnlyModeState(state)
fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton()
fun verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
fun verifyEnhancedTrackingProtectionState(state: String) =
assertEnhancedTrackingProtectionState(state)
fun verifyEnhancedTrackingProtectionState(option: String) =
assertEnhancedTrackingProtectionState(option)
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
fun verifySitePermissionsButton() = assertSitePermissionsButton()
fun verifyDeleteBrowsingDataButton() = assertDeleteBrowsingDataButton()
@ -96,8 +93,7 @@ class SettingsRobot {
fun verifyNotificationsButton() = assertNotificationsButton()
fun verifyDataCollectionButton() = assertDataCollectionButton()
fun verifyOpenLinksInAppsButton() = assertOpenLinksInAppsButton()
fun verifyOpenLinksInAppsSwitchState(enabled: Boolean) = assertOpenLinksInAppsSwitchState(enabled)
fun clickOpenLinksInAppsSwitch() = openLinksInAppsButton().click()
fun verifyOpenLinksInAppsState(state: String) = assertOpenLinksInAppsSwitchState(state)
fun verifySettingsView() = assertSettingsView()
fun verifySettingsToolbar() = assertSettingsToolbar()
@ -172,8 +168,11 @@ class SettingsRobot {
}
fun openAutofillSubMenu(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_autofill))).waitForExists(waitingTime)
onView(withText(R.string.preferences_autofill)).click()
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_autofill)))
.also {
it.waitForExists(waitingTime)
it.click()
}
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
@ -218,6 +217,14 @@ class SettingsRobot {
return SettingsSubMenuSetDefaultBrowserRobot.Transition()
}
fun openCookieBannerReductionSubMenu(interact: SettingsSubMenuCookieBannerReductionRobot.() -> Unit): SettingsSubMenuCookieBannerReductionRobot.Transition {
scrollToElementByText(getStringResource(R.string.preferences_cookie_banner_reduction))
itemContainingText(getStringResource(R.string.preferences_cookie_banner_reduction)).click()
SettingsSubMenuCookieBannerReductionRobot().interact()
return SettingsSubMenuCookieBannerReductionRobot.Transition()
}
fun openEnhancedTrackingProtectionSubMenu(interact: SettingsSubMenuEnhancedTrackingProtectionRobot.() -> Unit): SettingsSubMenuEnhancedTrackingProtectionRobot.Transition {
scrollToElementByText("Enhanced Tracking Protection")
fun enhancedTrackingProtectionButton() =
@ -305,6 +312,25 @@ class SettingsRobot {
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
}
fun openOpenLinksInAppsMenu(interact: SettingsSubMenuOpenLinksInAppsRobot.() -> Unit): SettingsSubMenuOpenLinksInAppsRobot.Transition {
openLinksInAppsButton().click()
SettingsSubMenuOpenLinksInAppsRobot().interact()
return SettingsSubMenuOpenLinksInAppsRobot.Transition()
}
fun openHttpsOnlyModeMenu(interact: SettingsSubMenuHttpsOnlyModeRobot.() -> Unit): SettingsSubMenuHttpsOnlyModeRobot.Transition {
scrollToElementByText("HTTPS-Only Mode")
onView(withText(getStringResource(R.string.preferences_https_only_title))).click()
mDevice.waitNotNull(
Until.findObjects(By.res("$packageName:id/https_only_switch")),
waitingTime,
)
SettingsSubMenuHttpsOnlyModeRobot().interact()
return SettingsSubMenuHttpsOnlyModeRobot.Transition()
}
}
companion object {
@ -354,8 +380,10 @@ private fun assertHomepageButton() =
private fun assertAutofillButton() =
onView(withText(R.string.preferences_autofill)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertLanguageButton() =
private fun assertLanguageButton() {
scrollToElementByText(getStringResource(R.string.preferences_language))
onView(withText(R.string.preferences_language)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertCustomizeButton() = onView(withText("Customize"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
@ -490,33 +518,13 @@ private fun assertOpenLinksInAppsButton() {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
fun assertOpenLinksInAppsSwitchState(enabled: Boolean) {
scrollToElementByText("Open links in apps")
if (enabled) {
openLinksInAppsButton()
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
isChecked(),
),
),
),
)
} else {
openLinksInAppsButton()
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
isNotChecked(),
),
),
),
)
}
fun assertOpenLinksInAppsSwitchState(state: String) {
onView(
allOf(
withText(R.string.preferences_open_links_in_apps),
hasSibling(withText(state)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
// DEVELOPER TOOLS SECTION

@ -100,17 +100,13 @@ private fun assertVersionNumber() {
val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
val buildNVersion = "${packageInfo.versionName} (Build #$versionCode)\n"
val componentsVersion =
"${mozilla.components.Build.version}, ${mozilla.components.Build.gitHash}"
val geckoVersion =
org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION + "-" + org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
val asVersion = mozilla.components.Build.applicationServicesVersion
onView(withId(R.id.about_text))
.check(matches(withText(containsString(buildNVersion))))
.check(matches(withText(containsString(componentsVersion))))
.check(matches(withText(containsString(geckoVersion))))
.check(matches(withText(containsString(asVersion))))
}

@ -5,13 +5,28 @@
package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.endsWith
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.hasCousin
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
@ -19,20 +34,207 @@ import org.mozilla.fenix.helpers.click
class SettingsSubMenuAutofillRobot {
fun verifyAutofillToolbarTitle() = assertItemContainingTextExists(autofillToolbarTitle)
fun verifyManageAddressesToolbarTitle() = assertItemContainingTextExists(manageAddressesToolbarTitle)
fun verifyAddressAutofillSection(isAddressAutofillEnabled: Boolean, userHasSavedAddress: Boolean) {
assertItemContainingTextExists(
autofillToolbarTitle,
addressesSectionTitle,
saveAndAutofillAddressesOption,
saveAndAutofillAddressesSummary,
)
if (userHasSavedAddress) {
assertItemContainingTextExists(manageAddressesButton)
} else {
assertItemContainingTextExists(addAddressButton)
}
verifyAddressesAutofillToggle(isAddressAutofillEnabled)
}
fun verifyCreditCardsAutofillSection(isAddressAutofillEnabled: Boolean, userHasSavedCreditCard: Boolean) {
assertItemContainingTextExists(
autofillToolbarTitle,
creditCardsSectionTitle,
saveAndAutofillCreditCardsOption,
saveAndAutofillCreditCardsSummary,
syncCreditCardsAcrossDevicesButton,
)
if (userHasSavedCreditCard) {
assertItemContainingTextExists(manageSavedCreditCardsButton)
} else {
assertItemContainingTextExists(addCreditCardButton)
}
verifySaveAndAutofillCreditCardsToggle(isAddressAutofillEnabled)
}
fun verifyManageAddressesSection(vararg savedAddressDetails: String) {
assertItemWithDescriptionExists(navigateBackButton)
assertItemContainingTextExists(
manageAddressesToolbarTitle,
addAddressButton,
)
for (savedAddressDetail in savedAddressDetails) {
assertTrue(
mDevice.findObject(
UiSelector().textContains(savedAddressDetail),
).waitForExists(waitingTime),
)
}
}
fun verifySavedCreditCardsSection(creditCardLastDigits: String, creditCardExpiryDate: String) {
assertItemWithDescriptionExists(navigateBackButton)
assertItemContainingTextExists(
savedCreditCardsToolbarTitle,
addCreditCardButton,
itemContainingText(creditCardLastDigits),
itemContainingText(creditCardExpiryDate),
)
}
fun verifyAddressesAutofillToggle(enabled: Boolean) =
onView(withText(R.string.preferences_addresses_save_and_autofill_addresses))
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
if (enabled) {
isChecked()
} else {
isNotChecked()
},
),
),
),
)
fun verifySaveAndAutofillCreditCardsToggle(enabled: Boolean) =
onView(withText(R.string.preferences_credit_cards_save_and_autofill_cards))
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
if (enabled) {
isChecked()
} else {
isNotChecked()
},
),
),
),
)
fun verifyAddAddressView() {
assertItemContainingTextExists(addAddressToolbarTitle)
assertItemWithDescriptionExists(navigateBackButton)
assertItemWithResIdExists(
toolbarCheckmarkButton,
firstNameTextInput,
middleNameTextInput,
)
scrollToElementByText(getStringResource(R.string.addresses_street_address))
assertItemWithResIdExists(
lastNameTextInput,
streetAddressTextInput,
)
scrollToElementByText(getStringResource(R.string.addresses_country))
assertItemWithResIdExists(
cityTextInput,
subRegionDropDown,
zipCodeTextInput,
)
scrollToElementByText(getStringResource(R.string.addresses_save_button))
assertItemWithResIdExists(
countryDropDown,
phoneTextInput,
emailTextInput,
)
assertItemWithResIdExists(
saveButton,
cancelButton,
)
}
fun verifyCountryOption(country: String) {
scrollToElementByText(getStringResource(R.string.addresses_country))
mDevice.pressBack()
assertItemContainingTextExists(itemContainingText(country))
}
fun verifyStateOption(state: String) =
assertItemContainingTextExists(itemContainingText(state))
fun verifyCountryOptions(vararg countries: String) {
countryDropDown.click()
for (country in countries) {
assertItemContainingTextExists(itemContainingText(country))
}
}
fun selectCountry(country: String) {
countryDropDown.click()
countryOption(country).click()
}
fun verifyEditAddressView() {
assertItemContainingTextExists(editAddressToolbarTitle)
assertItemWithDescriptionExists(navigateBackButton)
assertItemWithResIdExists(
toolbarDeleteAddressButton,
toolbarCheckmarkButton,
firstNameTextInput,
middleNameTextInput,
)
scrollToElementByText(getStringResource(R.string.addresses_street_address))
assertItemWithResIdExists(
lastNameTextInput,
streetAddressTextInput,
)
scrollToElementByText(getStringResource(R.string.addresses_country))
assertItemWithResIdExists(
cityTextInput,
subRegionDropDown,
zipCodeTextInput,
)
scrollToElementByText(getStringResource(R.string.addresses_save_button))
assertItemWithResIdExists(
countryDropDown,
phoneTextInput,
emailTextInput,
)
assertItemWithResIdExists(
saveButton,
cancelButton,
)
assertItemContainingTextExists(deleteAddressButton)
}
fun clickSaveAndAutofillAddressesOption() = saveAndAutofillAddressesOption.click()
fun clickAddAddressButton() = addAddressButton.click()
fun clickManageAddressesButton() = manageAddressesButton.click()
fun clickSavedAddress(firstName: String) = savedAddress(firstName).clickAndWaitForNewWindow(waitingTime)
fun clickDeleteAddressButton() {
deleteAddressButton.waitForExists(waitingTime)
deleteAddressButton.click()
toolbarDeleteAddressButton.waitForExists(waitingTime)
toolbarDeleteAddressButton.click()
}
fun clickCancelDeleteAddressButton() = cancelDeleteAddressButton.click()
fun clickConfirmDeleteAddressButton() = confirmDeleteAddressButton.click()
fun clickSubRegionOption(subRegion: String) {
subRegionOption(subRegion).waitForExists(waitingTime)
subRegionOption(subRegion).click()
scrollToElementByText(subRegion)
subRegionOption(subRegion).also {
it.waitForExists(waitingTime)
it.click()
}
}
fun clickCountryOption(country: String) {
countryOption(country).waitForExists(waitingTime)
@ -53,36 +255,42 @@ class SettingsSubMenuAutofillRobot {
emailAddress: String,
) {
firstNameTextInput.waitForExists(waitingTime)
mDevice.pressBack()
firstNameTextInput.setText(firstName)
middleNameTextInput.setText(middleName)
lastNameTextInput.setText(lastName)
streetAddressTextInput.setText(streetAddress)
scrollToElementByText(getStringResource(R.string.addresses_city))
cityTextInput.setText(city)
subRegionDropDown.click()
clickSubRegionOption(state)
zipCodeTextInput.setText(zipCode)
countryDropDown.click()
clickCountryOption(country)
scrollToElementByText(getStringResource(R.string.addresses_phone))
scrollToElementByText(getStringResource(R.string.addresses_save_button))
phoneTextInput.setText(phoneNumber)
emailTextInput.setText(emailAddress)
scrollToElementByText(getStringResource(R.string.addresses_save_button))
saveButton.click()
manageAddressesButton.waitForExists(waitingTime)
}
fun clickAddCreditCardButton() = addCreditCardButton.click()
fun clickManageSavedCardsButton() = manageSavedCardsButton.click()
fun clickManageSavedCreditCardsButton() = manageSavedCreditCardsButton.click()
fun clickSecuredCreditCardsLaterButton() = securedCreditCardsLaterButton.click()
fun clickSavedCreditCard() = savedCreditCardNumber.clickAndWaitForNewWindow(waitingTime)
fun clickDeleteCreditCardButton() {
deleteCreditCardButton.waitForExists(waitingTime)
deleteCreditCardButton.click()
fun clickDeleteCreditCardToolbarButton() {
deleteCreditCardToolbarButton.waitForExists(waitingTime)
deleteCreditCardToolbarButton.click()
}
fun clickDeleteCreditCardMenuButton() {
deleteCreditCardMenuButton.waitForExists(waitingTime)
deleteCreditCardMenuButton.click()
}
fun clickSaveAndAutofillCreditCardsOption() = saveAndAutofillCreditCardsOption.click()
fun clickConfirmDeleteCreditCardButton() = confirmDeleteCreditCardButton.click()
fun clickCancelDeleteCreditCardButton() = cancelDeleteCreditCardButton.click()
fun clickExpiryMonthOption(expiryMonth: String) {
expiryMonthOption(expiryMonth).waitForExists(waitingTime)
expiryMonthOption(expiryMonth).click()
@ -96,18 +304,76 @@ class SettingsSubMenuAutofillRobot {
fun verifyAddCreditCardsButton() = assertTrue(addCreditCardButton.waitForExists(waitingTime))
fun fillAndSaveCreditCard(cardNumber: String, cardName: String, expiryMonth: String, expiryYear: String) {
cardNumberTextInput.waitForExists(waitingTime)
cardNumberTextInput.setText(cardNumber)
nameOnCardTextInput.setText(cardName)
creditCardNumberTextInput.waitForExists(waitingTime)
creditCardNumberTextInput.setText(cardNumber)
nameOnCreditCardTextInput.setText(cardName)
expiryMonthDropDown.click()
clickExpiryMonthOption(expiryMonth)
expiryYearDropDown.click()
clickExpiryYearOption(expiryYear)
saveButton.click()
manageSavedCardsButton.waitForExists(waitingTime)
manageSavedCreditCardsButton.waitForExists(waitingTime)
}
fun clearCreditCardNumber() =
creditCardNumberTextInput.also {
it.waitForExists(waitingTime)
it.clearTextField()
}
fun clearNameOnCreditCard() =
nameOnCreditCardTextInput.also {
it.waitForExists(waitingTime)
it.clearTextField()
}
fun clickSaveCreditCardToolbarButton() = saveCreditCardToolbarButton.click()
fun verifyEditCreditCardView(
cardNumber: String,
cardName: String,
expiryMonth: String,
expiryYear: String,
) {
assertItemContainingTextExists(editCreditCardToolbarTitle)
assertItemWithDescriptionExists(navigateBackButton)
assertItemWithResIdExists(
deleteCreditCardToolbarButton,
saveCreditCardToolbarButton,
)
assertEquals(cardNumber, creditCardNumberTextInput.text)
assertEquals(cardName, nameOnCreditCardTextInput.text)
// Can't get the text from the drop-down items, need to verify them individually
assertItemWithResIdExists(
expiryYearDropDown,
expiryMonthDropDown,
)
assertItemContainingTextExists(
itemContainingText(expiryMonth),
itemContainingText(expiryYear),
)
assertItemWithResIdExists(
saveButton,
cancelButton,
)
assertItemContainingTextExists(deleteCreditCardMenuButton)
}
fun verifyEditCreditCardToolbarTitle() = assertItemContainingTextExists(editCreditCardToolbarTitle)
fun verifyCreditCardNumberErrorMessage() =
assertItemContainingTextExists(itemContainingText(getStringResource(R.string.credit_cards_number_validation_error_message)))
fun verifyNameOnCreditCardErrorMessage() =
assertItemContainingTextExists(itemContainingText(getStringResource(R.string.credit_cards_name_on_card_validation_error_message)))
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
mDevice.pressBack()
@ -117,40 +383,74 @@ class SettingsSubMenuAutofillRobot {
}
fun goBackToAutofillSettings(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
mDevice.pressBack()
navigateBackButton.click()
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
}
fun goBackToSavedCreditCards(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
navigateBackButton.click()
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
}
fun goBackToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.pressBack()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
}
}
private val addAddressButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_addresses_add_address)))
private val manageAddressesButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_addresses_manage_addresses)))
private val firstNameTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/first_name_input"))
private val middleNameTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/middle_name_input"))
private val lastNameTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/last_name_input"))
private val streetAddressTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/street_address_input"))
private val cityTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/city_input"))
private val subRegionDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/subregion_drop_down"))
private val zipCodeTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/zip_input"))
private val countryDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/country_drop_down"))
private val phoneTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/phone_input"))
private val emailTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/email_input"))
private val saveButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/save_button"))
private val deleteAddressButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_address_button"))
private val autofillToolbarTitle = itemContainingText(getStringResource(R.string.preferences_autofill))
private val addressesSectionTitle = itemContainingText(getStringResource(R.string.preferences_addresses))
private val manageAddressesToolbarTitle = itemContainingText(getStringResource(R.string.addresses_manage_addresses))
private val saveAndAutofillAddressesOption = itemContainingText(getStringResource(R.string.preferences_addresses_save_and_autofill_addresses))
private val saveAndAutofillAddressesSummary = itemContainingText(getStringResource(R.string.preferences_addresses_save_and_autofill_addresses_summary))
private val addAddressButton = itemContainingText(getStringResource(R.string.preferences_addresses_add_address))
private val manageAddressesButton = itemContainingText(getStringResource(R.string.preferences_addresses_manage_addresses))
private val addAddressToolbarTitle = itemContainingText(getStringResource(R.string.addresses_add_address))
private val editAddressToolbarTitle = itemContainingText(getStringResource(R.string.addresses_edit_address))
private val toolbarCheckmarkButton = itemWithResId("$packageName:id/save_address_button")
private val navigateBackButton = itemWithDescription(getStringResource(R.string.action_bar_up_description))
private val firstNameTextInput = itemWithResId("$packageName:id/first_name_input")
private val middleNameTextInput = itemWithResId("$packageName:id/middle_name_input")
private val lastNameTextInput = itemWithResId("$packageName:id/last_name_input")
private val streetAddressTextInput = itemWithResId("$packageName:id/street_address_input")
private val cityTextInput = itemWithResId("$packageName:id/city_input")
private val subRegionDropDown = itemWithResId("$packageName:id/subregion_drop_down")
private val zipCodeTextInput = itemWithResId("$packageName:id/zip_input")
private val countryDropDown = itemWithResId("$packageName:id/country_drop_down")
private val phoneTextInput = itemWithResId("$packageName:id/phone_input")
private val emailTextInput = itemWithResId("$packageName:id/email_input")
private val saveButton = itemWithResId("$packageName:id/save_button")
private val cancelButton = itemWithResId("$packageName:id/cancel_button")
private val deleteAddressButton = itemContainingText(getStringResource(R.string.addressess_delete_address_button))
private val toolbarDeleteAddressButton = itemWithResId("$packageName:id/delete_address_button")
private val cancelDeleteAddressButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog())
private val confirmDeleteAddressButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())
private val creditCardsSectionTitle = itemContainingText(getStringResource(R.string.preferences_credit_cards))
private val saveAndAutofillCreditCardsOption = itemContainingText(getStringResource(R.string.preferences_credit_cards_save_and_autofill_cards))
private val saveAndAutofillCreditCardsSummary = itemContainingText(getStringResource(R.string.preferences_credit_cards_save_and_autofill_cards_summary))
private val syncCreditCardsAcrossDevicesButton = itemContainingText(getStringResource(R.string.preferences_credit_cards_sync_cards_across_devices))
private val addCreditCardButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_add_credit_card)))
private val manageSavedCardsButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_manage_saved_cards)))
private val cardNumberTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/card_number_input"))
private val nameOnCardTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/name_on_card_input"))
private val savedCreditCardsToolbarTitle = itemContainingText(getStringResource(R.string.credit_cards_saved_cards))
private val editCreditCardToolbarTitle = itemContainingText(getStringResource(R.string.credit_cards_edit_card))
private val manageSavedCreditCardsButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_manage_saved_cards)))
private val creditCardNumberTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/card_number_input"))
private val nameOnCreditCardTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/name_on_card_input"))
private val expiryMonthDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_month_drop_down"))
private val expiryYearDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_year_drop_down"))
private val savedCreditCardNumber = mDevice.findObject(UiSelector().resourceId("$packageName:id/credit_card_logo"))
private val deleteCreditCardButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_credit_card_button"))
private val deleteCreditCardToolbarButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_credit_card_button"))
private val saveCreditCardToolbarButton = itemWithResId("$packageName:id/save_credit_card_button")
private val deleteCreditCardMenuButton = itemContainingText(getStringResource(R.string.credit_cards_delete_card_button))
private val confirmDeleteCreditCardButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())
private val cancelDeleteCreditCardButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog())
private val securedCreditCardsLaterButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog())
private fun savedAddress(firstName: String) = mDevice.findObject(UiSelector().textContains(firstName))

@ -0,0 +1,38 @@
/* 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.robots
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.assertCheckedItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.packageName
/**
* Implementation of Robot Pattern for the settings Cookie Banner Reduction sub menu.
*/
class SettingsSubMenuCookieBannerReductionRobot {
fun verifyCookieBannerView(isCookieBannerReductionChecked: Boolean) {
assertItemContainingTextExists(cookieBannerOptionTitle, cookieBannerOptionDescription)
assertCheckedItemWithResIdExists(checkedCookieBannerOptionToggle(isCookieBannerReductionChecked))
}
fun clickCookieBannerReductionToggle() = cookieBannerOptionToggle.click()
fun verifyCheckedCookieBannerReductionToggle(isCookieBannerReductionChecked: Boolean) =
assertCheckedItemWithResIdExists(checkedCookieBannerOptionToggle(isCookieBannerReductionChecked))
class Transition
}
private val cookieBannerOptionTitle =
itemContainingText(getStringResource(R.string.reduce_cookie_banner_option))
private val cookieBannerOptionDescription =
itemContainingText(getStringResource(R.string.reduce_cookie_banner_summary_1))
private val cookieBannerOptionToggle =
itemWithResId("$packageName:id/learn_more_switch")
private fun checkedCookieBannerOptionToggle(isChecked: Boolean = false) =
checkedItemWithResId("$packageName:id/learn_more_switch", isChecked)

@ -0,0 +1,108 @@
/* 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.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.assertIsChecked
import org.mozilla.fenix.helpers.assertIsEnabled
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.isChecked
class SettingsSubMenuHttpsOnlyModeRobot {
fun verifyHttpsOnlyModeMenuHeader(): ViewInteraction =
onView(
allOf(
withText(getStringResource(R.string.preferences_https_only_title)),
hasSibling(withContentDescription("Navigate up")),
),
).check(matches(isDisplayed()))
fun verifyHttpsOnlyModeSummary() {
onView(withId(R.id.https_only_title))
.check(matches(withText("HTTPS-Only Mode")))
onView(withId(R.id.https_only_summary))
.check(matches(withText("Automatically attempts to connect to sites using HTTPS encryption protocol for increased security. Learn more")))
}
fun verifyHttpsOnlyModeIsEnabled(shouldBeEnabled: Boolean) {
httpsModeOnlySwitch.check(
matches(
if (shouldBeEnabled) {
isChecked(true)
} else {
isChecked(false)
},
),
)
}
fun clickHttpsOnlyModeSwitch() = httpsModeOnlySwitch.click()
fun verifyHttpsOnlyModeOptionsEnabled(shouldBeEnabled: Boolean) {
allTabsOption.assertIsEnabled(shouldBeEnabled)
onlyPrivateTabsOption.assertIsEnabled(shouldBeEnabled)
}
fun verifyHttpsOnlyOptionSelected(allTabsOptionSelected: Boolean, privateTabsOptionSelected: Boolean) {
if (allTabsOptionSelected) {
allTabsOption.assertIsChecked(true)
onlyPrivateTabsOption.assertIsChecked(false)
} else if (privateTabsOptionSelected) {
allTabsOption.assertIsChecked(false)
onlyPrivateTabsOption.assertIsChecked(true)
}
}
fun selectHttpsOnlyModeOption(allTabsOptionSelected: Boolean, privateTabsOptionSelected: Boolean) {
if (allTabsOptionSelected) {
allTabsOption.click()
allTabsOption.assertIsChecked(true)
} else if (privateTabsOptionSelected) {
onlyPrivateTabsOption.click()
onlyPrivateTabsOption.assertIsChecked(true)
}
}
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
goBackButton.perform(click())
SettingsRobot().interact()
return SettingsRobot.Transition()
}
}
}
private val httpsModeOnlySwitch = onView(withId(R.id.https_only_switch))
private val allTabsOption =
onView(
allOf(
withId(R.id.https_only_all_tabs),
withText("Enable in all tabs"),
),
)
private val onlyPrivateTabsOption =
onView(
allOf(
withId(R.id.https_only_private_tabs),
withText("Enable only in private tabs"),
),
)
private val goBackButton = onView(withContentDescription("Navigate up"))

@ -8,8 +8,17 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isChecked
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
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.TestHelper.getStringResource
/**
* Implementation of Robot Pattern for the Privacy Settings > saved logins sub menu
@ -17,13 +26,52 @@ import org.hamcrest.CoreMatchers
class SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot {
fun verifySaveLoginsOptionsView() {
onView(ViewMatchers.withText("Ask to save"))
onView(withText("Ask to save"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
onView(ViewMatchers.withText("Never save"))
onView(withText("Never save"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
fun verifyAskToSaveRadioButton(isChecked: Boolean) {
if (isChecked) {
onView(
allOf(
withId(R.id.radio_button),
hasSibling(withText(R.string.preferences_passwords_save_logins_ask_to_save)),
),
).check(matches(isChecked()))
} else {
onView(
allOf(
withId(R.id.radio_button),
hasSibling(withText(R.string.preferences_passwords_save_logins_ask_to_save)),
),
).check(matches(not(isChecked())))
}
}
fun verifyNeverSaveSaveRadioButton(isChecked: Boolean) {
if (isChecked) {
onView(
allOf(
withId(R.id.radio_button),
hasSibling(withText(R.string.preferences_passwords_save_logins_never_save)),
),
).check(matches(isChecked()))
} else {
onView(
allOf(
withId(R.id.radio_button),
hasSibling(withText(R.string.preferences_passwords_save_logins_never_save)),
),
).check(matches(not(isChecked())))
}
}
fun clickNeverSaveOption() =
itemContainingText(getStringResource(R.string.preferences_passwords_save_logins_never_save)).click()
class Transition {
fun goBack(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordRobot.Transition {
goBackButton().perform(ViewActions.click())

@ -11,12 +11,21 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.endsWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.hasCousin
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
@ -27,8 +36,13 @@ import org.mozilla.fenix.helpers.ext.waitNotNull
class SettingsSubMenuLoginsAndPasswordRobot {
fun verifyDefaultView() {
mDevice.waitNotNull(Until.findObjects(By.text("Sync logins across devices")), TestAssetHelper.waitingTime)
assertDefaultView()
mDevice.waitNotNull(Until.findObjects(By.text("Save logins and passwords")), TestAssetHelper.waitingTime)
saveLoginsAndPasswordButton.check(matches(isDisplayed()))
autofillInFirefoxOption.check(matches(isDisplayed()))
autofillInOtherAppsOption.check(matches(isDisplayed()))
syncLoginsButton.check(matches(isDisplayed()))
savedLoginsButton.check(matches(isDisplayed()))
loginExceptionsButton.check(matches(isDisplayed()))
}
fun verifyDefaultViewBeforeSyncComplete() {
@ -39,10 +53,45 @@ class SettingsSubMenuLoginsAndPasswordRobot {
mDevice.waitNotNull(Until.findObjects(By.text("On")), TestAssetHelper.waitingTime)
}
fun verifyDefaultValueExceptions() = assertDefaultValueExceptions()
fun verifyDefaultValueAutofillLogins(context: Context) = assertDefaultValueAutofillLogins(context)
fun clickAutofillInFirefoxOption() = autofillInFirefoxOption.click()
fun verifyAutofillInFirefoxToggle(enabled: Boolean) {
autofillInFirefoxOption
.check(
matches(
hasCousin(
allOf(
withClassName(endsWith("Switch")),
if (enabled) {
isChecked()
} else {
isNotChecked()
},
),
),
),
)
}
fun verifyAutofillLoginsInOtherAppsToggle(enabled: Boolean) {
autofillInOtherAppsOption
.check(
matches(
hasCousin(
allOf(
withId(R.id.switch_widget),
if (enabled) {
isChecked()
} else {
isNotChecked()
},
),
),
),
)
}
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
@ -53,32 +102,28 @@ class SettingsSubMenuLoginsAndPasswordRobot {
}
fun openSavedLogins(interact: SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition {
fun savedLoginsButton() = onView(ViewMatchers.withText("Saved logins"))
savedLoginsButton().click()
savedLoginsButton.click()
SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot().interact()
return SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition()
}
fun openLoginExceptions(interact: SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition {
fun loginExceptionsButton() = onView(ViewMatchers.withText("Exceptions"))
loginExceptionsButton().click()
loginExceptionsButton.click()
SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot().interact()
return SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition()
}
fun openSyncLogins(interact: SettingsTurnOnSyncRobot.() -> Unit): SettingsTurnOnSyncRobot.Transition {
fun syncLoginsButton() = onView(ViewMatchers.withText("Sync logins across devices"))
syncLoginsButton().click()
syncLoginsButton.click()
SettingsTurnOnSyncRobot().interact()
return SettingsTurnOnSyncRobot.Transition()
}
fun saveLoginsAndPasswordsOptions(interact: SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot.Transition {
fun saveLoginsAndPasswordButton() = onView(ViewMatchers.withText("Save logins and passwords"))
saveLoginsAndPasswordButton().click()
fun openSaveLoginsAndPasswordsOptions(interact: SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot.Transition {
saveLoginsAndPasswordButton.click()
SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot().interact()
return SettingsSubMenuLoginsAndPasswordOptionsToSaveRobot.Transition()
@ -91,11 +136,16 @@ fun settingsSubMenuLoginsAndPassword(interact: SettingsSubMenuLoginsAndPasswordR
return SettingsSubMenuLoginsAndPasswordRobot.Transition()
}
private fun goBackButton() =
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))
private val saveLoginsAndPasswordButton = onView(withText("Save logins and passwords"))
private fun assertDefaultView() = onView(ViewMatchers.withText("Sync logins across devices"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private val savedLoginsButton = onView(withText("Saved logins"))
private val syncLoginsButton = onView(withText("Sync logins across devices"))
private val loginExceptionsButton = onView(withText("Exceptions"))
private fun goBackButton() =
onView(allOf(ViewMatchers.withContentDescription("Navigate up")))
private fun assertDefaultValueAutofillLogins(context: Context) = onView(
ViewMatchers.withText(
@ -107,8 +157,6 @@ private fun assertDefaultValueAutofillLogins(context: Context) = onView(
)
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertDefaultValueExceptions() = onView(ViewMatchers.withText("Exceptions"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private val autofillInFirefoxOption = onView(withText("Autofill in $appName"))
private fun assertDefaultValueSyncLogins() = onView(ViewMatchers.withText("Sync and save data"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private val autofillInOtherAppsOption = onView(withText("Autofill in other apps"))

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

Loading…
Cancel
Save