diff --git a/app/src/androidTest/assets/pages/lorem-ipsum.html b/app/src/androidTest/assets/pages/lorem-ipsum.html index 7d5aee9ca..ca519d617 100644 --- a/app/src/androidTest/assets/pages/lorem-ipsum.html +++ b/app/src/androidTest/assets/pages/lorem-ipsum.html @@ -1,16 +1,36 @@ - + + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy + eirmod tempor invidunt + + + -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit,
- sed do eiusmod tempor incididunt ut labore et dolore magna
- aliqua. Ut enim ad minim veniam, quis nostrud exercitation
- ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
- aute irure dolor in reprehenderit in voluptate velit esse cillum
- dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
- non proident, sunt in culpa qui officia deserunt mollit anim id est
- laborum. -

+

Page content: lorem ipsum

+ +

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt

+ +

+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy + eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, + sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. + Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, + sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. + Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor + sit amet. +

+ diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/Matchers.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/Matchers.kt index 694fa784a..0f3e7edf8 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/Matchers.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/Matchers.kt @@ -6,8 +6,11 @@ package org.mozilla.fenix.helpers import android.graphics.Bitmap import android.view.View +import android.view.ViewGroup import org.hamcrest.CoreMatchers.not +import org.hamcrest.Description import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher import org.mozilla.fenix.helpers.matchers.BitmapDrawableMatcher import androidx.test.espresso.matcher.ViewMatchers.isChecked as espressoIsChecked import androidx.test.espresso.matcher.ViewMatchers.isEnabled as espressoIsEnabled @@ -34,3 +37,22 @@ private fun maybeInvertMatcher(matcher: Matcher, useUnmodifiedMatcher: Boo } fun withBitmapDrawable(bitmap: Bitmap, name: String): Matcher? = BitmapDrawableMatcher(bitmap, name) + +fun nthChildOf( + parentMatcher: Matcher, + childPosition: Int +): Matcher { + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("Position is $childPosition") + } + + public override fun matchesSafely(view: View): Boolean { + if (view.parent !is ViewGroup) { + return parentMatcher.matches(view.parent) + } + val group = view.parent as ViewGroup + return parentMatcher.matches(view.parent) && group.getChildAt(childPosition) == view + } + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt new file mode 100644 index 000000000..766246f14 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ReaderViewTest.kt @@ -0,0 +1,271 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.ui + +import androidx.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.helpers.AndroidAssetDispatcher +import org.mozilla.fenix.helpers.HomeActivityIntentTestRule +import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.ui.robots.navigationToolbar +import org.mozilla.fenix.ui.robots.readerViewRobot + +/** + * Tests for verifying basic functionality of content context menus + * + * - Verifies Reader View entry and detection when available UI and functionality + * - Verifies Reader View exit UI and functionality + * - Verifies Reader View appearance controls UI and functionality + * + */ + +class ReaderViewTest { + private lateinit var mockWebServer: MockWebServer + val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + @get:Rule + val activityIntentTestRule = HomeActivityIntentTestRule() + + @Before + fun setUp() { + mockWebServer = MockWebServer().apply { + setDispatcher(AndroidAssetDispatcher()) + start() + } + } + + @After + fun tearDown() { + mockWebServer.shutdown() + } + + /** + * Verify that Reader View capable pages + * + * - Show blue notification in the three dot menu + * - Show the toggle button in the three dot menu + * + */ + @Test + fun verifyReaderViewPageMenuDetection() { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(readerViewPage.url) { + verifyPageContent(readerViewPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(true) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(true) + }.closeBrowserMenuToBrowser { } + } + + /** + * Verify that non Reader View capable pages + * + * - Do not show a blue notification in the three dot menu + * - Reader View toggle should not be visible in the three dot menu + * + */ + @Test + fun verifyNonReaderViewPageMenuNoDetection() { + var genericPage = + TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(genericPage.url) { + verifyPageContent(genericPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(false) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(false) + verifyReaderViewAppearance(false) + }.closeBrowserMenuToBrowser { } + } + + @Test + fun verifyReaderViewToggle() { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(readerViewPage.url) { + verifyPageContent(readerViewPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(true) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(true) + }.toggleReaderView { + }.openThreeDotMenu { + verifyReaderViewAppearance(true) + }.toggleReaderView { + }.openThreeDotMenu { + verifyReaderViewAppearance(false) + }.close { } + + readerViewRobot { + verifyReaderViewDetected(true) + } + } + + @Test + fun verifyReaderViewAppearanceUI() { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(readerViewPage.url) { + verifyPageContent(readerViewPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(true) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(true) + }.toggleReaderView { + }.openThreeDotMenu { + verifyReaderViewAppearance(true) + }.openReaderViewAppearance { + verifyAppearanceFontGroup(true) + verifyAppearanceFontSansSerif(true) + verifyAppearanceFontSerif(true) + verifyAppearanceFontIncrease(true) + verifyAppearanceFontDecrease(true) + verifyAppearanceColorGroup(true) + verifyAppearanceColorDark(true) + verifyAppearanceColorLight(true) + verifyAppearanceColorSepia(true) + } + } + + @Test + fun verifyReaderViewAppearanceFontToggle() { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(readerViewPage.url) { + verifyPageContent(readerViewPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(true) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(true) + }.toggleReaderView { + }.openThreeDotMenu { + verifyReaderViewAppearance(true) + }.openReaderViewAppearance { + verifyAppearanceFontGroup(true) + verifyAppearanceFontSansSerif(true) + verifyAppearanceFontSerif(true) + verifyAppearanceFontIncrease(true) + verifyAppearanceFontDecrease(true) + }.toggleSansSerif { + verifyAppearanceFontIsActive("SANSSERIF") + }.toggleSerif { + verifyAppearanceFontIsActive("SERIF") + } + } + + @Test + fun verifyReaderViewAppearanceFontSizeToggle() { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(readerViewPage.url) { + verifyPageContent(readerViewPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(true) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(true) + }.toggleReaderView { + }.openThreeDotMenu { + verifyReaderViewAppearance(true) + }.openReaderViewAppearance { + verifyAppearanceFontIncrease(true) + verifyAppearanceFontDecrease(true) + verifyAppearanceFontSize(3) + }.toggleFontSizeIncrease { + verifyAppearanceFontSize(4) + }.toggleFontSizeIncrease { + verifyAppearanceFontSize(5) + }.toggleFontSizeIncrease { + verifyAppearanceFontSize(6) + }.toggleFontSizeDecrease { + verifyAppearanceFontSize(5) + }.toggleFontSizeDecrease { + verifyAppearanceFontSize(4) + }.toggleFontSizeDecrease { + verifyAppearanceFontSize(3) + } + } + + @Test + fun verifyReaderViewAppearanceColorSchemeChange() { + val readerViewPage = + TestAssetHelper.getLoremIpsumAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(readerViewPage.url) { + verifyPageContent(readerViewPage.content) + } + + readerViewRobot { + verifyReaderViewDetected(true) + } + + navigationToolbar { + }.openThreeDotMenu { + verifyReaderViewToggle(true) + }.toggleReaderView { + }.openThreeDotMenu { + verifyReaderViewAppearance(true) + }.openReaderViewAppearance { + verifyAppearanceColorDark(true) + verifyAppearanceColorLight(true) + verifyAppearanceColorSepia(true) + }.toggleColorSchemeChangeDark { + verifyAppearanceColorSchemeChange("DARK") + }.toggleColorSchemeChangeSepia { + verifyAppearanceColorSchemeChange("SEPIA") + }.toggleColorSchemeChangeLight { + verifyAppearanceColorSchemeChange("LIGHT") + } + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ReaderViewRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ReaderViewRobot.kt new file mode 100644 index 000000000..840a011de --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ReaderViewRobot.kt @@ -0,0 +1,263 @@ +/* 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.content.Context +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 +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.click +import org.mozilla.fenix.helpers.nthChildOf + +/** + * Implementation of Robot Pattern for Reader View UI. + */ +class ReaderViewRobot { + + fun verifyReaderViewDetected(visible: Boolean = false): ViewInteraction = + assertReaderViewDetected(visible) + + fun verifyAppearanceFontGroup(visible: Boolean = false): ViewInteraction = + assertAppearanceFontGroup(visible) + + fun verifyAppearanceFontSansSerif(visible: Boolean = false): ViewInteraction = + assertAppearanceFontSansSerif(visible) + + fun verifyAppearanceFontSerif(visible: Boolean = false): ViewInteraction = + assertAppearanceFontSerif(visible) + + fun verifyAppearanceFontDecrease(visible: Boolean = false): ViewInteraction = + assertAppearanceFontDecrease(visible) + + fun verifyAppearanceFontIncrease(visible: Boolean = false): ViewInteraction = + assertAppearanceFontIncrease(visible) + + fun verifyAppearanceColorGroup(visible: Boolean = false): ViewInteraction = + assertAppearanceColorGroup(visible) + + fun verifyAppearanceColorSepia(visible: Boolean = false): ViewInteraction = + assertAppearanceColorSepia(visible) + + fun verifyAppearanceColorDark(visible: Boolean = false): ViewInteraction = + assertAppearanceColorDark(visible) + + fun verifyAppearanceColorLight(visible: Boolean = false): ViewInteraction = + assertAppearanceColorLight(visible) + + fun verifyAppearanceFontIsActive(fontType: String) { + val fontTypeKey: String = "mozac-readerview-fonttype" + + val prefs = InstrumentationRegistry.getInstrumentation() + .targetContext.getSharedPreferences( + "mozac_feature_reader_view", + Context.MODE_PRIVATE + ) + + assertEquals(fontType, prefs.getString(fontTypeKey, "")) + } + + fun verifyAppearanceFontSize(expectedFontSize: Int) { + val fontSizeKey: String = "mozac-readerview-fontsize" + + val prefs = InstrumentationRegistry.getInstrumentation() + .targetContext.getSharedPreferences( + "mozac_feature_reader_view", + Context.MODE_PRIVATE + ) + + val fontSizeKeyValue = prefs.getInt(fontSizeKey, 3) + + assertEquals(expectedFontSize, fontSizeKeyValue) + } + + fun verifyAppearanceColorSchemeChange(expectedColorScheme: String) { + val colorSchemeKey: String = "mozac-readerview-colorscheme" + + val prefs = InstrumentationRegistry.getInstrumentation() + .targetContext.getSharedPreferences( + "mozac_feature_reader_view", + Context.MODE_PRIVATE + ) + + assertEquals(expectedColorScheme, prefs.getString(colorSchemeKey, "")) + } + + class Transition { + fun toggleSansSerif(interact: ReaderViewRobot.() -> Unit): Transition { + fun sansSerifButton() = + onView( + withId(R.id.mozac_feature_readerview_font_sans_serif) + ) + + sansSerifButton().click() + + ReaderViewRobot().interact() + return Transition() + } + + fun toggleSerif(interact: ReaderViewRobot.() -> Unit): Transition { + fun serifButton() = + onView( + withId(R.id.mozac_feature_readerview_font_serif) + ) + + serifButton().click() + + ReaderViewRobot().interact() + return Transition() + } + + fun toggleFontSizeDecrease(interact: ReaderViewRobot.() -> Unit): Transition { + fun fontSizeDecrease() = + onView( + withId(R.id.mozac_feature_readerview_font_size_decrease) + ) + + fontSizeDecrease().click() + + ReaderViewRobot().interact() + return Transition() + } + + fun toggleFontSizeIncrease(interact: ReaderViewRobot.() -> Unit): Transition { + fun fontSizeIncrease() = + onView( + withId(R.id.mozac_feature_readerview_font_size_increase) + ) + + fontSizeIncrease().click() + + ReaderViewRobot().interact() + return Transition() + } + + fun toggleColorSchemeChangeLight(interact: ReaderViewRobot.() -> Unit): Transition { + fun toggleLightColorSchemeButton() = + onView( + withId(R.id.mozac_feature_readerview_color_light) + ) + + toggleLightColorSchemeButton().click() + + ReaderViewRobot().interact() + return Transition() + } + + fun toggleColorSchemeChangeDark(interact: ReaderViewRobot.() -> Unit): Transition { + fun toggleDarkColorSchemeButton() = + onView( + withId(R.id.mozac_feature_readerview_color_dark) + ) + + toggleDarkColorSchemeButton().click() + + ReaderViewRobot().interact() + return Transition() + } + + fun toggleColorSchemeChangeSepia(interact: ReaderViewRobot.() -> Unit): Transition { + fun toggleSepiaColorSchemeButton() = + onView( + withId(R.id.mozac_feature_readerview_color_sepia) + ) + + toggleSepiaColorSchemeButton().click() + + ReaderViewRobot().interact() + return Transition() + } + } +} + +fun readerViewRobot(interact: ReaderViewRobot.() -> Unit): ReaderViewRobot.Transition { + ReaderViewRobot().interact() + return ReaderViewRobot.Transition() +} + +/** + * Detects for the blue notification dot in the three dot menu + */ +private fun assertReaderViewDetected(visible: Boolean) = + onView( + nthChildOf( + withId(R.id.mozac_browser_toolbar_menu), 2 + ) + ).check( + matches(withEffectiveVisibility(visibleOrGone(visible))) + ) + +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 diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index f11fd9586..36b984d76 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -8,6 +8,7 @@ package org.mozilla.fenix.ui.robots import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasFocus @@ -43,6 +44,8 @@ class ThreeDotMenuMainRobot { fun verifyRefreshButton() = assertRefreshButton() fun verifyCloseAllTabsButton() = assertCloseAllTabsButton() fun verifyShareButton() = assertShareButton() + fun verifyReaderViewToggle(visible: Boolean) = assertReaderViewToggle(visible) + fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible) fun clickShareButton() { shareButton().click() mDevice.waitNotNull(Until.findObject(By.text("SHARE A LINK")), waitingTime) @@ -191,6 +194,20 @@ class ThreeDotMenuMainRobot { BrowserRobot().interact() return BrowserRobot.Transition() } + + fun toggleReaderView(interact: NavigationToolbarRobot.() -> Unit): NavigationToolbarRobot.Transition { + readerViewToggle().click() + + NavigationToolbarRobot().interact() + return NavigationToolbarRobot.Transition() + } + + fun openReaderViewAppearance(interact: ReaderViewRobot.() -> Unit): ReaderViewRobot.Transition { + readerViewAppearanceToggle().click() + + ReaderViewRobot().interact() + return ReaderViewRobot.Transition() + } } } @@ -286,3 +303,14 @@ private fun whatsNewButton() = onView( withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) private fun assertWhatsNewButton() = whatsNewButton() .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + +private fun readerViewToggle() = onView(allOf(withText(R.string.browser_menu_read))) +private fun assertReaderViewToggle(visible: Boolean) = readerViewToggle() + .check( + if (visible) matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)) else ViewAssertions.doesNotExist() + ) +private fun readerViewAppearanceToggle() = onView(allOf(withText(R.string.browser_menu_read_appearance))) +private fun assertReaderViewAppearanceButton(visible: Boolean) = readerViewAppearanceToggle() + .check( + if (visible) matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)) else ViewAssertions.doesNotExist() + )