| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.test.util; |
| |
| import android.app.Instrumentation; |
| import android.support.annotation.Nullable; |
| import android.support.test.InstrumentationRegistry; |
| import android.text.TextUtils; |
| import android.view.View; |
| |
| import org.junit.Assert; |
| |
| import org.chromium.base.Log; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.test.util.CallbackHelper; |
| import org.chromium.base.test.util.ScalableTimeout; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.ChromeTabbedActivity; |
| import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton; |
| import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelper; |
| import org.chromium.chrome.browser.tab.EmptyTabObserver; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.tab.Tab.TabHidingType; |
| import org.chromium.chrome.browser.tab.TabWebContentsObserver; |
| import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
| import org.chromium.chrome.browser.tabmodel.TabModel; |
| import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
| import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
| import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
| import org.chromium.chrome.test.ChromeTabbedActivityTestRule; |
| import org.chromium.chrome.test.util.browser.TabTitleObserver; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.browser.test.util.TestTouchUtils; |
| import org.chromium.content_public.browser.test.util.TouchCommon; |
| |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| /** |
| * A utility class that contains methods generic to all Tabs tests. |
| */ |
| public class ChromeTabUtils { |
| private static final String TAG = "cr_ChromeTabUtils"; |
| public static final int TITLE_UPDATE_TIMEOUT_MS = 3000; |
| /** |
| * An observer that waits for a Tab to load a page. |
| * |
| * The observer can be configured to either wait for the Tab to load a specific page |
| * (if expectedUrl is non-null) or any page (otherwise). On seeing the tab finish |
| * a page load or crash, the observer will notify the provided callback and stop |
| * watching the tab. On load stop, the observer will decrement the provided latch |
| * and continue watching the page in case the tab subsequently crashes or finishes |
| * a page load. |
| * |
| * This may seem complicated, but it's intended to handle three distinct cases: |
| * 1) Successful page load + observer starts watching before onPageLoadFinished fires. |
| * This is the most normal case: onPageLoadFinished fires, then onLoadStopped fires, |
| * and we see both. |
| * 2) Crash on page load. onLoadStopped fires, then onCrash fires, and we see both. |
| * 3) Successful page load + observer starts watching after onPageLoadFinished fires. |
| * We miss the onPageLoadFinished and *only* see onLoadStopped. |
| * |
| * Receiving onPageLoadFinished is sufficient to know that we're dealing with scenario #1. |
| * Receiving onCrash is sufficient to know that we're dealing with scenario #2. |
| * Receiving onLoadStopped without a preceding onPageLoadFinished indicates that we're dealing |
| * with either scenario #2 *or* #3, so we have to keep watching for a call to onCrash. |
| */ |
| private static class TabPageLoadedObserver extends EmptyTabObserver { |
| private CallbackHelper mCallback; |
| private String mExpectedUrl; |
| private CountDownLatch mLoadStoppedLatch; |
| |
| public TabPageLoadedObserver(CallbackHelper loadCompleteCallback, String expectedUrl, |
| CountDownLatch loadStoppedLatch) { |
| mCallback = loadCompleteCallback; |
| mExpectedUrl = expectedUrl; |
| mLoadStoppedLatch = loadStoppedLatch; |
| } |
| |
| @Override |
| public void onCrash(Tab tab) { |
| mCallback.notifyFailed("Tab crashed :("); |
| tab.removeObserver(this); |
| } |
| |
| @Override |
| public void onLoadStopped(Tab tab, boolean toDifferentDocument) { |
| mLoadStoppedLatch.countDown(); |
| } |
| |
| @Override |
| public void onPageLoadFinished(Tab tab, String url) { |
| if (mExpectedUrl == null || TextUtils.equals(url, mExpectedUrl)) { |
| mCallback.notifyCalled(); |
| tab.removeObserver(this); |
| } |
| } |
| } |
| |
| private static boolean loadComplete(Tab tab, String url) { |
| return !tab.isLoading() && (url == null || TextUtils.equals(tab.getUrl(), url)) |
| && !tab.getWebContents().isLoadingToDifferentDocument(); |
| } |
| |
| /** |
| * Waits for the given tab to finish loading the given URL, or, if the given URL is |
| * null, waits for the current page to load. |
| * |
| * @param tab The tab to wait for the page loading to be complete. |
| * @param url The URL that will be waited to load for. Pass in null if loading the |
| * current page is sufficient. |
| */ |
| public static void waitForTabPageLoaded(final Tab tab, @Nullable final String url) |
| throws InterruptedException { |
| waitForTabPageLoaded(tab, url, null, ScalableTimeout.scaleTimeout(10)); |
| } |
| |
| /** |
| * Waits for the given tab to load the given URL, or, if the given URL is null, waits |
| * for the triggered load to complete. |
| * |
| * @param tab The tab to wait for the page loading to be complete. |
| * @param url The expected url of the loaded page. Pass in null if loading the |
| * current page is sufficient. |
| * @param loadTrigger The trigger action that will result in a page load finished event |
| * to be fired (not run on the UI thread by default). |
| */ |
| public static void waitForTabPageLoaded(final Tab tab, @Nullable final String url, |
| @Nullable Runnable loadTrigger) throws InterruptedException { |
| waitForTabPageLoaded(tab, url, loadTrigger, CallbackHelper.WAIT_TIMEOUT_SECONDS); |
| } |
| |
| /** |
| * Waits for the given tab to finish loading its current page. |
| * |
| * @param tab The tab to wait for the page loading to be complete. |
| * @param loadTrigger The trigger action that will result in a page load finished event |
| * to be fired (not run on the UI thread by default). |
| * @param secondsToWait The number of seconds to wait for the page to be loaded. |
| */ |
| public static void waitForTabPageLoaded(final Tab tab, Runnable loadTrigger, long secondsToWait) |
| throws InterruptedException { |
| waitForTabPageLoaded(tab, null, loadTrigger, secondsToWait); |
| } |
| |
| /** |
| * Waits for the given tab to load the given URL, or, if the given URL is null, waits |
| * for the triggered load to complete. |
| * |
| * @param tab The tab to wait for the page loading to be complete. |
| * @param url The expected url of the loaded page. Pass in null if loading the |
| * current page is sufficient. |
| * @param loadTrigger The trigger action that will result in a page load finished event |
| * to be fired (not run on the UI thread by default). Pass in null if the |
| * load is triggered externally. |
| * @param secondsToWait The number of seconds to wait for the page to be loaded. |
| */ |
| public static void waitForTabPageLoaded(final Tab tab, @Nullable final String url, |
| @Nullable Runnable loadTrigger, long secondsToWait) throws InterruptedException { |
| Assert.assertFalse(ThreadUtils.runningOnUiThread()); |
| |
| final CountDownLatch loadStoppedLatch = new CountDownLatch(1); |
| final CallbackHelper loadedCallback = new CallbackHelper(); |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| // Don't check for the load being already complete if there is a trigger to run. |
| if (loadTrigger == null && loadComplete(tab, url)) { |
| loadedCallback.notifyCalled(); |
| return; |
| } |
| tab.addObserver(new TabPageLoadedObserver(loadedCallback, url, loadStoppedLatch)); |
| } |
| }); |
| if (loadTrigger != null) { |
| loadTrigger.run(); |
| } |
| try { |
| loadedCallback.waitForCallback(0, 1, secondsToWait, TimeUnit.SECONDS); |
| } catch (TimeoutException e) { |
| // In the event that: |
| // 1) the tab is on the correct page |
| // 2) we weren't notified that the page load finished |
| // 3) we *were* notified that the tab stopped loading |
| // 4) the tab didn't crash |
| // |
| // then it's likely the case that we started observing the tab after |
| // onPageLoadFinished but before onLoadStopped. (The latter sets tab.mIsLoading to |
| // false.) Try to carry on with the test. |
| if (loadStoppedLatch.getCount() == 0 && loadComplete(tab, url)) { |
| Log.w(TAG, |
| "onPageLoadFinished was never called, but loading stopped " |
| + "on the expected page. Tentatively continuing."); |
| } else { |
| WebContents webContents = tab.getWebContents(); |
| Assert.fail(String.format(Locale.ENGLISH, |
| "Page did not load. Tab information at time of failure -- " |
| + "expected url: '%s', actual URL: '%s', load progress: %d, is " |
| + "loading: %b, web contents init: %b, web contents loading: %b", |
| url, tab.getUrl(), tab.getProgress(), tab.isLoading(), webContents != null, |
| webContents == null ? false : webContents.isLoadingToDifferentDocument())); |
| } |
| } |
| } |
| |
| /** |
| * Waits for the given tab to start loading its current page. |
| * |
| * @param tab The tab to wait for the page loading to be started. |
| * @param loadTrigger The trigger action that will result in a page load started event |
| * to be fired (not run on the UI thread by default). |
| * @param secondsToWait The number of seconds to wait for the page to be load to be started. |
| */ |
| public static void waitForTabPageLoadStart( |
| final Tab tab, Runnable loadTrigger, long secondsToWait) |
| throws InterruptedException { |
| final CallbackHelper startedCallback = new CallbackHelper(); |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| tab.addObserver(new EmptyTabObserver() { |
| @Override |
| public void onPageLoadStarted(Tab tab, String url) { |
| startedCallback.notifyCalled(); |
| tab.removeObserver(this); |
| } |
| }); |
| } |
| }); |
| loadTrigger.run(); |
| try { |
| startedCallback.waitForCallback(0, 1, secondsToWait, TimeUnit.SECONDS); |
| } catch (TimeoutException e) { |
| Assert.fail("Page did not start loading. Tab information at time of failure --" |
| + " url: " + tab.getUrl() |
| + ", load progress: " + tab.getProgress() |
| + ", is loading: " + Boolean.toString(tab.isLoading())); |
| } |
| } |
| |
| /** |
| * An observer that waits for a Tab to become interactable. |
| * |
| * Notifies the provided callback when: |
| * - the page has become interactable |
| * - the tab has been hidden and will not become interactable. |
| * Stops observing with a failure if the tab has crashed. |
| * |
| * We treat the hidden case as success to handle loads in which a page immediately closes itself |
| * or opens a new foreground tab (popup), and may not become interactable. |
| */ |
| private static class TabPageInteractableObserver extends EmptyTabObserver { |
| private Tab mTab; |
| private CallbackHelper mCallback; |
| |
| public TabPageInteractableObserver(Tab tab, CallbackHelper interactableCallback) { |
| mTab = tab; |
| mCallback = interactableCallback; |
| } |
| |
| @Override |
| public void onCrash(Tab tab) { |
| mCallback.notifyFailed("Tab crashed :("); |
| mTab.removeObserver(this); |
| } |
| |
| @Override |
| public void onHidden(Tab tab, @TabHidingType int type) { |
| mCallback.notifyCalled(); |
| mTab.removeObserver(this); |
| } |
| |
| @Override |
| public void onInteractabilityChanged(boolean interactable) { |
| if (interactable) { |
| mCallback.notifyCalled(); |
| mTab.removeObserver(this); |
| } |
| } |
| } |
| |
| /** |
| * Waits for the tab to become interactable. This occurs after load, once all view |
| * animations have completed. |
| * |
| * @param tab The tab to wait for interactability on. |
| */ |
| public static void waitForInteractable(final Tab tab) throws InterruptedException { |
| Assert.assertFalse(ThreadUtils.runningOnUiThread()); |
| |
| final CallbackHelper interactableCallback = new CallbackHelper(); |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| // If a tab is hidden, don't wait for interactivity. See note in |
| // TabPageInteractableObserver. |
| if (tab.isUserInteractable() || tab.isHidden()) { |
| interactableCallback.notifyCalled(); |
| return; |
| } |
| tab.addObserver(new TabPageInteractableObserver(tab, interactableCallback)); |
| } |
| }); |
| |
| try { |
| interactableCallback.waitForCallback( |
| 0, 1, ScalableTimeout.scaleTimeout(10), TimeUnit.SECONDS); |
| } catch (TimeoutException e) { |
| Assert.fail("Page never became interactable."); |
| } |
| } |
| |
| /** |
| * Switch to the given TabIndex in the current tabModel. |
| * @param tabIndex |
| */ |
| public static void switchTabInCurrentTabModel(final ChromeActivity activity, |
| final int tabIndex) { |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| TabModelUtils.setIndex(activity.getCurrentTabModel(), tabIndex); |
| } |
| }); |
| } |
| |
| /** |
| * Simulates a click to the normal (not incognito) new tab button. |
| * <p> |
| * Does not wait for the tab to be loaded. |
| */ |
| public static void clickNewTabButton(Instrumentation instrumentation, |
| ChromeTabbedActivity activity) throws InterruptedException { |
| final TabModel normalTabModel = activity.getTabModelSelector().getModel(false); |
| final CallbackHelper createdCallback = new CallbackHelper(); |
| normalTabModel.addObserver(new EmptyTabModelObserver() { |
| @Override |
| public void didAddTab(Tab tab, @TabLaunchType int type) { |
| createdCallback.notifyCalled(); |
| normalTabModel.removeObserver(this); |
| } |
| }); |
| // Tablet and phone have different new tab buttons; click the right one. |
| if (activity.isTablet()) { |
| StripLayoutHelper strip = |
| TabStripUtils.getStripLayoutHelper(activity, false /* incognito */); |
| CompositorButton newTabButton = strip.getNewTabButton(); |
| TabStripUtils.clickCompositorButton(newTabButton, instrumentation, activity); |
| instrumentation.waitForIdleSync(); |
| } else { |
| TouchCommon.singleClickView(activity.findViewById(R.id.new_tab_button)); |
| } |
| |
| try { |
| createdCallback.waitForCallback(null, 0, 1, 10, TimeUnit.SECONDS); |
| } catch (TimeoutException e) { |
| Assert.fail("Never received tab creation event"); |
| } |
| } |
| |
| /** |
| * Creates a new tab by invoking the 'New Tab' menu item. |
| * <p> |
| * Returns when the tab has been created and has finished navigating. |
| */ |
| public static void newTabFromMenu(Instrumentation instrumentation, |
| final ChromeTabbedActivity activity) |
| throws InterruptedException { |
| newTabFromMenu(instrumentation, activity, false, true); |
| } |
| |
| /** |
| * Creates a new tab by invoking the 'New Tab' or 'New Incognito Tab' menu item. |
| * <p> |
| * Returns when the tab has been created and has finished navigating. |
| */ |
| public static void newTabFromMenu(Instrumentation instrumentation, |
| final ChromeTabbedActivity activity, boolean incognito, boolean waitForNtpLoad) |
| throws InterruptedException { |
| final CallbackHelper createdCallback = new CallbackHelper(); |
| final CallbackHelper selectedCallback = new CallbackHelper(); |
| |
| TabModel tabModel = activity.getTabModelSelector().getModel(incognito); |
| TabModelObserver observer = new EmptyTabModelObserver() { |
| @Override |
| public void didAddTab(Tab tab, @TabLaunchType int type) { |
| createdCallback.notifyCalled(); |
| } |
| |
| @Override |
| public void didSelectTab(Tab tab, @TabSelectionType int type, int lastId) { |
| selectedCallback.notifyCalled(); |
| } |
| }; |
| tabModel.addObserver(observer); |
| |
| MenuUtils.invokeCustomMenuActionSync(instrumentation, activity, |
| incognito ? R.id.new_incognito_tab_menu_id : R.id.new_tab_menu_id); |
| |
| try { |
| createdCallback.waitForCallback(0); |
| } catch (TimeoutException ex) { |
| Assert.fail("Never received tab created event"); |
| } |
| try { |
| selectedCallback.waitForCallback(0); |
| } catch (TimeoutException ex) { |
| Assert.fail("Never received tab selected event"); |
| } |
| tabModel.removeObserver(observer); |
| |
| Tab tab = activity.getActivityTab(); |
| waitForTabPageLoaded(tab, (String) null); |
| if (waitForNtpLoad) NewTabPageTestUtils.waitForNtpLoaded(tab); |
| instrumentation.waitForIdleSync(); |
| Log.d(TAG, "newTabFromMenu <<"); |
| } |
| |
| /** |
| * New multiple tabs by invoking the 'new' menu item n times. |
| * @param n The number of tabs you want to create. |
| */ |
| public static void newTabsFromMenu(Instrumentation instrumentation, |
| ChromeTabbedActivity activity, int n) |
| throws InterruptedException { |
| while (n > 0) { |
| newTabFromMenu(instrumentation, activity); |
| --n; |
| } |
| } |
| |
| /** |
| * Creates a new tab in the specified model then waits for it to load. |
| * <p> |
| * Returns when the tab has been created and finishes loading. |
| */ |
| public static void fullyLoadUrlInNewTab(Instrumentation instrumentation, |
| final ChromeTabbedActivity activity, final String url, final boolean incognito) |
| throws InterruptedException { |
| newTabFromMenu(instrumentation, activity, incognito, false); |
| |
| final Tab tab = activity.getActivityTab(); |
| waitForTabPageLoaded(tab, url, new Runnable() { |
| @Override |
| public void run() { |
| loadUrlOnUiThread(tab, url); |
| } |
| }); |
| instrumentation.waitForIdleSync(); |
| } |
| |
| public static void loadUrlOnUiThread(final Tab tab, final String url) { |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| tab.loadUrl(new LoadUrlParams(url)); |
| } |
| }); |
| } |
| |
| /** |
| * Ensure that at least some given number of tabs are open. |
| */ |
| public static void ensureNumOpenTabs(Instrumentation instrumentation, |
| ChromeTabbedActivity activity, int newCount) throws InterruptedException { |
| int curCount = getNumOpenTabs(activity); |
| if (curCount < newCount) { |
| newTabsFromMenu(instrumentation, activity, newCount - curCount); |
| } |
| } |
| |
| /** |
| * Fetch the number of tabs open in the current model. |
| */ |
| public static int getNumOpenTabs(final ChromeActivity activity) { |
| return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Integer>() { |
| @Override |
| public Integer call() throws Exception { |
| return activity.getCurrentTabModel().getCount(); |
| } |
| }); |
| } |
| |
| /** |
| * Closes the current tab through TabModelSelector. |
| * <p> |
| * Returns after the tab has been closed. |
| */ |
| public static void closeCurrentTab(final Instrumentation instrumentation, |
| final ChromeTabbedActivity activity) |
| throws InterruptedException { |
| closeTabWithAction(instrumentation, activity, new Runnable() { |
| @Override |
| public void run() { |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelUtils.closeCurrentTab(activity.getCurrentTabModel()); |
| } |
| }); |
| } |
| }); |
| } |
| |
| /** |
| * Closes a tab with the given action and waits for a tab closure to be observed. |
| */ |
| public static void closeTabWithAction(Instrumentation instrumentation, |
| final ChromeTabbedActivity activity, Runnable action) throws InterruptedException { |
| final CallbackHelper closeCallback = new CallbackHelper(); |
| final TabModelObserver observer = new EmptyTabModelObserver() { |
| @Override |
| public void willCloseTab(Tab tab, boolean animate) { |
| closeCallback.notifyCalled(); |
| } |
| }; |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelSelector selector = activity.getTabModelSelector(); |
| for (TabModel tabModel : selector.getModels()) { |
| tabModel.addObserver(observer); |
| } |
| } |
| }); |
| |
| action.run(); |
| |
| try { |
| closeCallback.waitForCallback(0); |
| } catch (TimeoutException e) { |
| Assert.fail("Tab closed event was never received"); |
| } |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelSelector selector = activity.getTabModelSelector(); |
| for (TabModel tabModel : selector.getModels()) { |
| tabModel.removeObserver(observer); |
| } |
| } |
| }); |
| instrumentation.waitForIdleSync(); |
| Log.d(TAG, "closeTabWithAction <<"); |
| } |
| |
| /** |
| * Close all tabs and waits for all tabs pending closure to be observed. |
| */ |
| public static void closeAllTabs(Instrumentation instrumentation, |
| final ChromeTabbedActivity activity) throws InterruptedException { |
| final CallbackHelper closeCallback = new CallbackHelper(); |
| final TabModelObserver observer = new EmptyTabModelObserver() { |
| @Override |
| public void allTabsPendingClosure(List<Tab> tabs) { |
| closeCallback.notifyCalled(); |
| } |
| }; |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelSelector selector = activity.getTabModelSelector(); |
| for (TabModel tabModel : selector.getModels()) { |
| tabModel.addObserver(observer); |
| } |
| } |
| }); |
| |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| activity.getTabModelSelector().closeAllTabs(); |
| } |
| }); |
| |
| try { |
| closeCallback.waitForCallback(0); |
| } catch (TimeoutException e) { |
| Assert.fail("All tabs pending closure event was never received"); |
| } |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelSelector selector = activity.getTabModelSelector(); |
| for (TabModel tabModel : selector.getModels()) { |
| tabModel.removeObserver(observer); |
| } |
| } |
| }); |
| instrumentation.waitForIdleSync(); |
| } |
| |
| /** |
| * Selects a tab with the given action and waits for the selection event to be observed. |
| */ |
| public static void selectTabWithAction(Instrumentation instrumentation, |
| final ChromeTabbedActivity activity, Runnable action) throws InterruptedException { |
| final CallbackHelper selectCallback = new CallbackHelper(); |
| final TabModelObserver observer = new EmptyTabModelObserver() { |
| @Override |
| public void didSelectTab(Tab tab, @TabSelectionType int type, int lastId) { |
| selectCallback.notifyCalled(); |
| } |
| }; |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelSelector selector = activity.getTabModelSelector(); |
| for (TabModel tabModel : selector.getModels()) { |
| tabModel.addObserver(observer); |
| } |
| } |
| }); |
| |
| action.run(); |
| |
| try { |
| selectCallback.waitForCallback(0); |
| } catch (TimeoutException e) { |
| Assert.fail("Tab selected event was never received"); |
| } |
| instrumentation.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| TabModelSelector selector = activity.getTabModelSelector(); |
| for (TabModel tabModel : selector.getModels()) { |
| tabModel.removeObserver(observer); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Long presses the view, selects an item from the context menu, and |
| * asserts that a new tab is opened and is incognito if expectIncognito is true. |
| * For use in testing long-press context menu options that open new tabs. |
| * |
| * @param testRule The {@link ChromeTabbedActivityTestRule} used to retrieve the currently |
| * running activity. |
| * @param view The {@link View} to long press. |
| * @param contextMenuItemId The context menu item to select on the view. |
| * @param expectIncognito Whether the opened tab is expected to be incognito. |
| * @param expectedUrl The expected url for the new tab. |
| */ |
| public static void invokeContextMenuAndOpenInANewTab(ChromeTabbedActivityTestRule testRule, |
| View view, int contextMenuItemId, boolean expectIncognito, final String expectedUrl) |
| throws InterruptedException, ExecutionException { |
| final CallbackHelper createdCallback = new CallbackHelper(); |
| final TabModel tabModel = |
| testRule.getActivity().getTabModelSelector().getModel(expectIncognito); |
| tabModel.addObserver(new EmptyTabModelObserver() { |
| @Override |
| public void didAddTab(Tab tab, @TabLaunchType int type) { |
| if (TextUtils.equals(expectedUrl, tab.getUrl())) { |
| createdCallback.notifyCalled(); |
| tabModel.removeObserver(this); |
| } |
| } |
| }); |
| |
| TestTouchUtils.performLongClickOnMainSync( |
| InstrumentationRegistry.getInstrumentation(), view); |
| Assert.assertTrue(InstrumentationRegistry.getInstrumentation().invokeContextMenuAction( |
| testRule.getActivity(), contextMenuItemId, 0)); |
| |
| try { |
| createdCallback.waitForCallback(0); |
| } catch (TimeoutException e) { |
| Assert.fail("Never received tab creation event"); |
| } |
| |
| if (expectIncognito) { |
| Assert.assertTrue(testRule.getActivity().getTabModelSelector().isIncognitoSelected()); |
| } else { |
| Assert.assertFalse(testRule.getActivity().getTabModelSelector().isIncognitoSelected()); |
| } |
| } |
| |
| /** |
| * Long presses the view, selects an item from the context menu, and |
| * asserts that a new tab is opened and is incognito if expectIncognito is true. |
| * For use in testing long-press context menu options that open new tabs in a different |
| * ChromeTabbedActivity instance. |
| * |
| * @param foregroundActivity The {@link ChromeTabbedActivity} currently in the foreground. |
| * @param backgroundActivity The {@link ChromeTabbedActivity} currently in the background. The |
| * new tab is expected to open in this activity. |
| * @param view The {@link View} in the {@code foregroundActivity} to long press. |
| * @param contextMenuItemId The context menu item to select on the view. |
| * @param expectIncognito Whether the opened tab is expected to be incognito. |
| * @param expectedUrl The expected url for the new tab. |
| */ |
| public static void invokeContextMenuAndOpenInOtherWindow( |
| ChromeTabbedActivity foregroundActivity, ChromeTabbedActivity backgroundActivity, |
| View view, int contextMenuItemId, boolean expectIncognito, final String expectedUrl) |
| throws InterruptedException, ExecutionException { |
| final CallbackHelper createdCallback = new CallbackHelper(); |
| final TabModel tabModel = |
| backgroundActivity.getTabModelSelector().getModel(expectIncognito); |
| tabModel.addObserver(new EmptyTabModelObserver() { |
| @Override |
| public void didAddTab(Tab tab, @TabLaunchType int type) { |
| if (TextUtils.equals(expectedUrl, tab.getUrl())) { |
| createdCallback.notifyCalled(); |
| tabModel.removeObserver(this); |
| } |
| } |
| }); |
| |
| TestTouchUtils.performLongClickOnMainSync( |
| InstrumentationRegistry.getInstrumentation(), view); |
| Assert.assertTrue(InstrumentationRegistry.getInstrumentation().invokeContextMenuAction( |
| foregroundActivity, contextMenuItemId, 0)); |
| |
| try { |
| createdCallback.waitForCallback(0); |
| } catch (TimeoutException e) { |
| Assert.fail("Never received tab creation event"); |
| } |
| |
| if (expectIncognito) { |
| Assert.assertTrue(backgroundActivity.getTabModelSelector().isIncognitoSelected()); |
| } else { |
| Assert.assertFalse(backgroundActivity.getTabModelSelector().isIncognitoSelected()); |
| } |
| } |
| |
| /** |
| * Issues a fake notification about the renderer being killed. |
| * |
| * @param tab {@link Tab} instance where the target renderer resides. |
| * @param wasOomProtected True if the renderer was protected from the OS out-of-memory killer |
| * (e.g. renderer for the currently selected tab) |
| */ |
| public static void simulateRendererKilledForTesting(Tab tab, boolean wasOomProtected) { |
| TabWebContentsObserver observer = TabWebContentsObserver.get(tab); |
| if (observer != null) { |
| observer.simulateRendererKilledForTesting(wasOomProtected); |
| } |
| } |
| |
| public static void waitForTitle(Tab tab, String newTitle) throws InterruptedException { |
| TabTitleObserver titleObserver = new TabTitleObserver(tab, newTitle); |
| try { |
| titleObserver.waitForTitleUpdate(TITLE_UPDATE_TIMEOUT_MS); |
| } catch (TimeoutException e) { |
| Assert.fail(String.format(Locale.ENGLISH, |
| "Tab title didn't update to %s in time.", newTitle)); |
| } |
| } |
| } |