Bug 1846707 - Fix closeAllTabsTest UI test
[gecko.git] / mobile / android / fenix / app / src / androidTest / java / org / mozilla / fenix / ui / robots / ComposeTabDrawerRobot.kt
blobd5af7dd0f835cebd264c08edc483187650883ee5
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 @file:Suppress("TooManyFunctions")
7 package org.mozilla.fenix.ui.robots
9 import android.util.Log
10 import android.view.View
11 import androidx.compose.ui.semantics.SemanticsActions
12 import androidx.compose.ui.test.ExperimentalTestApi
13 import androidx.compose.ui.test.assert
14 import androidx.compose.ui.test.assertIsNotSelected
15 import androidx.compose.ui.test.assertIsSelected
16 import androidx.compose.ui.test.filter
17 import androidx.compose.ui.test.hasAnyChild
18 import androidx.compose.ui.test.hasContentDescription
19 import androidx.compose.ui.test.hasParent
20 import androidx.compose.ui.test.hasTestTag
21 import androidx.compose.ui.test.hasText
22 import androidx.compose.ui.test.junit4.ComposeTestRule
23 import androidx.compose.ui.test.longClick
24 import androidx.compose.ui.test.onAllNodesWithTag
25 import androidx.compose.ui.test.onChildAt
26 import androidx.compose.ui.test.onChildren
27 import androidx.compose.ui.test.onFirst
28 import androidx.compose.ui.test.onNodeWithContentDescription
29 import androidx.compose.ui.test.onNodeWithTag
30 import androidx.compose.ui.test.performClick
31 import androidx.compose.ui.test.performScrollTo
32 import androidx.compose.ui.test.performSemanticsAction
33 import androidx.compose.ui.test.performTouchInput
34 import androidx.compose.ui.test.swipeLeft
35 import androidx.compose.ui.test.swipeRight
36 import androidx.test.espresso.Espresso
37 import androidx.test.espresso.UiController
38 import androidx.test.espresso.ViewAction
39 import androidx.test.espresso.action.GeneralLocation
40 import androidx.test.espresso.assertion.ViewAssertions
41 import androidx.test.espresso.matcher.ViewMatchers
42 import com.google.android.material.bottomsheet.BottomSheetBehavior
43 import org.hamcrest.Matcher
44 import org.mozilla.fenix.R
45 import org.mozilla.fenix.helpers.Constants
46 import org.mozilla.fenix.helpers.Constants.TAG
47 import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
48 import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
49 import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
50 import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
51 import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
52 import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
53 import org.mozilla.fenix.helpers.TestHelper.mDevice
54 import org.mozilla.fenix.helpers.clickAtLocationInView
55 import org.mozilla.fenix.helpers.idlingresource.BottomSheetBehaviorStateIdlingResource
56 import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorHalfExpandedMaxRatioMatcher
57 import org.mozilla.fenix.helpers.matchers.BottomSheetBehaviorStateMatcher
58 import org.mozilla.fenix.tabstray.TabsTrayTestTag
60 /**
61  * Implementation of Robot Pattern for the Tabs Tray.
62  */
63 class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTestRule) {
65     fun verifyNormalBrowsingButtonIsSelected(isSelected: Boolean = true) {
66         if (isSelected) {
67             Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Trying to verify that the normal browsing button is selected")
68             composeTestRule.normalBrowsingButton().assertIsSelected()
69             Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Verified that the normal browsing button is selected")
70         } else {
71             Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Trying to verify that the normal browsing button is not selected")
72             composeTestRule.normalBrowsingButton().assertIsNotSelected()
73             Log.i(TAG, "verifyNormalBrowsingButtonIsSelected: Verified that the normal browsing button is not selected")
74         }
75     }
77     fun verifyPrivateBrowsingButtonIsSelected(isSelected: Boolean = true) {
78         if (isSelected) {
79             Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Trying to verify that the private browsing button is selected")
80             composeTestRule.privateBrowsingButton().assertIsSelected()
81             Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Verified that the private browsing button is selected")
82         } else {
83             Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Trying to verify that the private browsing button is not selected")
84             composeTestRule.privateBrowsingButton().assertIsNotSelected()
85             Log.i(TAG, "verifyPrivateBrowsingButtonIsSelected: Verified that the private browsing button is not selected")
86         }
87     }
89     fun verifySyncedTabsButtonIsSelected(isSelected: Boolean = true) {
90         if (isSelected) {
91             Log.i(TAG, "verifySyncedTabsButtonIsSelected: Trying to verify that the synced tabs button is selected")
92             composeTestRule.syncedTabsButton().assertIsSelected()
93             Log.i(TAG, "verifySyncedTabsButtonIsSelected: Verified that the synced tabs button is selected")
94         } else {
95             Log.i(TAG, "verifySyncedTabsButtonIsSelected: Trying to verify that the synced tabs button is not selected")
96             composeTestRule.syncedTabsButton().assertIsNotSelected()
97             Log.i(TAG, "verifySyncedTabsButtonIsSelected: Verified that the synced tabs button is not selected")
98         }
99     }
101     fun verifySyncedTabsListWhenUserIsNotSignedIn() {
102         verifySyncedTabsList()
103         assertUIObjectExists(
104             itemContainingText(getStringResource(R.string.synced_tabs_sign_in_message)),
105             itemContainingText(getStringResource(R.string.sync_sign_in)),
106             itemContainingText(getStringResource(R.string.tab_drawer_fab_sync)),
107         )
108     }
110     fun verifyExistingOpenTabs(vararg titles: String) {
111         titles.forEach { title ->
112             Log.i(TAG, "verifyExistingOpenTabs: Waiting for $waitingTime ms for tab with title: $title to exist")
113             itemContainingText(title).waitForExists(waitingTime)
114             Log.i(TAG, "verifyExistingOpenTabs: Waited for $waitingTime ms for tab with title: $title to exist")
115             Log.i(TAG, "verifyExistingOpenTabs: Trying to verify that the open tab with title: $title exists")
116             composeTestRule.tabItem(title).assertExists()
117             Log.i(TAG, "verifyExistingOpenTabs: Verified that the open tab with title: $title exists")
118         }
119     }
121     fun verifyOpenTabsOrder(title: String, position: Int) {
122         Log.i(TAG, "verifyOpenTabsOrder: Trying to verify that the open tab at position: $position has title: $title")
123         composeTestRule.normalTabsList()
124             .onChildAt(position - 1)
125             .assert(hasTestTag(TabsTrayTestTag.tabItemRoot))
126             .assert(hasAnyChild(hasText(title)))
127         Log.i(TAG, "verifyOpenTabsOrder: Verified that the open tab at position: $position has title: $title")
128     }
130     fun verifyNoExistingOpenTabs(vararg titles: String) {
131         titles.forEach { title ->
132             assertUIObjectExists(
133                 itemContainingText(title),
134                 exists = false,
135             )
136         }
137     }
139     @OptIn(ExperimentalTestApi::class)
140     fun verifyNormalTabsList() {
141         composeTestRule.waitUntilDoesNotExist(hasTestTag("tabstray.tabList.normal.empty"), waitingTime)
142         Log.i(TAG, "verifyNormalTabsList: Trying to verify that the normal tabs list exists")
143         composeTestRule.normalTabsList().assertExists()
144         Log.i(TAG, "verifyNormalTabsList: Verified that the normal tabs list exists")
145     }
147     fun verifyPrivateTabsList() {
148         Log.i(TAG, "verifyPrivateTabsList: Trying to verify that the private tabs list exists")
149         composeTestRule.privateTabsList().assertExists()
150         Log.i(TAG, "verifyPrivateTabsList: Verified that the private tabs list exists")
151     }
153     fun verifySyncedTabsList() {
154         Log.i(TAG, "verifySyncedTabsList: Trying to verify that the synced tabs list exists")
155         composeTestRule.syncedTabsList().assertExists()
156         Log.i(TAG, "verifySyncedTabsList: Verified that the synced tabs list exists")
157     }
159     fun verifyNoOpenTabsInNormalBrowsing() {
160         Log.i(TAG, "verifyNoOpenTabsInNormalBrowsing: Trying to verify that the empty normal tabs list exists")
161         composeTestRule.emptyNormalTabsList().assertExists()
162         Log.i(TAG, "verifyNoOpenTabsInNormalBrowsing: Verified that the empty normal tabs list exists")
163     }
165     fun verifyNoOpenTabsInPrivateBrowsing() {
166         Log.i(TAG, "verifyNoOpenTabsInPrivateBrowsing: Trying to verify that the empty private tabs list exists")
167         composeTestRule.emptyPrivateTabsList().assertExists()
168         Log.i(TAG, "verifyNoOpenTabsInPrivateBrowsing: Verified that the empty private tabs list exists")
169     }
171     fun verifyAccountSettingsButton() {
172         Log.i(TAG, "verifyAccountSettingsButton: Trying to verify that the \"Account settings\" menu button exists")
173         composeTestRule.dropdownMenuItemAccountSettings().assertExists()
174         Log.i(TAG, "verifyAccountSettingsButton: Verified that the \"Account settings\" menu button exists")
175     }
177     fun verifyCloseAllTabsButton() {
178         Log.i(TAG, "verifyCloseAllTabsButton: Trying to verify that the \"Close all tabs\" menu button exists")
179         composeTestRule.dropdownMenuItemCloseAllTabs().assertExists()
180         Log.i(TAG, "verifyCloseAllTabsButton: Verified that the \"Close all tabs\" menu button exists")
181     }
183     fun verifySelectTabsButton() {
184         Log.i(TAG, "verifySelectTabsButton: Trying to verify that the \"Select tabs\" menu button exists")
185         composeTestRule.dropdownMenuItemSelectTabs().assertExists()
186         Log.i(TAG, "verifySelectTabsButton: Verified that the \"Select tabs\" menu button exists")
187     }
189     fun verifyShareAllTabsButton() {
190         Log.i(TAG, "verifyShareAllTabsButton: Trying to verify that the \"Share all tabs\" menu button exists")
191         composeTestRule.dropdownMenuItemShareAllTabs().assertExists()
192         Log.i(TAG, "verifyShareAllTabsButton: Verified that the \"Share all tabs\" menu button exists")
193     }
195     fun verifyRecentlyClosedTabsButton() {
196         Log.i(TAG, "verifyRecentlyClosedTabsButton: Trying to verify that the \"Recently closed tabs\" menu button exists")
197         composeTestRule.dropdownMenuItemRecentlyClosedTabs().assertExists()
198         Log.i(TAG, "verifyRecentlyClosedTabsButton: Verified that the \"Recently closed tabs\" menu button exists")
199     }
201     fun verifyTabSettingsButton() {
202         Log.i(TAG, "verifyTabSettingsButton: Trying to verify that the \"Tab settings\" menu button exists")
203         composeTestRule.dropdownMenuItemTabSettings().assertExists()
204         Log.i(TAG, "verifyTabSettingsButton: Verified that the \"Tab settings\" menu button exists")
205     }
207     fun verifyThreeDotButton() {
208         Log.i(TAG, "verifyThreeDotButton: Trying to verify that the three dot button exists")
209         composeTestRule.threeDotButton().assertExists()
210         Log.i(TAG, "verifyThreeDotButton: Verified that the three dot button exists")
211     }
213     fun verifyFab() {
214         Log.i(TAG, "verifyFab: Trying to verify that the new tab FAB button exists")
215         composeTestRule.tabsTrayFab().assertExists()
216         Log.i(TAG, "verifyFab: Verified that the new tab FAB button exists")
217     }
219     fun verifyNormalTabCounter() {
220         Log.i(TAG, "verifyNormalTabCounter: Trying to verify that the normal tabs list counter exists")
221         composeTestRule.normalTabsCounter().assertExists()
222         Log.i(TAG, "verifyNormalTabCounter: Verified that the normal tabs list counter exists")
223     }
225     /**
226      * Verifies a tab's thumbnail when there is only one tab open.
227      */
228     fun verifyTabThumbnail() {
229         Log.i(TAG, "verifyTabThumbnail: Trying to verify that the tab thumbnail exists")
230         composeTestRule.tabThumbnail().assertExists()
231         Log.i(TAG, "verifyTabThumbnail: Verified that the tab thumbnail exists")
232     }
234     /**
235      * Verifies a tab's close button when there is only one tab open.
236      */
237     fun verifyTabCloseButton() {
238         Log.i(TAG, "verifyTabCloseButton: Trying to verify that the close tab button exists")
239         composeTestRule.closeTabButton().assertExists()
240         Log.i(TAG, "verifyTabCloseButton: Verified that the close tab button exists")
241     }
243     fun verifyTabsTrayBehaviorState(expectedState: Int) {
244         Log.i(TAG, "verifyTabsTrayBehaviorState: Trying to verify that the tabs tray state matches: $expectedState")
245         tabsTrayView().check(ViewAssertions.matches(BottomSheetBehaviorStateMatcher(expectedState)))
246         Log.i(TAG, "verifyTabsTrayBehaviorState: Verified that the tabs tray state matches: $expectedState")
247     }
249     fun verifyMinusculeHalfExpandedRatio() {
250         Log.i(TAG, "verifyMinusculeHalfExpandedRatio: Trying to verify the tabs tray half expanded ratio")
251         tabsTrayView().check(ViewAssertions.matches(BottomSheetBehaviorHalfExpandedMaxRatioMatcher(0.001f)))
252         Log.i(TAG, "verifyMinusculeHalfExpandedRatio: Verified the tabs tray half expanded ratio")
253     }
255     fun verifyTabTrayIsOpen() {
256         Log.i(TAG, "verifyTabTrayIsOpen: Trying to verify that the tabs tray exists")
257         composeTestRule.tabsTray().assertExists()
258         Log.i(TAG, "verifyTabTrayIsOpen: Verified that the tabs tray exists")
259     }
261     fun verifyTabTrayIsClosed() {
262         Log.i(TAG, "verifyTabTrayIsClosed: Trying to verify that the tabs tray does not exist")
263         composeTestRule.tabsTray().assertDoesNotExist()
264         Log.i(TAG, "verifyTabTrayIsClosed: Verified that the tabs tray does not exist")
265     }
267     /**
268      * Closes a tab when there is only one tab open.
269      */
270     @OptIn(ExperimentalTestApi::class)
271     fun closeTab() {
272         Log.i(TAG, "closeTab: Waiting until the close tab button exists")
273         composeTestRule.waitUntilAtLeastOneExists(hasTestTag(TabsTrayTestTag.tabItemClose))
274         Log.i(TAG, "closeTab: Waited until the close tab button exists")
275         Log.i(TAG, "closeTab: Trying to verify that the close tab button exists")
276         composeTestRule.closeTabButton().assertExists()
277         Log.i(TAG, "closeTab: Verified that the close tab button exists")
278         Log.i(TAG, "closeTab: Trying to click the close tab button")
279         composeTestRule.closeTabButton().performClick()
280         Log.i(TAG, "closeTab: Clicked the close tab button")
281     }
283     /**
284      * Swipes a tab with [title] left.
285      */
286     fun swipeTabLeft(title: String) {
287         Log.i(TAG, "swipeTabLeft: Trying to perform swipe left action on tab: $title")
288         composeTestRule.tabItem(title).performTouchInput { swipeLeft() }
289         Log.i(TAG, "swipeTabLeft: Performed swipe left action on tab: $title")
290     }
292     /**
293      * Swipes a tab with [title] right.
294      */
295     fun swipeTabRight(title: String) {
296         Log.i(TAG, "swipeTabRight: Trying to perform swipe right action on tab: $title")
297         composeTestRule.tabItem(title).performTouchInput { swipeRight() }
298         Log.i(TAG, "swipeTabRight: Performed swipe right action on tab: $title")
299     }
301     /**
302      * Creates a collection from the provided [tabTitles].
303      */
304     fun createCollection(
305         vararg tabTitles: String,
306         collectionName: String,
307         firstCollection: Boolean = true,
308     ) {
309         Log.i(TAG, "createCollection: Trying to click the three dot button")
310         composeTestRule.threeDotButton().performClick()
311         Log.i(TAG, "createCollection: Clicked the three dot button")
312         Log.i(TAG, "createCollection: Trying to click the \"Select tabs\" menu button")
313         composeTestRule.dropdownMenuItemSelectTabs().performClick()
314         Log.i(TAG, "createCollection: Clicked the \"Select tabs\" menu button")
316         for (tab in tabTitles) {
317             selectTab(tab)
318         }
320         clickCollectionsButton(composeTestRule) {
321             if (!firstCollection) {
322                 clickAddNewCollection()
323             }
324             typeCollectionNameAndSave(collectionName)
325         }
326     }
328     /**
329      * Selects a tab with [title].
330      */
331     @OptIn(ExperimentalTestApi::class)
332     fun selectTab(title: String) {
333         Log.i(TAG, "selectTab: Waiting for $waitingTime ms until the tab with title: $title exists")
334         composeTestRule.waitUntilExactlyOneExists(hasText(title), waitingTime)
335         Log.i(TAG, "selectTab: Waited for $waitingTime ms until the tab with title: $title exists")
336         Log.i(TAG, "selectTab: Trying to click tab with title: $title")
337         composeTestRule.tabItem(title).performClick()
338         Log.i(TAG, "selectTab: Clicked tab with title: $title")
339     }
341     /**
342      * Performs a long click on a tab with [title].
343      */
344     fun longClickTab(title: String) {
345         Log.i(TAG, "longClickTab: Trying to long click tab with title: $title")
346         composeTestRule.tabItem(title)
347             .performTouchInput { longClick(durationMillis = Constants.LONG_CLICK_DURATION) }
348         Log.i(TAG, "longClickTab: Long clicked tab with title: $title")
349     }
351     /**
352      * Verifies the multi selection counter displays [numOfTabs].
353      */
354     fun verifyTabsMultiSelectionCounter(numOfTabs: Int) {
355         Log.i(TAG, "verifyTabsMultiSelectionCounter: Trying to verify that $numOfTabs tabs are selected")
356         composeTestRule.multiSelectionCounter()
357             .assert(hasText("$numOfTabs selected"))
358         Log.i(TAG, "verifyTabsMultiSelectionCounter: Verified that $numOfTabs tabs are selected")
359     }
361     /**
362      * Verifies a tab's media button matches [action] when there is only one tab with media.
363      */
364     @OptIn(ExperimentalTestApi::class)
365     fun verifyTabMediaControlButtonState(action: String) {
366         Log.i(TAG, "verifyTabMediaControlButtonStateTab: Waiting for $waitingTime ms until the media tab control button: $action exists")
367         composeTestRule.waitUntilAtLeastOneExists(hasContentDescription(action), waitingTime)
368         Log.i(TAG, "verifyTabMediaControlButtonStateTab: Waited for $waitingTime ms until the media tab control button: $action exists")
369         Log.i(TAG, "verifyTabMediaControlButtonStateTab: Trying to verify that the tab media control button: $action exists")
370         composeTestRule.tabMediaControlButton(action)
371             .assertExists()
372         Log.i(TAG, "verifyTabMediaControlButtonStateTab: Verified tab media control button: $action exists")
373     }
375     /**
376      * Clicks a tab's media button when there is only one tab with media.
377      */
378     @OptIn(ExperimentalTestApi::class)
379     fun clickTabMediaControlButton(action: String) {
380         Log.i(TAG, "clickTabMediaControlButton: Waiting for $waitingTime ms until the media tab control button: $action exists")
381         composeTestRule.waitUntilAtLeastOneExists(hasContentDescription(action), waitingTime)
382         Log.i(TAG, "clickTabMediaControlButton: Waited for $waitingTime ms until the media tab control button: $action exists")
383         Log.i(TAG, "clickTabMediaControlButton: Trying to click the tab media control button: $action")
384         composeTestRule.tabMediaControlButton(action)
385             .performClick()
386         Log.i(TAG, "clickTabMediaControlButton: Clicked the tab media control button: $action")
387     }
389     /**
390      * Closes a tab with a given [title].
391      */
392     fun closeTabWithTitle(title: String) {
393         Log.i(TAG, "closeTabWithTitle: Trying to click the close button for tab with title: $title")
394         composeTestRule.onAllNodesWithTag(TabsTrayTestTag.tabItemClose)
395             .filter(hasParent(hasText(title)))
396             .onFirst()
397             .performClick()
398         Log.i(TAG, "closeTabWithTitle: Clicked the close button for tab with title: $title")
399     }
401     class Transition(private val composeTestRule: HomeActivityComposeTestRule) {
403         fun openNewTab(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
404             Log.i(TAG, "openNewTab: Waiting for device to be idle")
405             mDevice.waitForIdle()
406             Log.i(TAG, "openNewTab: Waited for device to be idle")
407             Log.i(TAG, "openNewTab: Trying to click the new tab FAB button")
408             composeTestRule.tabsTrayFab().performClick()
409             Log.i(TAG, "openNewTab: Clicked the new tab FAB button")
410             SearchRobot().interact()
411             return SearchRobot.Transition()
412         }
414         fun toggleToNormalTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
415             Log.i(TAG, "toggleToNormalTabs: Trying to click the normal browsing button")
416             composeTestRule.normalBrowsingButton().performClick()
417             Log.i(TAG, "toggleToNormalTabs: Clicked the normal browsing button")
418             ComposeTabDrawerRobot(composeTestRule).interact()
419             return Transition(composeTestRule)
420         }
422         fun toggleToPrivateTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
423             Log.i(TAG, "toggleToPrivateTabs: Trying to click the private browsing button")
424             composeTestRule.privateBrowsingButton().performClick()
425             Log.i(TAG, "toggleToPrivateTabs: Clicked the private browsing button")
426             ComposeTabDrawerRobot(composeTestRule).interact()
427             return Transition(composeTestRule)
428         }
430         fun toggleToSyncedTabs(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
431             Log.i(TAG, "toggleToSyncedTabs: Trying to click the synced tabs button")
432             composeTestRule.syncedTabsButton().performClick()
433             Log.i(TAG, "toggleToSyncedTabs: Clicked the synced tabs button")
434             ComposeTabDrawerRobot(composeTestRule).interact()
435             return Transition(composeTestRule)
436         }
438         fun clickSignInToSyncButton(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
439             Log.i(TAG, "clickSignInToSyncButton: Trying to click the sign in to sync button and wait for $waitingTimeShort ms for a new window")
440             itemContainingText(getStringResource(R.string.sync_sign_in))
441                 .clickAndWaitForNewWindow(waitingTimeShort)
442             Log.i(TAG, "clickSignInToSyncButton: Clicked the sign in to sync button and waited for $waitingTimeShort ms for a new window")
443             SyncSignInRobot().interact()
444             return SyncSignInRobot.Transition()
445         }
447         fun openThreeDotMenu(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
448             Log.i(TAG, "openThreeDotMenu: Trying to click the three dot button")
449             composeTestRule.threeDotButton().performClick()
450             Log.i(TAG, "openThreeDotMenu: Clicked three dot button")
451             ComposeTabDrawerRobot(composeTestRule).interact()
452             return Transition(composeTestRule)
453         }
455         fun closeAllTabs(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
456             Log.i(TAG, "closeAllTabs: Trying to click the \"Close all tabs\" menu button")
457             composeTestRule.dropdownMenuItemCloseAllTabs().performClick()
458             Log.i(TAG, "closeAllTabs: Clicked the \"Close all tabs\" menu button")
459             HomeScreenRobot().interact()
460             return HomeScreenRobot.Transition()
461         }
463         fun openTab(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
464             Log.i(TAG, "openTab: Trying to scroll to tab with title: $title")
465             composeTestRule.tabItem(title).performScrollTo()
466             Log.i(TAG, "openTab: Scrolled to tab with title: $title")
467             Log.i(TAG, "openTab: Trying to click tab with title: $title")
468             composeTestRule.tabItem(title).performClick()
469             Log.i(TAG, "openTab: Clicked tab with title: $title")
471             BrowserRobot().interact()
472             return BrowserRobot.Transition()
473         }
475         fun openPrivateTab(position: Int, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
476             Log.i(TAG, "openPrivateTab: Trying to click private tab at position: ${position + 1}")
477             composeTestRule.privateTabsList()
478                 .onChildren()[position]
479                 .performClick()
480             Log.i(TAG, "openPrivateTab: Clicked private tab at position: ${position + 1}")
482             BrowserRobot().interact()
483             return BrowserRobot.Transition()
484         }
486         fun openNormalTab(position: Int, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
487             Log.i(TAG, "openNormalTab: Trying to click tab at position: ${position + 1}")
488             composeTestRule.normalTabsList()
489                 .onChildren()[position]
490                 .performClick()
491             Log.i(TAG, "openNormalTab: Clicked tab at position: ${position + 1}")
493             BrowserRobot().interact()
494             return BrowserRobot.Transition()
495         }
497         fun clickTopBar(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
498             // The topBar contains other views.
499             // Don't do the default click in the middle, rather click in some free space - top right.
500             Log.i(TAG, "clickTopBar: Trying to click the tabs tray top bar")
501             Espresso.onView(ViewMatchers.withId(R.id.topBar)).clickAtLocationInView(GeneralLocation.TOP_RIGHT)
502             Log.i(TAG, "clickTopBar: Clicked the tabs tray top bar")
503             ComposeTabDrawerRobot(composeTestRule).interact()
504             return Transition(composeTestRule)
505         }
507         fun waitForTabTrayBehaviorToIdle(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
508             // Need to get the behavior of tab_wrapper and wait for that to idle.
509             var behavior: BottomSheetBehavior<*>? = null
511             // Null check here since it's possible that the view is already animated away from the screen.
512             tabsTrayView()?.perform(
513                 object : ViewAction {
514                     override fun getDescription(): String {
515                         return "Postpone actions to after the BottomSheetBehavior has settled"
516                     }
518                     override fun getConstraints(): Matcher<View> {
519                         return ViewMatchers.isAssignableFrom(View::class.java)
520                     }
522                     override fun perform(uiController: UiController?, view: View?) {
523                         behavior = BottomSheetBehavior.from(view!!)
524                     }
525                 },
526             )
528             behavior?.let {
529                 runWithIdleRes(BottomSheetBehaviorStateIdlingResource(it)) {
530                     ComposeTabDrawerRobot(composeTestRule).interact()
531                 }
532             }
534             return Transition(composeTestRule)
535         }
537         fun advanceToHalfExpandedState(interact: ComposeTabDrawerRobot.() -> Unit): Transition {
538             tabsTrayView().perform(
539                 object : ViewAction {
540                     override fun getDescription(): String {
541                         return "Advance a BottomSheetBehavior to STATE_HALF_EXPANDED"
542                     }
544                     override fun getConstraints(): Matcher<View> {
545                         return ViewMatchers.isAssignableFrom(View::class.java)
546                     }
548                     override fun perform(uiController: UiController?, view: View?) {
549                         val behavior = BottomSheetBehavior.from(view!!)
550                         behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
551                     }
552                 },
553             )
554             ComposeTabDrawerRobot(composeTestRule).interact()
555             return Transition(composeTestRule)
556         }
558         fun closeTabDrawer(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
559             Log.i(TAG, "closeTabDrawer: Trying to close the tabs tray by clicking the handle")
560             composeTestRule.bannerHandle().performSemanticsAction(SemanticsActions.OnClick)
561             Log.i(TAG, "closeTabDrawer: Closed the tabs tray by clicking the handle")
563             BrowserRobot().interact()
564             return BrowserRobot.Transition()
565         }
567         fun clickSaveCollection(interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
568             Log.i(TAG, "clickSaveCollection: Trying to click the collections button")
569             composeTestRule.collectionsButton().performClick()
570             Log.i(TAG, "clickSaveCollection: Clicked collections button")
572             CollectionRobot().interact()
573             return CollectionRobot.Transition()
574         }
576         fun clickShareAllTabsButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
577             Log.i(TAG, "clickShareAllTabsButton: Trying to click the \"Share all tabs\" menu button button")
578             composeTestRule.dropdownMenuItemShareAllTabs().performClick()
579             Log.i(TAG, "clickShareAllTabsButton: Clicked the \"Share all tabs\" menu button button")
581             ShareOverlayRobot().interact()
582             return ShareOverlayRobot.Transition()
583         }
584     }
588  * Opens a transition in the [ComposeTabDrawerRobot].
589  */
590 fun composeTabDrawer(composeTestRule: HomeActivityComposeTestRule, interact: ComposeTabDrawerRobot.() -> Unit): ComposeTabDrawerRobot.Transition {
591     ComposeTabDrawerRobot(composeTestRule).interact()
592     return ComposeTabDrawerRobot.Transition(composeTestRule)
596  * Clicks on the Collections button in the Tabs Tray banner and opens a transition in the [CollectionRobot].
597  */
598 private fun clickCollectionsButton(composeTestRule: HomeActivityComposeTestRule, interact: CollectionRobot.() -> Unit): CollectionRobot.Transition {
599     Log.i(TAG, "clickCollectionsButton: Trying to click the collections button")
600     composeTestRule.collectionsButton().performClick()
601     Log.i(TAG, "clickCollectionsButton: Clicked the collections button")
603     CollectionRobot().interact()
604     return CollectionRobot.Transition()
608  * Obtains the root [View] that wraps the Tabs Tray.
609  */
610 private fun tabsTrayView() = Espresso.onView(ViewMatchers.withId(R.id.tabs_tray_root))
613  * Obtains the root Tabs Tray.
614  */
615 private fun ComposeTestRule.tabsTray() = onNodeWithTag(TabsTrayTestTag.tabsTray)
618  * Obtains the Tabs Tray FAB.
619  */
620 private fun ComposeTestRule.tabsTrayFab() = onNodeWithTag(TabsTrayTestTag.fab)
623  * Obtains the normal browsing page button of the Tabs Tray banner.
624  */
625 private fun ComposeTestRule.normalBrowsingButton() = onNodeWithTag(TabsTrayTestTag.normalTabsPageButton)
628  * Obtains the private browsing page button of the Tabs Tray banner.
629  */
630 private fun ComposeTestRule.privateBrowsingButton() = onNodeWithTag(TabsTrayTestTag.privateTabsPageButton)
633  * Obtains the synced tabs page button of the Tabs Tray banner.
634  */
635 private fun ComposeTestRule.syncedTabsButton() = onNodeWithTag(TabsTrayTestTag.syncedTabsPageButton)
638  * Obtains the normal tabs list.
639  */
640 private fun ComposeTestRule.normalTabsList() = onNodeWithTag(TabsTrayTestTag.normalTabsList)
643  * Obtains the private tabs list.
644  */
645 private fun ComposeTestRule.privateTabsList() = onNodeWithTag(TabsTrayTestTag.privateTabsList)
648  * Obtains the synced tabs list.
649  */
650 private fun ComposeTestRule.syncedTabsList() = onNodeWithTag(TabsTrayTestTag.syncedTabsList)
653  * Obtains the empty normal tabs list.
654  */
655 private fun ComposeTestRule.emptyNormalTabsList() = onNodeWithTag(TabsTrayTestTag.emptyNormalTabsList)
658  * Obtains the empty private tabs list.
659  */
660 private fun ComposeTestRule.emptyPrivateTabsList() = onNodeWithTag(TabsTrayTestTag.emptyPrivateTabsList)
663  * Obtains the tab with the provided [title]
664  */
665 private fun ComposeTestRule.tabItem(title: String) = onAllNodesWithTag(TabsTrayTestTag.tabItemRoot)
666     .filter(hasAnyChild(hasText(title)))
667     .onFirst()
670  * Obtains an open tab's close button when there's only one tab open.
671  */
672 private fun ComposeTestRule.closeTabButton() = onNodeWithTag(TabsTrayTestTag.tabItemClose)
675  * Obtains an open tab's thumbnail when there's only one tab open.
676  */
677 private fun ComposeTestRule.tabThumbnail() = onNodeWithTag(TabsTrayTestTag.tabItemThumbnail)
680  * Obtains the three dot button in the Tabs Tray banner.
681  */
682 private fun ComposeTestRule.threeDotButton() = onNodeWithTag(TabsTrayTestTag.threeDotButton)
685  * Obtains the dropdown menu item to access account settings.
686  */
687 private fun ComposeTestRule.dropdownMenuItemAccountSettings() = onNodeWithTag(TabsTrayTestTag.accountSettings)
690  * Obtains the dropdown menu item to close all tabs.
691  */
692 private fun ComposeTestRule.dropdownMenuItemCloseAllTabs() = onNodeWithTag(TabsTrayTestTag.closeAllTabs)
695  * Obtains the dropdown menu item to access recently closed tabs.
696  */
697 private fun ComposeTestRule.dropdownMenuItemRecentlyClosedTabs() = onNodeWithTag(TabsTrayTestTag.recentlyClosedTabs)
700  * Obtains the dropdown menu item to select tabs.
701  */
702 private fun ComposeTestRule.dropdownMenuItemSelectTabs() = onNodeWithTag(TabsTrayTestTag.selectTabs)
705  * Obtains the dropdown menu item to share all tabs.
706  */
707 private fun ComposeTestRule.dropdownMenuItemShareAllTabs() = onNodeWithTag(TabsTrayTestTag.shareAllTabs)
710  * Obtains the dropdown menu item to access tab settings.
711  */
712 private fun ComposeTestRule.dropdownMenuItemTabSettings() = onNodeWithTag(TabsTrayTestTag.tabSettings)
715  * Obtains the normal tabs counter.
716  */
717 private fun ComposeTestRule.normalTabsCounter() = onNodeWithTag(TabsTrayTestTag.normalTabsCounter)
720  * Obtains the Tabs Tray banner collections button.
721  */
722 private fun ComposeTestRule.collectionsButton() = onNodeWithTag(TabsTrayTestTag.collectionsButton)
725  * Obtains the Tabs Tray banner multi selection counter.
726  */
727 private fun ComposeTestRule.multiSelectionCounter() = onNodeWithTag(TabsTrayTestTag.selectionCounter)
730  * Obtains the Tabs Tray banner handle.
731  */
732 private fun ComposeTestRule.bannerHandle() = onNodeWithTag(TabsTrayTestTag.bannerHandle)
735  * Obtains the media control button with the given [action] as its content description.
736  */
737 private fun ComposeTestRule.tabMediaControlButton(action: String) = onNodeWithContentDescription(action)