diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt index c70bf8bb2..f08565aa7 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt @@ -4,30 +4,30 @@ package org.mozilla.fenix.ui -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice +import kotlinx.coroutines.runBlocking +import mozilla.appservices.places.BookmarkRoot import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test +import org.mozilla.fenix.ext.bookmarkStorage import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.ui.robots.browserScreen import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar +import org.mozilla.fenix.ui.robots.quickActionBar /** * Tests for verifying basic functionality of bookmarks - * */ - class BookmarksTest { /* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping. - private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private lateinit var mockWebServer: MockWebServer + private val bookmarksFolderName = "New Folder" @get:Rule val activityTestRule = HomeActivityTestRule() @@ -43,57 +43,156 @@ class BookmarksTest { @After fun tearDown() { mockWebServer.shutdown() + // Clearing all bookmarks data after each test to avoid overlapping data + val bookmarksStorage = activityTestRule.activity?.bookmarkStorage + runBlocking { + val bookmarks = bookmarksStorage?.getTree(BookmarkRoot.Mobile.id)?.children + bookmarks?.forEach { bookmarksStorage.deleteNode(it.guid) } + } } - @Ignore("This is a stub test, ignore for now") @Test - fun noBookmarkItemsInCacheTest() { - homeScreen { }.dismissOnboarding() - - // Verify "Your Library" in 3-dot menu is visible - // Verify "Bookmarks" line-item in library is visible - // Verify UI elements - // Verify "No bookmarks here" is visible + fun noBookmarkItemsTest() { + homeScreen { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + verifyEmptyBookmarksList() + } } - @Ignore("This is a stub test, ignore for now") @Test - fun bookmarkTest() { - // Setup: - // - Visit a URL - // Verify browser view exists - // Swipe up slide-up-tray - // Verify "slide-up tray" is visible - // Verify "Bookmark" is visible in "slide-up tray" - // Click "Bookmark" - // Verify "Bookmark saved" toast notification is visible - // Verify "Your Library" in 3-dot menu is visible - // Click "Your Library" - // Verify "Bookmarks" line-item in Library is visible - // Click "Bookmarks" - // Verify "Bookmarks" UI elements (view is visible) - // Verify bookmark added, URL matches bookmark added in Library - - // Verify bookmarks 3-dot menu functions: - // 1. Edit - // 2. Copy - // 3. Share - // 4. Open in new tab - // 5. Open in private tab - // 6. Delete - - // Verify bookmark visibility in new URL search - - // Verify return to "Your Library" - } - - @Ignore("This is a sample test, ignore") - @Test - fun sampleTest() { + fun verifyBookmarkButtonTest() { val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) navigationToolbar { }.enterURLAndEnterToBrowser(defaultWebPage.url) { + }.openQuickActionBar { + verifyAddBookmarkButton() + clickBookmarkButton() + } + browserScreen { + }.openQuickActionBar { + verifyEditBookmarkButton() + } + } + + @Test + fun addBookmarkTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + quickActionBar { + createBookmark(defaultWebPage.url) + } + + navigationToolbar { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + verifyBookmarkedURL(defaultWebPage.url) + verifyBookmarkFavicon() + } + } + + @Test + fun createBookmarkFolderTest() { + homeScreen { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + clickAddFolderButton() + addNewFolderName(bookmarksFolderName) + saveNewFolder() + verifyFolderTitle(bookmarksFolderName) + } + } + + @Test + fun editBookmarkViewTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + quickActionBar { createBookmark(defaultWebPage.url) } + + navigationToolbar { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + }.openThreeDotMenu { + }.clickEdit { + verifyEditBookmarksView() + verifyBoomarkNameEditBox() + verifyBookmarkURLEditBox() + verifyParentFolderSelector() + navigateUp() + verifyBookmarksMenuView() + } + } + + @Test + fun copyBookmarkURLTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + quickActionBar { createBookmark(defaultWebPage.url) } + + navigationToolbar { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + }.openThreeDotMenu { + }.clickCopy { + verifyCopySnackBarText() + } + } + + @Test + fun openBookmarkInNewTabTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + quickActionBar { createBookmark(defaultWebPage.url) } + + navigationToolbar { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + }.openThreeDotMenu { + }.clickOpenInNewTab { + verifyPageContent(defaultWebPage.content) + }.openHomeScreen { + verifyOpenTabsHeader() + } + } + + @Test + fun openBookmarkInPrivateTabTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + quickActionBar { createBookmark(defaultWebPage.url) } + + navigationToolbar { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + }.openThreeDotMenu { + }.clickOpenInPrivateTab { + verifyPageContent(defaultWebPage.content) + }.openHomeScreen { + verifyPrivateSessionHeader() + } + } + + @Test + fun deleteBookmarkTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + quickActionBar { createBookmark(defaultWebPage.url) } + + navigationToolbar { + }.openThreeDotMenu { + }.openLibrary { + }.openBookmarks { + }.openThreeDotMenu { + }.clickDelete { + verifyDeleteSnackBarText() } } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt index b550d8d61..94fec1194 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt @@ -1,28 +1,88 @@ +/* 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.net.Uri import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.espresso.matcher.ViewMatchers.withParent -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility -import androidx.test.espresso.matcher.ViewMatchers.Visibility +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.withContentDescription +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withParent +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.containsString import org.mozilla.fenix.R import org.mozilla.fenix.helpers.click +/** + * Implementation of Robot Pattern for the bookmarks menu. + */ class BookmarksRobot { fun verifyBookmarksMenuView() = assertBookmarksView() + fun verifyEmptyBookmarksList() = assertEmptyBookmarksList() + + fun verifyBookmarkFavicon() = assertBookmarkFavicon() + + fun verifyBookmarkedURL(url: Uri) = assertBookmarkURL(url) + + fun verifyFolderTitle(title: String) = assertFolderTitle(title) + + fun verifyDeleteSnackBarText() = assertDeleteSnackBarText() + + fun verifyCopySnackBarText() = assertCopySnackBarText() + + fun verifyEditBookmarksView() = assertEditBookmarksView() + + fun verifyBoomarkNameEditBox() = assertBookmarkNameEditBox() + + fun verifyBookmarkURLEditBox() = assertBookmarkURLEditBox() + + fun verifyParentFolderSelector() = assertBookmarkFolderSelector() + + fun clickAddFolderButton() { + addFolderButton().click() + } + + fun addNewFolderName(name: String) { + addFolderTitleField().click() + addFolderTitleField().perform(typeText(name)) + } + + fun saveNewFolder() { + saveFolderButton().click() + } + + fun navigateUp() { + goBackButton().click() + } + class Transition { + val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + fun goBack(interact: LibraryRobot.() -> Unit): LibraryRobot.Transition { goBackButton().click() LibraryRobot().interact() return LibraryRobot.Transition() } + + fun openThreeDotMenu(interact: ThreeDotMenuBookmarks.() -> Unit): ThreeDotMenuBookmarks.Transition { + threeDotMenu().click() + + ThreeDotMenuBookmarks().interact() + return ThreeDotMenuBookmarks.Transition() + } } } @@ -31,11 +91,69 @@ fun bookmarksMenu(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transitio return BookmarksRobot.Transition() } +private fun goBackButton() = onView(withContentDescription("Navigate up")) + +private fun bookmarkFavicon() = onView(withId(R.id.favicon)) + +private fun bookmarkURL() = onView(withId(R.id.url)) + +private fun folderTitle() = onView(withId(R.id.title)) + +private fun addFolderButton() = onView(withId(R.id.add_bookmark_folder)) + +private fun addFolderTitleField() = onView(withId(R.id.bookmarkAddFolderTitleEdit)) + +private fun saveFolderButton() = onView(withId(R.id.confirm_add_folder_button)) + +private fun threeDotMenu() = onView(withId(R.id.overflow_menu)) + +private fun snackBarText() = onView(withId(R.id.snackbar_text)) + private fun assertBookmarksView() { - onView(allOf( + onView( + allOf( withText("Bookmarks"), - withParent(withId(R.id.navigationToolbar)))) - .check(ViewAssertions.matches(withEffectiveVisibility(Visibility.VISIBLE))) + withParent(withId(R.id.navigationToolbar)) + ) + ) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) } -private fun goBackButton() = onView(withContentDescription("Navigate up")) +private fun assertEmptyBookmarksList() = + onView(withId(R.id.bookmarks_empty_view)).check(matches(withText("No bookmarks here"))) + +private fun assertBookmarkFavicon() = bookmarkFavicon().check( + matches( + withEffectiveVisibility( + ViewMatchers.Visibility.VISIBLE + ) + ) +) + +private fun assertBookmarkURL(expectedURL: Uri) = bookmarkURL() + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + .check(matches(withText(containsString(expectedURL.toString())))) + +private fun assertFolderTitle(expectedTitle: String) = folderTitle() + .check(matches(withText(expectedTitle))) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + +private fun assertDeleteSnackBarText() = + snackBarText().check(matches(withText(containsString("Deleted")))) + +private fun assertCopySnackBarText() = snackBarText().check(matches(withText("URL copied"))) + +private fun assertEditBookmarksView() = onView(withText("Edit bookmark")) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + +private fun assertBookmarkNameEditBox() = + onView(withId(R.id.bookmarkNameEdit)) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + +private fun assertBookmarkFolderSelector() = + onView(withId(R.id.bookmarkFolderSelector)) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + +private fun assertBookmarkURLEditBox() = + onView(withId(R.id.bookmarkUrlEdit)) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt index 4721da97d..63fced716 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt @@ -8,7 +8,6 @@ package org.mozilla.fenix.ui.robots import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry @@ -27,7 +26,7 @@ class BrowserRobot { val redirectUrl = "https://support.mozilla.org/" mDevice.wait(Until.findObject(By.res("mozac_browser_toolbar_url_view")), TestAssetHelper.waitingTime) onView(withId(R.id.mozac_browser_toolbar_url_view)) - .check(matches(withText(containsString(redirectUrl)))) + .check(matches(withText(containsString(redirectUrl)))) } fun verifyWhatsNewURL() { @@ -36,7 +35,7 @@ class BrowserRobot { mDevice.wait(Until.findObject(By.res("mozac_browser_toolbar_url_view")), TestAssetHelper.waitingTime) onView(withId(R.id.mozac_browser_toolbar_url_view)) - .check(matches(withText(containsString(redirectUrl)))) + .check(matches(withText(containsString(redirectUrl)))) } /* Asserts that the text within DOM element with ID="testContent" has the given text, i.e. @@ -49,7 +48,7 @@ class BrowserRobot { fun verifyTabCounter(expectedText: String) { onView(withId(R.id.counter_text)) - .check((matches(withText(containsString(expectedText))))) + .check((matches(withText(containsString(expectedText))))) } class Transition { @@ -71,6 +70,16 @@ class BrowserRobot { HomeScreenRobot().interact() return HomeScreenRobot.Transition() } + + fun openQuickActionBar(interact: QuickActionBarRobot.() -> Unit): QuickActionBarRobot.Transition { + mDevice.wait(Until.gone(By.res("org.mozilla.fenix.nightly:id/quick_action_sheet")), + TestAssetHelper.waitingTime + ) + quickActionBarHandle().click() + + QuickActionBarRobot().interact() + return QuickActionBarRobot.Transition() + } } } @@ -79,8 +88,9 @@ fun browserScreen(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { return BrowserRobot.Transition() } -private fun dismissOnboardingButton() = onView(ViewMatchers.withId(R.id.close_onboarding)) +private fun dismissOnboardingButton() = onView(withId(R.id.close_onboarding)) fun navURLBar() = onView(withId(R.id.mozac_browser_toolbar_url_view)) private fun tabsCounter() = onView(withId(R.id.counter_box)) +private fun quickActionBarHandle() = onView(withId(R.id.quick_action_sheet_handle)) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/QuickActionBarRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/QuickActionBarRobot.kt new file mode 100644 index 000000000..16c8cd50b --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/QuickActionBarRobot.kt @@ -0,0 +1,81 @@ +/* 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.net.Uri +import androidx.test.espresso.Espresso.onView +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.espresso.matcher.ViewMatchers.withText +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import org.hamcrest.Matchers.allOf +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.click +import org.mozilla.fenix.helpers.isSelected + +/** + * Implementation of Robot Pattern for the quick action bar. + */ +class QuickActionBarRobot { + + fun verifyAddBookmarkButton() = assertAddBookmarkButton() + + fun verifyEditBookmarkButton() = assertEditBookmarkButton() + + fun clickBookmarkButton() { + addBookmarkButton().click() + } + + fun createBookmark(url: Uri) { + + navigationToolbar { + }.enterURLAndEnterToBrowser(url) { + }.openQuickActionBar { + clickBookmarkButton() + } + } + + class Transition { + val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + fun closeQuickActionBar(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + quickActionBarHandle().click() + + BrowserRobot().interact() + return BrowserRobot.Transition() + } + } +} + +fun quickActionBar(interact: QuickActionBarRobot.() -> Unit): QuickActionBarRobot.Transition { + QuickActionBarRobot().interact() + return QuickActionBarRobot.Transition() +} + +private fun quickActionBarHandle() = onView(withId(R.id.quick_action_sheet_handle)) + +private fun addBookmarkButton() = + onView(allOf(withId(R.id.quick_action_bookmark), isSelected(false))) + +private fun editBookmarkButton() = + onView(allOf(withId(R.id.quick_action_bookmark), isSelected(true))) + +private fun snackBarText() = onView(withId(R.id.snackbar_text)) + +private fun assertAddBookmarkButton() = addBookmarkButton() + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) + .check(matches(withText("Bookmark"))) + +private fun assertBookmarkSavedSnackBarText() = + snackBarText().check(matches(withText("Bookmark saved!"))) + +private fun assertEditBookmarkButton() = editBookmarkButton().check(matches(withEffectiveVisibility( + ViewMatchers.Visibility.VISIBLE))) + .check(matches(withText("Edit Bookmark"))) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuBookmarks.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuBookmarks.kt new file mode 100644 index 000000000..78076dcda --- /dev/null +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuBookmarks.kt @@ -0,0 +1,65 @@ +/* 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 androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.withText +import org.mozilla.fenix.helpers.click + +/** + * Implementation of Robot Pattern for the Bookmarks three dot menu. + */ +class ThreeDotMenuBookmarks { + + class Transition { + + fun clickEdit(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition { + editButton().click() + + BookmarksRobot().interact() + return BookmarksRobot.Transition() + } + + fun clickCopy(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition { + copyButton().click() + + BookmarksRobot().interact() + return BookmarksRobot.Transition() + } + + fun clickOpenInNewTab(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + openInNewTabButton().click() + + BrowserRobot().interact() + return BrowserRobot.Transition() + } + + fun clickOpenInPrivateTab(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { + openInPrivateTabButton().click() + + BrowserRobot().interact() + return BrowserRobot.Transition() + } + + fun clickDelete(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transition { + deleteButton().click() + + BookmarksRobot().interact() + return BookmarksRobot.Transition() + } + } +} + +private fun editButton() = onView(withText("Edit")) + +private fun copyButton() = onView(withText("Copy")) + +private fun openInNewTabButton() = onView(withText("Open in new tab")) + +private fun openInPrivateTabButton() = onView(withText("Open in private tab")) + +private fun deleteButton() = onView(withText("Delete"))