Merge remote-tracking branch 'origin/fenix/115.2.0' into iceraven
commit
dbcc0ad145
@ -1 +1 @@
|
||||
Subproject commit 53f2ba8b6bbd3e8a720e5b90b073eabcc854094f
|
||||
Subproject commit ac015fe2d5ef0700f93e40b62094f1cf79edcf86
|
@ -0,0 +1,381 @@
|
||||
/* 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.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
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.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
import org.mozilla.fenix.helpers.RetryTestRule
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||
import org.mozilla.fenix.helpers.TestHelper
|
||||
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
|
||||
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
|
||||
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.notificationShade
|
||||
|
||||
/**
|
||||
* Tests for verifying basic functionality of tabbed browsing
|
||||
*
|
||||
* Including:
|
||||
* - Opening a tab
|
||||
* - Opening a private tab
|
||||
* - Verifying tab list
|
||||
* - Closing all tabs
|
||||
* - Close tab
|
||||
* - Swipe to close tab (temporarily disabled)
|
||||
* - Undo close tab
|
||||
* - Close private tabs persistent notification
|
||||
* - Empty tab tray state
|
||||
* - Tab tray details
|
||||
* - Shortcut context menu navigation
|
||||
*/
|
||||
|
||||
class ComposeTabbedBrowsingTest {
|
||||
private lateinit var mDevice: UiDevice
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
|
||||
@get:Rule(order = 0)
|
||||
val composeTestRule =
|
||||
AndroidComposeTestRule(
|
||||
HomeActivityTestRule.withDefaultSettingsOverrides(
|
||||
tabsTrayRewriteEnabled = true,
|
||||
),
|
||||
) { it.activity }
|
||||
|
||||
@Rule(order = 1)
|
||||
@JvmField
|
||||
val retryTestRule = RetryTestRule(3)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
mockWebServer = MockWebServer().apply {
|
||||
dispatcher = AndroidAssetDispatcher()
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openNewTabTest() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
mDevice.waitForIdle()
|
||||
verifyTabCounter("1")
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNormalBrowsingButtonIsSelected()
|
||||
verifyExistingOpenTabs("Test_Page_1")
|
||||
closeTab()
|
||||
}
|
||||
homeScreen {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNoOpenTabsInNormalBrowsing()
|
||||
}.openNewTab {
|
||||
}.submitQuery(defaultWebPage.url.toString()) {
|
||||
mDevice.waitForIdle()
|
||||
verifyTabCounter("1")
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNormalBrowsingButtonIsSelected()
|
||||
verifyExistingOpenTabs("Test_Page_1")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun openNewPrivateTabTest() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
homeScreen {
|
||||
}.togglePrivateBrowsingMode()
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
mDevice.waitForIdle()
|
||||
verifyTabCounter("1")
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyPrivateTabsList()
|
||||
verifyPrivateBrowsingButtonIsSelected()
|
||||
}.toggleToNormalTabs {
|
||||
verifyNoOpenTabsInNormalBrowsing()
|
||||
}.toggleToPrivateTabs {
|
||||
verifyPrivateTabsList()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun closeAllTabsTest() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNormalTabsList()
|
||||
}.openThreeDotMenu {
|
||||
verifyCloseAllTabsButton()
|
||||
verifyShareAllTabsButton()
|
||||
verifySelectTabsButton()
|
||||
}.closeAllTabs {
|
||||
verifyTabCounter("0")
|
||||
}
|
||||
|
||||
// Repeat for Private Tabs
|
||||
homeScreen {
|
||||
}.togglePrivateBrowsingMode()
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyPrivateTabsList()
|
||||
}.openThreeDotMenu {
|
||||
verifyCloseAllTabsButton()
|
||||
}.closeAllTabs {
|
||||
verifyTabCounter("0")
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Being converted in: https://bugzilla.mozilla.org/show_bug.cgi?id=1832617")
|
||||
@Test
|
||||
fun closeTabTest() {
|
||||
// val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
//
|
||||
// navigationToolbar {
|
||||
// }.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
// }.openTabDrawer {
|
||||
// verifyExistingOpenTabs("Test_Page_1")
|
||||
// closeTab()
|
||||
// }
|
||||
// homeScreen {
|
||||
// verifyTabCounter("0")
|
||||
// }.openNavigationToolbar {
|
||||
// }.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
// }.openTabDrawer {
|
||||
// verifyExistingOpenTabs("Test_Page_1")
|
||||
// swipeTabRight("Test_Page_1")
|
||||
// }
|
||||
// homeScreen {
|
||||
// verifyTabCounter("0")
|
||||
// }.openNavigationToolbar {
|
||||
// }.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
// }.openTabDrawer {
|
||||
// verifyExistingOpenTabs("Test_Page_1")
|
||||
// swipeTabLeft("Test_Page_1")
|
||||
// }
|
||||
// homeScreen {
|
||||
// verifyTabCounter("0")
|
||||
// }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyUndoSnackBarTest() {
|
||||
// disabling these features because they interfere with the snackbar visibility
|
||||
composeTestRule.activityRule.applySettingsExceptions {
|
||||
it.isPocketEnabled = false
|
||||
it.isRecentTabsFeatureEnabled = false
|
||||
it.isRecentlyVisitedFeatureEnabled = false
|
||||
}
|
||||
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyExistingOpenTabs("Test_Page_1")
|
||||
closeTab()
|
||||
verifySnackBarText("Tab closed")
|
||||
clickSnackbarButton("UNDO")
|
||||
}
|
||||
|
||||
browserScreen {
|
||||
verifyTabCounter("1")
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyExistingOpenTabs("Test_Page_1")
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1829838")
|
||||
// Try converting in: https://bugzilla.mozilla.org/show_bug.cgi?id=1832609
|
||||
@Test
|
||||
fun closePrivateTabTest() {
|
||||
// val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
//
|
||||
// homeScreen { }.togglePrivateBrowsingMode()
|
||||
// navigationToolbar {
|
||||
// }.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
// }.openTabDrawer {
|
||||
// verifyExistingOpenTabs("Test_Page_1")
|
||||
// verifyCloseTabsButton("Test_Page_1")
|
||||
// closeTab()
|
||||
// }
|
||||
// homeScreen {
|
||||
// verifyTabCounter("0")
|
||||
// }.openNavigationToolbar {
|
||||
// }.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
// }.openTabDrawer {
|
||||
// verifyExistingOpenTabs("Test_Page_1")
|
||||
// swipeTabRight("Test_Page_1")
|
||||
// }
|
||||
// homeScreen {
|
||||
// verifyTabCounter("0")
|
||||
// }.openNavigationToolbar {
|
||||
// }.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
// }.openTabDrawer {
|
||||
// verifyExistingOpenTabs("Test_Page_1")
|
||||
// swipeTabLeft("Test_Page_1")
|
||||
// }
|
||||
// homeScreen {
|
||||
// verifyTabCounter("0")
|
||||
// }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyPrivateTabUndoSnackBarTest() {
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
homeScreen { }.togglePrivateBrowsingMode()
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyExistingOpenTabs("Test_Page_1")
|
||||
closeTab()
|
||||
TestHelper.verifySnackBarText("Private tab closed")
|
||||
TestHelper.clickSnackbarButton("UNDO")
|
||||
}
|
||||
|
||||
browserScreen {
|
||||
verifyTabCounter("1")
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyExistingOpenTabs("Test_Page_1")
|
||||
verifyPrivateBrowsingButtonIsSelected()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun closePrivateTabsNotificationTest() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
homeScreen {
|
||||
}.togglePrivateBrowsingMode()
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
mDevice.openNotification()
|
||||
}
|
||||
|
||||
notificationShade {
|
||||
verifyPrivateTabsNotification()
|
||||
}.clickClosePrivateTabsNotification {
|
||||
verifyHomeScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyTabTrayNotShowingStateHalfExpanded() {
|
||||
homeScreen {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNoOpenTabsInNormalBrowsing()
|
||||
// With no tabs opened the state should be STATE_COLLAPSED.
|
||||
verifyTabsTrayBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
// Need to ensure the halfExpandedRatio is very small so that when in STATE_HALF_EXPANDED
|
||||
// the tabTray will actually have a very small height (for a very short time) akin to being hidden.
|
||||
verifyMinusculeHalfExpandedRatio()
|
||||
}.clickTopBar {
|
||||
}.waitForTabTrayBehaviorToIdle {
|
||||
// Touching the topBar would normally advance the tabTray to the next state.
|
||||
// We don't want that.
|
||||
verifyTabsTrayBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
}.advanceToHalfExpandedState {
|
||||
}.waitForTabTrayBehaviorToIdle {
|
||||
// TabTray should not be displayed in STATE_HALF_EXPANDED.
|
||||
// When advancing to this state it should immediately be hidden.
|
||||
verifyTabTrayIsClosed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyEmptyTabTray() {
|
||||
homeScreen {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNormalBrowsingButtonIsSelected()
|
||||
verifyPrivateBrowsingButtonIsSelected(false)
|
||||
verifySyncedTabsButtonIsSelected(false)
|
||||
verifyNoOpenTabsInNormalBrowsing()
|
||||
verifyFab()
|
||||
verifyThreeDotButton()
|
||||
}.openThreeDotMenu {
|
||||
verifyTabSettingsButton()
|
||||
verifyRecentlyClosedTabsButton()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyOpenTabDetails() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openComposeTabDrawer(composeTestRule) {
|
||||
verifyNormalBrowsingButtonIsSelected()
|
||||
verifyPrivateBrowsingButtonIsSelected(isSelected = false)
|
||||
verifySyncedTabsButtonIsSelected(isSelected = false)
|
||||
verifyThreeDotButton()
|
||||
verifyNormalTabCounter()
|
||||
verifyNormalTabsList()
|
||||
verifyFab()
|
||||
verifyTabThumbnail()
|
||||
verifyExistingOpenTabs(defaultWebPage.title)
|
||||
verifyTabCloseButton(defaultWebPage.title)
|
||||
}.openTab(defaultWebPage.title) {
|
||||
verifyUrl(defaultWebPage.url.toString())
|
||||
verifyTabCounter("1")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyContextMenuShortcuts() {
|
||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabButtonShortcutsMenu {
|
||||
verifyTabButtonShortcutMenuItems()
|
||||
}.closeTabFromShortcutsMenu {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabButtonShortcutsMenu {
|
||||
}.openNewPrivateTabFromShortcutsMenu {
|
||||
verifyKeyboardVisible()
|
||||
verifyFocusedNavigationToolbar()
|
||||
// dismiss search dialog
|
||||
homeScreen { }.pressBack()
|
||||
verifyCommonMythsLink()
|
||||
verifyNavigationToolbar()
|
||||
}
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
||||
}.openTabButtonShortcutsMenu {
|
||||
}.openTabFromShortcutsMenu {
|
||||
verifyKeyboardVisible()
|
||||
verifyFocusedNavigationToolbar()
|
||||
// dismiss search dialog
|
||||
homeScreen { }.pressBack()
|
||||
verifyHomeWordmark()
|
||||
verifyNavigationToolbar()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,377 @@
|
||||
/* 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.view.View
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsNotSelected
|
||||
import androidx.compose.ui.test.assertIsSelected
|
||||
import androidx.compose.ui.test.hasAnyChild
|
||||
import androidx.compose.ui.test.hasTestTag
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.action.GeneralLocation
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import org.hamcrest.Matcher
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
|
||||
import org.mozilla.fenix.helpers.TestHelper.mDevice
|
||||
import org.mozilla.fenix.helpers.clickAtLocationInView
|
||||
import org.mozilla.fenix.helpers.idlingresource.BottomSheetBehaviorStateIdlingResource
|
||||
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorHalfExpandedMaxRatioMatcher
|
||||
import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorStateMatcher
|
||||
import org.mozilla.fenix.tabstray.TabsTrayTestTag
|
||||
|
||||
/**
|
||||
* Implementation of Robot Pattern for the Tabs Tray.
|
||||
*/
|
||||
class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTestRule) {
|
||||
|
||||
fun verifyNormalBrowsingButtonIsSelected(isSelected: Boolean = true) {
|
||||
if (isSelected) {
|
||||
composeTestRule.normalBrowsingButton().assertIsSelected()
|
||||
} else {
|
||||
composeTestRule.normalBrowsingButton().assertIsNotSelected()
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyPrivateBrowsingButtonIsSelected(isSelected: Boolean = true) {
|
||||
if (isSelected) {
|
||||
composeTestRule.privateBrowsingButton().assertIsSelected()
|
||||
} else {
|
||||
composeTestRule.privateBrowsingButton().assertIsNotSelected()
|
||||
}
|
||||
}
|
||||
|
||||
fun verifySyncedTabsButtonIsSelected(isSelected: Boolean = true) {
|
||||
if (isSelected) {
|
||||
composeTestRule.syncedTabsButton().assertIsSelected()
|
||||
} else {
|
||||
composeTestRule.syncedTabsButton().assertIsNotSelected()
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyExistingOpenTabs(vararg titles: String) {
|
||||
titles.forEach { title ->
|
||||
composeTestRule.tabItem(title).assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyNormalTabsList() {
|
||||
composeTestRule.normalTabsList().assertExists()
|
||||
}
|
||||
|
||||
fun verifyPrivateTabsList() {
|
||||
composeTestRule.privateTabsList().assertExists()
|
||||
}
|
||||
|
||||
fun verifySyncedTabsList() {
|
||||
composeTestRule.syncedTabsList().assertExists()
|
||||
}
|
||||
|
||||
fun verifyNoOpenTabsInNormalBrowsing() {
|
||||
composeTestRule.emptyNormalTabsList().assertExists()
|
||||
}
|
||||
|
||||
fun verifyNoOpenTabsInPrivateBrowsing() {
|
||||
composeTestRule.emptyPrivateTabsList().assertExists()
|
||||
}
|
||||
|
||||
fun verifyAccountSettingsButton() {
|
||||
composeTestRule.dropdownMenuItemAccountSettings().assertExists()
|
||||
}
|
||||
|
||||
fun verifyCloseAllTabsButton() {
|
||||
composeTestRule.dropdownMenuItemCloseAllTabs().assertExists()
|
||||
}
|
||||
|
||||
fun verifySelectTabsButton() {
|
||||
composeTestRule.dropdownMenuItemSelectTabs().assertExists()
|
||||
}
|
||||
|
||||
fun verifyShareAllTabsButton() {
|
||||
composeTestRule.dropdownMenuItemShareAllTabs().assertExists()
|
||||
}
|
||||
|
||||
fun verifyRecentlyClosedTabsButton() {
|
||||
composeTestRule.dropdownMenuItemRecentlyClosedTabs().assertExists()
|
||||
}
|
||||
|
||||
fun verifyTabSettingsButton() {
|
||||
composeTestRule.dropdownMenuItemTabSettings().assertExists()
|
||||
}
|
||||
|
||||
fun verifyThreeDotButton() {
|
||||
composeTestRule.threeDotButton().assertExists()
|
||||
}
|
||||
|
||||
fun verifyFab() {
|
||||
composeTestRule.tabsTrayFab().assertExists()
|
||||
}
|
||||
|
||||
fun verifyNormalTabCounter() {
|
||||
composeTestRule.normalTabsCounter().assertExists()
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a tab's thumbnail when there is only one tab open.
|
||||
*/
|
||||
fun verifyTabThumbnail() {
|
||||
composeTestRule.tabThumbnail().assertExists()
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a tab with [title] has a close button.
|
||||
*/
|
||||
fun verifyTabCloseButton(title: String) {
|
||||
composeTestRule.tabItem(title).assert(
|
||||
hasAnyChild(
|
||||
hasTestTag(TabsTrayTestTag.tabItemClose),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun verifyTabsTrayBehaviorState(expectedState: Int) {
|
||||
tabsTrayView().check(ViewAssertions.matches(BottomSheetBehaviorStateMatcher(expectedState)))
|
||||
}
|
||||
|
||||
fun verifyMinusculeHalfExpandedRatio() {
|
||||
tabsTrayView().check(ViewAssertions.matches(BottomSheetBehaviorHalfExpandedMaxRatioMatcher(0.001f)))
|
||||
}
|
||||
|
||||
fun verifyTabTrayIsClosed() {
|
||||
composeTestRule.tabsTray().assertDoesNotExist()
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a tab when there is only one tab open.
|
||||
*/
|
||||
fun closeTab() {
|
||||
composeTestRule.closeTabButton().performClick()
|
||||
}
|
||||
|
||||
class Transition(private val composeTestRule: HomeActivityComposeTestRule) {
|
||||
|
||||
fun openNewTab(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
|
||||
mDevice.waitForIdle()
|
||||
|
||||
composeTestRule.tabsTrayFab().performClick()
|
||||
SearchRobot().interact()
|
||||
return SearchRobot.Transition()
|
||||
}
|
||||
|
||||
fun toggleToNormalTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
|
||||
composeTestRule.normalBrowsingButton().performClick()
|
||||
ComposeTabDrawerRobot(composeTestRule).interact()
|
||||
return Transition(composeTestRule)
|
||||
}
|
||||
|
||||
fun toggleToPrivateTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
|
||||
composeTestRule.privateBrowsingButton().performClick()
|
||||
ComposeTabDrawerRobot(composeTestRule).interact()
|
||||
return Transition(composeTestRule)
|
||||
}
|
||||
|
||||
fun openThreeDotMenu(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
|
||||
composeTestRule.threeDotButton().performClick()
|
||||
ComposeTabDrawerRobot(composeTestRule).interact()
|
||||
return Transition(composeTestRule)
|
||||
}
|
||||
|
||||
fun closeAllTabs(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
composeTestRule.dropdownMenuItemCloseAllTabs().performClick()
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
}
|
||||
|
||||
fun openTab(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
|
||||
composeTestRule.tabItem(title)
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
BrowserRobot().interact()
|
||||
return BrowserRobot.Transition()
|
||||
}
|
||||
|
||||
fun clickTopBar(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
|
||||
// The topBar contains other views.
|
||||
// Don't do the default click in the middle, rather click in some free space - top right.
|
||||
Espresso.onView(ViewMatchers.withId(R.id.topBar)).clickAtLocationInView(GeneralLocation.TOP_RIGHT)
|
||||
ComposeTabDrawerRobot(composeTestRule).interact()
|
||||
return Transition(composeTestRule)
|
||||
}
|
||||
|
||||
fun waitForTabTrayBehaviorToIdle(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
|
||||
// Need to get the behavior of tab_wrapper and wait for that to idle.
|
||||
var behavior: BottomSheetBehavior<*>? = null
|
||||
|
||||
// Null check here since it's possible that the view is already animated away from the screen.
|
||||
tabsTrayView()?.perform(
|
||||
object : ViewAction {
|
||||
override fun getDescription(): String {
|
||||
return "Postpone actions to after the BottomSheetBehavior has settled"
|
||||
}
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return ViewMatchers.isAssignableFrom(View::class.java)
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
behavior = BottomSheetBehavior.from(view!!)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
behavior?.let {
|
||||
runWithIdleRes(BottomSheetBehaviorStateIdlingResource(it)) {
|
||||
ComposeTabDrawerRobot(composeTestRule).interact()
|
||||
}
|
||||
}
|
||||
|
||||
return Transition(composeTestRule)
|
||||
}
|
||||
|
||||
fun advanceToHalfExpandedState(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
|
||||
tabsTrayView().perform(
|
||||
object : ViewAction {
|
||||
override fun getDescription(): String {
|
||||
return "Advance a BottomSheetBehavior to STATE_HALF_EXPANDED"
|
||||
}
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return ViewMatchers.isAssignableFrom(View::class.java)
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
val behavior = BottomSheetBehavior.from(view!!)
|
||||
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||
}
|
||||
},
|
||||
)
|
||||
ComposeTabDrawerRobot(composeTestRule).interact()
|
||||
return Transition(composeTestRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the root [View] that wraps the Tabs Tray.
|
||||
*/
|
||||
private fun tabsTrayView() = Espresso.onView(ViewMatchers.withId(R.id.tabs_tray_root))
|
||||
|
||||
/**
|
||||
* Obtains the root Tabs Tray.
|
||||
*/
|
||||
private fun ComposeTestRule.tabsTray() = onNodeWithTag(TabsTrayTestTag.tabsTray)
|
||||
|
||||
/**
|
||||
* Obtains the Tabs Tray FAB.
|
||||
*/
|
||||
private fun ComposeTestRule.tabsTrayFab() = onNodeWithTag(TabsTrayTestTag.fab)
|
||||
|
||||
/**
|
||||
* Obtains the normal browsing page button of the Tabs Tray banner.
|
||||
*/
|
||||
private fun ComposeTestRule.normalBrowsingButton() = onNodeWithTag(TabsTrayTestTag.normalTabsPageButton)
|
||||
|
||||
/**
|
||||
* Obtains the private browsing page button of the Tabs Tray banner.
|
||||
*/
|
||||
private fun ComposeTestRule.privateBrowsingButton() = onNodeWithTag(TabsTrayTestTag.privateTabsPageButton)
|
||||
|
||||
/**
|
||||
* Obtains the synced tabs page button of the Tabs Tray banner.
|
||||
*/
|
||||
private fun ComposeTestRule.syncedTabsButton() = onNodeWithTag(TabsTrayTestTag.syncedTabsPageButton)
|
||||
|
||||
/**
|
||||
* Obtains the normal tabs list.
|
||||
*/
|
||||
private fun ComposeTestRule.normalTabsList() = onNodeWithTag(TabsTrayTestTag.normalTabsList)
|
||||
|
||||
/**
|
||||
* Obtains the private tabs list.
|
||||
*/
|
||||
private fun ComposeTestRule.privateTabsList() = onNodeWithTag(TabsTrayTestTag.privateTabsList)
|
||||
|
||||
/**
|
||||
* Obtains the synced tabs list.
|
||||
*/
|
||||
private fun ComposeTestRule.syncedTabsList() = onNodeWithTag(TabsTrayTestTag.syncedTabsList)
|
||||
|
||||
/**
|
||||
* Obtains the empty normal tabs list.
|
||||
*/
|
||||
private fun ComposeTestRule.emptyNormalTabsList() = onNodeWithTag(TabsTrayTestTag.emptyNormalTabsList)
|
||||
|
||||
/**
|
||||
* Obtains the empty private tabs list.
|
||||
*/
|
||||
private fun ComposeTestRule.emptyPrivateTabsList() = onNodeWithTag(TabsTrayTestTag.emptyPrivateTabsList)
|
||||
|
||||
/**
|
||||
* Obtains the tab with the provided [title]
|
||||
*/
|
||||
private fun ComposeTestRule.tabItem(title: String) = onNodeWithText(title)
|
||||
|
||||
/**
|
||||
* Obtains an open tab's close button when there's only one tab open.
|
||||
*/
|
||||
private fun ComposeTestRule.closeTabButton() = onNodeWithTag(TabsTrayTestTag.tabItemClose)
|
||||
|
||||
/**
|
||||
* Obtains an open tab's thumbnail when there's only one tab open.
|
||||
*/
|
||||
private fun ComposeTestRule.tabThumbnail() = onNodeWithTag(TabsTrayTestTag.tabItemThumbnail)
|
||||
|
||||
/**
|
||||
* Obtains the three dot button in the Tabs Tray banner.
|
||||
*/
|
||||
private fun ComposeTestRule.threeDotButton() = onNodeWithTag(TabsTrayTestTag.threeDotButton)
|
||||
|
||||
/**
|
||||
* Obtains the dropdown menu item to access account settings.
|
||||
*/
|
||||
private fun ComposeTestRule.dropdownMenuItemAccountSettings() = onNodeWithTag(TabsTrayTestTag.accountSettings)
|
||||
|
||||
/**
|
||||
* Obtains the dropdown menu item to close all tabs.
|
||||
*/
|
||||
private fun ComposeTestRule.dropdownMenuItemCloseAllTabs() = onNodeWithTag(TabsTrayTestTag.closeAllTabs)
|
||||
|
||||
/**
|
||||
* Obtains the dropdown menu item to access recently closed tabs.
|
||||
*/
|
||||
private fun ComposeTestRule.dropdownMenuItemRecentlyClosedTabs() = onNodeWithTag(TabsTrayTestTag.recentlyClosedTabs)
|
||||
|
||||
/**
|
||||
* Obtains the dropdown menu item to select tabs.
|
||||
*/
|
||||
private fun ComposeTestRule.dropdownMenuItemSelectTabs() = onNodeWithTag(TabsTrayTestTag.selectTabs)
|
||||
|
||||
/**
|
||||
* Obtains the dropdown menu item to share all tabs.
|
||||
*/
|
||||
private fun ComposeTestRule.dropdownMenuItemShareAllTabs() = onNodeWithTag(TabsTrayTestTag.shareAllTabs)
|
||||
|
||||
/**
|
||||
* Obtains the dropdown menu item to access tab settings.
|
||||
*/
|
||||
private fun ComposeTestRule.dropdownMenuItemTabSettings() = onNodeWithTag(TabsTrayTestTag.tabSettings)
|
||||
|
||||
/**
|
||||
* Obtains the normal tabs counter.
|
||||
*/
|
||||
private fun ComposeTestRule.normalTabsCounter() = onNodeWithTag(TabsTrayTestTag.normalTabsCounter)
|
@ -0,0 +1,119 @@
|
||||
/* 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.components.accounts
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import mozilla.components.concept.sync.FxAEntryPoint
|
||||
|
||||
/**
|
||||
* Fenix implementation of [FxAEntryPoint].
|
||||
*/
|
||||
enum class FenixFxAEntryPoint(override val entryName: String) : FxAEntryPoint, Parcelable {
|
||||
/**
|
||||
* New user onboarding, the user accessed the sign in through new user onboarding
|
||||
*/
|
||||
NewUserOnboarding("newuser-onboarding"),
|
||||
|
||||
/**
|
||||
* Manual sign in from the onboarding menu
|
||||
*/
|
||||
OnboardingManualSignIn("onboarding-manual-sign-in"),
|
||||
|
||||
/**
|
||||
* User used a deep link to get to firefox accounts authentication
|
||||
*/
|
||||
DeepLink("deep-link"),
|
||||
|
||||
/**
|
||||
* Authenticating from the browser's toolbar
|
||||
*/
|
||||
BrowserToolbar("browser-toolbar"),
|
||||
|
||||
/**
|
||||
* Authenticating from the home menu (the hamburger menu)
|
||||
*/
|
||||
HomeMenu("home-menu"),
|
||||
|
||||
/**
|
||||
* Authenticating in the bookmark view, when getting attempting to get synced
|
||||
* bookmarks
|
||||
*/
|
||||
BookmarkView("bookmark-view"),
|
||||
|
||||
/**
|
||||
* Authenticating from the homepage onboarding dialog
|
||||
*/
|
||||
HomeOnboardingDialog("home-onboarding-dialog"),
|
||||
|
||||
/**
|
||||
* Authenticating from the settings menu
|
||||
*/
|
||||
SettingsMenu("settings-menu"),
|
||||
|
||||
/**
|
||||
* Authenticating from the autofill settings to enable synced
|
||||
* credit cards/addresses
|
||||
*/
|
||||
AutofillSetting("autofill-setting"),
|
||||
|
||||
/**
|
||||
* Authenticating from the saved logins menu to enable synced
|
||||
* logins
|
||||
*/
|
||||
SavedLogins("saved-logins"),
|
||||
|
||||
/**
|
||||
* Authenticating from the Share menu to enable send tab
|
||||
*/
|
||||
ShareMenu("share-menu"),
|
||||
|
||||
/**
|
||||
* Authenticating as a navigation interaction
|
||||
*/
|
||||
NavigationInteraction("navigation-interaction"),
|
||||
|
||||
/**
|
||||
* Authenticating from the synced tabs menu to enable synced tabs
|
||||
*/
|
||||
SyncedTabsMenu("synced-tabs-menu"),
|
||||
|
||||
/**
|
||||
* When serializing the value after navigating, the result is a nullable value. We have this
|
||||
* "unknown" as a default value in the odd chance that we receive an [entryName] is not part of this enum.
|
||||
*
|
||||
* Do not use within app code.
|
||||
*/
|
||||
Unknown("unknown"),
|
||||
;
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(entryName)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Override implementation of the [Parcelable.Creator].
|
||||
*
|
||||
* Implementation notes: We need to manually create an override for [Parcelable] instead of using the annotation,
|
||||
* because this is an enum implementation of the API and the auto-generated code does not know how to choose a
|
||||
* particular enum value in [Parcelable.Creator.createFromParcel].
|
||||
* We also introduce an [FxAEntryPoint.Unknown] value to use as a default return value in the off-chance that we
|
||||
* cannot safely serialize the enum value from the navigation library; this should be a rare case, if any.
|
||||
*/
|
||||
companion object CREATOR : Parcelable.Creator<FenixFxAEntryPoint> {
|
||||
override fun createFromParcel(parcel: Parcel): FenixFxAEntryPoint {
|
||||
val parcelEntryName = parcel.readString() ?: Unknown
|
||||
return FenixFxAEntryPoint.values().first { it.entryName == parcelEntryName }
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<FenixFxAEntryPoint?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/* 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.components.metrics.clientdeduplication
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
|
||||
/**
|
||||
* This observer allows us to mimic roughly the same schedule as the Glean SDK baseline ping.
|
||||
* https://github.com/mozilla/glean/blob/main/glean-core/android/src/main/java/mozilla/telemetry/glean/scheduler/GleanLifecycleObserver.kt
|
||||
*/
|
||||
class ClientDeduplicationLifecycleObserver(context: Context) : LifecycleEventObserver {
|
||||
private val clientDeduplicationPing = ClientDeduplicationPing(context)
|
||||
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
// The ping will only be sent whenever the Nimbus feature is enabled.
|
||||
if (FxNimbus.features.clientDeduplication.value().enabled) {
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_STOP -> {
|
||||
clientDeduplicationPing.triggerPing(active = false)
|
||||
}
|
||||
Lifecycle.Event.ON_START -> {
|
||||
// We use ON_START here because we don't want to incorrectly count metrics in
|
||||
// ON_RESUME as pause/resume can happen when interacting with things like the
|
||||
// navigation shade which could lead to incorrectly recording the start of a
|
||||
// duration, etc.
|
||||
//
|
||||
// https://developer.android.com/reference/android/app/Activity.html#onStart()
|
||||
|
||||
clientDeduplicationPing.triggerPing(active = true)
|
||||
}
|
||||
else -> {
|
||||
// For other lifecycle events, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/* 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.components.metrics.clientdeduplication
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mozilla.fenix.GleanMetrics.ClientDeduplication
|
||||
import org.mozilla.fenix.GleanMetrics.Pings
|
||||
import org.mozilla.fenix.components.metrics.MetricsUtils.getHashedIdentifier
|
||||
|
||||
/**
|
||||
* Class to help construct and send the `clientDeduplication` ping.
|
||||
*/
|
||||
class ClientDeduplicationPing(private val context: Context) {
|
||||
private val customHashingSalt = "bug-1813195-02-2023"
|
||||
|
||||
/**
|
||||
* Fills the metrics and triggers the 'clientDeduplication' ping.
|
||||
*/
|
||||
internal fun triggerPing(active: Boolean) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val hashedId = getHashedIdentifier(context, customHashingSalt)
|
||||
|
||||
// Record the metrics.
|
||||
if (hashedId != null) {
|
||||
// We have a valid, hashed Google Advertising ID.
|
||||
ClientDeduplication.hashedGaid.set(hashedId)
|
||||
ClientDeduplication.validAdvertisingId.set(true)
|
||||
} else {
|
||||
ClientDeduplication.validAdvertisingId.set(false)
|
||||
}
|
||||
ClientDeduplication.experimentTimeframe.set(customHashingSalt)
|
||||
|
||||
// Set the reason based on if the app is foregrounded or backgrounded.
|
||||
val reason = if (active) {
|
||||
Pings.clientDeduplicationReasonCodes.active
|
||||
} else {
|
||||
Pings.clientDeduplicationReasonCodes.inactive
|
||||
}
|
||||
|
||||
// Submit the ping.
|
||||
Pings.clientDeduplication.submit(reason)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/* 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.home.intent
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.navigation.NavController
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.NavGraphDirections
|
||||
import org.mozilla.fenix.ext.nav
|
||||
|
||||
/**
|
||||
* When the open password manager shortcut is tapped, Fenix should open to the password and login fragment.
|
||||
*/
|
||||
class OpenPasswordManagerIntentProcessor : HomeIntentProcessor {
|
||||
|
||||
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||
return if (intent.extras?.getBoolean(HomeActivity.OPEN_PASSWORD_MANAGER) == true) {
|
||||
out.removeExtra(HomeActivity.OPEN_PASSWORD_MANAGER)
|
||||
|
||||
val directions = NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
|
||||
navController.nav(null, directions)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue