1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.ntp; 6 7 import android.app.Activity; 8 import android.view.View; 9 10 import androidx.test.filters.LargeTest; 11 import androidx.test.filters.MediumTest; 12 13 import org.hamcrest.Matchers; 14 import org.junit.After; 15 import org.junit.Assert; 16 import org.junit.Before; 17 import org.junit.Rule; 18 import org.junit.Test; 19 import org.junit.runner.RunWith; 20 21 import org.chromium.base.test.util.CommandLineFlags; 22 import org.chromium.base.test.util.Criteria; 23 import org.chromium.base.test.util.CriteriaHelper; 24 import org.chromium.base.test.util.Feature; 25 import org.chromium.chrome.browser.flags.ChromeFeatureList; 26 import org.chromium.chrome.browser.flags.ChromeSwitches; 27 import org.chromium.chrome.browser.tab.Tab; 28 import org.chromium.chrome.test.ChromeJUnit4ClassRunner; 29 import org.chromium.chrome.test.ChromeTabbedActivityTestRule; 30 import org.chromium.chrome.test.util.ChromeRenderTestRule; 31 import org.chromium.chrome.test.util.RecentTabsPageTestUtils; 32 import org.chromium.chrome.test.util.browser.Features; 33 import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule; 34 import org.chromium.components.embedder_support.util.UrlConstants; 35 import org.chromium.components.signin.test.util.FakeProfileDataSource; 36 import org.chromium.content_public.browser.test.util.TestThreadUtils; 37 import org.chromium.content_public.common.ContentUrlConstants; 38 import org.chromium.url.GURL; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.concurrent.ExecutionException; 43 44 /** 45 * Instrumentation tests for {@link RecentTabsPage}. 46 */ 47 @RunWith(ChromeJUnit4ClassRunner.class) 48 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) 49 public class RecentTabsPageTest { 50 // FakeProfileDataSource is required to create the ProfileDataCache entry with sync_off badge 51 // for Sync promo. 52 @Rule 53 public final AccountManagerTestRule mAccountManagerTestRule = 54 new AccountManagerTestRule(new FakeProfileDataSource()); 55 56 @Rule 57 public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); 58 59 @Rule 60 public final ChromeRenderTestRule mRenderTestRule = 61 ChromeRenderTestRule.Builder.withPublicCorpus().setRevision(2).build(); 62 63 private FakeRecentlyClosedTabManager mManager; 64 private Tab mTab; 65 private RecentTabsPage mPage; 66 67 @Before setUp()68 public void setUp() throws Exception { 69 mManager = new FakeRecentlyClosedTabManager(); 70 RecentTabsManager.setRecentlyClosedTabManagerForTests(mManager); 71 mActivityTestRule.startMainActivityOnBlankPage(); 72 mTab = mActivityTestRule.getActivity().getActivityTab(); 73 } 74 75 @After tearDown()76 public void tearDown() { 77 leaveRecentTabsPage(); 78 RecentTabsManager.setRecentlyClosedTabManagerForTests(null); 79 } 80 81 @Test 82 @MediumTest 83 @Feature({"RecentTabsPage"}) testRecentlyClosedTabs()84 public void testRecentlyClosedTabs() throws ExecutionException { 85 mPage = loadRecentTabsPage(); 86 // Set a recently closed tab and confirm a view is rendered for it. 87 List<RecentlyClosedTab> tabs = setRecentlyClosedTabs(1); 88 Assert.assertEquals(1, mManager.getRecentlyClosedTabs(1).size()); 89 String title = tabs.get(0).title; 90 View view = waitForView(title); 91 92 // Clear the recently closed tabs with the context menu and confirm the view is gone. 93 openContextMenuAndInvokeItem(mActivityTestRule.getActivity(), view, 94 RecentTabsRowAdapter.RecentlyClosedTabsGroup.ID_REMOVE_ALL); 95 Assert.assertEquals(0, mManager.getRecentlyClosedTabs(1).size()); 96 waitForViewToDisappear(title); 97 } 98 99 @Test 100 @LargeTest 101 @Feature("RenderTest") 102 @Features.DisableFeatures({ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY}) testPersonalizedSigninPromoInRecentTabsPageLegacy()103 public void testPersonalizedSigninPromoInRecentTabsPageLegacy() throws Exception { 104 mAccountManagerTestRule.addAccount(mAccountManagerTestRule.createProfileDataFromName( 105 AccountManagerTestRule.TEST_ACCOUNT_EMAIL)); 106 mPage = loadRecentTabsPage(); 107 mRenderTestRule.render( 108 mPage.getView(), "personalized_signin_promo_recent_tabs_page_legacy"); 109 } 110 111 @Test 112 @LargeTest 113 @Feature("RenderTest") 114 @Features.EnableFeatures({ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY}) testPersonalizedSigninPromoInRecentTabsPage()115 public void testPersonalizedSigninPromoInRecentTabsPage() throws Exception { 116 mAccountManagerTestRule.addAccount(mAccountManagerTestRule.createProfileDataFromName( 117 AccountManagerTestRule.TEST_ACCOUNT_EMAIL)); 118 mPage = loadRecentTabsPage(); 119 mRenderTestRule.render(mPage.getView(), "personalized_signin_promo_recent_tabs_page"); 120 } 121 122 @Test 123 @LargeTest 124 @Feature("RenderTest") 125 @Features.EnableFeatures({ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY}) testPersonalizedSyncPromoInRecentTabsPage()126 public void testPersonalizedSyncPromoInRecentTabsPage() throws Exception { 127 mAccountManagerTestRule.addTestAccountThenSignin(); 128 mPage = loadRecentTabsPage(); 129 mRenderTestRule.render(mPage.getView(), "personalized_sync_promo_recent_tabs_page"); 130 } 131 132 /** 133 * Generates the specified number of {@link RecentlyClosedTab} instances and sets them on the 134 * manager. 135 */ setRecentlyClosedTabs(final int tabCount)136 private List<RecentlyClosedTab> setRecentlyClosedTabs(final int tabCount) { 137 final List<RecentlyClosedTab> tabs = new ArrayList<>(); 138 TestThreadUtils.runOnUiThreadBlocking(() -> { 139 for (int i = 0; i < tabCount; i++) { 140 tabs.add(new RecentlyClosedTab(i, "RecentlyClosedTab title " + i, 141 new GURL("https://www.example.com/url" + i))); 142 } 143 mManager.setRecentlyClosedTabs(tabs); 144 }); 145 return tabs; 146 } 147 loadRecentTabsPage()148 private RecentTabsPage loadRecentTabsPage() { 149 mActivityTestRule.loadUrl(UrlConstants.RECENT_TABS_URL); 150 RecentTabsPageTestUtils.waitForRecentTabsPageLoaded(mTab); 151 return (RecentTabsPage) mTab.getNativePage(); 152 } 153 154 /** 155 * Leaves and destroys the {@link RecentTabsPage} by navigating the tab to {@code about:blank}. 156 */ leaveRecentTabsPage()157 private void leaveRecentTabsPage() { 158 mActivityTestRule.loadUrl(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL); 159 CriteriaHelper.pollUiThread(() -> { 160 Criteria.checkThat("RecentTabsPage is still there", mTab.getNativePage(), 161 Matchers.not(Matchers.instanceOf(RecentTabsPage.class))); 162 }); 163 } 164 165 /** 166 * Waits for the view with the specified text to appear. 167 */ waitForView(final String text)168 private View waitForView(final String text) { 169 final ArrayList<View> views = new ArrayList<>(); 170 CriteriaHelper.pollUiThread(() -> { 171 mPage.getView().findViewsWithText(views, text, View.FIND_VIEWS_WITH_TEXT); 172 Criteria.checkThat( 173 "Could not find view with this text: " + text, views.size(), Matchers.is(1)); 174 }); 175 return views.get(0); 176 } 177 178 /** 179 * Waits for the view with the specified text to disappear. 180 */ waitForViewToDisappear(final String text)181 private void waitForViewToDisappear(final String text) { 182 CriteriaHelper.pollUiThread(() -> { 183 ArrayList<View> views = new ArrayList<>(); 184 mPage.getView().findViewsWithText(views, text, View.FIND_VIEWS_WITH_TEXT); 185 Criteria.checkThat( 186 "View with this text is still present: " + text, views, Matchers.empty()); 187 }); 188 } 189 openContextMenuAndInvokeItem( final Activity activity, final View view, final int itemId)190 private static void openContextMenuAndInvokeItem( 191 final Activity activity, final View view, final int itemId) { 192 // IMPLEMENTATION NOTE: Instrumentation.invokeContextMenuAction would've been much simpler, 193 // but it requires the View to be focused which is hard to achieve in touch mode. 194 TestThreadUtils.runOnUiThreadBlocking(() -> { 195 view.performLongClick(); 196 activity.getWindow().performContextMenuIdentifierAction(itemId, 0); 197 }); 198 } 199 } 200