Compare commits

...

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

@ -0,0 +1,153 @@
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: arm64-v8a
fileName: app-fenix-arm64-v8a-benchmark-unsigned.apk
- abi: armeabi-v7a
fileName: app-fenix-armeabi-v7a-benchmark-unsigned.apk
- abi: x86
fileName: app-fenix-x86-benchmark-unsigned.apk
- abi: x86_64
fileName: app-fenix-x86_64-benchmark-unsigned.apk
build_type: benchmark
name: fenixBenchmark
- 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}

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

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 🌟"

@ -0,0 +1,83 @@
name: CI
on:
push:
branches:
- iceraven
jobs:
release-automation:
name: Build App
runs-on: ubuntu-latest
steps:
- 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 -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:
# Try to stop the daemon from magically vanishing by adding random memory-related arguments.
# 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=1g -Xms2g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dev/stderr"
with:
gradle-home-cache-cleanup: true
gradle-executable: /usr/bin/time
arguments: -v ./gradlew app:assemblefenixForkRelease -x lintVitalFenixForkRelease -PversionName=${{ env.VERSION_NAME }} --stacktrace
- name: Create signed APKs
uses: abhijitvalluri/sign-apks@v0.8
with:
releaseDirectory: app/build/outputs/apk/fenix/forkRelease/
signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }}
alias: ${{ secrets.DEBUG_ALIAS }}
keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }}
- name: Upload arm64 apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-arm64-v8a-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-arm64-v8a-forkRelease.apk
- name: Upload armeabi apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-armeabi-v7a-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-armeabi-v7a-forkRelease.apk
- name: Upload x86 apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-x86-forkRelease.apk
- name: Upload x86_64 apk
uses: actions/upload-artifact@v3
with:
path: app/build/outputs/apk/fenix/forkRelease/app-fenix-x86_64-forkRelease.apk
name: ${{ env.VERSION_NAME }}-browser-x86_64-forkRelease.apk

@ -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,8 @@
name: Release Automation
name: Release
on:
push:
branches:
- fork
tags:
- '*'
create:
jobs:
release-automation:
name: Build App
@ -14,18 +11,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 +41,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 +61,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 -e "## 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 -e '```\n' >> temp_changelog.md
echo -e "## 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 -e "## Change log\n" >> temp_changelog.md
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 +87,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 +99,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 +110,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 +121,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 +132,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,8 +1,8 @@
# Community Participation Guidelines
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
For more details, please read the
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
## How to Report
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.

@ -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! [![CI](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/ci.yml/badge.svg)](https://github.com/fork-maintainers/iceraven-browser/actions/workflows/ci.yml) ![Release](https://img.shields.io/github/v/release/fork-maintainers/iceraven-browser)
Definitely not brought to you by Mozilla!
@ -9,6 +9,7 @@ Our goal is to be a close fork of the new Firefox for Android that seeks to prov
Notable features include:
* `about:config` support
* The ability to *attempt* to install a much longer list of add-ons than Mozilla's Fenix version of Firefox accepts. Currently the browser queries [this AMO collection](https://addons.mozilla.org/en-US/firefox/collections/16201230/What-I-want-on-Fenix/) **Most of them will not work**, because they depend on code that Mozilla is still working on writing in `android-components`, but you may attempt to install them. If you don't see an add-on you want, you can [request it](https://github.com/fork-maintainers/iceraven-browser/issues/new).
* Option to suspend tabs to avoid being killed for memory (https://bugzilla.mozilla.org/show_bug.cgi?id=1807364)
* **No warranties or guarantees of security or updates or even stability**! Note that Iceraven Browser includes some unstable code written by Mozilla, with our own added modifications on top, all shipped with the stable version of GeckoView engine. Hence, the browser may contain bugs introduced upstream. Binaries are currently built automatically by our Github release automation. These binaries are signed with a debug key. When we finally publish this somewhere official like F-droid, we will sign the apks with a proper key suitable for public release. Due to the current way we create the releases and sign them, you may not want to rely on such "alpha" quality software as your primary web browser, as it will have bugs. So, use this browser only if you are comfortable with these limitations/potential risks.
**Note/Disclaimer:** Iceraven Browser could not exist without the hardworking folks at the Mozilla Corporation who work on the Mozilla Android Components and Firefox projects, but it is not an official Mozilla product, and is not provided, endorsed, vetted, approved, or secured by Mozilla.
@ -61,7 +62,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 +74,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 ac015fe2d5ef0700f93e40b62094f1cf79edcf86

@ -7,6 +7,14 @@ cookie-banners:
sections-enabled:
type: json
description: This property provides a lookup table of whether or not the given section should be enabled.
glean:
description: A feature that provides server-side configurations for Glean metrics (aka Server Knobs).
hasExposure: true
exposureDescription: ""
variables:
metrics-enabled:
type: json
description: "A map of metric base-identifiers to booleans representing the state of the 'enabled' flag for that metric."
growth-data:
description: A feature measuring campaign growth data
hasExposure: true
@ -23,6 +31,17 @@ homescreen:
sections-enabled:
type: json
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."
juno-onboarding:
description: A feature that shows juno onboarding flow.
hasExposure: true
exposureDescription: ""
variables:
cards:
type: json
description: Collection of user facing onboarding cards.
enabled:
type: boolean
description: "if true, juno onboarding is shown to the user."
messaging:
description: "Configuration for the messaging system.\n\nIn practice this is a set of growable lookup tables for the\nmessage controller to piece together.\n"
hasExposure: true
@ -60,6 +79,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 Android.\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 +101,25 @@ 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
pdfjs:
description: PDF.js features
hasExposure: true
exposureDescription: ""
variables:
download-button:
type: boolean
description: Download button
open-in-app-button:
type: boolean
description: Open in app button
pre-permission-notification-prompt:
description: A feature that shows the pre-permission notification prompt.
hasExposure: true
@ -83,13 +129,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
@ -98,6 +147,14 @@ search-term-groups:
enabled:
type: boolean
description: "If true, the feature shows up on the homescreen and on the new tab screen."
toolbar:
description: The searchbar/awesomebar that user uses to search.
hasExposure: true
exposureDescription: ""
variables:
toolbar-position-top:
type: boolean
description: "If true, toolbar appears at top of the screen."
unified-search:
description: A feature allowing user to easily search for specified results directly in the search bar.
hasExposure: true

@ -1,8 +1,9 @@
import org.apache.tools.ant.util.StringUtils
import org.mozilla.fenix.gradle.tasks.ApkSizeTask
plugins {
id "com.jetbrains.python.envs" version "0.0.26"
id "com.google.protobuf" version "0.8.19"
id "com.jetbrains.python.envs" version "$python_envs_plugin"
id "com.google.protobuf" version "$protobuf_plugin"
}
apply plugin: 'com.android.application'
@ -11,8 +12,6 @@ apply plugin: 'kotlin-parcelize'
apply plugin: 'jacoco'
apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'androidx.benchmark'
import com.android.build.OutputFile
import groovy.json.JsonOutput
@ -64,19 +63,26 @@ android {
"}"
// This should be the base URL used to call the AMO API.
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
]
// This allows overriding the target activity for MozillaOnline builds, which happens
// as part of the defaultConfig below.
def targetActivity = "HomeActivity"
// 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,90 +113,95 @@ 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.putAll([
"deepLinkScheme": deepLinkSchemeValue
])
}
beta releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".firefox_beta"
def deepLinkSchemeValue = "fenix-beta"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
manifestPlaceholders.putAll([
// This release type is meant to replace Firefox (Beta channel) and therefore needs to inherit
// its sharedUserId for all eternity. See:
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false&regexp=false&path=
// https://searchfox.org/mozilla-esr68/search?q=moz_android_shared_id&case=false&regexp=false&path=
// Shipping an app update without sharedUserId can have
// fatal consequences. For example see:
// - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
"requestLegacyExternalStorage": true
]
])
}
release releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".firefox"
def deepLinkSchemeValue = "fenix"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
manifestPlaceholders.putAll([
// This release type is meant to replace Firefox (Release channel) and therefore needs to inherit
// its sharedUserId for all eternity. See:
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false&regexp=false&path=
// https://searchfox.org/mozilla-esr68/search?q=moz_android_shared_id&case=false&regexp=false&path=
// Shipping an app update without sharedUserId can have
// fatal consequences. For example see:
// - https://issuetracker.google.com/issues/36924841
// - https://issuetracker.google.com/issues/36905922
"sharedUserId": "org.mozilla.firefox.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
"requestLegacyExternalStorage": true
]
])
}
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
debuggable true
def deepLinkSchemeValue = "iceraven-debug"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders.putAll([
"sharedUserId": "io.github.forkmaintainers.iceraven.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
])
// 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"
matchingFallbacks = ['debug']
}
forkRelease releaseTemplate >> {
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
applicationIdSuffix ".iceraven"
def deepLinkSchemeValue = "iceraven"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
"deepLinkScheme": deepLinkSchemeValue
]
manifestPlaceholders.putAll([
"sharedUserId": "io.github.forkmaintainers.iceraven.sharedID",
"deepLinkScheme": deepLinkSchemeValue,
])
// Use custom default allowed addon list
buildConfigField "String", "AMO_COLLECTION_USER", "\"16201230\""
buildConfigField "String", "AMO_COLLECTION_NAME", "\"What-I-want-on-Fenix\""
}
benchmark releaseTemplate >> {
initWith buildTypes.nightly
applicationIdSuffix ".fenix"
debuggable false
}
}
buildFeatures {
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 +221,14 @@ android {
flavorDimensions "engine"
flavorDimensions "product"
productFlavors {
fenix {
dimension "product"
}
}
sourceSets {
androidTest {
resources.srcDirs += ['src/androidTest/resources']
@ -227,21 +246,22 @@ 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',
'META-INF/LICENSE.md', 'META-INF/LICENSE-notice.md']
}
}
testOptions {
unitTests.returnDefaultValues = true
@ -264,6 +284,7 @@ android {
kotlinCompilerExtensionVersion = FenixVersions.androidx_compose_compiler
}
namespace 'org.mozilla.fenix'
}
android.applicationVariants.all { variant ->
@ -277,12 +298,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 +455,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,58 +482,41 @@ 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",
fenixForkDebug: "forkDebug",
fenixForkRelease: "forkRelease",
fenixBenchmark: "developer"
]
// 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
// JUnit test runner. The runtime classpath in the built-in JUnit test runner gets the
// dependency from the `implementation`, which is type @aar, and therefore the JNA dependency
// doesn't provide the JNI dispatch libraries in the correct Java resource directories. I think
// what's happening is that @aar type in `implementation` resolves to the @jar type in
// `testImplementation`, and that it wins the dependency resolution battle.
//
// A workaround is to add a new configuration which depends on the @jar type and to reference
// the underlying JAR file directly in `testImplementation`. This JAR file doesn't resolve to
// the @aar type in `implementation`. This works when invoked via `gradle`, but also sets the
// correct runtime classpath when invoked with Android Studio's built-in JUnit test runner.
// Success!
jnaForTest
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
freeCompilerArgs += [
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
]
}
}
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,101 +524,106 @@ dependencies {
implementation FenixDependencies.androidx_constraintlayout
implementation FenixDependencies.androidx_coordinatorlayout
implementation FenixDependencies.google_accompanist_drawablepainter
implementation FenixDependencies.google_accompanist_insets
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
forkDebugImplementation FenixDependencies.leakcanary
debugImplementation FenixDependencies.androidx_compose_ui_tooling
implementation FenixDependencies.androidx_activity_compose
implementation FenixDependencies.androidx_activity_ktx
implementation FenixDependencies.androidx_annotation
implementation FenixDependencies.androidx_compose_ui
implementation FenixDependencies.androidx_compose_ui_tooling
implementation FenixDependencies.androidx_compose_ui_tooling_preview
implementation FenixDependencies.androidx_compose_foundation
implementation FenixDependencies.androidx_compose_material
implementation FenixDependencies.androidx_legacy
@ -629,19 +638,21 @@ dependencies {
implementation FenixDependencies.androidx_lifecycle_livedata
implementation FenixDependencies.androidx_lifecycle_process
implementation FenixDependencies.androidx_lifecycle_runtime
implementation FenixDependencies.androidx_lifecycle_viewmodel
implementation FenixDependencies.androidx_core
implementation FenixDependencies.androidx_core_ktx
implementation FenixDependencies.androidx_transition
implementation FenixDependencies.androidx_work_ktx
implementation FenixDependencies.androidx_datastore
implementation FenixDependencies.androidx_data_store_preferences
implementation FenixDependencies.protobuf_javalite
implementation FenixDependencies.google_material
androidTestImplementation FenixDependencies.uiautomator
androidTestImplementation "tools.fastlane:screengrab:2.0.0"
androidTestImplementation FenixDependencies.fastlane
// This Falcon version is added to maven central now required for Screengrab
implementation 'com.jraska:falcon:2.2.0'
androidTestImplementation FenixDependencies.falcon
androidTestImplementation FenixDependencies.androidx_compose_ui_test
@ -675,8 +686,8 @@ dependencies {
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
@ -684,8 +695,8 @@ dependencies {
exclude group: 'org.apache.maven'
}
testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3'
implementation FenixDependencies.mozilla_support_rusthttp
testImplementation FenixDependencies.maven_ant_tasks
implementation project(':support-rusthttp')
androidTestImplementation FenixDependencies.mockk_android
testImplementation FenixDependencies.mockk
@ -698,14 +709,8 @@ dependencies {
}
protobuf {
// Mac M1 workaround until we can bump the version. Dependent on A-S.
// See https://github.com/mozilla-mobile/fenix/issues/22321
protoc {
if (osdetector.os == "osx") {
artifact = "${FenixDependencies.protobuf_compiler}:osx-x86_64"
} else {
artifact = FenixDependencies.protobuf_compiler
}
artifact = FenixDependencies.protobuf_compiler
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
@ -729,7 +734,7 @@ if (project.hasProperty("coverage")) {
}
jacoco {
toolVersion = "0.8.7"
toolVersion = FenixVersions.jacoco
}
android.applicationVariants.all { variant ->
@ -864,63 +869,15 @@ if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcd
if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) {
ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir"
}
ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir"
ext.topsrcdir = StringUtils.removeSuffix(gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir", File.separator)
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')
} else if (gradle.hasProperty('localProperties.branchBuild.application-services.dir')) {
appServicesSrcDir = gradle.getProperty('localProperties.branchBuild.application-services.dir')
}
if (appServicesSrcDir) {
if (appServicesSrcDir.startsWith("/")) {
apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
} else {
apply from: "../${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
}
}
if (gradle.hasProperty('localProperties.autoPublish.glean.dir')) {
ext.gleanSrcDir = gradle."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 +887,4 @@ android.applicationVariants.all { variant ->
}
// Enable expiration by major version.
ext.gleanExpireByVersion = Config.majorVersion(project)
ext.gleanExpireByVersion = Config.majorVersion()

@ -1,4 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<issues format="6" by="lint 7.0.0" type="baseline" client="gradle" name="AGP (7.0.0)" variant="all" version="7.0.0">
<issue

File diff suppressed because it is too large Load Diff

@ -0,0 +1,384 @@
---
about:
description: Nimbus Feature Manifest for Fenix (Firefox Android)
kotlin:
package: org.mozilla.fenix
class: .nimbus.FxNimbus
channels:
- release
- beta
- nightly
- developer
- forkDebug
- forkRelease
includes:
- onboarding.fml.yaml
import:
- path: ../android-components/components/service/nimbus/messaging.fml.yaml
channel: release
features:
messaging:
- 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
MORE_THAN_24H_SINCE_INSTALLED_OR_UPDATED: days_since_update >= 1
# 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: 1
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)
- path: ../android-components/components/browser/engine-gecko/geckoview.fml.yaml
channel: release
features:
pdfjs:
- channel: developer
value: {
download-button: true,
open-in-app-button: true
}
features:
toolbar:
description: The searchbar/awesomebar that user uses to search.
variables:
toolbar-position-top:
description: If true, toolbar appears at top of the screen.
type: Boolean
default: false
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: true
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: false
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"]
glean:
description: "A feature that provides server-side configurations for Glean metrics (aka Server Knobs)."
variables:
metrics-enabled:
description: "A map of metric base-identifiers to booleans representing the state of the 'enabled' flag for that metric."
type: Map<String, Boolean>
default: {}
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.

@ -0,0 +1,114 @@
---
features:
juno-onboarding:
description: A feature that shows juno onboarding flow.
variables:
enabled:
description: if true, juno onboarding is shown to the user.
type: Boolean
default: false
cards:
description: Collection of user facing onboarding cards.
type: Map<String, OnboardingCardData>
default:
default-browser:
card-type: default-browser
title: juno_onboarding_default_browser_title_nimbus
ordering: 10
body: juno_onboarding_default_browser_description_nimbus
link-text: juno_onboarding_default_browser_description_link_text
image-res: ic_onboarding_welcome
primary-button-label: juno_onboarding_default_browser_positive_button
secondary-button-label: juno_onboarding_default_browser_negative_button
sync-sign-in:
card-type: sync-sign-in
title: juno_onboarding_sign_in_title
body: juno_onboarding_sign_in_description
image-res: ic_onboarding_sync
ordering: 20
primary-button-label: juno_onboarding_sign_in_positive_button
secondary-button-label: juno_onboarding_sign_in_negative_button
notification-permission:
card-type: notification-permission
title: juno_onboarding_enable_notifications_title_nimbus
body: juno_onboarding_enable_notifications_description_nimbus
image-res: ic_notification_permission
ordering: 30
primary-button-label: juno_onboarding_enable_notifications_positive_button
secondary-button-label: juno_onboarding_enable_notifications_negative_button
defaults:
- channel: developer
value:
enabled: false
- channel: nightly
value:
enabled: true
objects:
OnboardingCardData:
description: An object to describe a user facing onboarding card.
fields:
card-type:
type: OnboardingCardType
description: The type of the card.
# This should never be defaulted.
default: default-browser
title:
type: Text
description: The title text displayed to the user.
# This should never be defaulted.
default: ""
body:
type: Text
description: The message text displayed to the user. May contain linkable text.
# This should never be defaulted.
default: ""
link-text:
type: Option<Text>
description: >
The text to link from the body text. This should match the linkable text from the body text exactly.
e.g. body: This is a policy link
link-text: policy link
default: null
image-res:
type: Image
description: The resource id of the image to be displayed.
# This should never be defaulted.
default: ic_onboarding_welcome
image-is-illustration:
type: Boolean
description: True if the image type is an illustration.
default: true
ordering:
type: Int
description: Used to sequence the cards.
# This should never be defaulted.
default: 0
primary-button-label:
type: Text
description: The text to display on the primary button.
# This should never be defaulted.
default: ""
secondary-button-label:
type: Text
description: The text to display on the secondary button.
# This should never be defaulted.
default: ""
enums:
OnboardingCardType:
description: An enum to describe a type of card.
variants:
default-browser:
description: Allows user to set Firefox as the default browser.
sync-sign-in:
description: Allows user to sync with a Firefox account.
notification-permission:
description: Allows user to enable notification permission.

@ -21,9 +21,11 @@ activation:
first-session:
description: |
This ping is intended to capture the source of the app install
on the first session.
**THIS IS NOT A GENERIC FIRST USE PING** This ping is intended to capture
Adjust attribution. Use of this ping for other analyses will result in
undesirable outcomes.
include_client_id: true
send_if_empty: true
bugs:
- https://github.com/mozilla-mobile/fenix/issues/7295
data_reviews:
@ -62,3 +64,16 @@ spoc:
- https://github.com/mozilla-mobile/fenix/pull/27550#issuecomment-1295027631
notification_emails:
- android-probes@mozilla.com
cookie-banner-report-site:
description: |
This ping is needed when the cookie banner reducer doesn't work on
a website, and the user wants to report the site.
This ping doesn't include a client id.
include_client_id: false
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1805450
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/1298#pullrequestreview-1350344223
notification_emails:
- android-probes@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>

@ -10,4 +10,4 @@
</audio>
</div>
</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>

@ -1,4 +1,11 @@
<html>
<!-- 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 asset is using the code behind
- https://www.mozilla-anti-tracking.com/test/dfpi/storage_access_api.html
- test page.
- Source repository: https://github.com/mozilla/anti-tracking-test-pages -->
<body>
<h2>Cross-site cookies storage access test</h2>
<h3>anti-tracker-test.com</h3>

@ -19,4 +19,8 @@
<a href="tel://1234567890">Telephone link</a>
</section>
<section>
<a href="https://m.youtube.com/user/mozilla?cbrd=1">Youtube link</a>
</section>
</html>

@ -7,4 +7,4 @@
<p id="testContent">Page content: 1</p>
</h1>
</body>
</html>
</html>

@ -7,4 +7,4 @@
<p id="testContent">Page content: 2</p>
</h1>
</body>
</html>
</html>

@ -8,6 +8,9 @@
<p>
<a href="https://play.google.com/store/apps/details?id=org.mozilla.fenix">Mozilla Playstore link</a>
</p>
<p>
<a href="../resources/washington.pdf">PDF file</a>
</p>
</h1>
</body>
</html>
</html>

@ -35,6 +35,15 @@
<button onclick="printOption()" id="submitOption"> Submit drop down option </button>
<div id="displayOption"></div>
</br>
<form method="post" enctype="multipart/form-data">
<div>
<label>Choose file to upload</label>
</br>
<input type="file" id="upload_file"/>
</div>
</form>
<script>
function printOption() {
let dropDown = document.querySelector("#dropDown");

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<!-- 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/. -->
<html>
<head>
<title>Muted_Video_Test_Page</title>
</head>
<body>
<p id="testContent">Page content: muted video player</p>
<div class="playbackState">
<p>Media file not playing</p>
</div>
<div id="video-container" style="text-align:center">
<button onclick="play()">Play</button>
<button onclick="pause()">Pause</button>
<button onclick="fullscreen()">Full Screen</button>
<br><br>
<video id="mutedVideo" width="420" autoplay muted controls loop>
<source src="../resources/clip.mp4" type="video/mp4">
Your browser does not support HTML video.
</video>
</div>
<script>
const mutedVideo = document.getElementById("mutedVideo");
function play() {
mutedVideo.play();
}
function pause() {
mutedVideo.pause();
}
function fullscreen() {
mutedVideo.requestFullscreen();
}
mutedVideo.addEventListener('playing', (event) => {
document.querySelector('.playbackState').innerHTML="Media file is playing";
});
mutedVideo.addEventListener('pause', (event) => {
document.querySelector('.playbackState').innerHTML="Media file is paused";
});
</script>
</body>
</html>

@ -20,4 +20,4 @@
</script>
</body>
</html>
</html>

@ -25,4 +25,4 @@
</script>
</body>
</html>
</html>

@ -2,14 +2,16 @@
<!-- 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 asset is using the code behind
- https://www.mozilla-anti-tracking.com/test/trackingprotection/test_pages/tracking_protection.html
- test page.
- Source repository: https://github.com/mozilla/anti-tracking-test-pages -->
<html dir="ltr" xml:lang="en-US" lang="en-US">
<head>
<meta charset="utf8">
<script src="../resources/trackingAPI.js" type="text/javascript"></script>
</head>
<body>
<iframe src="http://trackertest.org/"></iframe>
<h3>Level 1 (Basic) List</h3>
<p>social-track-digest256:</p>
<img
@ -18,19 +20,19 @@
<br/>
<p>ads-track-digest256:</p>
<img
src="https://ads-track-digest256.dummytracker.org/test_not_blocked.png"
src="https://ads-track-digest256.dummytracker.org/test_not_blocked.png" alt="ads not blocked"
onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='ads blocked'">
<br/>
<p>analytics-track-digest256:</p>
<img
src="https://analytics-track-digest256.dummytracker.org/test_not_blocked.png"
src="https://analytics-track-digest256.dummytracker.org/test_not_blocked.png" alt="analytics not blocked"
onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='analytics blocked'">
<br/>
<p>Fingerprinting:
<pre id="result">test not run</pre>
<script src="https://base-fingerprinting-track-digest256.dummytracker.org/tracker.js"
onerror="this.onerror=null;var result=document.getElementById('result');result.innerHTML='Fingerprinting blocked';"
onload="this.onload=null;var result=document.getElementById('result');result.innerHTML='Fingerprinting NOT blocked';"
onload="this.onload=null;var result=document.getElementById('result');result.innerHTML='Fingerprinting not blocked';"
></script>
</p>
<br/>

@ -1,14 +1,49 @@
<!DOCTYPE HTML>
<!-- 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/. -->
<html>
<head>
<title>Video_Test_Page</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<p id="testContent">Page content: video player</p>
<div id="video-container">
<video id="videoSample" width="320" height="240" controls loop>
<source src="../resources/clip.mp4">
<div class="playbackState">
<p>Media file not playing</p>
</div>
<div id="video-container" style="text-align:center">
<button onclick="play()">Play</button>
<button onclick="pause()">Pause</button>
<button onclick="fullscreen()">Full Screen</button>
<br><br>
<video id="video" width="420" autoplay controls loop>
<source src="../resources/clip.mp4" type="video/mp4">
Your browser does not support HTML video.
</video>
</div>
<script>
const video = document.getElementById("video");
function play() {
video.play();
}
function pause() {
video.pause();
}
function fullscreen() {
video.requestFullscreen();
}
video.addEventListener('playing', (event) => {
document.querySelector('.playbackState').innerHTML="Media file is playing";
});
video.addEventListener('pause', (event) => {
document.querySelector('.playbackState').innerHTML="Media file is paused";
});
</script>
</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,21 @@ interface FeatureSettingsHelper {
*/
var etpPolicy: ETPPolicy
/**
* Enable or disable cookie banner reduction dialog.
*/
var isCookieBannerReductionDialogEnabled: Boolean
/**
* Enable or disable open in app banner.
*/
var isOpenInAppBannerEnabled: Boolean
/**
* Enable or disable the Tabs Tray to Compose rewrite.
*/
var tabsTrayRewriteEnabled: Boolean
fun applyFlagUpdates()
fun resetAllFeatureFlags()
@ -83,5 +98,4 @@ enum class ETPPolicy {
STANDARD,
STRICT,
CUSTOM,
;
}

@ -31,9 +31,14 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
isRecentlyVisitedFeatureEnabled = settings.historyMetadataUIFeature,
isPWAsPromptEnabled = !settings.userKnowsAboutPwas,
isTCPCFREnabled = settings.shouldShowTotalCookieProtectionCFR,
isUnifiedSearchEnabled = false,
isWallpaperOnboardingEnabled = settings.showWallpaperOnboarding,
isDeleteSitePermissionsEnabled = settings.deleteSitePermissions,
isCookieBannerReductionDialogEnabled = !settings.userOptOutOfReEngageCookieBannerDialog,
isOpenInAppBannerEnabled = settings.shouldShowOpenInAppBanner,
etpPolicy = getETPPolicy(settings),
tabsTrayRewriteEnabled = settings.enableTabsTrayToCompose,
newSearchSettingsEnabled = false,
)
/**
@ -58,7 +63,10 @@ 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 isOpenInAppBannerEnabled: Boolean by updatedFeatureFlags::isOpenInAppBannerEnabled
override var etpPolicy: ETPPolicy by updatedFeatureFlags::etpPolicy
override var tabsTrayRewriteEnabled: Boolean by updatedFeatureFlags::tabsTrayRewriteEnabled
override fun applyFlagUpdates() {
applyFeatureFlags(updatedFeatureFlags)
@ -67,6 +75,7 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
override fun resetAllFeatureFlags() {
applyFeatureFlags(initialFeatureFlags)
}
override var isDeleteSitePermissionsEnabled: Boolean by updatedFeatureFlags::isDeleteSitePermissionsEnabled
private fun applyFeatureFlags(featureFlags: FeatureFlags) {
@ -78,8 +87,13 @@ class FeatureSettingsHelperDelegate : FeatureSettingsHelper {
settings.historyMetadataUIFeature = featureFlags.isRecentlyVisitedFeatureEnabled
settings.userKnowsAboutPwas = !featureFlags.isPWAsPromptEnabled
settings.shouldShowTotalCookieProtectionCFR = featureFlags.isTCPCFREnabled
settings.showUnifiedSearchFeature = featureFlags.isUnifiedSearchEnabled
settings.showWallpaperOnboarding = featureFlags.isWallpaperOnboardingEnabled
settings.deleteSitePermissions = featureFlags.isDeleteSitePermissionsEnabled
settings.userOptOutOfReEngageCookieBannerDialog = !featureFlags.isCookieBannerReductionDialogEnabled
settings.shouldShowOpenInAppBanner = featureFlags.isOpenInAppBannerEnabled
settings.enableTabsTrayToCompose = featureFlags.tabsTrayRewriteEnabled
settings.enableUnifiedSearchSettingsUI = featureFlags.newSearchSettingsEnabled
setETPPolicy(featureFlags.etpPolicy)
}
}
@ -93,9 +107,14 @@ private data class FeatureFlags(
var isRecentlyVisitedFeatureEnabled: Boolean,
var isPWAsPromptEnabled: Boolean,
var isTCPCFREnabled: Boolean,
val isUnifiedSearchEnabled: Boolean,
var isWallpaperOnboardingEnabled: Boolean,
var isDeleteSitePermissionsEnabled: Boolean,
var isCookieBannerReductionDialogEnabled: Boolean,
var isOpenInAppBannerEnabled: Boolean,
var etpPolicy: ETPPolicy,
var tabsTrayRewriteEnabled: Boolean,
var newSearchSettingsEnabled: Boolean,
)
internal fun getETPPolicy(settings: Settings): ETPPolicy {

@ -8,6 +8,7 @@ package org.mozilla.fenix.helpers
import android.content.Intent
import android.view.ViewConfiguration.getLongPressTimeout
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiSelector
@ -17,6 +18,8 @@ import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.onboarding.FenixOnboarding
typealias HomeActivityComposeTestRule = AndroidComposeTestRule<HomeActivityTestRule, HomeActivity>
/**
* A [org.junit.Rule] to handle shared test set up for tests on [HomeActivity].
*
@ -48,7 +51,10 @@ class HomeActivityTestRule(
isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR,
isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding,
isDeleteSitePermissionsEnabled: Boolean = settings.deleteSitePermissions,
isCookieBannerReductionDialogEnabled: Boolean = !settings.userOptOutOfReEngageCookieBannerDialog,
isOpenInAppBannerEnabled: Boolean = settings.shouldShowOpenInAppBanner,
etpPolicy: ETPPolicy = getETPPolicy(settings),
tabsTrayRewriteEnabled: Boolean = false,
) : this(initialTouchMode, launchActivity, skipOnboarding) {
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
this.isPocketEnabled = isPocketEnabled
@ -59,7 +65,10 @@ class HomeActivityTestRule(
this.isTCPCFREnabled = isTCPCFREnabled
this.isWallpaperOnboardingEnabled = isWallpaperOnboardingEnabled
this.isDeleteSitePermissionsEnabled = isDeleteSitePermissionsEnabled
this.isCookieBannerReductionDialogEnabled = isCookieBannerReductionDialogEnabled
this.isOpenInAppBannerEnabled = isOpenInAppBannerEnabled
this.etpPolicy = etpPolicy
this.tabsTrayRewriteEnabled = tabsTrayRewriteEnabled
}
/**
@ -67,7 +76,7 @@ class HomeActivityTestRule(
*/
fun applySettingsExceptions(settings: (FeatureSettingsHelper) -> Unit) {
FeatureSettingsHelperDelegate().also {
settings(it)
settings(this)
applyFlagUpdates()
}
}
@ -103,14 +112,18 @@ class HomeActivityTestRule(
initialTouchMode: Boolean = false,
launchActivity: Boolean = true,
skipOnboarding: Boolean = false,
tabsTrayRewriteEnabled: Boolean = false,
) = HomeActivityTestRule(
initialTouchMode = initialTouchMode,
launchActivity = launchActivity,
skipOnboarding = skipOnboarding,
tabsTrayRewriteEnabled = tabsTrayRewriteEnabled,
isJumpBackInCFREnabled = false,
isPWAsPromptEnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
isCookieBannerReductionDialogEnabled = false,
isOpenInAppBannerEnabled = false,
)
}
}
@ -146,7 +159,10 @@ class HomeActivityIntentTestRule internal constructor(
isTCPCFREnabled: Boolean = settings.shouldShowTotalCookieProtectionCFR,
isWallpaperOnboardingEnabled: Boolean = settings.showWallpaperOnboarding,
isDeleteSitePermissionsEnabled: Boolean = settings.deleteSitePermissions,
isCookieBannerReductionDialogEnabled: Boolean = !settings.userOptOutOfReEngageCookieBannerDialog,
isOpenInAppBannerEnabled: Boolean = settings.shouldShowOpenInAppBanner,
etpPolicy: ETPPolicy = getETPPolicy(settings),
tabsTrayRewriteEnabled: Boolean = false,
) : this(initialTouchMode, launchActivity, skipOnboarding) {
this.isHomeOnboardingDialogEnabled = isHomeOnboardingDialogEnabled
this.isPocketEnabled = isPocketEnabled
@ -157,7 +173,10 @@ class HomeActivityIntentTestRule internal constructor(
this.isTCPCFREnabled = isTCPCFREnabled
this.isWallpaperOnboardingEnabled = isWallpaperOnboardingEnabled
this.isDeleteSitePermissionsEnabled = isDeleteSitePermissionsEnabled
this.isCookieBannerReductionDialogEnabled = isCookieBannerReductionDialogEnabled
this.isOpenInAppBannerEnabled = isOpenInAppBannerEnabled
this.etpPolicy = etpPolicy
this.tabsTrayRewriteEnabled = tabsTrayRewriteEnabled
}
private val longTapUserPreference = getLongPressTimeout()
@ -218,6 +237,8 @@ class HomeActivityIntentTestRule internal constructor(
isTCPCFREnabled = settings.shouldShowTotalCookieProtectionCFR
isWallpaperOnboardingEnabled = settings.showWallpaperOnboarding
isDeleteSitePermissionsEnabled = settings.deleteSitePermissions
isCookieBannerReductionDialogEnabled = !settings.userOptOutOfReEngageCookieBannerDialog
isOpenInAppBannerEnabled = settings.shouldShowOpenInAppBanner
etpPolicy = getETPPolicy(settings)
}
@ -236,14 +257,18 @@ class HomeActivityIntentTestRule internal constructor(
initialTouchMode: Boolean = false,
launchActivity: Boolean = true,
skipOnboarding: Boolean = false,
tabsTrayRewriteEnabled: Boolean = false,
) = HomeActivityIntentTestRule(
initialTouchMode = initialTouchMode,
launchActivity = launchActivity,
skipOnboarding = skipOnboarding,
tabsTrayRewriteEnabled = tabsTrayRewriteEnabled,
isJumpBackInCFREnabled = false,
isPWAsPromptEnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
isCookieBannerReductionDialogEnabled = false,
isOpenInAppBannerEnabled = false,
)
}
}

@ -6,8 +6,10 @@ 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.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.mDevice
/**
@ -21,6 +23,9 @@ object MatcherHelper {
fun itemContainingText(itemText: String) =
mDevice.findObject(UiSelector().textContains(itemText))
fun itemWithText(itemText: String) =
mDevice.findObject(UiSelector().text(itemText))
fun itemWithDescription(description: String) =
mDevice.findObject(UiSelector().descriptionContains(description))
@ -41,21 +46,38 @@ 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 itemWithResIdContainingText(resourceId: String, text: String) =
mDevice.findObject(UiSelector().resourceId(resourceId).textContains(text))
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(waitingTimeShort))
}
}
}
fun assertItemContainingTextExists(vararg appItems: UiObject) {
fun assertItemContainingTextExists(vararg appItems: UiObject, exists: Boolean = true) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
if (exists) {
assertTrue(appItem.waitForExists(waitingTime))
} else {
assertFalse(appItem.waitForExists(waitingTimeShort))
}
}
}
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(waitingTimeShort))
}
}
}
@ -77,9 +99,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(waitingTimeShort))
}
}
}
fun assertItemIsEnabledAndVisible(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime) && appItem.isEnabled)
}
}
}

@ -7,8 +7,10 @@ package org.mozilla.fenix.helpers
import android.graphics.Bitmap
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers
import junit.framework.AssertionFailedError
import org.hamcrest.CoreMatchers.not
@ -70,3 +72,21 @@ fun ViewInteraction.isVisibleForUser(): Boolean {
return true
}
fun atPosition(position: Int, itemMatcher: Matcher<View?>): Matcher<View?>? {
return object : BoundedMatcher<View?, RecyclerView>(
RecyclerView::class.java,
) {
override fun describeTo(description: Description) {
description.appendText("has item at position $position: ")
itemMatcher.describeTo(description)
}
override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position)
?: // has no item on such position
return false
return itemMatcher.matches(viewHolder.itemView)
}
}
}

@ -15,6 +15,7 @@ import org.junit.runners.model.Statement
import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.helpers.IdlingResourceHelper.unregisterAllIdlingResources
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.setNetworkEnabled
/**
* Rule to retry flaky tests for a given number of times, catching some of the more common exceptions.
@ -34,6 +35,7 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
base.evaluate()
break
} catch (t: AssertionError) {
setNetworkEnabled(true)
unregisterAllIdlingResources()
runBlocking {
permissionStorage.deleteAllSitePermissions()
@ -50,6 +52,7 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
throw t
}
} catch (t: UiObjectNotFoundException) {
setNetworkEnabled(true)
unregisterAllIdlingResources()
runBlocking {
permissionStorage.deleteAllSitePermissions()
@ -58,6 +61,7 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
throw t
}
} catch (t: NoMatchingViewException) {
setNetworkEnabled(true)
unregisterAllIdlingResources()
runBlocking {
permissionStorage.deleteAllSitePermissions()
@ -66,6 +70,7 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
throw t
}
} catch (t: IdlingResourceTimeoutException) {
setNetworkEnabled(true)
unregisterAllIdlingResources()
runBlocking {
permissionStorage.deleteAllSitePermissions()
@ -74,6 +79,7 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
throw t
}
} catch (t: RuntimeException) {
setNetworkEnabled(true)
unregisterAllIdlingResources()
runBlocking {
permissionStorage.deleteAllSitePermissions()
@ -82,6 +88,7 @@ class RetryTestRule(private val retryCount: Int = 5) : TestRule {
throw t
}
} catch (t: NullPointerException) {
setNetworkEnabled(true)
unregisterAllIdlingResources()
runBlocking {
permissionStorage.deleteAllSitePermissions()

@ -127,6 +127,14 @@ object TestAssetHelper {
return TestAsset(url, content, title)
}
fun getMutedVideoPageAsset(server: MockWebServer): TestAsset {
val url = server.url("pages/mutedVideoPage.html").toString().toUri()!!
val title = "Muted_Video_Test_Page"
val content = "Page content: muted video player"
return TestAsset(url, content, title)
}
fun getStorageTestAsset(server: MockWebServer, pageAsset: String): TestAsset {
val url = server.url("pages/$pageAsset").toString().toUri()!!

@ -64,16 +64,18 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.helpers.idlingresource.NetworkConnectionIdlingResource
import org.mozilla.fenix.ui.robots.BrowserRobot
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.utils.IntentUtils
import org.mozilla.gecko.util.ThreadUtils
import java.io.File
import java.util.Locale
import java.util.regex.Pattern
object TestHelper {
@ -113,7 +115,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"
}
@ -126,12 +128,24 @@ object TestHelper {
)
}
fun clickSnackbarButton(expectedText: String) =
clickPageObject(itemWithResIdAndText("$packageName:id/snackbar_btn", expectedText))
fun waitUntilSnackbarGone() {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/snackbar_layout"),
).waitUntilGone(waitingTime)
}
fun verifySnackBarText(expectedText: String) {
assertTrue(
mDevice.findObject(
UiSelector()
.textContains(expectedText),
).waitForExists(waitingTime),
)
}
fun verifyUrl(urlSubstring: String, resourceName: String, resId: Int) {
waitUntilObjectIsFound(resourceName)
mDevice.findObject(UiSelector().text(urlSubstring)).waitForExists(waitingTime)
@ -266,17 +280,7 @@ object TestHelper {
}
}
fun assertPlayStoreOpens() {
if (isPackageInstalled(Constants.PackageName.GOOGLE_PLAY_SERVICES)) {
try {
intended(toPackage(Constants.PackageName.GOOGLE_PLAY_SERVICES))
} catch (e: AssertionFailedError) {
BrowserRobot().verifyRateOnGooglePlayURL()
}
} else {
BrowserRobot().verifyRateOnGooglePlayURL()
}
}
fun assertYoutubeAppOpens() = intended(toPackage(YOUTUBE_APP))
/**
* Checks whether the latest activity of the application is used for custom tabs or PWAs.
@ -338,7 +342,7 @@ object TestHelper {
)
}
fun getStringResource(id: Int) = appContext.resources.getString(id, appName)
fun getStringResource(id: Int, argument: String = appName) = appContext.resources.getString(id, argument)
fun setCustomSearchEngine(searchEngine: SearchEngine) {
with(appContext.components.useCases.searchUseCases) {
@ -347,36 +351,31 @@ object TestHelper {
}
}
fun grantPermission() {
if (Build.VERSION.SDK_INT >= 23) {
// Permission allow dialogs differ on various Android APIs
fun grantSystemPermission() {
val whileUsingTheAppPermissionButton: UiObject =
mDevice.findObject(UiSelector().textContains("While using the app"))
val allowPermissionButton: UiObject =
mDevice.findObject(
By.text(
when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.R -> Pattern.compile(
"WHILE USING THE APP",
Pattern.CASE_INSENSITIVE,
)
else -> Pattern.compile("Allow", Pattern.CASE_INSENSITIVE)
},
),
).click()
UiSelector()
.textContains("Allow")
.className("android.widget.Button"),
)
if (Build.VERSION.SDK_INT >= 23) {
if (whileUsingTheAppPermissionButton.waitForExists(waitingTimeShort)) {
whileUsingTheAppPermissionButton.click()
} else if (allowPermissionButton.waitForExists(waitingTimeShort)) {
allowPermissionButton.click()
}
}
}
// Permission deny dialogs differ on various Android APIs
fun denyPermission() {
if (Build.VERSION.SDK_INT >= 23) {
mDevice.findObject(
By.text(
when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.R -> Pattern.compile(
"DENY",
Pattern.CASE_INSENSITIVE,
)
else -> Pattern.compile("Deny", Pattern.CASE_INSENSITIVE)
},
),
).click()
}
mDevice.findObject(UiSelector().textContains("Deny")).waitForExists(waitingTime)
mDevice.findObject(UiSelector().textContains("Deny")).click()
}
fun isTestLab(): Boolean {
@ -466,4 +465,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)
}
}

@ -0,0 +1,97 @@
package org.mozilla.fenix.onboarding.view
import androidx.compose.ui.layout.ContentScale
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.mozilla.experiments.nimbus.StringHolder
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.nimbus.OnboardingCardData
import org.mozilla.fenix.nimbus.OnboardingCardType
class JunoOnboardingMapperTest {
@get:Rule
val activityTestRule =
HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Test
fun showNotificationTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages() {
val expected = listOf(defaultBrowserPageUiData, syncPageUiData, notificationPageUiData)
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(true))
}
@Test
fun showNotificationFalse_pagesToDisplay_returnsSortedListOfConvertedPagesWithoutNotificationPage() {
val expected = listOf(defaultBrowserPageUiData, syncPageUiData)
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(false))
}
}
private val defaultBrowserPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.DEFAULT_BROWSER,
imageRes = R.drawable.ic_onboarding_welcome,
imageResContentScale = ContentScale.Fit,
title = "default browser title",
description = "default browser body with link text",
linkText = "link text",
primaryButtonLabel = "default browser primary button text",
secondaryButtonLabel = "default browser secondary button text",
)
private val syncPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.SYNC_SIGN_IN,
imageRes = R.drawable.ic_onboarding_sync,
imageResContentScale = ContentScale.Fit,
title = "sync title",
description = "sync body",
primaryButtonLabel = "sync primary button text",
secondaryButtonLabel = "sync secondary button text",
)
private val notificationPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.NOTIFICATION_PERMISSION,
imageRes = R.drawable.ic_notification_permission,
imageResContentScale = ContentScale.Crop,
title = "notification title",
description = "notification body",
primaryButtonLabel = "notification primary button text",
secondaryButtonLabel = "notification secondary button text",
)
private val defaultBrowserCardData = OnboardingCardData(
cardType = OnboardingCardType.DEFAULT_BROWSER,
imageRes = R.drawable.ic_onboarding_welcome,
imageIsIllustration = true,
title = StringHolder(null, "default browser title"),
body = StringHolder(null, "default browser body with link text"),
linkText = StringHolder(null, "link text"),
primaryButtonLabel = StringHolder(null, "default browser primary button text"),
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
ordering = 10,
)
private val syncCardData = OnboardingCardData(
cardType = OnboardingCardType.SYNC_SIGN_IN,
imageRes = R.drawable.ic_onboarding_sync,
imageIsIllustration = true,
title = StringHolder(null, "sync title"),
body = StringHolder(null, "sync body"),
primaryButtonLabel = StringHolder(null, "sync primary button text"),
secondaryButtonLabel = StringHolder(null, "sync secondary button text"),
ordering = 20,
)
private val notificationCardData = OnboardingCardData(
cardType = OnboardingCardType.NOTIFICATION_PERMISSION,
imageRes = R.drawable.ic_notification_permission,
imageIsIllustration = false,
title = StringHolder(null, "notification title"),
body = StringHolder(null, "notification body"),
primaryButtonLabel = StringHolder(null, "notification primary button text"),
secondaryButtonLabel = StringHolder(null, "notification secondary button text"),
ordering = 30,
)
private val unsortedAllKnownCardData = listOf(
syncCardData,
notificationCardData,
defaultBrowserCardData,
)

@ -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 = 19
/**
* 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": {}

@ -1,16 +1,17 @@
import logging
import subprocess
import os
logging.getLogger(__name__).addHandler(logging.NullHandler())
class ADBrun(object):
binary = 'adbrun'
binary = "adbrun"
logger = logging.getLogger()
def launch(self):
# First close sim if any then launch
os.system('~/Library/Android/sdk/platform-tools/adb devices | grep emulator | cut -f1 | while read line; do ~/Library/Android/sdk/platform-tools/adb -s $line emu kill; done')
os.system(
"~/Library/Android/sdk/platform-tools/adb devices | grep emulator | cut -f1 | while read line; do ~/Library/Android/sdk/platform-tools/adb -s $line emu kill; done"
)
# Then launch sim
os.system("sh launchSimScript.sh")

@ -2,89 +2,92 @@ import io
import json
import os
import time
import os.path as path
from mozdownload import DirectScraper, FactoryScraper
from mozprofile import Profile
import mozinstall
import mozversion
import pytest
import requests
from mozdownload import DirectScraper, FactoryScraper
from mozprofile import Profile
from .tps import TPS
from .gradlewbuild import GradlewBuild
from .tps import TPS
here = os.path.dirname(__file__)
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def firefox(pytestconfig, tmpdir_factory):
binary = os.getenv('MOZREGRESSION_BINARY',
pytestconfig.getoption('firefox'))
binary = os.getenv("MOZREGRESSION_BINARY", pytestconfig.getoption("firefox"))
if binary is None:
cache_dir = str(pytestconfig.cache.makedir('firefox'))
scraper = FactoryScraper('daily', destination=cache_dir)
cache_dir = str(pytestconfig.cache.makedir("firefox"))
scraper = FactoryScraper("daily", destination=cache_dir)
build_path = scraper.download()
install_path = str(tmpdir_factory.mktemp('firefox'))
install_path = str(tmpdir_factory.mktemp("firefox"))
install_dir = mozinstall.install(src=build_path, dest=install_path)
binary = mozinstall.get_binary(install_dir, 'firefox')
binary = mozinstall.get_binary(install_dir, "firefox")
version = mozversion.get_version(binary)
if hasattr(pytestconfig, '_metadata'):
if hasattr(pytestconfig, "_metadata"):
pytestconfig._metadata.update(version)
return binary
@pytest.fixture
def firefox_log(pytestconfig, tmpdir):
firefox_log = str(tmpdir.join('firefox.log'))
firefox_log = str(tmpdir.join("firefox.log"))
pytestconfig._firefox_log = firefox_log
yield firefox_log
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def tps_addon(pytestconfig, tmpdir_factory):
path = pytestconfig.getoption('tps')
path = pytestconfig.getoption("tps")
if path is not None:
return path
task_url = 'https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/' \
'gecko.v2.mozilla-central.latest.firefox.addons.tps'
task_id = requests.get(task_url).json().get('taskId')
cache_dir = str(pytestconfig.cache.makedir('tps-{}'.format(task_id)))
addon_url = 'https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/' \
'{}/artifacts/public/tps.xpi'.format(task_id)
task_url = (
"https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/"
"gecko.v2.mozilla-central.latest.firefox.addons.tps"
)
task_id = requests.get(task_url).json().get("taskId")
cache_dir = str(pytestconfig.cache.makedir("tps-{}".format(task_id)))
addon_url = (
"https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/"
"{}/artifacts/public/tps.xpi".format(task_id)
)
scraper = DirectScraper(addon_url, destination=cache_dir)
return scraper.download()
@pytest.fixture
def tps_config(fxa_account, monkeypatch):
monkeypatch.setenv('FXA_EMAIL', fxa_account.email)
monkeypatch.setenv('FXA_PASSWORD', fxa_account.password)
monkeypatch.setenv("FXA_EMAIL", fxa_account.email)
monkeypatch.setenv("FXA_PASSWORD", fxa_account.password)
# Go to resources folder
os.chdir('../../../../..')
resources = r'resources'
os.chdir("../../../../..")
resources = r"resources"
resourcesDir = os.path.join(os.getcwd(), resources)
with open (os.path.join(resourcesDir, 'email.txt'), "w") as f:
with open(os.path.join(resourcesDir, "email.txt"), "w") as f:
f.write(fxa_account.email)
with open (os.path.join(resourcesDir, 'password.txt'), "w") as f:
with open(os.path.join(resourcesDir, "password.txt"), "w") as f:
f.write(fxa_account.password)
# Set the path where tests are
os.chdir('../')
os.chdir("../")
currentDir = os.getcwd()
testsDir = currentDir + "/androidTest/java/org/mozilla/fenix/syncintegration"
os.chdir(testsDir)
yield {'fx_account': {
'username': fxa_account.email,
'password': fxa_account.password}
yield {
"fx_account": {"username": fxa_account.email, "password": fxa_account.password}
}
@pytest.fixture
def tps_log(pytestconfig, tmpdir):
tps_log = str(tmpdir.join('tps.log'))
tps_log = str(tmpdir.join("tps.log"))
pytestconfig._tps_log = tps_log
yield tps_log
@ -92,89 +95,98 @@ def tps_log(pytestconfig, tmpdir):
@pytest.fixture
def tps_profile(pytestconfig, tps_addon, tps_config, tps_log, fxa_urls):
preferences = {
'app.update.enabled': False,
'browser.dom.window.dump.enabled': True,
'browser.onboarding.enabled': False,
'browser.sessionstore.resume_from_crash': False,
'browser.shell.checkDefaultBrowser': False,
'browser.startup.homepage_override.mstone': 'ignore',
'browser.startup.page': 0,
'browser.tabs.warnOnClose': False,
'browser.warnOnQuit': False,
'datareporting.policy.dataSubmissionEnabled': False,
"app.update.enabled": False,
"browser.dom.window.dump.enabled": True,
"browser.onboarding.enabled": False,
"browser.sessionstore.resume_from_crash": False,
"browser.shell.checkDefaultBrowser": False,
"browser.startup.homepage_override.mstone": "ignore",
"browser.startup.page": 0,
"browser.tabs.warnOnClose": False,
"browser.warnOnQuit": False,
"datareporting.policy.dataSubmissionEnabled": False,
# 'devtools.chrome.enabled': True,
# 'devtools.debugger.remote-enabled': True,
'engine.bookmarks.repair.enabled': False,
'extensions.autoDisableScopes': 10,
'extensions.experiments.enabled': True,
'extensions.update.enabled': False,
'extensions.update.notifyUser': False,
"engine.bookmarks.repair.enabled": False,
"extensions.autoDisableScopes": 10,
"extensions.experiments.enabled": True,
"extensions.update.enabled": False,
"extensions.update.notifyUser": False,
# While this line is commented prod is launched instead of stage
'identity.fxaccounts.autoconfig.uri': fxa_urls['content'],
'testing.tps.skipPingValidation': True,
'services.sync.firstSync': 'notReady',
'services.sync.lastversion': '1.0',
'services.sync.log.appender.console': 'Trace',
'services.sync.log.appender.dump': 'Trace',
'services.sync.log.appender.file.level': 'Trace',
'services.sync.log.appender.file.logOnSuccess': True,
'services.sync.log.logger': 'Trace',
'services.sync.log.logger.engine': 'Trace',
'services.sync.testing.tps': True,
'testing.tps.logFile': tps_log,
'toolkit.startup.max_resumed_crashes': -1,
'tps.config': json.dumps(tps_config),
'tps.seconds_since_epoch': int(time.time()),
'xpinstall.signatures.required': False
"identity.fxaccounts.autoconfig.uri": fxa_urls["content"],
"testing.tps.skipPingValidation": True,
"services.sync.firstSync": "notReady",
"services.sync.lastversion": "1.0",
"services.sync.log.appender.console": "Trace",
"services.sync.log.appender.dump": "Trace",
"services.sync.log.appender.file.level": "Trace",
"services.sync.log.appender.file.logOnSuccess": True,
"services.sync.log.logger": "Trace",
"services.sync.log.logger.engine": "Trace",
"services.sync.testing.tps": True,
"testing.tps.logFile": tps_log,
"toolkit.startup.max_resumed_crashes": -1,
"tps.config": json.dumps(tps_config),
"tps.seconds_since_epoch": int(time.time()),
"xpinstall.signatures.required": False,
}
profile = Profile(addons=[tps_addon], preferences=preferences)
pytestconfig._profile = profile.profile
yield profile
@pytest.fixture
def tps(firefox, firefox_log, monkeypatch, pytestconfig, tps_log, tps_profile):
yield TPS(firefox, firefox_log, tps_log, tps_profile)
@pytest.fixture
def gradlewbuild_log(pytestconfig, tmpdir):
gradlewbuild_log = str(tmpdir.join('gradlewbuild.log'))
gradlewbuild_log = str(tmpdir.join("gradlewbuild.log"))
pytestconfig._gradlewbuild_log = gradlewbuild_log
yield gradlewbuild_log
@pytest.fixture
def gradlewbuild(fxa_account, monkeypatch, gradlewbuild_log):
monkeypatch.setenv('FXA_EMAIL', fxa_account.email)
monkeypatch.setenv('FXA_PASSWORD', fxa_account.password)
monkeypatch.setenv("FXA_EMAIL", fxa_account.email)
monkeypatch.setenv("FXA_PASSWORD", fxa_account.password)
yield GradlewBuild(gradlewbuild_log)
def pytest_addoption(parser):
parser.addoption('--firefox', help='path to firefox binary (defaults to '
'downloading latest nightly build)')
parser.addoption('--tps', help='path to tps add-on (defaults to '
'downloading latest nightly build)')
parser.addoption(
"--firefox",
help="path to firefox binary (defaults to " "downloading latest nightly build)",
)
parser.addoption(
"--tps",
help="path to tps add-on (defaults to " "downloading latest nightly build)",
)
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
extra = getattr(report, 'extra', [])
pytest_html = item.config.pluginmanager.getplugin('html')
profile = getattr(item.config, '_profile', None)
extra = getattr(report, "extra", [])
pytest_html = item.config.pluginmanager.getplugin("html")
profile = getattr(item.config, "_profile", None)
if profile is not None and os.path.exists(profile):
# add sync logs to HTML report
for root, _, files in os.walk(os.path.join(profile, 'weave', 'logs')):
for root, _, files in os.walk(os.path.join(profile, "weave", "logs")):
for f in files:
path = os.path.join(root, f)
if pytest_html is not None:
with io.open(path, 'r', encoding='utf8') as f:
extra.append(pytest_html.extras.text(f.read(), 'Sync'))
report.sections.append(('Sync', 'Log: {}'.format(path)))
for log in ('Firefox', 'TPS', 'GradlewBuild'):
attr = '_{}_log'.format(log.lower())
with io.open(path, "r", encoding="utf8") as f:
extra.append(pytest_html.extras.text(f.read(), "Sync"))
report.sections.append(("Sync", "Log: {}".format(path)))
for log in ("Firefox", "TPS", "GradlewBuild"):
attr = "_{}_log".format(log.lower())
path = getattr(item.config, attr, None)
if path is not None and os.path.exists(path):
if pytest_html is not None:
with io.open(path, 'r', encoding='utf8') as f:
with io.open(path, "r", encoding="utf8") as f:
extra.append(pytest_html.extras.text(f.read(), log))
report.sections.append((log, 'Log: {}'.format(path)))
report.sections.append((log, "Log: {}".format(path)))
report.extra = extra

@ -1,13 +1,13 @@
import os
import sys
def test_sync_account_settings(tps, gradlewbuild):
gradlewbuild.test('checkAccountSettings')
gradlewbuild.test("checkAccountSettings")
def test_sync_history_from_desktop(tps, gradlewbuild):
tps.run('test_history.js')
gradlewbuild.test('checkHistoryFromDesktopTest')
'''
tps.run("test_history.js")
gradlewbuild.test("checkHistoryFromDesktopTest")
"""
def test_sync_bookmark_from_desktop(tps, gradlewbuild):
tps.run('test_bookmark.js')
gradlewbuild.test('checkBookmarkFromDesktopTest')
@ -23,4 +23,4 @@ def test_sync_bookmark_from_device(tps, gradlewbuild):
def test_sync_history_from_device(tps, gradlewbuild):
gradlewbuild.test('checkHistoryFromDeviceTest')
tps.run('test_history_desktop.js')
'''
"""

@ -0,0 +1,113 @@
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.MatcherHelper.itemContainingText
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.clickPageObject
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()) {
clickPageObject(itemContainingText(pdfFileName))
verifyUrl(pdfFileURL)
verifyPageContent(pdfFileContent)
}.openThreeDotMenu {
expandMenu()
}.openAddToHomeScreen {
verifyShortcutTextFieldTitle(pdfFileName)
clickAddShortcutButton()
clickAddAutomaticallyButton()
}.openHomeScreenShortcut(pdfFileName) {
verifyUrl(pdfFileURL)
}
}
}

@ -0,0 +1,432 @@
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.ui.robots.clickPageObject
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) {
clickPageObject(itemWithResId("streetAddress"))
clickSelectAddressButton()
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/address_name",
"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) {
clickPageObject(itemWithResId("streetAddress"))
verifySelectAddressButtonExists(true)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickSaveAndAutofillAddressesOption()
verifyAddressAutofillSection(false, true)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(addressFormPage.url) {
clickPageObject(itemWithResId("streetAddress"))
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) {
clickPageObject(itemWithResId("streetAddress"))
clickSelectAddressButton()
}.clickManageAddressButton {
verifyAutofillToolbarTitle()
}.goBackToBrowser {
verifySaveLoginPromptIsNotDisplayed()
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1814032")
@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) {
clickPageObject(itemWithResId("streetAddress"))
clickSelectAddressButton()
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/address_name",
"Harrison Street",
),
)
verifyAutofilledAddress("Harrison Street")
clearAddressForm()
clickPageObject(itemWithResId("streetAddress"))
clickSelectAddressButton()
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/address_name",
"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) {
clickPageObject(itemWithResId("streetAddress"))
clickSelectAddressButton()
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/address_name",
"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",
)
}
}
}

@ -19,11 +19,13 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
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.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.ui.robots.bookmarksMenu
@ -31,6 +33,7 @@ import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.multipleSelectionToolbar
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.searchScreen
/**
* Tests for verifying basic functionality of bookmarks
@ -43,11 +46,11 @@ class BookmarksTest {
private val bookmarksFolderName = "New Folder"
private val testBookmark = object {
var title: String = "Bookmark title"
var url: String = "https://www.test.com"
var url: String = "https://www.example.com"
}
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Rule
@JvmField
@ -169,6 +172,31 @@ class BookmarksTest {
}
}
@SmokeTest
@Test
fun cancelEditBookmarkTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.bookmarkPage {
clickSnackbarButton("EDIT")
}
bookmarksMenu {
verifyEditBookmarksView()
changeBookmarkTitle(testBookmark.title)
changeBookmarkUrl(testBookmark.url)
}.closeEditBookmarkSection {
}
browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarkTitle(defaultWebPage.title)
verifyBookmarkedURL(defaultWebPage.url.toString())
}
}
@SmokeTest
@Test
fun editBookmarkTest() {
@ -177,22 +205,22 @@ class BookmarksTest {
browserScreen {
createBookmark(defaultWebPage.url)
}.openThreeDotMenu {
}.openBookmarks {
registerAndCleanupIdlingResources(
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2),
) {}
}.openThreeDotMenu(defaultWebPage.url) {
}.clickEdit {
}.editBookmarkPage {
verifyEditBookmarksView()
verifyBookmarkNameEditBox()
verifyBookmarkURLEditBox()
verifyParentFolderSelector()
changeBookmarkTitle(testBookmark.title)
changeBookmarkUrl(testBookmark.url)
saveEditBookmark()
}
browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
registerAndCleanupIdlingResources(
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2),
) {}
verifyBookmarkTitle(testBookmark.title)
verifyBookmarkedURL(testBookmark.url)
verifyKeyboardHidden()
}.openBookmarkWithTitle(testBookmark.title) {
verifyUrl("example.com")
}
}
@ -715,4 +743,116 @@ class BookmarksTest {
verifyFolderTitle("My Folder")
}
}
@Test
fun verifySearchBookmarksViewTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen {
createBookmark(defaultWebPage.url)
}.openThreeDotMenu {
}.openBookmarks {
clickSearchButton()
verifyBookmarksSearchBar(true)
verifyBookmarksSearchBarPosition(true)
clickOutsideTheSearchBar()
verifyBookmarksSearchBar(false)
}.goBackToBrowserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openCustomizeSubMenu {
clickTopToolbarToggle()
}
exitMenu()
browserScreen {
}.openThreeDotMenu {
}.openBookmarks {
clickSearchButton()
verifyBookmarksSearchBar(true)
verifyBookmarksSearchBarPosition(false)
dismissBookmarksSearchBarUsingBackButton()
verifyBookmarksSearchBar(false)
}
}
@Test
fun verifySearchForBookmarkedItemsTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
createFolder(bookmarksFolderName)
}
exitMenu()
browserScreen {
createBookmark(firstWebPage.url, bookmarksFolderName)
createBookmark(secondWebPage.url)
}.openThreeDotMenu {
}.openBookmarks {
clickSearchButton()
// Search for a valid term
searchBookmarkedItem(firstWebPage.title)
verifySearchedBookmarkExists(firstWebPage.url.toString(), true)
verifySearchedBookmarkExists(secondWebPage.url.toString(), false)
// Search for invalid term
searchBookmarkedItem("Android")
verifySearchedBookmarkExists(firstWebPage.url.toString(), false)
verifySearchedBookmarkExists(secondWebPage.url.toString(), false)
}
}
@Test
fun verifyVoiceSearchInBookmarksTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen {
createBookmark(defaultWebPage.url)
}.openThreeDotMenu {
}.openBookmarks {
clickSearchButton()
verifyBookmarksSearchBar(true)
}
searchScreen {
startVoiceSearch()
}
}
@Test
fun verifyDeletedBookmarksCanNotBeSearchedTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val thirdWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 3)
browserScreen {
createBookmark(firstWebPage.url)
createBookmark(secondWebPage.url)
createBookmark(thirdWebPage.url)
}.openThreeDotMenu {
}.openBookmarks {
}.openThreeDotMenu(firstWebPage.url) {
}.clickDelete {
verifyBookmarkIsDeleted(firstWebPage.title)
}.openThreeDotMenu(secondWebPage.url) {
}.clickDelete {
verifyBookmarkIsDeleted(secondWebPage.title)
clickSearchButton()
searchBookmarkedItem("generic")
verifySearchedBookmarkExists(firstWebPage.url.toString(), false)
verifySearchedBookmarkExists(secondWebPage.url.toString(), false)
verifySearchedBookmarkExists(thirdWebPage.url.toString(), true)
dismissBookmarksSearchBar()
}.openThreeDotMenu(thirdWebPage.url) {
}.clickDelete {
verifyBookmarkIsDeleted(thirdWebPage.title)
clickSearchButton()
searchBookmarkedItem("generic")
verifySearchedBookmarkExists(thirdWebPage.url.toString(), false)
}
}
}

@ -5,13 +5,22 @@
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.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.setNetworkEnabled
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
@ -23,6 +32,7 @@ class BrowsingErrorPagesTest {
private val unwantedSoftwareWarning =
getStringResource(R.string.mozac_browser_errorpages_safe_browsing_unwanted_uri_title)
private val harmfulSiteWarning = getStringResource(R.string.mozac_browser_errorpages_safe_harmful_uri_title)
private lateinit var mockWebServer: MockWebServer
@get: Rule
val mActivityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@ -31,6 +41,21 @@ class BrowsingErrorPagesTest {
@JvmField
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
// Restoring network connection
setNetworkEnabled(true)
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun blockMalwarePageTest() {
@ -74,4 +99,56 @@ class BrowsingErrorPagesTest {
verifyPageContent(harmfulSiteWarning)
}
}
// Failing with network interruption, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1833874
// This tests the server ERROR_CONNECTION_REFUSED
@Test
fun connectionRefusedErrorMessageTest() {
val testUrl = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(testUrl.url) {
waitForPageToLoad()
verifyPageContent(testUrl.content)
// Disconnecting the server
mockWebServer.shutdown()
}.openThreeDotMenu {
}.refreshPage {
waitForPageToLoad()
verifyConnectionErrorMessage()
}
}
@Test
fun addressNotFoundErrorMessageTest() {
val url = "ww.example.com"
navigationToolbar {
}.enterURLAndEnterToBrowser(url.toUri()) {
waitForPageToLoad()
verifyAddressNotFoundErrorMessage()
clickPageObject(itemWithResId("errorTryAgain"))
verifyAddressNotFoundErrorMessage()
}
}
@Test
fun noInternetConnectionErrorMessageTest() {
val url = "www.example.com"
setNetworkEnabled(false)
navigationToolbar {
}.enterURLAndEnterToBrowser(url.toUri()) {
verifyNoInternetConnectionErrorMessage()
}
setNetworkEnabled(true)
browserScreen {
clickPageObject(itemWithResId("errorTryAgain"))
waitForPageToLoad()
verifyPageContent("Example Domain")
}
}
}

@ -10,13 +10,13 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.collectionRobot
import org.mozilla.fenix.ui.robots.homeScreen
@ -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)
}
@ -397,7 +406,6 @@ class CollectionTest {
}
@Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun selectTabOnLongTapTest() {
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -422,14 +430,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 +470,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,14 +481,15 @@ class CollectionTest {
}
homeScreen {
}.expandCollection(collectionName, composeTestRule) {
verifyCollectionIsDisplayed(collectionName)
}.expandCollection(collectionName) {
clickCollectionThreeDotButton(composeTestRule)
selectDeleteCollection(composeTestRule)
}
homeScreen {
verifySnackBarText("Collection deleted")
clickUndoSnackBarButton()
clickSnackbarButton("UNDO")
verifyCollectionIsDisplayed(collectionName, true)
}
}

@ -0,0 +1,381 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.google.android.material.bottomsheet.BottomSheetBehavior
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
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.notificationShade
/**
* Tests for verifying basic functionality of tabbed browsing
*
* Including:
* - Opening a tab
* - Opening a private tab
* - Verifying tab list
* - Closing all tabs
* - Close tab
* - Swipe to close tab (temporarily disabled)
* - Undo close tab
* - Close private tabs persistent notification
* - Empty tab tray state
* - Tab tray details
* - Shortcut context menu navigation
*/
class ComposeTabbedBrowsingTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
@get:Rule(order = 0)
val composeTestRule =
AndroidComposeTestRule(
HomeActivityTestRule.withDefaultSettingsOverrides(
tabsTrayRewriteEnabled = true,
),
) { it.activity }
@Rule(order = 1)
@JvmField
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun openNewTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
verifyTabCounter("1")
}.openComposeTabDrawer(composeTestRule) {
verifyNormalBrowsingButtonIsSelected()
verifyExistingOpenTabs("Test_Page_1")
closeTab()
}
homeScreen {
}.openComposeTabDrawer(composeTestRule) {
verifyNoOpenTabsInNormalBrowsing()
}.openNewTab {
}.submitQuery(defaultWebPage.url.toString()) {
mDevice.waitForIdle()
verifyTabCounter("1")
}.openComposeTabDrawer(composeTestRule) {
verifyNormalBrowsingButtonIsSelected()
verifyExistingOpenTabs("Test_Page_1")
}
}
@Test
fun openNewPrivateTabTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
verifyTabCounter("1")
}.openComposeTabDrawer(composeTestRule) {
verifyPrivateTabsList()
verifyPrivateBrowsingButtonIsSelected()
}.toggleToNormalTabs {
verifyNoOpenTabsInNormalBrowsing()
}.toggleToPrivateTabs {
verifyPrivateTabsList()
}
}
@Test
fun closeAllTabsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openComposeTabDrawer(composeTestRule) {
verifyNormalTabsList()
}.openThreeDotMenu {
verifyCloseAllTabsButton()
verifyShareAllTabsButton()
verifySelectTabsButton()
}.closeAllTabs {
verifyTabCounter("0")
}
// Repeat for Private Tabs
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openComposeTabDrawer(composeTestRule) {
verifyPrivateTabsList()
}.openThreeDotMenu {
verifyCloseAllTabsButton()
}.closeAllTabs {
verifyTabCounter("0")
}
}
@Ignore("Being converted in: https://bugzilla.mozilla.org/show_bug.cgi?id=1832617")
@Test
fun closeTabTest() {
// val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
//
// navigationToolbar {
// }.enterURLAndEnterToBrowser(genericURL.url) {
// }.openTabDrawer {
// verifyExistingOpenTabs("Test_Page_1")
// closeTab()
// }
// homeScreen {
// verifyTabCounter("0")
// }.openNavigationToolbar {
// }.enterURLAndEnterToBrowser(genericURL.url) {
// }.openTabDrawer {
// verifyExistingOpenTabs("Test_Page_1")
// swipeTabRight("Test_Page_1")
// }
// homeScreen {
// verifyTabCounter("0")
// }.openNavigationToolbar {
// }.enterURLAndEnterToBrowser(genericURL.url) {
// }.openTabDrawer {
// verifyExistingOpenTabs("Test_Page_1")
// swipeTabLeft("Test_Page_1")
// }
// homeScreen {
// verifyTabCounter("0")
// }
}
@Test
fun verifyUndoSnackBarTest() {
// disabling these features because they interfere with the snackbar visibility
composeTestRule.activityRule.applySettingsExceptions {
it.isPocketEnabled = false
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openComposeTabDrawer(composeTestRule) {
verifyExistingOpenTabs("Test_Page_1")
closeTab()
verifySnackBarText("Tab closed")
clickSnackbarButton("UNDO")
}
browserScreen {
verifyTabCounter("1")
}.openComposeTabDrawer(composeTestRule) {
verifyExistingOpenTabs("Test_Page_1")
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1829838")
// Try converting in: https://bugzilla.mozilla.org/show_bug.cgi?id=1832609
@Test
fun closePrivateTabTest() {
// val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
//
// homeScreen { }.togglePrivateBrowsingMode()
// navigationToolbar {
// }.enterURLAndEnterToBrowser(genericURL.url) {
// }.openTabDrawer {
// verifyExistingOpenTabs("Test_Page_1")
// verifyCloseTabsButton("Test_Page_1")
// closeTab()
// }
// homeScreen {
// verifyTabCounter("0")
// }.openNavigationToolbar {
// }.enterURLAndEnterToBrowser(genericURL.url) {
// }.openTabDrawer {
// verifyExistingOpenTabs("Test_Page_1")
// swipeTabRight("Test_Page_1")
// }
// homeScreen {
// verifyTabCounter("0")
// }.openNavigationToolbar {
// }.enterURLAndEnterToBrowser(genericURL.url) {
// }.openTabDrawer {
// verifyExistingOpenTabs("Test_Page_1")
// swipeTabLeft("Test_Page_1")
// }
// homeScreen {
// verifyTabCounter("0")
// }
}
@Test
fun verifyPrivateTabUndoSnackBarTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen { }.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
}.openComposeTabDrawer(composeTestRule) {
verifyExistingOpenTabs("Test_Page_1")
closeTab()
TestHelper.verifySnackBarText("Private tab closed")
TestHelper.clickSnackbarButton("UNDO")
}
browserScreen {
verifyTabCounter("1")
}.openComposeTabDrawer(composeTestRule) {
verifyExistingOpenTabs("Test_Page_1")
verifyPrivateBrowsingButtonIsSelected()
}
}
@Test
fun closePrivateTabsNotificationTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.openNotification()
}
notificationShade {
verifyPrivateTabsNotification()
}.clickClosePrivateTabsNotification {
verifyHomeScreen()
}
}
@Test
fun verifyTabTrayNotShowingStateHalfExpanded() {
homeScreen {
}.openComposeTabDrawer(composeTestRule) {
verifyNoOpenTabsInNormalBrowsing()
// With no tabs opened the state should be STATE_COLLAPSED.
verifyTabsTrayBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
// Need to ensure the halfExpandedRatio is very small so that when in STATE_HALF_EXPANDED
// the tabTray will actually have a very small height (for a very short time) akin to being hidden.
verifyMinusculeHalfExpandedRatio()
}.clickTopBar {
}.waitForTabTrayBehaviorToIdle {
// Touching the topBar would normally advance the tabTray to the next state.
// We don't want that.
verifyTabsTrayBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
}.advanceToHalfExpandedState {
}.waitForTabTrayBehaviorToIdle {
// TabTray should not be displayed in STATE_HALF_EXPANDED.
// When advancing to this state it should immediately be hidden.
verifyTabTrayIsClosed()
}
}
@Test
fun verifyEmptyTabTray() {
homeScreen {
}.openComposeTabDrawer(composeTestRule) {
verifyNormalBrowsingButtonIsSelected()
verifyPrivateBrowsingButtonIsSelected(false)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInNormalBrowsing()
verifyFab()
verifyThreeDotButton()
}.openThreeDotMenu {
verifyTabSettingsButton()
verifyRecentlyClosedTabsButton()
}
}
@Test
fun verifyOpenTabDetails() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openComposeTabDrawer(composeTestRule) {
verifyNormalBrowsingButtonIsSelected()
verifyPrivateBrowsingButtonIsSelected(isSelected = false)
verifySyncedTabsButtonIsSelected(isSelected = false)
verifyThreeDotButton()
verifyNormalTabCounter()
verifyNormalTabsList()
verifyFab()
verifyTabThumbnail()
verifyExistingOpenTabs(defaultWebPage.title)
verifyTabCloseButton(defaultWebPage.title)
}.openTab(defaultWebPage.title) {
verifyUrl(defaultWebPage.url.toString())
verifyTabCounter("1")
}
}
@Test
fun verifyContextMenuShortcuts() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabButtonShortcutsMenu {
verifyTabButtonShortcutMenuItems()
}.closeTabFromShortcutsMenu {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabButtonShortcutsMenu {
}.openNewPrivateTabFromShortcutsMenu {
verifyKeyboardVisible()
verifyFocusedNavigationToolbar()
// dismiss search dialog
homeScreen { }.pressBack()
verifyCommonMythsLink()
verifyNavigationToolbar()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabButtonShortcutsMenu {
}.openTabFromShortcutsMenu {
verifyKeyboardVisible()
verifyFocusedNavigationToolbar()
// dismiss search dialog
homeScreen { }.pressBack()
verifyHomeWordmark()
verifyNavigationToolbar()
}
}
}

@ -4,6 +4,7 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
@ -15,11 +16,16 @@ 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.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.downloadRobot
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.longClickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.shareOverlay
/**
* Tests for verifying basic functionality of content context menus
@ -72,11 +78,11 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("Link 1")
longClickPageObject(itemWithText("Link 1"))
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInNewTab()
clickContextMenuItem("Open link in new tab")
verifySnackBarText("New tab opened")
snackBarButtonClick()
clickSnackbarButton("SWITCH")
verifyUrl(genericURL.url.toString())
}.openTabDrawer {
verifyNormalModeSelected()
@ -96,11 +102,11 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("Link 2")
longClickPageObject(itemWithText("Link 2"))
verifyLinkContextMenuItems(genericURL.url)
clickContextOpenLinkInPrivateTab()
clickContextMenuItem("Open link in private tab")
verifySnackBarText("New private tab opened")
snackBarButtonClick()
clickSnackbarButton("SWITCH")
verifyUrl(genericURL.url.toString())
}.openTabDrawer {
verifyPrivateModeSelected()
@ -118,9 +124,9 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("Link 3")
longClickPageObject(itemWithText("Link 3"))
verifyLinkContextMenuItems(genericURL.url)
clickContextCopyLink()
clickContextMenuItem("Copy link")
verifySnackBarText("Link copied to clipboard")
}.openNavigationToolbar {
}.visitLinkFromClipboard {
@ -135,9 +141,9 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
longClickMatchingText("Link 3")
longClickPageObject(itemWithText("Link 3"))
verifyLinkContextMenuItems(genericURL.url)
clickContextCopyLink()
clickContextMenuItem("Copy link")
verifySnackBarText("Link copied to clipboard")
}.openNavigationToolbar {
}.visitLinkFromClipboard {
@ -160,9 +166,12 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("Link 1")
longClickPageObject(itemWithText("Link 1"))
verifyLinkContextMenuItems(genericURL.url)
clickContextShareLink(genericURL.url) // verify share intent is matched with associated URL
clickContextMenuItem("Share link")
shareOverlay {
verifyShareLinkIntent(genericURL.url)
}
}
}
@ -176,11 +185,11 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("test_link_image")
longClickPageObject(itemWithText("test_link_image"))
verifyLinkImageContextMenuItems(imageResource.url)
clickContextOpenImageNewTab()
clickContextMenuItem("Open image in new tab")
verifySnackBarText("New tab opened")
snackBarButtonClick()
clickSnackbarButton("SWITCH")
verifyUrl(imageResource.url.toString())
}
}
@ -195,9 +204,9 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("test_link_image")
longClickPageObject(itemWithText("test_link_image"))
verifyLinkImageContextMenuItems(imageResource.url)
clickContextCopyImageLocation()
clickContextMenuItem("Copy image location")
verifySnackBarText("Link copied to clipboard")
}.openNavigationToolbar {
}.visitLinkFromClipboard {
@ -215,9 +224,9 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("test_link_image")
longClickPageObject(itemWithText("test_link_image"))
verifyLinkImageContextMenuItems(imageResource.url)
clickContextSaveImage()
clickContextMenuItem("Save image")
}
downloadRobot {
@ -240,58 +249,33 @@ class ContextMenusTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(pageLinks.url) {
mDevice.waitForIdle()
longClickLink("Link 1")
longClickPageObject(itemWithText("Link 1"))
verifyLinkContextMenuItems(genericURL.url)
dismissContentContextMenu(genericURL.url)
longClickLink("test_link_image")
dismissContentContextMenu()
longClickPageObject(itemWithText("test_link_image"))
verifyLinkImageContextMenuItems(imageResource.url)
dismissContentContextMenu(imageResource.url)
longClickLink("test_no_link_image")
dismissContentContextMenu()
longClickPageObject(itemWithText("test_no_link_image"))
verifyNoLinkImageContextMenuItems(imageResource.url)
}
}
@SmokeTest
@Test
fun shareSelectedTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickLink(genericURL.content)
}.clickShareSelectedText {
verifyAndroidShareLayout()
}
}
@SmokeTest
@Test
fun selectAndSearchTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickAndSearchText("Search", "content")
mDevice.waitForIdle()
verifyTabCounter("2")
verifyUrl("google")
}
}
@SmokeTest
@Test
fun privateSelectAndSearchTextTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
}.togglePrivateBrowsingMode()
fun verifyContextMixedVariationsInPDFTest() {
val genericURL =
TestAssetHelper.getGenericAsset(mockWebServer, 3)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
longClickAndSearchText("Private Search", "content")
mDevice.waitForIdle()
verifyTabCounter("2")
verifyUrl("google")
clickPageObject(itemWithText("PDF file"))
longClickPageObject(itemWithText("Wikipedia link"))
verifyLinkContextMenuItems("wikipedia.org".toUri(), false)
dismissContentContextMenu()
// Some options are missing from the linked and non liked images context menus in PDF files
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1012805 for more details
longClickPDFImage()
verifyLinkContextMenuItems("wikipedia.org".toUri())
dismissContentContextMenu()
}
}
}

@ -9,10 +9,16 @@ 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.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
@ -29,6 +35,7 @@ class ContextualHintsTest {
isTCPCFREnabled = true,
isPocketEnabled = false,
isRecentlyVisitedFeatureEnabled = false,
isCookieBannerReductionDialogEnabled = false,
)
@Before
@ -50,7 +57,7 @@ class ContextualHintsTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyCookiesProtectionHint()
verifyCookiesProtectionHintIsDisplayed(true)
// One back press to dismiss the TCP hint
mDevice.pressBack()
}.goToHomescreen {
@ -58,13 +65,29 @@ class ContextualHintsTest {
}
}
@SmokeTest
@Test
fun cookieProtectionHintTest() {
fun openTotalCookieProtectionLearnMoreLinkTest() {
val genericPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyCookiesProtectionHint()
verifyCookiesProtectionHintIsDisplayed(true)
clickPageObject(itemContainingText(getStringResource(R.string.tcp_cfr_learn_more)))
verifyUrl("support.mozilla.org/en-US/kb/enhanced-tracking-protection-firefox-android")
}
}
@SmokeTest
@Test
fun dismissTotalCookieProtectionHintTest() {
val genericPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyCookiesProtectionHintIsDisplayed(true)
clickPageObject(itemWithDescription(getStringResource(R.string.mozac_cfr_dismiss_button_content_description)))
verifyCookiesProtectionHintIsDisplayed(false)
}
}
}

@ -0,0 +1,115 @@
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.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)
// Bug causing flakiness https://bugzilla.mozilla.org/show_bug.cgi?id=1807440
@SmokeTest
@Test
fun verifyCookieBannerReductionTest() {
val webSite = "startsiden.no"
homeScreen {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webSite.toUri()) {
waitForPageToLoad()
verifyCookieBannerExists(exists = true)
}.openThreeDotMenu {
}.openSettings {
verifySettingsOptionSummary("Cookie banner reduction", "Off")
}.openCookieBannerReductionSubMenu {
verifyCookieBannerView(isCookieBannerReductionChecked = false)
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(isCookieBannerReductionChecked = true)
}.goBack {
verifySettingsOptionSummary("Cookie banner reduction", "On")
}
exitMenu()
browserScreen {
verifyCookieBannerExists(exists = false)
}
restartApp(activityTestRule)
browserScreen {
verifyCookieBannerExists(exists = false)
}.openThreeDotMenu {
}.openSettings {
}.openCookieBannerReductionSubMenu {
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(false)
}
exitMenu()
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyCookieBannerExists(exists = false)
}
}
// Bug causing flakiness https://bugzilla.mozilla.org/show_bug.cgi?id=1807440
@SmokeTest
@Test
fun verifyCookieBannerReductionInPrivateBrowsingTest() {
val webSite = "startsiden.no"
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(webSite.toUri()) {
waitForPageToLoad()
verifyCookieBannerExists(exists = true)
}.openThreeDotMenu {
}.openSettings {
verifySettingsOptionSummary("Cookie banner reduction", "Off")
}.openCookieBannerReductionSubMenu {
verifyCookieBannerView(isCookieBannerReductionChecked = false)
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(isCookieBannerReductionChecked = true)
}.goBack {
verifySettingsOptionSummary("Cookie banner reduction", "On")
}
exitMenu()
browserScreen {
verifyCookieBannerExists(exists = false)
}
restartApp(activityTestRule)
homeScreen {
}.openTabDrawer {
}.openTab("Startsiden.no") {
verifyCookieBannerExists(exists = false)
}.openThreeDotMenu {
}.openSettings {
}.openCookieBannerReductionSubMenu {
clickCookieBannerReductionToggle()
verifyCheckedCookieBannerReductionToggle(false)
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
verifyCookieBannerExists(exists = false)
}
}
}

@ -13,8 +13,11 @@ 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.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -69,7 +72,7 @@ class CrashReportingTest {
navigationToolbar {
}.openTabCrashReporter {
clickTabCrashedRestoreButton()
clickPageObject(itemWithResId("$packageName:id/restoreTabButton"))
verifyPageContent(website.content)
}
}
@ -129,7 +132,7 @@ class CrashReportingTest {
verifyExistingOpenTabs(secondWebPage.title)
}.closeTabDrawer {
}.goToHomescreen {
verifyPrivateSessionMessage()
verifyCommonMythsLink()
}.openThreeDotMenu {
verifySettingsButton()
}

@ -0,0 +1,668 @@
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.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
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.packageName
import org.mozilla.fenix.helpers.TestHelper.putAppToBackground
import org.mozilla.fenix.ui.robots.clickPageObject
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) {
clickPageObject(itemWithResId("cardNumber"))
clickPageObject(itemWithResId("$packageName:id/select_credit_card_header"))
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/credit_card_number",
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) {
clickPageObject(itemWithResId("cardNumber"))
clickPageObject(itemWithResId("$packageName:id/select_credit_card_header"))
}.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) {
clickPageObject(itemWithResId("cardNumber"))
verifySelectCreditCardPromptExists(true)
}.openThreeDotMenu {
}.openSettings {
}.openAutofillSubMenu {
clickSaveAndAutofillCreditCardsOption()
verifyCreditCardsAutofillSection(false, true)
}
exitMenu()
navigationToolbar {
}.enterURLAndEnterToBrowser(creditCardFormPage.url) {
clickPageObject(itemWithResId("cardNumber"))
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) {
clickPageObject(itemWithResId("cardNumber"))
clickPageObject(itemWithResId("$packageName:id/select_credit_card_header"))
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/credit_card_number",
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) {
clickPageObject(itemWithResId("cardNumber"))
clickPageObject(itemWithResId("$packageName:id/select_credit_card_header"))
verifyCreditCardSuggestion(
MockCreditCard1.MOCK_LAST_CARD_DIGITS,
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
)
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/credit_card_number",
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,
)
clickPageObject(itemWithResId("$packageName:id/save_cancel"))
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,
)
clickPageObject(itemWithResId("$packageName:id/save_confirm"))
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) {
clickPageObject(itemWithResId("cardNumber"))
clickPageObject(itemWithResId("$packageName:id/select_credit_card_header"))
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/credit_card_number",
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
),
)
verifyAutofilledCreditCard(MockCreditCard2.MOCK_CREDIT_CARD_NUMBER)
changeCreditCardExpiryDate(MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR)
clickCreditCardFormSubmitButton()
clickPageObject(itemWithResId("$packageName:id/save_cancel"))
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) {
clickPageObject(itemWithResId("cardNumber"))
clickPageObject(itemWithResId("$packageName:id/select_credit_card_header"))
clickPageObject(
itemWithResIdContainingText(
"$packageName:id/credit_card_number",
MockCreditCard2.MOCK_LAST_CARD_DIGITS,
),
)
verifyAutofilledCreditCard(MockCreditCard2.MOCK_CREDIT_CARD_NUMBER)
changeCreditCardExpiryDate(MockCreditCard1.MOCK_EXPIRATION_MONTH_AND_YEAR)
clickCreditCardFormSubmitButton()
clickPageObject(itemWithResId("$packageName:id/save_confirm"))
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()
}
}
}

@ -9,6 +9,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.IntentReceiverActivity
@ -16,11 +17,15 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.FeatureSettingsHelperDelegate
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.TestHelper.openAppFromExternalLink
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.customTabScreen
import org.mozilla.fenix.ui.robots.longClickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.notificationShade
import org.mozilla.fenix.ui.robots.openEditURLView
@ -38,7 +43,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 +61,6 @@ class CustomTabsTest {
dispatcher = AndroidAssetDispatcher()
start()
}
featureSettingsHelper.apply {
isTCPCFREnabled = false
}.applyFlagUpdates()
}
@After
@ -82,7 +83,7 @@ class CustomTabsTest {
customTabScreen {
waitForPageToLoad()
clickLinkMatchingText("External link")
clickPageObject(itemContainingText("External link"))
waitForPageToLoad()
verifyCustomTabToolbarTitle(externalLinkURL)
}
@ -105,7 +106,7 @@ class CustomTabsTest {
browserScreen {
verifySaveLoginPromptIsDisplayed()
saveLoginFromPrompt("Save")
clickPageObject(itemWithText("Save"))
}
openAppFromExternalLink(loginPage)
@ -117,7 +118,7 @@ class CustomTabsTest {
}.openSavedLogins {
verifySecurityPromptForLogins()
tapSetupLater()
verifySavedLoginFromPrompt("mozilla")
verifySavedLoginsSectionUsername("mozilla")
}
}
@ -168,12 +169,13 @@ class CustomTabsTest {
}
browserScreen {
longClickMatchingText("content")
longClickPageObject(itemContainingText("content"))
}.clickShareSelectedText {
verifyAndroidShareLayout()
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807289")
@SmokeTest
@Test
fun customTabDownloadTest() {

@ -123,7 +123,7 @@ class DeepLinkTest {
@Test
fun openSettingsTrackingProtection() {
robot.openSettingsTrackingProtection {
verifyEnhancedTrackingProtectionHeader()
verifyEnhancedTrackingProtectionSummary()
}
}

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

@ -5,15 +5,22 @@
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.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.TestAssetHelper
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
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.downloadRobot
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.notificationShade
@ -27,15 +34,23 @@ import org.mozilla.fenix.ui.robots.notificationShade
* - Verifies managing downloads inside the Downloads listing.
**/
class DownloadTest {
private lateinit var mockWebServer: MockWebServer
/* 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"
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
// clear all existing notifications
notificationShade {
mDevice.openNotification()
@ -48,6 +63,8 @@ class DownloadTest {
notificationShade {
cancelAllShownNotifications()
}
mockWebServer.shutdown()
}
@Test
@ -95,7 +112,7 @@ class DownloadTest {
verifyDownloadPrompt(downloadFile)
}.clickDownload {
verifyDownloadNotificationPopup()
}.closePrompt { }
}
mDevice.openNotification()
notificationShade {
verifySystemNotificationExists("Download completed")
@ -105,6 +122,11 @@ class DownloadTest {
@SmokeTest
@Test
fun pauseResumeCancelDownloadTest() {
// Clear the "Firefox Fenix default browser notification"
notificationShade {
cancelAllShownNotifications()
}
downloadFile = "1GB.zip"
navigationToolbar {
@ -184,4 +206,36 @@ class DownloadTest {
mDevice.pressBack()
}
}
@SmokeTest
@Test
fun openPDFInBrowserTest() {
val genericURL =
TestAssetHelper.getGenericAsset(mockWebServer, 3)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
clickPageObject(itemContainingText("PDF file"))
verifyPageContent("Washington Crossing the Delaware")
}
}
@SmokeTest
@Test
fun saveAndOpenPdfTest() {
val genericURL =
TestAssetHelper.getGenericAsset(mockWebServer, 3)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
clickPageObject(itemWithText("PDF file"))
}.openThreeDotMenu {
}.clickShareButton {
}.clickSaveAsPDF {
verifyDownloadPrompt(pdfFileName)
}.clickDownload {
}.clickOpen("application/pdf") {
assertExternalAppOpens("com.google.android.apps.docs")
}
}
}

@ -5,6 +5,7 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import androidx.test.espresso.Espresso.pressBack
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
@ -13,14 +14,18 @@ import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getEnhancedTrackingProtectionAsset
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.appContext
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.enhancedTrackingProtection
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.settingsSubMenuEnhancedTrackingProtection
/**
* Tests for verifying basic UI functionality of Enhanced Tracking Protection
@ -28,10 +33,10 @@ import org.mozilla.fenix.ui.robots.settingsSubMenuEnhancedTrackingProtection
* Including
* - Verifying default states
* - Verifying Enhanced Tracking Protection notification bubble
* - Verifying Enhanced Tracking Protection notification shield
* - Verifying Enhanced Tracking Protection content sheet
* - Verifying Enhanced Tracking Protection content sheet details
* - Verifying Enhanced Tracking Protection toggle
* - Verifying Enhanced Tracking Protection options and functionality
* - Verifying Enhanced Tracking Protection site exceptions
*/
@ -39,7 +44,7 @@ class EnhancedTrackingProtectionTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule(
val activityTestRule = HomeActivityIntentTestRule(
isJumpBackInCFREnabled = false,
isTCPCFREnabled = false,
isWallpaperOnboardingEnabled = false,
@ -59,25 +64,64 @@ class EnhancedTrackingProtectionTest {
}
@Test
fun testSettingsDefaults() {
fun testETPSettingsItemsAndSubMenus() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifyEnhancedTrackingProtectionButton()
verifyEnhancedTrackingProtectionState("On")
verifySettingsOptionSummary("Enhanced Tracking Protection", "Standard")
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionHeader()
verifyEnhancedTrackingProtectionOptionsEnabled()
verifyEnhancedTrackingProtectionSummary()
verifyLearnMoreText()
verifyEnhancedTrackingProtectionTextWithSwitchWidget()
verifyTrackingProtectionSwitchEnabled()
verifyEnhancedTrackingProtectionOptionsEnabled()
verifyEnhancedTrackingProtectionLevelSelected("Standard (default)", true)
verifyStandardOptionDescription()
verifyStrictOptionDescription()
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
scrollToElementByText("Standard (default)")
verifyWhatsBlockedByStandardETPInfo()
pressBack()
verifyWhatsBlockedByStrictETPInfo()
pressBack()
verifyWhatsBlockedByCustomETPInfo()
pressBack()
}.openExceptions {
verifyDefault()
verifyTPExceptionsDefaultView()
openExceptionsLearnMoreLink()
}
browserScreen {
verifyUrl("support.mozilla.org/en-US/kb/enhanced-tracking-protection-firefox-android")
}
}
@Test
fun testETPSettingsSummaryChange() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
verifyEnhancedTrackingProtectionButton()
verifySettingsOptionSummary("Enhanced Tracking Protection", "Standard")
}.openEnhancedTrackingProtectionSubMenu {
selectTrackingProtectionOption("Strict")
}.goBack {
verifySettingsOptionSummary("Enhanced Tracking Protection", "Strict")
}.openEnhancedTrackingProtectionSubMenu {
selectTrackingProtectionOption("Custom")
}.goBack {
verifySettingsOptionSummary("Enhanced Tracking Protection", "Custom")
}.openEnhancedTrackingProtectionSubMenu {
switchEnhancedTrackingProtectionToggle()
}.goBack {
verifySettingsOptionSummary("Enhanced Tracking Protection", "Off")
}
}
@SmokeTest
@Test
fun testETPOffGlobally() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val genericPage = getGenericAsset(mockWebServer, 1)
homeScreen {
}.openThreeDotMenu {
@ -107,69 +151,140 @@ class EnhancedTrackingProtectionTest {
}
}
// Tests adding ETP exceptions to websites and keeping that preference after restart
@SmokeTest
@Test
fun testStrictVisitProtectionSheet() {
appContext.settings().setStrictETP()
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
fun testDisableETPExceptionToggle() {
val firstPage = getGenericAsset(mockWebServer, 1)
val secondPage = "example.com"
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openTabDrawer {
closeTab()
}.enterURLAndEnterToBrowser(firstPage.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}.closeEnhancedTrackingProtectionSheet {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondPage.toUri()) {
verifyPageContent("Example Domain")
}
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}
restartApp(activityTestRule)
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}
}
@Test
fun testStrictVisitDisableExceptionToggle() {
appContext.settings().setStrictETP()
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
fun trackingProtectionSwitchEnabledRemovesExceptionTest() {
val trackingPage = getEnhancedTrackingProtectionAsset(mockWebServer)
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openTabDrawer {
closeTab()
}.enterURLAndEnterToBrowser(trackingPage.url) {
waitForPageToLoad()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
}.disableEnhancedTrackingProtectionFromSheet {
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}.closeEnhancedTrackingProtectionSheet {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
}.openExceptions {
verifySiteExceptionExists(trackingPage.url.host.toString(), true)
exitMenu()
}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
}.openProtectionSettings {
verifyEnhancedTrackingProtectionHeader()
verifyEnhancedTrackingProtectionOptionsEnabled()
verifyTrackingProtectionSwitchEnabled()
}.openExceptions {
verifySiteExceptionExists(trackingPage.url.host.toString(), false)
}
}
settingsSubMenuEnhancedTrackingProtection {
// Tests removing TP exceptions individually or all at once
@Test
fun clearTrackingProtectionExceptionsTest() {
val firstPage = getGenericAsset(mockWebServer, 1)
val secondPage = "example.com"
navigationToolbar {
}.enterURLAndEnterToBrowser(firstPage.url) {}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}.closeEnhancedTrackingProtectionSheet {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondPage.toUri()) {
verifyPageContent("Example Domain")
}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus("OFF", false)
}.closeEnhancedTrackingProtectionSheet {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
}.openExceptions {
verifyListedURL(trackingProtectionTest.url.host.toString())
removeOneSiteException(secondPage)
}.disableExceptions {
verifyDefault()
verifyTPExceptionsDefaultView()
exitMenu()
}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
}
}
@Test
fun testStandardETPVisitSheetDetails() {
val genericPage = getGenericAsset(mockWebServer, 1)
val trackingProtectionTest = getEnhancedTrackingProtectionAsset(mockWebServer).url
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyPageContent(genericPage.content)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest) {
verifyTrackingProtectionWebContent("social not blocked")
verifyTrackingProtectionWebContent("ads not blocked")
verifyTrackingProtectionWebContent("analytics not blocked")
verifyTrackingProtectionWebContent("Fingerprinting blocked")
verifyTrackingProtectionWebContent("Cryptomining blocked")
}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
}.openDetails {
verifyCrossSiteCookiesBlocked(true)
navigateBackToDetails()
verifyCryptominersBlocked(true)
navigateBackToDetails()
verifyFingerprintersBlocked(true)
navigateBackToDetails()
verifyTrackingContentBlocked(false)
}.closeEnhancedTrackingProtectionSheet {}
}
@Test
fun testStrictVisitSheetDetails() {
appContext.settings().setStrictETP()
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingProtectionTest =
TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
val genericPage = getGenericAsset(mockWebServer, 1)
val trackingProtectionTest = getEnhancedTrackingProtectionAsset(mockWebServer).url
// browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
@ -179,7 +294,7 @@ class EnhancedTrackingProtectionTest {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) {
}.enterURLAndEnterToBrowser(trackingProtectionTest) {
verifyTrackingProtectionWebContent("social blocked")
verifyTrackingProtectionWebContent("ads blocked")
verifyTrackingProtectionWebContent("analytics blocked")
@ -190,31 +305,31 @@ class EnhancedTrackingProtectionTest {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus("ON", true)
}.openDetails {
verifyEnhancedTrackingProtectionDetailsStatus("Blocked")
verifyTrackingCookiesBlocked()
verifyCryptominersBlocked()
verifyFingerprintersBlocked()
verifyTrackingContentBlocked()
verifySocialMediaTrackersBlocked(true)
navigateBackToDetails()
verifyCryptominersBlocked(true)
navigateBackToDetails()
verifyFingerprintersBlocked(true)
navigateBackToDetails()
verifyTrackingContentBlocked(true)
viewTrackingContentBlockList()
}
}
@SmokeTest
@Test
fun customTrackingProtectionSettingsTest() {
val genericWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val trackingPage = TestAssetHelper.getEnhancedTrackingProtectionAsset(mockWebServer)
fun defaultCustomTrackingProtectionSettingsTest() {
val genericWebPage = getGenericAsset(mockWebServer, 1)
val trackingPage = getEnhancedTrackingProtectionAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionOptionsEnabled()
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
}.goBackToHomeScreen {}
navigationToolbar {
}.goBackToHomeScreen {
}.openNavigationToolbar {
// browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) {
}.openNavigationToolbar {
@ -229,10 +344,138 @@ class EnhancedTrackingProtectionTest {
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.openDetails {
verifyTrackingCookiesBlocked()
verifyCryptominersBlocked()
verifyFingerprintersBlocked()
verifyTrackingContentBlocked()
verifyCrossSiteCookiesBlocked(true)
navigateBackToDetails()
verifyCryptominersBlocked(true)
navigateBackToDetails()
verifyFingerprintersBlocked(true)
navigateBackToDetails()
verifyTrackingContentBlocked(true)
viewTrackingContentBlockList()
}
}
// Tests the trackers blocked with the following Custom TP set up:
// - Cookies set to "All cookies"
// - Tracking content option OFF
// - Fingerprinters, cryptominers and redirect trackers checked
@Test
fun customizedTrackingProtectionOptionsTest() {
val genericWebPage = getGenericAsset(mockWebServer, 1)
val trackingPage = getEnhancedTrackingProtectionAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
selectTrackingProtectionOption("Isolate cross-site cookies")
selectTrackingProtectionOption("All cookies (will cause websites to break)")
selectTrackingProtectionOption("Tracking content")
}.goBackToHomeScreen {
mDevice.waitForIdle()
}.openNavigationToolbar {
// browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) {
waitForPageToLoad()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {
verifyTrackingProtectionWebContent("social not blocked")
verifyTrackingProtectionWebContent("ads not blocked")
verifyTrackingProtectionWebContent("analytics not blocked")
}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.openDetails {
verifyCrossSiteCookiesBlocked(true)
navigateBackToDetails()
verifyCryptominersBlocked(true)
navigateBackToDetails()
verifyFingerprintersBlocked(true)
navigateBackToDetails()
verifyTrackingContentBlocked(false)
}
}
@Test
fun disableCustomTrackingProtectionOptionsTest() {
val genericWebPage = getGenericAsset(mockWebServer, 1)
val trackingPage = getEnhancedTrackingProtectionAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
selectTrackingProtectionOption("Cookies")
selectTrackingProtectionOption("Tracking content")
selectTrackingProtectionOption("Cryptominers")
selectTrackingProtectionOption("Fingerprinters")
selectTrackingProtectionOption("Redirect Trackers")
}.goBackToHomeScreen {
mDevice.waitForIdle()
}.openNavigationToolbar {
// browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) {
waitForPageToLoad()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {
verifyTrackingProtectionWebContent("social not blocked")
verifyTrackingProtectionWebContent("ads not blocked")
verifyTrackingProtectionWebContent("analytics not blocked")
verifyTrackingProtectionWebContent("Fingerprinting not blocked")
verifyTrackingProtectionWebContent("Cryptomining not blocked")
}
}
@Test
fun testTrackingContentBlockedOnlyInPrivateTabs() {
val genericWebPage = getGenericAsset(mockWebServer, 1)
val trackingPage = getEnhancedTrackingProtectionAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
verifyEnhancedTrackingProtectionOptionsEnabled()
selectTrackingProtectionOption("Custom")
verifyCustomTrackingProtectionSettings()
selectTrackingProtectionOption("In all tabs")
selectTrackingProtectionOption("Only in Private tabs")
}.goBackToHomeScreen {
}.openNavigationToolbar {
// browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) {
waitForPageToLoad()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {
verifyTrackingProtectionWebContent("social not blocked")
verifyTrackingProtectionWebContent("ads not blocked")
verifyTrackingProtectionWebContent("analytics not blocked")
verifyTrackingProtectionWebContent("Fingerprinting blocked")
verifyTrackingProtectionWebContent("Cryptomining blocked")
}.goToHomescreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {
verifyTrackingProtectionWebContent("social blocked")
verifyTrackingProtectionWebContent("ads blocked")
verifyTrackingProtectionWebContent("analytics blocked")
verifyTrackingProtectionWebContent("Fingerprinting blocked")
verifyTrackingProtectionWebContent("Cryptomining blocked")
}
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
}.openDetails {
verifyCrossSiteCookiesBlocked(true)
navigateBackToDetails()
verifyCryptominersBlocked(true)
navigateBackToDetails()
verifyFingerprintersBlocked(true)
navigateBackToDetails()
verifyTrackingContentBlocked(true)
viewTrackingContentBlockList()
}
}
@ -241,12 +484,17 @@ class EnhancedTrackingProtectionTest {
@Test
fun blockCookiesStorageAccessTest() {
// With Standard TrackingProtection settings
val page = mockWebServer.url("pages/cross-site-cookies.html").toString().toUri()
val genericWebPage = getGenericAsset(mockWebServer, 1)
val testPage = mockWebServer.url("pages/cross-site-cookies.html").toString().toUri()
val originSite = "https://mozilla-mobile.github.io"
val currentSite = "http://localhost:${mockWebServer.port}"
navigationToolbar {
}.enterURLAndEnterToBrowser(page) {
}.enterURLAndEnterToBrowser(genericWebPage.url) {
waitForPageToLoad()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(testPage) {
waitForPageToLoad()
}.clickRequestStorageAccessButton {
verifyCrossOriginCookiesPermissionPrompt(originSite, currentSite)
}.clickPagePermissionButton(allow = false) {
@ -258,12 +506,17 @@ class EnhancedTrackingProtectionTest {
@Test
fun allowCookiesStorageAccessTest() {
// With Standard TrackingProtection settings
val page = mockWebServer.url("pages/cross-site-cookies.html").toString().toUri()
val genericWebPage = getGenericAsset(mockWebServer, 1)
val testPage = mockWebServer.url("pages/cross-site-cookies.html").toString().toUri()
val originSite = "https://mozilla-mobile.github.io"
val currentSite = "http://localhost:${mockWebServer.port}"
navigationToolbar {
}.enterURLAndEnterToBrowser(page) {
}.enterURLAndEnterToBrowser(genericWebPage.url) {
waitForPageToLoad()
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(testPage) {
waitForPageToLoad()
}.clickRequestStorageAccessButton {
verifyCrossOriginCookiesPermissionPrompt(originSite, currentSite)
}.clickPagePermissionButton(allow = true) {

@ -13,6 +13,7 @@ import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
@ -216,6 +217,7 @@ class HistoryTest {
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun openHistoryInNewTabTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -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,65 +93,63 @@ 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")
}
}
@Test
fun verifyJumpBackInSectionTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentlyVisitedFeatureEnabled = false
it.isPocketEnabled = false
}
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 4)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
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 {
@ -176,12 +171,13 @@ class HomeScreenTest {
}.openThreeDotMenu {
}.openCustomizeHome {
clickPocketButton()
}.goBack {
}.goBackToHomeScreen {
verifyThoughtProvokingStories(false)
verifyStoriesByTopic(false)
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1821016")
@Test
fun openPocketStoryItemTest() {
activityTestRule.activityRule.applySettingsExceptions {
@ -267,12 +263,12 @@ class HomeScreenTest {
clickRecentBookmarksButton()
clickRecentSearchesButton()
clickPocketButton()
}.goBack {
}.goBackToHomeScreen {
verifyCustomizeHomepageButton(false)
}.openThreeDotMenu {
}.openCustomizeHome {
clickJumpBackInButton()
}.goBack {
}.goBackToHomeScreen {
verifyCustomizeHomepageButton(true)
}
}

@ -0,0 +1,792 @@
/* 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.MatcherHelper
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
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.packageName
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.clearTextFieldItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.setPageObjectText
/**
* 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()
verifySaveLoginPromptIsDisplayed()
// Click save to save the login
clickPageObject(itemWithText("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()) {
setPageObjectText(itemWithResId("username"), userName)
setPageObjectText(itemWithResId("password"), password)
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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
clickPageObject(itemWithText("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()
verifySaveLoginPromptIsDisplayed()
// Click Save to save the login
clickPageObject(itemWithText("Save"))
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
enterPassword("test")
mDevice.waitForIdle()
clickSubmitLoginButton()
verifySaveLoginPromptIsDisplayed()
// Click Update to change the saved password
clickPageObject(itemWithText("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
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1816066")
@SmokeTest
@Test
fun verifyMultipleLoginsSelectionsTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/v2.0/loginForm.html"
val firstUser = "mozilla"
val firstPass = "firefox"
val secondUser = "fenix"
val secondPass = "pass"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
setPageObjectText(itemWithResId("username"), firstUser)
setPageObjectText(itemWithResId("password"), firstPass)
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
setPageObjectText(itemWithResId("username"), secondUser)
setPageObjectText(itemWithResId("password"), secondPass)
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
clearTextFieldItem(itemWithResId("username"))
clickSuggestedLoginsButton()
verifySuggestedUserName(firstUser)
verifySuggestedUserName(secondUser)
clickPageObject(
itemWithResIdAndText(
"$packageName:id/username",
firstUser,
),
)
clickPageObject(itemWithResId("togglePassword"))
verifyPrefilledLoginCredentials(firstUser, firstPass, true)
}
}
@Test
fun verifyEditLoginsViewTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
val originWebsite = "mozilla-mobile.github.io"
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
clickThreeDotButton(activityTestRule)
clickEditLoginButton()
setNewUserName("android")
setNewPassword("fenix")
saveEditedLogin()
}
exitMenu()
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
waitForPageToLoad()
clickPageObject(itemWithResId("togglePassword"))
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()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()
clickPageObject(itemWithText("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()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openTabDrawer {
closeTab()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
clickPageObject(itemWithResId("togglePassword"))
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "fenix")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Dont update"))
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
viewSavedLoginDetails(originWebsite)
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Test
fun searchLoginsByUsernameTest() {
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()
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
setPageObjectText(itemWithResId("username"), "android")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
clickSearchLoginButton()
searchLogin("ANDROID")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("android")
revealPassword()
verifyPasswordSaved("firefox")
}.goBackToSavedLogins {
clickSearchLoginButton()
searchLogin("android")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("android")
revealPassword()
verifyPasswordSaved("firefox")
}.goBackToSavedLogins {
clickSearchLoginButton()
searchLogin("AnDrOiD")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("android")
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Test
fun searchLoginsByUrlTest() {
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()
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
setPageObjectText(itemWithResId("username"), "android")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openThreeDotMenu {
}.openSettings {
}.openLoginsAndPasswordSubMenu {
}.openSavedLogins {
tapSetupLater()
clickSearchLoginButton()
searchLogin("MOZILLA")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("android")
revealPassword()
verifyPasswordSaved("firefox")
}.goBackToSavedLogins {
clickSearchLoginButton()
searchLogin("mozilla")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("android")
revealPassword()
verifyPasswordSaved("firefox")
}.goBackToSavedLogins {
clickSearchLoginButton()
searchLogin("MoZiLlA")
viewSavedLoginDetails(originWebsite)
verifyLoginItemUsername("android")
revealPassword()
verifyPasswordSaved("firefox")
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1815650")
@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()
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondLoginPage.toUri()) {
setPageObjectText(itemWithResId("username"), "mozilla")
setPageObjectText(itemWithResId("password"), "firefox")
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("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()) {
clickPageObject(MatcherHelper.itemWithResId("username"))
clickSuggestedLoginsButton()
verifySuggestedUserName("mozilla")
clickPageObject(itemWithResIdAndText("$packageName:id/username", "mozilla"))
clickPageObject(itemWithResId("togglePassword"))
verifyPrefilledLoginCredentials("mozilla", "firefox", true)
}
}
}

@ -0,0 +1,244 @@
package org.mozilla.fenix.ui
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import mozilla.components.concept.engine.utils.EngineReleaseChannel
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.components
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.runWithCondition
import org.mozilla.fenix.ui.robots.navigationToolbar
class MainMenuTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun verifyPageMainMenuItemsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad()
}.openThreeDotMenu {
verifyPageThreeDotMainMenuItems(isRequestDesktopSiteEnabled = false)
}
}
@SmokeTest
@Test
fun openMainMenuNewTabItemTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.clickNewTabButton {
verifySearchView()
}
}
@SmokeTest
@Test
fun openMainMenuBookmarksItemTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarksMenuView()
}
}
@SmokeTest
@Test
fun openMainMenuHistoryItemTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openHistory {
verifyHistoryListExists()
}
}
@SmokeTest
@Test
fun openMainMenuAddonsTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openAddonsManagerMenu {
TestHelper.registerAndCleanupIdlingResources(
RecyclerViewIdlingResource(
activityTestRule.activity.findViewById(R.id.add_ons_list),
1,
),
) {
verifyAddonsItems()
}
}
}
@SmokeTest
@Test
fun openMainMenuSyncItemTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
}.openSyncSignIn {
verifyTurnOnSyncMenu()
}
}
@SmokeTest
@Test
fun openMainMenuFindInPageTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openFindInPage {
verifyFindInPageSearchBarItems()
}
}
@SmokeTest
@Test
fun mainMenuDesktopSiteTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.switchDesktopSiteMode {
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(true)
}
}
@SmokeTest
@Test
fun mainMenuReportSiteIssueTest() {
runWithCondition(
// This test will not run on RC builds because the "Report site issue button" is not available.
activityTestRule.activity.components.core.engine.version.releaseChannel !== EngineReleaseChannel.RELEASE,
) {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openReportSiteIssue {
verifyUrl("webcompat.com/issues/new")
}
}
}
@SmokeTest
@Test
fun openMainMenuAddToCollectionTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openSaveToCollection {
verifyCollectionNameTextField()
}
}
// Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds
@SmokeTest
@Test
fun openMainMenuSettingsItemTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.openSettings {
verifySettingsView()
}
}
@SmokeTest
@Test
fun mainMenuShareButtonTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
}.clickShareButton {
verifyShareTabLayout()
}
}
@SmokeTest
@Test
fun mainMenuRefreshButtonTest() {
val refreshWebPage = TestAssetHelper.getRefreshAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(refreshWebPage.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
verifyThreeDotMenuExists()
}.refreshPage {
verifyPageContent("REFRESHED")
}
}
@SmokeTest
@Test
fun mainMenuForceRefreshTest() {
val refreshWebPage = TestAssetHelper.getRefreshAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(refreshWebPage.url) {
mDevice.waitForIdle()
}.openThreeDotMenu {
verifyThreeDotMenuExists()
}.forceRefreshPage {
verifyPageContent("REFRESHED")
}
}
}

@ -13,12 +13,15 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.notificationShade
@ -61,6 +64,7 @@ class MediaNotificationTest {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun videoPlaybackSystemNotificationTest() {
val videoTestPage = TestAssetHelper.getVideoPageAsset(mockWebServer)
@ -68,7 +72,7 @@ class MediaNotificationTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(videoTestPage.url) {
mDevice.waitForIdle()
clickMediaPlayerPlayButton()
clickPageObject(itemWithText("Play"))
assertPlaybackState(browserStore, MediaSession.PlaybackState.PLAYING)
}.openNotificationShade {
verifySystemNotificationExists(videoTestPage.title)
@ -94,6 +98,40 @@ class MediaNotificationTest {
mDevice.pressBack()
}
@SmokeTest
@Test
fun audioPlaybackSystemNotificationTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(audioTestPage.url) {
mDevice.waitForIdle()
clickPageObject(itemWithText("Play"))
assertPlaybackState(browserStore, MediaSession.PlaybackState.PLAYING)
}.openNotificationShade {
verifySystemNotificationExists(audioTestPage.title)
clickMediaNotificationControlButton("Pause")
verifyMediaSystemNotificationButtonState("Play")
}
mDevice.pressBack()
browserScreen {
assertPlaybackState(browserStore, MediaSession.PlaybackState.PAUSED)
}.openTabDrawer {
closeTab()
}
mDevice.openNotification()
notificationShade {
verifySystemNotificationGone(audioTestPage.title)
}
// close notification shade before the next test
mDevice.pressBack()
}
@Test
fun mediaSystemNotificationInPrivateModeTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)
@ -104,7 +142,7 @@ class MediaNotificationTest {
}.openNewTab {
}.submitQuery(audioTestPage.url.toString()) {
mDevice.waitForIdle()
clickMediaPlayerPlayButton()
clickPageObject(itemWithText("Play"))
assertPlaybackState(browserStore, MediaSession.PlaybackState.PLAYING)
}.openNotificationShade {
verifySystemNotificationExists("A site is playing media")

@ -15,8 +15,10 @@ import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.runWithSystemLocaleChanged
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import java.util.Locale
@ -174,9 +176,47 @@ 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() {
val genericURL =
TestAssetHelper.getGenericAsset(mockWebServer, 3)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericURL.url) {
clickPageObject(itemWithText("PDF file"))
}.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("p")
verifyFindNextInPageResult("1/1")
}.closeFindInPageWithBackButton {
verifyFindInPageBar(false)
}
}
@Test
@ -199,6 +239,7 @@ class NavigationToolbarTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad()
}.openSiteSecuritySheet {
verifyQuickActionSheet(defaultWebPage.url.toString(), false)
openSecureConnectionSubMenu(false)

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

Loading…
Cancel
Save