You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAboutRobot.kt

356 lines
19 KiB
Kotlin

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/* 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/. */
@file:Suppress("TooManyFunctions")
package org.mozilla.fenix.ui.robots
import android.os.Build
import android.util.Log
import android.widget.TextView
import androidx.core.content.pm.PackageInfoCompat
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import mozilla.components.support.utils.ext.getPackageInfoCompat
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
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.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.settings.SupportUtils
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.ChronoField
import java.util.Calendar
import java.util.Date
/**
* Implementation of Robot Pattern for the settings search sub menu.
*/
class SettingsSubMenuAboutRobot {
fun verifyAboutFirefoxPreviewInfo() {
verifyVersionNumber()
verifyProductCompany()
verifyCurrentTimestamp()
verifyTheLinksList()
}
fun verifyVersionNumber() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
val buildNVersion = "${packageInfo.versionName} (Build #$versionCode)\n"
val geckoVersion =
org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION + "-" + org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
val asVersion = mozilla.components.Build.applicationServicesVersion
Log.i(TAG, "verifyVersionNumber: Trying to verify that the about section contains build version: $buildNVersion")
onView(withId(R.id.about_text)).check(matches(withText(containsString(buildNVersion))))
Log.i(TAG, "verifyVersionNumber: Verified that the about section contains build version: $buildNVersion")
Log.i(TAG, "verifyVersionNumber: Trying to verify that the about section contains gecko version: $geckoVersion")
onView(withId(R.id.about_text)).check(matches(withText(containsString(geckoVersion))))
Log.i(TAG, "verifyVersionNumber: Verified that the about section contains gecko version: $geckoVersion")
Log.i(TAG, "verifyVersionNumber: Trying to verify that the about section contains android services version: $asVersion")
onView(withId(R.id.about_text)).check(matches(withText(containsString(asVersion))))
Log.i(TAG, "verifyVersionNumber: Verified that the about section contains android services version: $asVersion")
}
fun verifyProductCompany() {
Log.i(TAG, "verifyVersionNumber: Trying to verify that the about section contains the company that produced the app info: ${"$appName is produced by Mozilla."}")
onView(withId(R.id.about_content))
.check(matches(withText(containsString("$appName is produced by Mozilla."))))
Log.i(TAG, "verifyVersionNumber: Verified that the about section contains the company that produced the app info: ${"$appName is produced by Mozilla."}")
}
fun verifyCurrentTimestamp() {
Log.i(TAG, "verifyVersionNumber: Trying to verify that the about section contains \"debug build\"")
onView(withId(R.id.build_date))
// Currently UI tests run against debug builds, which display a hard-coded string 'debug build'
// instead of the date. See https://github.com/mozilla-mobile/fenix/pull/10812#issuecomment-633746833
.check(matches(withText(containsString("debug build"))))
// This assertion should be valid for non-debug build types.
// .check(BuildDateAssertion.isDisplayedDateAccurate())
Log.i(TAG, "verifyVersionNumber: Verified that the about section contains \"debug build\"")
}
fun verifyAboutToolbar() {
Log.i(TAG, "verifyAboutToolbar: Trying to verify that the \"About $appName\" toolbar title is visible")
onView(
allOf(
withId(R.id.navigationToolbar),
hasDescendant(withText("About $appName")),
),
).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyAboutToolbar: Verified that the \"About $appName\" toolbar title is visible")
}
fun verifyWhatIsNewInFirefoxLink() {
Log.i(TAG, "verifyWhatIsNewInFirefoxLink: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyWhatIsNewInFirefoxLink: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
val firefox = TestHelper.appContext.getString(R.string.firefox)
Log.i(TAG, "verifyAboutToolbar: Trying to verify that the \"Whats new in $firefox\" link is visible")
onView(withText("Whats new in $firefox")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyAboutToolbar: Verified that the \"Whats new in $firefox\" link is visible")
Log.i(TAG, "verifyAboutToolbar: Trying to click the \"Whats new in $firefox\" link")
onView(withText("Whats new in $firefox")).perform(click())
Log.i(TAG, "verifyAboutToolbar: Clicked the \"Whats new in $firefox\" link")
}
fun verifySupport() {
Log.i(TAG, "verifySupport: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifySupport: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
Log.i(TAG, "verifySupport: Trying to verify that the \"Support\" link is visible")
onView(withText("Support")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifySupport: Verified that the \"Support\" link is visible")
Log.i(TAG, "verifySupport: Trying to click the \"Support\" link")
onView(withText("Support")).perform(click())
Log.i(TAG, "verifySupport: Clicked the \"Support\" link")
TestHelper.verifyUrl(
"support.mozilla.org",
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view,
)
}
fun verifyCrashesLink() {
navigationToolbar {
}.openThreeDotMenu {
}.openSettings {
}.openAboutFirefoxPreview {}
Log.i(TAG, "verifyCrashesLink: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyCrashesLink: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
Log.i(TAG, "verifyCrashesLink: Trying to verify that the \"Crashes\" link is visible")
onView(withText("Crashes")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyCrashesLink: Verified that the \"Crashes\" link is visible")
Log.i(TAG, "verifyCrashesLink: Trying to click the \"Crashes\" link")
onView(withText("Crashes")).perform(click())
Log.i(TAG, "verifyCrashesLink: Clicked the \"Crashes\" link")
assertUIObjectExists(itemContainingText("No crash reports have been submitted."))
for (i in 1..3) {
Log.i(TAG, "verifyCrashesLink: Trying to perform press back action")
Espresso.pressBack()
Log.i(TAG, "verifyCrashesLink: Performed press back action")
}
}
fun verifyPrivacyNoticeLink() {
Log.i(TAG, "verifyPrivacyNoticeLink: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyPrivacyNoticeLink: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
Log.i(TAG, "verifyPrivacyNoticeLink: Trying to verify that the \"Privacy notice\" link is visible")
onView(withText("Privacy notice")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyPrivacyNoticeLink: Verified that the \"Privacy notice\" link is visible")
Log.i(TAG, "verifyPrivacyNoticeLink: Trying to click the \"Privacy notice\" link")
onView(withText("Privacy notice")).perform(click())
Log.i(TAG, "verifyPrivacyNoticeLink: Clicked the \"Privacy notice\" link")
TestHelper.verifyUrl(
"/privacy/firefox",
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view,
)
}
fun verifyKnowYourRightsLink() {
Log.i(TAG, "verifyKnowYourRightsLink: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyKnowYourRightsLink: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
Log.i(TAG, "verifyKnowYourRightsLink: Trying to verify that the \"Know your rights\" link is visible")
onView(withText("Know your rights")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyKnowYourRightsLink: Verified that the \"Know your rights\" link is visible")
Log.i(TAG, "verifyKnowYourRightsLink: Trying to click the \"Know your rights\" link")
onView(withText("Know your rights")).perform(click())
Log.i(TAG, "verifyKnowYourRightsLink: Clicked the \"Know your rights\" link")
TestHelper.verifyUrl(
SupportUtils.SumoTopic.YOUR_RIGHTS.topicStr,
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view,
)
}
fun verifyLicensingInformationLink() {
Log.i(TAG, "verifyLicensingInformationLink: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyLicensingInformationLink: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
Log.i(TAG, "verifyLicensingInformationLink: Trying to verify that the \"Licensing information\" link is visible")
onView(withText("Licensing information")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyLicensingInformationLink: Verified that the \"Licensing information\" link is visible")
Log.i(TAG, "verifyLicensingInformationLink: Trying to click the \"Licensing information\" link")
onView(withText("Licensing information")).perform(click())
Log.i(TAG, "verifyLicensingInformationLink: Clicked the \"Licensing information\" link")
TestHelper.verifyUrl(
"about:license",
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view,
)
}
fun verifyLibrariesUsedLink() {
Log.i(TAG, "verifyLibrariesUsedLink: Trying to perform ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
Log.i(TAG, "verifyLibrariesUsedLink: Performed ${LISTS_MAXSWIPES}x a scroll action to the end of the about list")
Log.i(TAG, "verifyLibrariesUsedLink: Trying to verify that the \"Libraries that we use\" link is visible")
onView(withText("Libraries that we use")).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
Log.i(TAG, "verifyLibrariesUsedLink: Verified that the \"Libraries that we use\" link is visible")
Log.i(TAG, "verifyLibrariesUsedLink: Trying to click the \"Libraries that we use\" link")
onView(withText("Libraries that we use")).perform(click())
Log.i(TAG, "verifyLibrariesUsedLink: Clicked the \"Libraries that we use\" link")
Log.i(TAG, "verifyLibrariesUsedLink: Trying to verify that the toolbar has title: \"$appName | OSS Libraries\"")
onView(withId(R.id.navigationToolbar)).check(matches(hasDescendant(withText(containsString("$appName | OSS Libraries")))))
Log.i(TAG, "verifyLibrariesUsedLink: Verified that the toolbar has title: \"$appName | OSS Libraries\"")
Log.i(TAG, "verifyLibrariesUsedLink: Trying to perform press back action")
Espresso.pressBack()
Log.i(TAG, "verifyLibrariesUsedLink: Performed press back action")
}
fun verifyTheLinksList() {
verifyAboutToolbar()
verifyWhatIsNewInFirefoxLink()
navigateBackToAboutPage()
verifySupport()
verifyCrashesLink()
navigateBackToAboutPage()
verifyPrivacyNoticeLink()
navigateBackToAboutPage()
verifyKnowYourRightsLink()
navigateBackToAboutPage()
verifyLicensingInformationLink()
navigateBackToAboutPage()
verifyLibrariesUsedLink()
}
class Transition {
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
Log.i(TAG, "goBack: Trying to click the navigate up button")
goBackButton().perform(click())
Log.i(TAG, "goBack: Clicked the navigate up button")
SettingsRobot().interact()
return SettingsRobot.Transition()
}
}
}
private fun navigateBackToAboutPage() {
navigationToolbar {
}.openThreeDotMenu {
}.openSettings {
}.openAboutFirefoxPreview {
}
}
private val aboutMenuList = UiScrollable(UiSelector().resourceId("$packageName:id/about_layout"))
private fun goBackButton() =
onView(withContentDescription("Navigate up"))
class BuildDateAssertion {
// When the app is built on firebase, there are times where the BuildDate is off by a few seconds or a few minutes.
// To compensate for that slight discrepancy, this assertion was added to see if the Build Date shown
// is within a reasonable amount of time from when the app was built.
companion object {
// this pattern represents the following date format: "Monday 12/30 @ 6:49 PM"
private const val DATE_PATTERN = "EEEE M/d @ h:m a"
//
private const val NUM_OF_HOURS = 1
fun isDisplayedDateAccurate(): ViewAssertion {
return ViewAssertion { view, noViewFoundException ->
if (noViewFoundException != null) throw noViewFoundException
val textFromView = (view as TextView).text
?: throw AssertionError("This view is not of type TextView")
verifyDateIsWithinRange(textFromView.toString(), NUM_OF_HOURS)
}
}
private fun verifyDateIsWithinRange(dateText: String, hours: Int) {
// This assertion checks whether has defined a range of tim
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
val simpleDateFormat = SimpleDateFormat(DATE_PATTERN)
val date = simpleDateFormat.parse(dateText)
if (date == null || !date.isWithinRangeOf(hours)) {
throw AssertionError("The build date is not within Range.")
}
} else {
val textviewDate = getLocalDateTimeFromString(dateText)
val buildConfigDate = getLocalDateTimeFromString(BuildConfig.BUILD_DATE)
if (!buildConfigDate.isEqual(textviewDate) &&
!textviewDate.isWithinRangeOf(hours, buildConfigDate)
) {
throw AssertionError("$textviewDate is not equal to the date within the build config: $buildConfigDate, and are not within a reasonable amount of time from each other.")
}
}
}
private fun Date.isWithinRangeOf(hours: Int): Boolean {
// To determine the date range, the maxDate is retrieved by adding the variable hours to the calendar.
// Since the calendar will represent the maxDate at this time, to retrieve the minDate the variable hours is multipled by negative 2 and added to the calendar
// This will result in the maxDate being equal to the original Date + hours, and minDate being equal to original Date - hours
val calendar = Calendar.getInstance()
val currentYear = calendar.get(Calendar.YEAR)
calendar.time = this
calendar.set(Calendar.YEAR, currentYear)
val updatedDate = calendar.time
calendar.add(Calendar.HOUR_OF_DAY, hours)
val maxDate = calendar.time
calendar.add(
Calendar.HOUR_OF_DAY,
hours * -2,
) // Gets the minDate by subtracting from maxDate
val minDate = calendar.time
return updatedDate.after(minDate) && updatedDate.before(maxDate)
}
private fun LocalDateTime.isWithinRangeOf(
hours: Int,
baselineDate: LocalDateTime,
): Boolean {
val upperBound = baselineDate.plusHours(hours.toLong())
val lowerBound = baselineDate.minusHours(hours.toLong())
val currentDate = this
return currentDate.isAfter(lowerBound) && currentDate.isBefore(upperBound)
}
private fun getLocalDateTimeFromString(buildDate: String): LocalDateTime {
val dateFormatter = DateTimeFormatterBuilder().appendPattern(DATE_PATTERN)
.parseDefaulting(ChronoField.YEAR, LocalDateTime.now().year.toLong())
.toFormatter()
return LocalDateTime.parse(buildDate, dateFormatter)
}
}
}