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

313 lines
12 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.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
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.UiDevice
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.containsString
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.isVisibleForUser
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 verifyAboutFirefoxPreview() = assertFirefoxPreviewPage()
class Transition {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
goBackButton().perform(click())
SettingsRobot().interact()
return SettingsRobot.Transition()
}
}
}
private fun assertFirefoxPreviewPage() {
assertVersionNumber()
assertProductCompany()
assertCurrentTimestamp()
verifyListElements()
}
private fun navigateBackToAboutPage(itemToInteract: () -> Unit) {
browserScreen {
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openAboutFirefoxPreview {
itemToInteract()
}
}
private fun verifyListElements() {
assertWhatIsNewInFirefoxPreview()
navigateBackToAboutPage(::assertSupport)
navigateBackToAboutPage(::assertPrivacyNotice)
navigateBackToAboutPage(::assertKnowYourRights)
navigateBackToAboutPage(::assertLicensingInformation)
navigateBackToAboutPage(::assertLibrariesUsed)
}
private fun assertVersionNumber() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
val buildNVersion = "${packageInfo.versionName} (Build #$versionCode)\n"
val componentsVersion =
"${mozilla.components.Build.version}, ${mozilla.components.Build.gitHash}"
val geckoVersion =
org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION + "-" + org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
val asVersion = mozilla.components.Build.applicationServicesVersion
onView(withId(R.id.about_text))
.check(matches(withText(containsString(buildNVersion))))
.check(matches(withText(containsString(componentsVersion))))
.check(matches(withText(containsString(geckoVersion))))
.check(matches(withText(containsString(asVersion))))
}
private fun assertProductCompany() {
onView(withId(R.id.about_content))
.check(matches(withText(containsString("$appName is produced by Mozilla."))))
}
private fun assertCurrentTimestamp() {
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())
}
private fun assertWhatIsNewInFirefoxPreview() {
if (!onView(withText("Whats new in $appName")).isVisibleForUser()) {
onView(withId(R.id.about_layout)).perform(ViewActions.swipeUp())
}
onView(withText("Whats new in $appName"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
// Commenting out since the Text to verify in the web site seems to be different now
/*
TestHelper.verifyUrl(
SupportUtils.SumoTopic.WHATS_NEW.topicStr,
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view
)*/
Espresso.pressBack()
}
private fun assertSupport() {
if (!onView(withText("Support")).isVisibleForUser()) {
onView(withId(R.id.about_layout)).perform(ViewActions.swipeUp())
}
onView(withText("Support"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
TestHelper.verifyUrl(
"support.mozilla.org",
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view
)
Espresso.pressBack()
}
private fun assertPrivacyNotice() {
if (!onView(withText("Privacy notice")).isVisibleForUser()) {
onView(withId(R.id.about_layout)).perform(ViewActions.swipeUp())
}
onView(withText("Privacy notice"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
TestHelper.verifyUrl(
"/privacy/firefox",
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view
)
Espresso.pressBack()
}
private fun assertKnowYourRights() {
if (!onView(withText("Know your rights")).isVisibleForUser()) {
onView(withId(R.id.about_layout)).perform(ViewActions.swipeUp())
}
onView(withText("Know your rights"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
TestHelper.verifyUrl(
SupportUtils.SumoTopic.YOUR_RIGHTS.topicStr,
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view
)
Espresso.pressBack()
}
private fun assertLicensingInformation() {
if (!onView(withText("Licensing information")).isVisibleForUser()) {
onView(withId(R.id.about_layout)).perform(ViewActions.swipeUp())
}
onView(withText("Licensing information"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
TestHelper.verifyUrl(
"about:license",
"org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
R.id.mozac_browser_toolbar_url_view
)
Espresso.pressBack()
}
private fun assertLibrariesUsed() {
if (!onView(withText("Libraries that we use")).isVisibleForUser()) {
onView(withId(R.id.about_layout)).perform(ViewActions.swipeUp())
}
onView(withText("Libraries that we use"))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
.perform(click())
onView(withId(R.id.navigationToolbar)).check(matches(hasDescendant(withText(containsString("$appName | OSS Libraries")))))
Espresso.pressBack()
}
private fun goBackButton() =
onView(CoreMatchers.allOf(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)
}
}
}