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