Merge remote-tracking branch 'origin/fenix/124.0' into iceraven

iceraven
akliuxingyuan 2 months ago
commit 09e75c1a78

@ -676,8 +676,8 @@ dependencies {
implementation ComponentsDependencies.androidx_paging
implementation ComponentsDependencies.androidx_preferences
implementation ComponentsDependencies.androidx_fragment
implementation FenixDependencies.androidx_navigation_fragment
implementation FenixDependencies.androidx_navigation_ui
implementation ComponentsDependencies.androidx_navigation_fragment
implementation ComponentsDependencies.androidx_navigation_ui
implementation ComponentsDependencies.androidx_compose_navigation
implementation ComponentsDependencies.androidx_recyclerview

@ -1101,6 +1101,16 @@
column="1"/>
</issue>
<issue id="UnusedResources"
message="The resource R.drawable.ic_onboarding_key_features appears to be unused">
<location file="src/main/res/drawable/ic_onboarding_key_features.xml" />
</issue>
<issue id="UnusedResources"
message="The resource R.drawable.ic_onboarding_key_features_icons_only appears to be unused">
<location file="src/main/res/drawable/ic_onboarding_key_features_icons_only.xml" />
</issue>
<issue
id="IconXmlAndPng"
message="The following images appear both as density independent `.xml` files and as bitmap files: /Users/oracle/Projects/fenix/app/src/main/res/drawable-hdpi/ic_logo_wordmark_normal.png, /Users/oracle/Projects/fenix/app/src/main/res/drawable-night/ic_logo_wordmark_normal.xml">

@ -25,6 +25,7 @@ import:
- value:
messages:
default-browser:
title: default_browser_experiment_card_title
text: default_browser_experiment_card_text
surface: homescreen
action: "MAKE_DEFAULT_BROWSER"

@ -843,6 +843,17 @@ events:
metadata:
tags:
- Search
first_session_ping_cancelled:
type: event
description: |
First session ping cancelled because Adjust metrics were empty.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1875514
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/5223
notification_emails:
- android-probes@mozilla.com
expires: never
splash_screen:
first_launch_extended:
@ -1061,78 +1072,6 @@ onboarding:
metadata:
tags:
- Onboarding
notif_ppp_impression:
type: event
description: |
Notification pre permission prompt was shown to the user.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1810115
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28529
- https://github.com/mozilla-mobile/firefox-android/pull/4039
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Notifications
- Onboarding
notif_ppp_close_click:
type: event
description: |
User clicked the close button on the notification pre permission prompt.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1810115
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28529
- https://github.com/mozilla-mobile/firefox-android/pull/4039
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Notifications
- Onboarding
notif_ppp_positive_btn_click:
type: event
description: |
User clicked the positive button on notification pre permission prompt.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1810115
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28529
- https://github.com/mozilla-mobile/firefox-android/pull/4039
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Notifications
- Onboarding
notif_ppp_negative_btn_click:
type: event
description: |
User clicked the negative button on notification pre permission prompt.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1810115
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/28529
- https://github.com/mozilla-mobile/firefox-android/pull/4039
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never
metadata:
tags:
- Notifications
- Onboarding
set_to_default_card:
type: event
description: |
@ -9265,8 +9204,10 @@ awesomebar:
A sponsored suggestion was visible when the user finished interacting with the awesomebar.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1871156
- https://bugzilla.mozilla.org/show_bug.cgi?id=1878434
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4914#issuecomment-1874271848
- https://github.com/mozilla-mobile/firefox-android/pull/5438#issuecomment-1930970336
data_sensitivity:
- interaction
notification_emails:
@ -9277,12 +9218,6 @@ awesomebar:
expires: never
extra_keys:
provider: *sponsored_suggestion_provider
engagement_abandoned: &awesomebar_engagement_abandoned
description: |
If `true`, the user dismissed the awesomebar without navigating to a destination. If
`false`, the user finished engaging with the awesomebar by navigating to a destination,
like a URL, a search results page, or a suggestion.
type: boolean
metadata:
tags:
- Search
@ -9292,8 +9227,10 @@ awesomebar:
A non-sponsored suggestion was visible when the user finished interacting with the awesomebar.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1871156
- https://bugzilla.mozilla.org/show_bug.cgi?id=1878434
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/4914#issuecomment-1874271848
- https://github.com/mozilla-mobile/firefox-android/pull/5438#issuecomment-1930970336
data_sensitivity:
- interaction
notification_emails:
@ -9304,10 +9241,49 @@ awesomebar:
expires: never
extra_keys:
provider: *non_sponsored_suggestion_provider
engagement_abandoned: *awesomebar_engagement_abandoned
metadata:
tags:
- Search
engagement:
type: event
description: |
The user completed their search session by tapping a search result,
or entering a URL or a search term.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1878434
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/5438#issuecomment-1930970336
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- lina@mozilla.com
- ttran@mozilla.com
- najiang@mozilla.com
expires: never
metadata:
tags:
- Search
abandonment:
type: event
description: |
The user dismissed the awesomebar without completing their search.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1878434
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/5438#issuecomment-1930970336
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- lina@mozilla.com
- ttran@mozilla.com
- najiang@mozilla.com
expires: never
metadata:
tags:
- Search
android_autofill:
supported:
type: boolean
@ -11414,3 +11390,19 @@ fx_suggest:
expires: never
send_in_pings:
- fx-suggest
debug_drawer:
debug_drawer_enabled:
type: boolean
description: |
Whether or not the user has enabled the Debug Drawer feature.
send_in_pings:
- metrics
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1876596
data_reviews:
- https://github.com/mozilla-mobile/firefox-android/pull/5356
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: never

@ -32,6 +32,7 @@ import:
- value:
available-suggestion-types: {
"amp": true,
"ampMobile": false,
"wikipedia": true,
}
@ -238,14 +239,6 @@ features:
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."

@ -14,11 +14,11 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
import android.os.storage.StorageVolume
import android.provider.Settings
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.espresso.Espresso
import androidx.test.espresso.IdlingRegistry
@ -33,6 +33,7 @@ import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import junit.framework.AssertionFailedError
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.mozilla.fenix.Config
@ -60,46 +61,88 @@ object AppAndSystemHelper {
}
}
@RequiresApi(Build.VERSION_CODES.R)
/**
* Checks if a specific download file is inside the device storage and deletes it.
* Different implementation needed for newer API levels,
* as Environment.getExternalStorageDirectory() is deprecated starting with API 29.
*
*/
fun deleteDownloadedFileOnStorage(fileName: String) {
val storageManager: StorageManager? = TestHelper.appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager?
val storageVolumes = storageManager!!.storageVolumes
val storageVolume: StorageVolume = storageVolumes[0]
val file = File(storageVolume.directory!!.path + "/Download/" + fileName)
try {
if (file.exists()) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
val storageManager: StorageManager? =
TestHelper.appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager?
val storageVolumes = storageManager!!.storageVolumes
val storageVolume: StorageVolume = storageVolumes[0]
val file = File(storageVolume.directory!!.path + "/Download/" + fileName)
try {
if (file.exists()) {
file.delete()
Log.d("TestLog", "File delete try 1")
Assert.assertFalse("The file was not deleted", file.exists())
}
} catch (e: AssertionError) {
file.delete()
Log.d("TestLog", "File delete try 1")
Log.d("TestLog", "File delete retried")
Assert.assertFalse("The file was not deleted", file.exists())
}
} catch (e: AssertionError) {
file.delete()
Log.d("TestLog", "File delete retried")
Assert.assertFalse("The file was not deleted", file.exists())
} else {
runBlocking {
val downloadedFile = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
fileName,
)
if (downloadedFile.exists()) {
Log.i(TAG, "deleteDownloadedFileOnStorage: Verifying if $downloadedFile exists.")
downloadedFile.delete()
Log.i(TAG, "deleteDownloadedFileOnStorage: $downloadedFile deleted.")
}
}
}
}
@RequiresApi(Build.VERSION_CODES.R)
/**
* Checks if there are download files inside the device storage and deletes all of them.
* Different implementation needed for newer API levels, as
* Environment.getExternalStorageDirectory() is deprecated starting with API 29.
*/
fun clearDownloadsFolder() {
val storageManager: StorageManager? = TestHelper.appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager?
val storageVolumes = storageManager!!.storageVolumes
val storageVolume: StorageVolume = storageVolumes[0]
val downloadsFolder = File(storageVolume.directory!!.path + "/Download/")
// Check if the downloads folder exists
if (downloadsFolder.exists() && downloadsFolder.isDirectory) {
Log.i(TAG, "clearDownloadsFolder: Verified that \"DOWNLOADS\" folder exists")
val files = downloadsFolder.listFiles()
// Check if the folder is not empty
if (files != null && files.isNotEmpty()) {
Log.i(TAG, "clearDownloadsFolder: Verified that \"DOWNLOADS\" folder is not empty")
// Delete all files in the folder
for (file in files) {
file.delete()
Log.i(TAG, "clearDownloadsFolder: Deleted $file from \"DOWNLOADS\" folder")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
Log.i(TAG, "clearDownloadsFolder: API > 29")
val storageManager: StorageManager? =
TestHelper.appContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager?
val storageVolumes = storageManager!!.storageVolumes
val storageVolume: StorageVolume = storageVolumes[0]
val downloadsFolder = File(storageVolume.directory!!.path + "/Download/")
// Check if the downloads folder exists
if (downloadsFolder.exists() && downloadsFolder.isDirectory) {
Log.i(TAG, "clearDownloadsFolder: Verified that \"DOWNLOADS\" folder exists")
val files = downloadsFolder.listFiles()
// Check if the folder is not empty
if (files != null && files.isNotEmpty()) {
Log.i(
TAG,
"clearDownloadsFolder: Verified that \"DOWNLOADS\" folder is not empty",
)
// Delete all files in the folder
for (file in files) {
file.delete()
Log.i(TAG, "clearDownloadsFolder: Deleted $file from \"DOWNLOADS\" folder")
}
}
}
} else {
runBlocking {
Log.i(TAG, "clearDownloadsFolder: API <= 29")
Log.i(TAG, "clearDownloadsFolder: Verifying if any download files exist.")
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.listFiles()?.forEach {
it.delete()
Log.i(TAG, "clearDownloadsFolder: Download file $it deleted.")
}
}
}
}
@ -135,11 +178,12 @@ object AppAndSystemHelper {
}
fun isPackageInstalled(packageName: String): Boolean {
Log.i(TAG, "isPackageInstalled: Trying to verify that $packageName is installed")
return try {
val packageManager = InstrumentationRegistry.getInstrumentation().context.packageManager
packageManager.getApplicationInfo(packageName, 0).enabled
} catch (e: PackageManager.NameNotFoundException) {
Log.i(TAG, "isPackageInstalled: Catch block - ${e.message}")
Log.i(TAG, "isPackageInstalled: $packageName is not installed - ${e.message}")
false
}
}
@ -183,6 +227,7 @@ object AppAndSystemHelper {
* @return Boolean value that helps us know if the current activity supports custom tabs or PWAs.
*/
fun isExternalAppBrowserActivityInCurrentTask(): Boolean {
Log.i(TAG, "Trying to verify that the latest activity of the application is used for custom tabs or PWAs")
val activityManager = TestHelper.appContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
mDevice.waitForIdle(TestAssetHelper.waitingTimeShort)

@ -4,7 +4,7 @@
package org.mozilla.fenix.helpers
import org.mozilla.experiments.nimbus.GleanPlumbMessageHelper
import org.mozilla.experiments.nimbus.NimbusMessagingHelperInterface
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.TestHelper.appContext
@ -12,7 +12,7 @@ object Experimentation {
val experiments =
appContext.components.analytics.experiments
fun withHelper(block: GleanPlumbMessageHelper.() -> Unit) {
fun withHelper(block: NimbusMessagingHelperInterface.() -> Unit) {
val helper = experiments.createMessageHelper()
block(helper)
}

@ -17,6 +17,7 @@ object TestAssetHelper {
val waitingTime: Long = TimeUnit.SECONDS.toMillis(15)
val waitingTimeLong = TimeUnit.SECONDS.toMillis(25)
val waitingTimeShort: Long = TimeUnit.SECONDS.toMillis(3)
val waitingTimeVeryShort: Long = TimeUnit.SECONDS.toMillis(1)
data class TestAsset(val url: Uri, val content: String, val title: String)

@ -34,9 +34,12 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
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.TestAssetHelper.waitingTimeVeryShort
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.ui.robots.clickPageObject
@ -49,8 +52,10 @@ object TestHelper {
fun scrollToElementByText(text: String): UiScrollable {
val appView = UiScrollable(UiSelector().scrollable(true))
Log.i(TAG, "scrollToElementByText: Waiting for app view")
appView.waitForExists(waitingTime)
appView.scrollTextIntoView(text)
Log.i(TAG, "scrollToElementByText: Scrolled to element with text: $text")
return appView
}
@ -101,14 +106,7 @@ object TestHelper {
).waitUntilGone(waitingTime)
}
fun verifySnackBarText(expectedText: String) {
assertTrue(
mDevice.findObject(
UiSelector()
.textContains(expectedText),
).waitForExists(waitingTime),
)
}
fun verifySnackBarText(expectedText: String) = assertUIObjectExists(itemContainingText(expectedText))
fun verifyUrl(urlSubstring: String, resourceName: String, resId: Int) {
waitUntilObjectIsFound(resourceName)
@ -145,4 +143,10 @@ object TestHelper {
assertFalse("Light theme not selected", expected)
fun verifyDarkThemeApplied(expected: Boolean) = assertTrue("Dark theme not selected", expected)
fun waitForAppWindowToBeUpdated() {
Log.i(TAG, "waitForAppWindowToBeUpdated: Waiting for $waitingTimeVeryShort ms for $packageName window to be updated")
mDevice.waitForWindowUpdate(packageName, waitingTimeVeryShort)
Log.i(TAG, "waitForAppWindowToBeUpdated: Waited for $waitingTimeVeryShort ms for $packageName window to be updated")
}
}

@ -0,0 +1,52 @@
package org.mozilla.fenix.helpers
import android.util.Log
import kotlinx.coroutines.runBlocking
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.storage.sync.PlacesBookmarksStorage
import okhttp3.mockwebserver.MockWebServer
import org.junit.Before
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.ui.robots.notificationShade
open class TestSetup {
lateinit var mockWebServer: MockWebServer
private val bookmarksStorage = PlacesBookmarksStorage(appContext.applicationContext)
@Before
fun setUp() {
Log.i(TAG, "TestSetup: Starting the @Before setup")
// Clear pre-existing notifications
notificationShade {
cancelAllShownNotifications()
}
runBlocking {
// Reset locale to EN-US if needed.
AppAndSystemHelper.resetSystemLocaleToEnUS()
// Check and clear the downloads folder
AppAndSystemHelper.clearDownloadsFolder()
// Make sure the Wifi and Mobile Data connections are on
AppAndSystemHelper.setNetworkEnabled(true)
// Clear bookmarks left after a failed test
val bookmarks = bookmarksStorage.getTree(BookmarkRoot.Mobile.id)?.children
Log.i(TAG, "Before cleanup: Bookmarks storage contains: $bookmarks")
bookmarks?.forEach {
bookmarksStorage.deleteNode(it.guid)
// TODO: Follow-up with a method to handle the DB update; the logs will still show the bookmarks in the storage before the test starts.
Log.i(TAG, "After cleanup: Bookmarks storage contains: $bookmarks")
}
}
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
}
try {
Log.i(TAG, "Try starting mockWebServer")
mockWebServer.start()
} catch (e: Exception) {
Log.i(TAG, "Exception caught. Re-starting mockWebServer")
mockWebServer.shutdown()
mockWebServer.start()
}
}
}

@ -95,7 +95,7 @@
"sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956",
"sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"
],
"markers": "python_version >= '3.8'",
"markers": "platform_python_implementation != 'PyPy'",
"version": "==1.16.0"
},
"charset-normalizer": {
@ -196,33 +196,42 @@
},
"cryptography": {
"hashes": [
"sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596",
"sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c",
"sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660",
"sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4",
"sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead",
"sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed",
"sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3",
"sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7",
"sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09",
"sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c",
"sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43",
"sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65",
"sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6",
"sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da",
"sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c",
"sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b",
"sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8",
"sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c",
"sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d",
"sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9",
"sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86",
"sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36",
"sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"
"sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b",
"sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd",
"sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94",
"sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221",
"sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e",
"sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513",
"sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d",
"sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc",
"sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0",
"sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2",
"sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87",
"sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01",
"sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0",
"sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4",
"sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b",
"sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81",
"sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3",
"sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4",
"sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf",
"sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec",
"sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce",
"sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0",
"sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f",
"sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f",
"sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3",
"sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689",
"sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08",
"sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139",
"sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434",
"sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17",
"sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8",
"sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==41.0.6"
"version": "==42.0.0"
},
"distro": {
"hashes": [

@ -4,27 +4,22 @@
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.exitMenu
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.autofillScreen
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
class AddressAutofillTest : TestSetup() {
object FirstAddressAutofillDetails {
var navigateToAutofillSettings = true
var isAddressAutofillEnabled = true
@ -58,19 +53,6 @@ class AddressAutofillTest {
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1836845
@SmokeTest
@Test

@ -8,20 +8,13 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.Espresso.pressBack
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import kotlinx.coroutines.runBlocking
import mozilla.appservices.places.BookmarkRoot
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createBookmarkItem
@ -31,7 +24,10 @@ import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.bookmarksMenu
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
@ -41,9 +37,7 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying basic functionality of bookmarks
*/
class BookmarksTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var mDevice: UiDevice
class BookmarksTest : TestSetup() {
private val bookmarksFolderName = "New Folder"
private val testBookmark = object {
var title: String = "Bookmark title"
@ -60,26 +54,6 @@ class BookmarksTest {
@JvmField
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
mDevice = UiDevice.getInstance(getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
// Clearing all bookmarks data after each test to avoid overlapping data
val bookmarksStorage = activityTestRule.activity?.bookmarkStorage
runBlocking {
val bookmarks = bookmarksStorage?.getTree(BookmarkRoot.Mobile.id)?.children
bookmarks?.forEach { bookmarksStorage.deleteNode(it.guid) }
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/522919
@Test
fun verifyEmptyBookmarksMenuTest() {
@ -137,7 +111,7 @@ class BookmarksTest {
clickAddFolderButton()
addNewFolderName(bookmarksFolderName)
navigateUp()
verifyKeyboardHidden()
verifyKeyboardHidden(isExpectedToBeVisible = false)
verifyBookmarkFolderIsNotCreated(bookmarksFolderName)
}
}
@ -209,7 +183,7 @@ class BookmarksTest {
) {}
}.openThreeDotMenu(defaultWebPage.title) {
}.clickCopy {
verifyCopySnackBarText()
verifySnackBarText(expectedText = "URL copied")
navigateUp()
}
@ -496,7 +470,7 @@ class BookmarksTest {
}
bookmarksMenu {
verifyDeleteMultipleBookmarksSnackBar()
verifySnackBarText(expectedText = "Bookmarks deleted")
clickUndoDeleteButton()
verifyBookmarkedURL(firstWebPage.url.toString())
verifyBookmarkedURL(secondWebPage.url.toString())
@ -514,7 +488,7 @@ class BookmarksTest {
}
bookmarksMenu {
verifyDeleteMultipleBookmarksSnackBar()
verifySnackBarText(expectedText = "Bookmarks deleted")
}
}
@ -602,7 +576,7 @@ class BookmarksTest {
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list)),
) {
longTapDesktopFolder("Desktop Bookmarks")
verifySelectDefaultFolderSnackBarText()
verifySnackBarText(expectedText = "Cant edit default folders")
}
}
}
@ -625,7 +599,7 @@ class BookmarksTest {
cancelDeletion()
clickDeleteInEditModeButton()
confirmDeletion()
verifyDeleteSnackBarText()
verifySnackBarText(expectedText = "Deleted")
verifyBookmarkIsDeleted("Test_Page_1")
}
}
@ -786,13 +760,13 @@ class BookmarksTest {
}.openThreeDotMenu("My Folder") {
}.clickDelete {
confirmDeletion()
verifyDeleteSnackBarText()
verifySnackBarText(expectedText = "Deleted")
clickUndoDeleteButton()
verifyFolderTitle("My Folder")
}.openThreeDotMenu("My Folder") {
}.clickDelete {
confirmDeletion()
verifyDeleteSnackBarText()
verifySnackBarText(expectedText = "Deleted")
verifyBookmarkIsDeleted("My Folder")
verifyBookmarkIsDeleted("My Folder 2")
verifyBookmarkIsDeleted("Test_Page_1")

@ -5,20 +5,17 @@
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.AppAndSystemHelper.setNetworkEnabled
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
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.TestSetup
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -26,13 +23,12 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests that verify errors encountered while browsing websites: unsafe pages, connection errors, etc
*/
class BrowsingErrorPagesTest {
class BrowsingErrorPagesTest : TestSetup() {
private val malwareWarning = getStringResource(R.string.mozac_browser_errorpages_safe_browsing_malware_uri_title)
private val phishingWarning = getStringResource(R.string.mozac_browser_errorpages_safe_phishing_uri_title)
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()
@ -41,21 +37,6 @@ 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()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2326774
@SmokeTest
@Test

@ -5,19 +5,16 @@
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.collectionRobot
import org.mozilla.fenix.ui.robots.homeScreen
@ -29,9 +26,7 @@ import org.mozilla.fenix.ui.robots.tabDrawer
*
*/
class CollectionTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
class CollectionTest : TestSetup() {
private val firstCollectionName = "testcollection_1"
private val secondCollectionName = "testcollection_2"
private val collectionName = "First Collection"
@ -50,20 +45,6 @@ class CollectionTest {
),
) { it.activity }
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/353823
@SmokeTest
@Test
@ -490,7 +471,7 @@ class CollectionTest {
selectTab(secondWebPage.title, numOfTabs = 2)
}.clickSaveCollection {
typeCollectionNameAndSave(collectionName)
verifySnackBarText("Tabs saved!")
verifySnackBarText("Collection saved!")
}
tabDrawer {

@ -28,6 +28,7 @@ import org.mozilla.fenix.helpers.TestAssetHelper
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.verifySnackBarText
import org.mozilla.fenix.ui.robots.bookmarksMenu
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
@ -111,7 +112,7 @@ class ComposeBookmarksTest {
clickAddFolderButton()
addNewFolderName(bookmarksFolderName)
navigateUp()
verifyKeyboardHidden()
verifyKeyboardHidden(isExpectedToBeVisible = false)
verifyBookmarkFolderIsNotCreated(bookmarksFolderName)
}
}
@ -183,7 +184,7 @@ class ComposeBookmarksTest {
) {}
}.openThreeDotMenu(defaultWebPage.title) {
}.clickCopy {
verifyCopySnackBarText()
verifySnackBarText(expectedText = "URL copied")
navigateUp()
}
@ -469,7 +470,7 @@ class ComposeBookmarksTest {
}
bookmarksMenu {
verifyDeleteMultipleBookmarksSnackBar()
verifySnackBarText(expectedText = "Bookmarks deleted")
clickUndoDeleteButton()
verifyBookmarkedURL(firstWebPage.url.toString())
verifyBookmarkedURL(secondWebPage.url.toString())
@ -487,7 +488,7 @@ class ComposeBookmarksTest {
}
bookmarksMenu {
verifyDeleteMultipleBookmarksSnackBar()
verifySnackBarText(expectedText = "Bookmarks deleted")
}
}
@ -575,7 +576,7 @@ class ComposeBookmarksTest {
RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list)),
) {
longTapDesktopFolder("Desktop Bookmarks")
verifySelectDefaultFolderSnackBarText()
verifySnackBarText(expectedText = "Cant edit default folders")
}
}
}
@ -598,7 +599,7 @@ class ComposeBookmarksTest {
cancelDeletion()
clickDeleteInEditModeButton()
confirmDeletion()
verifyDeleteSnackBarText()
verifySnackBarText(expectedText = "Deleted")
verifyBookmarkIsDeleted("Test_Page_1")
}
}
@ -764,13 +765,13 @@ class ComposeBookmarksTest {
}.openThreeDotMenu("My Folder") {
}.clickDelete {
confirmDeletion()
verifyDeleteSnackBarText()
verifySnackBarText(expectedText = "Deleted")
clickUndoDeleteButton()
verifyFolderTitle("My Folder")
}.openThreeDotMenu("My Folder") {
}.clickDelete {
confirmDeletion()
verifyDeleteSnackBarText()
verifySnackBarText(expectedText = "Deleted")
verifyBookmarkIsDeleted("My Folder")
verifyBookmarkIsDeleted("My Folder 2")
verifyBookmarkIsDeleted("Test_Page_1")

@ -475,7 +475,7 @@ class ComposeCollectionTest {
verifyTabsMultiSelectionCounter(2)
}.clickSaveCollection {
typeCollectionNameAndSave(collectionName)
verifySnackBarText("Tabs saved!")
verifySnackBarText("Collection saved!")
}
composeTabDrawer(composeTestRule) {

@ -23,6 +23,7 @@ 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.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.downloadRobot

@ -28,6 +28,7 @@ import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.historyMenu
import org.mozilla.fenix.ui.robots.homeScreen
@ -128,7 +129,7 @@ class ComposeHistoryTest {
) {
clickDeleteHistoryButton(firstWebPage.url.toString())
}
verifyDeleteSnackbarText("Deleted")
verifySnackBarText(expectedText = "Deleted")
verifyEmptyHistoryView()
}
}
@ -153,7 +154,7 @@ class ComposeHistoryTest {
verifyDeleteConfirmationMessage()
selectEverythingOption()
confirmDeleteAllHistory()
verifyDeleteSnackbarText("Browsing data deleted")
verifySnackBarText(expectedText = "Browsing data deleted")
verifyEmptyHistoryView()
}
}

@ -31,6 +31,7 @@ import org.mozilla.fenix.helpers.SearchDispatcher
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.verifySnackBarText
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
@ -472,7 +473,7 @@ class ComposeSearchTest {
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
clickDeleteAllHistoryButton()
confirmDeleteAllHistory()
verifyDeleteSnackbarText("Group deleted")
verifySnackBarText(expectedText = "Group deleted")
verifyHistoryItemExists(shouldExist = false, firstPageUrl.toString())
}.goBack {}
homeScreen {

@ -20,8 +20,10 @@ import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.helpers.TestHelper.waitUntilSnackbarGone
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.homeScreenWithComposeTopSites
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -66,8 +68,12 @@ class ComposeTopSitesTest {
fun addAWebsiteAsATopSiteTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreenWithComposeTopSites(composeTestRule) {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(true)
@ -84,8 +90,12 @@ class ComposeTopSitesTest {
fun openTopSiteInANewTabTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreenWithComposeTopSites(composeTestRule) {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(true)
@ -112,8 +122,12 @@ class ComposeTopSitesTest {
fun openTopSiteInANewPrivateTabTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreenWithComposeTopSites(composeTestRule) {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(true)
@ -135,6 +149,9 @@ class ComposeTopSitesTest {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
val newPageTitle = generateRandomString(5)
homeScreenWithComposeTopSites(composeTestRule) {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad()
@ -159,8 +176,12 @@ class ComposeTopSitesTest {
fun removeTopSiteUsingMenuButtonTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreenWithComposeTopSites(composeTestRule) {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(true)
@ -186,8 +207,12 @@ class ComposeTopSitesTest {
fun removeTopSiteFromMainMenuTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreenWithComposeTopSites(composeTestRule) {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(true)
@ -236,9 +261,11 @@ class ComposeTopSitesTest {
verifyExistingTopSitesList()
verifyExistingTopSiteItem(defaultWebPage.title)
}.openContextMenuOnTopSitesWithTitle(defaultWebPage.title) {
}.deleteTopSiteFromHistory {
}.removeTopSite {
verifySnackBarText(getStringResource(R.string.snackbar_top_site_removed))
waitUntilSnackbarGone()
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()

@ -5,15 +5,8 @@
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
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.AppAndSystemHelper.assertExternalAppOpens
import org.mozilla.fenix.helpers.Constants.PackageName.YOUTUBE_APP
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -22,6 +15,9 @@ 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.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.downloadRobot
@ -43,32 +39,15 @@ import org.mozilla.fenix.ui.robots.shareOverlay
*
*/
class ContextMenusTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
class ContextMenusTest : TestSetup() {
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
val activityIntentTestRule = HomeActivityIntentTestRule(isJumpBackInCFREnabled = false)
@Rule
@JvmField
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
activityIntentTestRule.activity.applicationContext.settings().shouldShowJumpBackInCFR = false
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/243837
@Test
fun verifyOpenLinkNewTabContextMenuOptionTest() {

@ -12,13 +12,14 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Tests for verifying the new Cookie banner blocker option and functionality.
*/
class CookieBannerBlockerTest {
class CookieBannerBlockerTest : TestSetup() {
@get:Rule
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)

@ -5,29 +5,23 @@
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
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.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class CrashReportingTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
class CrashReportingTest : TestSetup() {
private val tabCrashMessage = getStringResource(R.string.tab_crash_title_2)
@get:Rule
@ -40,20 +34,6 @@ class CrashReportingTest {
),
) { it.activity }
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/308906
@Test
fun closeTabFromCrashedTabReporterTest() {
@ -105,7 +85,7 @@ class CrashReportingTest {
verifyPageContent(tabCrashMessage)
}.openTabDrawer {
verifyExistingOpenTabs(firstWebPage.title)
verifyExistingOpenTabs(secondWebPage.title)
verifyExistingOpenTabs("about:crashcontent")
}.closeTabDrawer {
}.goToHomescreen {
verifyExistingTopSitesList()

@ -4,13 +4,9 @@
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.AppAndSystemHelper.bringAppToForeground
import org.mozilla.fenix.helpers.AppAndSystemHelper.putAppToBackground
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -19,14 +15,13 @@ 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.helpers.TestSetup
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
class CreditCardAutofillTest : TestSetup() {
object MockCreditCard1 {
const val MOCK_CREDIT_CARD_NUMBER = "5555555555554444"
const val MOCK_LAST_CARD_DIGITS = "4444"
@ -48,19 +43,6 @@ class CreditCardAutofillTest {
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1512792
@SmokeTest
@Test

@ -7,25 +7,20 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.AppAndSystemHelper.openAppFromExternalLink
import org.mozilla.fenix.helpers.DataGenerationHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.FeatureSettingsHelperDelegate
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
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.exitMenu
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestSetup
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.customTabScreen
@ -36,9 +31,7 @@ import org.mozilla.fenix.ui.robots.notificationShade
import org.mozilla.fenix.ui.robots.openEditURLView
import org.mozilla.fenix.ui.robots.searchScreen
class CustomTabsTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
class CustomTabsTest : TestSetup() {
private val customMenuItem = "TestMenuItem"
private val customTabActionButton = "CustomActionButton"
@ -58,23 +51,6 @@ class CustomTabsTest {
false,
)
private val featureSettingsHelper = FeatureSettingsHelperDelegate()
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
featureSettingsHelper.resetAllFeatureFlags()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/249659
@SmokeTest
@Test

@ -48,9 +48,9 @@ class FirefoxSuggestTest {
"Nike.com - Official Site",
"nike.com/?cp=16423867261_search_318370984us128${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
),
"Macy" to listOf(
"macys.com - Official Site",
"macys.com/?cm_mmc=Google_AdMarketPlace-_-Privacy_Instant%20Suggest-_-319101130_Broad-_-kclickid__kenshoo_clickid_&m_sc=sem&m_sb=Admarketplace&m_tp=Search&m_ac=Admarketplace&m_ag=Instant%20Suggest&m_cn=Privacy&m_pi=kclickid__kenshoo_clickid__319101130us1201${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
"Houzz" to listOf(
"Houzz.com - Official Site",
"houzz.com/products?m_refid=us-dsp-mpl-admp-219577_15416306_kwd-353208810&adcid=319104989us1287${getSponsoredFxSuggestPlaceHolder()}&mfadid=adm",
),
"Spanx" to listOf(
"SPANX® - Official Site",
@ -101,7 +101,7 @@ class FirefoxSuggestTest {
private val nonSponsoredKeyWord = nonSponsoredKeyWords.keys.random()
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348361
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@SmokeTest
@Test
fun verifyFirefoxSuggestSponsoredSearchResultsTest() {
@ -123,7 +123,7 @@ class FirefoxSuggestTest {
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348362
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@Test
fun verifyFirefoxSuggestSponsoredSearchResultsWithPartialKeywordTest() {
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
@ -144,7 +144,7 @@ class FirefoxSuggestTest {
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348363
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@Test
fun openFirefoxSuggestSponsoredSearchResultsTest() {
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
@ -168,7 +168,7 @@ class FirefoxSuggestTest {
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348369
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1874831")
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@Test
fun verifyFirefoxSuggestSponsoredSearchResultsWithEditedKeywordTest() {
runWithCondition(TestHelper.appContext.settings().enableFxSuggest) {
@ -192,6 +192,7 @@ class FirefoxSuggestTest {
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348374
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1882035")
@SmokeTest
@Test
@ -219,6 +220,7 @@ class FirefoxSuggestTest {
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348375
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1882035")
@Test
fun verifyFirefoxSuggestNonSponsoredSearchResultsWithPartialKeywordTest() {
@ -239,6 +241,7 @@ class FirefoxSuggestTest {
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2348376
// Known bug that might affect this UI test: https://bugzilla.mozilla.org/show_bug.cgi?id=1813587
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1882035")
@Test
fun openFirefoxSuggestNonSponsoredSearchResultsTest() {

@ -29,6 +29,7 @@ import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.historyMenu
import org.mozilla.fenix.ui.robots.homeScreen
@ -131,7 +132,7 @@ class HistoryTest {
clickUndoDeleteButton()
verifyHistoryItemExists(true, firstWebPage.url.toString())
clickDeleteHistoryButton(firstWebPage.url.toString())
verifyDeleteSnackbarText("Deleted")
verifySnackBarText(expectedText = "Deleted")
verifyEmptyHistoryView()
}
}
@ -161,7 +162,7 @@ class HistoryTest {
verifyDeleteConfirmationMessage()
selectEverythingOption()
confirmDeleteAllHistory()
verifyDeleteSnackbarText("Browsing data deleted")
verifySnackBarText(expectedText = "Browsing data deleted")
verifyEmptyHistoryView()
}
}

@ -28,6 +28,7 @@ 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.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.helpers.TestHelper.waitForAppWindowToBeUpdated
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clearTextFieldItem
import org.mozilla.fenix.ui.robots.clickPageObject
@ -260,12 +261,16 @@ class LoginsTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
setPageObjectText(itemWithResId("username"), firstUser)
waitForAppWindowToBeUpdated()
setPageObjectText(itemWithResId("password"), firstPass)
waitForAppWindowToBeUpdated()
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
setPageObjectText(itemWithResId("username"), secondUser)
waitForAppWindowToBeUpdated()
setPageObjectText(itemWithResId("password"), secondPass)
waitForAppWindowToBeUpdated()
clickPageObject(itemWithResId("submit"))
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
@ -505,9 +510,13 @@ class LoginsTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
waitForPageToLoad()
setPageObjectText(itemWithResId("username"), "mozilla")
waitForAppWindowToBeUpdated()
setPageObjectText(itemWithResId("password"), "firefox")
waitForAppWindowToBeUpdated()
clickPageObject(itemWithResId("submit"))
waitForPageToLoad()
verifySaveLoginPromptIsDisplayed()
clickPageObject(itemWithText("Save"))
}.openTabDrawer {
@ -516,6 +525,8 @@ class LoginsTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(loginPage.toUri()) {
waitForPageToLoad()
clickPageObject(itemWithResId("togglePassword"))
verifyPrefilledLoginCredentials("mozilla", "firefox", true)
}.openTabDrawer {
closeTab()

@ -175,21 +175,21 @@ class MainMenuTest {
verifyFindInPagePrevButton()
verifyFindInPageCloseButton()
enterFindInPageQuery("a")
verifyFindNextInPageResult("1/3")
verifyFindInPageResult("1/3")
clickFindInPageNextButton()
verifyFindNextInPageResult("2/3")
verifyFindInPageResult("2/3")
clickFindInPageNextButton()
verifyFindNextInPageResult("3/3")
verifyFindInPageResult("3/3")
clickFindInPagePrevButton()
verifyFindPrevInPageResult("2/3")
verifyFindInPageResult("2/3")
clickFindInPagePrevButton()
verifyFindPrevInPageResult("1/3")
verifyFindInPageResult("1/3")
}.closeFindInPageWithCloseButton {
verifyFindInPageBar(false)
}.openThreeDotMenu {
}.openFindInPage {
enterFindInPageQuery("3")
verifyFindNextInPageResult("1/1")
verifyFindInPageResult("1/1")
}.closeFindInPageWithBackButton {
verifyFindInPageBar(false)
}

@ -20,6 +20,7 @@ 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.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen

@ -117,17 +117,17 @@ class PDFViewerTest {
verifyFindInPagePrevButton()
verifyFindInPageCloseButton()
enterFindInPageQuery("l")
verifyFindNextInPageResult("1/2")
verifyFindInPageResult("1/2")
clickFindInPageNextButton()
verifyFindNextInPageResult("2/2")
verifyFindInPageResult("2/2")
clickFindInPagePrevButton()
verifyFindPrevInPageResult("1/2")
verifyFindInPageResult("1/2")
}.closeFindInPageWithCloseButton {
verifyFindInPageBar(false)
}.openThreeDotMenu {
}.openFindInPage {
enterFindInPageQuery("p")
verifyFindNextInPageResult("1/1")
verifyFindInPageResult("1/1")
}.closeFindInPageWithBackButton {
verifyFindInPageBar(false)
}

@ -44,6 +44,7 @@ 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.mDevice
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
@ -487,7 +488,7 @@ class SearchTest {
}.openRecentlyVisitedSearchGroupHistoryList(queryString) {
clickDeleteAllHistoryButton()
confirmDeleteAllHistory()
verifyDeleteSnackbarText("Group deleted")
verifySnackBarText("Group deleted")
verifyHistoryItemExists(shouldExist = false, firstPageUrl.toString())
}.goBack {}
homeScreen {

@ -38,7 +38,6 @@ class SettingsAdvancedTest {
private val youTubeFullLink = itemContainingText("Youtube full link")
private val playStoreLink = itemContainingText("Playstore link")
private val playStoreUrl = "play.google.com"
private val youTubePage = "vnd.youtube://".toUri()
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
@ -159,11 +158,10 @@ class SettingsAdvancedTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickPageObject(youTubeFullLink)
clickPageObject(youTubeSchemaLink)
verifyOpenLinkInAnotherAppPrompt()
clickPageObject(itemWithResIdAndText("android:id/button2", "CANCEL"))
waitForPageToLoad()
verifyUrl("youtube")
verifyUrl(externalLinksPage.url.toString())
}
}
@ -226,14 +224,13 @@ class SettingsAdvancedTest {
navigationToolbar {
}.enterURLAndEnterToBrowser(externalLinksPage.url) {
clickPageObject(youTubeFullLink)
clickPageObject(youTubeSchemaLink)
verifyPrivateBrowsingOpenLinkInAnotherAppPrompt(
url = "youtube",
pageObject = youTubeFullLink,
pageObject = youTubeSchemaLink,
)
clickPageObject(itemWithResIdAndText("android:id/button2", "CANCEL"))
waitForPageToLoad()
verifyUrl("youtube")
verifyUrl(externalLinksPage.url.toString())
}
}
@ -311,7 +308,7 @@ class SettingsAdvancedTest {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(youTubePage) {
}.enterURLAndEnterToBrowser("https://m.youtube.com/".toUri()) {
waitForPageToLoad()
verifyOpenLinksInAppsCFRExists(true)
clickOpenLinksInAppsDismissCFRButton()

@ -18,6 +18,7 @@ import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.restartApp
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

@ -23,6 +23,7 @@ import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.closeApp
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen

@ -20,6 +20,7 @@ import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.helpers.TestHelper.waitUntilSnackbarGone
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
@ -61,8 +62,12 @@ class TopSitesTest {
fun addAWebsiteAsATopSiteTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreen {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(shouldExist = true)
@ -79,8 +84,12 @@ class TopSitesTest {
fun openTopSiteInANewTabTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreen {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(shouldExist = true)
@ -107,8 +116,12 @@ class TopSitesTest {
fun openTopSiteInANewPrivateTabTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreen {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(shouldExist = true)
@ -130,6 +143,9 @@ class TopSitesTest {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
val newPageTitle = generateRandomString(5)
homeScreen {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad()
@ -154,8 +170,12 @@ class TopSitesTest {
fun removeTopSiteUsingMenuButtonTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreen {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(shouldExist = true)
@ -181,8 +201,12 @@ class TopSitesTest {
fun removeTopSiteFromMainMenuTest() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
homeScreen {
verifyExistingTopSitesList()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton(shouldExist = true)

@ -5,6 +5,7 @@
package org.mozilla.fenix.ui.robots
import android.os.Build
import android.util.Log
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
@ -13,6 +14,7 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
@ -27,27 +29,47 @@ import java.util.regex.Pattern
*/
class AddToHomeScreenRobot {
fun verifyAddPrivateBrowsingShortcutButton(composeTestRule: ComposeTestRule) =
fun verifyAddPrivateBrowsingShortcutButton(composeTestRule: ComposeTestRule) {
Log.i(TAG, "verifyAddPrivateBrowsingShortcutButton: Trying to verify \"Add to Home screen\" private browsing shortcut dialog button is displayed")
composeTestRule.onNodeWithTag("private.add").assertIsDisplayed()
Log.i(TAG, "verifyAddPrivateBrowsingShortcutButton: Verified \"Add to Home screen\" private browsing shortcut dialog button is displayed")
}
fun verifyNoThanksPrivateBrowsingShortcutButton(composeTestRule: ComposeTestRule) =
fun verifyNoThanksPrivateBrowsingShortcutButton(composeTestRule: ComposeTestRule) {
Log.i(TAG, "verifyNoThanksPrivateBrowsingShortcutButton: Trying to verify \"No thanks\" private browsing shortcut dialog button is displayed")
composeTestRule.onNodeWithTag("private.cancel").assertIsDisplayed()
Log.i(TAG, "verifyNoThanksPrivateBrowsingShortcutButton: Verified \"No thanks\" private browsing shortcut dialog button is displayed")
}
fun clickAddPrivateBrowsingShortcutButton(composeTestRule: ComposeTestRule) =
fun clickAddPrivateBrowsingShortcutButton(composeTestRule: ComposeTestRule) {
Log.i(TAG, "clickAddPrivateBrowsingShortcutButton: Trying to click \"Add to Home screen\" private browsing shortcut dialog button")
composeTestRule.onNodeWithTag("private.add").performClick()
Log.i(TAG, "clickAddPrivateBrowsingShortcutButton: Clicked \"Add to Home screen\" private browsing shortcut dialog button")
}
fun addShortcutName(title: String) = shortcutTextField.setText(title)
fun addShortcutName(title: String) {
Log.i(TAG, "addShortcutName: Trying to set shortcut name to: $title")
shortcutTextField().setText(title)
Log.i(TAG, "addShortcutName: Set shortcut name to: $title")
}
fun verifyShortcutTextFieldTitle(title: String) = assertUIObjectExists(shortcutTitle(title))
fun clickAddShortcutButton() =
confirmAddToHomeScreenButton.clickAndWaitForNewWindow(waitingTime)
fun clickAddShortcutButton() {
Log.i(TAG, "clickAddShortcutButton: Trying to click \"Add\" button from \"Add to home screen\" dialog and wait for $waitingTime ms for a new window")
confirmAddToHomeScreenButton().clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "clickAddShortcutButton: Clicked \"Add\" button from \"Add to home screen\" dialog and waited for $waitingTime ms for a new window")
}
fun clickCancelShortcutButton() =
cancelAddToHomeScreenButton.click()
fun clickCancelShortcutButton() {
Log.i(TAG, "clickCancelShortcutButton: Trying to click \"Cancel\" button from \"Add to home screen\" dialog")
cancelAddToHomeScreenButton().click()
Log.i(TAG, "clickCancelShortcutButton: Clicked \"Cancel\" button from \"Add to home screen\" dialog")
}
fun clickAddAutomaticallyButton() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.i(TAG, "clickAddAutomaticallyButton: Waiting for $waitingTime ms until finding \"Add automatically\" system dialog button")
mDevice.wait(
Until.findObject(
By.text(
@ -56,7 +78,10 @@ class AddToHomeScreenRobot {
),
waitingTime,
)
Log.i(TAG, "clickAddAutomaticallyButton: Waited for $waitingTime ms until \"Add automatically\" system dialog button was found")
Log.i(TAG, "clickAddAutomaticallyButton: Trying to click \"Add automatically\" system dialog button")
addAutomaticallyButton().click()
Log.i(TAG, "clickAddAutomaticallyButton: Clicked \"Add automatically\" system dialog button")
}
}
@ -65,27 +90,37 @@ class AddToHomeScreenRobot {
class Transition {
fun openHomeScreenShortcut(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "openHomeScreenShortcut: Waiting for $waitingTime ms until finding $title home screen shortcut")
mDevice.wait(
Until.findObject(By.text(title)),
waitingTime,
)
Log.i(TAG, "openHomeScreenShortcut: Waited for $waitingTime ms until $title home screen shortcut was found")
Log.i(TAG, "openHomeScreenShortcut: Trying to click $title home screen shortcut and wait for $waitingTime ms for a new window")
mDevice.findObject((UiSelector().text(title))).clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "openHomeScreenShortcut: Clicked $title home screen shortcut and waited for $waitingTime ms for a new window")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun searchAndOpenHomeScreenShortcut(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "searchAndOpenHomeScreenShortcut: Trying to press device home button")
mDevice.pressHome()
Log.i(TAG, "searchAndOpenHomeScreenShortcut: Pressed device home button")
fun homeScreenView() = UiScrollable(UiSelector().scrollable(true))
Log.i(TAG, "searchAndOpenHomeScreenShortcut: Waiting for $waitingTime ms for home screen view to exist")
homeScreenView().waitForExists(waitingTime)
Log.i(TAG, "searchAndOpenHomeScreenShortcut: Waited for $waitingTime ms for home screen view to exist")
fun shortcut() =
homeScreenView()
.setAsHorizontalList()
.getChildByText(UiSelector().textContains(title), title, true)
Log.i(TAG, "searchAndOpenHomeScreenShortcut: Trying to click home screen shortcut: $title and wait for a new window")
shortcut().clickAndWaitForNewWindow()
Log.i(TAG, "searchAndOpenHomeScreenShortcut: Clicked home screen shortcut: $title and waited for a new window")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -101,11 +136,11 @@ fun addToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenR
private fun addAutomaticallyButton() =
mDevice.findObject(UiSelector().textContains("add automatically"))
private val cancelAddToHomeScreenButton =
private fun cancelAddToHomeScreenButton() =
itemWithResId("$packageName:id/cancel_button")
private val confirmAddToHomeScreenButton =
private fun confirmAddToHomeScreenButton() =
itemWithResId("$packageName:id/add_button")
private val shortcutTextField =
private fun shortcutTextField() =
itemWithResId("$packageName:id/shortcut_text")
private fun shortcutTitle(title: String) =
itemWithResIdAndText("$packageName:id/shortcut_text", title)

@ -7,6 +7,7 @@
package org.mozilla.fenix.ui.robots
import android.net.Uri
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.clearText
import androidx.test.espresso.action.ViewActions.longClick
@ -32,6 +33,7 @@ import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.junit.Assert.assertEquals
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
@ -40,7 +42,6 @@ import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -54,51 +55,114 @@ import org.mozilla.fenix.helpers.ext.waitNotNull
class BookmarksRobot {
fun verifyBookmarksMenuView() {
Log.i(TAG, "verifyBookmarksMenuView: Waiting for $waitingTime ms for bookmarks view to exist")
mDevice.findObject(
UiSelector().text("Bookmarks"),
).waitForExists(waitingTime)
assertBookmarksView()
Log.i(TAG, "verifyBookmarksMenuView: Waited for $waitingTime ms for bookmarks view to exist")
Log.i(TAG, "verifyBookmarksMenuView: Trying to verify bookmarks view is displayed")
onView(
allOf(
withText("Bookmarks"),
withParent(withId(R.id.navigationToolbar)),
),
).check(matches(isDisplayed()))
Log.i(TAG, "verifyBookmarksMenuView: Verified bookmarks view is displayed")
}
fun verifyAddFolderButton() = assertAddFolderButton()
fun verifyCloseButton() = assertCloseButton()
fun verifyAddFolderButton() {
Log.i(TAG, "verifyAddFolderButton: Trying to verify add bookmarks folder button is visible")
addFolderButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyAddFolderButton: Verified add bookmarks folder button is visible")
}
fun verifyDeleteMultipleBookmarksSnackBar() = assertSnackBarText("Bookmarks deleted")
fun verifyCloseButton() {
Log.i(TAG, "verifyCloseButton: Trying to verify close bookmarks section button is visible")
closeButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyCloseButton: Verified close bookmarks section button is visible")
}
fun verifyBookmarkFavicon(forUrl: Uri) = assertBookmarkFavicon(forUrl)
fun verifyBookmarkFavicon(forUrl: Uri) {
Log.i(TAG, "verifyBookmarkFavicon: Trying to verify bookmarks favicon for $forUrl is visible")
bookmarkFavicon(forUrl.toString()).check(
matches(
withEffectiveVisibility(
ViewMatchers.Visibility.VISIBLE,
),
),
)
Log.i(TAG, "verifyBookmarkFavicon: Verified bookmarks favicon for $forUrl is visible")
}
fun verifyBookmarkedURL(url: String) = assertBookmarkURL(url)
fun verifyBookmarkedURL(url: String) {
Log.i(TAG, "verifyBookmarkedURL: Trying to verify bookmarks url: $url is displayed")
bookmarkURL(url).check(matches(isDisplayed()))
Log.i(TAG, "verifyBookmarkedURL: Verified bookmarks url: $url is displayed")
}
fun verifyFolderTitle(title: String) {
Log.i(TAG, "verifyFolderTitle: Waiting for $waitingTime ms for bookmarks folder with title: $title to exist")
mDevice.findObject(UiSelector().text(title)).waitForExists(waitingTime)
assertFolderTitle(title)
Log.i(TAG, "verifyFolderTitle: Waited for $waitingTime ms for bookmarks folder with title: $title to exist")
Log.i(TAG, "verifyFolderTitle: Trying to verify bookmarks folder with title: $title is displayed")
onView(withText(title)).check(matches(isDisplayed()))
Log.i(TAG, "verifyFolderTitle: Verified bookmarks folder with title: $title is displayed")
}
fun verifyBookmarkFolderIsNotCreated(title: String) = assertBookmarkFolderIsNotCreated(title)
fun verifyBookmarkFolderIsNotCreated(title: String) {
Log.i(TAG, "verifyBookmarkFolderIsNotCreated: Waiting for $waitingTime ms for bookmarks folder with title: $title to exist")
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/bookmarks_wrapper"),
).waitForExists(waitingTime)
Log.i(TAG, "verifyBookmarkFolderIsNotCreated: Waited for $waitingTime ms for bookmarks folder with title: $title to exist")
assertUIObjectExists(itemContainingText(title), exists = false)
}
fun verifyBookmarkTitle(title: String) {
Log.i(TAG, "verifyBookmarkTitle: Waiting for $waitingTime ms for bookmark with title: $title to exist")
mDevice.findObject(UiSelector().text(title)).waitForExists(waitingTime)
assertBookmarkTitle(title)
Log.i(TAG, "verifyBookmarkTitle: Waited for $waitingTime ms for bookmark with title: $title to exist")
Log.i(TAG, "verifyBookmarkTitle: Trying to verify bookmark with title: $title is displayed")
onView(withText(title)).check(matches(isDisplayed()))
Log.i(TAG, "verifyBookmarkTitle: Verified bookmark with title: $title is displayed")
}
fun verifyBookmarkIsDeleted(expectedTitle: String) = assertBookmarkIsDeleted(expectedTitle)
fun verifyDeleteSnackBarText() = assertSnackBarText("Deleted")
fun verifyBookmarkIsDeleted(expectedTitle: String) {
Log.i(TAG, "verifyBookmarkIsDeleted: Waiting for $waitingTime ms for bookmarks view to exist")
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/bookmarks_wrapper"),
).waitForExists(waitingTime)
Log.i(TAG, "verifyBookmarkIsDeleted: Waited for $waitingTime ms for bookmarks view to exist")
assertUIObjectExists(
itemWithResIdContainingText(
"$packageName:id/title",
expectedTitle,
),
exists = false,
)
}
fun verifyUndoDeleteSnackBarButton() = assertUndoDeleteSnackBarButton()
fun verifyUndoDeleteSnackBarButton() {
Log.i(TAG, "verifyUndoDeleteSnackBarButton: Trying to verify bookmark deletion undo snack bar button")
snackBarUndoButton().check(matches(withText("UNDO")))
Log.i(TAG, "verifyUndoDeleteSnackBarButton: Verified bookmark deletion undo snack bar button")
}
fun verifySnackBarHidden() {
Log.i(TAG, "verifySnackBarHidden: Waiting until undo snack bar button is gone")
mDevice.waitNotNull(
Until.gone(By.text("UNDO")),
TestAssetHelper.waitingTime,
waitingTime,
)
Log.i(TAG, "verifySnackBarHidden: Waited until undo snack bar button was gone")
Log.i(TAG, "verifySnackBarHidden: Trying to verify bookmark snack bar does not exist")
onView(withId(R.id.snackbar_layout)).check(doesNotExist())
Log.i(TAG, "verifySnackBarHidden: Verified bookmark snack bar does not exist")
}
fun verifyCopySnackBarText() = assertSnackBarText("URL copied")
fun verifyEditBookmarksView() =
assertUIObjectExists(
itemWithDescription("Navigate up"),
@ -110,27 +174,50 @@ class BookmarksRobot {
itemWithResId("$packageName:id/bookmarkParentFolderSelector"),
)
fun verifyKeyboardHidden() = assertKeyboardVisibility(isExpectedToBeVisible = false)
fun verifyKeyboardVisible() = assertKeyboardVisibility(isExpectedToBeVisible = true)
fun verifyShareOverlay() = assertShareOverlay()
fun verifyKeyboardHidden(isExpectedToBeVisible: Boolean) {
Log.i(TAG, "assertKeyboardVisibility: Trying to verify that the keyboard is visible: $isExpectedToBeVisible")
assertEquals(
isExpectedToBeVisible,
mDevice
.executeShellCommand("dumpsys input_method | grep mInputShown")
.contains("mInputShown=true"),
)
Log.i(TAG, "assertKeyboardVisibility: Verified that the keyboard is visible: $isExpectedToBeVisible")
}
fun verifyShareBookmarkFavicon() = assertShareBookmarkFavicon()
fun verifyShareOverlay() {
Log.i(TAG, "verifyShareOverlay: Trying to verify bookmarks sharing overlay is displayed")
onView(withId(R.id.shareWrapper)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareOverlay: Verified bookmarks sharing overlay is displayed")
}
fun verifyShareBookmarkTitle() = assertShareBookmarkTitle()
fun verifyShareBookmarkFavicon() {
Log.i(TAG, "verifyShareBookmarkFavicon: Trying to verify shared bookmarks favicon is displayed")
onView(withId(R.id.share_tab_favicon)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareBookmarkFavicon: Verified shared bookmarks favicon is displayed")
}
fun verifyShareBookmarkUrl() = assertShareBookmarkUrl()
fun verifyShareBookmarkTitle() {
Log.i(TAG, "verifyShareBookmarkTitle: Trying to verify shared bookmarks title is displayed")
onView(withId(R.id.share_tab_title)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareBookmarkTitle: Verified shared bookmarks title is displayed")
}
fun verifySelectDefaultFolderSnackBarText() = assertSnackBarText("Cant edit default folders")
fun verifyShareBookmarkUrl() {
Log.i(TAG, "verifyShareBookmarkUrl: Trying to verify shared bookmarks url is displayed")
onView(withId(R.id.share_tab_url)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareBookmarkUrl: Verified shared bookmarks url is displayed")
}
fun verifyCurrentFolderTitle(title: String) {
Log.i(TAG, "verifyCurrentFolderTitle: Waiting for $waitingTime ms for bookmark with title: $title to exist")
mDevice.findObject(
UiSelector().resourceId("$packageName:id/navigationToolbar")
.textContains(title),
)
.waitForExists(waitingTime)
Log.i(TAG, "verifyCurrentFolderTitle: Waited for $waitingTime ms for bookmark with title: $title to exist")
Log.i(TAG, "verifyCurrentFolderTitle: Trying to verify bookmark with title: $title is displayed")
onView(
allOf(
withText(title),
@ -138,28 +225,34 @@ class BookmarksRobot {
),
)
.check(matches(isDisplayed()))
Log.i(TAG, "verifyCurrentFolderTitle: Verified bookmark with title: $title is displayed")
}
fun waitForBookmarksFolderContentToExist(parentFolderName: String, childFolderName: String) {
Log.i(TAG, "waitForBookmarksFolderContentToExist: Waiting for $waitingTime ms for navigation toolbar containing bookmark folder with title: $parentFolderName to exist")
mDevice.findObject(
UiSelector().resourceId("$packageName:id/navigationToolbar")
.textContains(parentFolderName),
)
.waitForExists(waitingTime)
Log.i(TAG, "waitForBookmarksFolderContentToExist: Waited for $waitingTime ms for navigation toolbar containing bookmark folder with title: $parentFolderName to exist")
mDevice.waitNotNull(Until.findObject(By.text(childFolderName)), waitingTime)
}
fun verifySyncSignInButton() =
fun verifySyncSignInButton() {
Log.i(TAG, "verifySyncSignInButton: Trying to verify sign in to sync button is visible")
syncSignInButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
fun verifyDeleteFolderConfirmationMessage() = assertDeleteFolderConfirmationMessage()
Log.i(TAG, "verifySyncSignInButton: Verified sign in to sync button is visible")
}
fun cancelFolderDeletion() {
Log.i(TAG, "cancelFolderDeletion: Trying to click \"Cancel\" bookmarks folder deletion dialog button")
onView(withText("CANCEL"))
.inRoot(RootMatchers.isDialog())
.check(matches(isDisplayed()))
.click()
Log.i(TAG, "cancelFolderDeletion: Clicked \"Cancel\" bookmarks folder deletion dialog button")
}
fun createFolder(name: String, parent: String? = null) {
@ -180,90 +273,127 @@ class BookmarksRobot {
fun clickAddFolderButton() {
mDevice.waitNotNull(
Until.findObject(By.desc("Add folder")),
TestAssetHelper.waitingTime,
waitingTime,
)
Log.i(TAG, "clickAddFolderButton: Trying to click add bookmarks folder button")
addFolderButton().click()
Log.i(TAG, "clickAddFolderButton: Clicked add bookmarks folder button")
}
fun clickAddNewFolderButtonFromSelectFolderView() =
fun clickAddNewFolderButtonFromSelectFolderView() {
itemWithResId("$packageName:id/add_folder_button")
.also {
Log.i(TAG, "clickAddNewFolderButtonFromSelectFolderView: Waiting for $waitingTime ms for add bookmarks folder button from folder selection view to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "clickAddNewFolderButtonFromSelectFolderView: Waited for $waitingTime ms for add bookmarks folder button from folder selection view to exist")
Log.i(TAG, "clickAddNewFolderButtonFromSelectFolderView: Trying to click add bookmarks folder button from folder selection view")
it.click()
Log.i(TAG, "clickAddNewFolderButtonFromSelectFolderView: Clicked add bookmarks folder button from folder selection view")
}
}
fun addNewFolderName(name: String) {
addFolderTitleField()
.click()
.perform(replaceText(name))
Log.i(TAG, "addNewFolderName: Trying to click add folder name field")
addFolderTitleField().click()
Log.i(TAG, "addNewFolderName: Clicked to click add folder name field")
Log.i(TAG, "addNewFolderName: Trying to set bookmarks folder name to: $name")
addFolderTitleField().perform(replaceText(name))
Log.i(TAG, "addNewFolderName: Bookmarks folder name was set to: $name")
}
fun saveNewFolder() {
Log.i(TAG, "saveNewFolder: Trying to click save folder button")
saveFolderButton().click()
Log.i(TAG, "saveNewFolder: Clicked save folder button")
}
fun navigateUp() {
Log.i(TAG, "navigateUp: Trying to click navigate up toolbar button")
goBackButton().click()
Log.i(TAG, "navigateUp: Clicked navigate up toolbar button")
}
fun clickUndoDeleteButton() {
Log.i(TAG, "clickUndoDeleteButton: Trying to click undo snack bar button")
snackBarUndoButton().click()
Log.i(TAG, "clickUndoDeleteButton: Clicked undo snack bar button")
}
fun changeBookmarkTitle(newTitle: String) {
bookmarkNameEditBox()
.perform(clearText())
.perform(typeText(newTitle))
Log.i(TAG, "changeBookmarkTitle: Trying to clear bookmark name text box")
bookmarkNameEditBox().perform(clearText())
Log.i(TAG, "changeBookmarkTitle: Cleared bookmark name text box")
Log.i(TAG, "changeBookmarkTitle: Trying to set bookmark title to: $newTitle")
bookmarkNameEditBox().perform(typeText(newTitle))
Log.i(TAG, "changeBookmarkTitle: Bookmark title was set to: $newTitle")
}
fun changeBookmarkUrl(newUrl: String) {
bookmarkURLEditBox()
.perform(clearText())
.perform(typeText(newUrl))
Log.i(TAG, "changeBookmarkUrl: Trying to clear bookmark url text box")
bookmarkURLEditBox().perform(clearText())
Log.i(TAG, "changeBookmarkUrl: Cleared bookmark url text box")
Log.i(TAG, "changeBookmarkUrl: Trying to set bookmark url to: $newUrl")
bookmarkURLEditBox().perform(typeText(newUrl))
Log.i(TAG, "changeBookmarkUrl: Bookmark url was set to: $newUrl")
}
fun saveEditBookmark() {
Log.i(TAG, "saveEditBookmark: Trying to click save bookmark button")
saveBookmarkButton().click()
Log.i(TAG, "saveEditBookmark: Clicked save bookmark button")
Log.i(TAG, "saveEditBookmark: Waiting for $waitingTime ms for bookmarks list to exist")
mDevice.findObject(UiSelector().resourceId("org.mozilla.fenix.debug:id/bookmark_list")).waitForExists(waitingTime)
Log.i(TAG, "saveEditBookmark: Waited for $waitingTime ms for bookmarks list to exist")
}
fun clickParentFolderSelector() = bookmarkFolderSelector().click()
fun clickParentFolderSelector() {
Log.i(TAG, "clickParentFolderSelector: Trying to click folder selector")
bookmarkFolderSelector().click()
Log.i(TAG, "clickParentFolderSelector: Clicked folder selector")
}
fun selectFolder(title: String) = onView(withText(title)).click()
fun selectFolder(title: String) {
Log.i(TAG, "selectFolder: Trying to click folder with title: $title")
onView(withText(title)).click()
Log.i(TAG, "selectFolder: Clicked folder with title: $title")
}
fun longTapDesktopFolder(title: String) = onView(withText(title)).perform(longClick())
fun longTapDesktopFolder(title: String) {
Log.i(TAG, "longTapDesktopFolder: Trying to long tap folder with title: $title")
onView(withText(title)).perform(longClick())
Log.i(TAG, "longTapDesktopFolder: Long tapped folder with title: $title")
}
fun cancelDeletion() {
val cancelButton = mDevice.findObject(UiSelector().textContains("CANCEL"))
Log.i(TAG, "cancelDeletion: Waiting for $waitingTime ms for \"Cancel\" bookmarks deletion button to exist")
cancelButton.waitForExists(waitingTime)
Log.i(TAG, "cancelDeletion: Waited for $waitingTime ms for \"Cancel\" bookmarks deletion button to exist")
Log.i(TAG, "cancelDeletion: Trying to click \"Cancel\" bookmarks deletion button")
cancelButton.click()
Log.i(TAG, "cancelDeletion: Clicked \"Cancel\" bookmarks deletion button")
}
fun confirmDeletion() {
Log.i(TAG, "confirmDeletion: Trying to click \"Delete\" bookmarks deletion button")
onView(withText(R.string.delete_browsing_data_prompt_allow))
.inRoot(RootMatchers.isDialog())
.check(matches(isDisplayed()))
.click()
Log.i(TAG, "confirmDeletion: Clicked \"Delete\" bookmarks deletion button")
}
fun clickDeleteInEditModeButton() = deleteInEditModeButton().click()
fun searchBookmarkedItem(bookmarkedItem: String) {
itemWithResId("$packageName:id/mozac_browser_toolbar_edit_url_view").also {
it.waitForExists(waitingTime)
it.setText(bookmarkedItem)
}
mDevice.waitForWindowUpdate(packageName, waitingTimeShort)
fun clickDeleteInEditModeButton() {
Log.i(TAG, "clickDeleteInEditModeButton: Trying to click delete bookmarks button while in edit mode")
deleteInEditModeButton().click()
Log.i(TAG, "clickDeleteInEditModeButton: Clicked delete bookmarks button while in edit mode")
}
fun verifySearchedBookmarkExists(bookmarkUrl: String, exists: Boolean = true) =
assertUIObjectExists(itemContainingText(bookmarkUrl), exists = exists)
fun dismissBookmarksSearchBar() = mDevice.pressBack()
class Transition {
fun closeMenu(interact: HomeScreenRobot.() -> Unit): Transition {
Log.i(TAG, "closeMenu: Trying to click close bookmarks section button")
closeButton().click()
Log.i(TAG, "closeMenu: Clicked close bookmarks section button")
HomeScreenRobot().interact()
return Transition()
@ -271,35 +401,45 @@ class BookmarksRobot {
fun openThreeDotMenu(bookmark: String, interact: ThreeDotMenuBookmarksRobot.() -> Unit): ThreeDotMenuBookmarksRobot.Transition {
mDevice.waitNotNull(Until.findObject(res("$packageName:id/overflow_menu")))
Log.i(TAG, "openThreeDotMenu: Trying to click three dot button for bookmark item: $bookmark")
threeDotMenu(bookmark).click()
Log.i(TAG, "openThreeDotMenu: Clicked three dot button for bookmark item: $bookmark")
ThreeDotMenuBookmarksRobot().interact()
return ThreeDotMenuBookmarksRobot.Transition()
}
fun clickSingInToSyncButton(interact: SettingsTurnOnSyncRobot.() -> Unit): SettingsTurnOnSyncRobot.Transition {
Log.i(TAG, "clickSingInToSyncButton: Trying to click sign in to sync button")
syncSignInButton().click()
Log.i(TAG, "clickSingInToSyncButton: Clicked sign in to sync button")
SettingsTurnOnSyncRobot().interact()
return SettingsTurnOnSyncRobot.Transition()
}
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "goBack: Trying to click go back button")
goBackButton().click()
Log.i(TAG, "goBack: Clicked go back button")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun goBackToBrowserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "goBackToBrowserScreen: Trying to click go back button")
goBackButton().click()
Log.i(TAG, "goBackToBrowserScreen: Clicked go back button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun closeEditBookmarkSection(interact: BookmarksRobot.() -> Unit): Transition {
Log.i(TAG, "goBackToBrowserScreen: Trying to click go back button")
goBackButton().click()
Log.i(TAG, "goBackToBrowserScreen: Clicked go back button")
BookmarksRobot().interact()
return Transition()
@ -308,8 +448,12 @@ class BookmarksRobot {
fun openBookmarkWithTitle(bookmarkTitle: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
itemWithResIdAndText("$packageName:id/title", bookmarkTitle)
.also {
Log.i(TAG, "openBookmarkWithTitle: Waiting for $waitingTime ms for bookmark with title: $bookmarkTitle")
it.waitForExists(waitingTime)
Log.i(TAG, "openBookmarkWithTitle: Waited for $waitingTime ms for bookmark with title: $bookmarkTitle")
Log.i(TAG, "openBookmarkWithTitle: Trying to click bookmark with title: $bookmarkTitle and wait for $waitingTimeShort ms for a new window")
it.clickAndWaitForNewWindow(waitingTimeShort)
Log.i(TAG, "openBookmarkWithTitle: Clicked bookmark with title: $bookmarkTitle and waited for $waitingTimeShort ms for a new window")
}
BrowserRobot().interact()
@ -317,7 +461,9 @@ class BookmarksRobot {
}
fun clickSearchButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
Log.i(TAG, "clickSearchButton: Trying to click search bookmarks button")
itemWithResId("$packageName:id/bookmark_search").click()
Log.i(TAG, "clickSearchButton: Clicked search bookmarks button")
SearchRobot().interact()
return SearchRobot.Transition()
@ -375,92 +521,3 @@ private fun saveBookmarkButton() = onView(withId(R.id.save_bookmark_button))
private fun deleteInEditModeButton() = onView(withId(R.id.delete_bookmark_button))
private fun syncSignInButton() = onView(withId(R.id.bookmark_folders_sign_in))
private fun assertBookmarksView() {
onView(
allOf(
withText("Bookmarks"),
withParent(withId(R.id.navigationToolbar)),
),
)
.check(matches(isDisplayed()))
}
private fun assertAddFolderButton() =
addFolderButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertCloseButton() = closeButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertEmptyBookmarksList() =
onView(withId(R.id.bookmarks_empty_view)).check(matches(withText("No bookmarks here")))
private fun assertBookmarkFolderIsNotCreated(title: String) {
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/bookmarks_wrapper"),
).waitForExists(waitingTime)
assertUIObjectExists(itemContainingText(title), exists = false)
}
private fun assertBookmarkFavicon(forUrl: Uri) = bookmarkFavicon(forUrl.toString()).check(
matches(
withEffectiveVisibility(
ViewMatchers.Visibility.VISIBLE,
),
),
)
private fun assertBookmarkURL(expectedURL: String) =
bookmarkURL(expectedURL).check(matches(isDisplayed()))
private fun assertFolderTitle(expectedTitle: String) =
onView(withText(expectedTitle)).check(matches(isDisplayed()))
private fun assertBookmarkTitle(expectedTitle: String) =
onView(withText(expectedTitle)).check(matches(isDisplayed()))
private fun assertBookmarkIsDeleted(expectedTitle: String) {
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/bookmarks_wrapper"),
).waitForExists(waitingTime)
assertUIObjectExists(
itemWithResIdContainingText(
"$packageName:id/title",
expectedTitle,
),
exists = false,
)
}
private fun assertUndoDeleteSnackBarButton() =
snackBarUndoButton().check(matches(withText("UNDO")))
private fun assertSnackBarText(text: String) =
snackBarText().check(matches(withText(containsString(text))))
private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean) =
assertEquals(
isExpectedToBeVisible,
mDevice
.executeShellCommand("dumpsys input_method | grep mInputShown")
.contains("mInputShown=true"),
)
private fun assertShareOverlay() =
onView(withId(R.id.shareWrapper)).check(matches(isDisplayed()))
private fun assertShareBookmarkTitle() =
onView(withId(R.id.share_tab_title)).check(matches(isDisplayed()))
private fun assertShareBookmarkFavicon() =
onView(withId(R.id.share_tab_favicon)).check(matches(isDisplayed()))
private fun assertShareBookmarkUrl() =
onView(withId(R.id.share_tab_url)).check(matches(isDisplayed()))
private fun assertDeleteFolderConfirmationMessage() =
onView(withText(R.string.bookmark_delete_folder_confirmation_dialog))
.inRoot(RootMatchers.isDialog())
.check(matches(isDisplayed()))

@ -15,17 +15,14 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.uiautomator.By
import androidx.test.uiautomator.By.text
@ -74,7 +71,9 @@ class BrowserRobot {
fun verifyCurrentPrivateSession(context: Context) {
val selectedTab = context.components.core.store.state.selectedTab
Log.i(TAG, "verifyCurrentPrivateSession: Trying to verify that current browsing session is private")
assertTrue("Current session is private", selectedTab?.content?.private ?: false)
Log.i(TAG, "verifyCurrentPrivateSession: Verified that current browsing session is private")
}
fun verifyUrl(url: String) {
@ -165,11 +164,6 @@ class BrowserRobot {
),
)
fun verifySnackBarText(expectedText: String) {
mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(expectedText)))
assertUIObjectExists(itemContainingText(expectedText))
}
fun verifyContextMenuForLocalHostLinks(containsURL: Uri) {
// If the link is directing to another local asset the "Download link" option is not available
// If the link is not re-directing to an external app the "Open link in external app" option is not available
@ -234,11 +228,11 @@ class BrowserRobot {
fun verifyNavURLBarHidden() = assertUIObjectIsGone(navURLBar())
fun verifySecureConnectionLockIcon() =
onView(withId(R.id.mozac_browser_toolbar_security_indicator))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
fun verifyMenuButton() = threeDotButton().check(matches(isDisplayed()))
fun verifyMenuButton() {
Log.i(TAG, "verifyMenuButton: Trying to verify main menu button is displayed")
threeDotButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyMenuButton: Verified main menu button is displayed")
}
fun verifyNoLinkImageContextMenuItems(containsURL: Uri) {
mDevice.waitNotNull(Until.findObject(By.textContains(containsURL.toString())))
@ -256,13 +250,10 @@ class BrowserRobot {
fun verifyNotificationDotOnMainMenu() =
assertUIObjectExists(itemWithResId("$packageName:id/notification_dot"))
fun verifyHomeScreenButton() =
homeScreenButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
fun verifySearchBar() = assertUIObjectExists(searchBar())
fun dismissContentContextMenu() {
Log.i(TAG, "dismissContentContextMenu: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "dismissContentContextMenu: Clicked device back button")
assertUIObjectExists(itemWithResId("$packageName:id/engineView"))
}
@ -292,7 +283,9 @@ class BrowserRobot {
fun clickSubmitLoginButton() {
clickPageObject(itemWithResId("submit"))
assertUIObjectIsGone(itemWithResId("submit"))
Log.i(TAG, "clickSubmitLoginButton: Waiting for device to be idle for $waitingTimeLong ms")
mDevice.waitForIdle(waitingTimeLong)
Log.i(TAG, "clickSubmitLoginButton: Waited for device to be idle for $waitingTimeLong ms")
}
fun enterPassword(password: String) {
@ -334,10 +327,16 @@ class BrowserRobot {
fun swipeNavBarRight(tabUrl: String) {
// failing to swipe on Firebase sometimes, so it tries again
try {
Log.i(TAG, "swipeNavBarRight: Try block")
Log.i(TAG, "swipeNavBarRight: Trying to perform swipe right action on navigation toolbar")
navURLBar().swipeRight(2)
Log.i(TAG, "swipeNavBarRight: Performed swipe right action on navigation toolbar")
assertUIObjectIsGone(itemWithText(tabUrl))
} catch (e: AssertionError) {
Log.i(TAG, "swipeNavBarRight: AssertionError caught, executing fallback methods")
Log.i(TAG, "swipeNavBarRight: Trying to perform swipe right action on navigation toolbar")
navURLBar().swipeRight(2)
Log.i(TAG, "swipeNavBarRight: Performed swipe right action on navigation toolbar")
assertUIObjectIsGone(itemWithText(tabUrl))
}
}
@ -345,10 +344,16 @@ class BrowserRobot {
fun swipeNavBarLeft(tabUrl: String) {
// failing to swipe on Firebase sometimes, so it tries again
try {
Log.i(TAG, "swipeNavBarLeft: Try block")
Log.i(TAG, "swipeNavBarLeft: Trying to perform swipe left action on navigation toolbar")
navURLBar().swipeLeft(2)
Log.i(TAG, "swipeNavBarLeft: Performed swipe left action on navigation toolbar")
assertUIObjectIsGone(itemWithText(tabUrl))
} catch (e: AssertionError) {
Log.i(TAG, "swipeNavBarLeft: AssertionError caught, executing fallback methods")
Log.i(TAG, "swipeNavBarLeft: Trying to perform swipe left action on navigation toolbar")
navURLBar().swipeLeft(2)
Log.i(TAG, "swipeNavBarLeft: Performed swipe left action on navigation toolbar")
assertUIObjectIsGone(itemWithText(tabUrl))
}
}
@ -356,11 +361,15 @@ class BrowserRobot {
fun clickSuggestedLoginsButton() {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "clickSuggestedLoginsButton: Started try #$i")
mDevice.waitForObjects(suggestedLogins())
Log.i(TAG, "clickSuggestedLoginsButton: Trying to click suggested logins button")
suggestedLogins().click()
Log.i(TAG, "clickSuggestedLoginsButton: Clicked suggested logins button")
mDevice.waitForObjects(suggestedLogins())
break
} catch (e: UiObjectNotFoundException) {
Log.i(TAG, "clickSuggestedLoginsButton: UiObjectNotFoundException caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -370,8 +379,11 @@ class BrowserRobot {
}
}
fun setTextForApartmentTextBox(apartment: String) =
fun setTextForApartmentTextBox(apartment: String) {
Log.i(TAG, "setTextForApartmentTextBox: Trying to set the text for the apartment text box to: $apartment")
itemWithResId("apartment").setText(apartment)
Log.i(TAG, "setTextForApartmentTextBox: The text for the apartment text box was set to: $apartment")
}
fun clearAddressForm() {
clearTextFieldItem(itemWithResId("streetAddress"))
@ -385,11 +397,15 @@ class BrowserRobot {
fun clickSelectAddressButton() {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "clickSelectAddressButton: Started try #$i")
assertUIObjectExists(selectAddressButton())
Log.i(TAG, "clickSelectAddressButton: Trying to click the select address button and wait for $waitingTime ms for a new window")
selectAddressButton().clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "clickSelectAddressButton: Clicked the select address button and waited for $waitingTime ms for a new window")
break
} catch (e: AssertionError) {
Log.i(TAG, "clickSelectAddressButton: AssertionError caught, executing fallback methods")
// Retrying to trigger the prompt, in case we hit https://bugzilla.mozilla.org/show_bug.cgi?id=1816869
// This should be removed when the bug is fixed.
if (i == RETRY_COUNT) {
@ -404,28 +420,52 @@ class BrowserRobot {
fun verifySelectAddressButtonExists(exists: Boolean) = assertUIObjectExists(selectAddressButton(), exists = exists)
fun changeCreditCardExpiryDate(expiryDate: String) =
fun changeCreditCardExpiryDate(expiryDate: String) {
Log.i(TAG, "changeCreditCardExpiryDate: Trying to set credit card expiry date to: $expiryDate")
itemWithResId("expiryMonthAndYear").setText(expiryDate)
Log.i(TAG, "changeCreditCardExpiryDate: Credit card expiry date was set to: $expiryDate")
}
fun clickCreditCardNumberTextBox() {
Log.i(TAG, "clickCreditCardNumberTextBox: Waiting for $waitingTime ms until finding the credit card number text box")
mDevice.wait(Until.findObject(By.res("cardNumber")), waitingTime)
Log.i(TAG, "clickCreditCardNumberTextBox: Waited for $waitingTime ms until the credit card number text box was found")
Log.i(TAG, "clickCreditCardNumberTextBox: Trying to click the credit card number text box")
mDevice.findObject(By.res("cardNumber")).click()
Log.i(TAG, "clickCreditCardNumberTextBox: Clicked the credit card number text box")
Log.i(TAG, "clickCreditCardNumberTextBox: Waiting for $waitingTimeShort ms for $appName window to be updated")
mDevice.waitForWindowUpdate(appName, waitingTimeShort)
Log.i(TAG, "clickCreditCardNumberTextBox: Waited for $waitingTimeShort ms for $appName window to be updated")
}
fun clickCreditCardFormSubmitButton() =
fun clickCreditCardFormSubmitButton() {
Log.i(TAG, "clickCreditCardFormSubmitButton: Trying to click the credit card form submit button and wait for $waitingTime ms for a new window")
itemWithResId("submit").clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "clickCreditCardFormSubmitButton: Clicked the credit card form submit button and waited for $waitingTime ms for a new window")
}
fun fillAndSaveCreditCard(cardNumber: String, cardName: String, expiryMonthAndYear: String) {
Log.i(TAG, "fillAndSaveCreditCard: Tying to set credit card number to: $cardNumber")
itemWithResId("cardNumber").setText(cardNumber)
Log.i(TAG, "fillAndSaveCreditCard: Credit card number was set to: $cardNumber")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "fillAndSaveCreditCard: Trying to set credit card name to: $cardName")
itemWithResId("nameOnCard").setText(cardName)
Log.i(TAG, "fillAndSaveCreditCard: Credit card name was set to: $cardName")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "fillAndSaveCreditCard: Trying to set credit card expiry month and year to: $expiryMonthAndYear")
itemWithResId("expiryMonthAndYear").setText(expiryMonthAndYear)
Log.i(TAG, "fillAndSaveCreditCard: Credit card expiry month and year were set to: $expiryMonthAndYear")
Log.i(TAG, "fillAndSaveCreditCard: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "fillAndSaveCreditCard: Waited for device to be idle for $waitingTime ms")
Log.i(TAG, "fillAndSaveCreditCard: Trying to click the credit card form submit button and wait for $waitingTime ms for a new window")
itemWithResId("submit").clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "fillAndSaveCreditCard: Clicked the credit card form submit button and waited for $waitingTime ms for a new window")
waitForPageToLoad()
Log.i(TAG, "fillAndSaveCreditCard: Waiting for $waitingTime ms for $packageName window to be updated")
mDevice.waitForWindowUpdate(packageName, waitingTime)
Log.i(TAG, "fillAndSaveCreditCard: Waited for $waitingTime ms for $packageName window to be updated")
}
fun verifyUpdateOrSaveCreditCardPromptExists(exists: Boolean) =
@ -449,10 +489,9 @@ class BrowserRobot {
}
fun verifySuggestedUserName(userName: String) {
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_feature_login_multiselect_expand"),
).waitForExists(waitingTime)
Log.i(TAG, "verifySuggestedUserName: Waiting for $waitingTime ms for suggested logins fragment to exist")
itemWithResId("$packageName:id/mozac_feature_login_multiselect_expand").waitForExists(waitingTime)
Log.i(TAG, "verifySuggestedUserName: Waited for $waitingTime ms for suggested logins fragment to exist")
assertUIObjectExists(itemContainingText(userName))
}
@ -460,6 +499,7 @@ class BrowserRobot {
// Sometimes the assertion of the pre-filled logins fails so we are re-trying after refreshing the page
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifyPrefilledLoginCredentials: Started try #$i")
mDevice.waitForObjects(itemWithResId("username"))
assertItemTextEquals(itemWithResId("username"), expectedText = userName, isEqual = credentialsArePrefilled)
mDevice.waitForObjects(itemWithResId("password"))
@ -467,6 +507,7 @@ class BrowserRobot {
break
} catch (e: AssertionError) {
Log.i(TAG, "verifyPrefilledLoginCredentials: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -499,23 +540,6 @@ class BrowserRobot {
assertUIObjectExists(itemWithResIdAndText("cardNumber", creditCardNumber))
}
fun verifyPrefilledPWALoginCredentials(userName: String, shortcutTitle: String) {
mDevice.waitForIdle(waitingTime)
var currentTries = 0
while (currentTries++ < 3) {
try {
assertUIObjectExists(itemWithResId("submit"))
itemWithResId("submit").click()
assertItemTextEquals(itemWithResId("username"), expectedText = userName)
break
} catch (e: AssertionError) {
addToHomeScreen {
}.searchAndOpenHomeScreenShortcut(shortcutTitle) {}
}
}
}
fun verifySaveLoginPromptIsDisplayed() =
assertUIObjectExists(
itemWithResId("$packageName:id/feature_prompt_login_fragment"),
@ -530,14 +554,16 @@ class BrowserRobot {
fun verifyTrackingProtectionWebContent(state: String) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifyTrackingProtectionWebContent: Started try #$i")
assertUIObjectExists(itemContainingText(state))
break
} catch (e: AssertionError) {
Log.i(TAG, "verifyTrackingProtectionWebContent: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
Log.e("TestLog", "On try $i, trackers are not: $state")
Log.e(TAG, "On try $i, trackers are not: $state")
navigationToolbar {
}.openThreeDotMenu {
@ -550,38 +576,61 @@ class BrowserRobot {
fun verifyCookiesProtectionHintIsDisplayed(composeTestRule: HomeActivityComposeTestRule, isDisplayed: Boolean) {
if (isDisplayed) {
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Trying to verify that the total cookie protection message is displayed")
composeTestRule.onNodeWithTag("tcp_cfr.message").assertIsDisplayed()
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Verified total cookie protection message is displayed")
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Trying to verify that the total cookie protection learn more link is displayed")
composeTestRule.onNodeWithTag("tcp_cfr.action").assertIsDisplayed()
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Verified that the total cookie protection learn more link is displayed")
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Trying to verify that the total cookie protection dismiss button is displayed")
composeTestRule.onNodeWithTag("cfr.dismiss").assertIsDisplayed()
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Verified total cookie protection dismiss button is displayed")
} else {
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Trying to verify that the total cookie protection message does not exist")
composeTestRule.onNodeWithTag("tcp_cfr.message").assertDoesNotExist()
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Verified that the total cookie protection message does not exist")
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Trying to verify that the total cookie protection learn more link does not exist")
composeTestRule.onNodeWithTag("tcp_cfr.action").assertDoesNotExist()
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Verified total cookie protection learn more link does not exist")
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Trying to verify that the total cookie protection dismiss button does not exist")
composeTestRule.onNodeWithTag("cfr.dismiss").assertDoesNotExist()
Log.i(TAG, "verifyCookiesProtectionHintIsDisplayed: Verified that the total cookie protection dismiss button does not exist")
}
}
fun clickTCPCFRLearnMore(composeTestRule: HomeActivityComposeTestRule) {
Log.i(TAG, "clickTCPCFRLearnMore: Trying to click the total cookie protection learn more link")
composeTestRule.onNodeWithTag("tcp_cfr.action").performClick()
Log.i(TAG, "clickTCPCFRLearnMore: Clicked total cookie protection learn more link")
}
fun dismissTCPCFRPopup(composeTestRule: HomeActivityComposeTestRule) {
Log.i(TAG, "dismissTCPCFRPopup: Trying to click the total cookie protection dismiss button")
composeTestRule.onNodeWithTag("cfr.dismiss").performClick()
Log.i(TAG, "dismissTCPCFRPopup: Clicked total cookie protection dismiss button")
}
fun verifyShouldShowCFRTCP(shouldShow: Boolean, settings: Settings) {
if (shouldShow) {
Log.i(TAG, "verifyShouldShowCFRTCP: Trying to verify that the TCP CFR should be shown")
assertTrue(settings.shouldShowTotalCookieProtectionCFR)
Log.i(TAG, "verifyShouldShowCFRTCP: Verified that the TCP CFR should be shown")
} else {
Log.i(TAG, "verifyShouldShowCFRTCP: Trying to verify that the TCP CFR should not be shown")
assertFalse(settings.shouldShowTotalCookieProtectionCFR)
Log.i(TAG, "verifyShouldShowCFRTCP: Verified that the TCP CFR should not be shown")
}
}
fun selectTime(hour: Int, minute: Int): ViewInteraction =
fun selectTime(hour: Int, minute: Int) {
Log.i(TAG, "selectTime: Trying to select time picker hour: $hour and minute: $minute")
onView(
isAssignableFrom(TimePicker::class.java),
).inRoot(
isDialog(),
).perform(PickerActions.setTime(hour, minute))
Log.i(TAG, "selectTime: Selected time picker hour: $hour and minute: $minute")
}
fun verifySelectedDate() {
val currentDate = LocalDate.now()
@ -591,11 +640,13 @@ class BrowserRobot {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifySelectedDate: Started try #$i")
assertUIObjectExists(itemContainingText("Selected date is: $currentDate"))
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected time isn't displayed ${e.localizedMessage}")
Log.i(TAG, "verifySelectedDate: AssertionError caught, executing fallback methods")
Log.e(TAG, "Selected time isn't displayed ${e.localizedMessage}")
clickPageObject(itemWithResId("calendar"))
clickPageObject(itemWithDescription("$currentDay $currentMonth $currentYear"))
@ -618,11 +669,13 @@ class BrowserRobot {
fun verifySelectedTime(hour: Int, minute: Int) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifySelectedTime: Started try #$i")
assertUIObjectExists(itemContainingText("Selected time is: $hour:$minute"))
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected time isn't displayed ${e.localizedMessage}")
Log.i(TAG, "verifySelectedTime: AssertionError caught, executing fallback methods")
Log.e(TAG, "Selected time isn't displayed ${e.localizedMessage}")
clickPageObject(itemWithResId("clock"))
clickPageObject(itemContainingText("CLEAR"))
@ -638,11 +691,13 @@ class BrowserRobot {
fun verifySelectedColor(hexValue: String) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifySelectedColor: Started try #$i")
assertUIObjectExists(itemContainingText("Selected color is: $hexValue"))
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected color isn't displayed ${e.localizedMessage}")
Log.i(TAG, "verifySelectedColor: AssertionError caught, executing fallback methods")
Log.e(TAG, "Selected color isn't displayed ${e.localizedMessage}")
clickPageObject(itemWithResId("colorPicker"))
clickPageObject(itemWithDescription(hexValue))
@ -657,17 +712,20 @@ class BrowserRobot {
fun verifySelectedDropDownOption(optionName: String) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifySelectedDropDownOption: Started try #$i")
Log.i(TAG, "verifySelectedDropDownOption: Waiting for $waitingTime ms for \"Submit drop down option\" form button to exist")
mDevice.findObject(
UiSelector()
.textContains("Submit drop down option")
.resourceId("submitOption"),
).waitForExists(waitingTime)
Log.i(TAG, "verifySelectedDropDownOption: Waited for $waitingTime ms for \"Submit drop down option\" form button to exist")
assertUIObjectExists(itemContainingText("Selected option is: $optionName"))
break
} catch (e: AssertionError) {
Log.e("TestLog", "Selected option isn't displayed ${e.localizedMessage}")
Log.i(TAG, "verifySelectedDropDownOption: AssertionError caught, executing fallback methods")
Log.e(TAG, "Selected option isn't displayed ${e.localizedMessage}")
clickPageObject(itemWithResId("dropDown"))
clickPageObject(itemContainingText(optionName))
@ -686,16 +744,18 @@ class BrowserRobot {
fun verifyCookieBannerExists(exists: Boolean) {
for (i in 1..RETRY_COUNT) {
Log.i(TAG, "verifyCookieBannerExists: For loop: $i")
Log.i(TAG, "verifyCookieBannerExists: Started try #$i")
try {
// Wait for the blocker to kick-in and make the cookie banner disappear
Log.i(TAG, "verifyCookieBannerExists: Waiting for $waitingTime ms for cookie banner to be gone")
itemWithResId("CybotCookiebotDialog").waitUntilGone(waitingTime)
Log.i(TAG, "verifyCookieBannerExists: Waiting for window update")
Log.i(TAG, "verifyCookieBannerExists: Waited for $waitingTime ms for cookie banner to be gone")
// Assert that the blocker properly dismissed the cookie banner
assertUIObjectExists(itemWithResId("CybotCookiebotDialog"), exists = exists)
break
} catch (e: AssertionError) {
Log.i(TAG, "verifyCookieBannerExists: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
}
@ -724,6 +784,7 @@ class BrowserRobot {
fun verifyPrivateBrowsingOpenLinkInAnotherAppPrompt(url: String, pageObject: UiObject) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifyPrivateBrowsingOpenLinkInAnotherAppPrompt: Started try #$i")
assertUIObjectExists(
itemContainingText(
getStringResource(R.string.mozac_feature_applinks_confirm_dialog_title),
@ -733,6 +794,7 @@ class BrowserRobot {
break
} catch (e: AssertionError) {
Log.i(TAG, "verifyPrivateBrowsingOpenLinkInAnotherAppPrompt: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -774,6 +836,7 @@ class BrowserRobot {
fun verifyOpenLinksInAppsCFRExists(exists: Boolean) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "verifyOpenLinksInAppsCFRExists: Started try #$i")
assertUIObjectExists(
itemWithResId("$packageName:id/banner_container"),
itemWithResIdContainingText(
@ -791,6 +854,7 @@ class BrowserRobot {
exists = exists,
)
} catch (e: AssertionError) {
Log.i(TAG, "verifyOpenLinksInAppsCFRExists: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -817,11 +881,14 @@ class BrowserRobot {
fun verifyHomeScreenSurveyCloseButton() =
assertUIObjectExists(itemWithDescription("Close"))
fun clickOpenLinksInAppsDismissCFRButton() =
fun clickOpenLinksInAppsDismissCFRButton() {
Log.i(TAG, "clickOpenLinksInAppsDismissCFRButton: Trying to click the open links in apps banner \"Dismiss\" button")
itemWithResIdContainingText(
"$packageName:id/dismiss",
getStringResource(R.string.open_in_app_cfr_negative_button_text),
).click()
Log.i(TAG, "clickOpenLinksInAppsDismissCFRButton: Clicked the open links in apps banner \"Dismiss\" button")
}
fun clickTakeSurveyButton() {
val button = mDevice.findObject(
@ -846,7 +913,11 @@ class BrowserRobot {
button.click()
}
fun longClickToolbar() = onView(withId(R.id.mozac_browser_toolbar_url_view)).perform(longClick())
fun longClickToolbar() {
Log.i(TAG, "longClickToolbar: Trying to long click the toolbar")
onView(withId(R.id.mozac_browser_toolbar_url_view)).perform(longClick())
Log.i(TAG, "longClickToolbar: Long clicked the toolbar")
}
fun verifyDownloadPromptIsDismissed() =
assertUIObjectExists(
@ -876,27 +947,31 @@ class BrowserRobot {
}
fun clickStayInPrivateBrowsingPromptButton() {
Log.i(TAG, "clickStayInPrivateBrowsingPromptButton: Trying to click the \"STAY IN PRIVATE BROWSING\" prompt button")
itemWithResIdContainingText(
"$packageName:id/deny_button",
getStringResource(R.string.mozac_feature_downloads_cancel_active_private_downloads_deny),
).click()
Log.i(TAG, "clickStayInPrivateBrowsingPromptButton: Clicked \"STAY IN PRIVATE BROWSING\" prompt button")
Log.i(TAG, "clickStayInPrivateBrowsingPromptButton: Clicked the \"STAY IN PRIVATE BROWSING\" prompt button")
}
fun clickCancelPrivateDownloadsPromptButton() {
Log.i(TAG, "clickCancelPrivateDownloadsPromptButton: Trying to click the \"CANCEL DOWNLOADS\" prompt button")
itemWithResIdContainingText(
"$packageName:id/accept_button",
getStringResource(R.string.mozac_feature_downloads_cancel_active_downloads_accept),
).click()
Log.i(TAG, "clickCancelPrivateDownloadsPromptButton: Clicked \"CANCEL DOWNLOADS\" prompt button")
Log.i(TAG, "clickCancelPrivateDownloadsPromptButton: Clicked the \"CANCEL DOWNLOADS\" prompt button")
Log.i(TAG, "clickCancelPrivateDownloadsPromptButton: Waiting for $waitingTime ms for $packageName window to be updated")
mDevice.waitForWindowUpdate(packageName, waitingTime)
Log.i(TAG, "clickCancelPrivateDownloadsPromptButton: Waited for $waitingTime ms for $packageName window to be updated")
}
fun fillPdfForm(name: String) {
// Set PDF form text for the text box
Log.i(TAG, "fillPdfForm: Trying to set the text of the PDF form text box to: $name")
itemWithResId("pdfjs_internal_id_10R").setText(name)
Log.i(TAG, "fillPdfForm: Set PDF form text box text to: $name")
Log.i(TAG, "fillPdfForm: PDF form text box text was set to: $name")
mDevice.waitForWindowUpdate(packageName, waitingTime)
if (
!itemWithResId("pdfjs_internal_id_11R").exists() &&
@ -905,18 +980,22 @@ class BrowserRobot {
.contains("mInputShown=true")
) {
// Close the keyboard
Log.i(TAG, "fillPdfForm: Trying to close the keyboard using device back button")
mDevice.pressBack()
Log.i(TAG, "fillPdfForm: Closing the keyboard using device back button")
Log.i(TAG, "fillPdfForm: Closed the keyboard using device back button")
}
// Click PDF form check box
Log.i(TAG, "fillPdfForm: Trying to click the PDF form check box")
itemWithResId("pdfjs_internal_id_11R").click()
Log.i(TAG, "fillPdfForm: Clicked PDF form check box")
}
class Transition {
fun openThreeDotMenu(interact: ThreeDotMenuMainRobot.() -> Unit): ThreeDotMenuMainRobot.Transition {
Log.i(TAG, "openThreeDotMenu: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "openThreeDotMenu: Device was idle for $waitingTime ms")
Log.i(TAG, "openThreeDotMenu: Trying to click the main menu button")
threeDotButton().perform(click())
Log.i(TAG, "openThreeDotMenu: Clicked the main menu button")
@ -926,7 +1005,9 @@ class BrowserRobot {
fun openNavigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition {
clickPageObject(navURLBar())
Log.i(TAG, "openNavigationToolbar: Waiting for $waitingTime ms for for search bar to exist")
searchBar().waitForExists(waitingTime)
Log.i(TAG, "openNavigationToolbar: Waited for $waitingTime ms for for search bar to exist")
NavigationToolbarRobot().interact()
return NavigationToolbarRobot.Transition()
@ -935,6 +1016,7 @@ class BrowserRobot {
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "openTabDrawer: Started try #$i")
mDevice.waitForObjects(
mDevice.findObject(
UiSelector()
@ -942,16 +1024,20 @@ class BrowserRobot {
),
waitingTime,
)
Log.i(TAG, "openTabDrawer: Trying to click the tab counter button")
tabsCounter().click()
Log.i(TAG, "openTabDrawer: Clicked the tab counter button")
assertUIObjectExists(itemWithResId("$packageName:id/new_tab_button"))
break
} catch (e: AssertionError) {
Log.i(TAG, "openTabDrawer: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
Log.i(TAG, "openTabDrawer: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "openTabDrawer: Waited for device to be idle")
}
}
}
@ -965,6 +1051,7 @@ class BrowserRobot {
fun openComposeTabDrawer(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "openComposeTabDrawer: Started try #$i")
mDevice.waitForObjects(
mDevice.findObject(
UiSelector()
@ -972,30 +1059,37 @@ class BrowserRobot {
),
waitingTime,
)
Log.i(TAG, "openComposeTabDrawer: Trying to click the tab counter button")
tabsCounter().click()
Log.i(TAG, "openComposeTabDrawer: Clicked the tab counter button")
Log.i(TAG, "openComposeTabDrawer: Trying to verify the tabs tray exists")
composeTestRule.onNodeWithTag(TabsTrayTestTag.tabsTray).assertExists()
Log.i(TAG, "openComposeTabDrawer: Verified the tabs tray exists")
break
} catch (e: AssertionError) {
Log.i(TAG, "openComposeTabDrawer: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
Log.i(TAG, "openComposeTabDrawer: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "openComposeTabDrawer: Waited for device to be idle")
}
}
}
Log.i(TAG, "openComposeTabDrawer: Trying to verify the tabs tray new tab FAB button exists")
composeTestRule.onNodeWithTag(TabsTrayTestTag.fab).assertExists()
Log.i(TAG, "openComposeTabDrawer: Verified the tabs tray new tab FAB button exists")
ComposeTabDrawerRobot(composeTestRule).interact()
return ComposeTabDrawerRobot.Transition(composeTestRule)
}
fun openNotificationShade(interact: NotificationRobot.() -> Unit): NotificationRobot.Transition {
Log.i(TAG, "openNotificationShade: Trying to open the notification tray")
mDevice.openNotification()
Log.i(TAG, "openNotificationShade: Opened notification tray")
Log.i(TAG, "openNotificationShade: Opened the notification tray")
NotificationRobot().interact()
return NotificationRobot.Transition()
@ -1003,7 +1097,7 @@ class BrowserRobot {
fun goToHomescreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
clickPageObject(itemWithDescription("Home screen"))
Log.i(TAG, "goToHomescreen: Waiting for $waitingTime ms for for home screen layout or jump back in contextual hint to exist")
mDevice.findObject(UiSelector().resourceId("$packageName:id/homeLayout"))
.waitForExists(waitingTime) ||
mDevice.findObject(
@ -1011,6 +1105,7 @@ class BrowserRobot {
getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
),
).waitForExists(waitingTime)
Log.i(TAG, "goToHomescreen: Waited for $waitingTime ms for for home screen layout or jump back in contextual hint to exist")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
@ -1019,6 +1114,7 @@ class BrowserRobot {
fun goToHomescreenWithComposeTopSites(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTopSitesRobot.() -> Unit): ComposeTopSitesRobot.Transition {
clickPageObject(itemWithDescription("Home screen"))
Log.i(TAG, "goToHomescreenWithComposeTopSites: Waiting for $waitingTime ms for for home screen layout or jump back in contextual hint to exist")
mDevice.findObject(UiSelector().resourceId("$packageName:id/homeLayout"))
.waitForExists(waitingTime) ||
mDevice.findObject(
@ -1026,13 +1122,16 @@ class BrowserRobot {
getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
),
).waitForExists(waitingTime)
Log.i(TAG, "goToHomescreenWithComposeTopSites: Waited for $waitingTime ms for for home screen layout or jump back in contextual hint to exist")
ComposeTopSitesRobot(composeTestRule).interact()
return ComposeTopSitesRobot.Transition(composeTestRule)
}
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "goBack: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "goBack: Clicked device back button")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
@ -1040,7 +1139,9 @@ class BrowserRobot {
fun clickTabCrashedCloseButton(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
clickPageObject(itemWithText("Close tab"))
Log.i(TAG, "clickTabCrashedCloseButton: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "clickTabCrashedCloseButton: Waited for device to be idle")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
@ -1126,45 +1227,56 @@ class BrowserRobot {
}
fun openSiteSecuritySheet(interact: SiteSecurityRobot.() -> Unit): SiteSecurityRobot.Transition {
Log.i(TAG, "openSiteSecuritySheet: Waiting for $waitingTime ms for site security toolbar button to exist")
siteSecurityToolbarButton().waitForExists(waitingTime)
Log.i(TAG, "openSiteSecuritySheet: Waited for $waitingTime ms for site security toolbar button to exist")
Log.i(TAG, "openSiteSecuritySheet: Trying to click the site security toolbar button and wait for $waitingTime ms for a new window")
siteSecurityToolbarButton().clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "openSiteSecuritySheet: Clicked the site security toolbar button and waited for $waitingTime ms for a new window")
SiteSecurityRobot().interact()
return SiteSecurityRobot.Transition()
}
fun clickManageAddressButton(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
Log.i(TAG, "clickManageAddressButton: Trying to click the manage address button and wait for $waitingTime ms for a new window")
itemWithResId("$packageName:id/manage_addresses")
.clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "clickManageAddressButton: Clicked the manage address button and waited for $waitingTime ms for a new window")
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
}
fun clickManageCreditCardsButton(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
Log.i(TAG, "clickManageCreditCardsButton: Trying to click the manage credit cards button and wait for $waitingTime ms for a new window")
itemWithResId("$packageName:id/manage_credit_cards")
.clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "clickManageCreditCardsButton: Clicked the manage credit cards button and waited for $waitingTime ms for a new window")
SettingsSubMenuAutofillRobot().interact()
return SettingsSubMenuAutofillRobot.Transition()
}
fun clickOpenLinksInAppsGoToSettingsCFRButton(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Trying to click the \"Go to settings\" open links in apps CFR button and wait for $waitingTime ms for a new window")
itemWithResIdContainingText(
"$packageName:id/action",
getStringResource(R.string.open_in_app_cfr_positive_button_text),
).clickAndWaitForNewWindow(waitingTime)
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Clicked \"Go to settings\" open links in apps CFR button")
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Clicked the \"Go to settings\" open links in apps CFR button and waited for $waitingTime ms for a new window")
SettingsRobot().interact()
return SettingsRobot.Transition()
}
fun clickDownloadPDFButton(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
Log.i(TAG, "clickDownloadPDFButton: Trying to click the download PDF button")
itemWithResIdContainingText(
"download",
"Download",
).click()
Log.i(TAG, "clickDownloadPDFButton: Clicked the download PDF button")
DownloadRobot().interact()
return DownloadRobot.Transition()
@ -1205,8 +1317,6 @@ private fun navURLBar() = itemWithResId("$packageName:id/toolbar")
private fun searchBar() = itemWithResId("$packageName:id/mozac_browser_toolbar_url_view")
fun homeScreenButton() = onView(withContentDescription(R.string.browser_toolbar_home))
private fun threeDotButton() = onView(withContentDescription("Menu"))
private fun tabsCounter() =
@ -1224,26 +1334,25 @@ private fun siteSecurityToolbarButton() =
fun clickPageObject(item: UiObject) {
for (i in 1..RETRY_COUNT) {
Log.i(TAG, "clickPageObject: For loop i = $i")
try {
Log.i(TAG, "clickPageObject: Try block")
Log.i(TAG, "clickPageObject: Started try #$i")
Log.i(TAG, "clickPageObject: Waiting for $waitingTime ms for ${item.selector} to exist")
item.waitForExists(waitingTime)
Log.i(TAG, "clickPageObject: Waited for $waitingTime ms for ${item.selector} to exist")
Log.i(TAG, "clickPageObject: Trying to click ${item.selector}")
item.click()
Log.i(TAG, "clickPageObject: Clicked ${item.selector}")
break
} catch (e: UiObjectNotFoundException) {
Log.i(TAG, "clickPageObject: Catch block")
Log.i(TAG, "clickPageObject: UiObjectNotFoundException caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
browserScreen {
Log.i(TAG, "clickPageObject: Browser screen")
}.openThreeDotMenu {
Log.i(TAG, "clickPageObject: Opened main menu")
}.refreshPage {
waitForPageToLoad()
Log.i(TAG, "clickPageObject: Page refreshed, progress bar is gone")
}
}
}
@ -1253,11 +1362,17 @@ fun clickPageObject(item: UiObject) {
fun longClickPageObject(item: UiObject) {
for (i in 1..RETRY_COUNT) {
try {
Log.i(TAG, "longClickPageObject: Started try #$i")
Log.i(TAG, "longClickPageObject: Waiting for $waitingTime ms for ${item.selector} to exist")
item.waitForExists(waitingTime)
Log.i(TAG, "longClickPageObject: Waited for $waitingTime ms for ${item.selector} to exist")
Log.i(TAG, "longClickPageObject: Trying to long click ${item.selector}")
item.longClick()
Log.i(TAG, "longClickPageObject: Long clicked ${item.selector}")
break
} catch (e: UiObjectNotFoundException) {
Log.i(TAG, "longClickPageObject: UiObjectNotFoundException caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -1276,20 +1391,30 @@ fun clickContextMenuItem(item: String) {
Until.findObject(text(item)),
waitingTime,
)
Log.i(TAG, "clickContextMenuItem: Trying to click context menu item: $item")
mDevice.findObject(text(item)).click()
Log.i(TAG, "clickContextMenuItem: Clicked context menu item: $item")
}
fun setPageObjectText(webPageItem: UiObject, text: String) {
for (i in 1..RETRY_COUNT) {
Log.i(TAG, "setPageObjectText: Started try #$i")
try {
webPageItem.also {
Log.i(TAG, "setPageObjectText: Waiting for $waitingTime ms for ${webPageItem.selector} to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "setPageObjectText: Waited for $waitingTime ms for ${webPageItem.selector} to exist")
Log.i(TAG, "setPageObjectText: Trying to clear ${webPageItem.selector} text field")
it.clearTextField()
Log.i(TAG, "setPageObjectText: Cleared ${webPageItem.selector} text field")
Log.i(TAG, "setPageObjectText: Trying to set ${webPageItem.selector} text to $text")
it.text = text
Log.i(TAG, "setPageObjectText: ${webPageItem.selector} text was set to $text")
}
break
} catch (e: UiObjectNotFoundException) {
Log.i(TAG, "setPageObjectText: UiObjectNotFoundException caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -1304,8 +1429,12 @@ fun setPageObjectText(webPageItem: UiObject, text: String) {
}
fun clearTextFieldItem(item: UiObject) {
Log.i(TAG, "clearTextFieldItem: Waiting for $waitingTime ms for ${item.selector} to exist")
item.waitForExists(waitingTime)
Log.i(TAG, "clearTextFieldItem: Waited for $waitingTime ms for ${item.selector} to exist")
Log.i(TAG, "clearTextFieldItem: Trying to clear ${item.selector} text field")
item.clearTextField()
Log.i(TAG, "clearTextFieldItem: Cleared ${item.selector} text field")
}
// Context menu items

@ -4,6 +4,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasText
@ -17,10 +18,10 @@ import androidx.test.espresso.action.ViewActions.pressImeActionButton
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.MatcherHelper.assertItemTextEquals
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
@ -46,29 +47,45 @@ class CollectionRobot {
itemWithResId("$packageName:id/collections_list"),
)
fun clickAddNewCollection() = addNewCollectionButton().click()
fun clickAddNewCollection() {
Log.i(TAG, "clickAddNewCollection: Trying to click the add new collection button")
addNewCollectionButton().click()
Log.i(TAG, "clickAddNewCollection: Clicked the add new collection button")
}
fun verifyCollectionNameTextField() = assertUIObjectExists(mainMenuEditCollectionNameField())
// names a collection saved from tab drawer
fun typeCollectionNameAndSave(collectionName: String) {
Log.i(TAG, "typeCollectionNameAndSave: Trying to set collection name text field to: $collectionName")
collectionNameTextField().text = collectionName
addCollectionButtonPanel.waitForExists(waitingTime)
addCollectionOkButton.click()
Log.i(TAG, "typeCollectionNameAndSave: Collection name text field set to: $collectionName")
Log.i(TAG, "typeCollectionNameAndSave: Waiting for $waitingTime ms for add collection button panel to exist")
addCollectionButtonPanel().waitForExists(waitingTime)
Log.i(TAG, "typeCollectionNameAndSave: Waited for $waitingTime ms for add collection button panel to exist")
Log.i(TAG, "typeCollectionNameAndSave: Trying to click \"OK\" panel button")
addCollectionOkButton().click()
Log.i(TAG, "typeCollectionNameAndSave: Clicked \"OK\" panel button")
}
fun verifyTabsSelectedCounterText(numOfTabs: Int) {
Log.i(TAG, "verifyTabsSelectedCounterText: Waiting for $waitingTime ms for \"Select tabs to save\" prompt to be gone")
itemWithText("Select tabs to save").waitUntilGone(waitingTime)
Log.i(TAG, "verifyTabsSelectedCounterText: Waited for $waitingTime ms for \"Select tabs to save\" prompt to be gone")
val tabsCounter = mDevice.findObject(UiSelector().resourceId("$packageName:id/bottom_bar_text"))
Log.i(TAG, "verifyTabsSelectedCounterText: Trying to assert that number of tabs selected is: $numOfTabs")
when (numOfTabs) {
1 -> assertItemTextEquals(tabsCounter, expectedText = "$numOfTabs tab selected")
2 -> assertItemTextEquals(tabsCounter, expectedText = "$numOfTabs tabs selected")
}
Log.i(TAG, "verifyTabsSelectedCounterText: Asserted number of tabs selected is: $numOfTabs")
}
fun saveTabsSelectedForCollection() {
mDevice.findObject(UiSelector().resourceId("$packageName:id/save_button")).click()
Log.i(TAG, "saveTabsSelectedForCollection: Trying to click \"Save\" button")
itemWithResId("$packageName:id/save_button").click()
Log.i(TAG, "saveTabsSelectedForCollection: Clicked \"Save\" button")
}
fun verifyTabSavedInCollection(title: String, visible: Boolean = true) {
@ -88,83 +105,114 @@ class CollectionRobot {
fun verifyCollectionMenuIsVisible(visible: Boolean, rule: ComposeTestRule) {
if (visible) {
collectionThreeDotButton(rule)
.assertExists()
.assertIsDisplayed()
Log.i(TAG, "verifyCollectionMenuIsVisible: Trying to verify collection three dot button exists")
collectionThreeDotButton(rule).assertExists()
Log.i(TAG, "verifyCollectionMenuIsVisible: Verified collection three dot button exists")
Log.i(TAG, "verifyCollectionMenuIsVisible: Trying to verify collection three dot button is displayed")
collectionThreeDotButton(rule).assertIsDisplayed()
Log.i(TAG, "verifyCollectionMenuIsVisible: Verified collection three dot button is displayed")
} else {
Log.i(TAG, "verifyCollectionMenuIsVisible: Trying to verify collection three dot button does not exist")
collectionThreeDotButton(rule)
.assertDoesNotExist()
Log.i(TAG, "verifyCollectionMenuIsVisible: Verified collection three dot button does not exist")
}
}
fun clickCollectionThreeDotButton(rule: ComposeTestRule) {
collectionThreeDotButton(rule)
.assertIsDisplayed()
.performClick()
Log.i(TAG, "clickCollectionThreeDotButton: Trying to verify three dot button is displayed")
collectionThreeDotButton(rule).assertIsDisplayed()
Log.i(TAG, "clickCollectionThreeDotButton: Verified three dot button is displayed")
Log.i(TAG, "clickCollectionThreeDotButton: Trying to click three dot button")
collectionThreeDotButton(rule).performClick()
Log.i(TAG, "clickCollectionThreeDotButton: Clicked three dot button")
}
fun selectOpenTabs(rule: ComposeTestRule) {
rule.onNode(hasText("Open tabs"))
.assertIsDisplayed()
.performClick()
Log.i(TAG, "selectOpenTabs: Trying to verify \"Open tabs\" menu option is displayed")
rule.onNode(hasText("Open tabs")).assertIsDisplayed()
Log.i(TAG, "selectOpenTabs: Verified \"Open tabs\" menu option is displayed")
Log.i(TAG, "selectOpenTabs: Trying to click \"Open tabs\" menu option")
rule.onNode(hasText("Open tabs")).performClick()
Log.i(TAG, "selectOpenTabs: Clicked \"Open tabs\" menu option")
}
fun selectRenameCollection(rule: ComposeTestRule) {
rule.onNode(hasText("Rename collection"))
.assertIsDisplayed()
.performClick()
Log.i(TAG, "selectRenameCollection: Trying to verify \"Rename collection\" menu option is displayed")
rule.onNode(hasText("Rename collection")).assertIsDisplayed()
Log.i(TAG, "selectRenameCollection: Verified \"Rename collection\" menu option is displayed")
Log.i(TAG, "selectRenameCollection: Trying to click \"Rename collection\" menu option")
rule.onNode(hasText("Rename collection")).performClick()
Log.i(TAG, "selectRenameCollection: Clicked \"Rename collection\" menu option")
Log.i(TAG, "selectRenameCollection: Waiting for $waitingTime ms for collection name text field to exist")
mainMenuEditCollectionNameField().waitForExists(waitingTime)
Log.i(TAG, "selectRenameCollection: Waited for $waitingTime ms for collection name text field to exist")
}
fun selectAddTabToCollection(rule: ComposeTestRule) {
rule.onNode(hasText("Add tab"))
.assertIsDisplayed()
.performClick()
Log.i(TAG, "selectAddTabToCollection: Trying to verify \"Add tab\" menu option is displayed")
rule.onNode(hasText("Add tab")).assertIsDisplayed()
Log.i(TAG, "selectAddTabToCollection: Verified \"Add tab\" menu option is displayed")
Log.i(TAG, "selectAddTabToCollection: Trying to click \"Add tab\" menu option")
rule.onNode(hasText("Add tab")).performClick()
Log.i(TAG, "selectAddTabToCollection: Clicked \"Add tab\" menu option")
mDevice.waitNotNull(Until.findObject(By.text("Select Tabs")))
}
fun selectDeleteCollection(rule: ComposeTestRule) {
rule.onNode(hasText("Delete collection"))
.assertIsDisplayed()
.performClick()
Log.i(TAG, "selectDeleteCollection: Trying to verify \"Delete collection\" menu option is displayed")
rule.onNode(hasText("Delete collection")).assertIsDisplayed()
Log.i(TAG, "selectDeleteCollection: Verified \"Delete collection\" menu option is displayed")
Log.i(TAG, "selectDeleteCollection: Trying to click \"Delete collection\" menu option")
rule.onNode(hasText("Delete collection")).performClick()
Log.i(TAG, "selectDeleteCollection: Clicked \"Delete collection\" menu option")
}
fun verifyCollectionItemRemoveButtonIsVisible(title: String, visible: Boolean) =
assertUIObjectExists(removeTabFromCollectionButton(title), exists = visible)
fun removeTabFromCollection(title: String) = removeTabFromCollectionButton(title).click()
fun removeTabFromCollection(title: String) {
Log.i(TAG, "removeTabFromCollection: Trying to click remove button for tab: $title")
removeTabFromCollectionButton(title).click()
Log.i(TAG, "removeTabFromCollection: Clicked remove button for tab: $title")
}
fun swipeTabLeft(title: String, rule: ComposeTestRule) {
Log.i(TAG, "swipeTabLeft: Trying to remove tab: $title using swipe left action")
rule.onNode(hasText(title), useUnmergedTree = true)
.performTouchInput { swipeLeft() }
Log.i(TAG, "swipeTabLeft: Removed tab: $title using swipe left action")
Log.i(TAG, "swipeTabLeft: Waiting for rule to be idle")
rule.waitForIdle()
Log.i(TAG, "swipeTabLeft: Waited for rule to be idle")
}
fun swipeTabRight(title: String, rule: ComposeTestRule) {
Log.i(TAG, "swipeTabRight: Trying to remove tab: $title using swipe right action")
rule.onNode(hasText(title), useUnmergedTree = true)
.performTouchInput { swipeRight() }
Log.i(TAG, "swipeTabRight: Removed tab: $title using swipe right action")
Log.i(TAG, "swipeTabRight: Waiting for rule to be idle")
rule.waitForIdle()
Log.i(TAG, "swipeTabRight: Waited for rule to be idle")
}
fun verifySnackBarText(expectedText: String) {
mDevice.findObject(UiSelector().text(expectedText)).waitForExists(waitingTime)
fun goBackInCollectionFlow() {
Log.i(TAG, "goBackInCollectionFlow: Trying to click collection creation flow back button")
backButton().click()
Log.i(TAG, "goBackInCollectionFlow: Clicked collection creation flow back button")
}
fun goBackInCollectionFlow() = backButton().click()
fun swipeToBottom() =
UiScrollable(
UiSelector().resourceId("$packageName:id/sessionControlRecyclerView"),
).scrollToEnd(3)
class Transition {
fun collapseCollection(
title: String,
interact: HomeScreenRobot.() -> Unit,
): HomeScreenRobot.Transition {
assertUIObjectExists(itemContainingText(title))
Log.i(TAG, "collapseCollection: Trying to click collection $title and wait for $waitingTimeShort ms for a new window")
itemContainingText(title).clickAndWaitForNewWindow(waitingTimeShort)
Log.i(TAG, "collapseCollection: Clicked collection $title and waited for $waitingTimeShort ms for a new window")
assertUIObjectExists(itemWithDescription(getStringResource(R.string.remove_tab_from_collection)), exists = false)
HomeScreenRobot().interact()
@ -176,9 +224,15 @@ class CollectionRobot {
name: String,
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
Log.i(TAG, "typeCollectionNameAndSave: Waiting for $waitingTime ms for collection name text field to exist")
mainMenuEditCollectionNameField().waitForExists(waitingTime)
Log.i(TAG, "typeCollectionNameAndSave: Waited for $waitingTime ms for collection name text field to exist")
Log.i(TAG, "typeCollectionNameAndSave: Trying to set collection name text field to: $name")
mainMenuEditCollectionNameField().text = name
Log.i(TAG, "typeCollectionNameAndSave: Collection name text field set to: $name")
Log.i(TAG, "typeCollectionNameAndSave: Trying to press done action button")
onView(withId(R.id.name_collection_edittext)).perform(pressImeActionButton())
Log.i(TAG, "typeCollectionNameAndSave: Pressed done action button")
// wait for the collection creation wrapper to be dismissed
mDevice.waitNotNull(Until.gone(By.res("$packageName:id/createCollectionWrapper")))
@ -191,16 +245,24 @@ class CollectionRobot {
title: String,
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
Log.i(TAG, "selectExistingCollection: Waiting for $waitingTime ms for collection with title: $title to exist")
collectionTitle(title).waitForExists(waitingTime)
Log.i(TAG, "selectExistingCollection: Waited for $waitingTime ms for collection with title: $title to exist")
Log.i(TAG, "selectExistingCollection: Trying to click collection with title: $title")
collectionTitle(title).click()
Log.i(TAG, "selectExistingCollection: Clicked collection with title: $title")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickShareCollectionButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
Log.i(TAG, "clickShareCollectionButton: Waiting for $waitingTime ms for share collection button to exist")
shareCollectionButton().waitForExists(waitingTime)
Log.i(TAG, "clickShareCollectionButton: Waited for $waitingTime ms for share collection button to exist")
Log.i(TAG, "clickShareCollectionButton: Trying to click share collection button")
shareCollectionButton().click()
Log.i(TAG, "clickShareCollectionButton: Clicked share collection button")
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
@ -213,21 +275,14 @@ fun collectionRobot(interact: CollectionRobot.() -> Unit): CollectionRobot.Trans
return CollectionRobot.Transition()
}
private fun collectionTitle(title: String) =
mDevice.findObject(
UiSelector()
.text(title),
)
private fun collectionTitle(title: String) = itemWithText(title)
private fun collectionThreeDotButton(rule: ComposeTestRule) =
rule.onNode(hasContentDescription("Collection menu"))
private fun collectionListItem(title: String) = mDevice.findObject(UiSelector().text(title))
private fun shareCollectionButton() =
mDevice.findObject(
UiSelector().description("Share"),
)
private fun shareCollectionButton() = itemWithDescription("Share")
private fun removeTabFromCollectionButton(title: String) =
mDevice.findObject(
@ -245,9 +300,7 @@ private fun collectionNameTextField() =
// collection name text field, when saving from the main menu option
private fun mainMenuEditCollectionNameField() =
mDevice.findObject(
UiSelector().resourceId("$packageName:id/name_collection_edittext"),
)
itemWithResId("$packageName:id/name_collection_edittext")
private fun addNewCollectionButton() =
mDevice.findObject(UiSelector().text("Add new collection"))
@ -256,7 +309,7 @@ private fun backButton() =
mDevice.findObject(
UiSelector().resourceId("$packageName:id/back_button"),
)
private val addCollectionButtonPanel =
private fun addCollectionButtonPanel() =
itemWithResId("$packageName:id/buttonPanel")
private val addCollectionOkButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())
private fun addCollectionOkButton() = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())

@ -6,6 +6,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import android.view.View
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.ExperimentalTestApi
@ -42,12 +43,13 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import org.hamcrest.Matcher
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.clickAtLocationInView
import org.mozilla.fenix.helpers.idlingresource.BottomSheetBehaviorStateIdlingResource
@ -62,25 +64,37 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
fun verifyNormalBrowsingButtonIsSelected(isSelected: Boolean = true) {
if (isSelected) {
Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Trying to verify that the normal browsing button is selected")
composeTestRule.normalBrowsingButton().assertIsSelected()
Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Verified that the normal browsing button is selected")
} else {
Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Trying to verify that the normal browsing button is not selected")
composeTestRule.normalBrowsingButton().assertIsNotSelected()
Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Verified that the normal browsing button is not selected")
}
}
fun verifyPrivateBrowsingButtonIsSelected(isSelected: Boolean = true) {
if (isSelected) {
Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Trying to verify that the private browsing button is selected")
composeTestRule.privateBrowsingButton().assertIsSelected()
Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Verified that the private browsing button is selected")
} else {
Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Trying to verify that the private browsing button is not selected")
composeTestRule.privateBrowsingButton().assertIsNotSelected()
Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Verified that the private browsing button is not selected")
}
}
fun verifySyncedTabsButtonIsSelected(isSelected: Boolean = true) {
if (isSelected) {
Log.i(TAG, "verifySyncedTabsButtonIsSelected: Trying to verify that the synced tabs button is selected")
composeTestRule.syncedTabsButton().assertIsSelected()
Log.i(TAG, "verifySyncedTabsButtonIsSelected: Verified that the synced tabs button is selected")
} else {
Log.i(TAG, "verifySyncedTabsButtonIsSelected: Trying to verify that the synced tabs button is not selected")
composeTestRule.syncedTabsButton().assertIsNotSelected()
Log.i(TAG, "verifySyncedTabsButtonIsSelected: Verified that the synced tabs button is not selected")
}
}
@ -95,16 +109,23 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
fun verifyExistingOpenTabs(vararg titles: String) {
titles.forEach { title ->
Log.i(TAG, "verifyExistingOpenTabs: Waiting for $waitingTime ms for tab with title: $title to exist")
itemContainingText(title).waitForExists(waitingTime)
Log.i(TAG, "verifyExistingOpenTabs: Waited for $waitingTime ms for tab with title: $title to exist")
Log.i(TAG, "verifyExistingOpenTabs: Trying to verify that the open tab with title: $title exists")
composeTestRule.tabItem(title).assertExists()
Log.i(TAG, "verifyExistingOpenTabs: Verified that the open tab with title: $title exists")
}
}
fun verifyOpenTabsOrder(title: String, position: Int) =
fun verifyOpenTabsOrder(title: String, position: Int) {
Log.i(TAG, "verifyOpenTabsOrder: Trying to verify that the open tab at position: $position has title: $title")
composeTestRule.normalTabsList()
.onChildAt(position - 1)
.assert(hasTestTag(TabsTrayTestTag.tabItemRoot))
.assert(hasAnyChild(hasText(title)))
Log.i(TAG, "verifyOpenTabsOrder: Verified that the open tab at position: $position has title: $title")
}
fun verifyNoExistingOpenTabs(vararg titles: String) {
titles.forEach { title ->
@ -116,110 +137,163 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
}
fun verifyNormalTabsList() {
Log.i(TAG, "verifyNormalTabsList: Trying to verify that the normal tabs list exists")
composeTestRule.normalTabsList().assertExists()
Log.i(TAG, "verifyNormalTabsList: Verified that the normal tabs list exists")
}
fun verifyPrivateTabsList() {
Log.i(TAG, "verifyPrivateTabsList: Trying to verify that the private tabs list exists")
composeTestRule.privateTabsList().assertExists()
Log.i(TAG, "verifyPrivateTabsList: Verified that the private tabs list exists")
}
fun verifySyncedTabsList() {
Log.i(TAG, "verifySyncedTabsList: Trying to verify that the synced tabs list exists")
composeTestRule.syncedTabsList().assertExists()
Log.i(TAG, "verifySyncedTabsList: Verified that the synced tabs list exists")
}
fun verifyNoOpenTabsInNormalBrowsing() {
Log.i(TAG, "verifyNoOpenTabsInNormalBrowsing: Trying to verify that the empty normal tabs list exists")
composeTestRule.emptyNormalTabsList().assertExists()
Log.i(TAG, "verifyNoOpenTabsInNormalBrowsing: Verified that the empty normal tabs list exists")
}
fun verifyNoOpenTabsInPrivateBrowsing() {
Log.i(TAG, "verifyNoOpenTabsInPrivateBrowsing: Trying to verify that the empty private tabs list exists")
composeTestRule.emptyPrivateTabsList().assertExists()
Log.i(TAG, "verifyNoOpenTabsInPrivateBrowsing: Verified that the empty private tabs list exists")
}
fun verifyAccountSettingsButton() {
Log.i(TAG, "verifyAccountSettingsButton: Trying to verify that the \"Account settings\" menu button exists")
composeTestRule.dropdownMenuItemAccountSettings().assertExists()
Log.i(TAG, "verifyAccountSettingsButton: Verified that the \"Account settings\" menu button exists")
}
fun verifyCloseAllTabsButton() {
Log.i(TAG, "verifyCloseAllTabsButton: Trying to verify that the \"Close all tabs\" menu button exists")
composeTestRule.dropdownMenuItemCloseAllTabs().assertExists()
Log.i(TAG, "verifyCloseAllTabsButton: Verified that the \"Close all tabs\" menu button exists")
}
fun verifySelectTabsButton() {
Log.i(TAG, "verifySelectTabsButton: Trying to verify that the \"Select tabs\" menu button exists")
composeTestRule.dropdownMenuItemSelectTabs().assertExists()
Log.i(TAG, "verifySelectTabsButton: Verified that the \"Select tabs\" menu button exists")
}
fun verifyShareAllTabsButton() {
Log.i(TAG, "verifyShareAllTabsButton: Trying to verify that the \"Share all tabs\" menu button exists")
composeTestRule.dropdownMenuItemShareAllTabs().assertExists()
Log.i(TAG, "verifyShareAllTabsButton: Verified that the \"Share all tabs\" menu button exists")
}
fun verifyRecentlyClosedTabsButton() {
Log.i(TAG, "verifyRecentlyClosedTabsButton: Trying to verify that the \"Recently closed tabs\" menu button exists")
composeTestRule.dropdownMenuItemRecentlyClosedTabs().assertExists()
Log.i(TAG, "verifyRecentlyClosedTabsButton: Verified that the \"Recently closed tabs\" menu button exists")
}
fun verifyTabSettingsButton() {
Log.i(TAG, "verifyTabSettingsButton: Trying to verify that the \"Tab settings\" menu button exists")
composeTestRule.dropdownMenuItemTabSettings().assertExists()
Log.i(TAG, "verifyTabSettingsButton: Verified that the \"Tab settings\" menu button exists")
}
fun verifyThreeDotButton() {
Log.i(TAG, "verifyThreeDotButton: Trying to verify that the three dot button exists")
composeTestRule.threeDotButton().assertExists()
Log.i(TAG, "verifyThreeDotButton: Verified that the three dot button exists")
}
fun verifyFab() {
Log.i(TAG, "verifyFab: Trying to verify that the new tab FAB button exists")
composeTestRule.tabsTrayFab().assertExists()
Log.i(TAG, "verifyFab: Verified that the new tab FAB button exists")
}
fun verifyNormalTabCounter() {
Log.i(TAG, "verifyNormalTabCounter: Trying to verify that the normal tabs list counter exists")
composeTestRule.normalTabsCounter().assertExists()
Log.i(TAG, "verifyNormalTabCounter: Verified that the normal tabs list counter exists")
}
/**
* Verifies a tab's thumbnail when there is only one tab open.
*/
fun verifyTabThumbnail() {
Log.i(TAG, "verifyTabThumbnail: Trying to verify that the tab thumbnail exists")
composeTestRule.tabThumbnail().assertExists()
Log.i(TAG, "verifyTabThumbnail: Verified that the tab thumbnail exists")
}
/**
* Verifies a tab's close button when there is only one tab open.
*/
fun verifyTabCloseButton() {
Log.i(TAG, "verifyTabCloseButton: Trying to verify that the close tab button exists")
composeTestRule.closeTabButton().assertExists()
Log.i(TAG, "verifyTabCloseButton: Verified that the close tab button exists")
}
fun verifyTabsTrayBehaviorState(expectedState: Int) {
Log.i(TAG, "verifyTabsTrayBehaviorState: Trying to verify that the tabs tray state matches: $expectedState")
tabsTrayView().check(ViewAssertions.matches(BottomSheetBehaviorStateMatcher(expectedState)))
Log.i(TAG, "verifyTabsTrayBehaviorState: Verified that the tabs tray state matches: $expectedState")
}
fun verifyMinusculeHalfExpandedRatio() {
Log.i(TAG, "verifyMinusculeHalfExpandedRatio: Trying to verify the tabs tray half expanded ratio")
tabsTrayView().check(ViewAssertions.matches(BottomSheetBehaviorHalfExpandedMaxRatioMatcher(0.001f)))
Log.i(TAG, "verifyMinusculeHalfExpandedRatio: Verified the tabs tray half expanded ratio")
}
fun verifyTabTrayIsOpen() {
Log.i(TAG, "verifyTabTrayIsOpen: Trying to verify that the tabs tray exists")
composeTestRule.tabsTray().assertExists()
Log.i(TAG, "verifyTabTrayIsOpen: Verified that the tabs tray exists")
}
fun verifyTabTrayIsClosed() {
Log.i(TAG, "verifyTabTrayIsClosed: Trying to verify that the tabs tray does not exist")
composeTestRule.tabsTray().assertDoesNotExist()
Log.i(TAG, "verifyTabTrayIsClosed: Verified that the tabs tray does not exist")
}
/**
* Closes a tab when there is only one tab open.
*/
@OptIn(ExperimentalTestApi::class)
fun closeTab() {
Log.i(TAG, "closeTab: Waiting until the close tab button exists")
composeTestRule.waitUntilAtLeastOneExists(hasTestTag(TabsTrayTestTag.tabItemClose))
Log.i(TAG, "closeTab: Waited until the close tab button exists")
Log.i(TAG, "closeTab: Trying to verify that the close tab button exists")
composeTestRule.closeTabButton().assertExists()
Log.i(TAG, "closeTab: Verified that the close tab button exists")
Log.i(TAG, "closeTab: Trying to click the close tab button")
composeTestRule.closeTabButton().performClick()
Log.i(TAG, "closeTab: Clicked the close tab button")
}
/**
* Swipes a tab with [title] left.
*/
fun swipeTabLeft(title: String) {
Log.i(TAG, "swipeTabLeft: Trying to perform swipe left action on tab: $title")
composeTestRule.tabItem(title).performTouchInput { swipeLeft() }
Log.i(TAG, "swipeTabLeft: Performed swipe left action on tab: $title")
}
/**
* Swipes a tab with [title] right.
*/
fun swipeTabRight(title: String) {
Log.i(TAG, "swipeTabRight: Trying to perform swipe right action on tab: $title")
composeTestRule.tabItem(title).performTouchInput { swipeRight() }
Log.i(TAG, "swipeTabRight: Performed swipe right action on tab: $title")
}
/**
@ -230,8 +304,12 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
collectionName: String,
firstCollection: Boolean = true,
) {
Log.i(TAG, "createCollection: Trying to click the three dot button")
composeTestRule.threeDotButton().performClick()
Log.i(TAG, "createCollection: Clicked the three dot button")
Log.i(TAG, "createCollection: Trying to click the \"Select tabs\" menu button")
composeTestRule.dropdownMenuItemSelectTabs().performClick()
Log.i(TAG, "createCollection: Clicked the \"Select tabs\" menu button")
for (tab in tabTitles) {
selectTab(tab)
@ -250,24 +328,32 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
*/
@OptIn(ExperimentalTestApi::class)
fun selectTab(title: String) {
composeTestRule.waitUntilExactlyOneExists(hasText(title), TestAssetHelper.waitingTime)
Log.i(TAG, "selectTab: Waiting for $waitingTime ms until the tab with title: $title exists")
composeTestRule.waitUntilExactlyOneExists(hasText(title), waitingTime)
Log.i(TAG, "selectTab: Waited for $waitingTime ms until the tab with title: $title exists")
Log.i(TAG, "selectTab: Trying to click tab with title: $title")
composeTestRule.tabItem(title).performClick()
Log.i(TAG, "selectTab: Clicked tab with title: $title")
}
/**
* Performs a long click on a tab with [title].
*/
fun longClickTab(title: String) {
Log.i(TAG, "longClickTab: Trying to long click tab with title: $title")
composeTestRule.tabItem(title)
.performTouchInput { longClick(durationMillis = Constants.LONG_CLICK_DURATION) }
Log.i(TAG, "longClickTab: Long clicked tab with title: $title")
}
/**
* Verifies the multi selection counter displays [numOfTabs].
*/
fun verifyTabsMultiSelectionCounter(numOfTabs: Int) {
Log.i(TAG, "verifyTabsMultiSelectionCounter: Trying to verify that $numOfTabs tabs are selected")
composeTestRule.multiSelectionCounter()
.assert(hasText("$numOfTabs selected"))
Log.i(TAG, "verifyTabsMultiSelectionCounter: Verified that $numOfTabs tabs are selected")
}
/**
@ -275,9 +361,13 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
*/
@OptIn(ExperimentalTestApi::class)
fun verifyTabMediaControlButtonState(action: String) {
Log.i(TAG, "verifyTabMediaControlButtonStateTab: Waiting for $waitingTime ms until the media tab control button: $action exists")
composeTestRule.waitUntilAtLeastOneExists(hasContentDescription(action), waitingTime)
Log.i(TAG, "verifyTabMediaControlButtonStateTab: Waited for $waitingTime ms until the media tab control button: $action exists")
Log.i(TAG, "verifyTabMediaControlButtonStateTab: Trying to verify that the tab media control button: $action exists")
composeTestRule.tabMediaControlButton(action)
.assertExists()
Log.i(TAG, "verifyTabMediaControlButtonStateTab: Verified tab media control button: $action exists")
}
/**
@ -285,90 +375,118 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
*/
@OptIn(ExperimentalTestApi::class)
fun clickTabMediaControlButton(action: String) {
Log.i(TAG, "clickTabMediaControlButton: Waiting for $waitingTime ms until the media tab control button: $action exists")
composeTestRule.waitUntilAtLeastOneExists(hasContentDescription(action), waitingTime)
Log.i(TAG, "clickTabMediaControlButton: Waited for $waitingTime ms until the media tab control button: $action exists")
Log.i(TAG, "clickTabMediaControlButton: Trying to click the tab media control button: $action")
composeTestRule.tabMediaControlButton(action)
.performClick()
Log.i(TAG, "clickTabMediaControlButton: Clicked the tab media control button: $action")
}
/**
* Closes a tab with a given [title].
*/
fun closeTabWithTitle(title: String) {
Log.i(TAG, "closeTabWithTitle: Trying to click the close button for tab with title: $title")
composeTestRule.onAllNodesWithTag(TabsTrayTestTag.tabItemClose)
.filter(hasParent(hasText(title)))
.onFirst()
.performClick()
Log.i(TAG, "closeTabWithTitle: Clicked the close button for tab with title: $title")
}
class Transition(private val composeTestRule: HomeActivityComposeTestRule) {
fun openNewTab(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
Log.i(TAG, "openNewTab: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "openNewTab: Waited for device to be idle")
Log.i(TAG, "openNewTab: Trying to click the new tab FAB button")
composeTestRule.tabsTrayFab().performClick()
Log.i(TAG, "openNewTab: Clicked the new tab FAB button")
SearchRobot().interact()
return SearchRobot.Transition()
}
fun toggleToNormalTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
Log.i(TAG, "toggleToNormalTabs: Trying to click the normal browsing button")
composeTestRule.normalBrowsingButton().performClick()
Log.i(TAG, "toggleToNormalTabs: Clicked the normal browsing button")
ComposeTabDrawerRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
fun toggleToPrivateTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
Log.i(TAG, "toggleToPrivateTabs: Trying to click the private browsing button")
composeTestRule.privateBrowsingButton().performClick()
Log.i(TAG, "toggleToPrivateTabs: Clicked the private browsing button")
ComposeTabDrawerRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
fun toggleToSyncedTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
Log.i(TAG, "toggleToSyncedTabs: Trying to click the synced tabs button")
composeTestRule.syncedTabsButton().performClick()
Log.i(TAG, "toggleToSyncedTabs: Clicked the synced tabs button")
ComposeTabDrawerRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
fun clickSignInToSyncButton(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
Log.i(TAG, "clickSignInToSyncButton: Trying to click the sign in to sync button and wait for $waitingTimeShort ms for a new window")
itemContainingText(getStringResource(R.string.sync_sign_in))
.clickAndWaitForNewWindow(TestAssetHelper.waitingTimeShort)
.clickAndWaitForNewWindow(waitingTimeShort)
Log.i(TAG, "clickSignInToSyncButton: Clicked the sign in to sync button and waited for $waitingTimeShort ms for a new window")
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
}
fun openThreeDotMenu(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
Log.i(TAG, "openThreeDotMenu: Trying to click the three dot button")
composeTestRule.threeDotButton().performClick()
Log.i(TAG, "openThreeDotMenu: Clicked three dot button")
ComposeTabDrawerRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
fun closeAllTabs(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "closeAllTabs: Trying to click the \"Close all tabs\" menu button")
composeTestRule.dropdownMenuItemCloseAllTabs().performClick()
Log.i(TAG, "closeAllTabs: Clicked the \"Close all tabs\" menu button")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun openTab(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
composeTestRule.tabItem(title)
.performScrollTo()
.performClick()
Log.i(TAG, "openTab: Trying to scroll to tab with title: $title")
composeTestRule.tabItem(title).performScrollTo()
Log.i(TAG, "openTab: Scrolled to tab with title: $title")
Log.i(TAG, "openTab: Trying to click tab with title: $title")
composeTestRule.tabItem(title).performClick()
Log.i(TAG, "openTab: Clicked tab with title: $title")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun openPrivateTab(position: Int, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "openPrivateTab: Trying to click private tab at position: ${position + 1}")
composeTestRule.privateTabsList()
.onChildren()[position]
.performClick()
Log.i(TAG, "openPrivateTab: Clicked private tab at position: ${position + 1}")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun openNormalTab(position: Int, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "openNormalTab: Trying to click tab at position: ${position + 1}")
composeTestRule.normalTabsList()
.onChildren()[position]
.performClick()
Log.i(TAG, "openNormalTab: Clicked tab at position: ${position + 1}")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -377,7 +495,9 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
fun clickTopBar(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
// The topBar contains other views.
// Don't do the default click in the middle, rather click in some free space - top right.
Log.i(TAG, "clickTopBar: Trying to click the tabs tray top bar")
Espresso.onView(ViewMatchers.withId(R.id.topBar)).clickAtLocationInView(GeneralLocation.TOP_RIGHT)
Log.i(TAG, "clickTopBar: Clicked the tabs tray top bar")
ComposeTabDrawerRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
@ -434,21 +554,27 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
}
fun closeTabDrawer(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "closeTabDrawer: Trying to close the tabs tray by clicking the handle")
composeTestRule.bannerHandle().performSemanticsAction(SemanticsActions.OnClick)
Log.i(TAG, "closeTabDrawer: Closed the tabs tray by clicking the handle")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickSaveCollection(interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
Log.i(TAG, "clickSaveCollection: Trying to click the collections button")
composeTestRule.collectionsButton().performClick()
Log.i(TAG, "clickSaveCollection: Clicked collections button")
CollectionRobot().interact()
return CollectionRobot.Transition()
}
fun clickShareAllTabsButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
Log.i(TAG, "clickShareAllTabsButton: Trying to click the \"Share all tabs\" menu button button")
composeTestRule.dropdownMenuItemShareAllTabs().performClick()
Log.i(TAG, "clickShareAllTabsButton: Clicked the \"Share all tabs\" menu button button")
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
@ -468,7 +594,9 @@ fun composeTabDrawer(composeTestRule: HomeActivityComposeTestRule, interact: Com
* Clicks on the Collections button in the Tabs Tray banner and opens a transition in the [CollectionRobot].
*/
private fun clickCollectionsButton(composeTestRule: HomeActivityComposeTestRule, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
Log.i(TAG, "clickCollectionsButton: Trying to click the collections button")
composeTestRule.collectionsButton().performClick()
Log.i(TAG, "clickCollectionsButton: Clicked the collections button")
CollectionRobot().interact()
return CollectionRobot.Transition()

@ -4,6 +4,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.hasAnyChild
@ -16,14 +17,13 @@ import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTouchInput
import androidx.test.uiautomator.UiSelector
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.home.topsites.TopSitesTestTag
@ -34,21 +34,31 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
@OptIn(ExperimentalTestApi::class)
fun verifyExistingTopSitesList() {
composeTestRule.waitUntilExactlyOneExists(hasTestTag(TopSitesTestTag.topSites), timeoutMillis = waitingTime)
Log.i(TAG, "verifyExistingTopSitesList: Waiting for $waitingTime ms until the top sites list exists")
composeTestRule.waitUntilAtLeastOneExists(hasTestTag(TopSitesTestTag.topSites), timeoutMillis = waitingTime)
Log.i(TAG, "verifyExistingTopSitesList: Waited for $waitingTime ms until the top sites list to exists")
}
@OptIn(ExperimentalTestApi::class)
fun verifyExistingTopSiteItem(vararg titles: String) {
titles.forEach { title ->
mDevice.findObject(UiSelector().textContains(title)).waitForExists(waitingTimeShort)
Log.i(TAG, "verifyExistingTopSiteItem: Waiting for $waitingTime ms until the top site with title: $title exists")
composeTestRule.waitUntilAtLeastOneExists(hasText(title), timeoutMillis = waitingTime)
Log.i(TAG, "verifyExistingTopSiteItem: Waited for $waitingTime ms until the top site with title: $title exists")
Log.i(TAG, "verifyExistingTopSiteItem: Trying to verify that the top site with title: $title exists")
composeTestRule.topSiteItem(title).assertExists()
Log.i(TAG, "verifyExistingTopSiteItem: Verified that the top site with title: $title exists")
}
}
fun verifyNotExistingTopSiteItem(vararg titles: String) {
titles.forEach { title ->
Log.i(TAG, "verifyNotExistingTopSiteItem: Waiting for $waitingTime ms for top site with title: $title to exist")
itemContainingText(title).waitForExists(waitingTime)
Log.i(TAG, "verifyNotExistingTopSiteItem: Waited for $waitingTime ms for top site with title: $title to exist")
Log.i(TAG, "verifyNotExistingTopSiteItem: Trying to verify that top site with title: $title does not exist")
composeTestRule.topSiteItem(title).assertDoesNotExist()
Log.i(TAG, "verifyNotExistingTopSiteItem: Verified that top site with title: $title does not exist")
}
}
@ -59,15 +69,21 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
}
fun verifyTopSiteContextMenuOpenInPrivateTabButton() {
Log.i(TAG, "verifyTopSiteContextMenuOpenInPrivateTabButton: Trying to verify that the \"Open in private tab\" menu button exists")
composeTestRule.contextMenuItemOpenInPrivateTab().assertExists()
Log.i(TAG, "verifyTopSiteContextMenuOpenInPrivateTabButton: Verified that the \"Open in private tab\" menu button exists")
}
fun verifyTopSiteContextMenuRenameButton() {
Log.i(TAG, "verifyTopSiteContextMenuRenameButton: Trying to verify that the \"Rename\" menu button exists")
composeTestRule.contextMenuItemRename().assertExists()
Log.i(TAG, "verifyTopSiteContextMenuRenameButton: Verified that the \"Rename\" menu button exists")
}
fun verifyTopSiteContextMenuRemoveButton() {
Log.i(TAG, "verifyTopSiteContextMenuRemoveButton: Trying to verify that the \"Remove\" menu button exists")
composeTestRule.contextMenuItemRemove().assertExists()
Log.i(TAG, "verifyTopSiteContextMenuRemoveButton: Verified that the \"Remove\" menu button exists")
}
class Transition(private val composeTestRule: HomeActivityComposeTestRule) {
@ -76,7 +92,12 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
title: String,
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
composeTestRule.topSiteItem(title).performScrollTo().performClick()
Log.i(TAG, "openTopSiteTabWithTitle: Trying to scroll to top site with title: $title")
composeTestRule.topSiteItem(title).performScrollTo()
Log.i(TAG, "openTopSiteTabWithTitle: Scrolled to top site with title: $title")
Log.i(TAG, "openTopSiteTabWithTitle: Trying to click top site with title: $title")
composeTestRule.topSiteItem(title).performClick()
Log.i(TAG, "openTopSiteTabWithTitle: Clicked top site with title: $title")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -85,7 +106,9 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
fun openTopSiteInPrivate(
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
Log.i(TAG, "openTopSiteInPrivate: Trying to click the \"Open in private tab\" menu button")
composeTestRule.contextMenuItemOpenInPrivateTab().performClick()
Log.i(TAG, "openTopSiteInPrivate: Clicked the \"Open in private tab\" menu button")
composeTestRule.waitForIdle()
BrowserRobot().interact()
@ -96,9 +119,12 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
title: String,
interact: ComposeTopSitesRobot.() -> Unit,
): Transition {
composeTestRule.topSiteItem(title).performScrollTo().performTouchInput {
longClick()
}
Log.i(TAG, "openContextMenuOnTopSitesWithTitle: Trying to scroll to top site with title: $title")
composeTestRule.topSiteItem(title).performScrollTo()
Log.i(TAG, "openContextMenuOnTopSitesWithTitle: Scrolled to top site with title: $title")
Log.i(TAG, "openContextMenuOnTopSitesWithTitle: Trying to long click top site with title: $title")
composeTestRule.topSiteItem(title).performTouchInput { longClick() }
Log.i(TAG, "openContextMenuOnTopSitesWithTitle: Long clicked top site with title: $title")
ComposeTopSitesRobot(composeTestRule).interact()
return Transition(composeTestRule)
@ -108,13 +134,21 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
title: String,
interact: ComposeTopSitesRobot.() -> Unit,
): Transition {
Log.i(TAG, "renameTopSite: Trying to click the \"Rename\" menu button")
composeTestRule.contextMenuItemRename().performClick()
Log.i(TAG, "renameTopSite: Clicked the \"Rename\" menu button")
itemWithResId("$packageName:id/top_site_title")
.also {
Log.i(TAG, "renameTopSite: Waiting for $waitingTimeShort ms for top site rename text box to exist")
it.waitForExists(waitingTimeShort)
Log.i(TAG, "renameTopSite: Waited for $waitingTimeShort ms for top site rename text box to exist")
Log.i(TAG, "renameTopSite: Trying to set top site rename text box text to: $title")
it.setText(title)
Log.i(TAG, "renameTopSite: Top site rename text box text was set to: $title")
}
Log.i(TAG, "renameTopSite: Trying to click the \"Ok\" dialog button")
itemWithResIdContainingText("android:id/button1", "OK").click()
Log.i(TAG, "renameTopSite: Clicked the \"Ok\" dialog button")
ComposeTopSitesRobot(composeTestRule).interact()
return Transition(composeTestRule)
@ -124,23 +158,16 @@ class ComposeTopSitesRobot(private val composeTestRule: HomeActivityComposeTestR
fun removeTopSite(
interact: ComposeTopSitesRobot.() -> Unit,
): Transition {
Log.i(TAG, "removeTopSite: Trying to click the \"Remove\" menu button")
composeTestRule.contextMenuItemRemove().performClick()
Log.i(TAG, "removeTopSite: Clicked the \"Remove\" menu button")
Log.i(TAG, "removeTopSite: Waiting for $waitingTime ms until the \"Remove\" menu button does not exist")
composeTestRule.waitUntilDoesNotExist(hasTestTag(TopSitesTestTag.remove), waitingTime)
Log.i(TAG, "removeTopSite: Waited for $waitingTime ms until the \"Remove\" menu button does not exist")
ComposeTopSitesRobot(composeTestRule).interact()
return Transition(composeTestRule)
}
@OptIn(ExperimentalTestApi::class)
fun deleteTopSiteFromHistory(
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
composeTestRule.contextMenuItemRemove().performClick()
composeTestRule.waitUntilDoesNotExist(hasTestTag(TopSitesTestTag.remove), waitingTime)
BrowserRobot().interact()
return BrowserRobot.Transition()
}
}
}

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui.robots
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
@ -14,6 +15,7 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
@ -43,21 +45,27 @@ class CustomTabRobot {
itemWithDescription(getStringResource(R.string.mozac_feature_customtabs_share_link)),
)
fun verifyMainMenuButton() = assertUIObjectExists(mainMenuButton)
fun verifyMainMenuButton() = assertUIObjectExists(mainMenuButton())
fun verifyDesktopSiteButtonExists() {
Log.i(TAG, "verifyDesktopSiteButtonExists: Trying to verify that the request desktop site button is displayed")
desktopSiteButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyDesktopSiteButtonExists: Verified that the request desktop site button is displayed")
}
fun verifyFindInPageButtonExists() {
Log.i(TAG, "verifyFindInPageButtonExists: Trying to verify that the find in page button is displayed")
findInPageButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyFindInPageButtonExists: Verified that the find in page button is displayed")
}
fun verifyPoweredByTextIsDisplayed() =
assertUIObjectExists(itemContainingText("POWERED BY $appName"))
fun verifyOpenInBrowserButtonExists() {
Log.i(TAG, "verifyOpenInBrowserButtonExists: Trying to verify that the \"Open in Firefox\" button is displayed")
openInBrowserButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyOpenInBrowserButtonExists: Verified that the \"Open in Firefox\" button is displayed")
}
fun verifyBackButtonExists() = assertUIObjectExists(itemWithDescription("Back"))
@ -69,7 +77,9 @@ class CustomTabRobot {
fun verifyCustomMenuItem(label: String) = assertUIObjectExists(itemContainingText(label))
fun verifyCustomTabCloseButton() {
Log.i(TAG, "verifyCustomTabCloseButton: Trying to verify that the close custom tab button is displayed")
closeButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyCustomTabCloseButton: Verified that the close custom tab button is displayed")
}
fun verifyCustomTabToolbarTitle(title: String) {
@ -103,12 +113,16 @@ class CustomTabRobot {
mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar")),
waitingTime,
)
Log.i(TAG, "longCLickAndCopyToolbarUrl: Trying to long click the custom tab toolbar")
customTabToolbar().click(LONG_CLICK_DURATION)
Log.i(TAG, "longCLickAndCopyToolbarUrl: Long clicked the custom tab toolbar")
clickContextMenuItem("Copy")
}
fun fillAndSubmitLoginCredentials(userName: String, password: String) {
Log.i(TAG, "fillAndSubmitLoginCredentials: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "fillAndSubmitLoginCredentials: Waited for device to be idle for $waitingTime ms")
setPageObjectText(itemWithResId("username"), userName)
setPageObjectText(itemWithResId("password"), password)
clickPageObject(itemWithResId("submit"))
@ -118,9 +132,17 @@ class CustomTabRobot {
)
}
fun waitForPageToLoad() = progressBar.waitUntilGone(waitingTime)
fun waitForPageToLoad() {
Log.i(TAG, "waitForPageToLoad: Waiting for $waitingTime ms until progress bar is gone")
progressBar().waitUntilGone(waitingTime)
Log.i(TAG, "waitForPageToLoad: Waited for $waitingTime ms until progress bar was gone")
}
fun clickCustomTabCloseButton() = closeButton().click()
fun clickCustomTabCloseButton() {
Log.i(TAG, "clickCustomTabCloseButton: Trying to click close custom tab button")
closeButton().click()
Log.i(TAG, "clickCustomTabCloseButton: Clicked close custom tab button")
}
fun verifyCustomTabActionButton(customTabActionButtonDescription: String) =
assertUIObjectExists(itemWithDescription(customTabActionButtonDescription))
@ -133,9 +155,13 @@ class CustomTabRobot {
class Transition {
fun openMainMenu(interact: CustomTabRobot.() -> Unit): Transition {
mainMenuButton.also {
mainMenuButton().also {
Log.i(TAG, "openMainMenu: Waiting for $waitingTime ms for the main menu button to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "openMainMenu: Waited for $waitingTime ms for the main menu button to exist")
Log.i(TAG, "openMainMenu: Trying to click the main menu button")
it.click()
Log.i(TAG, "openMainMenu: Clicked the main menu button")
}
CustomTabRobot().interact()
@ -143,14 +169,18 @@ class CustomTabRobot {
}
fun clickOpenInBrowserButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "clickOpenInBrowserButton: Trying to click the \"Open in Firefox\" button")
openInBrowserButton().perform(click())
Log.i(TAG, "clickOpenInBrowserButton: Clicked the \"Open in Firefox\" button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
Log.i(TAG, "clickShareButton: Trying to click the share button")
itemWithDescription(getStringResource(R.string.mozac_feature_customtabs_share_link)).click()
Log.i(TAG, "clickShareButton: Clicked the share button")
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
@ -163,7 +193,7 @@ fun customTabScreen(interact: CustomTabRobot.() -> Unit): CustomTabRobot.Transit
return CustomTabRobot.Transition()
}
private val mainMenuButton = itemWithResId("$packageName:id/mozac_browser_toolbar_menu")
private fun mainMenuButton() = itemWithResId("$packageName:id/mozac_browser_toolbar_menu")
private fun desktopSiteButton() = onView(withId(R.id.switch_widget))
@ -175,7 +205,7 @@ private fun closeButton() = onView(withContentDescription("Return to previous ap
private fun customTabToolbar() = mDevice.findObject(By.res("$packageName:id/toolbar"))
private val progressBar =
private fun progressBar() =
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_progress"),
)

@ -51,9 +51,8 @@ class DownloadRobot {
fun verifyDownloadPrompt(fileName: String) {
var currentTries = 0
while (currentTries++ < 3) {
Log.i(TAG, "verifyDownloadPrompt: While loop currentTries = $currentTries")
Log.i(TAG, "verifyDownloadPrompt: Started try #$currentTries")
try {
Log.i(TAG, "verifyDownloadPrompt: Try block")
assertUIObjectExists(
itemWithResId("$packageName:id/download_button"),
itemContainingText(fileName),
@ -61,7 +60,7 @@ class DownloadRobot {
break
} catch (e: AssertionError) {
Log.i(TAG, "verifyDownloadPrompt: Catch block")
Log.i(TAG, "verifyDownloadPrompt: AssertionError caught, executing fallback methods")
Log.e("DOWNLOAD_ROBOT", "Failed to find locator: ${e.localizedMessage}")
browserScreen {
@ -96,11 +95,12 @@ class DownloadRobot {
)
fun clickTryAgainButton() {
Log.i(TAG, "clickTryAgainButton: Trying to click the \"TRY AGAIN\" in app prompt button")
itemWithResIdAndText(
"$packageName:id/download_dialog_action_button",
"Try Again",
).click()
Log.i(TAG, "clickTryAgainButton: Clicked \"TRY AGAIN\" in app prompt button")
Log.i(TAG, "clickTryAgainButton: Clicked the \"TRY AGAIN\" in app prompt button")
}
fun verifyPhotosAppOpens() = assertExternalAppOpens(GOOGLE_APPS_PHOTOS)
@ -111,34 +111,40 @@ class DownloadRobot {
fun verifyDownloadedFileIcon() = assertUIObjectExists(itemWithResId("$packageName:id/favicon"))
fun verifyEmptyDownloadsList() {
Log.i(TAG, "verifyEmptyDownloadsList: Looking for empty download list")
Log.i(TAG, "verifyEmptyDownloadsList: Waiting for $waitingTime ms for for empty download list to exist")
mDevice.findObject(UiSelector().resourceId("$packageName:id/download_empty_view"))
.waitForExists(waitingTime)
Log.i(TAG, "verifyEmptyDownloadsList: Waited for $waitingTime ms for for empty download list to exist")
Log.i(TAG, "verifyEmptyDownloadsList: Trying to verify that the \"No downloaded files\" list message is displayed")
onView(withText("No downloaded files")).check(matches(isDisplayed()))
Log.i(TAG, "verifyEmptyDownloadsList: Verified \"No downloaded files\" list message")
Log.i(TAG, "verifyEmptyDownloadsList: Verified that the \"No downloaded files\" list message is displayed")
}
fun waitForDownloadsListToExist() =
assertUIObjectExists(itemWithResId("$packageName:id/download_list"))
fun openDownloadedFile(fileName: String) {
downloadedFile(fileName)
.check(matches(isDisplayed()))
.click()
Log.i(TAG, "openDownloadedFile: Trying to verify that the downloaded file: $fileName is displayed")
downloadedFile(fileName).check(matches(isDisplayed()))
Log.i(TAG, "openDownloadedFile: Verified that the downloaded file: $fileName is displayed")
Log.i(TAG, "openDownloadedFile: Trying to click downloaded file: $fileName")
downloadedFile(fileName).click()
Log.i(TAG, "openDownloadedFile: Clicked downloaded file: $fileName")
}
fun deleteDownloadedItem(fileName: String) {
Log.i(TAG, "deleteDownloadedItem: Trying to click the trash bin icon to delete downloaded file: $fileName")
onView(
allOf(
withId(R.id.overflow_menu),
hasSibling(withText(fileName)),
),
).click()
Log.i(TAG, "deleteDownloadedItem: Deleted downloaded file: $fileName using trash bin icon")
Log.i(TAG, "deleteDownloadedItem: Clicked the trash bin icon to delete downloaded file: $fileName")
}
fun longClickDownloadedItem(title: String) {
Log.i(TAG, "longClickDownloadedItem: Trying to long click downloaded file: $title")
onView(
allOf(
withId(R.id.title),
@ -149,21 +155,24 @@ class DownloadRobot {
}
fun selectDownloadedItem(title: String) {
Log.i(TAG, "selectDownloadedItem: Trying click downloaded file: $title to select it")
onView(
allOf(
withId(R.id.title),
withText(title),
),
).perform(click())
Log.i(TAG, "selectDownloadedItem: Selected downloaded file: $title")
Log.i(TAG, "selectDownloadedItem: Clicked downloaded file: $title to select it")
}
fun openMultiSelectMoreOptionsMenu() {
Log.i(TAG, "openMultiSelectMoreOptionsMenu: Trying to click multi-select more options button")
itemWithDescription(getStringResource(R.string.content_description_menu)).click()
Log.i(TAG, "openMultiSelectMoreOptionsMenu: Clicked multi-select more options button")
}
fun clickMultiSelectRemoveButton() {
Log.i(TAG, "clickMultiSelectRemoveButton: Trying to click multi-select remove button")
itemWithResIdContainingText("$packageName:id/title", "Remove").click()
Log.i(TAG, "clickMultiSelectRemoveButton: Clicked multi-select remove button")
}
@ -180,27 +189,31 @@ class DownloadRobot {
class Transition {
fun clickDownload(interact: DownloadRobot.() -> Unit): Transition {
Log.i(TAG, "clickDownload: Trying to click the \"Download\" download prompt button")
downloadButton().click()
Log.i(TAG, "clickDownload: Clicked \"DOWNLOAD\" button from prompt")
Log.i(TAG, "clickDownload: Clicked the \"Download\" download prompt button")
DownloadRobot().interact()
return Transition()
}
fun closeDownloadPrompt(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "closeDownloadPrompt: Trying to click the close download prompt button")
itemWithResId("$packageName:id/download_dialog_close_button").click()
Log.i(TAG, "closeDownloadPrompt: Dismissed download prompt by clicking close prompt button")
Log.i(TAG, "closeDownloadPrompt: Clicked the close download prompt button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickOpen(type: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "clickOpen: Looking for \"OPEN\" download prompt button")
Log.i(TAG, "clickOpen: Waiting for $waitingTime ms for the for \"OPEN\" download prompt button to exist")
openDownloadButton().waitForExists(waitingTime)
Log.i(TAG, "clickOpen: Waited for $waitingTime ms for the for \"OPEN\" download prompt button to exist")
Log.i(TAG, "clickOpen: Trying to click the \"OPEN\" download prompt button")
openDownloadButton().click()
Log.i(TAG, "clickOpen: Clicked \"OPEN\" download prompt button")
Log.i(TAG, "clickOpen: Clicked the \"OPEN\" download prompt button")
Log.i(TAG, "clickOpen: Trying to verify that the open intent is matched with associated data type")
// verify open intent is matched with associated data type
Intents.intended(
allOf(
@ -208,37 +221,38 @@ class DownloadRobot {
IntentMatchers.hasType(type),
),
)
Log.i(TAG, "clickOpen: Verified that open intent is matched with associated data type")
Log.i(TAG, "clickOpen: Verified that the open intent is matched with associated data type")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickAllowPermission(interact: DownloadRobot.() -> Unit): Transition {
Log.i(TAG, "clickAllowPermission: Looking for \"ALLOW\" permission button")
mDevice.waitNotNull(
Until.findObject(By.res(getPermissionAllowID() + ":id/permission_allow_button")),
waitingTime,
)
Log.i(TAG, "clickAllowPermission: Trying to click the \"ALLOW\" permission button")
mDevice.findObject(By.res(getPermissionAllowID() + ":id/permission_allow_button")).click()
Log.i(TAG, "clickAllowPermission: Clicked \"ALLOW\" permission button")
Log.i(TAG, "clickAllowPermission: Clicked the \"ALLOW\" permission button")
DownloadRobot().interact()
return Transition()
}
fun exitDownloadsManagerToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "exitDownloadsManagerToBrowser: Trying to click the navigate up toolbar button")
onView(withContentDescription("Navigate up")).click()
Log.i(TAG, "exitDownloadsManagerToBrowser: Exited download manager to browser by clicking the navigate up toolbar button")
Log.i(TAG, "exitDownloadsManagerToBrowser: Clicked the navigate up toolbar button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "goBack: Trying to click the navigate up toolbar button")
goBackButton().click()
Log.i(TAG, "exitDownloadsManagerToBrowser: Exited download manager to home screen by clicking the navigate up toolbar button")
Log.i(TAG, "goBack: Clicked the navigate up toolbar button")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()

@ -6,6 +6,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers
@ -21,6 +22,7 @@ import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.not
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
@ -36,15 +38,40 @@ import org.mozilla.fenix.helpers.isChecked
* Implementation of Robot Pattern for Enhanced Tracking Protection UI.
*/
class EnhancedTrackingProtectionRobot {
fun verifyEnhancedTrackingProtectionSheetStatus(status: String, state: Boolean) =
assertEnhancedTrackingProtectionSheetStatus(status, state)
fun verifyEnhancedTrackingProtectionSheetStatus(status: String, state: Boolean) {
mDevice.waitNotNull(Until.findObjects(By.text("Protections are $status for this site")))
Log.i(TAG, "verifyEnhancedTrackingProtectionSheetStatus: Trying to check ETP toggle is checked: $state")
onView(ViewMatchers.withResourceName("switch_widget")).check(
matches(
isChecked(
state,
),
),
)
Log.i(TAG, "verifyEnhancedTrackingProtectionSheetStatus: Verified ETP toggle is checked: $state")
}
fun verifyETPSwitchVisibility(visible: Boolean) = assertETPSwitchVisibility(visible)
fun verifyETPSwitchVisibility(visible: Boolean) {
if (visible) {
Log.i(TAG, "verifyETPSwitchVisibility: Trying to verify ETP toggle is displayed")
enhancedTrackingProtectionSwitch()
.check(matches(isDisplayed()))
Log.i(TAG, "verifyETPSwitchVisibility: Verified ETP toggle is displayed")
} else {
Log.i(TAG, "verifyETPSwitchVisibility: Trying to verify ETP toggle is not displayed")
enhancedTrackingProtectionSwitch()
.check(matches(not(isDisplayed())))
Log.i(TAG, "verifyETPSwitchVisibility: Verified ETP toggle is not displayed")
}
}
fun verifyCrossSiteCookiesBlocked(isBlocked: Boolean) {
assertUIObjectExists(itemWithResId("$packageName:id/cross_site_tracking"))
crossSiteCookiesBlockListButton.click()
Log.i(TAG, "verifyCrossSiteCookiesBlocked: Trying to click cross site cookies block list button")
crossSiteCookiesBlockListButton().click()
Log.i(TAG, "verifyCrossSiteCookiesBlocked: Clicked cross site cookies block list button")
// Verifies the trackers block/allow list
Log.i(TAG, "verifyCrossSiteCookiesBlocked: Trying to verify cross site cookies are blocked: $isBlocked")
onView(withId(R.id.details_blocking_header))
.check(
matches(
@ -57,12 +84,16 @@ class EnhancedTrackingProtectionRobot {
),
),
)
Log.i(TAG, "verifyCrossSiteCookiesBlocked: Verified cross site cookies are blocked: $isBlocked")
}
fun verifySocialMediaTrackersBlocked(isBlocked: Boolean) {
assertUIObjectExists(itemWithResId("$packageName:id/social_media_trackers"))
socialTrackersBlockListButton.click()
Log.i(TAG, "verifySocialMediaTrackersBlocked: Trying to click social trackers block list button")
socialTrackersBlockListButton().click()
Log.i(TAG, "verifySocialMediaTrackersBlocked: Clicked social trackers block list button")
// Verifies the trackers block/allow list
Log.i(TAG, "verifySocialMediaTrackersBlocked: Trying to verify social trackers are blocked: $isBlocked")
onView(withId(R.id.details_blocking_header))
.check(
matches(
@ -75,13 +106,19 @@ class EnhancedTrackingProtectionRobot {
),
),
)
Log.i(TAG, "verifySocialMediaTrackersBlocked: Verified social trackers are blocked: $isBlocked")
Log.i(TAG, "verifySocialMediaTrackersBlocked: Trying to verify blocked social trackers list is displayed")
onView(withId(R.id.blocking_text_list)).check(matches(isDisplayed()))
Log.i(TAG, "verifySocialMediaTrackersBlocked: Verified blocked social trackers list is displayed")
}
fun verifyFingerprintersBlocked(isBlocked: Boolean) {
assertUIObjectExists(itemWithResId("$packageName:id/fingerprinters"))
fingerprintersBlockListButton.click()
Log.i(TAG, "verifyFingerprintersBlocked: Trying to click fingerprinters block list button")
fingerprintersBlockListButton().click()
Log.i(TAG, "verifyFingerprintersBlocked: Clicked fingerprinters block list button")
// Verifies the trackers block/allow list
Log.i(TAG, "verifyFingerprintersBlocked: Trying to verify fingerprinters are blocked: $isBlocked")
onView(withId(R.id.details_blocking_header))
.check(
matches(
@ -94,13 +131,19 @@ class EnhancedTrackingProtectionRobot {
),
),
)
Log.i(TAG, "verifyFingerprintersBlocked: Verified fingerprinters are blocked: $isBlocked")
Log.i(TAG, "verifyFingerprintersBlocked: Trying to verify blocked fingerprinter trackers list is displayed")
onView(withId(R.id.blocking_text_list)).check(matches(isDisplayed()))
Log.i(TAG, "verifyFingerprintersBlocked: Verified blocked fingerprinter trackers list is displayed")
}
fun verifyCryptominersBlocked(isBlocked: Boolean) {
assertUIObjectExists(itemWithResId("$packageName:id/cryptominers"))
cryptominersBlockListButton.click()
Log.i(TAG, "verifyCryptominersBlocked: Trying to click cryptominers block list button")
cryptominersBlockListButton().click()
Log.i(TAG, "verifyCryptominersBlocked: Clicked cryptominers block list button")
// Verifies the trackers block/allow list
Log.i(TAG, "verifyCryptominersBlocked: Trying to verify cryptominers are blocked: $isBlocked")
onView(withId(R.id.details_blocking_header))
.check(
matches(
@ -113,13 +156,19 @@ class EnhancedTrackingProtectionRobot {
),
),
)
Log.i(TAG, "verifyCryptominersBlocked: Verified cryptominers are blocked: $isBlocked")
Log.i(TAG, "verifyCryptominersBlocked: Trying to verify blocked cryptominers trackers list is displayed")
onView(withId(R.id.blocking_text_list)).check(matches(isDisplayed()))
Log.i(TAG, "verifyCryptominersBlocked: Verified blocked cryptominers trackers list is displayed")
}
fun verifyTrackingContentBlocked(isBlocked: Boolean) {
assertUIObjectExists(itemWithText("Tracking Content"))
trackingContentBlockListButton.click()
Log.i(TAG, "verifyTrackingContentBlocked: Trying to click tracking content block list button")
trackingContentBlockListButton().click()
Log.i(TAG, "verifyTrackingContentBlocked: Clicked tracking content block list button")
// Verifies the trackers block/allow list
Log.i(TAG, "verifyTrackingContentBlocked: Trying to verify tracking content is blocked: $isBlocked")
onView(withId(R.id.details_blocking_header))
.check(
matches(
@ -132,10 +181,14 @@ class EnhancedTrackingProtectionRobot {
),
),
)
Log.i(TAG, "verifyTrackingContentBlocked: Verified tracking content is blocked: $isBlocked")
Log.i(TAG, "verifyTrackingContentBlocked: Trying to verify blocked tracking content trackers list is displayed")
onView(withId(R.id.blocking_text_list)).check(matches(isDisplayed()))
Log.i(TAG, "verifyTrackingContentBlocked: Verified blocked tracking content trackers list is displayed")
}
fun viewTrackingContentBlockList() {
Log.i(TAG, "viewTrackingContentBlockList: Trying to verify blocked tracking content trackers")
onView(withId(R.id.blocking_text_list))
.check(
matches(
@ -148,6 +201,7 @@ class EnhancedTrackingProtectionRobot {
),
),
)
Log.i(TAG, "viewTrackingContentBlockList: Verified blocked tracking content trackers")
}
fun verifyETPSectionIsDisplayedInQuickSettingsSheet(isDisplayed: Boolean) =
@ -157,14 +211,24 @@ class EnhancedTrackingProtectionRobot {
)
fun navigateBackToDetails() {
Log.i(TAG, "navigateBackToDetails: Trying to click details list back button")
onView(withId(R.id.details_back)).click()
Log.i(TAG, "navigateBackToDetails: Clicked details list back button")
}
class Transition {
fun openEnhancedTrackingProtectionSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
Log.i(TAG, "openEnhancedTrackingProtectionSheet: Waiting for $waitingTime ms for site security button to exist")
pageSecurityIndicator().waitForExists(waitingTime)
Log.i(TAG, "openEnhancedTrackingProtectionSheet: Waited for $waitingTime ms for site security button to exist")
Log.i(TAG, "openEnhancedTrackingProtectionSheet: Trying to click site security button")
pageSecurityIndicator().click()
assertSecuritySheetIsCompletelyDisplayed()
Log.i(TAG, "openEnhancedTrackingProtectionSheet: Clicked site security button")
Log.i(TAG, "openEnhancedTrackingProtectionSheet: Waiting for $waitingTime ms for quick actions sheet to exits")
mDevice.findObject(UiSelector().description(getStringResource(R.string.quick_settings_sheet)))
.waitForExists(waitingTime)
Log.i(TAG, "openEnhancedTrackingProtectionSheet: Waited for $waitingTime ms for quick actions sheet to exits")
assertUIObjectExists(itemWithResId("$packageName:id/quick_action_sheet"))
EnhancedTrackingProtectionRobot().interact()
return Transition()
@ -172,31 +236,45 @@ class EnhancedTrackingProtectionRobot {
fun closeEnhancedTrackingProtectionSheet(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
// Back out of the Enhanced Tracking Protection sheet
Log.i(TAG, "closeEnhancedTrackingProtectionSheet: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "closeEnhancedTrackingProtectionSheet: Clicked device back button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun toggleEnhancedTrackingProtectionFromSheet(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
Log.i(TAG, "toggleEnhancedTrackingProtectionFromSheet: Trying to click ETP switch")
enhancedTrackingProtectionSwitch().click()
Log.i(TAG, "toggleEnhancedTrackingProtectionFromSheet: Clicked ETP switch")
EnhancedTrackingProtectionRobot().interact()
return Transition()
}
fun openProtectionSettings(interact: SettingsSubMenuEnhancedTrackingProtectionRobot.() -> Unit): SettingsSubMenuEnhancedTrackingProtectionRobot.Transition {
Log.i(TAG, "openProtectionSettings: Waiting for $waitingTime ms for ETP sheet \"Details\" button to exist")
openEnhancedTrackingProtectionDetails().waitForExists(waitingTime)
Log.i(TAG, "openProtectionSettings: Waited for $waitingTime ms for ETP sheet \"Details\" button to exist")
Log.i(TAG, "openProtectionSettings: Trying to click ETP sheet \"Details\" button")
openEnhancedTrackingProtectionDetails().click()
Log.i(TAG, "openProtectionSettings: Clicked ETP sheet \"Details\" button")
Log.i(TAG, "openProtectionSettings: Trying to click \"Protection Settings\" button")
trackingProtectionSettingsButton().click()
Log.i(TAG, "openProtectionSettings: Clicked \"Protection Settings\" button")
SettingsSubMenuEnhancedTrackingProtectionRobot().interact()
return SettingsSubMenuEnhancedTrackingProtectionRobot.Transition()
}
fun openDetails(interact: EnhancedTrackingProtectionRobot.() -> Unit): Transition {
Log.i(TAG, "openDetails: Waiting for $waitingTime ms for ETP sheet \"Details\" button to exist")
openEnhancedTrackingProtectionDetails().waitForExists(waitingTime)
Log.i(TAG, "openDetails: Waited for $waitingTime ms for ETP sheet \"Details\" button to exist")
Log.i(TAG, "openDetails: Trying to click ETP sheet \"Details\" button")
openEnhancedTrackingProtectionDetails().click()
Log.i(TAG, "openDetails: Clicked ETP sheet \"Details\" button")
EnhancedTrackingProtectionRobot().interact()
return Transition()
@ -209,27 +287,6 @@ fun enhancedTrackingProtection(interact: EnhancedTrackingProtectionRobot.() -> U
return EnhancedTrackingProtectionRobot.Transition()
}
private fun assertETPSwitchVisibility(visible: Boolean) {
if (visible) {
enhancedTrackingProtectionSwitch()
.check(matches(isDisplayed()))
} else {
enhancedTrackingProtectionSwitch()
.check(matches(not(isDisplayed())))
}
}
private fun assertEnhancedTrackingProtectionSheetStatus(status: String, state: Boolean) {
mDevice.waitNotNull(Until.findObjects(By.text("Protections are $status for this site")))
onView(ViewMatchers.withResourceName("switch_widget")).check(
matches(
isChecked(
state,
),
),
)
}
private fun pageSecurityIndicator() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_security_indicator"))
@ -246,7 +303,7 @@ private fun trackingProtectionSettingsButton() =
private fun openEnhancedTrackingProtectionDetails() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/trackingProtectionDetails"))
private val trackingContentBlockListButton =
private fun trackingContentBlockListButton() =
onView(
allOf(
withText("Tracking Content"),
@ -254,7 +311,7 @@ private val trackingContentBlockListButton =
),
)
private val socialTrackersBlockListButton =
private fun socialTrackersBlockListButton() =
onView(
allOf(
withId(R.id.social_media_trackers),
@ -262,7 +319,7 @@ private val socialTrackersBlockListButton =
),
)
private val crossSiteCookiesBlockListButton =
private fun crossSiteCookiesBlockListButton() =
onView(
allOf(
withId(R.id.cross_site_tracking),
@ -270,7 +327,7 @@ private val crossSiteCookiesBlockListButton =
),
)
private val cryptominersBlockListButton =
private fun cryptominersBlockListButton() =
onView(
allOf(
withId(R.id.cryptominers),
@ -278,16 +335,10 @@ private val cryptominersBlockListButton =
),
)
private val fingerprintersBlockListButton =
private fun fingerprintersBlockListButton() =
onView(
allOf(
withId(R.id.fingerprinters),
withText("Fingerprinters"),
),
)
private fun assertSecuritySheetIsCompletelyDisplayed() {
mDevice.findObject(UiSelector().description(getStringResource(R.string.quick_settings_sheet)))
.waitForExists(waitingTime)
assertUIObjectExists(itemWithResId("$packageName:id/quick_action_sheet"))
}

@ -6,6 +6,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.clearText
import androidx.test.espresso.action.ViewActions.typeText
@ -16,6 +17,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.click
@ -25,52 +27,78 @@ import org.mozilla.fenix.helpers.ext.waitNotNull
* Implementation of Robot Pattern for the find in page UI.
*/
class FindInPageRobot {
fun verifyFindInPageQuery() = assertFindInPageQuery()!!
fun verifyFindInPageNextButton() = assertFindInPageNextButton()!!
fun verifyFindInPagePrevButton() = assertFindInPagePrevButton()!!
fun verifyFindInPageCloseButton() = assertFindInPageCloseButton()!!
fun clickFindInPageNextButton() = findInPageNextButton().click()
fun clickFindInPagePrevButton() = findInPagePrevButton().click()
fun verifyFindInPageSearchBarItems() {
verifyFindInPageQuery()
verifyFindInPageNextButton()
verifyFindInPagePrevButton()
verifyFindInPageCloseButton()
fun verifyFindInPageNextButton() {
Log.i(TAG, "verifyFindInPageNextButton: Trying to verify find in page next result button is visible")
findInPageNextButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyFindInPageNextButton: Verified find in page next result button is visible")
}
fun verifyFindInPagePrevButton() {
Log.i(TAG, "verifyFindInPagePrevButton: Trying to verify find in page previous result button is visible")
findInPagePrevButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyFindInPagePrevButton: Verified find in page previous result button is visible")
}
fun verifyFindInPageCloseButton() {
Log.i(TAG, "verifyFindInPageCloseButton: Trying to verify find in page close button is visible")
findInPageCloseButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyFindInPageCloseButton: Verified find in page close button is visible")
}
fun clickFindInPageNextButton() {
Log.i(TAG, "clickFindInPageNextButton: Trying to click next result button")
findInPageNextButton().click()
Log.i(TAG, "clickFindInPageNextButton: Clicked next result button")
}
fun clickFindInPagePrevButton() {
Log.i(TAG, "clickFindInPagePrevButton: Trying to click previous result button")
findInPagePrevButton().click()
Log.i(TAG, "clickFindInPagePrevButton: Clicked previous result button")
}
fun enterFindInPageQuery(expectedText: String) {
mDevice.waitNotNull(Until.findObject(By.res("org.mozilla.fenix.debug:id/find_in_page_query_text")), waitingTime)
Log.i(TAG, "enterFindInPageQuery: Trying to clear find in page bar text")
findInPageQuery().perform(clearText())
Log.i(TAG, "enterFindInPageQuery: Cleared find in page bar text")
mDevice.waitNotNull(Until.gone(By.res("org.mozilla.fenix.debug:id/find_in_page_result_text")), waitingTime)
Log.i(TAG, "enterFindInPageQuery: Trying to type $expectedText in find in page bar")
findInPageQuery().perform(typeText(expectedText))
Log.i(TAG, "enterFindInPageQuery: Typed $expectedText in find page bar")
mDevice.waitNotNull(Until.findObject(By.res("org.mozilla.fenix.debug:id/find_in_page_result_text")), waitingTime)
}
fun verifyFindNextInPageResult(ratioCounter: String) {
mDevice.waitNotNull(Until.findObject(By.text(ratioCounter)), waitingTime)
findInPageResult().check(matches(withText((ratioCounter))))
}
fun verifyFindPrevInPageResult(ratioCounter: String) {
fun verifyFindInPageResult(ratioCounter: String) {
mDevice.waitNotNull(Until.findObject(By.text(ratioCounter)), waitingTime)
Log.i(TAG, "verifyFindInPageResult: Trying to verify $ratioCounter results")
findInPageResult().check(matches(withText((ratioCounter))))
Log.i(TAG, "verifyFindInPageResult: Verified $ratioCounter results")
}
class Transition {
fun closeFindInPageWithCloseButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "closeFindInPageWithCloseButton: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "closeFindInPageWithCloseButton: Device was idle")
Log.i(TAG, "closeFindInPageWithCloseButton: Trying to close find in page button")
findInPageCloseButton().click()
Log.i(TAG, "closeFindInPageWithCloseButton: Clicked close find in page button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun closeFindInPageWithBackButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "closeFindInPageWithBackButton: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "closeFindInPageWithBackButton: Device was idle")
// Will need to press back 2x, the first will only dismiss the keyboard
Log.i(TAG, "closeFindInPageWithBackButton: Trying to press 1x the device back button")
mDevice.pressBack()
Log.i(TAG, "closeFindInPageWithBackButton: Pressed 1x the device back button")
Log.i(TAG, "closeFindInPageWithBackButton: Trying to press 2x the device back button")
mDevice.pressBack()
Log.i(TAG, "closeFindInPageWithBackButton: Pressed 2x the device back button")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -83,15 +111,3 @@ private fun findInPageResult() = onView(withId(R.id.find_in_page_result_text))
private fun findInPageNextButton() = onView(withId(R.id.find_in_page_next_btn))
private fun findInPagePrevButton() = onView(withId(R.id.find_in_page_prev_btn))
private fun findInPageCloseButton() = onView(withId(R.id.find_in_page_close_btn))
private fun assertFindInPageQuery() = findInPageQuery()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertFindInPageNextButton() = findInPageNextButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertFindInPagePrevButton() = findInPagePrevButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private fun assertFindInPageCloseButton() = findInPageCloseButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))

@ -5,6 +5,7 @@
package org.mozilla.fenix.ui.robots
import android.net.Uri
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
@ -19,9 +20,9 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.Matchers
import org.hamcrest.Matchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
@ -38,17 +39,32 @@ import org.mozilla.fenix.helpers.ext.waitNotNull
*/
class HistoryRobot {
fun verifyHistoryMenuView() = assertHistoryMenuView()
fun verifyHistoryMenuView() {
Log.i(TAG, "verifyHistoryMenuView: Trying to verify that history menu view is visible")
onView(
allOf(withText("History"), withParent(withId(R.id.navigationToolbar))),
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyHistoryMenuView: Verified that history menu view is visible")
}
fun verifyEmptyHistoryView() {
Log.i(TAG, "verifyEmptyHistoryView: Waiting for $waitingTime ms for empty history list view to exist")
mDevice.findObject(
UiSelector().text("No history here"),
).waitForExists(waitingTime)
Log.i(TAG, "verifyEmptyHistoryView: Waited for $waitingTime ms for empty history list view to exist")
assertEmptyHistoryView()
Log.i(TAG, "verifyEmptyHistoryView: Trying to verify empty history list view")
onView(
allOf(
withId(R.id.history_empty_view),
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
),
).check(matches(withText("No history here")))
Log.i(TAG, "verifyEmptyHistoryView: Verified empty history list view")
}
fun verifyHistoryListExists() = assertHistoryListExists()
fun verifyHistoryListExists() = assertUIObjectExists(itemWithResId("$packageName:id/history_list"))
fun verifyVisitedTimeTitle() {
mDevice.waitNotNull(
@ -57,53 +73,87 @@ class HistoryRobot {
),
waitingTime,
)
assertVisitedTimeTitle()
Log.i(TAG, "verifyVisitedTimeTitle: Trying to verify \"Today\" chronological timeline title")
onView(withId(R.id.header_title)).check(matches(withText("Today")))
Log.i(TAG, "verifyVisitedTimeTitle: Verified \"Today\" chronological timeline title")
}
fun verifyHistoryItemExists(shouldExist: Boolean, item: String) =
assertUIObjectExists(itemContainingText(item), exists = shouldExist)
fun verifyFirstTestPageTitle(title: String) = assertTestPageTitle(title)
fun verifyTestPageUrl(expectedUrl: Uri) = pageUrl(expectedUrl.toString()).check(matches(isDisplayed()))
fun verifyCopySnackBarText() = assertCopySnackBarText()
fun verifyFirstTestPageTitle(title: String) {
Log.i(TAG, "verifyFirstTestPageTitle: Trying to verify $title page title is visible")
testPageTitle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(withText(title)))
Log.i(TAG, "verifyFirstTestPageTitle: Verified $title page title is visible")
}
fun verifyDeleteConfirmationMessage() = assertDeleteConfirmationMessage()
fun verifyTestPageUrl(expectedUrl: Uri) {
Log.i(TAG, "verifyTestPageUrl: Trying to verify page url: $expectedUrl is displayed")
pageUrl(expectedUrl.toString()).check(matches(isDisplayed()))
Log.i(TAG, "verifyTestPageUrl: Verified page url: $expectedUrl is displayed")
}
fun verifyHomeScreen() = HomeScreenRobot().verifyHomeScreen()
fun verifyDeleteConfirmationMessage() =
assertUIObjectExists(
itemWithResIdContainingText("$packageName:id/title", getStringResource(R.string.delete_history_prompt_title)),
itemWithResIdContainingText("$packageName:id/body", getStringResource(R.string.delete_history_prompt_body_2)),
)
fun clickDeleteHistoryButton(item: String) {
Log.i(TAG, "clickDeleteHistoryButton: Trying to click delete history button for item: $item")
deleteButton(item).click()
Log.i(TAG, "clickDeleteHistoryButton: Clicked delete history button for item: $item")
}
fun verifyDeleteHistoryItemButton(historyItemTitle: String) =
fun verifyDeleteHistoryItemButton(historyItemTitle: String) {
Log.i(TAG, "verifyDeleteHistoryItemButton: Trying to verify delete history button for item: $historyItemTitle is visible")
deleteButton(historyItemTitle).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyDeleteHistoryItemButton: Verified delete history button for item: $historyItemTitle is visible")
}
fun clickDeleteAllHistoryButton() = deleteButton().click()
fun clickDeleteAllHistoryButton() {
Log.i(TAG, "clickDeleteAllHistoryButton: Trying to click delete all history button")
deleteButton().click()
Log.i(TAG, "clickDeleteAllHistoryButton: Clicked delete all history button")
}
fun selectEverythingOption() = deleteHistoryEverythingOption().click()
fun selectEverythingOption() {
Log.i(TAG, "selectEverythingOption: Trying to click \"Everything\" dialog option")
deleteHistoryEverythingOption().click()
Log.i(TAG, "selectEverythingOption: Clicked \"Everything\" dialog option")
}
fun confirmDeleteAllHistory() {
Log.i(TAG, "confirmDeleteAllHistory: Trying to click \"Delete\" dialog button")
onView(withText("Delete"))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.click()
Log.i(TAG, "confirmDeleteAllHistory: Clicked \"Delete\" dialog button")
}
fun cancelDeleteHistory() =
fun cancelDeleteHistory() {
Log.i(TAG, "cancelDeleteHistory: Trying to click \"Cancel\" dialog button")
mDevice
.findObject(
UiSelector()
.textContains(getStringResource(R.string.delete_browsing_data_prompt_cancel)),
).click()
Log.i(TAG, "cancelDeleteHistory: Clicked \"Cancel\" dialog button")
}
fun verifyDeleteSnackbarText(text: String) = assertSnackBarText(text)
fun verifyUndoDeleteSnackBarButton() = assertUndoDeleteSnackBarButton()
fun verifyUndoDeleteSnackBarButton() {
Log.i(TAG, "verifyUndoDeleteSnackBarButton: Trying to verify \"Undo\" snackbar button")
snackBarUndoButton().check(matches(withText("UNDO")))
Log.i(TAG, "verifyUndoDeleteSnackBarButton: Verified \"Undo\" snackbar button")
}
fun clickUndoDeleteButton() {
Log.i(TAG, "verifyUndoDeleteSnackBarButton: Trying to click \"Undo\" snackbar button")
snackBarUndoButton().click()
Log.i(TAG, "verifyUndoDeleteSnackBarButton: Clicked \"Undo\" snackbar button")
}
fun verifySearchGroupDisplayed(shouldBeDisplayed: Boolean, searchTerm: String, groupSize: Int) =
@ -117,36 +167,50 @@ class HistoryRobot {
)
fun openSearchGroup(searchTerm: String) {
Log.i(TAG, "openSearchGroup: Waiting for $waitingTime ms for search group: $searchTerm to exist")
mDevice.findObject(UiSelector().text(searchTerm)).waitForExists(waitingTime)
Log.i(TAG, "openSearchGroup: Waited for $waitingTime ms for search group: $searchTerm to exist")
Log.i(TAG, "openSearchGroup: Trying to click search group: $searchTerm")
mDevice.findObject(UiSelector().text(searchTerm)).click()
Log.i(TAG, "openSearchGroup: Clicked search group: $searchTerm")
}
class Transition {
fun goBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "goBack: Trying to click go back menu button")
onView(withContentDescription("Navigate up")).click()
Log.i(TAG, "goBack: Clicked go back menu button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun openWebsite(url: Uri, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
assertHistoryListExists()
assertUIObjectExists(itemWithResId("$packageName:id/history_list"))
Log.i(TAG, "openWebsite: Trying to click history item with url: $url")
onView(withText(url.toString())).click()
Log.i(TAG, "openWebsite: Clicked history item with url: $url")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun openRecentlyClosedTabs(interact: RecentlyClosedTabsRobot.() -> Unit): RecentlyClosedTabsRobot.Transition {
recentlyClosedTabsListButton.waitForExists(waitingTime)
recentlyClosedTabsListButton.click()
Log.i(TAG, "openRecentlyClosedTabs: Waiting for $waitingTime ms for \"Recently closed tabs\" button to exist")
recentlyClosedTabsListButton().waitForExists(waitingTime)
Log.i(TAG, "openRecentlyClosedTabs: Waited for $waitingTime ms for \"Recently closed tabs\" button to exist")
Log.i(TAG, "openRecentlyClosedTabs: Trying to click \"Recently closed tabs\" button")
recentlyClosedTabsListButton().click()
Log.i(TAG, "openRecentlyClosedTabs: Clicked \"Recently closed tabs\" button")
RecentlyClosedTabsRobot().interact()
return RecentlyClosedTabsRobot.Transition()
}
fun clickSearchButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
Log.i(TAG, "clickSearchButton: Trying to click search history button")
itemWithResId("$packageName:id/history_search").click()
Log.i(TAG, "clickSearchButton: Clicked search history button")
SearchRobot().interact()
return SearchRobot.Transition()
@ -168,50 +232,8 @@ private fun deleteButton(title: String) =
private fun deleteButton() = onView(withId(R.id.history_delete))
private fun snackBarText() = onView(withId(R.id.snackbar_text))
private fun assertHistoryMenuView() {
onView(
allOf(withText("History"), withParent(withId(R.id.navigationToolbar))),
)
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
private fun assertEmptyHistoryView() =
onView(
allOf(
withId(R.id.history_empty_view),
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
),
)
.check(matches(withText("No history here")))
private fun assertHistoryListExists() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/history_list")).waitForExists(waitingTime)
private fun assertVisitedTimeTitle() =
onView(withId(R.id.header_title)).check(matches(withText("Today")))
private fun assertTestPageTitle(title: String) = testPageTitle()
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.check(matches(withText(title)))
private fun assertDeleteConfirmationMessage() =
assertUIObjectExists(
itemWithResIdContainingText("$packageName:id/title", getStringResource(R.string.delete_history_prompt_title)),
itemWithResIdContainingText("$packageName:id/body", getStringResource(R.string.delete_history_prompt_body_2)),
)
private fun assertCopySnackBarText() = snackBarText().check(matches(withText("URL copied")))
private fun assertSnackBarText(text: String) =
snackBarText().check(matches(withText(Matchers.containsString(text))))
private fun snackBarUndoButton() = onView(withId(R.id.snackbar_btn))
private fun assertUndoDeleteSnackBarButton() =
snackBarUndoButton().check(matches(withText("UNDO")))
private fun deleteHistoryEverythingOption() =
mDevice
.findObject(
@ -220,5 +242,5 @@ private fun deleteHistoryEverythingOption() =
.resourceId("$packageName:id/everything_button"),
)
private val recentlyClosedTabsListButton =
private fun recentlyClosedTabsListButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/recently_closed_tabs_header"))

@ -5,7 +5,7 @@
package org.mozilla.fenix.ui.robots
import android.net.Uri
import android.widget.TextView
import android.util.Log
import androidx.compose.ui.test.onNodeWithTag
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
@ -16,12 +16,12 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
@ -34,30 +34,90 @@ import org.mozilla.fenix.tabstray.TabsTrayTestTag
*/
class LibrarySubMenusMultipleSelectionToolbarRobot {
fun verifyMultiSelectionCheckmark() = assertMultiSelectionCheckmark()
fun verifyMultiSelectionCheckmark(url: Uri) = assertMultiSelectionCheckmark(url)
fun verifyMultiSelectionCheckmark() {
Log.i(TAG, "verifyMultiSelectionCheckmark: Trying to verify that the multi-selection checkmark is displayed")
onView(withId(R.id.checkmark)).check(matches(isDisplayed()))
Log.i(TAG, "verifyMultiSelectionCheckmark: Verified that the multi-selection checkmark is displayed")
}
fun verifyMultiSelectionCounter() = assertMultiSelectionCounter()
fun verifyMultiSelectionCheckmark(url: Uri) {
Log.i(TAG, "verifyMultiSelectionCheckmark: Trying to verify that the multi-selection checkmark for item with url: $url is displayed")
onView(
allOf(
withId(R.id.checkmark),
withParent(
withParent(
withChild(
allOf(
withId(R.id.url),
withText(url.toString()),
),
),
),
),
// This is used as part of the `multiSelectionToolbarItemsTest` test. Somehow, in the view hierarchy,
// the match above is finding two checkmark views - one visible, one hidden, which is throwing off
// the matcher. This 'isDisplayed' check is a hacky workaround for this, we're explicitly ignoring
// the hidden one. Why are there two to begin with, though?
isDisplayed(),
),
).check(matches(isDisplayed()))
Log.i(Constants.TAG, "verifyMultiSelectionCheckmark: Verified that the multi-selection checkmark for item with url: $url is displayed")
}
fun verifyShareHistoryButton() = assertShareHistoryButton()
fun verifyMultiSelectionCounter() {
Log.i(TAG, "verifyMultiSelectionCounter: Trying to verify that the multi-selection toolbar containing: \"1 selected\" is displayed")
onView(withText("1 selected")).check(matches(isDisplayed()))
Log.i(TAG, "verifyMultiSelectionCounter: Verified that the multi-selection toolbar containing: \"1 selected\" is displayed")
}
fun verifyShareBookmarksButton() = assertShareBookmarksButton()
fun verifyShareHistoryButton() {
Log.i(TAG, "verifyShareHistoryButton: Trying to verify that the multi-selection share history button is displayed")
shareHistoryButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyShareHistoryButton: Verified that the multi-selection share history button is displayed")
}
fun verifyShareOverlay() = assertShareOverlay()
fun verifyShareBookmarksButton() {
Log.i(TAG, "verifyShareBookmarksButton: Trying to verify that the multi-selection share bookmarks button is displayed")
shareBookmarksButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyShareBookmarksButton: Verified that the multi-selection share bookmarks button is displayed")
}
fun verifyShareAppsLayout() = assertShareAppsLayout()
fun verifyShareOverlay() {
Log.i(TAG, "verifyShareOverlay: Trying to verify that the share overlay is displayed")
onView(withId(R.id.shareWrapper)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareOverlay: Verified that the share overlay is displayed")
}
fun verifyShareTabFavicon() = assertShareTabFavicon()
fun verifyShareTabFavicon() {
Log.i(TAG, "verifyShareTabFavicon: Trying to verify that the shared tab favicon is displayed")
onView(withId(R.id.share_tab_favicon)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareTabFavicon: Verified that the shared tab favicon is displayed")
}
fun verifyShareTabTitle() = assertShareTabTitle()
fun verifyShareTabTitle() {
Log.i(TAG, "verifyShareTabTitle: Trying to verify that the shared tab title is displayed")
onView(withId(R.id.share_tab_title)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareTabTitle: Verified that the shared tab title is displayed")
}
fun verifyShareTabUrl() = assertShareTabUrl()
fun verifyShareTabUrl() {
Log.i(TAG, "verifyShareTabUrl: Trying to verify that the shared tab url is displayed")
onView(withId(R.id.share_tab_url)).check(matches(isDisplayed()))
Log.i(TAG, "verifyShareTabUrl: Verified that the shared tab url is displayed")
}
fun verifyCloseToolbarButton() = assertCloseToolbarButton()
fun verifyCloseToolbarButton() {
Log.i(TAG, "verifyCloseToolbarButton: Trying to verify that the navigate up toolbar button is displayed")
closeToolbarButton().check(matches(isDisplayed()))
Log.i(TAG, "verifyCloseToolbarButton: Verified that the navigate up toolbar button is displayed")
}
fun clickShareHistoryButton() {
Log.i(TAG, "clickShareHistoryButton: Trying to click the multi-selection share history button")
shareHistoryButton().click()
Log.i(TAG, "clickShareHistoryButton: Clicked the multi-selection share history button")
mDevice.waitNotNull(
Until.findObject(
@ -68,7 +128,9 @@ class LibrarySubMenusMultipleSelectionToolbarRobot {
}
fun clickShareBookmarksButton() {
Log.i(TAG, "clickShareBookmarksButton: Trying to click the multi-selection share bookmarks button")
shareBookmarksButton().click()
Log.i(TAG, "clickShareBookmarksButton: Clicked the multi-selection share bookmarks button")
mDevice.waitNotNull(
Until.findObject(
@ -79,31 +141,35 @@ class LibrarySubMenusMultipleSelectionToolbarRobot {
}
fun clickMultiSelectionDelete() {
Log.i(TAG, "clickMultiSelectionDelete: Trying to click the multi-selection delete button")
deleteButton().click()
Log.i(TAG, "clickMultiSelectionDelete: Clicked the multi-selection delete button")
}
class Transition {
fun closeShareDialogReturnToPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun closeToolbarReturnToHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
Log.i(TAG, "closeToolbarReturnToHistory: Trying to click the navigate up toolbar button")
closeToolbarButton().click()
Log.i(TAG, "closeToolbarReturnToHistory: Clicked the navigate up toolbar button")
HistoryRobot().interact()
return HistoryRobot.Transition()
}
fun closeToolbarReturnToBookmarks(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition {
Log.i(TAG, "closeToolbarReturnToBookmarks: Trying to click the navigate up toolbar button")
closeToolbarButton().click()
Log.i(TAG, "closeToolbarReturnToBookmarks: Clicked the navigate up toolbar button")
BookmarksRobot().interact()
return BookmarksRobot.Transition()
}
fun clickOpenNewTab(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
Log.i(TAG, "clickOpenNewTab: Trying to click the multi-select \"Open in a new tab\" context menu button")
openInNewTabButton().click()
Log.i(TAG, "clickOpenNewTab: Clicked the multi-select \"Open in a new tab\" context menu button")
mDevice.waitNotNull(
Until.findObject(By.res("$packageName:id/tab_layout")),
waitingTime,
@ -114,15 +180,21 @@ class LibrarySubMenusMultipleSelectionToolbarRobot {
}
fun clickOpenNewTab(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
Log.i(TAG, "clickOpenNewTab: Trying to click the multi-select \"Open in a new tab\" context menu button")
openInNewTabButton().click()
Log.i(TAG, "clickOpenNewTab: Clicked the multi-select \"Open in a new tab\" context menu button")
Log.i(TAG, "clickOpenNewTab: Trying to verify that the tabs tray exists")
composeTestRule.onNodeWithTag(TabsTrayTestTag.tabsTray).assertExists()
Log.i(TAG, "clickOpenNewTab: Verified that the tabs tray exists")
ComposeTabDrawerRobot(composeTestRule).interact()
return ComposeTabDrawerRobot.Transition(composeTestRule)
}
fun clickOpenPrivateTab(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
Log.i(TAG, "clickOpenPrivateTab: Trying to click the multi-select \"Open in a private tab\" context menu button")
openInPrivateTabButton().click()
Log.i(TAG, "clickOpenPrivateTab: Clicked the multi-select \"Open in a private tab\" context menu button")
mDevice.waitNotNull(
Until.findObject(By.res("$packageName:id/tab_layout")),
waitingTime,
@ -133,7 +205,9 @@ class LibrarySubMenusMultipleSelectionToolbarRobot {
}
fun clickOpenPrivateTab(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
Log.i(TAG, "clickOpenPrivateTab: Trying to click the multi-select \"Open in a private tab\" context menu button")
openInPrivateTabButton().click()
Log.i(TAG, "clickOpenPrivateTab: Clicked the multi-select \"Open in a private tab\" context menu button")
ComposeTabDrawerRobot(composeTestRule).interact()
return ComposeTabDrawerRobot.Transition(composeTestRule)
@ -157,53 +231,3 @@ private fun openInNewTabButton() = onView(withText("Open in new tab"))
private fun openInPrivateTabButton() = onView(withText("Open in private tab"))
private fun deleteButton() = onView(withText("Delete"))
private fun assertMultiSelectionCheckmark() =
onView(withId(R.id.checkmark))
.check(matches(isDisplayed()))
private fun assertMultiSelectionCheckmark(url: Uri) =
onView(
allOf(
withId(R.id.checkmark),
withParent(withParent(withChild(allOf(withId(R.id.url), withText(url.toString()))))),
// This is used as part of the `multiSelectionToolbarItemsTest` test. Somehow, in the view hierarchy,
// the match above is finding two checkmark views - one visible, one hidden, which is throwing off
// the matcher. This 'isDisplayed' check is a hacky workaround for this, we're explicitly ignoring
// the hidden one. Why are there two to begin with, though?
isDisplayed(),
),
)
.check(matches(isDisplayed()))
private fun assertMultiSelectionCounter() =
onView(withText("1 selected")).check(matches(isDisplayed()))
private fun assertShareHistoryButton() =
shareHistoryButton().check(matches(isDisplayed()))
private fun assertShareBookmarksButton() =
shareBookmarksButton().check(matches(isDisplayed()))
private fun assertShareOverlay() =
onView(withId(R.id.shareWrapper)).check(matches(isDisplayed()))
private fun assertShareAppsLayout() = {
val sendToDeviceTitle = mDevice.findObject(
UiSelector()
.instance(0)
.className(TextView::class.java),
)
sendToDeviceTitle.waitForExists(TestAssetHelper.waitingTime)
}
private fun assertShareTabTitle() =
onView(withId(R.id.share_tab_title)).check(matches(isDisplayed()))
private fun assertShareTabFavicon() =
onView(withId(R.id.share_tab_favicon)).check(matches(isDisplayed()))
private fun assertShareTabUrl() = onView(withId(R.id.share_tab_url))
private fun assertCloseToolbarButton() = closeToolbarButton().check(matches(isDisplayed()))

@ -31,7 +31,6 @@ import androidx.test.uiautomator.By.textContains
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants
@ -58,27 +57,55 @@ import org.mozilla.fenix.tabstray.TabsTrayTestTag
* Implementation of Robot Pattern for the URL toolbar.
*/
class NavigationToolbarRobot {
fun verifyUrl(url: String) =
fun verifyUrl(url: String) {
Log.i(TAG, "verifyUrl: Trying to verify toolbar text matches $url")
onView(withId(R.id.mozac_browser_toolbar_url_view)).check(matches(withText(url)))
Log.i(TAG, "verifyUrl: Verified toolbar text matches $url")
}
fun verifyNoHistoryBookmarks() = assertNoHistoryBookmarks()
fun verifyTabButtonShortcutMenuItems() = assertTabButtonShortcutMenuItems()
fun verifyReaderViewDetected(visible: Boolean = false) =
assertReaderViewDetected(visible)
fun verifyTabButtonShortcutMenuItems() {
Log.i(TAG, "verifyTabButtonShortcutMenuItems: Trying to verify tab counter shortcut options")
onView(withId(R.id.mozac_browser_menu_recyclerView))
.check(matches(hasDescendant(withText("Close tab"))))
.check(matches(hasDescendant(withText("New private tab"))))
.check(matches(hasDescendant(withText("New tab"))))
Log.i(TAG, "verifyTabButtonShortcutMenuItems: Verified tab counter shortcut options")
}
fun verifyCloseReaderViewDetected(visible: Boolean = false) =
assertCloseReaderViewDetected(visible)
fun verifyReaderViewDetected(visible: Boolean = false) {
Log.i(TAG, "verifyReaderViewDetected: Waiting for $waitingTime ms for reader view button to exist")
mDevice.findObject(
UiSelector()
.description("Reader view"),
).waitForExists(waitingTime)
Log.i(TAG, "verifyReaderViewDetected: Waited for $waitingTime ms for reader view button to exist")
Log.i(TAG, "verifyReaderViewDetected: Trying to verify that the reader view button is visible")
onView(
allOf(
withParent(withId(R.id.mozac_browser_toolbar_page_actions)),
withContentDescription("Reader view"),
),
).check(
if (visible) {
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
} else {
ViewAssertions.doesNotExist()
},
)
Log.i(TAG, "verifyReaderViewDetected: Verified that the reader view button is visible")
}
fun toggleReaderView() {
Log.i(TAG, "toggleReaderView: Waiting for $waitingTime ms for reader view button to exist")
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/mozac_browser_toolbar_page_actions"),
)
.waitForExists(waitingTime)
Log.i(TAG, "toggleReaderView: Waited for $waitingTime ms for reader view button to exist")
Log.i(TAG, "toggleReaderView: Trying to click the reader view button")
readerViewToggle().click()
Log.i(TAG, "toggleReaderView: Clicked the reader view button")
}
fun verifyClipboardSuggestionsAreDisplayed(link: String = "", shouldBeDisplayed: Boolean) =
@ -91,35 +118,49 @@ class NavigationToolbarRobot {
exists = shouldBeDisplayed,
)
fun longClickEditModeToolbar() =
mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_edit_url_view")).click(LONG_CLICK_DURATION)
fun longClickEditModeToolbar() {
Log.i(TAG, "longClickEditModeToolbar: Trying to long click the edit mode toolbar")
mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_edit_url_view"))
.click(LONG_CLICK_DURATION)
Log.i(TAG, "longClickEditModeToolbar: Long clicked the edit mode toolbar")
}
fun clickContextMenuItem(item: String) {
mDevice.waitNotNull(
Until.findObject(By.text(item)),
waitingTime,
)
Log.i(TAG, "clickContextMenuItem: Trying click context menu item: $item")
mDevice.findObject(By.text(item)).click()
Log.i(TAG, "clickContextMenuItem: Clicked context menu item: $item")
}
fun clickClearToolbarButton() = clearAddressBarButton().click()
fun clickClearToolbarButton() {
Log.i(TAG, "clickClearToolbarButton: Trying click the clear address button")
clearAddressBarButton().click()
Log.i(TAG, "clickClearToolbarButton: Clicked the clear address button")
}
fun verifyToolbarIsEmpty() =
itemWithResIdContainingText(
"$packageName:id/mozac_browser_toolbar_edit_url_view",
getStringResource(R.string.search_hint),
assertUIObjectExists(
itemWithResIdContainingText(
"$packageName:id/mozac_browser_toolbar_edit_url_view",
getStringResource(R.string.search_hint),
),
)
// New unified search UI selector
fun verifySearchBarPlaceholder(text: String) {
Log.i(TAG, "verifySearchBarPlaceholder: Waiting for $waitingTime ms for the toolbar to exist")
urlBar().waitForExists(waitingTime)
Log.i(TAG, "verifySearchBarPlaceholder: Waited for $waitingTime ms for the toolbar to exist")
assertItemTextEquals(urlBar(), expectedText = text)
}
// New unified search UI selector
fun verifyDefaultSearchEngine(engineName: String) =
assertUIObjectExists(
searchSelectorButton.getChild(UiSelector().description(engineName)),
searchSelectorButton().getChild(UiSelector().description(engineName)),
)
fun verifyTextSelectionOptions(vararg textSelectionOptions: String) {
@ -138,19 +179,21 @@ class NavigationToolbarRobot {
sessionLoadedIdlingResource = SessionLoadedIdlingResource()
openEditURLView()
Log.i(TAG, "enterURLAndEnterToBrowser: Opened edit mode URL view")
Log.i(TAG, "enterURLAndEnterToBrowser: Trying to set toolbar text to: $url")
awesomeBar().setText(url.toString())
Log.i(TAG, "enterURLAndEnterToBrowser: Set toolbar text to: $url")
Log.i(TAG, "enterURLAndEnterToBrowser: Toolbar text was set to: $url")
Log.i(TAG, "enterURLAndEnterToBrowser: Trying to press device enter button")
mDevice.pressEnter()
Log.i(TAG, "enterURLAndEnterToBrowser: Clicked enter on keyboard, submitted query")
Log.i(TAG, "enterURLAndEnterToBrowser: Pressed device enter button")
runWithIdleRes(sessionLoadedIdlingResource) {
Log.i(TAG, "enterURLAndEnterToBrowser: Trying to assert that home screen layout or download button or the total cookie protection contextual hint exist")
assertTrue(
itemWithResId("$packageName:id/browserLayout").waitForExists(waitingTime) ||
itemWithResId("$packageName:id/download_button").waitForExists(waitingTime) ||
itemWithResId("cfr.dismiss").waitForExists(waitingTime),
)
Log.i(TAG, "enterURLAndEnterToBrowser: Asserted that home screen layout or download button or the total cookie protection contextual hint exist")
}
BrowserRobot().interact()
@ -162,9 +205,12 @@ class NavigationToolbarRobot {
interact: BrowserRobot.() -> Unit,
): BrowserRobot.Transition {
openEditURLView()
Log.i(TAG, "enterURLAndEnterToBrowserForTCPCFR: Trying to set toolbar text to: $url")
awesomeBar().setText(url.toString())
Log.i(TAG, "enterURLAndEnterToBrowserForTCPCFR: Toolbar text was set to: $url")
Log.i(TAG, "enterURLAndEnterToBrowserForTCPCFR: Trying to press device enter button")
mDevice.pressEnter()
Log.i(TAG, "enterURLAndEnterToBrowserForTCPCFR: Pressed device enter button")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -176,12 +222,17 @@ class NavigationToolbarRobot {
sessionLoadedIdlingResource = SessionLoadedIdlingResource()
openEditURLView()
Log.i(TAG, "openTabCrashReporter: Trying to set toolbar text to: $crashUrl")
awesomeBar().setText(crashUrl)
Log.i(TAG, "openTabCrashReporter: Toolbar text was set to: $crashUrl")
Log.i(TAG, "openTabCrashReporter: Trying to press device enter button")
mDevice.pressEnter()
Log.i(TAG, "openTabCrashReporter: Pressed device enter button")
runWithIdleRes(sessionLoadedIdlingResource) {
Log.i(TAG, "openTabCrashReporter: Trying to find the tab crasher image")
mDevice.findObject(UiSelector().resourceId("$packageName:id/crash_tab_image"))
Log.i(TAG, "openTabCrashReporter: Found the tab crasher image")
}
BrowserRobot().interact()
@ -190,15 +241,21 @@ class NavigationToolbarRobot {
fun openThreeDotMenu(interact: ThreeDotMenuMainRobot.() -> Unit): ThreeDotMenuMainRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/mozac_browser_toolbar_menu")), waitingTime)
Log.i(TAG, "openThreeDotMenu: Trying to click the main menu button")
threeDotButton().click()
Log.i(TAG, "openThreeDotMenu: Clicked the main menu button")
ThreeDotMenuMainRobot().interact()
return ThreeDotMenuMainRobot.Transition()
}
fun openTabTray(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
Log.i(TAG, "openTabTray: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "openTabTray: Waited for device to be idle for $waitingTime ms")
Log.i(TAG, "openTabTray: Trying to click the tabs tray button")
tabTrayButton().click()
Log.i(TAG, "openTabTray: Clicked the tabs tray button")
mDevice.waitNotNull(
Until.findObject(By.res("$packageName:id/tab_layout")),
waitingTime,
@ -211,6 +268,7 @@ class NavigationToolbarRobot {
fun openComposeTabDrawer(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
for (i in 1..Constants.RETRY_COUNT) {
try {
Log.i(TAG, "openComposeTabDrawer: Started try #$i")
mDevice.waitForObjects(
mDevice.findObject(
UiSelector()
@ -218,30 +276,40 @@ class NavigationToolbarRobot {
),
waitingTime,
)
Log.i(TAG, "openComposeTabDrawer: Trying to click the tabs tray button")
tabTrayButton().click()
Log.i(TAG, "openComposeTabDrawer: Clicked the tabs tray button")
Log.i(TAG, "openComposeTabDrawer: Trying to verify that the tabs tray exists")
composeTestRule.onNodeWithTag(TabsTrayTestTag.tabsTray).assertExists()
Log.i(TAG, "openComposeTabDrawer: Verified that the tabs tray exists")
break
} catch (e: AssertionError) {
Log.i(TAG, "openComposeTabDrawer: AssertionError caught, executing fallback methods")
if (i == Constants.RETRY_COUNT) {
throw e
} else {
Log.i(TAG, "openComposeTabDrawer: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "openComposeTabDrawer: Waited for device to be idle")
}
}
}
Log.i(TAG, "openComposeTabDrawer: Trying to verify the tabs tray new tab FAB button exists")
composeTestRule.onNodeWithTag(TabsTrayTestTag.fab).assertExists()
Log.i(TAG, "openComposeTabDrawer: Verified the tabs tray new tab FAB button exists")
ComposeTabDrawerRobot(composeTestRule).interact()
return ComposeTabDrawerRobot.Transition(composeTestRule)
}
fun visitLinkFromClipboard(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "visitLinkFromClipboard: Waiting for $waitingTimeShort ms for clear address button to exist")
if (clearAddressBarButton().waitForExists(waitingTimeShort)) {
Log.i(TAG, "visitLinkFromClipboard: Waited for $waitingTimeShort ms for clear address button to exist")
Log.i(TAG, "visitLinkFromClipboard: Trying to click the clear address button")
clearAddressBarButton().click()
Log.i(TAG, "visitLinkFromClipboard: Clicked the clear address button")
}
mDevice.waitNotNull(
@ -257,26 +325,33 @@ class NavigationToolbarRobot {
waitingTime,
)
}
Log.i(TAG, "visitLinkFromClipboard: Trying to click the fill link from clipboard button")
fillLinkButton().click()
Log.i(TAG, "visitLinkFromClipboard: Clicked the fill link from clipboard button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun goBackToHomeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "goBackToHomeScreen: Trying to click the device back button")
mDevice.pressBack()
Log.i(TAG, "goBackToHomeScreen: Clicked the device back button")
Log.i(TAG, "goBackToHomeScreen: Waiting for $waitingTimeShort ms for $packageName window to be updated")
mDevice.waitForWindowUpdate(packageName, waitingTimeShort)
Log.i(TAG, "goBackToHomeScreen: Waited for $waitingTimeShort ms for $packageName window to be updated")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun goBackToBrowserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "goBackToBrowserScreen: Trying to click the device back button")
mDevice.pressBack()
Log.i(TAG, "goBackToBrowserScreen: Dismiss awesome bar using device back button")
Log.i(TAG, "goBackToBrowserScreen: Clicked the device back button")
Log.i(TAG, "goBackToBrowserScreen: Waiting for $waitingTimeShort ms for $packageName window to be updated")
mDevice.waitForWindowUpdate(packageName, waitingTimeShort)
Log.i(TAG, "goBackToBrowserScreen: Waited $waitingTimeShort for window update")
Log.i(TAG, "goBackToBrowserScreen: Waited for $waitingTimeShort ms for $packageName window to be updated")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -284,16 +359,19 @@ class NavigationToolbarRobot {
fun openTabButtonShortcutsMenu(interact: NavigationToolbarRobot.() -> Unit): Transition {
mDevice.waitNotNull(Until.findObject(By.res("$packageName:id/counter_root")))
Log.i(TAG, "openTabButtonShortcutsMenu: Trying to long click the tab counter button")
tabsCounter().perform(longClick())
Log.i(TAG, "Tabs counter long-click successful.")
Log.i(TAG, "openTabButtonShortcutsMenu: Long clicked the tab counter button")
NavigationToolbarRobot().interact()
return Transition()
}
fun closeTabFromShortcutsMenu(interact: NavigationToolbarRobot.() -> Unit): Transition {
Log.i(TAG, "closeTabFromShortcutsMenu: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "closeTabFromShortcutsMenu: Waited for device to be idle for $waitingTime ms")
Log.i(TAG, "closeTabFromShortcutsMenu: Trying to click the \"Close tab\" button")
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
@ -303,15 +381,17 @@ class NavigationToolbarRobot {
ViewActions.click(),
),
)
Log.i(TAG, "Clicked the tab shortcut Close tab button.")
Log.i(TAG, "closeTabFromShortcutsMenu: Clicked the \"Close tab\" button")
NavigationToolbarRobot().interact()
return Transition()
}
fun openNewTabFromShortcutsMenu(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
Log.i(TAG, "openNewTabFromShortcutsMenu: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "Looking for tab shortcut New tab button.")
Log.i(TAG, "openNewTabFromShortcutsMenu: Waited for device to be idle for $waitingTime ms")
Log.i(TAG, "openNewTabFromShortcutsMenu: Trying to click the \"New tab\" button")
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
@ -321,15 +401,17 @@ class NavigationToolbarRobot {
ViewActions.click(),
),
)
Log.i(TAG, "Clicked the tab shortcut New tab button.")
Log.i(TAG, "openNewTabFromShortcutsMenu: Clicked the \"New tab\" button")
SearchRobot().interact()
return SearchRobot.Transition()
}
fun openNewPrivateTabFromShortcutsMenu(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
Log.i(TAG, "openNewPrivateTabFromShortcutsMenu: Waiting for device to be idle for $waitingTime ms")
mDevice.waitForIdle(waitingTime)
Log.i(TAG, "Looking for tab shortcut New private tab button.")
Log.i(TAG, "openNewPrivateTabFromShortcutsMenu: Waited for device to be idle for $waitingTime ms")
Log.i(TAG, "openNewPrivateTabFromShortcutsMenu: Trying to click the \"New private tab\" button")
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
@ -339,26 +421,33 @@ class NavigationToolbarRobot {
ViewActions.click(),
),
)
Log.i(TAG, "Clicked the tab shortcut New private tab button.")
Log.i(TAG, "openNewPrivateTabFromShortcutsMenu: Clicked the \"New private tab\" button")
SearchRobot().interact()
return SearchRobot.Transition()
}
fun clickUrlbar(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
Log.i(TAG, "clickUrlbar: Trying to click the toolbar")
urlBar().click()
Log.i(TAG, "clickUrlbar: Clicked the toolbar")
Log.i(TAG, "clickUrlbar: Waiting for $waitingTime ms for the edit mode toolbar to exist")
mDevice.findObject(
UiSelector().resourceId("$packageName:id/mozac_browser_toolbar_edit_url_view"),
).waitForExists(waitingTime)
Log.i(TAG, "clickUrlbar: Waited for $waitingTime ms for the edit mode toolbar to exist")
SearchRobot().interact()
return SearchRobot.Transition()
}
fun clickSearchSelectorButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
searchSelectorButton.waitForExists(waitingTime)
searchSelectorButton.click()
Log.i(TAG, "clickSearchSelectorButton: Waiting for $waitingTime ms for the search selector button to exist")
searchSelectorButton().waitForExists(waitingTime)
Log.i(TAG, "clickSearchSelectorButton: Waited for $waitingTime ms for the search selector button to exist")
Log.i(TAG, "clickSearchSelectorButton: Trying to click the search selector button")
searchSelectorButton().click()
Log.i(TAG, "clickSearchSelectorButton: Clicked the search selector button")
SearchRobot().interact()
return SearchRobot.Transition()
@ -372,25 +461,15 @@ fun navigationToolbar(interact: NavigationToolbarRobot.() -> Unit): NavigationTo
}
fun openEditURLView() {
Log.i(TAG, "openEditURLView: Waiting for $waitingTime ms for the toolbar to exist")
urlBar().waitForExists(waitingTime)
Log.i(TAG, "openEditURLView: Waited for $waitingTime ms for the toolbar to exist")
Log.i(TAG, "openEditURLView: Trying to click the toolbar")
urlBar().click()
Log.i(TAG, "openEditURLView: URL bar clicked.")
Log.i(TAG, "openEditURLView: Clicked the toolbar")
Log.i(TAG, "openEditURLView: Waiting for $waitingTime ms for the edit mode toolbar to exist")
itemWithResId("$packageName:id/mozac_browser_toolbar_edit_url_view").waitForExists(waitingTime)
Log.i(TAG, "openEditURLView: Edit URL bar displayed.")
}
private fun assertNoHistoryBookmarks() {
onView(withId(R.id.container))
.check(matches(not(hasDescendant(withText("Test_Page_1")))))
.check(matches(not(hasDescendant(withText("Test_Page_2")))))
.check(matches(not(hasDescendant(withText("Test_Page_3")))))
}
private fun assertTabButtonShortcutMenuItems() {
onView(withId(R.id.mozac_browser_menu_recyclerView))
.check(matches(hasDescendant(withText("Close tab"))))
.check(matches(hasDescendant(withText("New private tab"))))
.check(matches(hasDescendant(withText("New tab"))))
Log.i(TAG, "openEditURLView: Waited for $waitingTime ms for the edit mode toolbar to exist")
}
private fun urlBar() = mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))
@ -404,49 +483,7 @@ private fun clearAddressBarButton() = itemWithResId("$packageName:id/mozac_brows
private fun readerViewToggle() =
onView(withParent(withId(R.id.mozac_browser_toolbar_page_actions)))
private fun assertReaderViewDetected(visible: Boolean) {
mDevice.findObject(
UiSelector()
.description("Reader view"),
)
.waitForExists(waitingTime)
onView(
allOf(
withParent(withId(R.id.mozac_browser_toolbar_page_actions)),
withContentDescription("Reader view"),
),
).check(
if (visible) {
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
} else {
ViewAssertions.doesNotExist()
},
)
}
private fun assertCloseReaderViewDetected(visible: Boolean) {
mDevice.findObject(
UiSelector()
.description("Close reader view"),
)
.waitForExists(waitingTime)
onView(
allOf(
withParent(withId(R.id.mozac_browser_toolbar_page_actions)),
withContentDescription("Close reader view"),
),
).check(
if (visible) {
matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))
} else {
ViewAssertions.doesNotExist()
},
)
}
private val searchSelectorButton =
private fun searchSelectorButton() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/search_selector"))
inline fun runWithIdleRes(ir: IdlingResource?, pendingCheck: () -> Unit) {

@ -29,44 +29,48 @@ class NotificationRobot {
var notificationFound = mDevice.findObject(notification).waitForExists(waitingTime)
while (!notificationFound) {
Log.i(TAG, "verifySystemNotificationExists: Waiting for $waitingTime ms for notification: $notification to exist")
scrollToEnd()
Log.i(TAG, "verifySystemNotificationExists: Scrolling to the end of the notification tray")
Log.i(TAG, "verifySystemNotificationExists: Looking for $notificationMessage notification")
notificationFound = mDevice.findObject(notification).waitForExists(waitingTime)
Log.i(TAG, "verifySystemNotificationExists: Waited for $waitingTime ms for notification: $notification to exist")
}
assertUIObjectExists(itemWithText(notificationMessage))
}
fun clearNotifications() {
if (clearButton.exists()) {
Log.i(TAG, "clearNotifications: Verified that clear notifications button exists")
clearButton.click()
Log.i(TAG, "clearNotifications: Clicked clear notifications button")
if (clearButton().exists()) {
Log.i(TAG, "clearNotifications:The clear notifications button exists")
Log.i(TAG, "clearNotifications: Trying to click the clear notifications button")
clearButton().click()
Log.i(TAG, "clearNotifications: Clicked the clear notifications button")
} else {
scrollToEnd()
Log.i(TAG, "clearNotifications: Scrolled to end of notifications tray")
if (clearButton.exists()) {
Log.i(TAG, "clearNotifications: Verified that clear notifications button exists")
clearButton.click()
Log.i(TAG, "clearNotifications: Clicked clear notifications button")
if (clearButton().exists()) {
Log.i(TAG, "clearNotifications:The clear notifications button exists")
Log.i(TAG, "clearNotifications: Trying to click the clear notifications button")
clearButton().click()
Log.i(TAG, "clearNotifications: Clicked the clear notifications button")
} else if (notificationTray().exists()) {
Log.i(TAG, "clearNotifications: The notifications tray is still displayed")
Log.i(TAG, "clearNotifications: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "clearNotifications: Dismiss notifications tray by clicking device back button")
Log.i(TAG, "clearNotifications: Clicked device back button")
}
}
}
fun cancelAllShownNotifications() {
Log.i(TAG, "cancelAllShownNotifications: Trying to cancel all system notifications")
cancelAll()
Log.i(TAG, "cancelAllShownNotifications: Canceled all system notifications")
}
fun verifySystemNotificationDoesNotExist(notificationMessage: String) {
Log.i(TAG, "verifySystemNotificationDoesNotExist: Waiting for $notificationMessage notification to be gone")
Log.i(TAG, "verifySystemNotificationDoesNotExist: Waiting for $waitingTime ms for notification: $notificationMessage to be gone")
mDevice.findObject(UiSelector().textContains(notificationMessage)).waitUntilGone(waitingTime)
Log.i(TAG, "verifySystemNotificationDoesNotExist: Waited for $waitingTime ms for notification: $notificationMessage to be gone")
assertUIObjectExists(itemContainingText(notificationMessage), exists = false)
Log.i(TAG, "verifySystemNotificationDoesNotExist: Verified that $notificationMessage notification does not exist")
}
fun verifyPrivateTabsNotification() {
@ -75,17 +79,22 @@ class NotificationRobot {
}
fun clickMediaNotificationControlButton(action: String) {
Log.i(TAG, "clickMediaNotificationControlButton: Waiting for $waitingTime ms for the system media control button: $action to exist")
mediaSystemNotificationButton(action).waitForExists(waitingTime)
Log.i(TAG, "clickMediaNotificationControlButton: Waited for $waitingTime ms for the system media control button: $action to exist")
Log.i(TAG, "clickMediaNotificationControlButton: Trying to click the system media control button: $action")
mediaSystemNotificationButton(action).click()
Log.i(TAG, "clickMediaNotificationControlButton: Clicked the system media control button: $action")
}
fun clickDownloadNotificationControlButton(action: String) {
for (i in 1..RETRY_COUNT) {
Log.i(TAG, "clickPageObject: For loop i = $i")
Log.i(TAG, "clickDownloadNotificationControlButton: Started try #$i")
try {
assertUIObjectExists(downloadSystemNotificationButton(action))
Log.i(TAG, "clickDownloadNotificationControlButton: Trying to click the download system notification: $action button and wait for $waitingTimeShort ms for a new window")
downloadSystemNotificationButton(action).clickAndWaitForNewWindow(waitingTimeShort)
Log.i(TAG, "clickDownloadNotificationControlButton: Clicked app notification $action button and waits for a new window for $waitingTimeShort ms")
Log.i(TAG, "clickDownloadNotificationControlButton: Clicked the download system notification: $action button and waited for $waitingTimeShort ms for a new window")
assertUIObjectExists(
downloadSystemNotificationButton(action),
exists = false,
@ -93,12 +102,13 @@ class NotificationRobot {
break
} catch (e: AssertionError) {
Log.i(TAG, "clickDownloadNotificationControlButton: Catch block")
Log.i(TAG, "clickDownloadNotificationControlButton: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
}
Log.i(TAG, "clickDownloadNotificationControlButton: Waiting for $waitingTimeShort ms for $packageName window to be updated")
mDevice.waitForWindowUpdate(packageName, waitingTimeShort)
Log.i(TAG, "clickDownloadNotificationControlButton: Waited $waitingTimeShort ms for window update")
Log.i(TAG, "clickDownloadNotificationControlButton: Waited for $waitingTimeShort ms for $packageName window to be updated")
}
}
}
@ -107,15 +117,17 @@ class NotificationRobot {
assertUIObjectExists(mediaSystemNotificationButton(action))
fun expandNotificationMessage() {
while (!notificationHeader.exists()) {
while (!notificationHeader().exists()) {
Log.i(TAG, "expandNotificationMessage: Waiting for $appName notification to exist")
scrollToEnd()
Log.i(TAG, "expandNotificationMessage: Scrolled to end of notification tray")
}
if (notificationHeader.exists()) {
if (notificationHeader().exists()) {
Log.i(TAG, "expandNotificationMessage: $appName notification exists")
// expand the notification
notificationHeader.click()
Log.i(TAG, "expandNotificationMessage: Clicked the app notification")
Log.i(TAG, "expandNotificationMessage: Trying to click $appName notification")
notificationHeader().click()
Log.i(TAG, "expandNotificationMessage: Clicked $appName notification")
// double check if notification actions are viewable by checking for action existence; otherwise scroll again
while (!mDevice.findObject(UiSelector().resourceId("android:id/action0")).exists() &&
@ -123,7 +135,6 @@ class NotificationRobot {
) {
Log.i(TAG, "expandNotificationMessage: App notification action buttons do not exist")
scrollToEnd()
Log.i(TAG, "expandNotificationMessage: Scrolled to end of notification tray")
}
}
}
@ -136,41 +147,46 @@ class NotificationRobot {
) {
// In case it fails, retry max 3x the swipe action on download system notifications
for (i in 1..RETRY_COUNT) {
Log.i(TAG, "swipeDownloadNotification: For loop i = $i")
Log.i(TAG, "swipeDownloadNotification: Started try #$i")
try {
Log.i(TAG, "swipeDownloadNotification: Try block")
var retries = 0
while (itemContainingText(appName).exists() && retries++ < 3) {
Log.i(TAG, "swipeDownloadNotification: While loop retries = $retries")
// Swipe left the download system notification
if (direction == "Left") {
itemContainingText(appName)
.also {
Log.i(TAG, "swipeDownloadNotification: Waiting for $waitingTime ms for $appName notification to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "swipeDownloadNotification: Waited for $waitingTime ms for $appName notification to exist")
Log.i(TAG, "swipeDownloadNotification: Trying to perform swipe left action on $appName notification")
it.swipeLeft(3)
Log.i(TAG, "swipeDownloadNotification: Performed swipe left action on $appName notification")
}
Log.i(TAG, "swipeDownloadNotification: Swiped left download notification")
} else {
// Swipe right the download system notification
itemContainingText(appName)
.also {
Log.i(TAG, "swipeDownloadNotification: Waiting for $waitingTime ms for $appName notification to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "swipeDownloadNotification: Waited for $waitingTime ms for $appName notification to exist")
Log.i(TAG, "swipeDownloadNotification: Trying to perform swipe right action on $appName notification")
it.swipeRight(3)
Log.i(TAG, "swipeDownloadNotification: Performed swipe right action on $appName notification")
}
Log.i(TAG, "swipeDownloadNotification: Swiped right download notification")
}
}
// Not all download related system notifications can be dismissed
if (shouldDismissNotification) {
Log.i(TAG, "swipeDownloadNotification: $appName notification can't be dismissed: $shouldDismissNotification")
assertUIObjectExists(itemContainingText(appName), exists = false)
} else {
Log.i(TAG, "swipeDownloadNotification: $appName notification can be dismissed: $shouldDismissNotification")
assertUIObjectExists(itemContainingText(appName))
Log.i(TAG, "swipeDownloadNotification: Verified that $appName notification exist")
}
break
} catch (e: AssertionError) {
Log.i(TAG, "swipeDownloadNotification: Catch block")
Log.i(TAG, "swipeDownloadNotification: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
@ -179,11 +195,12 @@ class NotificationRobot {
}.openNotificationShade {
// The download complete system notification can't be expanded
if (canExpandNotification) {
Log.i(TAG, "swipeDownloadNotification: $appName notification can be expanded: $canExpandNotification")
// In all cases the download system notification title will be the app name
verifySystemNotificationExists(appName)
Log.i(TAG, "swipeDownloadNotification: Verified that $appName notification exist")
expandNotificationMessage()
} else {
Log.i(TAG, "swipeDownloadNotification: $appName notification can't be expanded: $canExpandNotification")
// Using the download completed system notification summary to bring in to view an properly verify it
verifySystemNotificationExists("Download completed")
}
@ -194,10 +211,12 @@ class NotificationRobot {
}
fun clickNotification(notificationMessage: String) {
Log.i(TAG, "clickNotification: Looking for $notificationMessage notification")
Log.i(TAG, "clickNotification: Waiting for $waitingTime ms for $notificationMessage notification to exist")
mDevice.findObject(UiSelector().text(notificationMessage)).waitForExists(waitingTime)
Log.i(TAG, "clickNotification: Waited for $waitingTime ms for $notificationMessage notification to exist")
Log.i(TAG, "clickNotification: Trying to click the $notificationMessage notification and wait for $waitingTimeShort ms for a new window")
mDevice.findObject(UiSelector().text(notificationMessage)).clickAndWaitForNewWindow(waitingTimeShort)
Log.i(TAG, "clickNotification: Clicked $notificationMessage notification and waiting for $waitingTimeShort ms for a new window")
Log.i(TAG, "clickNotification: Clicked the $notificationMessage notification and waited for $waitingTimeShort ms for a new window")
}
class Transition {
@ -206,18 +225,22 @@ class NotificationRobot {
try {
assertUIObjectExists(closePrivateTabsNotification())
} catch (e: AssertionError) {
Log.i(TAG, "clickClosePrivateTabsNotification: Trying to perform fling action to the end of the notification tray")
notificationTray().flingToEnd(1)
Log.i(TAG, "clickClosePrivateTabsNotification: Performed fling action to the end of the notification tray")
}
Log.i(TAG, "clickClosePrivateTabsNotification: Trying to click the close private tabs notification")
closePrivateTabsNotification().click()
Log.i(TAG, "clickClosePrivateTabsNotification: Clicked the close private tabs notification")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun closeNotificationTray(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "closeNotificationTray: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "closeNotificationTray: Closed notification tray using device back button")
Log.i(TAG, "closeNotificationTray: Clicked device back button")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -251,7 +274,7 @@ private fun notificationTray() = UiScrollable(
UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"),
).setAsVerticalList()
private val notificationHeader =
private fun notificationHeader() =
mDevice.findObject(
UiSelector()
.resourceId("android:id/app_name_text")
@ -259,10 +282,12 @@ private val notificationHeader =
)
private fun scrollToEnd() {
Log.i(TAG, "scrollToEnd: Trying to perform scroll to the end of the notification tray action")
notificationTray().scrollToEnd(1)
Log.i(TAG, "scrollToEnd: Performed scroll to the end of the notification tray action")
}
private val clearButton = mDevice.findObject(UiSelector().resourceId("com.android.systemui:id/dismiss_text"))
private fun clearButton() = mDevice.findObject(UiSelector().resourceId("com.android.systemui:id/dismiss_text"))
private fun cancelAll() {
val notificationManager: NotificationManager =

@ -4,24 +4,30 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import androidx.test.uiautomator.UiSelector
import org.junit.Assert.assertTrue
import org.mozilla.fenix.helpers.AppAndSystemHelper.isExternalAppBrowserActivityInCurrentTask
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
class PwaRobot {
fun verifyCustomTabToolbarIsNotDisplayed() = assertUIObjectExists(customTabToolbar(), exists = false)
fun verifyPwaActivityInCurrentTask() = assertTrue(isExternalAppBrowserActivityInCurrentTask())
fun verifyCustomTabToolbarIsNotDisplayed() = assertUIObjectExists(itemWithResId("$packageName:id/toolbar"), exists = false)
fun verifyPwaActivityInCurrentTask() {
assertTrue("$TAG: The latest activity of the application is not used for custom tabs or PWAs", isExternalAppBrowserActivityInCurrentTask())
}
class Transition
}
fun pwaScreen(interact: PwaRobot.() -> Unit): PwaRobot.Transition {
Log.i(TAG, "pwaScreen: Trying to find the engine view")
mDevice.findObject(UiSelector().resourceId("$packageName:id/engineView"))
Log.i(Constants.TAG, "pwaScreen: Found the engine view")
PwaRobot().interact()
return PwaRobot.Transition()
}
private fun customTabToolbar() = mDevice.findObject(UiSelector().resourceId("$packageName:id/toolbar"))

@ -7,8 +7,8 @@
package org.mozilla.fenix.ui.robots
import android.content.Context
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
@ -16,6 +16,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.click
@ -24,34 +25,98 @@ import org.mozilla.fenix.helpers.click
*/
class ReaderViewRobot {
fun verifyAppearanceFontGroup(visible: Boolean = false): ViewInteraction =
assertAppearanceFontGroup(visible)
fun verifyAppearanceFontGroup(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceFontGroup: Trying to verify that the font group buttons are visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_font_group),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceFontGroup: Verified that the font group buttons are visible: $visible")
}
fun verifyAppearanceFontSansSerif(visible: Boolean = false): ViewInteraction =
assertAppearanceFontSansSerif(visible)
fun verifyAppearanceFontSansSerif(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceFontSansSerif: Trying to verify that the sans serif font button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_font_sans_serif),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceFontSansSerif: Verified that the sans serif font button is visible: $visible")
}
fun verifyAppearanceFontSerif(visible: Boolean = false): ViewInteraction =
assertAppearanceFontSerif(visible)
fun verifyAppearanceFontSerif(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceFontSerif: Trying to verify that the serif font button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_font_serif),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceFontSerif: Verified that the serif font button is visible: $visible")
}
fun verifyAppearanceFontDecrease(visible: Boolean = false): ViewInteraction =
assertAppearanceFontDecrease(visible)
fun verifyAppearanceFontDecrease(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceFontDecrease: Trying to verify that the decrease font button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_font_size_decrease),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceFontDecrease: Verified that the decrease font button is visible: $visible")
}
fun verifyAppearanceFontIncrease(visible: Boolean = false): ViewInteraction =
assertAppearanceFontIncrease(visible)
fun verifyAppearanceFontIncrease(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceFontIncrease: Trying to verify that the increase font button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_font_size_increase),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceFontIncrease: Verified that the increase font button is visible: $visible")
}
fun verifyAppearanceColorGroup(visible: Boolean = false): ViewInteraction =
assertAppearanceColorGroup(visible)
fun verifyAppearanceColorGroup(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceColorGroup: Trying to verify that the color group buttons are visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_color_scheme_group),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceColorGroup: Verified that the color group buttons are visible: $visible")
}
fun verifyAppearanceColorSepia(visible: Boolean = false): ViewInteraction =
assertAppearanceColorSepia(visible)
fun verifyAppearanceColorSepia(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceColorSepia: Trying to verify that the sepia color button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_color_sepia),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceColorSepia: Verified that the sepia color button is visible: $visible")
}
fun verifyAppearanceColorDark(visible: Boolean = false): ViewInteraction =
assertAppearanceColorDark(visible)
fun verifyAppearanceColorDark(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceColorDark: Trying to verify that the dark color button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_color_dark),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceColorDark: Verified that the dark color button is visible: $visible")
}
fun verifyAppearanceColorLight(visible: Boolean = false): ViewInteraction =
assertAppearanceColorLight(visible)
fun verifyAppearanceColorLight(visible: Boolean = false) {
Log.i(TAG, "verifyAppearanceColorLight: Trying to verify that the light color button is visible: $visible")
onView(
withId(R.id.mozac_feature_readerview_color_light),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
Log.i(TAG, "verifyAppearanceColorLight: Verified that the light color button is visible: $visible")
}
fun verifyAppearanceFontIsActive(fontType: String) {
Log.i(TAG, "verifyAppearanceFontIsActive: Trying to verify that the font type is: $fontType")
val fontTypeKey: String = "mozac-readerview-fonttype"
val prefs = InstrumentationRegistry.getInstrumentation()
@ -61,9 +126,11 @@ class ReaderViewRobot {
)
assertEquals(fontType, prefs.getString(fontTypeKey, ""))
Log.i(TAG, "verifyAppearanceFontIsActive: Verified that the font type is: $fontType")
}
fun verifyAppearanceFontSize(expectedFontSize: Int) {
Log.i(TAG, "verifyAppearanceFontSize: Trying to verify that the font size is: $expectedFontSize")
val fontSizeKey: String = "mozac-readerview-fontsize"
val prefs = InstrumentationRegistry.getInstrumentation()
@ -75,9 +142,11 @@ class ReaderViewRobot {
val fontSizeKeyValue = prefs.getInt(fontSizeKey, 3)
assertEquals(expectedFontSize, fontSizeKeyValue)
Log.i(TAG, "verifyAppearanceFontSize: Verified that the font size is: $expectedFontSize")
}
fun verifyAppearanceColorSchemeChange(expectedColorScheme: String) {
Log.i(TAG, "verifyAppearanceColorSchemeChange: Trying to verify that the color scheme is: $expectedColorScheme")
val colorSchemeKey: String = "mozac-readerview-colorscheme"
val prefs = InstrumentationRegistry.getInstrumentation()
@ -87,12 +156,15 @@ class ReaderViewRobot {
)
assertEquals(expectedColorScheme, prefs.getString(colorSchemeKey, ""))
Log.i(TAG, "verifyAppearanceColorSchemeChange: Verified that the color scheme is: $expectedColorScheme")
}
class Transition {
fun closeAppearanceMenu(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "closeAppearanceMenu: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "closeAppearanceMenu: Clicked device back button")
BrowserRobot().interact()
return BrowserRobot.Transition()
@ -103,8 +175,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_font_sans_serif),
)
Log.i(TAG, "toggleSansSerif: Trying to click sans serif button")
sansSerifButton().click()
Log.i(TAG, "toggleSansSerif: Clicked sans serif button")
ReaderViewRobot().interact()
return Transition()
@ -115,8 +188,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_font_serif),
)
Log.i(TAG, "toggleSerif: Trying to click serif button")
serifButton().click()
Log.i(TAG, "toggleSerif: Clicked serif button")
ReaderViewRobot().interact()
return Transition()
@ -127,8 +201,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_font_size_decrease),
)
Log.i(TAG, "toggleFontSizeDecrease: Trying to click the decrease font button")
fontSizeDecrease().click()
Log.i(TAG, "toggleFontSizeDecrease: Clicked the decrease font button")
ReaderViewRobot().interact()
return Transition()
@ -139,8 +214,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_font_size_increase),
)
Log.i(TAG, "toggleFontSizeIncrease: Trying to click the increase font button")
fontSizeIncrease().click()
Log.i(TAG, "toggleFontSizeIncrease: Clicked the increase font button")
ReaderViewRobot().interact()
return Transition()
@ -151,8 +227,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_color_light),
)
Log.i(TAG, "toggleColorSchemeChangeLight: Trying to click the light color button")
toggleLightColorSchemeButton().click()
Log.i(TAG, "toggleColorSchemeChangeLight: Clicked the light color button")
ReaderViewRobot().interact()
return Transition()
@ -163,8 +240,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_color_dark),
)
Log.i(TAG, "toggleColorSchemeChangeDark: Trying to click the dark color button")
toggleDarkColorSchemeButton().click()
Log.i(TAG, "toggleColorSchemeChangeDark: Clicked the dark color button")
ReaderViewRobot().interact()
return Transition()
@ -175,8 +253,9 @@ class ReaderViewRobot {
onView(
withId(R.id.mozac_feature_readerview_color_sepia),
)
Log.i(TAG, "toggleColorSchemeChangeSepia: Trying to click the sepia color button")
toggleSepiaColorSchemeButton().click()
Log.i(TAG, "toggleColorSchemeChangeSepia: Clicked the sepia color button")
ReaderViewRobot().interact()
return Transition()
@ -184,73 +263,5 @@ class ReaderViewRobot {
}
}
fun readerViewRobot(interact: ReaderViewRobot.() -> Unit): ReaderViewRobot.Transition {
ReaderViewRobot().interact()
return ReaderViewRobot.Transition()
}
private fun assertAppearanceFontGroup(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_font_group),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceFontSansSerif(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_font_sans_serif),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceFontSerif(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_font_serif),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceFontDecrease(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_font_size_decrease),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceFontIncrease(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_font_size_increase),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceColorDark(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_color_dark),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceColorLight(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_color_light),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceColorSepia(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_color_sepia),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun assertAppearanceColorGroup(visible: Boolean) =
onView(
withId(R.id.mozac_feature_readerview_color_scheme_group),
).check(
matches(withEffectiveVisibility(visibleOrGone(visible))),
)
private fun visibleOrGone(visibility: Boolean) =
if (visibility) ViewMatchers.Visibility.VISIBLE else ViewMatchers.Visibility.GONE

@ -5,6 +5,7 @@
package org.mozilla.fenix.ui.robots
import android.net.Uri
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.Visibility
@ -17,6 +18,7 @@ import androidx.test.uiautomator.UiSelector
import org.hamcrest.Matchers
import org.hamcrest.Matchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
@ -32,28 +34,36 @@ import org.mozilla.fenix.helpers.click
class RecentlyClosedTabsRobot {
fun waitForListToExist() =
fun waitForListToExist() {
Log.i(TAG, "waitForListToExist: Waiting for $waitingTime ms for recently closed tabs list to exist")
mDevice.findObject(UiSelector().resourceId("$packageName:id/recently_closed_list"))
.waitForExists(waitingTime)
Log.i(TAG, "waitForListToExist: Waited for $waitingTime ms for recently closed tabs list to exist")
}
fun verifyRecentlyClosedTabsMenuView() {
Log.i(TAG, "verifyRecentlyClosedTabsMenuView: Trying to verify that the recently closed tabs menu view is visible")
onView(
allOf(
withText("Recently closed tabs"),
withParent(withId(R.id.navigationToolbar)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyRecentlyClosedTabsMenuView: Verified that the recently closed tabs menu view is visible")
}
fun verifyEmptyRecentlyClosedTabsList() {
Log.i(TAG, "verifyEmptyRecentlyClosedTabsList: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "verifyEmptyRecentlyClosedTabsList: Waited for device to be idle")
Log.i(TAG, "verifyEmptyRecentlyClosedTabsList: Trying to verify that the empty recently closed tabs list is visible")
onView(
allOf(
withId(R.id.recently_closed_empty_view),
withText(R.string.recently_closed_empty_message),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyEmptyRecentlyClosedTabsList: Verified that the empty recently closed tabs list is visible")
}
fun verifyRecentlyClosedTabsPageTitle(title: String) =
@ -62,6 +72,7 @@ class RecentlyClosedTabsRobot {
)
fun verifyRecentlyClosedTabsUrl(expectedUrl: Uri) {
Log.i(TAG, "verifyRecentlyClosedTabsUrl: Trying to verify that the recently closed tab with url: $expectedUrl is visible")
onView(
allOf(
withId(R.id.url),
@ -70,45 +81,64 @@ class RecentlyClosedTabsRobot {
),
),
).check(matches(withText(Matchers.containsString(expectedUrl.toString()))))
Log.i(TAG, "verifyRecentlyClosedTabsUrl: Verified that the recently closed tab with url: $expectedUrl is visible")
}
fun clickDeleteRecentlyClosedTabs() = recentlyClosedTabDeleteButton().click()
fun clickDeleteRecentlyClosedTabs() {
Log.i(TAG, "clickDeleteRecentlyClosedTabs: Trying to click the recently closed tab item delete button")
recentlyClosedTabDeleteButton().click()
Log.i(TAG, "clickDeleteRecentlyClosedTabs: Clicked the recently closed tab item delete button")
}
class Transition {
fun clickRecentlyClosedItem(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
recentlyClosedTabsPageTitle(title).also {
Log.i(TAG, "clickRecentlyClosedItem: Waiting for $waitingTimeShort ms for recently closed tab with title: $title to exist")
it.waitForExists(waitingTimeShort)
Log.i(TAG, "clickRecentlyClosedItem: Waited for $waitingTimeShort ms for recently closed tab with title: $title to exist")
Log.i(TAG, "clickRecentlyClosedItem: Trying to click the recently closed tab with title: $title")
it.click()
Log.i(TAG, "clickRecentlyClosedItem: Clicked the recently closed tab with title: $title")
}
Log.i(TAG, "clickRecentlyClosedItem: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "clickRecentlyClosedItem: Waited for device to be idle")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickOpenInNewTab(testRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
openInNewTabOption.click()
Log.i(TAG, "clickOpenInNewTab: Trying to click the multi-select \"Open in a new tab\" context menu button")
openInNewTabOption().click()
Log.i(TAG, "clickOpenInNewTab: Clicked the multi-select \"Open in a new tab\" context menu button")
ComposeTabDrawerRobot(testRule).interact()
return ComposeTabDrawerRobot.Transition(testRule)
}
fun clickOpenInPrivateTab(testRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
openInPrivateTabOption.click()
Log.i(TAG, "clickOpenInPrivateTab: Trying to click the multi-select \"Open in a private tab\" context menu button")
openInPrivateTabOption().click()
Log.i(TAG, "clickOpenInPrivateTab: Clicked the multi-select \"Open in a private tab\" context menu button")
ComposeTabDrawerRobot(testRule).interact()
return ComposeTabDrawerRobot.Transition(testRule)
}
fun clickShare(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
multipleSelectionShareButton.click()
Log.i(TAG, "clickShare: Trying to click the share recently closed tabs button")
multipleSelectionShareButton().click()
Log.i(TAG, "clickShare: Clicked the share recently closed tabs button")
ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition()
}
fun goBackToHistoryMenu(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
Log.i(TAG, "goBackToHistoryMenu: Trying to click navigate up toolbar button")
onView(withContentDescription("Navigate up")).click()
Log.i(TAG, "goBackToHistoryMenu: Clicked navigate up toolbar button")
HistoryRobot().interact()
return HistoryRobot.Transition()
@ -132,8 +162,8 @@ private fun recentlyClosedTabDeleteButton() =
),
)
private val openInNewTabOption = onView(withText("Open in new tab"))
private fun openInNewTabOption() = onView(withText("Open in new tab"))
private val openInPrivateTabOption = onView(withText("Open in private tab"))
private fun openInPrivateTabOption() = onView(withText("Open in private tab"))
private val multipleSelectionShareButton = onView(withId(R.id.share_history_multi_select))
private fun multipleSelectionShareButton() = onView(withId(R.id.share_history_multi_select))

@ -11,15 +11,12 @@ import android.net.Uri
import android.util.Log
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
@ -28,6 +25,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
@ -69,20 +67,64 @@ import org.mozilla.fenix.ui.robots.SettingsRobot.Companion.DEFAULT_APPS_SETTINGS
class SettingsRobot {
// BASICS SECTION
fun verifyGeneralHeading() = assertGeneralHeading()
fun verifyGeneralHeading() {
scrollToElementByText("General")
Log.i(TAG, "verifyGeneralHeading: Trying to verify that the \"General\" heading is visible")
onView(withText("General"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyGeneralHeading: Verified that the \"General\" heading is visible")
}
fun verifySearchButton() {
Log.i(TAG, "verifySearchButton: Waiting for $waitingTime ms until finding the \"Search\" button")
mDevice.wait(Until.findObject(By.text("Search")), waitingTime)
Log.i(TAG, "verifySearchButton: Waited for $waitingTime ms until the \"Search\" button was found")
Log.i(TAG, "verifySearchButton: Trying to verify that the \"Search\" button is visible")
onView(withText(R.string.preferences_search))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySearchButton: Verified that the \"Search\" button is visible")
}
fun verifyCustomizeButton() {
Log.i(TAG, "verifyCustomizeButton: Trying to verify that the \"Customize\" button is visible")
onView(withText("Customize"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyCustomizeButton: Verified that the \"Customize\" button is visible")
}
fun verifySearchButton() = assertSearchButton()
fun verifyCustomizeButton() = assertCustomizeButton()
fun verifyThemeSelected() = assertThemeSelected()
fun verifyAccessibilityButton() = assertAccessibilityButton()
fun verifySetAsDefaultBrowserButton() = assertSetAsDefaultBrowserButton()
fun verifyAccessibilityButton() {
Log.i(TAG, "verifyAccessibilityButton: Trying to verify that the \"Accessibility\" button is visible")
onView(withText("Accessibility"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyAccessibilityButton: Verified that the \"Accessibility\" button is visible")
}
fun verifySetAsDefaultBrowserButton() {
scrollToElementByText("Set as default browser")
Log.i(TAG, "verifySetAsDefaultBrowserButton: Trying to verify that the \"Set as default browser\" button is visible")
onView(withText("Set as default browser"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySetAsDefaultBrowserButton: Verified that the \"Set as default browser\" button is visible")
}
fun verifyTabsButton() =
assertUIObjectExists(itemContainingText(getStringResource(R.string.preferences_tabs)))
fun verifyHomepageButton() = assertHomepageButton()
fun verifyAutofillButton() = assertAutofillButton()
fun verifyLanguageButton() = assertLanguageButton()
fun verifyHomepageButton() {
Log.i(TAG, "verifyHomepageButton: Trying to verify that the \"Homepage\" button is visible")
onView(withText(R.string.preferences_home_2)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyHomepageButton: Verified that the \"Homepage\" button is visible")
}
fun verifyAutofillButton() {
Log.i(TAG, "verifyAutofillButton: Trying to verify that the \"Autofill\" button is visible")
onView(withText(R.string.preferences_autofill)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyAutofillButton: Verified that the \"Autofill\" button is visible")
}
fun verifyLanguageButton() {
scrollToElementByText(getStringResource(R.string.preferences_language))
Log.i(TAG, "verifyLanguageButton: Trying to verify that the \"Language\" button is visible")
onView(withText(R.string.preferences_language)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyLanguageButton: Verified that the \"Language\" button is visible")
}
fun verifyDefaultBrowserToggle(isEnabled: Boolean) {
scrollToElementByText(getStringResource(R.string.preferences_set_as_default_browser))
Log.i(TAG, "verifyDefaultBrowserToggle: Trying to verify that the \"Set as default browser\" toggle is enabled: $isEnabled")
onView(withText(R.string.preferences_set_as_default_browser))
.check(
matches(
@ -98,18 +140,36 @@ class SettingsRobot {
),
),
)
Log.i(TAG, "verifyDefaultBrowserToggle: Verified that the \"Set as default browser\" toggle is enabled: $isEnabled")
}
fun clickDefaultBrowserSwitch() = toggleDefaultBrowserSwitch()
fun verifyAndroidDefaultAppsMenuAppears() = assertAndroidDefaultAppsMenuAppears()
fun verifyAndroidDefaultAppsMenuAppears() {
Log.i(TAG, "verifyAndroidDefaultAppsMenuAppears: Trying to verify that default browser apps dialog appears")
intended(hasAction(DEFAULT_APPS_SETTINGS_ACTION))
Log.i(TAG, "verifyAndroidDefaultAppsMenuAppears: Verified that the default browser apps dialog appears")
}
// PRIVACY SECTION
fun verifyPrivacyHeading() = assertPrivacyHeading()
fun verifyHTTPSOnlyModeButton() = assertHTTPSOnlyModeButton()
fun verifyPrivacyHeading() {
scrollToElementByText("Privacy and security")
Log.i(TAG, "verifyPrivacyHeading: Trying to verify that the \"Privacy and security\" heading is visible")
onView(withText("Privacy and security"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyPrivacyHeading: Verified that the \"Privacy and security\" heading is visible")
}
fun verifyHTTPSOnlyModeButton() {
scrollToElementByText(getStringResource(R.string.preferences_https_only_title))
Log.i(TAG, "verifyHTTPSOnlyModeButton: Trying to verify that the \"HTTPS-Only Mode\" button is visible")
onView(
withText(R.string.preferences_https_only_title),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyHTTPSOnlyModeButton: Verified that the \"HTTPS-Only Mode\" button is visible")
}
fun verifyCookieBannerBlockerButton(enabled: Boolean) {
scrollToElementByText(getStringResource(R.string.preferences_cookie_banner_reduction_private_mode))
Log.i(TAG, "verifyCookieBannerBlockerButton: Trying to verify that the \"Cookie Banner Blocker in private browsing\" toggle is enabled: $enabled")
onView(withText(R.string.preferences_cookie_banner_reduction_private_mode))
.check(
matches(
@ -125,31 +185,167 @@ class SettingsRobot {
),
),
)
Log.i(TAG, "verifyCookieBannerBlockerButton: Verified if cookie banner blocker toggle is enabled: $enabled")
Log.i(TAG, "verifyCookieBannerBlockerButton: Verified that the \"Cookie Banner Blocker in private browsing\" toggle is enabled: $enabled")
}
fun verifyEnhancedTrackingProtectionButton() = assertEnhancedTrackingProtectionButton()
fun verifyLoginsAndPasswordsButton() = assertLoginsAndPasswordsButton()
fun verifyPrivateBrowsingButton() = assertPrivateBrowsingButton()
fun verifySitePermissionsButton() = assertSitePermissionsButton()
fun verifyDeleteBrowsingDataButton() = assertDeleteBrowsingDataButton()
fun verifyDeleteBrowsingDataOnQuitButton() = assertDeleteBrowsingDataOnQuitButton()
fun verifyNotificationsButton() = assertNotificationsButton()
fun verifyDataCollectionButton() = assertDataCollectionButton()
fun verifyOpenLinksInAppsButton() = assertOpenLinksInAppsButton()
fun verifySettingsView() = assertSettingsView()
fun verifySettingsToolbar() = assertSettingsToolbar()
fun verifyEnhancedTrackingProtectionButton() {
Log.i(TAG, "verifyEnhancedTrackingProtectionButton: Waiting for $waitingTime ms until finding the \"Privacy and Security\" heading")
mDevice.wait(Until.findObject(By.text("Privacy and Security")), waitingTime)
Log.i(TAG, "verifyEnhancedTrackingProtectionButton: Waited for $waitingTime ms until the \"Privacy and Security\" heading was found")
Log.i(TAG, "verifyEnhancedTrackingProtectionButton: Trying to verify that the \"Enhanced Tracking Protection\" button is visible")
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Enhanced Tracking Protection")),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyEnhancedTrackingProtectionButton: Verified that the \"Enhanced Tracking Protection\" button is visible")
}
fun verifyLoginsAndPasswordsButton() {
scrollToElementByText("Logins and passwords")
Log.i(TAG, "verifyLoginsAndPasswordsButton: Trying to verify that the \"Logins and passwords\" button is visible")
onView(withText(R.string.preferences_passwords_logins_and_passwords))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyLoginsAndPasswordsButton: Verified that the \"Logins and passwords\" button is visible")
}
fun verifyPrivateBrowsingButton() {
scrollToElementByText("Private browsing")
Log.i(TAG, "verifyPrivateBrowsingButton: Waiting for $waitingTime ms until finding the \"Private browsing\" button")
mDevice.wait(Until.findObject(By.text("Private browsing")), waitingTime)
Log.i(TAG, "verifyPrivateBrowsingButton: Waited for $waitingTime ms until the \"Private browsing\" button was found")
Log.i(TAG, "verifyPrivateBrowsingButton: Trying to verify that the \"Private browsing\" button is visible")
onView(withText("Private browsing"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyPrivateBrowsingButton: Verified that the \"Private browsing\" button is visible")
}
fun verifySitePermissionsButton() {
scrollToElementByText("Site permissions")
Log.i(TAG, "verifySitePermissionsButton: Trying to verify that the \"Site permissions\" button is visible")
onView(withText("Site permissions"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySitePermissionsButton: Verified that the \"Site permissions\" button is visible")
}
fun verifyDeleteBrowsingDataButton() {
scrollToElementByText("Delete browsing data")
Log.i(TAG, "verifyDeleteBrowsingDataButton: Trying to verify that the \"Delete browsing data\" button is visible")
onView(withText("Delete browsing data"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyDeleteBrowsingDataButton: Verified that the \"Delete browsing data\" button is visible")
}
fun verifyDeleteBrowsingDataOnQuitButton() {
scrollToElementByText("Delete browsing data on quit")
Log.i(TAG, "verifyDeleteBrowsingDataOnQuitButton: Trying to verify that the \"Delete browsing data on quit\" button is visible")
onView(withText("Delete browsing data on quit"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyDeleteBrowsingDataOnQuitButton: Verified that the \"Delete browsing data on quit\" button is visible")
}
fun verifyNotificationsButton() {
scrollToElementByText("Notifications")
Log.i(TAG, "verifyNotificationsButton: Trying to verify that the \"Notifications\" button is visible")
onView(withText("Notifications"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyNotificationsButton: Verified that the \"Notifications\" button is visible")
}
fun verifyDataCollectionButton() {
scrollToElementByText("Data collection")
Log.i(TAG, "verifyDataCollectionButton: Trying to verify that the \"Data collection\" button is visible")
onView(withText("Data collection"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyDataCollectionButton: Verified that the \"Data collection\" button is visible")
}
fun verifyOpenLinksInAppsButton() {
scrollToElementByText("Open links in apps")
Log.i(TAG, "verifyOpenLinksInAppsButton: Trying to verify that the \"Open links in apps\" button is visible")
openLinksInAppsButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyOpenLinksInAppsButton: Verified that the \"Open links in apps\" button is visible")
}
fun verifySettingsView() {
scrollToElementByText("General")
Log.i(TAG, "verifySettingsView: Trying to verify that the \"General\" heading is visible")
onView(withText("General"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySettingsView: Verified that the \"General\" heading is visible")
scrollToElementByText("Privacy and security")
Log.i(TAG, "verifySettingsView: Trying to verify that the \"Privacy and security\" heading is visible")
onView(withText("Privacy and security"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySettingsView: Verified that the \"Privacy and security\" heading is visible")
Log.i(TAG, "verifySettingsView: Trying to perform scroll to the \"Add-ons\" button")
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons")),
),
)
Log.i(TAG, "verifySettingsView: Performed scroll to the \"Add-ons\" button")
Log.i(TAG, "verifySettingsView: Trying to verify that the \"Add-ons\" button is completely displayed")
onView(withText("Add-ons"))
.check(matches(isCompletelyDisplayed()))
Log.i(TAG, "verifySettingsView: Verified that the \"Add-ons\" button is completely displayed")
Log.i(TAG, "verifySettingsView: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the settings list")
settingsList().scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifySettingsView: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the settings list")
Log.i(TAG, "verifySettingsView: Trying to verify that the \"About\" heading is visible")
onView(withText("About"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySettingsView: Verified that the \"About\" heading is visible")
}
fun verifySettingsToolbar() {
Log.i(TAG, "verifySettingsToolbar: Trying to verify that the navigate up button is visible")
onView(
allOf(
withId(R.id.navigationToolbar),
hasDescendant(withContentDescription(R.string.action_bar_up_description)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySettingsToolbar: Verified that the navigate up button is visible")
Log.i(TAG, "verifySettingsToolbar: Trying to verify that the \"Settings\" toolbar title is visible")
onView(
allOf(
withId(R.id.navigationToolbar),
hasDescendant(withText(R.string.settings)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySettingsToolbar: Verified that the \"Settings\" toolbar title is visible")
}
// ADVANCED SECTION
fun verifyAdvancedHeading() = assertAdvancedHeading()
fun verifyAddons() = assertAddonsButton()
fun verifyAdvancedHeading() {
Log.i(TAG, "verifyAdvancedHeading: Trying to perform scroll to the \"Add-ons\" button")
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons")),
),
)
Log.i(TAG, "verifyAdvancedHeading: Performed scroll to the \"Add-ons\" button")
Log.i(TAG, "verifyAdvancedHeading: Trying to verify that the \"Add-ons\" button is completely displayed")
onView(withText("Add-ons"))
.check(matches(isCompletelyDisplayed()))
Log.i(TAG, "verifyAdvancedHeading: Verified that the \"Add-ons\" button is completely displayed")
}
fun verifyAddons() {
Log.i(TAG, "verifyAddons: Trying to perform scroll to the \"Add-ons\" button")
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons")),
),
)
Log.i(TAG, "verifyAddons: Performed scroll to the \"Add-ons\" button")
Log.i(TAG, "verifyAddons: Trying to verify that the \"Add-ons\" button is completely displayed")
addonsManagerButton()
.check(matches(isCompletelyDisplayed()))
Log.i(TAG, "verifyAddons: Verified that the \"Add-ons\" button is completely displayed")
}
fun verifyExternalDownloadManagerButton() =
fun verifyExternalDownloadManagerButton() {
Log.i(TAG, "verifyExternalDownloadManagerButton: Trying to verify that the \"External download manager\" button is visible")
onView(
withText(R.string.preferences_external_download_manager),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyExternalDownloadManagerButton: Verified that the \"External download manager\" button is visible")
}
fun verifyExternalDownloadManagerToggle(enabled: Boolean) =
fun verifyExternalDownloadManagerToggle(enabled: Boolean) {
Log.i(TAG, "verifyExternalDownloadManagerToggle: Trying to verify that the \"External download manager\" toggle is enabled: $enabled")
onView(withText(R.string.preferences_external_download_manager))
.check(
matches(
@ -165,8 +361,11 @@ class SettingsRobot {
),
),
)
Log.i(TAG, "verifyExternalDownloadManagerToggle: Verified that the \"External download manager\" toggle is enabled: $enabled")
}
fun verifyLeakCanaryToggle(enabled: Boolean) =
fun verifyLeakCanaryToggle(enabled: Boolean) {
Log.i(TAG, "verifyLeakCanaryToggle: Trying to verify that the \"LeakCanary\" toggle is enabled: $enabled")
onView(withText(R.string.preference_leakcanary))
.check(
matches(
@ -182,8 +381,11 @@ class SettingsRobot {
),
),
)
Log.i(TAG, "verifyLeakCanaryToggle: Verified that the \"LeakCanary\" toggle is enabled: $enabled")
}
fun verifyRemoteDebuggingToggle(enabled: Boolean) =
fun verifyRemoteDebuggingToggle(enabled: Boolean) {
Log.i(TAG, "verifyRemoteDebuggingToggle: Trying to verify that the \"Remote debugging via USB\" toggle is enabled: $enabled")
onView(withText(R.string.preferences_remote_debugging))
.check(
matches(
@ -199,53 +401,106 @@ class SettingsRobot {
),
),
)
Log.i(TAG, "verifyRemoteDebuggingToggle: Verified that the \"Remote debugging via USB\" toggle is enabled: $enabled")
}
// DEVELOPER TOOLS SECTION
fun verifyRemoteDebuggingButton() = assertRemoteDebuggingButton()
fun verifyLeakCanaryButton() = assertLeakCanaryButton()
fun verifyRemoteDebuggingButton() {
scrollToElementByText("Remote debugging via USB")
Log.i(TAG, "verifyRemoteDebuggingButton: Trying to verify that the \"Remote debugging via USB\" button is visible")
onView(withText("Remote debugging via USB"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyRemoteDebuggingButton: Verified that the \"Remote debugging via USB\" button is visible")
}
fun verifyLeakCanaryButton() {
scrollToElementByText("LeakCanary")
Log.i(TAG, "verifyLeakCanaryButton: Trying to verify that the \"LeakCanary\" button is visible")
onView(withText("LeakCanary"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyLeakCanaryButton: Verified that the \"LeakCanary\" button is visible")
}
// ABOUT SECTION
fun verifyAboutHeading() = assertAboutHeading()
fun verifyAboutHeading() {
Log.i(TAG, "verifyAboutHeading: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the settings list")
settingsList().scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyAboutHeading: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the settings list")
Log.i(TAG, "verifyAboutHeading: Trying to verify that the \"About\" heading is visible")
onView(withText("About"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyAboutHeading: Verified that the \"About\" heading is visible")
}
fun verifyRateOnGooglePlay() = assertUIObjectExists(rateOnGooglePlayHeading())
fun verifyAboutFirefoxPreview() = assertUIObjectExists(aboutFirefoxHeading())
fun verifyGooglePlayRedirect() = assertGooglePlayRedirect()
fun verifyGooglePlayRedirect() {
if (isPackageInstalled(GOOGLE_PLAY_SERVICES)) {
Log.i(TAG, "verifyGooglePlayRedirect: $GOOGLE_PLAY_SERVICES is installed")
try {
Log.i(TAG, "verifyGooglePlayRedirect: Trying to verify intent to: $GOOGLE_PLAY_SERVICES")
intended(
allOf(
hasAction(Intent.ACTION_VIEW),
hasData(Uri.parse(SupportUtils.RATE_APP_URL)),
),
)
Log.i(TAG, "verifyGooglePlayRedirect: Verified intent to: $GOOGLE_PLAY_SERVICES")
} catch (e: AssertionFailedError) {
Log.i(TAG, "verifyGooglePlayRedirect: AssertionFailedError caught, executing fallback methods")
BrowserRobot().verifyRateOnGooglePlayURL()
}
} else {
BrowserRobot().verifyRateOnGooglePlayURL()
}
}
fun verifySettingsOptionSummary(setting: String, summary: String) {
scrollToElementByText(setting)
Log.i(TAG, "verifySettingsOptionSummary: Trying to verify that setting: $setting with summary:$summary is visible")
onView(
allOf(
withText(setting),
hasSibling(withText(summary)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifySettingsOptionSummary: Verified that setting: $setting with summary:$summary is visible")
}
class Transition {
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "goBack: Trying to click the navigate up button")
goBackButton().click()
Log.i(TAG, "goBack: Clicked the navigate up button")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun goBackToOnboardingScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
Log.i(TAG, "goBackToOnboardingScreen: Trying to click device back button")
mDevice.pressBack()
Log.i(TAG, "goBackToOnboardingScreen: Clicked device back button")
Log.i(TAG, "goBackToOnboardingScreen: Waiting for device to be idle for $waitingTimeShort ms")
mDevice.waitForIdle(waitingTimeShort)
Log.i(TAG, "goBackToOnboardingScreen: Device was idle for $waitingTimeShort ms")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun goBackToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
Log.i(TAG, "goBackToBrowser: Trying to click the navigate up button")
goBackButton().click()
Log.i(TAG, "goBackToBrowser: Clicked the navigate up button")
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun openAboutFirefoxPreview(interact: SettingsSubMenuAboutRobot.() -> Unit): SettingsSubMenuAboutRobot.Transition {
Log.i(TAG, "openAboutFirefoxPreview: Trying to click the \"About Firefox\" button")
aboutFirefoxHeading().click()
Log.i(TAG, "openAboutFirefoxPreview: Clicked the \"About Firefox\" button")
SettingsSubMenuAboutRobot().interact()
return SettingsSubMenuAboutRobot.Transition()
}
@ -253,8 +508,12 @@ class SettingsRobot {
fun openSearchSubMenu(interact: SettingsSubMenuSearchRobot.() -> Unit): SettingsSubMenuSearchRobot.Transition {
itemWithText(getStringResource(R.string.preferences_search))
.also {
Log.i(TAG, "openSearchSubMenu: Waiting for $waitingTimeShort ms for the \"Search\" button to exist")
it.waitForExists(waitingTimeShort)
Log.i(TAG, "openSearchSubMenu: Waited for $waitingTimeShort ms for the \"Search\" button to exist")
Log.i(TAG, "openSearchSubMenu: Trying to click the \"Search\" button")
it.click()
Log.i(TAG, "openSearchSubMenu: Clicked the \"Search\" button")
}
SettingsSubMenuSearchRobot().interact()
@ -262,8 +521,9 @@ class SettingsRobot {
}
fun openCustomizeSubMenu(interact: SettingsSubMenuCustomizeRobot.() -> Unit): SettingsSubMenuCustomizeRobot.Transition {
fun customizeButton() = onView(withText("Customize"))
customizeButton().click()
Log.i(TAG, "openCustomizeSubMenu: Trying to click the \"Customize\" button")
onView(withText("Customize")).click()
Log.i(TAG, "openCustomizeSubMenu: Clicked the \"Customize\" button")
SettingsSubMenuCustomizeRobot().interact()
return SettingsSubMenuCustomizeRobot.Transition()
@ -272,8 +532,12 @@ class SettingsRobot {
fun openTabsSubMenu(interact: SettingsSubMenuTabsRobot.() -> Unit): SettingsSubMenuTabsRobot.Transition {
itemWithText(getStringResource(R.string.preferences_tabs))
.also {
Log.i(TAG, "openTabsSubMenu: Waiting for $waitingTime ms for the \"Tabs\" button to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "openTabsSubMenu: Waited for $waitingTime ms for the \"Tabs\" button to exist")
Log.i(TAG, "openTabsSubMenu: Trying to click the \"Tabs\" button and wait for $waitingTimeShort ms for a new window")
it.clickAndWaitForNewWindow(waitingTimeShort)
Log.i(TAG, "openTabsSubMenu: Clicked the \"Tabs\" button and wait for $waitingTimeShort ms for a new window")
}
SettingsSubMenuTabsRobot().interact()
@ -281,8 +545,12 @@ class SettingsRobot {
}
fun openHomepageSubMenu(interact: SettingsSubMenuHomepageRobot.() -> Unit): SettingsSubMenuHomepageRobot.Transition {
Log.i(TAG, "openHomepageSubMenu: Waiting for $waitingTime ms for the \"Homepage\" button to exist")
mDevice.findObject(UiSelector().textContains("Homepage")).waitForExists(waitingTime)
Log.i(TAG, "openHomepageSubMenu: Waited for $waitingTime ms for the \"Homepage\" button to exist")
Log.i(TAG, "openHomepageSubMenu: Trying to click the \"Homepage\" button")
onView(withText(R.string.preferences_home_2)).click()
Log.i(TAG, "openHomepageSubMenu: Clicked the \"Homepage\" button")
SettingsSubMenuHomepageRobot().interact()
return SettingsSubMenuHomepageRobot.Transition()
@ -291,10 +559,12 @@ class SettingsRobot {
fun openAutofillSubMenu(interact: SettingsSubMenuAutofillRobot.() -> Unit): SettingsSubMenuAutofillRobot.Transition {
mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_autofill)))
.also {
Log.i(TAG, "openAutofillSubMenu: Looking for \"Autofill\" settings button")
Log.i(TAG, "openAutofillSubMenu: Waiting for $waitingTime ms for the \"Autofill\" button to exist")
it.waitForExists(waitingTime)
Log.i(TAG, "openAutofillSubMenu: Waited for $waitingTime ms for the \"Autofill\" button to exist")
Log.i(TAG, "openAutofillSubMenu: Trying to click the \"Autofill\" button")
it.click()
Log.i(TAG, "openAutofillSubMenu: Clicked \"Autofill\" settings button")
Log.i(TAG, "openAutofillSubMenu: Clicked the \"Autofill\" button")
}
SettingsSubMenuAutofillRobot().interact()
@ -303,11 +573,12 @@ class SettingsRobot {
fun openAccessibilitySubMenu(interact: SettingsSubMenuAccessibilityRobot.() -> Unit): SettingsSubMenuAccessibilityRobot.Transition {
scrollToElementByText("Accessibility")
fun accessibilityButton() = onView(withText("Accessibility"))
accessibilityButton()
.check(matches(isDisplayed()))
.click()
Log.i(TAG, "openAccessibilitySubMenu: Trying to verify that the \"Accessibility\" button is displayed")
onView(withText("Accessibility")).check(matches(isDisplayed()))
Log.i(TAG, "openAccessibilitySubMenu: Verified that the \"Accessibility\" button is displayed")
Log.i(TAG, "openAccessibilitySubMenu: Trying to click the \"Accessibility\" button")
onView(withText("Accessibility")).click()
Log.i(TAG, "openAccessibilitySubMenu: Clicked the \"Accessibility\" button")
SettingsSubMenuAccessibilityRobot().interact()
return SettingsSubMenuAccessibilityRobot.Transition()
@ -317,6 +588,7 @@ class SettingsRobot {
localizedText: String = getStringResource(R.string.preferences_language),
interact: SettingsSubMenuLanguageRobot.() -> Unit,
): SettingsSubMenuLanguageRobot.Transition {
Log.i(TAG, "openLanguageSubMenu: Trying to click the $localizedText button")
onView(withId(R.id.recycler_view))
.perform(
RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
@ -326,6 +598,7 @@ class SettingsRobot {
ViewActions.click(),
),
)
Log.i(TAG, "openLanguageSubMenu: Clicked the $localizedText button")
SettingsSubMenuLanguageRobot().interact()
return SettingsSubMenuLanguageRobot.Transition()
@ -333,8 +606,9 @@ class SettingsRobot {
fun openSetDefaultBrowserSubMenu(interact: SettingsSubMenuSetDefaultBrowserRobot.() -> Unit): SettingsSubMenuSetDefaultBrowserRobot.Transition {
scrollToElementByText("Set as default browser")
fun setDefaultBrowserButton() = onView(withText("Set as default browser"))
setDefaultBrowserButton().click()
Log.i(TAG, "openSetDefaultBrowserSubMenu: Trying to click the \"Set as default browser\" button")
onView(withText("Set as default browser")).click()
Log.i(TAG, "openSetDefaultBrowserSubMenu: Clicked the \"Set as default browser\" button")
SettingsSubMenuSetDefaultBrowserRobot().interact()
return SettingsSubMenuSetDefaultBrowserRobot.Transition()
@ -342,9 +616,9 @@ class SettingsRobot {
fun openEnhancedTrackingProtectionSubMenu(interact: SettingsSubMenuEnhancedTrackingProtectionRobot.() -> Unit): SettingsSubMenuEnhancedTrackingProtectionRobot.Transition {
scrollToElementByText("Enhanced Tracking Protection")
fun enhancedTrackingProtectionButton() =
onView(withText("Enhanced Tracking Protection"))
enhancedTrackingProtectionButton().click()
Log.i(TAG, "openEnhancedTrackingProtectionSubMenu: Trying to click the \"Enhanced Tracking Protection\" button")
onView(withText("Enhanced Tracking Protection")).click()
Log.i(TAG, "openEnhancedTrackingProtectionSubMenu: Clicked the \"Enhanced Tracking Protection\" button")
SettingsSubMenuEnhancedTrackingProtectionRobot().interact()
return SettingsSubMenuEnhancedTrackingProtectionRobot.Transition()
@ -352,16 +626,18 @@ class SettingsRobot {
fun openLoginsAndPasswordSubMenu(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordRobot.Transition {
scrollToElementByText("Logins and passwords")
fun loginsAndPasswordsButton() = onView(withText("Logins and passwords"))
loginsAndPasswordsButton().click()
Log.i(TAG, "openLoginsAndPasswordSubMenu: Trying to click the \"Logins and passwords\" button")
onView(withText("Logins and passwords")).click()
Log.i(TAG, "openLoginsAndPasswordSubMenu: Clicked the \"Logins and passwords\" button")
SettingsSubMenuLoginsAndPasswordRobot().interact()
return SettingsSubMenuLoginsAndPasswordRobot.Transition()
}
fun openTurnOnSyncMenu(interact: SettingsTurnOnSyncRobot.() -> Unit): SettingsTurnOnSyncRobot.Transition {
fun turnOnSyncButton() = onView(withText("Sync and save your data"))
turnOnSyncButton().click()
Log.i(TAG, "openTurnOnSyncMenu: Trying to click the \"Sync and save your data\" button")
onView(withText("Sync and save your data")).click()
Log.i(TAG, "openTurnOnSyncMenu: Clicked the \"Sync and save your data\" button")
SettingsTurnOnSyncRobot().interact()
return SettingsTurnOnSyncRobot.Transition()
@ -369,8 +645,9 @@ class SettingsRobot {
fun openPrivateBrowsingSubMenu(interact: SettingsSubMenuPrivateBrowsingRobot.() -> Unit): SettingsSubMenuPrivateBrowsingRobot.Transition {
scrollToElementByText("Private browsing")
fun privateBrowsingButton() = mDevice.findObject(textContains("Private browsing"))
privateBrowsingButton().click()
Log.i(TAG, "openPrivateBrowsingSubMenu: Trying to click the \"Private browsing\" button")
mDevice.findObject(textContains("Private browsing")).click()
Log.i(TAG, "openPrivateBrowsingSubMenu: Clicked the \"Private browsing\" button")
SettingsSubMenuPrivateBrowsingRobot().interact()
return SettingsSubMenuPrivateBrowsingRobot.Transition()
@ -378,8 +655,9 @@ class SettingsRobot {
fun openSettingsSubMenuSitePermissions(interact: SettingsSubMenuSitePermissionsRobot.() -> Unit): SettingsSubMenuSitePermissionsRobot.Transition {
scrollToElementByText("Site permissions")
fun sitePermissionButton() = mDevice.findObject(textContains("Site permissions"))
sitePermissionButton().click()
Log.i(TAG, "openSettingsSubMenuSitePermissions: Trying to click the \"Site permissions\" button")
mDevice.findObject(textContains("Site permissions")).click()
Log.i(TAG, "openSettingsSubMenuSitePermissions: Clicked the \"Site permissions\" button")
SettingsSubMenuSitePermissionsRobot().interact()
return SettingsSubMenuSitePermissionsRobot.Transition()
@ -387,8 +665,9 @@ class SettingsRobot {
fun openSettingsSubMenuDeleteBrowsingData(interact: SettingsSubMenuDeleteBrowsingDataRobot.() -> Unit): SettingsSubMenuDeleteBrowsingDataRobot.Transition {
scrollToElementByText("Delete browsing data")
fun deleteBrowsingDataButton() = mDevice.findObject(textContains("Delete browsing data"))
deleteBrowsingDataButton().click()
Log.i(TAG, "openSettingsSubMenuDeleteBrowsingData: Trying to click the \"Delete browsing data\" button")
mDevice.findObject(textContains("Delete browsing data")).click()
Log.i(TAG, "openSettingsSubMenuDeleteBrowsingData: Clicked the \"Delete browsing data\" button")
SettingsSubMenuDeleteBrowsingDataRobot().interact()
return SettingsSubMenuDeleteBrowsingDataRobot.Transition()
@ -396,8 +675,9 @@ class SettingsRobot {
fun openSettingsSubMenuDeleteBrowsingDataOnQuit(interact: SettingsSubMenuDeleteBrowsingDataOnQuitRobot.() -> Unit): SettingsSubMenuDeleteBrowsingDataOnQuitRobot.Transition {
scrollToElementByText("Delete browsing data on quit")
fun deleteBrowsingDataOnQuitButton() = mDevice.findObject(textContains("Delete browsing data on quit"))
deleteBrowsingDataOnQuitButton().click()
Log.i(TAG, "openSettingsSubMenuDeleteBrowsingDataOnQuit: Trying to click the \"Delete browsing data on quit\" button")
mDevice.findObject(textContains("Delete browsing data on quit")).click()
Log.i(TAG, "openSettingsSubMenuDeleteBrowsingDataOnQuit: Clicked the \"Delete browsing data on quit\" button")
SettingsSubMenuDeleteBrowsingDataOnQuitRobot().interact()
return SettingsSubMenuDeleteBrowsingDataOnQuitRobot.Transition()
@ -405,8 +685,9 @@ class SettingsRobot {
fun openSettingsSubMenuNotifications(interact: SystemSettingsRobot.() -> Unit): SystemSettingsRobot.Transition {
scrollToElementByText("Notifications")
fun notificationsButton() = mDevice.findObject(textContains("Notifications"))
notificationsButton().click()
Log.i(TAG, "openSettingsSubMenuNotifications: Trying to click the \"Notifications\" button")
mDevice.findObject(textContains("Notifications")).click()
Log.i(TAG, "openSettingsSubMenuNotifications: Clicked the \"Notifications\" button")
SystemSettingsRobot().interact()
return SystemSettingsRobot.Transition()
@ -414,22 +695,27 @@ class SettingsRobot {
fun openSettingsSubMenuDataCollection(interact: SettingsSubMenuDataCollectionRobot.() -> Unit): SettingsSubMenuDataCollectionRobot.Transition {
scrollToElementByText("Data collection")
fun dataCollectionButton() = mDevice.findObject(textContains("Data collection"))
dataCollectionButton().click()
Log.i(TAG, "openSettingsSubMenuDataCollection: Trying to click the \"Data collection\" button")
mDevice.findObject(textContains("Data collection")).click()
Log.i(TAG, "openSettingsSubMenuDataCollection: Clicked the \"Data collection\" button")
SettingsSubMenuDataCollectionRobot().interact()
return SettingsSubMenuDataCollectionRobot.Transition()
}
fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
Log.i(TAG, "openAddonsManagerMenu: Trying to click the \"Add-ons\" button")
addonsManagerButton().click()
Log.i(TAG, "openAddonsManagerMenu: Clicked the \"Add-ons\" button")
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
}
fun openOpenLinksInAppsMenu(interact: SettingsSubMenuOpenLinksInAppsRobot.() -> Unit): SettingsSubMenuOpenLinksInAppsRobot.Transition {
Log.i(TAG, "openOpenLinksInAppsMenu: Trying to click the \"Open links in apps\" button")
openLinksInAppsButton().click()
Log.i(TAG, "openOpenLinksInAppsMenu: Clicked the \"Open links in apps\" button")
SettingsSubMenuOpenLinksInAppsRobot().interact()
return SettingsSubMenuOpenLinksInAppsRobot.Transition()
@ -437,7 +723,9 @@ class SettingsRobot {
fun openHttpsOnlyModeMenu(interact: SettingsSubMenuHttpsOnlyModeRobot.() -> Unit): SettingsSubMenuHttpsOnlyModeRobot.Transition {
scrollToElementByText("HTTPS-Only Mode")
Log.i(TAG, "openHttpsOnlyModeMenu: Trying to click the \"HTTPS-Only Mode\" button")
onView(withText(getStringResource(R.string.preferences_https_only_title))).click()
Log.i(TAG, "openHttpsOnlyModeMenu: Clicked the \"HTTPS-Only Mode\" button")
mDevice.waitNotNull(
Until.findObjects(By.res("$packageName:id/https_only_switch")),
waitingTime,
@ -466,189 +754,15 @@ fun settingsScreen(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition
return SettingsRobot.Transition()
}
private fun assertSettingsView() {
// verify that we are in the correct library view
assertGeneralHeading()
assertPrivacyHeading()
assertAdvancedHeading()
assertAboutHeading()
}
// GENERAL SECTION
private fun assertSettingsToolbar() =
onView(
CoreMatchers.allOf(
withId(R.id.navigationToolbar),
hasDescendant(ViewMatchers.withContentDescription(R.string.action_bar_up_description)),
hasDescendant(withText(R.string.settings)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertGeneralHeading() {
scrollToElementByText("General")
onView(withText("General"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertSearchButton() {
mDevice.wait(Until.findObject(By.text("Search")), waitingTime)
onView(withText(R.string.preferences_search))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertHomepageButton() =
onView(withText(R.string.preferences_home_2)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertAutofillButton() =
onView(withText(R.string.preferences_autofill)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertLanguageButton() {
scrollToElementByText(getStringResource(R.string.preferences_language))
onView(withText(R.string.preferences_language)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertCustomizeButton() = onView(withText("Customize"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertThemeSelected() = onView(withText("Light"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertAccessibilityButton() = onView(withText("Accessibility"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertSetAsDefaultBrowserButton() {
scrollToElementByText("Set as default browser")
onView(withText("Set as default browser"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun toggleDefaultBrowserSwitch() {
scrollToElementByText("Privacy and security")
Log.i(TAG, "toggleDefaultBrowserSwitch: Trying to click the \"Set as default browser\" button")
onView(withText("Set as default browser")).perform(ViewActions.click())
}
private fun assertAndroidDefaultAppsMenuAppears() {
intended(IntentMatchers.hasAction(DEFAULT_APPS_SETTINGS_ACTION))
}
// PRIVACY SECTION
private fun assertPrivacyHeading() {
scrollToElementByText("Privacy and security")
onView(withText("Privacy and security"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertHTTPSOnlyModeButton() {
scrollToElementByText(getStringResource(R.string.preferences_https_only_title))
onView(
withText(R.string.preferences_https_only_title),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertEnhancedTrackingProtectionButton() {
mDevice.wait(Until.findObject(By.text("Privacy and Security")), waitingTime)
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Enhanced Tracking Protection")),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertLoginsAndPasswordsButton() {
scrollToElementByText("Logins and passwords")
onView(withText(R.string.preferences_passwords_logins_and_passwords))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertPrivateBrowsingButton() {
scrollToElementByText("Private browsing")
mDevice.wait(Until.findObject(By.text("Private browsing")), waitingTime)
onView(withText("Private browsing"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertSitePermissionsButton() {
scrollToElementByText("Site permissions")
onView(withText("Site permissions"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertDeleteBrowsingDataButton() {
scrollToElementByText("Delete browsing data")
onView(withText("Delete browsing data"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertDeleteBrowsingDataOnQuitButton() {
scrollToElementByText("Delete browsing data on quit")
onView(withText("Delete browsing data on quit"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertNotificationsButton() {
scrollToElementByText("Notifications")
onView(withText("Notifications"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertDataCollectionButton() {
scrollToElementByText("Data collection")
onView(withText("Data collection"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "toggleDefaultBrowserSwitch: Clicked the \"Set as default browser\" button")
}
private fun openLinksInAppsButton() = onView(withText(R.string.preferences_open_links_in_apps))
private fun assertOpenLinksInAppsButton() {
scrollToElementByText("Open links in apps")
openLinksInAppsButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "clickOpenLinksInAppsGoToSettingsCFRButton: Verified \"Open links in apps\" setting option")
}
// ADVANCED SECTION
private fun assertAdvancedHeading() {
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons")),
),
)
onView(withText("Add-ons"))
.check(matches(isCompletelyDisplayed()))
}
private fun assertAddonsButton() {
onView(withId(R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Add-ons")),
),
)
addonsManagerButton()
.check(matches(isCompletelyDisplayed()))
}
private fun assertRemoteDebuggingButton() {
scrollToElementByText("Remote debugging via USB")
onView(withText("Remote debugging via USB"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertLeakCanaryButton() {
scrollToElementByText("LeakCanary")
onView(withText("LeakCanary"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
// ABOUT SECTION
private fun assertAboutHeading(): ViewInteraction {
settingsList().scrollToEnd(LISTS_MAXSWIPES)
return onView(withText("About"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun rateOnGooglePlayHeading(): UiObject {
val rateOnGooglePlay = mDevice.findObject(UiSelector().text("Rate on Google Play"))
settingsList().scrollToEnd(LISTS_MAXSWIPES)
@ -679,27 +793,10 @@ fun clickRateButtonGooglePlay() {
rateOnGooglePlayHeading().click()
}
private fun assertGooglePlayRedirect() {
if (isPackageInstalled(GOOGLE_PLAY_SERVICES)) {
try {
intended(
allOf(
hasAction(Intent.ACTION_VIEW),
hasData(Uri.parse(SupportUtils.RATE_APP_URL)),
),
)
} catch (e: AssertionFailedError) {
BrowserRobot().verifyRateOnGooglePlayURL()
}
} else {
BrowserRobot().verifyRateOnGooglePlayURL()
}
}
private fun addonsManagerButton() = onView(withText(R.string.preferences_addons))
private fun goBackButton() =
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))
onView(CoreMatchers.allOf(withContentDescription("Navigate up")))
private fun settingsList() =
UiScrollable(UiSelector().resourceId("$packageName:id/recycler_view"))

@ -6,6 +6,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import android.view.KeyEvent
import android.view.KeyEvent.ACTION_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
@ -28,7 +29,9 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matcher
import org.junit.Assert.assertTrue
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.assertIsEnabled
import org.mozilla.fenix.helpers.isEnabled
@ -50,22 +53,68 @@ class SettingsSubMenuAccessibilityRobot {
const val TEXT_SIZE = 16f
}
fun verifyAutomaticFontSizingMenuItems() = assertAutomaticFontSizingMenuItems()
fun clickFontSizingSwitch() = toggleFontSizingSwitch()
fun verifyEnabledMenuItems() = assertEnabledMenuItems()
fun verifyEnabledMenuItems() {
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify that the \"Font Size\" title is visible")
onView(withText("Font Size")).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyEnabledMenuItems: Verified that the \"Font Size\" title is visible")
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify that the \"Font Size\" title is enabled")
onView(withText("Font Size")).check(matches(isEnabled(true)))
Log.i(TAG, "verifyEnabledMenuItems: Verified that the \"Font Size\" title is enabled")
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify that the \"Make text on websites larger or smaller\" summary is visible")
onView(withText("Make text on websites larger or smaller")).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyEnabledMenuItems: Verified that the \"Make text on websites larger or smaller\" summary is visible")
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify that the \"Make text on websites larger or smaller\" summary is enabled")
onView(withText("Make text on websites larger or smaller")).check(matches(isEnabled(true)))
Log.i(TAG, "verifyEnabledMenuItems: Verified that the \"Make text on websites larger or smaller\" summary is enabled")
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify the \"This is sample text. It is here to show how text will appear\" sample text")
onView(withId(org.mozilla.fenix.R.id.sampleText))
.check(matches(withText("This is sample text. It is here to show how text will appear when you increase or decrease the size with this setting.")))
Log.i(TAG, "verifyEnabledMenuItems: Verified the \"This is sample text. It is here to show how text will appear\" sample text")
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify that the seek bar value is set to 100%")
onView(withId(org.mozilla.fenix.R.id.seekbar_value)).check(matches(withText("100%")))
Log.i(TAG, "verifyEnabledMenuItems: Verified that the seek bar value is set to 100%")
Log.i(TAG, "verifyEnabledMenuItems: Trying to verify that the seek bar is visible")
onView(withId(org.mozilla.fenix.R.id.seekbar)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyEnabledMenuItems: Verified that the seek bar is visible")
}
fun verifyMenuItemsAreDisabled() = assertMenuItemsAreDisabled()
fun verifyMenuItemsAreDisabled() {
Log.i(TAG, "verifyMenuItemsAreDisabled: Trying to verify that the \"Font Size\" title is not enabled")
onView(withText("Font Size")).assertIsEnabled(false)
Log.i(TAG, "verifyMenuItemsAreDisabled: Verified that the \"Font Size\" title is not enabled")
Log.i(TAG, "verifyMenuItemsAreDisabled: Trying to verify that the \"Make text on websites larger or smaller\" summary is not enabled")
onView(withText("Make text on websites larger or smaller")).assertIsEnabled(false)
Log.i(TAG, "verifyMenuItemsAreDisabled: Verified that the \"Make text on websites larger or smaller\" summary is not enabled")
Log.i(TAG, "verifyMenuItemsAreDisabled: Trying to verify that the \"This is sample text. It is here to show how text will appear\" sample text is not enabled")
onView(withId(org.mozilla.fenix.R.id.sampleText)).assertIsEnabled(false)
Log.i(TAG, "verifyMenuItemsAreDisabled: Verified that the \"This is sample text. It is here to show how text will appear\" sample text is not enabled")
Log.i(TAG, "verifyMenuItemsAreDisabled: Trying to verify that the seek bar value is not enabled")
onView(withId(org.mozilla.fenix.R.id.seekbar_value)).assertIsEnabled(false)
Log.i(TAG, "verifyMenuItemsAreDisabled: Verified that the seek bar value is not enabled")
Log.i(TAG, "verifyMenuItemsAreDisabled: Trying to verify that the seek bar is not enabled")
onView(withId(org.mozilla.fenix.R.id.seekbar)).assertIsEnabled(false)
Log.i(TAG, "verifyMenuItemsAreDisabled: Verified that the seek bar is not enabled")
}
fun changeTextSizeSlider(seekBarPercentage: Int) = adjustTextSizeSlider(seekBarPercentage)
fun verifyTextSizePercentage(textSize: Int) = assertTextSizePercentage(textSize)
fun verifyTextSizePercentage(textSize: Int) {
Log.i(TAG, "verifyTextSizePercentage: Trying to verify that the text size percentage is set to: $textSize")
onView(withId(org.mozilla.fenix.R.id.sampleText))
.check(textSizePercentageEquals(textSize))
Log.i(TAG, "verifyTextSizePercentage: Verified that the text size percentage is set to: $textSize")
}
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
Log.i(TAG, "goBack: Waiting for device to be idle")
mDevice.waitForIdle()
Log.i(TAG, "goBack: Waited for device to be idle")
Log.i(TAG, "goBack: Trying to click the navigate up toolbar button")
goBackButton().perform(click())
Log.i(TAG, "goBack: Clicked the navigate up toolbar button")
SettingsRobot().interact()
return SettingsRobot.Transition()
@ -73,69 +122,20 @@ class SettingsSubMenuAccessibilityRobot {
}
}
private fun assertAutomaticFontSizingMenuItems() {
onView(withText("Automatic font sizing"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
val strFont = "Font size will match your Android settings. Disable to manage font size here."
onView(withText(strFont))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun toggleFontSizingSwitch() {
Log.i(TAG, "toggleFontSizingSwitch: Trying to click the \"Automatic font sizing\" toggle")
// Toggle font size to off
onView(withText("Automatic font sizing"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.perform(click())
}
private fun assertEnabledMenuItems() {
assertFontSize()
assertSliderBar()
}
private fun assertFontSize() {
val view = onView(withText("Font Size"))
view.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.check(matches(isEnabled(true)))
val strFont = "Make text on websites larger or smaller"
onView(withText(strFont))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.check(matches(isEnabled(true)))
}
private fun assertSliderBar() {
onView(withId(org.mozilla.fenix.R.id.sampleText))
.check(matches(withText("This is sample text. It is here to show how text will appear when you increase or decrease the size with this setting.")))
onView(withId(org.mozilla.fenix.R.id.seekbar_value))
.check(matches(withText("100%")))
onView(withId(org.mozilla.fenix.R.id.seekbar))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "toggleFontSizingSwitch: Clicked the \"Automatic font sizing\" toggle")
}
private fun adjustTextSizeSlider(seekBarPercentage: Int) {
Log.i(TAG, "adjustTextSizeSlider: Trying to set the seek bar value to: $seekBarPercentage")
onView(withId(org.mozilla.fenix.R.id.seekbar))
.perform(SeekBarChangeProgressViewAction(seekBarPercentage))
}
private fun assertTextSizePercentage(textSize: Int) {
onView(withId(org.mozilla.fenix.R.id.sampleText))
.check(textSizePercentageEquals(textSize))
}
private fun assertMenuItemsAreDisabled() {
onView(withText("Font Size")).assertIsEnabled(false)
val strFont = "Make text on websites larger or smaller"
onView(withText(strFont)).assertIsEnabled(false)
onView(withId(org.mozilla.fenix.R.id.sampleText)).assertIsEnabled(false)
onView(withId(org.mozilla.fenix.R.id.seekbar_value)).assertIsEnabled(false)
onView(withId(org.mozilla.fenix.R.id.seekbar)).assertIsEnabled(false)
Log.i(TAG, "adjustTextSizeSlider: Seek bar value was set to: $seekBarPercentage")
}
private fun goBackButton() =
@ -189,9 +189,11 @@ fun calculateStepSizeFromPercentage(textSizePercentage: Int): Int {
return ((textSizePercentage - MIN_VALUE) / STEP_SIZE)
}
fun checkTextSizeOnWebsite(textSizePercentage: Int, components: Components): Boolean {
fun checkTextSizeOnWebsite(textSizePercentage: Int, components: Components) {
Log.i(TAG, "checkTextSizeOnWebsite: Trying to verify that text size on website is: $textSizePercentage")
// Checks the Gecko engine settings for the font size
val textSize = calculateStepSizeFromPercentage(textSizePercentage)
val newTextScale = ((textSize * STEP_SIZE) + MIN_VALUE).toFloat() / DECIMAL_CONVERSION
return components.core.engine.settings.fontSizeFactor == newTextScale
assertTrue("$TAG: text size on website was not set to: $textSizePercentage", components.core.engine.settings.fontSizeFactor == newTextScale)
Log.i(TAG, "checkTextSizeOnWebsite: Verified that text size on website is: $textSizePercentage")
}

@ -5,6 +5,7 @@
package org.mozilla.fenix.ui.robots
import android.util.Log
import android.view.View
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
@ -17,6 +18,7 @@ import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.AppAndSystemHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.Constants.TAG
import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource
import org.mozilla.fenix.helpers.click
@ -28,8 +30,9 @@ class SettingsSubMenuAddonsManagerAddonDetailedMenuRobot {
class Transition {
fun goBack(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))
goBackButton().click()
Log.i(TAG, "goBack: Trying to click the navigate up button")
onView(allOf(withContentDescription("Navigate up"))).click()
Log.i(TAG, "goBack: Clicked the navigate up button")
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()
@ -42,8 +45,12 @@ class SettingsSubMenuAddonsManagerAddonDetailedMenuRobot {
View.VISIBLE,
),
) {
Log.i(TAG, "removeAddon: Trying to verify that the remove add-on button is visible")
removeAddonButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "removeAddon: Verified that the remove add-on button is visible")
Log.i(TAG, "removeAddon: Trying to click the remove add-on button")
removeAddonButton().click()
Log.i(TAG, "removeAddon: Clicked the remove add-on button")
}
SettingsSubMenuAddonsManagerRobot().interact()

@ -55,12 +55,15 @@ class SettingsSubMenuAddonsManagerRobot {
fun verifyAddonsListIsDisplayed(shouldBeDisplayed: Boolean) =
assertUIObjectExists(addonsList(), exists = shouldBeDisplayed)
fun verifyAddonDownloadOverlay() =
fun verifyAddonDownloadOverlay() {
Log.i(TAG, "verifyAddonDownloadOverlay: Trying to verify that the \"Downloading and verifying add-on\" prompt is displayed")
onView(withText(R.string.mozac_add_on_install_progress_caption)).check(matches(isDisplayed()))
Log.i(TAG, "verifyAddonDownloadOverlay: Verified that the \"Downloading and verifying add-on\" prompt is displayed")
}
fun verifyAddonPermissionPrompt(addonName: String) {
mDevice.waitNotNull(Until.findObject(By.text("Add $addonName?")), waitingTime)
Log.i(TAG, "verifyAddonPermissionPrompt: Trying to verify that the add-ons permission prompt items are displayed")
onView(
allOf(
withText("Add $addonName?"),
@ -71,11 +74,14 @@ class SettingsSubMenuAddonsManagerRobot {
)
.inRoot(isDialog())
.check(matches(isDisplayed()))
Log.i(TAG, "verifyAddonPermissionPrompt: Verified that the add-ons permission prompt items are displayed")
}
fun clickInstallAddon(addonName: String) {
Log.i(TAG, "clickInstallAddon: Looking for $addonName install button")
Log.i(TAG, "clickInstallAddon: Waiting for $waitingTime ms for add-ons list to exist")
addonsList().waitForExists(waitingTime)
Log.i(TAG, "clickInstallAddon: Waited for $waitingTime ms for add-ons list to exist")
Log.i(TAG, "clickInstallAddon: Trying to scroll into view the install $addonName button")
addonsList().scrollIntoView(
mDevice.findObject(
UiSelector()
@ -83,6 +89,8 @@ class SettingsSubMenuAddonsManagerRobot {
.childSelector(UiSelector().text(addonName)),
),
)
Log.i(TAG, "clickInstallAddon: Scrolled into view the install $addonName button")
Log.i(TAG, "clickInstallAddon: Trying to ensure the full visibility of the the install $addonName button")
addonsList().ensureFullyVisible(
mDevice.findObject(
UiSelector()
@ -90,22 +98,24 @@ class SettingsSubMenuAddonsManagerRobot {
.childSelector(UiSelector().text(addonName)),
),
)
Log.i(TAG, "clickInstallAddon: Found $addonName install button")
Log.i(TAG, "clickInstallAddon: Ensured the full visibility of the the install $addonName button")
Log.i(TAG, "clickInstallAddon: Trying to click the install $addonName button")
installButtonForAddon(addonName).click()
Log.i(TAG, "clickInstallAddon: Clicked Install $addonName button")
Log.i(TAG, "clickInstallAddon: Clicked the install $addonName button")
}
fun verifyAddonInstallCompleted(addonName: String, activityTestRule: HomeActivityIntentTestRule) {
for (i in 1..RETRY_COUNT) {
Log.i(TAG, "verifyAddonInstallCompleted: Started try #$i")
try {
assertUIObjectExists(itemWithText("Okay, Got it"), waitingTime = waitingTimeLong)
assertUIObjectExists(itemWithText("OK"), waitingTime = waitingTimeLong)
break
} catch (e: AssertionError) {
Log.i(TAG, "verifyAddonInstallCompleted: AssertionError caught, executing fallback methods")
if (i == RETRY_COUNT) {
throw e
} else {
Log.i(TAG, "verifyAddonInstallCompleted: $addonName failed to install on try #$i")
restartApp(activityTestRule)
homeScreen {
}.openThreeDotMenu {
@ -121,40 +131,98 @@ class SettingsSubMenuAddonsManagerRobot {
}
fun verifyAddonInstallCompletedPrompt(addonName: String) {
Log.i(TAG, "verifyAddonInstallCompletedPrompt: Trying to verify that completed add-on install prompt items are visible")
onView(
allOf(
withText("Okay, Got it"),
withText("OK"),
withParent(instanceOf(RelativeLayout::class.java)),
hasSibling(withText("$addonName has been added to $appName")),
hasSibling(withText("Open it in the menu")),
hasSibling(withText("Access $addonName from the $appName menu.")),
hasSibling(withText("Allow in private browsing")),
),
)
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyAddonInstallCompletedPrompt: Verified that completed add-on install prompt items are visible")
}
fun closeAddonInstallCompletePrompt() {
onView(withText("Okay, Got it")).click()
Log.i(TAG, "closeAddonInstallCompletePrompt: Trying to click the \"OK\" button from the completed add-on install prompt")
onView(withText("OK")).click()
Log.i(TAG, "closeAddonInstallCompletePrompt: Clicked the \"OK\" button from the completed add-on install prompt")
}
fun verifyAddonIsInstalled(addonName: String) {
scrollToElementByText(addonName)
assertAddonIsInstalled(addonName)
Log.i(TAG, "verifyAddonIsInstalled: Trying to verify that the $addonName add-on was installed")
onView(
allOf(
withId(R.id.add_button),
isDescendantOfA(withId(R.id.add_on_item)),
hasSibling(hasDescendant(withText(addonName))),
),
).check(matches(withEffectiveVisibility(Visibility.INVISIBLE)))
Log.i(TAG, "verifyAddonIsInstalled: Verified that the $addonName add-on was installed")
}
fun verifyEnabledTitleDisplayed() {
Log.i(TAG, "verifyEnabledTitleDisplayed: Trying to verify that the \"Enabled\" heading is displayed")
onView(withText("Enabled"))
.check(matches(isCompletelyDisplayed()))
Log.i(TAG, "verifyEnabledTitleDisplayed: Verified that the \"Enabled\" heading is displayed")
}
fun cancelInstallAddon() = cancelInstall()
fun acceptPermissionToInstallAddon() = allowPermissionToInstall()
fun verifyAddonsItems() = assertAddonsItems()
fun verifyAddonCanBeInstalled(addonName: String) = assertAddonCanBeInstalled(addonName)
fun verifyAddonsItems() {
Log.i(TAG, "verifyAddonsItems: Trying to verify that the \"Recommended\" heading is visible")
onView(allOf(withId(R.id.title), withText("Recommended")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyAddonsItems: Verified that the \"Recommended\" heading is visible")
Log.i(TAG, "verifyAddonsItems: Trying to verify that all uBlock Origin items are completely displayed")
onView(
allOf(
isAssignableFrom(RelativeLayout::class.java),
withId(R.id.add_on_item),
hasDescendant(allOf(withId(R.id.add_on_icon), isCompletelyDisplayed())),
hasDescendant(
allOf(
withId(R.id.details_container),
hasDescendant(withText("uBlock Origin")),
hasDescendant(withText("Finally, an efficient wide-spectrum content blocker. Easy on CPU and memory.")),
hasDescendant(withId(R.id.rating)),
hasDescendant(withId(R.id.review_count)),
),
),
hasDescendant(withId(R.id.add_button)),
),
).check(matches(isCompletelyDisplayed()))
Log.i(TAG, "verifyAddonsItems: Verified that all uBlock Origin items are completely displayed")
}
fun verifyAddonCanBeInstalled(addonName: String) {
scrollToElementByText(addonName)
mDevice.waitNotNull(Until.findObject(By.text(addonName)), waitingTime)
Log.i(TAG, "verifyAddonCanBeInstalled: Trying to verify that the install $addonName button is visible")
onView(
allOf(
withId(R.id.add_button),
hasSibling(
hasDescendant(
allOf(
withId(R.id.add_on_name),
withText(addonName),
),
),
),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "verifyAddonCanBeInstalled: Verified that the install $addonName button is visible")
}
fun selectAllowInPrivateBrowsing() {
assertUIObjectExists(itemWithText("Allow in private browsing"), waitingTime = waitingTimeLong)
Log.i(TAG, "selectAllowInPrivateBrowsing: Trying to click the \"Allow in private browsing\" check box")
onView(withId(R.id.allow_in_private_browsing)).click()
Log.i(TAG, "selectAllowInPrivateBrowsing: Clicked the \"Allow in private browsing\" check box")
}
fun installAddon(addonName: String, activityTestRule: HomeActivityIntentTestRule) {
@ -170,8 +238,9 @@ class SettingsSubMenuAddonsManagerRobot {
class Transition {
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
fun goBackButton() = onView(allOf(withContentDescription("Navigate up")))
goBackButton().click()
Log.i(TAG, "goBack: Trying to click navigate up toolbar button")
onView(allOf(withContentDescription("Navigate up"))).click()
Log.i(TAG, "goBack: Clicked the navigate up toolbar button")
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
@ -182,19 +251,12 @@ class SettingsSubMenuAddonsManagerRobot {
interact: SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.() -> Unit,
): SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.Transition {
scrollToElementByText(addonName)
onView(
allOf(
withId(R.id.add_on_item),
hasDescendant(
allOf(
withId(R.id.add_on_name),
withText(addonName),
),
),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
.perform(click())
Log.i(TAG, "openDetailedMenuForAddon: Trying to verify that the $addonName add-on is visible")
addonItem(addonName).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "openDetailedMenuForAddon: Verified that the $addonName add-on is visible")
Log.i(TAG, "openDetailedMenuForAddon: Trying to click the $addonName add-on")
addonItem(addonName).perform(click())
Log.i(TAG, "openDetailedMenuForAddon: Clicked the $addonName add-on")
SettingsSubMenuAddonsManagerAddonDetailedMenuRobot().interact()
return SettingsSubMenuAddonsManagerAddonDetailedMenuRobot.Transition()
@ -204,87 +266,29 @@ class SettingsSubMenuAddonsManagerRobot {
private fun installButtonForAddon(addonName: String) =
onView(
allOf(
withContentDescription(R.string.mozac_feature_addons_install_addon_content_description),
withContentDescription("Install $addonName"),
isDescendantOfA(withId(R.id.add_on_item)),
hasSibling(hasDescendant(withText(addonName))),
),
)
private fun assertAddonIsInstalled(addonName: String) {
onView(
allOf(
withId(R.id.add_button),
isDescendantOfA(withId(R.id.add_on_item)),
hasSibling(hasDescendant(withText(addonName))),
),
).check(matches(withEffectiveVisibility(Visibility.INVISIBLE)))
}
private fun cancelInstall() {
onView(allOf(withId(R.id.deny_button), withText("Cancel")))
.check(matches(isCompletelyDisplayed()))
.perform(click())
Log.i(TAG, "cancelInstall: Trying to verify that the \"Cancel\" button is completely displayed")
onView(allOf(withId(R.id.deny_button), withText("Cancel"))).check(matches(isCompletelyDisplayed()))
Log.i(TAG, "cancelInstall: Verified that the \"Cancel\" button is completely displayed")
Log.i(TAG, "cancelInstall: Trying to click the \"Cancel\" button")
onView(allOf(withId(R.id.deny_button), withText("Cancel"))).perform(click())
Log.i(TAG, "cancelInstall: Clicked the \"Cancel\" button")
}
private fun allowPermissionToInstall() {
mDevice.waitNotNull(Until.findObject(By.text("Add")), waitingTime)
onView(allOf(withId(R.id.allow_button), withText("Add")))
.check(matches(isCompletelyDisplayed()))
.perform(click())
}
private fun assertAddonsItems() {
assertRecommendedTitleDisplayed()
assertAddons()
}
private fun assertRecommendedTitleDisplayed() {
onView(allOf(withId(R.id.title), withText("Recommended")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertAddons() {
assertAddonUblock()
}
private fun assertAddonUblock() {
onView(
allOf(
isAssignableFrom(RelativeLayout::class.java),
withId(R.id.add_on_item),
hasDescendant(allOf(withId(R.id.add_on_icon), isCompletelyDisplayed())),
hasDescendant(
allOf(
withId(R.id.details_container),
hasDescendant(withText("uBlock Origin")),
hasDescendant(withText("Finally, an efficient wide-spectrum content blocker. Easy on CPU and memory.")),
hasDescendant(withId(R.id.rating)),
hasDescendant(withId(R.id.review_count)),
),
),
hasDescendant(withId(R.id.add_button)),
),
).check(matches(isCompletelyDisplayed()))
}
private fun assertAddonCanBeInstalled(addonName: String) {
scrollToElementByText(addonName)
mDevice.waitNotNull(Until.findObject(By.text(addonName)), waitingTime)
onView(
allOf(
withId(R.id.add_button),
hasSibling(
hasDescendant(
allOf(
withId(R.id.add_on_name),
withText(addonName),
),
),
),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
Log.i(TAG, "allowPermissionToInstall: Trying to verify that the \"Add\" button is completely displayed")
onView(allOf(withId(R.id.allow_button), withText("Add"))).check(matches(isCompletelyDisplayed()))
Log.i(TAG, "allowPermissionToInstall: Verified that the \"Add\" button is completely displayed")
Log.i(TAG, "allowPermissionToInstall: Trying to click the \"Add\" button")
onView(allOf(withId(R.id.allow_button), withText("Add"))).perform(click())
Log.i(TAG, "allowPermissionToInstall: Clicked the \"Add\" button")
}
}
@ -293,5 +297,18 @@ fun addonsMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): Settings
return SettingsSubMenuAddonsManagerRobot.Transition()
}
private fun addonItem(addonName: String) =
onView(
allOf(
withId(R.id.add_on_item),
hasDescendant(
allOf(
withId(R.id.add_on_name),
withText(addonName),
),
),
),
)
private fun addonsList() =
UiScrollable(UiSelector().resourceId("$packageName:id/add_ons_list")).setAsVerticalList()

@ -9,7 +9,9 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
@ -38,8 +40,16 @@ class SettingsSubMenuAutofillRobot {
Log.i(TAG, "verifyAutofillToolbarTitle: Verified \"Autofill\" toolbar title exists")
}
fun verifyManageAddressesToolbarTitle() {
assertUIObjectExists(manageAddressesToolbarTitle)
Log.i(TAG, "verifyManageAddressesToolbarTitle: Verified \"Manage addresses\" toolbar title exists")
Log.i(TAG, "verifyManageAddressesToolbarTitle: Trying to verify the \"Manage addresses\" toolbar title is displayed")
onView(
allOf(
withId(R.id.navigationToolbar),
withChild(
withText(R.string.preferences_addresses_manage_addresses),
),
),
).check(matches(isDisplayed()))
Log.i(TAG, "verifyManageAddressesToolbarTitle: Verified the \"Manage addresses\" toolbar title is displayed")
}
fun verifyAddressAutofillSection(isAddressAutofillEnabled: Boolean, userHasSavedAddress: Boolean) {

@ -23,8 +23,6 @@ import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matchers
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -101,11 +99,6 @@ class SettingsSubMenuHomepageRobot {
fun selectWallpaper(wallpaperName: String) =
mDevice.findObject(UiSelector().description(wallpaperName)).click()
fun verifySnackBarText(expectedText: String) =
assertUIObjectExists(
itemContainingText(expectedText),
)
fun verifySponsoredShortcutsCheckBox(checked: Boolean) = assertSponsoredShortcutsCheckBox(checked)
class Transition {

@ -206,9 +206,6 @@ class TabDrawerRobot {
}
}
fun verifySnackBarText(expectedText: String) =
assertUIObjectExists(itemContainingText(expectedText))
fun snackBarButtonClick(expectedText: String) {
val snackBarButton =
mDevice.findObject(

@ -67,7 +67,7 @@ object FeatureFlags {
* Allows users to enable translations.
* Preference to fully enable translations is pref_key_enable_translations.
*/
val translations = Config.channel.isDebug
val translations = Config.channel.isNightlyOrDebug
/**
* Allows users to enable Firefox Suggest.

@ -381,6 +381,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
components.fxSuggest.ingestionScheduler.stopPeriodicIngestion()
}
}
components.core.fileUploadsDirCleaner.cleanUploadsDirectory()
}
// Account manager initialization needs to happen on the main thread.
GlobalScope.launch(Dispatchers.Main) {

@ -14,7 +14,6 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.StrictMode
import android.os.SystemClock
import android.text.TextUtils
import android.text.format.DateUtils
import android.util.AttributeSet
@ -31,7 +30,6 @@ import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.ActionBar
import androidx.appcompat.widget.Toolbar
import androidx.core.app.NotificationManagerCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
@ -63,7 +61,6 @@ import mozilla.components.feature.media.ext.findActiveMediaTab
import mozilla.components.feature.privatemode.notification.PrivateNotificationFeature
import mozilla.components.feature.search.BrowserStoreSearchAdapter
import mozilla.components.service.fxa.sync.SyncReason
import mozilla.components.support.base.ext.areNotificationsEnabledSafe
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.log.logger.Logger
@ -128,7 +125,6 @@ import org.mozilla.fenix.messaging.FenixNimbusMessagingController
import org.mozilla.fenix.messaging.MessageNotificationWorker
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker
import org.mozilla.fenix.onboarding.ensureMarketingChannelExists
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.perf.Performance
@ -155,12 +151,6 @@ import java.util.Locale
*/
@SuppressWarnings("TooManyFunctions", "LargeClass", "LongMethod")
open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
// DO NOT MOVE ANYTHING ABOVE THIS, GETTING INIT TIME IS CRITICAL
// we need to store startup timestamp for warm startup. we cant directly store
// inside AppStartupTelemetry since that class lives inside components and
// components requires context to access.
protected val homeActivityInitTimeStampNanoSeconds = SystemClock.elapsedRealtimeNanos()
private lateinit var binding: ActivityHomeBinding
lateinit var themeManager: ThemeManager
lateinit var browsingModeManager: BrowsingModeManager
@ -243,8 +233,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
// There is disk read violations on some devices such as samsung and pixel for android 9/10
components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
// Theme setup should always be called before super.onCreate
setupThemeAndBrowsingMode(getModeFromIntentOrLastKnown(intent))
// Browsing mode & theme setup should always be called before super.onCreate.
setupBrowsingMode(getModeFromIntentOrLastKnown(intent))
setupTheme()
super.onCreate(savedInstanceState)
}
@ -269,34 +261,32 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
binding = ActivityHomeBinding.inflate(layoutInflater)
if (Config.channel.isNightlyOrDebug) {
lifecycleScope.launch {
val debugSettingsRepository = DefaultDebugSettingsRepository(
context = this@HomeActivity,
writeScope = this,
)
lifecycleScope.launch {
val debugSettingsRepository = DefaultDebugSettingsRepository(
context = this@HomeActivity,
writeScope = this,
)
debugSettingsRepository.debugDrawerEnabled
.distinctUntilChanged()
.collect { enabled ->
with(binding.debugOverlay) {
if (enabled) {
visibility = View.VISIBLE
setContent {
FenixOverlay(
browserStore = components.core.store,
inactiveTabsEnabled = settings().inactiveTabsAreEnabled,
)
}
} else {
setContent {}
visibility = View.GONE
debugSettingsRepository.debugDrawerEnabled
.distinctUntilChanged()
.collect { enabled ->
with(binding.debugOverlay) {
if (enabled) {
visibility = View.VISIBLE
setContent {
FenixOverlay(
browserStore = components.core.store,
inactiveTabsEnabled = settings().inactiveTabsAreEnabled,
)
}
} else {
setContent {}
visibility = View.GONE
}
}
}
}
}
setContentView(binding.root)
@ -345,8 +335,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
if (settings().showHomeOnboardingDialog && components.fenixOnboarding.userHasBeenOnboarded()) {
navHost.navController.navigate(NavGraphDirections.actionGlobalHomeOnboardingDialog())
}
showNotificationPermissionPromptIfRequired()
}
Performance.processIntentIfPerformanceTest(intent, this)
@ -443,30 +431,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
}
/**
* On Android 13 or above, prompt the user for notification permission at the start.
* Show the pre permission dialog to the user once if the notification are not enabled.
*/
private fun showNotificationPermissionPromptIfRequired() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!NotificationManagerCompat.from(applicationContext).areNotificationsEnabledSafe() &&
settings().numberOfAppLaunches <= 1
) {
// Recording the exposure event here to capture all users who met all criteria to receive
// the pre permission notification prompt
FxNimbus.features.prePermissionNotificationPrompt.recordExposure()
if (settings().notificationPrePermissionPromptEnabled) {
if (!settings().isNotificationPrePermissionShown) {
navHost.navController.navigate(NavGraphDirections.actionGlobalHomeNotificationPermissionDialog())
}
} else {
// This will trigger the notification permission system dialog as app targets sdk 32.
ensureMarketingChannelExists(applicationContext)
}
}
}
private fun maybeShowSplashScreen() {
if (components.settings.isFirstSplashScreenShown) {
return
@ -555,7 +519,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.core.store.dispatch(SearchAction.RefreshSearchEnginesAction)
}
override fun onStart() {
final override fun onStart() {
// DO NOT MOVE ANYTHING ABOVE THIS getProfilerTime CALL.
val startProfilerTime = components.core.engine.profiler?.getProfilerTime()
@ -575,7 +539,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
) // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL.
}
override fun onStop() {
final override fun onStop() {
super.onStop()
// Diagnostic breadcrumb for "Display already aquired" crash:
@ -631,6 +595,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
outContent?.webUri = currentTabUrl?.let { Uri.parse(it) }
}
@CallSuper
override fun onDestroy() {
super.onDestroy()
@ -649,12 +614,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
privateNotificationObserver?.stop()
components.notificationsDelegate.unBindActivity(this)
if (this !is ExternalAppBrowserActivity) {
val activityStartedWithLink = startupPathProvider.startupPathForActivity == StartupPathProvider.StartupPath.VIEW
if (this !is ExternalAppBrowserActivity && !activityStartedWithLink) {
stopMediaSession()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
final override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Diagnostic breadcrumb for "Display already aquired" crash:
@ -664,7 +630,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
)
}
override fun recreate() {
final override fun recreate() {
// Diagnostic breadcrumb for "Display already aquired" crash:
// https://github.com/mozilla-mobile/android-components/issues/7960
breadcrumb(
@ -749,12 +715,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
else -> super.onCreateView(parent, name, context, attrs)
}
override fun onActionModeStarted(mode: ActionMode?) {
final override fun onActionModeStarted(mode: ActionMode?) {
actionMode = mode
super.onActionModeStarted(mode)
}
override fun onActionModeFinished(mode: ActionMode?) {
final override fun onActionModeFinished(mode: ActionMode?) {
actionMode = null
super.onActionModeFinished(mode)
}
@ -821,7 +787,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
return false
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
final override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ProfilerMarkers.addForDispatchTouchEvent(components.core.engine.profiler, ev)
return super.dispatchTouchEvent(ev)
}
@ -882,6 +848,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
* private mode directly before the content view is created. Returns the mode set by the intent
* otherwise falls back to the last known mode.
*/
@VisibleForTesting
internal fun getModeFromIntentOrLastKnown(intent: Intent?): BrowsingMode {
intent?.toSafeIntent()?.let {
if (it.hasExtra(PRIVATE_BROWSING_MODE)) {
@ -905,12 +872,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
return false
}
private fun setupThemeAndBrowsingMode(mode: BrowsingMode) {
private fun setupBrowsingMode(mode: BrowsingMode) {
settings().lastKnownMode = mode
browsingModeManager = createBrowsingModeManager(mode)
}
private fun setupTheme() {
themeManager = createThemeManager()
themeManager.setActivityTheme(this)
themeManager.applyStatusBarTheme(this)
// ExternalAppBrowserActivity exclusively handles it's own theming unless in private mode.
if (this !is ExternalAppBrowserActivity || browsingModeManager.mode.isPrivate) {
themeManager.setActivityTheme(this)
themeManager.applyStatusBarTheme(this)
}
}
// Stop active media when activity is destroyed.
@ -934,7 +907,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
* Returns the [supportActionBar], inflating it if necessary.
* Everyone should call this instead of supportActionBar.
*/
override fun getSupportActionBarAndInflateIfNecessary(): ActionBar {
final override fun getSupportActionBarAndInflateIfNecessary(): ActionBar {
if (!isToolbarInflated) {
navigationToolbar = binding.navigationToolbarStub.inflate() as Toolbar
@ -949,7 +922,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
@Suppress("SpreadOperator")
fun setupNavigationToolbar(vararg topLevelDestinationIds: Int) {
private fun setupNavigationToolbar(vararg topLevelDestinationIds: Int) {
NavigationUI.setupWithNavController(
navigationToolbar,
navHost.navController,
@ -1132,13 +1105,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
navController.navigate(NavGraphDirections.actionStartupHome())
}
override fun attachBaseContext(base: Context) {
final override fun attachBaseContext(base: Context) {
base.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
super.attachBaseContext(base)
}
}
override fun getSystemService(name: String): Any? {
final override fun getSystemService(name: String): Any? {
// Issue #17759 had a crash with the PerformanceInflater.kt on Android 5.0 and 5.1
// when using the TimePicker. Since the inflater was created for performance monitoring
// purposes and that we test on new android versions, this means that any difference in
@ -1152,7 +1125,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
return super.getSystemService(name)
}
protected open fun createBrowsingModeManager(initialMode: BrowsingMode): BrowsingModeManager {
private fun createBrowsingModeManager(initialMode: BrowsingMode): BrowsingModeManager {
return DefaultBrowsingModeManager(initialMode, components.settings) { newMode ->
updateSecureWindowFlags(newMode)
themeManager.currentTheme = newMode
@ -1169,7 +1142,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
}
protected open fun createThemeManager(): ThemeManager {
private fun createThemeManager(): ThemeManager {
return DefaultThemeManager(browsingModeManager.mode, this)
}
@ -1220,7 +1193,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
* Indicates if the user should be redirected to the [BrowserFragment] or to the [HomeFragment],
* links from an external apps should always opened in the [BrowserFragment].
*/
fun shouldStartOnHome(intent: Intent? = this.intent): Boolean {
@VisibleForTesting
internal fun shouldStartOnHome(intent: Intent? = this.intent): Boolean {
return components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
// We only want to open on home when users tap the app,
// we want to ignore other cases when the app gets open by users clicking on links.
@ -1300,6 +1274,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
// PWA must have been used within last 30 days to be considered "recently used" for the
// telemetry purposes.
const val PWA_RECENTLY_USED_THRESHOLD = DateUtils.DAY_IN_MILLIS * 30L
private const val PWA_RECENTLY_USED_THRESHOLD = DateUtils.DAY_IN_MILLIS * 30L
}
}

@ -10,6 +10,8 @@ import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.view.View
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.core.net.toUri
import androidx.core.text.HtmlCompat
import androidx.core.text.getSpans
@ -20,6 +22,7 @@ import mozilla.components.feature.addons.ui.updatedAtDate
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentAddOnDetailsBinding
import org.mozilla.fenix.ext.addUnderline
import java.text.DateFormat
import java.text.NumberFormat
import java.util.Locale
@ -61,14 +64,18 @@ class AddonDetailsBindingDelegate(
private fun bindRating(addon: Addon) {
addon.rating?.let { rating ->
val resources = binding.root.resources
val ratingContentDescription = resources.getString(R.string.mozac_feature_addons_rating_content_description)
binding.ratingView.contentDescription = String.format(ratingContentDescription, rating.average)
val ratingContentDescription =
resources.getString(R.string.mozac_feature_addons_rating_content_description_2)
binding.ratingLabel.contentDescription = String.format(ratingContentDescription, rating.average)
binding.ratingView.rating = rating.average
val reviewCount = resources.getString(R.string.mozac_feature_addons_user_rating_count_2)
binding.reviewCount.contentDescription = String.format(reviewCount, numberFormatter.format(rating.reviews))
binding.reviewCount.text = numberFormatter.format(rating.reviews)
if (addon.ratingUrl.isNotBlank()) {
binding.reviewCount.setTextColor(binding.root.context.getColorFromAttr(R.attr.textAccent))
binding.reviewCount.addUnderline()
binding.reviewCount.setOnClickListener {
interactor.openWebsite(addon.ratingUrl.toUri())
}
@ -83,6 +90,7 @@ class AddonDetailsBindingDelegate(
return
}
binding.homePageLabel.addUnderline()
binding.homePageLabel.setOnClickListener {
interactor.openWebsite(addon.homepageUrl.toUri())
}
@ -96,7 +104,9 @@ class AddonDetailsBindingDelegate(
return
}
binding.lastUpdatedText.text = dateFormatter.format(addon.updatedAtDate)
val formattedDate = dateFormatter.format(addon.updatedAtDate)
binding.lastUpdatedText.text = formattedDate
binding.lastUpdatedLabel.joinContentDescriptions(formattedDate)
}
private fun bindVersion(addon: Addon) {
@ -114,6 +124,7 @@ class AddonDetailsBindingDelegate(
} else {
binding.versionText.setOnLongClickListener(null)
}
binding.versionLabel.joinContentDescriptions(version)
}
private fun bindAuthor(addon: Addon) {
@ -129,10 +140,12 @@ class AddonDetailsBindingDelegate(
if (author.url.isNotBlank()) {
binding.authorText.setTextColor(binding.root.context.getColorFromAttr(R.attr.textAccent))
binding.authorText.addUnderline()
binding.authorText.setOnClickListener {
interactor.openWebsite(author.url.toUri())
}
}
binding.authorLabel.joinContentDescriptions(author.name)
}
private fun bindDetails(addon: Addon) {
@ -175,8 +188,14 @@ class AddonDetailsBindingDelegate(
return
}
binding.detailUrl.addUnderline()
binding.detailUrl.setOnClickListener {
interactor.openWebsite(addon.detailUrl.toUri())
}
}
@VisibleForTesting
internal fun TextView.joinContentDescriptions(text: String) {
this.contentDescription = "${this.text} $text"
}
}

@ -43,6 +43,7 @@ abstract class AddonPopupBaseFragment : Fragment(), EngineSession.Observer, User
store = requireComponents.core.store,
customTabId = it.id,
fragmentManager = parentFragmentManager,
fileUploadsDirCleaner = requireComponents.core.fileUploadsDirCleaner,
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_PROMPT_PERMISSIONS)
},

@ -162,9 +162,10 @@ class InstalledAddonDetailsFragment : Fragment() {
runIfFragmentIsAttached {
this.addon = it
switch.isClickable = true
privateBrowsingSwitch.isVisible = it.isEnabled()
privateBrowsingSwitch.isChecked = it.isAllowedInPrivateBrowsing()
switch.setText(R.string.mozac_feature_addons_enabled)
privateBrowsingSwitch.isVisible = it.isEnabled()
privateBrowsingSwitch.isChecked =
it.incognito != Addon.Incognito.NOT_ALLOWED && it.isAllowedInPrivateBrowsing()
binding.settings.isVisible = shouldSettingsBeVisible()
enableButtons()
context?.let {
@ -203,8 +204,8 @@ class InstalledAddonDetailsFragment : Fragment() {
runIfFragmentIsAttached {
this.addon = it
switch.isClickable = true
privateBrowsingSwitch.isVisible = it.isEnabled()
switch.setText(R.string.mozac_feature_addons_disabled)
privateBrowsingSwitch.isVisible = it.isEnabled()
enableButtons()
context?.let {
showSnackBar(
@ -220,9 +221,8 @@ class InstalledAddonDetailsFragment : Fragment() {
onError = {
runIfFragmentIsAttached {
switch.isClickable = true
privateBrowsingSwitch.isClickable = true
enableButtons()
switch.setState(addon.isEnabled())
enableButtons()
context?.let {
showSnackBar(
binding.root,
@ -261,6 +261,45 @@ class InstalledAddonDetailsFragment : Fragment() {
}
}
@VisibleForTesting
internal fun bindAllowInPrivateBrowsingSwitch() {
val switch = providePrivateBrowsingSwitch()
switch.isVisible = addon.isEnabled()
if (addon.incognito == Addon.Incognito.NOT_ALLOWED) {
switch.isChecked = false
switch.isEnabled = false
switch.text = requireContext().getString(R.string.mozac_feature_addons_not_allowed_in_private_browsing)
return
}
switch.isChecked = addon.isAllowedInPrivateBrowsing()
switch.setOnCheckedChangeListener { v, isChecked ->
val addonManager = v.context.components.addonManager
switch.isClickable = false
disableButtons()
addonManager.setAddonAllowedInPrivateBrowsing(
addon,
isChecked,
onSuccess = {
runIfFragmentIsAttached {
this.addon = it
switch.isClickable = true
enableButtons()
}
},
onError = {
runIfFragmentIsAttached {
switch.isChecked = addon.isAllowedInPrivateBrowsing()
switch.isClickable = true
enableButtons()
}
},
)
}
}
private fun bindReportButton() {
binding.reportAddOn.setOnClickListener {
val shouldCreatePrivateSession = (activity as HomeActivity).browsingModeManager.mode.isPrivate
@ -325,35 +364,6 @@ class InstalledAddonDetailsFragment : Fragment() {
}
}
private fun bindAllowInPrivateBrowsingSwitch() {
val switch = binding.allowInPrivateBrowsingSwitch
switch.isChecked = addon.isAllowedInPrivateBrowsing()
switch.isVisible = addon.isEnabled()
switch.setOnCheckedChangeListener { v, isChecked ->
val addonManager = v.context.components.addonManager
switch.isClickable = false
disableButtons()
addonManager.setAddonAllowedInPrivateBrowsing(
addon,
isChecked,
onSuccess = {
runIfFragmentIsAttached {
this.addon = it
switch.isClickable = true
enableButtons()
}
},
onError = {
runIfFragmentIsAttached {
switch.isChecked = addon.isAllowedInPrivateBrowsing()
switch.isClickable = true
enableButtons()
}
},
)
}
}
private fun bindRemoveButton() {
binding.removeAddOn.setOnClickListener {
setAllInteractiveViewsClickable(binding, false)

@ -0,0 +1,37 @@
/* 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.bindings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChangedBy
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.helpers.AbstractBinding
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
/**
* Binding to update the [AppStore] based on changes to [BrowserState].
*/
class BrowserStoreBinding(
browserStore: BrowserStore,
private val appStore: AppStore,
) : AbstractBinding<BrowserState>(browserStore) {
override suspend fun onState(flow: Flow<BrowserState>) {
// Update the AppStore with the latest selected tab
flow.distinctUntilChangedBy { it.selectedTabId }
.collectLatest { state ->
state.selectedTab?.let { tab ->
// Ignore re-observations due to lifecycle events, or other pieces of state like
// [mode] may get overwritten
if (appStore.state.selectedTabId != tab.id) {
appStore.dispatch(AppAction.SelectedTabChanged(tab))
}
}
}
}
}

@ -92,7 +92,6 @@ import mozilla.components.feature.session.PictureInPictureFeature
import mozilla.components.feature.session.ScreenOrientationFeature
import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SwipeRefreshFeature
import mozilla.components.feature.session.behavior.EngineViewBrowserToolbarBehavior
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
import mozilla.components.feature.webauthn.WebAuthnFeature
import mozilla.components.lib.state.ext.consumeFlow
@ -112,6 +111,7 @@ import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.kotlin.getOrigin
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.locale.ActivityContextWrapper
import mozilla.components.ui.widgets.behavior.EngineViewClippingBehavior
import mozilla.components.ui.widgets.withCenterAlignedButtons
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.FeatureFlags
@ -138,6 +138,7 @@ import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor
import org.mozilla.fenix.crashes.CrashContentIntegration
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.databinding.FragmentBrowserBinding
import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.downloads.DynamicDownloadDialog
@ -169,7 +170,7 @@ import org.mozilla.fenix.utils.allowUndo
import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration
import java.lang.ref.WeakReference
import kotlin.coroutines.cancellation.CancellationException
import mozilla.components.feature.session.behavior.ToolbarPosition as MozacToolbarPosition
import mozilla.components.ui.widgets.behavior.ToolbarPosition as MozacToolbarPosition
/**
* Base fragment extended by [BrowserFragment].
@ -267,7 +268,11 @@ abstract class BaseBrowserFragment :
_binding = FragmentBrowserBinding.inflate(inflater, container, false)
val activity = activity as HomeActivity
activity.themeManager.applyStatusBarTheme(activity)
// ExternalAppBrowserActivity exclusively handles it's own theming unless in private mode.
if (activity !is ExternalAppBrowserActivity || activity.browsingModeManager.mode.isPrivate) {
activity.themeManager.applyStatusBarTheme(activity)
}
val originalContext = ActivityContextWrapper.getOriginalContext(activity)
binding.engineView.setActivityContext(originalContext)
@ -696,6 +701,7 @@ abstract class BaseBrowserFragment :
fragmentManager = parentFragmentManager,
identityCredentialColorsProvider = colorsProvider,
tabsUseCases = requireComponents.useCases.tabsUseCases,
fileUploadsDirCleaner = requireComponents.core.fileUploadsDirCleaner,
creditCardValidationDelegate = DefaultCreditCardValidationDelegate(
context.components.core.lazyAutofillStorage,
),
@ -1130,7 +1136,7 @@ abstract class BaseBrowserFragment :
MozacToolbarPosition.TOP
}
(getSwipeRefreshLayout().layoutParams as CoordinatorLayout.LayoutParams).behavior =
EngineViewBrowserToolbarBehavior(
EngineViewClippingBehavior(
context,
null,
getSwipeRefreshLayout(),
@ -1543,7 +1549,10 @@ abstract class BaseBrowserFragment :
activity?.exitImmersiveMode()
(view as? SwipeGestureLayout)?.isSwipeEnabled = true
(activity as? HomeActivity)?.let { activity ->
activity.themeManager.applyStatusBarTheme(activity)
// ExternalAppBrowserActivity exclusively handles it's own theming unless in private mode.
if (activity !is ExternalAppBrowserActivity || activity.browsingModeManager.mode.isPrivate) {
activity.themeManager.applyStatusBarTheme(activity)
}
}
if (webAppToolbarShouldBeVisible) {
browserToolbarView.view.isVisible = true

@ -230,7 +230,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
context,
R.drawable.mozac_ic_translate_24,
)!!,
contentDescription = "",
contentDescription = context.getString(R.string.browser_toolbar_translate),
contentDescriptionSelected = "",
visible = {
translationsAvailable || context.settings().enableTranslations

@ -22,7 +22,7 @@ import org.mozilla.fenix.databinding.TabPreviewBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import kotlin.math.max
import kotlin.math.min
class TabPreview @JvmOverloads constructor(
context: Context,
@ -72,7 +72,7 @@ class TabPreview @JvmOverloads constructor(
fun loadPreviewThumbnail(thumbnailId: String, isPrivate: Boolean) {
doOnNextLayout {
val previewThumbnail = binding.previewThumbnail
val thumbnailSize = max(previewThumbnail.height, previewThumbnail.width)
val thumbnailSize = min(previewThumbnail.height, previewThumbnail.width)
thumbnailLoader.loadIntoView(
previewThumbnail,
ImageLoadRequest(thumbnailId, thumbnailSize, isPrivate),

@ -43,6 +43,7 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.SyncAuth
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.maxActiveTime
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
@ -143,7 +144,7 @@ class BackgroundServices(
}
val syncedTabsStorage by lazyMonitored {
SyncedTabsStorage(accountManager, context.components.core.store, remoteTabsStorage.value)
SyncedTabsStorage(accountManager, context.components.core.store, remoteTabsStorage.value, maxActiveTime)
}
val syncedTabsAutocompleteProvider by lazyMonitored {
SyncedTabsAutocompleteProvider(syncedTabsStorage)

@ -207,7 +207,7 @@ class Components(private val context: Context) {
)
}
val fxSuggest by lazyMonitored { FxSuggest(context, analytics.crashReporter) }
val fxSuggest by lazyMonitored { FxSuggest(context) }
}
/**

@ -12,6 +12,10 @@ import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.browser.domains.autocomplete.BaseDomainAutocompleteProvider
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
import mozilla.components.browser.engine.gecko.GeckoEngine
@ -46,6 +50,8 @@ import mozilla.components.feature.media.MediaSessionFeature
import mozilla.components.feature.media.middleware.LastMediaAccessMiddleware
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
import mozilla.components.feature.prompts.PromptMiddleware
import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
import mozilla.components.feature.prompts.file.FileUploadsDirCleanerMiddleware
import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.feature.readerview.ReaderViewMiddleware
@ -56,6 +62,7 @@ import mozilla.components.feature.search.middleware.AdsTelemetryMiddleware
import mozilla.components.feature.search.middleware.SearchExtraParams
import mozilla.components.feature.search.middleware.SearchMiddleware
import mozilla.components.feature.search.region.RegionMiddleware
import mozilla.components.feature.search.telemetry.SerpTelemetryRepository
import mozilla.components.feature.search.telemetry.ads.AdsTelemetry
import mozilla.components.feature.search.telemetry.incontent.InContentTelemetry
import mozilla.components.feature.session.HistoryDelegate
@ -82,6 +89,7 @@ import mozilla.components.service.pocket.Profile
import mozilla.components.service.sync.autofill.AutofillCreditCardsAddressesStorage
import mozilla.components.service.sync.logins.SyncableLoginsStorage
import mozilla.components.support.base.worker.Frequency
import mozilla.components.support.ktx.android.content.res.readJSONObject
import mozilla.components.support.locale.LocaleManager
import org.mozilla.fenix.AppRequestInterceptor
import org.mozilla.fenix.BuildConfig
@ -189,6 +197,10 @@ class Core(
)
}
val fileUploadsDirCleaner: FileUploadsDirCleaner by lazyMonitored {
FileUploadsDirCleaner { context.cacheDir }
}
val geckoRuntime: GeckoRuntime by lazyMonitored {
GeckoProvider.getOrCreateRuntime(
context,
@ -286,6 +298,7 @@ class Core(
SessionPrioritizationMiddleware(),
SaveToPDFMiddleware(context),
FxSuggestFactsMiddleware(),
FileUploadsDirCleanerMiddleware(fileUploadsDirCleaner),
)
BrowserStore(
@ -309,11 +322,25 @@ class Core(
// Install the "icons" WebExtension to automatically load icons for every visited website.
icons.install(engine, this)
// Install the "ads" WebExtension to get the links in an partner page.
adsTelemetry.install(engine, this)
// Install the "cookies" WebExtension and tracks user interaction with SERPs.
searchTelemetry.install(engine, this)
CoroutineScope(Dispatchers.Main).launch {
val readJson = { context.assets.readJSONObject("search/search_telemetry_v2.json") }
val providerList = withContext(Dispatchers.IO) {
SerpTelemetryRepository(
rootStorageDirectory = context.filesDir,
readJson = readJson,
collectionName = COLLECTION_NAME,
serverUrl = if (context.settings().useProductionRemoteSettingsServer) {
REMOTE_PROD_ENDPOINT_URL
} else {
REMOTE_STAGE_ENDPOINT_URL
},
).updateProviderList()
}
// Install the "ads" WebExtension to get the links in an partner page.
adsTelemetry.install(engine, this@apply, providerList)
// Install the "cookies" WebExtension and tracks user interaction with SERPs.
searchTelemetry.install(engine, this@apply, providerList)
}
WebNotificationFeature(
context,
@ -598,5 +625,10 @@ class Core(
// Maximum number of suggestions returned from shortcut search engine.
const val METADATA_SHORTCUT_SUGGESTION_LIMIT = 20
// collection name to fetch from server for SERP telemetry
const val COLLECTION_NAME = "search-telemetry-v2"
internal const val REMOTE_PROD_ENDPOINT_URL = "https://firefox.settings.services.mozilla.com"
internal const val REMOTE_STAGE_ENDPOINT_URL = "https://firefox.settings.services.allizom.org"
}
}

@ -121,7 +121,7 @@ class FenixSnackbar private constructor(
* Suppressing ComplexCondition. Yes it's unfortunately complex but that's the nature
* of the snackbar handling by Android. It will be simpler once dynamic toolbar is always on.
*/
@Suppress("ComplexCondition")
@Suppress("ComplexCondition", "RestrictedApi")
fun make(
view: View,
duration: Int = LENGTH_LONG,

@ -5,7 +5,6 @@
package org.mozilla.fenix.components
import android.content.Context
import mozilla.components.concept.base.crash.CrashReporting
import mozilla.components.feature.fxsuggest.FxSuggestIngestionScheduler
import mozilla.components.feature.fxsuggest.FxSuggestStorage
import org.mozilla.fenix.perf.lazyMonitored
@ -14,12 +13,10 @@ import org.mozilla.fenix.perf.lazyMonitored
* Component group for Firefox Suggest.
*
* @param context The Android application context.
* @param crashReporter An optional [CrashReporting] instance for reporting unexpected caught
* exceptions.
*/
class FxSuggest(context: Context, crashReporter: CrashReporting? = null) {
class FxSuggest(context: Context) {
val storage by lazyMonitored {
FxSuggestStorage(context, crashReporter)
FxSuggestStorage(context)
}
val ingestionScheduler by lazyMonitored {

@ -8,8 +8,11 @@ import androidx.annotation.VisibleForTesting
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags.Companion.ALLOW_ADDITIONAL_HEADERS
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags.Companion.BYPASS_CACHE
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags.Companion.LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE
import mozilla.components.concept.engine.request.RequestInterceptor
import java.net.MalformedURLException
import java.net.URL
/**
* [RequestInterceptor] implementation for intercepting URL load requests to allow custom
@ -19,9 +22,10 @@ import mozilla.components.concept.engine.request.RequestInterceptor
*/
class UrlRequestInterceptor(private val isDeviceRamAboveThreshold: Boolean) : RequestInterceptor {
private val isGoogleSearchRequest by lazy {
Regex("^https://www\\.google\\.(?:.+)/search")
private val isGoogleRequest by lazy {
Regex("^https://www\\.google\\..+")
}
private val googleRequestPaths = setOf("/search", "/webhp", "/preferences")
@VisibleForTesting
internal fun getAdditionalHeaders(isDeviceRamAboveThreshold: Boolean): Map<String, String> {
@ -41,7 +45,15 @@ class UrlRequestInterceptor(private val isDeviceRamAboveThreshold: Boolean) : Re
uri: String,
isSubframeRequest: Boolean,
): Boolean {
return !isSubframeRequest && isGoogleSearchRequest.containsMatchIn(uri)
if (isSubframeRequest || !isGoogleRequest.containsMatchIn(uri)) {
return false
}
return try {
googleRequestPaths.contains(URL(uri).path)
} catch (e: MalformedURLException) {
false
}
}
override fun onLoadRequest(
@ -58,12 +70,24 @@ class UrlRequestInterceptor(private val isDeviceRamAboveThreshold: Boolean) : Re
return null
}
return RequestInterceptor.InterceptionResponse.Url(
url = uri,
flags = LoadUrlFlags.select(
// This is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1875668
// Remove this by implementing https://bugzilla.mozilla.org/show_bug.cgi?id=1883496
val loadUrlFlags = if (uri.endsWith("#ip=1", true)) {
LoadUrlFlags.select(
LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE,
ALLOW_ADDITIONAL_HEADERS,
BYPASS_CACHE,
)
} else {
LoadUrlFlags.select(
LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE,
ALLOW_ADDITIONAL_HEADERS,
),
)
}
return RequestInterceptor.InterceptionResponse.Url(
url = uri,
flags = loadUrlFlags,
additionalHeaders = getAdditionalHeaders(isDeviceRamAboveThreshold),
)
}

@ -4,6 +4,7 @@
package org.mozilla.fenix.components.appstate
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.crash.Crash.NativeCodeCrash
@ -128,6 +129,13 @@ sealed class AppAction : Action {
*/
data class RemoveRecentSyncedTab(val syncedTab: RecentSyncedTab) : AppAction()
/**
* Action indicating that the selected tab has been changed.
*
* @property tab The tab that has been selected.
*/
data class SelectedTabChanged(val tab: TabSessionState) : AppAction()
/**
* [Action]s related to interactions with the Messaging Framework.
*/

@ -38,6 +38,7 @@ import org.mozilla.fenix.wallpapers.WallpaperState
* @property expandedCollections A set containing the ids of the [TabCollection] that are expanded
* in the [HomeFragment].
* @property mode Whether the app is in private browsing mode.
* @property selectedTabId The currently selected tab ID. This should be bound to [BrowserStore].
* @property topSites The list of [TopSite] in the [HomeFragment].
* @property showCollectionPlaceholder If true, shows a placeholder when there are no collections.
* @property recentTabs The list of recent [RecentTab] in the [HomeFragment].
@ -64,6 +65,7 @@ data class AppState(
val collections: List<TabCollection> = emptyList(),
val expandedCollections: Set<Long> = emptySet(),
val mode: BrowsingMode = BrowsingMode.Normal,
val selectedTabId: String? = null,
val topSites: List<TopSite> = emptyList(),
val showCollectionPlaceholder: Boolean = false,
val recentTabs: List<RecentTab> = emptyList(),

@ -8,6 +8,7 @@ import androidx.annotation.VisibleForTesting
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
import mozilla.components.service.pocket.PocketStory.PocketSponsoredStory
import mozilla.components.service.pocket.ext.recordNewImpression
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.shopping.ShoppingStateReducer
import org.mozilla.fenix.ext.filterOutTab
@ -100,6 +101,10 @@ internal object AppStoreReducer {
else -> state.recentSyncedTabState
},
)
is AppAction.SelectedTabChanged -> state.copy(
selectedTabId = action.tab.id,
mode = BrowsingMode.fromBoolean(action.tab.content.private),
)
is AppAction.DisbandSearchGroupAction -> state.copy(
recentHistory = state.recentHistory.filterNot {
it is RecentHistoryGroup && it.title.equals(action.searchTerm, true)

@ -10,8 +10,10 @@ import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.Config
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.FirstSession
import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.ext.settings
@ -74,6 +76,8 @@ class FirstSessionPing(private val context: Context) {
Pings.firstSession.submit()
markAsTriggered()
}
} else {
Events.firstSessionPingCancelled.record(NoExtras())
}
}

@ -66,7 +66,6 @@ class GleanMetricsService(
private var initialized = false
private val activationPing = ActivationPing(context)
private val installationPing = FirstSessionPing(context)
override fun start() {
logger.debug("Enabling Glean.")
@ -87,7 +86,6 @@ class GleanMetricsService(
Glean.registerPings(Pings)
activationPing.checkAndSend()
installationPing.checkAndSend()
}
}

@ -339,7 +339,6 @@ internal class ReleaseMetricController(
Awesomebar.sponsoredSuggestionImpressed.record(
Awesomebar.SponsoredSuggestionImpressedExtra(
provider = "amp",
engagementAbandoned = engagementAbandoned,
),
)
}
@ -347,7 +346,6 @@ internal class ReleaseMetricController(
Awesomebar.nonSponsoredSuggestionImpressed.record(
Awesomebar.NonSponsoredSuggestionImpressedExtra(
provider = "wikipedia",
engagementAbandoned = engagementAbandoned,
),
)
}

@ -19,9 +19,9 @@ import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.browser.state.state.ExternalAppType
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.behavior.BrowserToolbarBehavior
import mozilla.components.browser.toolbar.display.DisplayToolbar
import mozilla.components.support.ktx.util.URLStringUtils
import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior
import org.mozilla.fenix.R
import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
import org.mozilla.fenix.customtabs.CustomTabToolbarIntegration
@ -32,7 +32,7 @@ import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.utils.ToolbarPopupWindow
import java.lang.ref.WeakReference
import mozilla.components.browser.toolbar.behavior.ToolbarPosition as MozacToolbarPosition
import mozilla.components.ui.widgets.behavior.ViewPosition as MozacToolbarPosition
@SuppressWarnings("LargeClass")
class BrowserToolbarView(
@ -53,8 +53,7 @@ class BrowserToolbarView(
private val layout = LayoutInflater.from(context)
.inflate(toolbarLayout, container, true)
@VisibleForTesting
internal var view: BrowserToolbar = layout
var view: BrowserToolbar = layout
.findViewById(R.id.toolbar)
val toolbarIntegration: ToolbarIntegration
@ -202,7 +201,7 @@ class BrowserToolbarView(
}
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? BrowserToolbarBehavior)?.forceExpand(view)
(behavior as? EngineViewScrollingBehavior)?.forceExpand(view)
}
}
@ -213,7 +212,7 @@ class BrowserToolbarView(
}
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? BrowserToolbarBehavior)?.forceCollapse(view)
(behavior as? EngineViewScrollingBehavior)?.forceCollapse(view)
}
}
@ -266,7 +265,7 @@ class BrowserToolbarView(
@VisibleForTesting
internal fun setDynamicToolbarBehavior(toolbarPosition: MozacToolbarPosition) {
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
behavior = BrowserToolbarBehavior(view.context, null, toolbarPosition)
behavior = EngineViewScrollingBehavior(view.context, null, toolbarPosition)
}
}

@ -6,11 +6,15 @@ package org.mozilla.fenix.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -24,8 +28,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.theme.FirefoxTheme
@ -62,14 +69,51 @@ private fun Menu(
.background(color = FirefoxTheme.colors.layer2)
.then(modifier),
) {
val hasCheckedItems = menuItems.any { it.isChecked }
for (item in menuItems) {
val checkmarkModifier = if (hasCheckedItems) {
Modifier.selectable(
selected = item.isChecked,
role = Role.Button,
onClick = {
onDismissRequest()
item.onClick()
},
)
} else {
Modifier
}
DropdownMenuItem(
modifier = Modifier.testTag(item.testTag),
modifier = Modifier
.testTag(item.testTag)
.align(alignment = Alignment.CenterHorizontally)
.then(checkmarkModifier),
onClick = {
onDismissRequest()
item.onClick()
},
) {
if (hasCheckedItems) {
if (item.isChecked) {
Icon(
painter = painterResource(id = R.drawable.mozac_ic_checkmark_24),
modifier = Modifier
.size(24.dp),
contentDescription = null,
tint = FirefoxTheme.colors.iconPrimary,
)
} else {
Spacer(
modifier = Modifier
.size(24.dp),
)
}
Spacer(modifier = Modifier.width(12.dp))
}
Text(
text = item.title,
color = item.color ?: FirefoxTheme.colors.textPrimary,
@ -117,12 +161,14 @@ fun ContextualMenu(
*
* @property title Text the item should display.
* @property color Color used to display the text.
* @property isChecked Whether a checkmark should appear next to the text.
* @property testTag Tag used to identify the item in automated tests.
* @property onClick Callback to be called when the item is clicked.
*/
data class MenuItem(
val title: String,
val color: Color? = null,
val isChecked: Boolean = false,
val testTag: String = "",
val onClick: () -> Unit,
)

@ -208,7 +208,7 @@ private fun MessageCardPreview() {
) {
MessageCard(
messageText = stringResource(id = R.string.default_browser_experiment_card_text),
titleText = stringResource(id = R.string.bookmark_empty_title_error),
titleText = stringResource(id = R.string.default_browser_experiment_card_title),
onClick = {},
onCloseButtonClick = {},
)
@ -245,7 +245,7 @@ private fun MessageCardWithButtonLabelPreview() {
) {
MessageCard(
messageText = stringResource(id = R.string.default_browser_experiment_card_text),
titleText = stringResource(id = R.string.bookmark_empty_title_error),
titleText = stringResource(id = R.string.default_browser_experiment_card_title),
buttonText = stringResource(id = R.string.preferences_set_as_default_browser),
onClick = {},
onCloseButtonClick = {},

@ -0,0 +1,136 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.ext.toLocaleString
import org.mozilla.fenix.tabstray.TabsTrayTestTag
import org.mozilla.fenix.theme.FirefoxTheme
private const val MAX_VISIBLE_TABS = 99
private const val SO_MANY_TABS_OPEN = ""
private val NORMAL_TABS_BOTTOM_PADDING = 0.5.dp
private const val ONE_DIGIT_SIZE_RATIO = 0.5f
private const val TWO_DIGITS_SIZE_RATIO = 0.4f
private const val MIN_SINGLE_DIGIT = 0
private const val MAX_SINGLE_DIGIT = 9
private const val TWO_DIGIT_THRESHOLD = 10
private const val TAB_TEXT_BOTTOM_PADDING_RATIO = 4
/**
* UI for displaying the number of opened tabs.
*
* This composable uses LocalContentColor, provided by CompositionLocalProvider,
* to set the color of its icons and text.
*
* @param tabCount the number to be displayed inside the counter.
*/
@Composable
fun TabCounter(tabCount: Int) {
val formattedTabCount = tabCount.toLocaleString()
val normalTabCountText: String
val tabCountTextRatio: Float
val needsBottomPaddingForInfiniteTabs: Boolean
when (tabCount) {
in MIN_SINGLE_DIGIT..MAX_SINGLE_DIGIT -> {
normalTabCountText = formattedTabCount
tabCountTextRatio = ONE_DIGIT_SIZE_RATIO
needsBottomPaddingForInfiniteTabs = false
}
in TWO_DIGIT_THRESHOLD..MAX_VISIBLE_TABS -> {
normalTabCountText = formattedTabCount
tabCountTextRatio = TWO_DIGITS_SIZE_RATIO
needsBottomPaddingForInfiniteTabs = false
}
else -> {
normalTabCountText = SO_MANY_TABS_OPEN
tabCountTextRatio = ONE_DIGIT_SIZE_RATIO
needsBottomPaddingForInfiniteTabs = true
}
}
val normalTabsContentDescription = if (tabCount == 1) {
stringResource(id = R.string.mozac_tab_counter_open_tab_tray_single)
} else {
stringResource(
id = R.string.mozac_tab_counter_open_tab_tray_plural,
formattedTabCount,
)
}
val counterBoxWidthDp =
dimensionResource(id = mozilla.components.ui.tabcounter.R.dimen.mozac_tab_counter_box_width_height)
val counterBoxWidthPx = LocalDensity.current.run { counterBoxWidthDp.roundToPx() }
val counterTabsTextSize = (tabCountTextRatio * counterBoxWidthPx).toInt()
val normalTabsTextModifier = if (needsBottomPaddingForInfiniteTabs) {
val bottomPadding = with(LocalDensity.current) { counterTabsTextSize.toDp() / TAB_TEXT_BOTTOM_PADDING_RATIO }
Modifier.padding(bottom = bottomPadding)
} else {
Modifier.padding(bottom = NORMAL_TABS_BOTTOM_PADDING)
}
Box(
modifier = Modifier
.semantics(mergeDescendants = true) {
testTag = TabsTrayTestTag.normalTabsCounter
},
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(
id = mozilla.components.ui.tabcounter.R.drawable.mozac_ui_tabcounter_box,
),
contentDescription = normalTabsContentDescription,
)
Text(
text = normalTabCountText,
modifier = normalTabsTextModifier,
color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
fontSize = with(LocalDensity.current) { counterTabsTextSize.toDp().toSp() },
fontWeight = FontWeight.W700,
textAlign = TextAlign.Center,
)
}
}
@LightDarkPreview
@Preview(locale = "ar")
@Composable
private fun TabCounterPreview() {
FirefoxTheme {
Box(
modifier = Modifier.background(color = FirefoxTheme.colors.layer1),
) {
TabCounter(tabCount = 55)
}
}
}

@ -47,7 +47,10 @@ fun FloatingActionButton(
modifier: Modifier = Modifier,
contentDescription: String? = null,
label: String? = null,
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(defaultElevation = 5.dp),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(
defaultElevation = 5.dp,
pressedElevation = 5.dp,
),
onClick: () -> Unit,
) {
FloatingActionButton(

@ -0,0 +1,15 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.compose.ext
import androidx.compose.ui.text.intl.Locale
import java.text.NumberFormat
import java.util.Locale as JavaLocale
/**
* Returns a localized string representation of the value.
*/
fun Int.toLocaleString(): String =
NumberFormat.getNumberInstance(JavaLocale(Locale.current.language)).format(this)

@ -5,7 +5,6 @@
package org.mozilla.fenix.customtabs
import android.content.Context
import android.content.Intent
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
@ -28,7 +27,6 @@ import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BaseBrowserFragment
import org.mozilla.fenix.browser.CustomTabContextMenuCandidate
@ -80,21 +78,7 @@ class ExternalAppBrowserFragment : BaseBrowserFragment() {
)
windowFeature.set(
feature = CustomTabWindowFeature(
activity,
components.core.store,
customTabSessionId,
) { uri ->
val intent =
Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0)
if (intent.action == Intent.ACTION_VIEW) {
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.component = null
intent.selector = null
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
activity.startActivity(intent)
},
feature = CustomTabWindowFeature(activity, components.core.store, customTabSessionId),
owner = this,
view = view,
)

@ -43,6 +43,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.compose.Divider
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.compose.ext.toLocaleString
import org.mozilla.fenix.debugsettings.ui.DebugDrawer
import org.mozilla.fenix.ext.maxActiveTime
import org.mozilla.fenix.tabstray.ext.isNormalTabInactive
@ -152,19 +153,19 @@ private fun TabCounter(
TabCountRow(
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_normal),
count = activeTabCount.toString(),
count = activeTabCount,
)
if (inactiveTabsEnabled) {
TabCountRow(
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_inactive),
count = inactiveTabCount.toString(),
count = inactiveTabCount,
)
}
TabCountRow(
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_private),
count = privateTabCount.toString(),
count = privateTabCount,
)
Spacer(modifier = Modifier.height(8.dp))
@ -175,7 +176,7 @@ private fun TabCounter(
TabCountRow(
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_total),
count = totalTabCount.toString(),
count = totalTabCount,
)
}
}
@ -183,7 +184,7 @@ private fun TabCounter(
@Composable
private fun TabCountRow(
tabType: String,
count: String,
count: Int,
) {
Row(
modifier = Modifier
@ -198,14 +199,14 @@ private fun TabCountRow(
)
Text(
text = count,
text = count.toLocaleString(),
color = FirefoxTheme.colors.textSecondary,
style = FirefoxTheme.typography.headline6,
)
}
}
private const val DEFAULT_TABS_TO_ADD = "1"
private const val DEFAULT_TABS_TO_ADD = 1
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@ -213,7 +214,7 @@ private fun TabCreationTool(
inactiveTabsEnabled: Boolean,
onCreateTabsClick: ((quantity: Int, isInactive: Boolean, isPrivate: Boolean) -> Unit),
) {
var tabQuantityToCreate by rememberSaveable { mutableStateOf(DEFAULT_TABS_TO_ADD) }
var tabQuantityToCreate by rememberSaveable { mutableStateOf(DEFAULT_TABS_TO_ADD.toLocaleString()) }
var hasError by rememberSaveable { mutableStateOf(false) }
val keyboardController = LocalSoftwareKeyboardController.current

@ -21,7 +21,7 @@ suspend fun Client.bitmapForUrl(url: String): Bitmap? = withContext(Dispatchers.
// Code below will cache it in Gecko's cache, which ensures that as long as we've fetched it once,
// we will be able to display this avatar as long as the cache isn't purged (e.g. via 'clear user data').
val body = try {
fetch(Request(url, useCaches = true)).body
fetch(Request(url, useCaches = true, conservative = true)).body
} catch (e: IOException) {
return@withContext null
}

@ -68,7 +68,7 @@ fun NavController.navigateWithBreadcrumb(
*/
@SuppressLint("RestrictedApi")
fun NavController.hasTopDestination(fragmentClassName: String): Boolean {
return this.backQueue.lastOrNull()?.destination?.displayName?.contains(
return this.currentBackStackEntry?.destination?.displayName?.contains(
fragmentClassName,
true,
) == true

@ -33,6 +33,7 @@ fun Toolbar.setToolbarColors(@ColorInt foreground: Int, @ColorInt background: In
}
}
@Suppress("RestrictedApi")
private fun themeActionMenuView(item: ActionMenuView, colorFilter: ColorFilter?) {
item.forEach { innerChild ->
if (innerChild is ActionMenuItemView) {

@ -30,6 +30,23 @@ fun View.increaseTapArea(@Dimension(unit = DP) extraDps: Int) {
}
}
/**
* Increase tap area only vertically.
*
* @param extraDps the extra dps that's wanted to be added on top and bottom of the view
*/
fun View.increaseTapAreaVertically(@Dimension(unit = DP) extraDps: Int) {
val dips = extraDps.dpToPx(resources.displayMetrics)
val parent = this.parent as View
parent.post {
val touchArea = Rect()
getHitRect(touchArea)
touchArea.top -= dips
touchArea.bottom += dips
parent.touchDelegate = TouchDelegate(touchArea, this)
}
}
fun View.removeTouchDelegate() {
val parent = this.parent as View
parent.post {

@ -248,7 +248,11 @@ class HomeFragment : Fragment() {
val components = requireComponents
val currentWallpaperName = requireContext().settings().currentWallpaperName
applyWallpaper(wallpaperName = currentWallpaperName, orientationChange = false)
applyWallpaper(
wallpaperName = currentWallpaperName,
orientationChange = false,
orientation = requireContext().resources.configuration.orientation,
)
components.appStore.dispatch(AppAction.ModeChange(browsingModeManager.mode))
@ -454,7 +458,11 @@ class HomeFragment : Fragment() {
homeMenuView?.dismissMenu()
val currentWallpaperName = requireContext().settings().currentWallpaperName
applyWallpaper(wallpaperName = currentWallpaperName, orientationChange = true)
applyWallpaper(
wallpaperName = currentWallpaperName,
orientationChange = true,
orientation = newConfig.orientation,
)
}
/**
@ -1000,7 +1008,7 @@ class HomeFragment : Fragment() {
internal fun shouldEnableWallpaper() =
(activity as? HomeActivity)?.themeManager?.currentTheme?.isPrivate?.not() ?: false
private fun applyWallpaper(wallpaperName: String, orientationChange: Boolean) {
private fun applyWallpaper(wallpaperName: String, orientationChange: Boolean, orientation: Int) {
when {
!shouldEnableWallpaper() ||
(wallpaperName == lastAppliedWallpaperName && !orientationChange) -> return
@ -1013,8 +1021,7 @@ class HomeFragment : Fragment() {
// loadBitmap does file lookups based on name, so we don't need a fully
// qualified type to load the image
val wallpaper = Wallpaper.Default.copy(name = wallpaperName)
val wallpaperImage =
context?.let { requireComponents.useCases.wallpaperUseCases.loadBitmap(it, wallpaper) }
val wallpaperImage = requireComponents.useCases.wallpaperUseCases.loadBitmap(wallpaper, orientation)
wallpaperImage?.let {
it.scaleToBottomOfView(binding.wallpaperImageView)
binding.wallpaperImageView.isVisible = true
@ -1060,7 +1067,11 @@ class HomeFragment : Fragment() {
.distinctUntilChanged()
.collect {
if (it.name != lastAppliedWallpaperName) {
applyWallpaper(wallpaperName = it.name, orientationChange = false)
applyWallpaper(
wallpaperName = it.name,
orientationChange = false,
orientation = requireContext().resources.configuration.orientation,
)
}
}
}

@ -20,6 +20,7 @@ import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.GleanMetrics.UnifiedSearch
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentHomeBinding
import org.mozilla.fenix.ext.increaseTapAreaVertically
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
@ -51,6 +52,8 @@ class SearchSelectorBinding(
orientation = orientation,
)
}
increaseTapAreaVertically(SEARCH_SELECTOR_INCREASE_HEIGHT_DPS)
}
}
@ -76,4 +79,8 @@ class SearchSelectorBinding(
binding.searchSelectorButton.setIcon(icon, name)
}
}
companion object {
const val SEARCH_SELECTOR_INCREASE_HEIGHT_DPS = 10
}
}

@ -97,6 +97,7 @@ class TopSitesPagerAdapter(
}
override fun areContentsTheSame(oldItem: List<TopSite>, newItem: List<TopSite>): Boolean {
@Suppress("DiffUtilEquals")
return newItem.zip(oldItem).all { (new, old) -> new == old }
}

@ -1,64 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.onboarding
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.DialogFragment
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.view.NotificationPermissionDialogScreen
import org.mozilla.fenix.theme.FirefoxTheme
/**
* Dialog displaying notification pre-permission prompt.
*/
class HomeNotificationPermissionDialogFragment : DialogFragment() {
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.HomeOnboardingDialogStyle)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
override fun onDestroy() {
super.onDestroy()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View = ComposeView(requireContext()).apply {
setContent {
FirefoxTheme {
NotificationPermissionDialogScreen(
onDismiss = ::onDismiss,
grantNotificationPermission = {
ensureMarketingChannelExists(context.applicationContext)
requireComponents.notificationsDelegate.requestNotificationPermission()
onDismiss()
},
)
}
}
}
private fun onDismiss() {
dismiss()
context?.settings()?.isNotificationPrePermissionShown = true
}
}

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

Loading…
Cancel
Save