From e7d0bfe581ba66311ab40dc29666b0167a0c4ed2 Mon Sep 17 00:00:00 2001 From: Richard Pappalardo Date: Fri, 5 Jul 2019 10:38:09 -0700 Subject: [PATCH] Update HomeScreenTest (#3882) --- app/build.gradle | 1 + .../java/org/mozilla/fenix/helpers/Assert.kt | 34 ++++++++++ .../mozilla/fenix/helpers/MockWebServer.kt | 63 ++++++++++++++++++ .../mozilla/fenix/helpers/TestAssetHelper.kt | 66 +++++++++++++++++++ .../org/mozilla/fenix/helpers/ext/String.kt | 45 +++++++++++++ .../org/mozilla/fenix/ui/HomeScreenTest.kt | 42 ++++++++++++ .../fenix/ui/robots/HomeScreenRobot.kt | 64 ++++++++++++++++++ buildSrc/src/main/java/Dependencies.kt | 2 + 8 files changed, 317 insertions(+) create mode 100644 app/src/androidTest/java/org/mozilla/fenix/helpers/Assert.kt create mode 100644 app/src/androidTest/java/org/mozilla/fenix/helpers/MockWebServer.kt create mode 100644 app/src/androidTest/java/org/mozilla/fenix/helpers/TestAssetHelper.kt create mode 100644 app/src/androidTest/java/org/mozilla/fenix/helpers/ext/String.kt diff --git a/app/build.gradle b/app/build.gradle index 7cc1bcbf0..6d9c11597 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -399,6 +399,7 @@ dependencies { exclude group: 'com.android.support', module: 'support-annotations' } + androidTestImplementation Deps.mockwebserver testImplementation Deps.mozilla_support_test testImplementation Deps.androidx_junit testImplementation Deps.robolectric diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/Assert.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/Assert.kt new file mode 100644 index 000000000..d5f155ef2 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/Assert.kt @@ -0,0 +1,34 @@ + +package org.mozilla.fenix.helpers + +import android.graphics.Bitmap + +/* 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/. */ + +import android.graphics.Color +import org.junit.Assert.assertEquals + +/** + * Asserts the two bitmaps are the same by ensuring their dimensions, config, and + * pixel data are the same (within the provided delta): this is the same metrics that + * [Bitmap.sameAs] uses. + */ +fun assertEqualsWithDelta(expectedB: Bitmap, actualB: Bitmap, delta: Float) { + assertEquals("widths should be equal", expectedB.width, actualB.width) + assertEquals("heights should be equal", expectedB.height, actualB.height) + assertEquals("config should be equal", expectedB.config, actualB.config) + + for (i in 0 until expectedB.width) { + for (j in 0 until expectedB.height) { + val ePx = expectedB.getPixel(i, j) + val aPx = actualB.getPixel(i, j) + val warn = "Pixel ${i}x$j" + assertEquals("$warn a", Color.alpha(ePx).toFloat(), Color.alpha(aPx).toFloat(), delta) + assertEquals("$warn r", Color.red(ePx).toFloat(), Color.red(aPx).toFloat(), delta) + assertEquals("$warn g", Color.green(ePx).toFloat(), Color.green(aPx).toFloat(), delta) + assertEquals("$warn b", Color.blue(ePx).toFloat(), Color.blue(aPx).toFloat(), delta) + } + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/MockWebServer.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/MockWebServer.kt new file mode 100644 index 000000000..4a0b0f4cc --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/MockWebServer.kt @@ -0,0 +1,63 @@ +/* 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.helpers + +import java.io.IOException + +import android.net.Uri +import android.os.Handler +import android.os.Looper +import androidx.test.InstrumentationRegistry +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.mozilla.fenix.helpers.ext.toUri + +object MockWebServerHelper { + + fun initMockWebServerAndReturnEndpoints(vararg messages: String): List { + val mockServer = MockWebServer() + var uniquePath = 0 + val uris = mutableListOf() + messages.forEach { message -> + val response = MockResponse().setBody("$message") + mockServer.enqueue(response) + val endpoint = mockServer.url(uniquePath++.toString()).toString().toUri()!! + uris += endpoint + } + return uris + } +} + +/** + * A [MockWebServer] [Dispatcher] that will return Android assets in the body of requests. + * + * If the dispatcher is unable to read a requested asset, it will fail the test by throwing an + * Exception on the main thread. + * + * @sample [org.mozilla.tv.firefox.ui.BasicNavigationTest.basicNavigationTest] + */ +const val HTTP_OK = 200 +const val HTTP_NOT_FOUND = 404 + +class AndroidAssetDispatcher : Dispatcher() { + private val mainThreadHandler = Handler(Looper.getMainLooper()) + + override fun dispatch(request: RecordedRequest): MockResponse { + val assetManager = InstrumentationRegistry.getContext().assets + val assetContents = try { + val pathNoLeadingSlash = request.path.drop(1) + assetManager.open(pathNoLeadingSlash).use { inputStream -> + inputStream.bufferedReader().use { it.readText() } + } + } catch (e: IOException) { // e.g. file not found. + // We're on a background thread so we need to forward the exception to the main thread. + mainThreadHandler.postAtFrontOfQueue { throw e } + return MockResponse().setResponseCode(HTTP_NOT_FOUND) + } + return MockResponse().setResponseCode(HTTP_OK).setBody(assetContents) + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/TestAssetHelper.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/TestAssetHelper.kt new file mode 100644 index 000000000..562569bf3 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/TestAssetHelper.kt @@ -0,0 +1,66 @@ +/* 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.helpers + +import android.net.Uri + +import okhttp3.mockwebserver.MockWebServer +import org.mozilla.fenix.helpers.ext.toUri +import java.util.concurrent.TimeUnit + +/** + * Helper for hosting web pages locally for testing purposes. + */ +object TestAssetHelper { + @Suppress("MagicNumber") + val waitingTime: Long = TimeUnit.SECONDS.toMillis(15) + val waitingTimeShort: Long = TimeUnit.SECONDS.toMillis(1) + data class TestAsset(val url: Uri, val content: String) + + /** + * Hosts 3 simple websites, found at androidTest/assets/pages/generic[1|2|3].html + * Returns a list of TestAsset, which can be used to navigate to each and + * assert that the correct information is being displayed. + * + * Content for these pages all follow the same pattern. See [generic1.html] for + * content implementation details. + */ + fun getGenericAssets(server: MockWebServer): List { + @Suppress("MagicNumber") + return (1..3).map { + TestAsset( + server.url("pages/generic$it.html").toString().toUri()!!, + "Page content: $it" + ) + } + } + + fun getGenericAsset(server: MockWebServer, pageNum: Int): TestAsset { + val url = server.url("pages/generic$pageNum.html").toString().toUri()!! + val content = "Page content: $pageNum" + + return TestAsset(url, content) + } + + fun getLoremIpsumAsset(server: MockWebServer): TestAsset { + val url = server.url("pages/lorem-ipsum.html").toString().toUri()!! + val content = "Page content: lorem ipsum" + return TestAsset(url, content) + } + + fun getRefreshAsset(server: MockWebServer): TestAsset { + val url = server.url("pages/refresh.html").toString().toUri()!! + val content = "Page content: refresh" + + return TestAsset(url, content) + } + + fun getUUIDPage(server: MockWebServer): TestAsset { + val url = server.url("pages/basic_nav_uuid.html").toString().toUri()!! + val content = "Page content: basic_nav_uuid" + + return TestAsset(url, content) + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/ext/String.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/ext/String.kt new file mode 100644 index 000000000..b754a8634 --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/ext/String.kt @@ -0,0 +1,45 @@ +/* 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.helpers.ext + +import android.net.Uri +import java.net.URI +import java.net.URISyntaxException + +// Extension functions for the String class + +/** + * If this string starts with the one or more of the given [prefixes] (in order and ignoring case), + * returns a copy of this string with the prefixes removed. Otherwise, returns this string. + */ +fun String.removePrefixesIgnoreCase(vararg prefixes: String): String { + var value = this + var lower = this.toLowerCase() + + prefixes.forEach { + if (lower.startsWith(it.toLowerCase())) { + value = value.substring(it.length) + lower = lower.substring(it.length) + } + } + + return value +} + +fun String?.toUri(): Uri? = if (this == null) { + null +} else { + Uri.parse(this) +} + +fun String?.toJavaURI(): URI? = if (this == null) { + null +} else { + try { + URI(this) + } catch (e: URISyntaxException) { + null + } +} diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt index c5f4054be..d7692ca93 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt @@ -4,9 +4,16 @@ 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.HomeActivityTestRule +import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.ui.robots.homeScreen /** @@ -16,15 +23,50 @@ import org.mozilla.fenix.ui.robots.homeScreen class HomeScreenTest { /* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping. + + private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + private lateinit var mockWebServer: MockWebServer + @get:Rule val activityTestRule = HomeActivityTestRule() + + @Before + fun setUp() { + mockWebServer = MockWebServer().apply { + setDispatcher(AndroidAssetDispatcher()) + start() + } + } + + @After + fun tearDown() { + mockWebServer.shutdown() + } + @Test fun homeScreenItemsTest() { + + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + // temp work-around until FIRST_RUN pref = false + // is added: https://github.com/mozilla-mobile/fenix/issues/3777 + homeScreen { + verifyNavigationToolbar() + }.completeFirstRun(defaultWebPage.url) { + } + homeScreen { verifyHomeScreen() verifyHomePrivateBrowsingButton() verifyHomeMenu() verifyHomeWordmark() + verifyOpenTabsHeader() + verifyAddTabButton() + verifyNoTabsOpenedHeader() + verifyNoTabsOpenedText() + verifyCollectionsHeader() + verifyNoCollectionsHeader() + verifyNoCollectionsText() verifyHomeToolbar() verifyHomeComponent() } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt index e867a23b8..879f1cbbe 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt @@ -6,24 +6,40 @@ package org.mozilla.fenix.ui.robots +import android.net.Uri import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import org.hamcrest.CoreMatchers import org.hamcrest.Matchers.allOf import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime +import org.mozilla.fenix.helpers.click /** * Implementation of Robot Pattern for the home screen menu. */ class HomeScreenRobot { + fun verifyNavigationToolbar() = assertNavigationToolbar() fun verifyHomeScreen() = assertHomeScreen() fun verifyHomePrivateBrowsingButton() = assertHomePrivateBrowsingButton() fun verifyHomeMenu() = assertHomeMenu() + fun verifyOpenTabsHeader() = assertOpenTabsHeader() + fun verifyAddTabButton() = assertAddTabButton() + fun verifyNoTabsOpenedText() = assertNoTabsOpenedText() + fun verifyCollectionsHeader() = assertCollectionsHeader() + fun verifyNoCollectionsHeader() = assertNoCollectionsHeader() + fun verifyNoCollectionsText() = assertNoCollectionsText() + fun verifyNoTabsOpenedHeader() = assertNoTabsOpenedHeader() fun verifyHomeWordmark() = assertHomeWordmark() fun verifyHomeToolbar() = assertHomeToolbar() fun verifyHomeComponent() = assertHomeComponent() @@ -40,6 +56,23 @@ class HomeScreenRobot { ThreeDotMenuRobot().interact() return ThreeDotMenuRobot.Transition() } + + fun completeFirstRun(url: Uri, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + + mDevice.wait(Until.findObject(By.text("Search or enter address")), waitingTime) + navigationToolbar().perform(click()) + + browserToolbarEditView().perform( + typeText(url.toString()), + ViewActions.pressImeActionButton()) + + tabCounterText().click() + mDevice.wait(Until.findObject(By.res("close_tab_button")), waitingTime) + closeTabButton().click() + + BrowserRobot().interact() + return BrowserRobot.Transition() + } } } @@ -48,6 +81,17 @@ fun homeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition return HomeScreenRobot.Transition() } +val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + +private fun navigationToolbar() = onView(CoreMatchers.allOf(ViewMatchers.withText("Search or enter address"))) + +private fun browserToolbarEditView() = onView(allOf(ViewMatchers.withId(R.id.mozac_browser_toolbar_edit_url_view))) +private fun closeTabButton() = onView(allOf(ViewMatchers.withId(R.id.close_tab_button))) + +private fun assertNavigationToolbar() = onView(CoreMatchers.allOf(ViewMatchers.withText("Search or enter address"))) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +private fun tabCounterText() = onView(allOf(ViewMatchers.withId(R.id.counter_text))) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) private fun assertHomeScreen() = onView(ViewMatchers.withResourceName("homeLayout")) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) private fun assertHomeMenu() = onView(ViewMatchers.withResourceName("menuButton")) @@ -58,6 +102,26 @@ private fun assertHomeWordmark() = onView(ViewMatchers.withResourceName("wordmar .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) private fun assertHomeToolbar() = onView(ViewMatchers.withResourceName("toolbar")) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +private fun assertOpenTabsHeader() { + mDevice.wait(Until.findObject(By.text("Open tabs")), waitingTime) +} +private fun assertAddTabButton() { + mDevice.wait(Until.findObject(By.res("add_tab_button")), waitingTime) +} +private fun assertNoTabsOpenedHeader() { + mDevice.wait(Until.findObject(By.text("No tabs opened")), waitingTime) +} +private fun assertNoTabsOpenedText() { + onView(CoreMatchers.allOf(ViewMatchers.withText("Your open tabs will be shown here."))) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +} +private fun assertCollectionsHeader() = onView(CoreMatchers.allOf(ViewMatchers.withText("Collections"))) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +private fun assertNoCollectionsHeader() = onView(CoreMatchers.allOf(ViewMatchers.withText("No collections"))) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) +private fun assertNoCollectionsText() = + onView(CoreMatchers.allOf(ViewMatchers.withText("Your collections will be shown here."))) + .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) private fun assertHomeComponent() = onView(ViewMatchers.withResourceName("home_component")) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 8dabf5a79..e85d35525 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -52,6 +52,7 @@ private object Versions { const val espresso_core = "2.2.2" const val espresso_version = "3.0.2" + const val mockwebserver = "3.11.0" const val orchestrator = "1.1.1" const val tools_test_rules = "1.1.1" const val tools_test_runner = "1.1.1" @@ -183,6 +184,7 @@ object Deps { const val espresso_contrib = "com.android.support.test.espresso:espresso-contrib:${Versions.espresso_version}" const val espresso_core = "com.android.support.test.espresso:espresso-core:${Versions.espresso_core}" const val espresso_idling_resources = "com.android.support.test.espresso:espresso-idling-resource:${Versions.espresso_version}" + const val mockwebserver = "com.squareup.okhttp3:mockwebserver:${Versions.mockwebserver}" const val orchestrator = "androidx.test:orchestrator:${Versions.orchestrator}" const val tools_test_rules = "com.android.support.test:rules:${Versions.tools_test_rules}" const val tools_test_runner = "com.android.support.test:runner:${Versions.tools_test_runner}"