1
0
Fork 0

Update HomeScreenTest (#3882)

master
Richard Pappalardo 2019-07-05 10:38:09 -07:00 committed by GitHub
parent 00271cc20d
commit e7d0bfe581
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 317 additions and 0 deletions

View File

@ -399,6 +399,7 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
} }
androidTestImplementation Deps.mockwebserver
testImplementation Deps.mozilla_support_test testImplementation Deps.mozilla_support_test
testImplementation Deps.androidx_junit testImplementation Deps.androidx_junit
testImplementation Deps.robolectric testImplementation Deps.robolectric

View File

@ -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)
}
}
}

View File

@ -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<Uri> {
val mockServer = MockWebServer()
var uniquePath = 0
val uris = mutableListOf<Uri>()
messages.forEach { message ->
val response = MockResponse().setBody("<html><body>$message</body></html>")
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)
}
}

View File

@ -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<TestAsset> {
@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)
}
}

View File

@ -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
}
}

View File

@ -4,9 +4,16 @@
package org.mozilla.fenix.ui 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.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
/** /**
@ -16,15 +23,50 @@ import org.mozilla.fenix.ui.robots.homeScreen
class HomeScreenTest { class HomeScreenTest {
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping. /* 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 @get:Rule
val activityTestRule = HomeActivityTestRule() val activityTestRule = HomeActivityTestRule()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
setDispatcher(AndroidAssetDispatcher())
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test @Test
fun homeScreenItemsTest() { 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 { homeScreen {
verifyHomeScreen() verifyHomeScreen()
verifyHomePrivateBrowsingButton() verifyHomePrivateBrowsingButton()
verifyHomeMenu() verifyHomeMenu()
verifyHomeWordmark() verifyHomeWordmark()
verifyOpenTabsHeader()
verifyAddTabButton()
verifyNoTabsOpenedHeader()
verifyNoTabsOpenedText()
verifyCollectionsHeader()
verifyNoCollectionsHeader()
verifyNoCollectionsText()
verifyHomeToolbar() verifyHomeToolbar()
verifyHomeComponent() verifyHomeComponent()
} }

View File

@ -6,24 +6,40 @@
package org.mozilla.fenix.ui.robots package org.mozilla.fenix.ui.robots
import android.net.Uri
import androidx.test.espresso.Espresso.onView 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.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
import org.mozilla.fenix.R 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. * Implementation of Robot Pattern for the home screen menu.
*/ */
class HomeScreenRobot { class HomeScreenRobot {
fun verifyNavigationToolbar() = assertNavigationToolbar()
fun verifyHomeScreen() = assertHomeScreen() fun verifyHomeScreen() = assertHomeScreen()
fun verifyHomePrivateBrowsingButton() = assertHomePrivateBrowsingButton() fun verifyHomePrivateBrowsingButton() = assertHomePrivateBrowsingButton()
fun verifyHomeMenu() = assertHomeMenu() 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 verifyHomeWordmark() = assertHomeWordmark()
fun verifyHomeToolbar() = assertHomeToolbar() fun verifyHomeToolbar() = assertHomeToolbar()
fun verifyHomeComponent() = assertHomeComponent() fun verifyHomeComponent() = assertHomeComponent()
@ -40,6 +56,23 @@ class HomeScreenRobot {
ThreeDotMenuRobot().interact() ThreeDotMenuRobot().interact()
return ThreeDotMenuRobot.Transition() 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() 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")) private fun assertHomeScreen() = onView(ViewMatchers.withResourceName("homeLayout"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertHomeMenu() = onView(ViewMatchers.withResourceName("menuButton")) private fun assertHomeMenu() = onView(ViewMatchers.withResourceName("menuButton"))
@ -58,6 +102,26 @@ private fun assertHomeWordmark() = onView(ViewMatchers.withResourceName("wordmar
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertHomeToolbar() = onView(ViewMatchers.withResourceName("toolbar")) private fun assertHomeToolbar() = onView(ViewMatchers.withResourceName("toolbar"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .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")) private fun assertHomeComponent() = onView(ViewMatchers.withResourceName("home_component"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))

View File

@ -52,6 +52,7 @@ private object Versions {
const val espresso_core = "2.2.2" const val espresso_core = "2.2.2"
const val espresso_version = "3.0.2" const val espresso_version = "3.0.2"
const val mockwebserver = "3.11.0"
const val orchestrator = "1.1.1" const val orchestrator = "1.1.1"
const val tools_test_rules = "1.1.1" const val tools_test_rules = "1.1.1"
const val tools_test_runner = "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_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_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 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 orchestrator = "androidx.test:orchestrator:${Versions.orchestrator}"
const val tools_test_rules = "com.android.support.test:rules:${Versions.tools_test_rules}" 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}" const val tools_test_runner = "com.android.support.test:runner:${Versions.tools_test_runner}"