Bug 1884903 - Remove redundant assertion functions from SettingsSubMenuAboutRobot
[gecko.git] / mobile / android / fenix / app / src / androidTest / java / org / mozilla / fenix / ui / robots / SettingsSubMenuAboutRobot.kt
blob51156d089596080af8dc968f03244aede113f4af
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 @file:Suppress("TooManyFunctions")
7 package org.mozilla.fenix.ui.robots
9 import android.os.Build
10 import android.widget.TextView
11 import androidx.core.content.pm.PackageInfoCompat
12 import androidx.test.espresso.Espresso
13 import androidx.test.espresso.Espresso.onView
14 import androidx.test.espresso.ViewAssertion
15 import androidx.test.espresso.action.ViewActions.click
16 import androidx.test.espresso.assertion.ViewAssertions.matches
17 import androidx.test.espresso.matcher.ViewMatchers
18 import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
19 import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
20 import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
21 import androidx.test.espresso.matcher.ViewMatchers.withId
22 import androidx.test.espresso.matcher.ViewMatchers.withText
23 import androidx.test.platform.app.InstrumentationRegistry
24 import androidx.test.uiautomator.UiScrollable
25 import androidx.test.uiautomator.UiSelector
26 import mozilla.components.support.utils.ext.getPackageInfoCompat
27 import org.hamcrest.CoreMatchers.allOf
28 import org.hamcrest.CoreMatchers.containsString
29 import org.mozilla.fenix.BuildConfig
30 import org.mozilla.fenix.R
31 import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
32 import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
33 import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
34 import org.mozilla.fenix.helpers.TestHelper
35 import org.mozilla.fenix.helpers.TestHelper.appName
36 import org.mozilla.fenix.helpers.TestHelper.packageName
37 import org.mozilla.fenix.settings.SupportUtils
38 import java.text.SimpleDateFormat
39 import java.time.LocalDateTime
40 import java.time.format.DateTimeFormatterBuilder
41 import java.time.temporal.ChronoField
42 import java.util.Calendar
43 import java.util.Date
45 /**
46  * Implementation of Robot Pattern for the settings search sub menu.
47  */
48 class SettingsSubMenuAboutRobot {
49     fun verifyAboutFirefoxPreviewInfo() {
50         verifyVersionNumber()
51         verifyProductCompany()
52         verifyCurrentTimestamp()
53         verifyTheLinksList()
54     }
56     fun verifyVersionNumber() {
57         val context = InstrumentationRegistry.getInstrumentation().targetContext
59         val packageInfo = context.packageManager.getPackageInfoCompat(context.packageName, 0)
60         val versionCode = PackageInfoCompat.getLongVersionCode(packageInfo).toString()
61         val buildNVersion = "${packageInfo.versionName} (Build #$versionCode)\n"
62         val geckoVersion =
63             org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION + "-" + org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
64         val asVersion = mozilla.components.Build.applicationServicesVersion
66         onView(withId(R.id.about_text))
67             .check(matches(withText(containsString(buildNVersion))))
68             .check(matches(withText(containsString(geckoVersion))))
69             .check(matches(withText(containsString(asVersion))))
70     }
72     fun verifyProductCompany() {
73         onView(withId(R.id.about_content))
74             .check(matches(withText(containsString("$appName is produced by Mozilla."))))
75     }
77     fun verifyCurrentTimestamp() {
78         onView(withId(R.id.build_date))
79             // Currently UI tests run against debug builds, which display a hard-coded string 'debug build'
80             // instead of the date. See https://github.com/mozilla-mobile/fenix/pull/10812#issuecomment-633746833
81             .check(matches(withText(containsString("debug build"))))
82         // This assertion should be valid for non-debug build types.
83         // .check(BuildDateAssertion.isDisplayedDateAccurate())
84     }
86     fun verifyAboutToolbar() =
87         onView(
88             allOf(
89                 withId(R.id.navigationToolbar),
90                 hasDescendant(withText("About $appName")),
91             ),
92         ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
94     fun verifyWhatIsNewInFirefoxLink() {
95         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
97         val firefox = TestHelper.appContext.getString(R.string.firefox)
98         onView(withText("What’s new in $firefox"))
99             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
100             .perform(click())
101     }
102     fun verifySupport() {
103         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
105         onView(withText("Support"))
106             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
107             .perform(click())
109         TestHelper.verifyUrl(
110             "support.mozilla.org",
111             "org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
112             R.id.mozac_browser_toolbar_url_view,
113         )
114     }
116     fun verifyCrashesLink() {
117         navigationToolbar {
118         }.openThreeDotMenu {
119         }.openSettings {
120         }.openAboutFirefoxPreview {}
122         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
124         onView(withText("Crashes"))
125             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
126             .perform(click())
128         assertUIObjectExists(itemContainingText("No crash reports have been submitted."))
130         for (i in 1..3) {
131             Espresso.pressBack()
132         }
133     }
135     fun verifyPrivacyNoticeLink() {
136         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
138         onView(withText("Privacy notice"))
139             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
140             .perform(click())
142         TestHelper.verifyUrl(
143             "/privacy/firefox",
144             "org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
145             R.id.mozac_browser_toolbar_url_view,
146         )
147     }
149     fun verifyKnowYourRightsLink() {
150         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
152         onView(withText("Know your rights"))
153             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
154             .perform(click())
156         TestHelper.verifyUrl(
157             SupportUtils.SumoTopic.YOUR_RIGHTS.topicStr,
158             "org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
159             R.id.mozac_browser_toolbar_url_view,
160         )
161     }
163     fun verifyLicensingInformationLink() {
164         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
166         onView(withText("Licensing information"))
167             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
168             .perform(click())
170         TestHelper.verifyUrl(
171             "about:license",
172             "org.mozilla.fenix.debug:id/mozac_browser_toolbar_url_view",
173             R.id.mozac_browser_toolbar_url_view,
174         )
175     }
177     fun verifyLibrariesUsedLink() {
178         aboutMenuList.scrollToEnd(LISTS_MAXSWIPES)
180         onView(withText("Libraries that we use"))
181             .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
182             .perform(click())
184         onView(withId(R.id.navigationToolbar)).check(matches(hasDescendant(withText(containsString("$appName | OSS Libraries")))))
185         Espresso.pressBack()
186     }
188     fun verifyTheLinksList() {
189         verifyAboutToolbar()
190         verifyWhatIsNewInFirefoxLink()
191         navigateBackToAboutPage()
192         verifySupport()
193         verifyCrashesLink()
194         navigateBackToAboutPage()
195         verifyPrivacyNoticeLink()
196         navigateBackToAboutPage()
197         verifyKnowYourRightsLink()
198         navigateBackToAboutPage()
199         verifyLicensingInformationLink()
200         navigateBackToAboutPage()
201         verifyLibrariesUsedLink()
202     }
204     class Transition {
205         fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
206             goBackButton().perform(click())
208             SettingsRobot().interact()
209             return SettingsRobot.Transition()
210         }
211     }
214 private fun navigateBackToAboutPage() {
215     navigationToolbar {
216     }.openThreeDotMenu {
217     }.openSettings {
218     }.openAboutFirefoxPreview {
219     }
222 private val aboutMenuList = UiScrollable(UiSelector().resourceId("$packageName:id/about_layout"))
224 private fun goBackButton() =
225     onView(withContentDescription("Navigate up"))
227 class BuildDateAssertion {
228     // When the app is built on firebase, there are times where the BuildDate is off by a few seconds or a few minutes.
229     // To compensate for that slight discrepancy, this assertion was added to see if the Build Date shown
230     // is within a reasonable amount of time from when the app was built.
231     companion object {
232         // this pattern represents the following date format: "Monday 12/30 @ 6:49 PM"
233         private const val DATE_PATTERN = "EEEE M/d @ h:m a"
235         //
236         private const val NUM_OF_HOURS = 1
238         fun isDisplayedDateAccurate(): ViewAssertion {
239             return ViewAssertion { view, noViewFoundException ->
240                 if (noViewFoundException != null) throw noViewFoundException
242                 val textFromView = (view as TextView).text
243                     ?: throw AssertionError("This view is not of type TextView")
245                 verifyDateIsWithinRange(textFromView.toString(), NUM_OF_HOURS)
246             }
247         }
249         private fun verifyDateIsWithinRange(dateText: String, hours: Int) {
250             // This assertion checks whether has defined a range of tim
251             if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
252                 val simpleDateFormat = SimpleDateFormat(DATE_PATTERN)
253                 val date = simpleDateFormat.parse(dateText)
254                 if (date == null || !date.isWithinRangeOf(hours)) {
255                     throw AssertionError("The build date is not within Range.")
256                 }
257             } else {
258                 val textviewDate = getLocalDateTimeFromString(dateText)
259                 val buildConfigDate = getLocalDateTimeFromString(BuildConfig.BUILD_DATE)
261                 if (!buildConfigDate.isEqual(textviewDate) &&
262                     !textviewDate.isWithinRangeOf(hours, buildConfigDate)
263                 ) {
264                     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.")
265                 }
266             }
267         }
269         private fun Date.isWithinRangeOf(hours: Int): Boolean {
270             // To determine the date range, the maxDate is retrieved by adding the variable hours to the calendar.
271             // 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
272             // This will result in the maxDate being equal to the original Date + hours, and minDate being equal to original Date - hours
274             val calendar = Calendar.getInstance()
275             val currentYear = calendar.get(Calendar.YEAR)
276             calendar.time = this
277             calendar.set(Calendar.YEAR, currentYear)
278             val updatedDate = calendar.time
280             calendar.add(Calendar.HOUR_OF_DAY, hours)
281             val maxDate = calendar.time
282             calendar.add(
283                 Calendar.HOUR_OF_DAY,
284                 hours * -2,
285             ) // Gets the minDate by subtracting from maxDate
286             val minDate = calendar.time
287             return updatedDate.after(minDate) && updatedDate.before(maxDate)
288         }
290         private fun LocalDateTime.isWithinRangeOf(
291             hours: Int,
292             baselineDate: LocalDateTime,
293         ): Boolean {
294             val upperBound = baselineDate.plusHours(hours.toLong())
295             val lowerBound = baselineDate.minusHours(hours.toLong())
296             val currentDate = this
297             return currentDate.isAfter(lowerBound) && currentDate.isBefore(upperBound)
298         }
300         private fun getLocalDateTimeFromString(buildDate: String): LocalDateTime {
301             val dateFormatter = DateTimeFormatterBuilder().appendPattern(DATE_PATTERN)
302                 .parseDefaulting(ChronoField.YEAR, LocalDateTime.now().year.toLong())
303                 .toFormatter()
304             return LocalDateTime.parse(buildDate, dateFormatter)
305         }
306     }