Index: chrome/test/android/javatests_staging/src/org/chromium/chrome/test/ChromeActivityTestCaseBase.java |
diff --git a/chrome/test/android/javatests_staging/src/org/chromium/chrome/test/ChromeActivityTestCaseBase.java b/chrome/test/android/javatests_staging/src/org/chromium/chrome/test/ChromeActivityTestCaseBase.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8c5d6b4d101007ff40dc0d718b01d2258752ef2c |
--- /dev/null |
+++ b/chrome/test/android/javatests_staging/src/org/chromium/chrome/test/ChromeActivityTestCaseBase.java |
@@ -0,0 +1,1068 @@ |
+// 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; |
+ |
+import android.app.Activity; |
+import android.app.ActivityManager; |
+import android.app.ActivityManager.AppTask; |
+import android.app.Instrumentation; |
+import android.content.ComponentName; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.content.pm.PackageManager; |
+import android.net.Uri; |
+import android.os.AsyncTask; |
+import android.os.PowerManager; |
+import android.provider.Browser; |
+import android.text.TextUtils; |
+import android.util.Log; |
+import android.view.KeyEvent; |
+import android.view.View; |
+import android.widget.ListView; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import junit.framework.Assert; |
+ |
+import org.chromium.base.PerfTraceEvent; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.annotations.SuppressFBWarnings; |
+import org.chromium.base.test.BaseActivityInstrumentationTestCase; |
+import org.chromium.base.test.util.CommandLineFlags; |
+import org.chromium.base.test.util.PerfTest; |
+import org.chromium.chrome.browser.ChromeActivity; |
+import org.chromium.chrome.browser.ChromeMobileApplication; |
+import org.chromium.chrome.browser.ChromeSwitches; |
+import org.chromium.chrome.browser.ChromeTabbedActivity; |
+import org.chromium.chrome.browser.DeferredStartupHandler; |
+import org.chromium.chrome.browser.EmptyTabObserver; |
+import org.chromium.chrome.browser.Tab; |
+import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
+import org.chromium.chrome.browser.document.DocumentActivity; |
+import org.chromium.chrome.browser.document.DocumentMetricIds; |
+import org.chromium.chrome.browser.document.DocumentUtils; |
+import org.chromium.chrome.browser.document.IncognitoDocumentActivity; |
+import org.chromium.chrome.browser.infobar.InfoBar; |
+import org.chromium.chrome.browser.ntp.NewTabPage; |
+import org.chromium.chrome.browser.omaha.OmahaClient; |
+import org.chromium.chrome.browser.omnibox.AutocompleteController; |
+import org.chromium.chrome.browser.omnibox.LocationBarLayout; |
+import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultItem; |
+import org.chromium.chrome.browser.omnibox.OmniboxSuggestion; |
+import org.chromium.chrome.browser.omnibox.UrlBar; |
+import org.chromium.chrome.browser.preferences.NetworkPredictionOptions; |
+import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
+import org.chromium.chrome.browser.preferences.Preferences; |
+import org.chromium.chrome.browser.preferences.PreferencesLauncher; |
+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.util.FeatureUtilities; |
+import org.chromium.chrome.test.util.ActivityUtils; |
+import org.chromium.chrome.test.util.ApplicationData; |
+import org.chromium.chrome.test.util.ChromeTabUtils; |
+import org.chromium.chrome.test.util.MenuUtils; |
+import org.chromium.chrome.test.util.NewTabPageTestUtils; |
+import org.chromium.chrome.test.util.OmniboxTestUtils; |
+import org.chromium.chrome.test.util.TestHttpServerClient; |
+import org.chromium.content.browser.test.util.CallbackHelper; |
+import org.chromium.content.browser.test.util.Criteria; |
+import org.chromium.content.browser.test.util.CriteriaHelper; |
+import org.chromium.content.browser.test.util.JavaScriptUtils; |
+import org.chromium.content.browser.test.util.KeyUtils; |
+import org.chromium.content.browser.test.util.RenderProcessLimit; |
+import org.chromium.content.browser.test.util.TestTouchUtils; |
+import org.chromium.content.browser.test.util.TouchCommon; |
+import org.chromium.content_public.browser.LoadUrlParams; |
+import org.chromium.ui.base.PageTransition; |
+ |
+import java.io.File; |
+import java.lang.reflect.Method; |
+import java.util.LinkedList; |
+import java.util.List; |
+import java.util.concurrent.Callable; |
+import java.util.concurrent.ExecutionException; |
+import java.util.concurrent.Semaphore; |
+import java.util.concurrent.TimeUnit; |
+import java.util.concurrent.TimeoutException; |
+import java.util.concurrent.atomic.AtomicBoolean; |
+import java.util.concurrent.atomic.AtomicInteger; |
+ |
+/** |
+ * Base class for all Chrome instrumentation tests. |
+ * All tests must inherit from this class and define their own test methods |
+ * See ChromeTabbedActivityTestBase.java for example. |
+ * @param <T> A {@link ChromeActivity} class |
+ */ |
+@CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE) |
+public abstract class ChromeActivityTestCaseBase<T extends ChromeActivity> |
+ extends BaseActivityInstrumentationTestCase<T> { |
+ |
+ private static final String TAG = "ChromeActivityTestCaseBase"; |
+ |
+ // The number of ms to wait for the rendering activity to be started. |
+ protected static final int ACTIVITY_START_TIMEOUT_MS = 1000; |
+ |
+ private static final String PERF_NORUN_TAG = "--NORUN--"; |
+ |
+ private static final String PERF_ANNOTATION_FORMAT = "**PERFANNOTATION(%s):"; |
+ |
+ private static final String MEMORY_TRACE_GRAPH_SUFFIX = " - browser PSS"; |
+ |
+ private static final String PERF_OUTPUT_FILE = "PerfTestData.txt"; |
+ |
+ private static final long OMNIBOX_FIND_SUGGESTION_TIMEOUT_MS = 10 * 1000; |
+ |
+ public ChromeActivityTestCaseBase(Class<T> activityClass) { |
+ super(activityClass); |
+ } |
+ |
+ protected boolean mSkipClearAppData = false; |
+ private PowerManager.WakeLock mWakeLock = null; |
+ protected boolean mSkipCheckHttpServer = false; |
+ |
+ @Override |
+ protected void setUp() throws Exception { |
+ super.setUp(); |
+ |
+ setActivityInitialTouchMode(false); |
+ if (!mSkipClearAppData) { |
+ // We shouldn't clear the data at the end of test, it is needed for debugging. |
+ assertTrue("Unable to clear the app data", clearAppData()); |
+ if (FeatureUtilities.isDocumentMode(getInstrumentation().getTargetContext())) { |
+ closeAllChromeActivityAppTasks(); |
+ } |
+ } |
+ // Make sure the screen is on during test runs. |
+ PowerManager pm = (PowerManager) getInstrumentation().getTargetContext() |
+ .getSystemService(Context.POWER_SERVICE); |
+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
+ | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, TAG); |
+ mWakeLock.acquire(); |
+ |
+ // Disable Omaha related activities. |
+ OmahaClient.setEnableCommunication(false); |
+ OmahaClient.setEnableUpdateDetection(false); |
+ |
+ if (!mSkipCheckHttpServer) { |
+ TestHttpServerClient.checkServerIsUp(); |
+ } |
+ startMainActivity(); |
+ } |
+ |
+ @Override |
+ protected void tearDown() throws Exception { |
+ assertNotNull("Uninitialized wake lock", mWakeLock); |
+ mWakeLock.release(); |
+ super.tearDown(); |
+ } |
+ |
+ /** |
+ * Called to start the Main Activity, the subclass should implemented with it desired start |
+ * method. |
+ * TODO: Make startMainActivityFromLauncher the default. |
+ */ |
+ public abstract void startMainActivity() throws InterruptedException; |
+ |
+ /** |
+ * Matches testString against baseString. |
+ * Returns 0 if there is no match, 1 if an exact match and 2 if a fuzzy match. |
+ */ |
+ protected static int matchUrl(String baseString, String testString) { |
+ if (baseString.equals(testString)) { |
+ return 1; |
+ } |
+ if (baseString.contains(testString)) { |
+ return 2; |
+ } |
+ return 0; |
+ } |
+ |
+ /** |
+ * Invokes {@link Instrumentation#startActivitySync(Intent)} and sets the |
+ * test case's activity to the result. See the documentation for |
+ * {@link Instrumentation#startActivitySync(Intent)} on the timing of the |
+ * return, but generally speaking the activity's "onCreate" has completed |
+ * and the activity's main looper has become idle. |
+ */ |
+ protected void startActivityCompletely(Intent intent) { |
+ final Class<?> activityClazz = |
+ FeatureUtilities.isDocumentMode(getInstrumentation().getTargetContext()) |
+ ? DocumentActivity.class : ChromeTabbedActivity.class; |
+ Instrumentation.ActivityMonitor monitor = getInstrumentation().addMonitor( |
+ activityClazz.getName(), null, false); |
+ Activity activity = getInstrumentation().startActivitySync(intent); |
+ assertNotNull("Main activity did not start", activity); |
+ ChromeActivity chromeActivity = (ChromeActivity) |
+ monitor.waitForActivityWithTimeout(ACTIVITY_START_TIMEOUT_MS); |
+ assertNotNull("ChromeActivity did not start", chromeActivity); |
+ setActivity(chromeActivity); |
+ Log.d(TAG, "startActivityCompletely <<"); |
+ } |
+ |
+ /** |
+ * Clear all files and folders in the Chrome application directory except 'lib'. |
+ * |
+ * The 'cache' directory is recreated as an empty directory. |
+ * |
+ * @return Whether clearing the application data was successful. |
+ */ |
+ protected boolean clearAppData() throws InterruptedException { |
+ return ApplicationData.clearAppData(getInstrumentation().getTargetContext()); |
+ } |
+ |
+ /** |
+ * Closes all Chrome activity app tasks. This is for cleaning up Chrome tasks in the recent, |
+ * those are not necessarily associated with a live activity. |
+ */ |
+ private void closeAllChromeActivityAppTasks() throws ClassNotFoundException { |
+ ActivityManager am = (ActivityManager) getInstrumentation().getTargetContext() |
+ .getSystemService(Context.ACTIVITY_SERVICE); |
+ PackageManager pm = getInstrumentation().getTargetContext().getPackageManager(); |
+ List<AppTask> taskList = am.getAppTasks(); |
+ for (AppTask task : taskList) { |
+ String className = DocumentUtils.getTaskClassName(task, pm); |
+ if (ChromeActivity.class.isAssignableFrom(Class.forName(className))) { |
+ task.finishAndRemoveTask(); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Lets tests specify whether they want prerendering turned on. |
+ * It is on by default. Since in some places different code paths are used for the same feature |
+ * depending of whether instant is on or off (ex: infobars), it is necessary for some tests to |
+ * test with and without instant. |
+ * |
+ * @param enabled whether prerender should be on. |
+ */ |
+ protected void setAllowPrerender(final boolean enabled) { |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ PrefServiceBridge.getInstance().setNetworkPredictionOptions(enabled |
+ ? NetworkPredictionOptions.NETWORK_PREDICTION_ALWAYS |
+ : NetworkPredictionOptions.NETWORK_PREDICTION_NEVER); |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Starts (synchronously) a drag motion. Normally followed by dragTo() and dragEnd(). |
+ * |
+ * @param x |
+ * @param y |
+ * @param downTime (in ms) |
+ * @see TestTouchUtils |
+ */ |
+ protected void dragStart(float x, float y, long downTime) { |
+ TouchCommon.dragStart(getActivity(), x, y, downTime); |
+ } |
+ |
+ /** |
+ * Drags / moves (synchronously) to the specified coordinates. Normally preceeded by |
+ * dragStart() and followed by dragEnd() |
+ * |
+ * @param fromX |
+ * @param toX |
+ * @param fromY |
+ * @param toY |
+ * @param stepCount |
+ * @param downTime (in ms) |
+ * @see TestTouchUtils |
+ */ |
+ protected void dragTo(float fromX, float toX, float fromY, |
+ float toY, int stepCount, long downTime) { |
+ TouchCommon.dragTo(getActivity(), fromX, toX, fromY, toY, stepCount, downTime); |
+ } |
+ |
+ /** |
+ * Finishes (synchronously) a drag / move at the specified coordinate. |
+ * Normally preceeded by dragStart() and dragTo(). |
+ * |
+ * @param x |
+ * @param y |
+ * @param downTime (in ms) |
+ * @see TestTouchUtils |
+ */ |
+ protected void dragEnd(float x, float y, long downTime) { |
+ TouchCommon.dragEnd(getActivity(), x, y, downTime); |
+ } |
+ |
+ /** |
+ * Sends (synchronously) a single click to an absolute screen coordinates. |
+ * |
+ * @param x screen absolute |
+ * @param y screen absolute |
+ * @see TestTouchUtils |
+ */ |
+ public void singleClick(float x, float y) { |
+ TouchCommon.singleClick(getActivity(), x, y); |
+ } |
+ |
+ /** |
+ * Sends (synchronously) a single click to the View at the specified coordinates. |
+ * |
+ * <p> |
+ * Differs from |
+ * {@link TestTouchUtils#singleClickView(android.app.Instrumentation, View, int, int)} |
+ * as this does not rely on injecting events into the different activity. Injecting events has |
+ * been unreliable for us and simulating the touch events in this manner is just as effective. |
+ * |
+ * @param v The view to be clicked. |
+ * @param x Relative x location to v |
+ * @param y Relative y location to v |
+ */ |
+ public void singleClickView(View v, int x, int y) { |
+ TouchCommon.singleClickView(v, x, y); |
+ } |
+ |
+ /** |
+ * Sends (synchronously) a single click to the center of the View. |
+ * |
+ * <p> |
+ * Differs from |
+ * {@link TestTouchUtils#singleClickView(android.app.Instrumentation, View)} |
+ * as this does not rely on injecting events into the different activity. Injecting events has |
+ * been unreliable for us and simulating the touch events in this manner is just as effective. |
+ * |
+ * @param v The view to be clicked. |
+ */ |
+ public void singleClickView(View v) { |
+ TouchCommon.singleClickView(v); |
+ } |
+ |
+ /** |
+ * Waits for {@link AsyncTask}'s that have been queued to finish. Note, this |
+ * only waits for tasks that have been started using the default |
+ * {@link java.util.concurrent.Executor}, which executes tasks serially. |
+ * |
+ * @param timeout how long to wait for tasks to complete |
+ */ |
+ public void waitForAsyncTasks(long timeout) throws InterruptedException { |
+ final Semaphore s = new Semaphore(0); |
+ new AsyncTask<Void, Void, Void>() { |
+ @Override |
+ protected Void doInBackground(Void... arg0) { |
+ s.release(); |
+ return null; |
+ } |
+ }.execute(); |
+ assertTrue(s.tryAcquire(timeout, TimeUnit.MILLISECONDS)); |
+ } |
+ |
+ /** |
+ * Navigates to a URL directly without going through the UrlBar. This bypasses the page |
+ * preloading mechanism of the UrlBar. |
+ * @param url The url to load in the current tab. |
+ * @return FULL_PRERENDERED_PAGE_LOAD or PARTIAL_PRERENDERED_PAGE_LOAD if the page has been |
+ * prerendered. DEFAULT_PAGE_LOAD if it had not. |
+ */ |
+ public int loadUrl(final String url) throws IllegalArgumentException, InterruptedException { |
+ return loadUrlInTab(url, PageTransition.TYPED | PageTransition.FROM_ADDRESS_BAR, |
+ getActivity().getActivityTab()); |
+ } |
+ |
+ /** |
+ * @param url The url of the page to load. |
+ * @param pageTransition The type of transition. see |
+ * {@link org.chromium.content.browser.PageTransition} |
+ * for valid values. |
+ * @param tab The tab to load the url into. |
+ * @return FULL_PRERENDERED_PAGE_LOAD or PARTIAL_PRERENDERED_PAGE_LOAD if the |
+ * page has been prerendered. DEFAULT_PAGE_LOAD if it had not. |
+ */ |
+ public int loadUrlInTab(final String url, final int pageTransition, final Tab tab) |
+ throws InterruptedException { |
+ assertNotNull("Cannot load the url in a null tab", tab); |
+ final AtomicInteger result = new AtomicInteger(); |
+ |
+ ChromeTabUtils.waitForTabPageLoaded(tab, new Runnable() { |
+ @Override |
+ public void run() { |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ result.set(tab.loadUrl( |
+ new LoadUrlParams(url, pageTransition))); |
+ } |
+ }); |
+ } |
+ }); |
+ getInstrumentation().waitForIdleSync(); |
+ return result.get(); |
+ } |
+ |
+ /** |
+ * Load a url in a new tab. The {@link Tab} will pretend to be created from a link. |
+ * @param url The url of the page to load. |
+ */ |
+ public void loadUrlInNewTab(final String url) throws InterruptedException { |
+ // TODO(mariakhomenko): There is no current tab creator in document mode, will need |
+ // additional logic here for Document tests. |
+ if (FeatureUtilities.isDocumentMode(getInstrumentation().getContext())) { |
+ fail("Document mode not yet supported."); |
+ } |
+ try { |
+ Tab tab = ThreadUtils.runOnUiThreadBlocking(new Callable<Tab>() { |
+ @Override |
+ public Tab call() throws Exception { |
+ return getActivity().getCurrentTabCreator() |
+ .launchUrl(url, TabLaunchType.FROM_LINK); |
+ } |
+ }); |
+ |
+ ChromeTabUtils.waitForTabPageLoaded(tab, url); |
+ getInstrumentation().waitForIdleSync(); |
+ } catch (ExecutionException e) { |
+ fail("Failed to create new tab"); |
+ } |
+ } |
+ |
+ /** |
+ * Load a url in a new tab. The {@link Tab} will pretend to be created from a link. |
+ * @param url The url of the page to load. |
+ * @param incognito Whether the new tab should be incognito. |
+ */ |
+ public void loadUrlInNewTab(final String url, final boolean incognito) |
+ throws InterruptedException { |
+ Tab tab = null; |
+ if (FeatureUtilities.isDocumentMode(getInstrumentation().getContext())) { |
+ Runnable activityTrigger = new Runnable() { |
+ @Override |
+ public void run() { |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ ChromeLauncherActivity.launchDocumentInstance(getActivity(), incognito, |
+ ChromeLauncherActivity.LAUNCH_MODE_FOREGROUND, url, |
+ DocumentMetricIds.STARTED_BY_UNKNOWN, |
+ PageTransition.AUTO_TOPLEVEL, |
+ false, null); |
+ } |
+ }); |
+ } |
+ }; |
+ final DocumentActivity activity = ActivityUtils.waitForActivity( |
+ getInstrumentation(), |
+ incognito ? IncognitoDocumentActivity.class : DocumentActivity.class, |
+ activityTrigger); |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return activity.getActivityTab() != null; |
+ } |
+ }); |
+ tab = activity.getActivityTab(); |
+ } else { |
+ try { |
+ tab = ThreadUtils.runOnUiThreadBlocking(new Callable<Tab>() { |
+ @Override |
+ public Tab call() throws Exception { |
+ return getActivity().getTabCreator(incognito) |
+ .launchUrl(url, TabLaunchType.FROM_LINK); |
+ } |
+ }); |
+ } catch (ExecutionException e) { |
+ fail("Failed to create new tab"); |
+ } |
+ } |
+ ChromeTabUtils.waitForTabPageLoaded(tab, url); |
+ getInstrumentation().waitForIdleSync(); |
+ } |
+ |
+ /** |
+ * Simulates starting Main Activity from launcher, blocks until it is started. |
+ */ |
+ protected void startMainActivityFromLauncher() throws InterruptedException { |
+ startMainActivityWithURL(null); |
+ } |
+ |
+ /** |
+ * Starts the Main activity on the specified URL. Passing a null URL ensures the default page is |
+ * loaded, which is the NTP with a new profile . |
+ */ |
+ protected void startMainActivityWithURL(String url) throws InterruptedException { |
+ // Only launch Chrome. |
+ Intent intent = new Intent( |
+ TextUtils.isEmpty(url) ? Intent.ACTION_MAIN : Intent.ACTION_VIEW); |
+ intent.addCategory(Intent.CATEGORY_LAUNCHER); |
+ startMainActivityFromIntent(intent, url); |
+ } |
+ |
+ /** |
+ * Starts the Main activity and open a blank page. |
+ * This is faster and less flakyness-prone than starting on the NTP. |
+ */ |
+ protected void startMainActivityOnBlankPage() throws InterruptedException { |
+ startMainActivityWithURL("about:blank"); |
+ } |
+ |
+ /** |
+ * Starts the Main activity as if it was started from an external application, on the specified |
+ * URL. |
+ */ |
+ protected void startMainActivityFromExternalApp(String url, String appId) |
+ throws InterruptedException { |
+ Intent intent = new Intent(Intent.ACTION_VIEW); |
+ if (appId != null) { |
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, appId); |
+ } |
+ startMainActivityFromIntent(intent, url); |
+ } |
+ |
+ /** |
+ * Starts the Main activity using the passed intent, and using the specified URL. |
+ * This method waits for DEFERRED_STARTUP to fire as well as a subsequent |
+ * idle-sync of the main looper thread, and the initial tab must either |
+ * complete its load or it must crash before this method will return. |
+ */ |
+ protected void startMainActivityFromIntent(Intent intent, String url) |
+ throws InterruptedException { |
+ prepareUrlIntent(intent, url); |
+ |
+ final boolean isDocumentMode = |
+ FeatureUtilities.isDocumentMode(getInstrumentation().getContext()); |
+ |
+ startActivityCompletely(intent); |
+ |
+ assertTrue("Tab never selected/initialized.", |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return getActivity().getActivityTab() != null; |
+ } |
+ })); |
+ Tab tab = getActivity().getActivityTab(); |
+ |
+ ChromeTabUtils.waitForTabPageLoaded(tab, (String) null); |
+ |
+ if (!isDocumentMode && tab != null && NewTabPage.isNTPUrl(tab.getUrl())) { |
+ boolean ntpReady = NewTabPageTestUtils.waitForNtpLoaded(tab); |
+ if (!ntpReady && tab.isShowingSadTab()) { |
+ fail("Renderer crashed before NTP finished loading. " |
+ + "Look at logcat for renderer stack dump."); |
+ } |
+ assertTrue("Initial NTP never fully loaded.", ntpReady); |
+ } |
+ |
+ assertTrue("Deferred startup never completed", |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return DeferredStartupHandler.getInstance().isDeferredStartupComplete(); |
+ } |
+ })); |
+ |
+ assertNotNull(tab); |
+ assertNotNull(tab.getView()); |
+ getInstrumentation().waitForIdleSync(); |
+ } |
+ |
+ /** |
+ * Prepares a URL intent to start the activity. |
+ * @param intent the intent to be modified |
+ * @param url the URL to be used (may be null) |
+ */ |
+ protected Intent prepareUrlIntent(Intent intent, String url) { |
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ intent.setComponent(new ComponentName(getInstrumentation().getTargetContext(), |
+ ChromeLauncherActivity.class)); |
+ |
+ if (url != null) { |
+ intent.setData(Uri.parse(url)); |
+ } |
+ |
+ try { |
+ Method method = getClass().getMethod(getName(), (Class[]) null); |
+ if (method.isAnnotationPresent(RenderProcessLimit.class)) { |
+ RenderProcessLimit limit = method.getAnnotation(RenderProcessLimit.class); |
+ intent.putExtra(ChromeTabbedActivity.INTENT_EXTRA_TEST_RENDER_PROCESS_LIMIT, |
+ limit.value()); |
+ } |
+ } catch (Exception ex) { |
+ // Ignore exception. |
+ } |
+ return intent; |
+ } |
+ |
+ /** |
+ * Open an incognito tab by invoking the 'new incognito' menu item. |
+ * Returns when receiving the 'PAGE_LOAD_FINISHED' notification. |
+ */ |
+ protected void newIncognitoTabFromMenu() throws InterruptedException { |
+ Tab tab = null; |
+ |
+ if (FeatureUtilities.isDocumentMode(getInstrumentation().getContext())) { |
+ final IncognitoDocumentActivity activity = ActivityUtils.waitForActivity( |
+ getInstrumentation(), IncognitoDocumentActivity.class, |
+ new Runnable() { |
+ @Override |
+ public void run() { |
+ MenuUtils.invokeCustomMenuActionSync( |
+ getInstrumentation(), getActivity(), |
+ R.id.new_incognito_tab_menu_id); |
+ } |
+ }); |
+ |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return activity.getActivityTab() != null; |
+ } |
+ }); |
+ |
+ tab = activity.getActivityTab(); |
+ } else { |
+ final CallbackHelper createdCallback = new CallbackHelper(); |
+ final CallbackHelper selectedCallback = new CallbackHelper(); |
+ |
+ TabModel incognitoTabModel = getActivity().getTabModelSelector().getModel(true); |
+ TabModelObserver observer = new EmptyTabModelObserver() { |
+ @Override |
+ public void didAddTab(Tab tab, TabLaunchType type) { |
+ createdCallback.notifyCalled(); |
+ } |
+ |
+ @Override |
+ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) { |
+ selectedCallback.notifyCalled(); |
+ } |
+ }; |
+ incognitoTabModel.addObserver(observer); |
+ |
+ MenuUtils.invokeCustomMenuActionSync(getInstrumentation(), getActivity(), |
+ R.id.new_incognito_tab_menu_id); |
+ |
+ try { |
+ createdCallback.waitForCallback(0); |
+ } catch (TimeoutException ex) { |
+ fail("Never received tab created event"); |
+ } |
+ try { |
+ selectedCallback.waitForCallback(0); |
+ } catch (TimeoutException ex) { |
+ fail("Never received tab selected event"); |
+ } |
+ incognitoTabModel.removeObserver(observer); |
+ |
+ tab = getActivity().getActivityTab(); |
+ } |
+ |
+ ChromeTabUtils.waitForTabPageLoaded(tab, (String) null); |
+ Assert.assertTrue("NTP never fully loaded.", |
+ NewTabPageTestUtils.waitForNtpLoaded(tab)); |
+ getInstrumentation().waitForIdleSync(); |
+ Log.d(TAG, "newIncognitoTabFromMenu <<"); |
+ } |
+ |
+ /** |
+ * New multiple incognito tabs by invoking the 'new incognito' menu item n times. |
+ * @param n The number of tabs you want to create. |
+ */ |
+ protected void newIncognitoTabsFromMenu(int n) |
+ throws InterruptedException { |
+ while (n > 0) { |
+ newIncognitoTabFromMenu(); |
+ --n; |
+ } |
+ } |
+ |
+ /** |
+ * @return The number of incognito tabs currently open. |
+ */ |
+ protected int incognitoTabsCount() { |
+ return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Integer>() { |
+ @Override |
+ public Integer call() { |
+ TabModelSelector tabModelSelector; |
+ if (FeatureUtilities.isDocumentMode(getInstrumentation().getContext())) { |
+ tabModelSelector = ChromeMobileApplication.getDocumentTabModelSelector(); |
+ } else { |
+ tabModelSelector = getActivity().getTabModelSelector(); |
+ } |
+ return tabModelSelector.getModel(true).getCount(); |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Types the passed text in the omnibox to trigger a navigation. You can pass a URL or a search |
+ * term. |
+ * <p> |
+ * Note that this code triggers suggestions and prerendering. Unless you are testing these |
+ * features specifically, you should use loadUrl() which is less prone to flakyness. |
+ * @param url The Url to navigate to. |
+ * @return the url in the UrlBar. |
+ * @throws InterruptedException |
+ */ |
+ public String typeInOmniboxAndNavigate(final String url) throws InterruptedException { |
+ final UrlBar urlBar = (UrlBar) getActivity().findViewById(R.id.url_bar); |
+ assertNotNull("urlBar is null", urlBar); |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ urlBar.requestFocus(); |
+ urlBar.setText(url); |
+ } |
+ }); |
+ final LocationBarLayout locationBar = |
+ (LocationBarLayout) getActivity().findViewById(R.id.location_bar); |
+ assertTrue("Omnibox Suggestions never shown.", |
+ OmniboxTestUtils.waitForOmniboxSuggestions(locationBar)); |
+ |
+ Tab currentTab = getActivity().getActivityTab(); |
+ final CallbackHelper loadedCallback = new CallbackHelper(); |
+ final AtomicBoolean tabCrashReceived = new AtomicBoolean(); |
+ currentTab.addObserver(new EmptyTabObserver() { |
+ @Override |
+ public void onPageLoadFinished(Tab tab) { |
+ loadedCallback.notifyCalled(); |
+ tab.removeObserver(this); |
+ } |
+ |
+ @Override |
+ public void onCrash(Tab tab, boolean sadTabShown) { |
+ tabCrashReceived.set(true); |
+ tab.removeObserver(this); |
+ } |
+ }); |
+ |
+ // Loads the url. |
+ KeyUtils.singleKeyEventView(getInstrumentation(), urlBar, KeyEvent.KEYCODE_ENTER); |
+ |
+ boolean pageLoadReceived = true; |
+ try { |
+ loadedCallback.waitForCallback(0); |
+ } catch (TimeoutException ex) { |
+ pageLoadReceived = false; |
+ } |
+ |
+ assertTrue("Neither PAGE_LOAD_FINISHED nor a TAB_CRASHED event was received", |
+ pageLoadReceived || tabCrashReceived.get()); |
+ getInstrumentation().waitForIdleSync(); |
+ |
+ // The title has been set before the page notification was broadcast, so it is safe to |
+ // access the title. |
+ return urlBar.getText().toString(); |
+ } |
+ |
+ /** |
+ * Looks up the Omnibox in the view hierarchy and types the specified |
+ * text into it, requesting focus and using an inter-character delay of |
+ * 200ms. |
+ * |
+ * @param oneCharAtATime Whether to type text one character at a time or all at once. |
+ * |
+ * @throws InterruptedException |
+ */ |
+ public void typeInOmnibox(final String text, final boolean oneCharAtATime) |
+ throws InterruptedException { |
+ final UrlBar urlBar = (UrlBar) getActivity().findViewById(R.id.url_bar); |
+ assertNotNull(urlBar); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ urlBar.requestFocus(); |
+ if (!oneCharAtATime) { |
+ urlBar.setText(text); |
+ } |
+ } |
+ }); |
+ |
+ if (oneCharAtATime) { |
+ final Instrumentation instrumentation = getInstrumentation(); |
+ for (int i = 0; i < text.length(); ++i) { |
+ instrumentation.sendStringSync(text.substring(i, i + 1)); |
+ // Let's put some delay between key strokes to simulate a user pressing the keys. |
+ Thread.sleep(20); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Searches for a given suggestion after typing given text in the Omnibox. |
+ * |
+ * @param inputText Input text to type into the Omnibox. |
+ * @param displayText Suggestion text expected to be found. Passing in null ignores this field. |
+ * @param url URL expected to be found. Passing in null ignores this field. |
+ * @param type Type of suggestion expected to be found. Passing in null ignores this field. |
+ * |
+ * @throws InterruptedException |
+ */ |
+ protected OmniboxSuggestion findOmniboxSuggestion(String inputText, String displayText, |
+ String url, OmniboxSuggestion.Type type) throws InterruptedException { |
+ long endTime = System.currentTimeMillis() + OMNIBOX_FIND_SUGGESTION_TIMEOUT_MS; |
+ |
+ // Multiple suggestion events may occur before the one we're interested in is received. |
+ final CallbackHelper onSuggestionsReceivedHelper = new CallbackHelper(); |
+ final LocationBarLayout locationBar = |
+ (LocationBarLayout) getActivity().findViewById(R.id.location_bar); |
+ locationBar.setAutocompleteController(new AutocompleteController(locationBar) { |
+ @Override |
+ public void onSuggestionsReceived( |
+ List<OmniboxSuggestion> suggestions, |
+ String inlineAutocompleteText, |
+ long currentNativeAutocompleteResult) { |
+ super.onSuggestionsReceived( |
+ suggestions, inlineAutocompleteText, currentNativeAutocompleteResult); |
+ onSuggestionsReceivedHelper.notifyCalled(); |
+ } |
+ }); |
+ |
+ try { |
+ typeInOmnibox(inputText, false); |
+ |
+ while (true) { |
+ try { |
+ int callbackCount = onSuggestionsReceivedHelper.getCallCount(); |
+ onSuggestionsReceivedHelper.waitForCallback( |
+ callbackCount, 1, |
+ endTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); |
+ } catch (TimeoutException exception) { |
+ return null; |
+ } |
+ |
+ // Wait for suggestions to show up. |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return ((LocationBarLayout) getActivity().findViewById( |
+ R.id.location_bar)).getSuggestionList() != null; |
+ } |
+ }, 3000, 10)); |
+ final ListView suggestionListView = locationBar.getSuggestionList(); |
+ OmniboxResultItem popupItem = (OmniboxResultItem) suggestionListView |
+ .getItemAtPosition(0); |
+ OmniboxSuggestion suggestion = popupItem.getSuggestion(); |
+ if (suggestionListView.getCount() == 1 |
+ && suggestion.getDisplayText().equals(inputText) |
+ && !suggestion.getDisplayText().equals(displayText)) { |
+ // If there is only one suggestion and it's the same as inputText, |
+ // wait for other suggestions before looking for the one we want. |
+ CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return suggestionListView.getCount() > 1; |
+ } |
+ }, 3000, 10); |
+ } |
+ int count = suggestionListView.getCount(); |
+ for (int i = 0; i < count; i++) { |
+ popupItem = (OmniboxResultItem) suggestionListView.getItemAtPosition(i); |
+ suggestion = popupItem.getSuggestion(); |
+ if (type != null && suggestion.getType() != type) { |
+ continue; |
+ } |
+ if (displayText != null && !suggestion.getDisplayText().equals(displayText)) { |
+ continue; |
+ } |
+ if (url != null && !suggestion.getUrl().equals(url)) { |
+ continue; |
+ } |
+ return suggestion; |
+ } |
+ } |
+ } finally { |
+ locationBar.setAutocompleteController(new AutocompleteController(locationBar)); |
+ } |
+ } |
+ |
+ /** |
+ * Returns the infobars being displayed by the current tab, or null if they don't exist. |
+ */ |
+ protected List<InfoBar> getInfoBars() { |
+ Tab currentTab = getActivity().getActivityTab(); |
+ if (currentTab == null) { |
+ return null; |
+ } |
+ |
+ if (currentTab.getInfoBarContainer() != null) { |
+ return currentTab.getInfoBarContainer().getInfoBars(); |
+ } else { |
+ return null; |
+ } |
+ } |
+ |
+ /** |
+ * Launches the preferences menu and starts the preferences activity named fragmentName. |
+ * Returns the activity that was started. |
+ */ |
+ protected Preferences startPreferences(String fragmentName) { |
+ Context context = getInstrumentation().getTargetContext(); |
+ Intent intent = PreferencesLauncher.createIntentForSettingsPage(context, fragmentName); |
+ Activity activity = getInstrumentation().startActivitySync(intent); |
+ assertTrue(activity instanceof Preferences); |
+ return (Preferences) activity; |
+ } |
+ |
+ /** |
+ * Executes the given snippet of JavaScript code within the current tab. Returns the result of |
+ * its execution in JSON format. |
+ * @throws InterruptedException |
+ */ |
+ protected String runJavaScriptCodeInCurrentTab(String code) throws InterruptedException, |
+ TimeoutException { |
+ return JavaScriptUtils.executeJavaScriptAndWaitForResult( |
+ getActivity().getCurrentContentViewCore().getWebContents(), code); |
+ } |
+ |
+ @Override |
+ protected void runTest() throws Throwable { |
+ boolean shouldRun = true; |
+ String perfTagAnalysisString = ""; |
+ try { |
+ shouldRun = RestrictedInstrumentationTestCase.shouldRunTest(this); |
+ perfTagAnalysisString = setupPotentialPerfTest(shouldRun); |
+ } catch (Exception e) { |
+ // eat the exception here; super.runTest() will catch it again and handle it properly |
+ } |
+ |
+ if (shouldRun) super.runTest(); |
+ |
+ endPerfTest(perfTagAnalysisString); |
+ } |
+ |
+ /** |
+ * Waits till the ContentViewCore receives the expected page scale factor |
+ * from the compositor and asserts that this happens. |
+ * |
+ * Upstream {@code ContentShellTestBase} has the same copy. Also, this is a temporary solution |
+ * for waiting a page load. Please refer to the bug at the upstream function. |
+ */ |
+ protected void assertWaitForPageScaleFactorMatch(final float expectedScale) |
+ throws InterruptedException { |
+ boolean scaleFactorMatch = CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return getActivity().getCurrentContentViewCore().getScale() |
+ == expectedScale; |
+ } |
+ }); |
+ assertTrue("Expecting scale factor of: " + expectedScale + ", got: " |
+ + getActivity().getCurrentContentViewCore().getScale(), scaleFactorMatch); |
+ } |
+ |
+ /** |
+ * This method creates a special string that tells the python test harness what |
+ * trace calls to track for this particular test run. It can support multiple trace calls for |
+ * each test and will make a new graph entry for all of them. It should be noted that this |
+ * method eats all exceptions. This is so that it can never be the cause of a test failure. |
+ * We still need to call this method even if we know the test will not run (ie: willTestRun is |
+ * false). This is because this method lets the python test harness know not to expect any |
+ * perf output in this case. In the case that the autoTrace parameter is set for the current |
+ * test method, this will also start the PerfTrace facility automatically. |
+ * |
+ * @param willTestRun Whether or not this test will actually be run. |
+ * @return A specially formatted string that contains which JSON perf markers to look at. This |
+ * will be analyzed by the perf test harness. |
+ */ |
+ @SuppressFBWarnings({ |
+ "REC_CATCH_EXCEPTION", |
+ "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", |
+ }) |
+ private String setupPotentialPerfTest(boolean willTestRun) { |
+ File perfFile = getInstrumentation().getTargetContext().getFileStreamPath( |
+ PERF_OUTPUT_FILE); |
+ perfFile.delete(); |
+ PerfTraceEvent.setOutputFile(perfFile); |
+ |
+ String perfAnnotationString = ""; |
+ |
+ try { |
+ Method method = getClass().getMethod(getName(), (Class[]) null); |
+ PerfTest annotation = method.getAnnotation(PerfTest.class); |
+ if (annotation != null) { |
+ StringBuilder annotationData = new StringBuilder(); |
+ annotationData.append(String.format(PERF_ANNOTATION_FORMAT, method.getName())); |
+ |
+ if (!willTestRun) { |
+ annotationData.append(PERF_NORUN_TAG); |
+ } else { |
+ // Grab the minimum number of trace calls we will track (if names(), |
+ // graphNames(), and graphValues() do not have the same number of elements, we |
+ // will track as many as we can given the data available. |
+ final int maxIndex = Math.min(annotation.traceNames().length, Math.min( |
+ annotation.graphNames().length, annotation.seriesNames().length)); |
+ |
+ List<String> allNames = new LinkedList<String>(); |
+ for (int i = 0; i < maxIndex; ++i) { |
+ // Prune out all of ',' and ';' from the strings. Replace them with '-'. |
+ String name = annotation.traceNames()[i].replaceAll("[,;]", "-"); |
+ allNames.add(name); |
+ String graphName = annotation.graphNames()[i].replaceAll("[,;]", "-"); |
+ String seriesName = annotation.seriesNames()[i].replaceAll("[,;]", "-"); |
+ if (annotation.traceTiming()) { |
+ annotationData.append(name).append(",") |
+ .append(graphName).append(",") |
+ .append(seriesName).append(';'); |
+ } |
+ |
+ // If memory tracing is enabled, add an additional graph for each one |
+ // defined to track timing perf that will track the corresponding memory |
+ // usage. |
+ // Keep the series name the same, but just append a memory identifying |
+ // prefix to the graph. |
+ if (annotation.traceMemory()) { |
+ String memName = PerfTraceEvent.makeMemoryTraceNameFromTimingName(name); |
+ String memGraphName = PerfTraceEvent.makeSafeTraceName( |
+ graphName, MEMORY_TRACE_GRAPH_SUFFIX); |
+ annotationData.append(memName).append(",") |
+ .append(memGraphName).append(",") |
+ .append(seriesName).append(';'); |
+ allNames.add(memName); |
+ } |
+ } |
+ // We only record perf trace events for the names explicitly listed. |
+ PerfTraceEvent.setFilter(allNames); |
+ |
+ // Figure out if we should automatically start or stop the trace. |
+ if (annotation.autoTrace()) { |
+ PerfTraceEvent.setEnabled(true); |
+ } |
+ PerfTraceEvent.setTimingTrackingEnabled(annotation.traceTiming()); |
+ PerfTraceEvent.setMemoryTrackingEnabled(annotation.traceMemory()); |
+ } |
+ |
+ perfAnnotationString = annotationData.toString(); |
+ } |
+ } catch (Exception ex) { |
+ // Eat exception here. |
+ } |
+ |
+ return perfAnnotationString; |
+ } |
+ |
+ /** |
+ * This handles cleaning up the performance component of this test if it was a UI Perf test. |
+ * This includes potentially shutting down PerfTraceEvent. This method eats all exceptions so |
+ * that it can never be the cause of a test failure. The test harness will wait for |
+ * {@code perfTagAnalysisString} to show up in the logcat before processing the JSON perf file, |
+ * giving this method the chance to flush and dump the performance data before the harness reads |
+ * it. |
+ * |
+ * @param perfTagAnalysisString A specially formatted string that tells the perf test harness |
+ * which perf tags to analyze. |
+ */ |
+ private void endPerfTest(String perfTagAnalysisString) { |
+ try { |
+ Method method = getClass().getMethod(getName(), (Class[]) null); |
+ PerfTest annotation = method.getAnnotation(PerfTest.class); |
+ if (annotation != null) { |
+ if (PerfTraceEvent.enabled()) { |
+ PerfTraceEvent.setEnabled(false); |
+ } |
+ |
+ System.out.println(perfTagAnalysisString); |
+ } |
+ } catch (Exception ex) { |
+ // Eat exception here. |
+ } |
+ } |
+} |