blob: 45ab0b339a2fa2d2206ed58595b99ab2b1300740 [file] [log] [blame]
// 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.browser.contextualsearch;
import static org.junit.Assert.assertNotNull;
import static org.chromium.base.test.util.CriteriaHelper.DEFAULT_POLLING_INTERVAL;
import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.waitForSecondChromeTabbedActivity;
import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.waitForTabs;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Point;
import android.os.Build;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewConfiguration;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import com.google.common.collect.ImmutableMap;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ContextUtils;
import org.chromium.base.FeatureList;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask;
import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterProvider;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.FlakyTest;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UserActionTester;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeTabbedActivity2;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.compositor.bottombar.OverlayContentDelegate;
import org.chromium.chrome.browser.compositor.bottombar.OverlayContentProgressObserver;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchBarControl;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchImageControl;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchQuickActionControl;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFakeServer.ContextualSearchTestHost;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFakeServer.FakeResolveSearch;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFakeServer.FakeSlowResolveSearch;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchInternalStateController.InternalState;
import org.chromium.chrome.browser.contextualsearch.ResolvedSearchTerm.CardTag;
import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
import org.chromium.chrome.browser.findinpage.FindToolbar;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.gsa.GSAContextDisplaySelection;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.locale.LocaleManagerDelegate;
import org.chromium.chrome.browser.omnibox.UrlBar;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabCreationState;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.FullscreenTestUtils;
import org.chromium.chrome.test.util.MenuUtils;
import org.chromium.chrome.test.util.OmniboxTestUtils;
import org.chromium.components.browser_ui.widget.chips.ChipProperties;
import org.chromium.components.external_intents.ExternalNavigationHandler;
import org.chromium.components.navigation_interception.NavigationParams;
import org.chromium.content_public.browser.SelectionClient;
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.KeyUtils;
import org.chromium.content_public.browser.test.util.TestSelectionPopupController;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.test.util.UiDisableIf;
import org.chromium.ui.test.util.UiRestriction;
import org.chromium.ui.touch_selection.SelectionEventType;
import org.chromium.url.GURL;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
// TODO(donnd): Create class with limited API to encapsulate the internals of simulations.
// TODO(donnd): Separate tests into different classes grouped by type of tests. Examples:
// Gestures (Tap, Long-press), Search Term Resolution (resolves, expand selection, prevent preload,
// translation), Panel interaction (tap, fling up/down, close), Content (creation, loading,
// visibility, history, delayed load), Tab Promotion, Policy (add tests to check if policies
// affect the behavior correctly), General (remaining tests), etc.
/**
* Tests the Contextual Search Manager using instrumentation tests.
*/
@RunWith(ParameterizedRunner.class)
@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
// NOTE: Disable online detection so we we'll default to online on test bots with no network.
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ContextualSearchFieldTrial.ONLINE_DETECTION_DISABLED,
"disable-features=" + ChromeFeatureList.CONTEXTUAL_SEARCH_ML_TAP_SUPPRESSION + ","
+ ChromeFeatureList.CONTEXTUAL_SEARCH_THIN_WEB_VIEW_IMPLEMENTATION})
@Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
@Batch(Batch.PER_CLASS)
public class ContextualSearchManagerTest {
@ClassRule
public static final ChromeTabbedActivityTestRule sActivityTestRule =
new ChromeTabbedActivityTestRule();
@Rule
public final BlankCTATabInitialStateRule mInitialStateRule =
new BlankCTATabInitialStateRule(sActivityTestRule, false);
/** Parameter provider for enabling/disabling triggering-related Features. */
public static class FeatureParamProvider implements ParameterProvider {
@Override
public Iterable<ParameterSet> getParameters() {
return Arrays.asList(new ParameterSet().value(EnabledFeature.NONE).name("default"),
new ParameterSet()
.value(EnabledFeature.TRANSLATIONS)
.name("enableTranslations"));
}
}
private static final String TAG = "SearchManagerTest";
private static final String TEST_PAGE =
"/chrome/test/data/android/contextualsearch/tap_test.html";
private static final int TEST_TIMEOUT = 15000;
private static final int TEST_EXPECTED_FAILURE_TIMEOUT = 1000;
private static final int PANEL_INTERACTION_MAX_RETRIES = 3;
private static final int PANEL_INTERACTION_RETRY_DELAY_MS = 200;
// TODO(donnd): get these from TemplateURL once the low-priority or Contextual Search API
// is fully supported.
private static final String NORMAL_PRIORITY_SEARCH_ENDPOINT = "/search?";
private static final String LOW_PRIORITY_SEARCH_ENDPOINT = "/s?";
private static final String LOW_PRIORITY_INVALID_SEARCH_ENDPOINT = "/s/invalid";
private static final String CONTEXTUAL_SEARCH_PREFETCH_PARAM = "&pf=c";
// DOM element IDs in our test page based on what functions they trigger.
// TODO(donnd): add more, and also the associated Search Term, or build a similar mapping.
/**
* The DOM node for the word "search" on the test page, which causes a plain search response
* with the Search Term "Search" from the Fake server.
*/
private static final String SIMPLE_SEARCH_NODE_ID = "search";
/**
* The DOM node for the word "intelligence" on the test page, which causes a search response
* for the Search Term "Intelligence" and also includes Related Searches suggestions.
*/
private static final String RELATED_SEARCHES_NODE_ID = "intelligence";
/**
* Feature maps that we use for parameterized tests.
*/
/** This represents the current fully-launched configuration. */
private static final ImmutableMap<String, Boolean> ENABLE_NONE =
ImmutableMap.of(ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE, false,
ChromeFeatureList.CONTEXTUAL_SEARCH_LITERAL_SEARCH_TAP, false,
ChromeFeatureList.CONTEXTUAL_SEARCH_TRANSLATIONS, false);
/**
* This represents the Translations addition to the Longpress with LiteralTap configuration.
* This is likely the best launch candidate.
*/
private static final ImmutableMap<String, Boolean> ENABLE_TRANSLATIONS =
ImmutableMap.of(ChromeFeatureList.CONTEXTUAL_SEARCH_LONGPRESS_RESOLVE, false,
ChromeFeatureList.CONTEXTUAL_SEARCH_LITERAL_SEARCH_TAP, true,
ChromeFeatureList.CONTEXTUAL_SEARCH_TRANSLATIONS, true);
/** Feature maps that we use for individual tests. */
private static final ImmutableMap<String, Boolean> ENABLE_RELATED_SEARCHES = ImmutableMap.of(
ChromeFeatureList.RELATED_SEARCHES, true, ChromeFeatureList.RELATED_SEARCHES_UI, false);
private static final ImmutableMap<String, Boolean> ENABLE_RELATED_SEARCHES_UI = ImmutableMap.of(
ChromeFeatureList.RELATED_SEARCHES, true, ChromeFeatureList.RELATED_SEARCHES_UI, true);
private static final ImmutableMap<String, Boolean> ENABLE_RELATED_SEARCHES_IN_BAR =
ImmutableMap.of(ChromeFeatureList.RELATED_SEARCHES, true,
ChromeFeatureList.RELATED_SEARCHES_UI, true,
ChromeFeatureList.RELATED_SEARCHES_IN_BAR, true);
private static final ImmutableMap<String, Boolean> ENABLE_RELATED_SEARCHES_IN_PANEL =
ImmutableMap.of(ChromeFeatureList.RELATED_SEARCHES, true,
ChromeFeatureList.RELATED_SEARCHES_UI, true,
ChromeFeatureList.RELATED_SEARCHES_ALTERNATE_UX, true);
private static final ImmutableMap<String, Boolean> DISABLE_FORCE_CAPTION =
ImmutableMap.of(ChromeFeatureList.CONTEXTUAL_SEARCH_FORCE_CAPTION, false);
private static final ImmutableMap<String, Boolean> ENABLE_FORCE_CAPTION =
ImmutableMap.of(ChromeFeatureList.CONTEXTUAL_SEARCH_FORCE_CAPTION, true);
private ActivityMonitor mActivityMonitor;
private ContextualSearchFakeServer mFakeServer;
private ContextualSearchManager mManager;
private ContextualSearchPanel mPanel;
private ContextualSearchPolicy mPolicy;
private ContextualSearchSelectionController mSelectionController;
private EmbeddedTestServer mTestServer;
private ContextualSearchManagerTestHost mTestHost;
private UserActionTester mActionTester;
private float mDpToPx;
// State for an individual test.
private FakeSlowResolveSearch mLatestSlowResolveSearch;
@IntDef({EnabledFeature.NONE, EnabledFeature.TRANSLATIONS})
@Retention(RetentionPolicy.SOURCE)
private @interface EnabledFeature {
int NONE = 0;
int TRANSLATIONS = 1;
}
// Tracks whether a long-press triggering experiment is active.
private @EnabledFeature int mEnabledFeature;
@ParameterAnnotations.UseMethodParameterBefore(FeatureParamProvider.class)
public void setFeatureParameterForTest(@EnabledFeature int enabledFeature) {
mEnabledFeature = enabledFeature;
}
@Before
public void setUp() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> {
LocaleManager.getInstance().setDelegateForTest(new LocaleManagerDelegate() {
@Override
public boolean needToCheckForSearchEnginePromo() {
return false;
}
});
});
mTestServer = sActivityTestRule.getTestServer();
sActivityTestRule.loadUrl(mTestServer.getURL(TEST_PAGE));
mManager = sActivityTestRule.getActivity().getContextualSearchManager();
mTestHost = new ContextualSearchManagerTestHost();
Assert.assertNotNull(mManager);
mPanel = (ContextualSearchPanel) mManager.getContextualSearchPanel();
mSelectionController = mManager.getSelectionController();
mPolicy = mManager.getContextualSearchPolicy();
mPolicy.overrideDecidedStateForTesting(true);
mSelectionController.setPolicy(mPolicy);
resetCounters();
mFakeServer = new ContextualSearchFakeServer(mPolicy, mTestHost, mManager,
mManager.getOverlayContentDelegate(), new OverlayContentProgressObserver(),
sActivityTestRule.getActivity());
mPanel.setOverlayPanelContentFactory(mFakeServer);
mManager.setNetworkCommunicator(mFakeServer);
mPolicy.setNetworkCommunicator(mFakeServer);
registerFakeSearches();
IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
filter.addCategory(Intent.CATEGORY_BROWSABLE);
filter.addDataScheme("market");
mActivityMonitor = InstrumentationRegistry.getInstrumentation().addMonitor(
filter, new Instrumentation.ActivityResult(Activity.RESULT_OK, null), true);
mDpToPx = sActivityTestRule.getActivity().getResources().getDisplayMetrics().density;
// Set the test Features map for all tests regardless of whether they are parameterized.
// Non-parameterized tests typically override this setting by calling setTestFeatures
// again.
ImmutableMap<String, Boolean> whichFeature = null;
switch (mEnabledFeature) {
case EnabledFeature.NONE:
whichFeature = ENABLE_NONE;
break;
case EnabledFeature.TRANSLATIONS:
whichFeature = ENABLE_TRANSLATIONS;
break;
}
Assert.assertNotNull(
"Did you change test Features without setting the correct Map?", whichFeature);
FeatureList.setTestFeatures(whichFeature);
}
@After
public void tearDown() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mManager.dismissContextualSearchBar();
mPanel.closePanel(StateChangeReason.UNKNOWN, false);
});
InstrumentationRegistry.getInstrumentation().removeMonitor(mActivityMonitor);
mActivityMonitor = null;
mLatestSlowResolveSearch = null;
if (mActionTester != null) mActionTester.tearDown();
FeatureList.setTestFeatures(null);
CompositorAnimationHandler.setTestingMode(false);
}
private class ContextualSearchManagerTestHost implements ContextualSearchTestHost {
@Override
public void triggerNonResolve(String nodeId) throws TimeoutException {
// TODO(donnd): remove support for the LiteralSearchTap Feature.
if (mPolicy.isLiteralSearchTapEnabled()) {
clickWordNode(nodeId);
} else if (!mPolicy.canResolveLongpress()) {
longPressNode(nodeId);
} else {
Assert.fail(
"Cannot trigger a non-resolving gesture with literal tap or non-resolve!");
}
}
@Override
public void triggerResolve(String nodeId) throws TimeoutException {
if (mPolicy.canResolveLongpress()) {
longPressNode(nodeId);
} else {
// When tap can trigger a resolve, we use a tap (aka click).
clickWordNode(nodeId);
}
}
@Override
public void waitForSelectionToBe(final String text) {
CriteriaHelper.pollInstrumentationThread(() -> {
Criteria.checkThat(getSelectedText(), Matchers.is(text));
}, TEST_TIMEOUT, DEFAULT_POLLING_INTERVAL);
}
@Override
public void waitForSearchTermResolutionToStart(final FakeResolveSearch search) {
CriteriaHelper.pollInstrumentationThread(
()
-> { return search.didStartSearchTermResolution(); },
"Fake Search Term Resolution never started.", TEST_TIMEOUT,
DEFAULT_POLLING_INTERVAL);
}
@Override
public void waitForSearchTermResolutionToFinish(final FakeResolveSearch search) {
CriteriaHelper.pollInstrumentationThread(() -> {
return search.didFinishSearchTermResolution();
}, "Fake Search was never ready.", TEST_TIMEOUT, DEFAULT_POLLING_INTERVAL);
}
@Override
public ContextualSearchPanel getPanel() {
return mPanel;
}
}
/**
* Gets the name of the given outcome when it's expected to be logged.
* @param feature A feature whose name we want.
* @return The name of the outcome if the give parameter is an outcome, or {@code null} if it's
* not.
*/
private static final String expectedOutcomeName(
@ContextualSearchInteractionRecorder.Feature int feature) {
switch (feature) {
// We don't log whether the quick action was clicked unless we actually have a
// quick action.
case ContextualSearchInteractionRecorder.Feature.OUTCOME_WAS_QUICK_ACTION_CLICKED:
return null;
default:
return ContextualSearchRankerLoggerImpl.outcomeName(feature);
}
}
/**
* Gets the name of the given feature when it's expected to be logged.
* @param feature An outcome that might have been expected to be logged.
* @return The name of the outcome if it's expected to be logged, or {@code null} if it's not
* expected to be logged.
*/
private static final String expectedFeatureName(
@ContextualSearchInteractionRecorder.Feature int feature) {
switch (feature) {
// We don't log previous user impressions and CTR if not available for the
// current user.
case ContextualSearchInteractionRecorder.Feature.PREVIOUS_WEEK_CTR_PERCENT:
case ContextualSearchInteractionRecorder.Feature.PREVIOUS_WEEK_IMPRESSIONS_COUNT:
case ContextualSearchInteractionRecorder.Feature.PREVIOUS_28DAY_CTR_PERCENT:
case ContextualSearchInteractionRecorder.Feature.PREVIOUS_28DAY_IMPRESSIONS_COUNT:
return null;
default:
return ContextualSearchRankerLoggerImpl.featureName(feature);
}
}
/**
* Sets the online status and reloads the current Tab with our test URL.
* @param isOnline Whether to go online.
*/
private void setOnlineStatusAndReload(boolean isOnline) {
mFakeServer.setIsOnline(isOnline);
final String testUrl = mTestServer.getURL(TEST_PAGE);
final Tab tab = sActivityTestRule.getActivity().getActivityTab();
TestThreadUtils.runOnUiThreadBlocking(() -> tab.reload());
// Make sure the page is fully loaded.
ChromeTabUtils.waitForTabPageLoaded(tab, testUrl);
}
private interface ThrowingRunnable {
void run() throws TimeoutException;
}
// Panel interactions are flaky, see crbug.com/635661. Rather than adding a long delay to
// each test, we can retry failures. When trying to make the panel peak, we may also have to
// clear the selection before trying again.
private void retryPanelBarInteractions(ThrowingRunnable r, boolean clearSelection)
throws AssertionError, TimeoutException {
int tries = 0;
boolean success = false;
while (!success) {
tries++;
try {
r.run();
success = true;
} catch (AssertionError | TimeoutException e) {
if (tries > PANEL_INTERACTION_MAX_RETRIES) {
throw e;
} else {
Log.e(TAG, "Failed to peek panel bar, trying again.", e);
if (clearSelection) clearSelection();
try {
Thread.sleep(PANEL_INTERACTION_RETRY_DELAY_MS);
} catch (InterruptedException ex) {
}
}
}
}
}
private void clearSelection() {
ThreadUtils.runOnUiThreadBlocking(() -> {
SelectionPopupController.fromWebContents(sActivityTestRule.getWebContents())
.clearSelection();
});
}
//============================================================================================
// Public API
//============================================================================================
/**
* Simulates a long-press on the given node without waiting for the panel to respond.
* @param nodeId A string containing the node ID.
*/
public void longPressNodeWithoutWaiting(String nodeId) throws TimeoutException {
Tab tab = sActivityTestRule.getActivity().getActivityTab();
DOMUtils.longPressNode(tab.getWebContents(), nodeId);
}
/**
* Simulates a long-press on the given node and waits for the panel to peek.
* @param nodeId A string containing the node ID.
*/
public void longPressNode(String nodeId) throws TimeoutException {
retryPanelBarInteractions(() -> {
longPressNodeWithoutWaiting(nodeId);
waitForPanelToPeek();
}, true);
}
/**
* Simulates a resolving trigger on the given node but does not wait for the panel to peek.
* @param nodeId A string containing the node ID.
*/
private void triggerResolve(String nodeId) throws TimeoutException {
mTestHost.triggerResolve(nodeId);
}
/**
* Simulates a non-resolve trigger on the given node and waits for the panel to peek.
* @param nodeId A string containing the node ID.
*/
private void triggerNonResolve(String nodeId) throws TimeoutException {
mTestHost.triggerNonResolve(nodeId);
}
/**
* Long-press a node without completing the action, by keeping the touch down by not letting up.
* @param nodeId The ID of the node to touch
* @return A time stamp to use with {@link #longPressExtendSelection}
* @throws TimeoutException
* @see #longPressExtendSelection
*/
public long longPressNodeWithoutUp(String nodeId) throws TimeoutException {
long downTime = SystemClock.uptimeMillis();
Tab tab = sActivityTestRule.getActivity().getActivityTab();
DOMUtils.longPressNodeWithoutUp(tab.getWebContents(), nodeId, downTime);
waitForSelectActionBarVisible();
waitForPanelToPeek();
return downTime;
}
/**
* Extends a Long-press selection by completing a drag action.
* @param startNodeId The ID of the node that has already been touched
* @param endNodeId The ID of the node that the touch should be extended to
* @param downTime A time stamp returned by {@link #longPressNodeWithoutUp}
* @throws TimeoutException
* @see #longPressNodeWithoutUp
*/
public void longPressExtendSelection(String startNodeId, String endNodeId, long downTime)
throws TimeoutException {
// TODO(donnd): figure out why we need this one line here, and why the selection does not
// match our expected nodes!
longPressNodeWithoutUp("term");
// Drag to the specified position by a DOM node id.
int stepCount = 100;
Tab tab = sActivityTestRule.getActivity().getActivityTab();
DOMUtils.dragNodeTo(tab.getWebContents(), startNodeId, endNodeId, stepCount, downTime);
DOMUtils.dragNodeEnd(tab.getWebContents(), endNodeId, downTime);
// Make sure the selection controller knows we did a drag.
// TODO(donnd): figure out how to reliably simulate a drag on all platforms.
float unused = 0.0f;
@SelectionEventType
int dragStoppedEvent = SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED;
TestThreadUtils.runOnUiThreadBlocking(
() -> mSelectionController.handleSelectionEvent(dragStoppedEvent, unused, unused));
waitForSelectActionBarVisible();
}
/**
* Simulates a click on the given node.
* @param nodeId A string containing the node ID.
*/
public void clickNode(String nodeId) throws TimeoutException {
Tab tab = sActivityTestRule.getActivity().getActivityTab();
DOMUtils.clickNode(tab.getWebContents(), nodeId);
}
/**
* Waits for the selected text string to be the given string, and asserts.
* @param text The string to wait for the selection to become.
*/
private void waitForSelectionToBe(final String text) {
mTestHost.waitForSelectionToBe(text);
}
/**
* Waits for the Search Term Resolution to become ready.
* @param search A given FakeResolveSearch.
*/
private void waitForSearchTermResolutionToStart(final FakeResolveSearch search) {
mTestHost.waitForSearchTermResolutionToStart(search);
}
/**
* Waits for the Search Term Resolution to finish.
* @param search A given FakeResolveSearch.
*/
private void waitForSearchTermResolutionToFinish(final FakeResolveSearch search) {
mTestHost.waitForSearchTermResolutionToFinish(search);
}
/**
* Waits for a Normal priority URL to be loaded, or asserts that the load never happened.
* This is needed when we test with a live internet connection and an invalid url fails to
* load (as expected. See crbug.com/682953 for background.
*/
private void waitForNormalPriorityUrlLoaded() {
CriteriaHelper.pollInstrumentationThread(() -> {
Criteria.checkThat(mFakeServer.getLoadedUrl(), Matchers.notNullValue());
Criteria.checkThat(mFakeServer.getLoadedUrl(),
Matchers.containsString(NORMAL_PRIORITY_SEARCH_ENDPOINT));
}, TEST_TIMEOUT, DEFAULT_POLLING_INTERVAL);
}
/**
* Runs the given Runnable in the main thread.
* @param runnable The Runnable.
*/
public void runOnMainSync(Runnable runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
}
//============================================================================================
// Fake Searches Helpers
//============================================================================================
/**
* Simulates a non-resolving search.
*
* @param nodeId The id of the node to be triggered.
* @throws InterruptedException
* @throws TimeoutException
*/
private void simulateNonResolveSearch(String nodeId)
throws InterruptedException, TimeoutException {
ContextualSearchFakeServer.FakeNonResolveSearch search =
mFakeServer.getFakeNonResolveSearch(nodeId);
search.simulate();
waitForPanelToPeek();
}
/**
* Simulates a resolve-triggering search.
*
* @param nodeId The id of the node to be tapped.
* @throws InterruptedException
* @throws TimeoutException
*/
private FakeResolveSearch simulateResolveSearch(String nodeId)
throws InterruptedException, TimeoutException {
return simulateResolvableSearchAndAssertResolveAndPreload(nodeId, true);
}
/**
* Simulates a resolve-triggering gesture that may or may not actually resolve.
* If the gesture should Resolve, the resolve and preload are asserted, and vice versa.
*
* @param nodeId The id of the node to be tapped.
* @param isResolveExpected Whether a resolve is expected or not. Enforce by asserting.
* @throws InterruptedException
* @throws TimeoutException
*/
private FakeResolveSearch simulateResolvableSearchAndAssertResolveAndPreload(String nodeId,
boolean isResolveExpected) throws InterruptedException, TimeoutException {
FakeResolveSearch search = mFakeServer.getFakeResolveSearch(nodeId);
assertNotNull("Could not find FakeResolveSearch for node ID:" + nodeId, search);
search.simulate();
waitForPanelToPeek();
if (isResolveExpected) {
assertLoadedSearchTermMatches(search.getSearchTerm());
} else {
assertSearchTermNotRequested();
assertNoSearchesLoaded();
assertNoWebContents();
}
return search;
}
/**
* Simulates a resolving search with slow server response.
*
* @param nodeId The id of the node to be triggered.
* @throws InterruptedException
* @throws TimeoutException
*/
private void simulateSlowResolveSearch(String nodeId)
throws InterruptedException, TimeoutException {
mLatestSlowResolveSearch = mFakeServer.getFakeSlowResolveSearch(nodeId);
assertNotNull("Could not find FakeSlowResolveSearch for node ID:" + nodeId,
mLatestSlowResolveSearch);
mLatestSlowResolveSearch.simulate();
waitForPanelToPeek();
}
/**
* Simulates a slow response for the most recent {@link FakeSlowResolveSearch} set up
* by calling simulateSlowResolveSearch.
* @throws TimeoutException
* @throws InterruptedException
*/
private void simulateSlowResolveFinished() throws InterruptedException, TimeoutException {
// Allow the slow Resolution to finish, waiting for it to complete.
mLatestSlowResolveSearch.finishResolve();
assertLoadedSearchTermMatches(mLatestSlowResolveSearch.getSearchTerm());
}
/**
* Registers all fake searches to be used in tests.
*/
private void registerFakeSearches() throws Exception {
mFakeServer.registerFakeSearches();
}
//============================================================================================
// Fake Response
// TODO(donnd): remove these methods and use the new infrastructure instead.
//============================================================================================
/**
* Posts a fake response on the Main thread.
*/
private final class FakeResponseOnMainThread implements Runnable {
private final ResolvedSearchTerm mResolvedSearchTerm;
public FakeResponseOnMainThread(ResolvedSearchTerm resolvedSearchTerm) {
mResolvedSearchTerm = resolvedSearchTerm;
}
@Override
public void run() {
mFakeServer.handleSearchTermResolutionResponse(mResolvedSearchTerm);
}
}
/**
* Fakes a server response with the parameters given and startAdjust and endAdjust equal to 0.
* {@See ContextualSearchManager#handleSearchTermResolutionResponse}.
*/
private void fakeResponse(boolean isNetworkUnavailable, int responseCode, String searchTerm,
String displayText, String alternateTerm, boolean doPreventPreload) {
fakeResponse(new ResolvedSearchTerm
.Builder(isNetworkUnavailable, responseCode, searchTerm, displayText,
alternateTerm, doPreventPreload)
.build());
}
/**
* Fakes a server response with the parameters given.
* {@See ContextualSearchManager#handleSearchTermResolutionResponse}.
*/
private void fakeResponse(ResolvedSearchTerm resolvedSearchTerm) {
if (mFakeServer.getSearchTermRequested() != null) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(
new FakeResponseOnMainThread(resolvedSearchTerm));
}
}
//============================================================================================
// Content Helpers
//============================================================================================
/**
* @return The Panel's WebContents.
*/
private WebContents getPanelWebContents() {
return mPanel.getWebContents();
}
/**
* @return Whether the Panel's WebContents is visible.
*/
private boolean isWebContentsVisible() {
return mFakeServer.isContentVisible();
}
/**
* Asserts that the Panel's WebContents is created.
*/
private void assertWebContentsCreated() {
Assert.assertNotNull(getPanelWebContents());
}
/**
* Asserts that the Panel's WebContents is not created.
*/
private void assertNoWebContents() {
Assert.assertNull(getPanelWebContents());
}
/**
* Asserts that the Panel's WebContents is visible.
*/
private void assertWebContentsVisible() {
Assert.assertTrue(isWebContentsVisible());
}
/**
* Asserts that the Panel's WebContents.onShow() method was never called.
*/
private void assertNeverCalledWebContentsOnShow() {
Assert.assertFalse(mFakeServer.didEverCallWebContentsOnShow());
}
/**
* Asserts that the Panel's WebContents is created
*/
private void assertWebContentsCreatedButNeverMadeVisible() {
assertWebContentsCreated();
Assert.assertFalse(isWebContentsVisible());
assertNeverCalledWebContentsOnShow();
}
/**
* Fakes navigation of the Content View to the URL that was previously requested.
* @param isFailure whether the request resulted in a failure.
*/
private void fakeContentViewDidNavigate(boolean isFailure) {
String url = mFakeServer.getLoadedUrl();
mManager.getOverlayContentDelegate().onMainFrameNavigation(url, false, isFailure, false);
}
/**
* A SelectionPopupController that has some methods stubbed out for testing.
*/
private static final class StubbedSelectionPopupController
extends TestSelectionPopupController {
private boolean mIsFocusedNodeEditable;
public StubbedSelectionPopupController() {}
public void setIsFocusedNodeEditableForTest(boolean isFocusedNodeEditable) {
mIsFocusedNodeEditable = isFocusedNodeEditable;
}
@Override
public boolean isFocusedNodeEditable() {
return mIsFocusedNodeEditable;
}
}
//============================================================================================
// Other Helpers
// TODO(donnd): organize into sections.
//============================================================================================
/**
* Simulates a click on the given word node.
* Waits for the bar to peek.
* TODO(donnd): rename to include the waitForPanelToPeek semantic, or rename clickNode to
* clickNodeWithoutWaiting.
* @param nodeId A string containing the node ID.
*/
private void clickWordNode(String nodeId) throws TimeoutException {
retryPanelBarInteractions(() -> {
clickNode(nodeId);
waitForPanelToPeek();
}, true);
}
/**
* Simulates a simple gesture that could trigger a resolve on the given node in the given tab.
* @param tab The tab that contains the node to trigger (must be frontmost).
* @param nodeId A string containing the node ID.
*/
public void triggerNode(Tab tab, String nodeId) throws TimeoutException {
if (mPolicy.canResolveLongpress()) {
DOMUtils.longPressNode(tab.getWebContents(), nodeId);
} else {
DOMUtils.clickNode(tab.getWebContents(), nodeId);
}
}
/**
* Simulates a key press.
* @param keycode The key's code.
*/
private void pressKey(int keycode) {
KeyUtils.singleKeyEventActivity(InstrumentationRegistry.getInstrumentation(),
sActivityTestRule.getActivity(), keycode);
}
/**
* Simulates pressing back button.
*/
private void pressBackButton() {
pressKey(KeyEvent.KEYCODE_BACK);
}
/**
* @return The selected text.
*/
private String getSelectedText() {
return mSelectionController.getSelectedText();
}
/**
* Asserts that the loaded search term matches the provided value.
* @param searchTerm The provided search term.
*/
private void assertLoadedSearchTermMatches(String searchTerm) {
boolean doesMatch = false;
String loadedUrl = mFakeServer.getLoadedUrl();
doesMatch = loadedUrl != null && loadedUrl.contains("q=" + searchTerm);
String message =
loadedUrl == null ? "but there was no loaded URL!" : "in URL: " + loadedUrl;
Assert.assertTrue(
"Expected to find searchTerm '" + searchTerm + "', " + message, doesMatch);
}
/**
* Asserts that the given parameters are present in the most recently loaded URL.
*/
private void assertContainsParameters(String... terms) {
Assert.assertNotNull("Fake server didn't load a SERP URL", mFakeServer.getLoadedUrl());
for (String term : terms) {
Assert.assertTrue("Expected search term not found:" + term,
mFakeServer.getLoadedUrl().contains(term));
}
}
/**
* Asserts that a Search Term has been requested.
*/
private void assertSearchTermRequested() {
Assert.assertNotNull(mFakeServer.getSearchTermRequested());
}
/**
* Asserts that there has not been any Search Term requested.
*/
private void assertSearchTermNotRequested() {
Assert.assertNull(mFakeServer.getSearchTermRequested());
}
/**
* Asserts that the panel is currently closed or in an undefined state.
*/
private void assertPanelClosedOrUndefined() {
boolean success = false;
if (mPanel == null) {
success = true;
} else {
@PanelState
int panelState = mPanel.getPanelState();
success = panelState == PanelState.CLOSED || panelState == PanelState.UNDEFINED;
}
Assert.assertTrue(success);
}
/**
* Asserts that no URL has been loaded in the Overlay Panel.
*/
private void assertLoadedNoUrl() {
Assert.assertTrue("Requested a search or preload when none was expected!",
mFakeServer.getLoadedUrl() == null);
}
/**
* Asserts that a low-priority URL has been loaded in the Overlay Panel.
*/
private void assertLoadedLowPriorityUrl() {
String message = "Expected a low priority search request URL, but got "
+ (mFakeServer.getLoadedUrl() != null ? mFakeServer.getLoadedUrl() : "null");
Assert.assertTrue(message,
mFakeServer.getLoadedUrl() != null
&& mFakeServer.getLoadedUrl().contains(LOW_PRIORITY_SEARCH_ENDPOINT));
Assert.assertTrue("Low priority request does not have the required prefetch parameter!",
mFakeServer.getLoadedUrl() != null
&& mFakeServer.getLoadedUrl().contains(CONTEXTUAL_SEARCH_PREFETCH_PARAM));
}
/**
* Asserts that a low-priority URL that is intentionally invalid has been loaded in the Overlay
* Panel (in order to produce an error).
*/
private void assertLoadedLowPriorityInvalidUrl() {
String message = "Expected a low priority invalid search request URL, but got "
+ (String.valueOf(mFakeServer.getLoadedUrl()));
Assert.assertTrue(message,
mFakeServer.getLoadedUrl() != null
&& mFakeServer.getLoadedUrl().contains(
LOW_PRIORITY_INVALID_SEARCH_ENDPOINT));
Assert.assertTrue("Low priority request does not have the required prefetch parameter!",
mFakeServer.getLoadedUrl() != null
&& mFakeServer.getLoadedUrl().contains(CONTEXTUAL_SEARCH_PREFETCH_PARAM));
}
/**
* Asserts that a normal priority URL has been loaded in the Overlay Panel.
*/
private void assertLoadedNormalPriorityUrl() {
String message = "Expected a normal priority search request URL, but got "
+ (mFakeServer.getLoadedUrl() != null ? mFakeServer.getLoadedUrl() : "null");
Assert.assertTrue(message,
mFakeServer.getLoadedUrl() != null
&& mFakeServer.getLoadedUrl().contains(NORMAL_PRIORITY_SEARCH_ENDPOINT));
Assert.assertTrue(
"Normal priority request should not have the prefetch parameter, but did!",
mFakeServer.getLoadedUrl() != null
&& !mFakeServer.getLoadedUrl().contains(CONTEXTUAL_SEARCH_PREFETCH_PARAM));
}
/**
* Asserts that no URLs have been loaded in the Overlay Panel since the last
* {@link ContextualSearchFakeServer#reset}.
*/
private void assertNoSearchesLoaded() {
Assert.assertEquals(0, mFakeServer.getLoadedUrlCount());
assertLoadedNoUrl();
}
/**
* Asserts that a Search Term has been requested.
* @param isExactResolve Whether the Resolve request must be exact (non-expanding).
*/
private void assertExactResolve(boolean isExactResolve) {
Assert.assertEquals(isExactResolve, mFakeServer.getIsExactResolve());
}
/**
* Waits for the Search Panel (the Search Bar) to peek up from the bottom, and asserts that it
* did peek.
*/
private void waitForPanelToPeek() {
waitForPanelToEnterState(PanelState.PEEKED);
}
/**
* Waits for the Search Panel to expand, and asserts that it did expand.
*/
private void waitForPanelToExpand() {
waitForPanelToEnterState(PanelState.EXPANDED);
}
/**
* Waits for the Search Panel to maximize, and asserts that it did maximize.
*/
private void waitForPanelToMaximize() {
waitForPanelToEnterState(PanelState.MAXIMIZED);
}
/**
* Waits for the Search Panel to close, and asserts that it did close.
*/
private void waitForPanelToClose() {
waitForPanelToEnterState(PanelState.CLOSED);
}
/**
* Waits for the Search Panel to enter the given {@code PanelState} and assert.
* @param state The {@link PanelState} to wait for.
*/
private void waitForPanelToEnterState(final @PanelState int state) {
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(mPanel, Matchers.notNullValue());
Criteria.checkThat(mPanel.getPanelState(), Matchers.is(state));
Criteria.checkThat(mPanel.isHeightAnimationRunning(), Matchers.is(false));
});
}
/**
* Asserts that the panel is still in the given state and continues to stay that way
* for a while.
* Waits for a reasonable amount of time for the panel to change to a different state,
* and verifies that it did not change state while this method is executing.
* Note that it's quite possible for the panel to transition through some other state and
* back to the initial state before this method is called without that being detected,
* because this method only monitors state during its own execution.
* @param initialState The initial state of the panel at the beginning of an operation that
* should not change the panel state.
* @throws InterruptedException
*/
private void assertPanelStillInState(final @PanelState int initialState)
throws InterruptedException {
boolean didChangeState = false;
long startTime = SystemClock.uptimeMillis();
while (!didChangeState
&& SystemClock.uptimeMillis() - startTime < TEST_EXPECTED_FAILURE_TIMEOUT) {
Thread.sleep(DEFAULT_POLLING_INTERVAL);
didChangeState = mPanel.getPanelState() != initialState;
}
Assert.assertFalse(didChangeState);
}
/**
* Shorthand for a common sequence:
* 1) Waits for gesture processing,
* 2) Waits for the panel to close,
* 3) Asserts that there is no selection and that the panel closed.
*/
private void waitForGestureToClosePanelAndAssertNoSelection() {
waitForPanelToClose();
assertPanelClosedOrUndefined();
Assert.assertTrue(TextUtils.isEmpty(getSelectedText()));
}
/**
* Waits for the selection to be empty.
* Use this method any time a test repeatedly establishes and dissolves a selection to ensure
* that the selection has been completely dissolved before simulating the next selection event.
* This is needed because the renderer's notification of a selection going away is async,
* and a subsequent tap may think there's a current selection until it has been dissolved.
*/
private void waitForSelectionEmpty() {
CriteriaHelper.pollUiThread(
() -> mSelectionController.isSelectionEmpty(), "Selection never empty.");
}
/**
* Waits for the panel to close and then waits for the selection to dissolve.
*/
private void waitForPanelToCloseAndSelectionEmpty() {
waitForPanelToClose();
waitForSelectionEmpty();
}
private void waitToPreventDoubleTapRecognition() throws InterruptedException {
// Avoid issues with double-tap detection by ensuring sequential taps
// aren't treated as such. Double-tapping can also select words much as
// longpress, in turn showing the pins and preventing contextual tap
// refinement from nearby taps. The double-tap timeout is sufficiently
// short that this shouldn't conflict with tap refinement by the user.
int doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
Thread.sleep(doubleTapTimeout);
}
/**
* Generate a fling sequence from the given start/end X,Y percentages, for the given steps.
* Works in either landscape or portrait orientation.
*/
private void fling(float startX, float startY, float endX, float endY, int stepCount) {
Point size = new Point();
sActivityTestRule.getActivity().getWindowManager().getDefaultDisplay().getSize(size);
float dragStartX = size.x * startX;
float dragEndX = size.x * endX;
float dragStartY = size.y * startY;
float dragEndY = size.y * endY;
long downTime = SystemClock.uptimeMillis();
TouchCommon.dragStart(sActivityTestRule.getActivity(), dragStartX, dragStartY, downTime);
TouchCommon.dragTo(sActivityTestRule.getActivity(), dragStartX, dragEndX, dragStartY,
dragEndY, stepCount, downTime);
TouchCommon.dragEnd(sActivityTestRule.getActivity(), dragEndX, dragEndY, downTime);
}
/**
* Generate a swipe sequence from the given start/end X,Y percentages, for the given steps.
* Works in either landscape or portrait orientation.
*/
private void swipe(float startX, float startY, float endX, float endY, int stepCount) {
Point size = new Point();
sActivityTestRule.getActivity().getWindowManager().getDefaultDisplay().getSize(size);
float dragStartX = size.x * startX;
float dragEndX = size.x * endX;
float dragStartY = size.y * startY;
float dragEndY = size.y * endY;
int halfCount = stepCount / 2;
long downTime = SystemClock.uptimeMillis();
TouchCommon.dragStart(sActivityTestRule.getActivity(), dragStartX, dragStartY, downTime);
TouchCommon.dragTo(sActivityTestRule.getActivity(), dragStartX, dragEndX, dragStartY,
dragEndY, halfCount, downTime);
// Generate events in the stationary end position in order to simulate a "pause" in
// the movement, therefore preventing this gesture from being interpreted as a fling.
TouchCommon.dragTo(sActivityTestRule.getActivity(), dragEndX, dragEndX, dragEndY, dragEndY,
halfCount, downTime);
TouchCommon.dragEnd(sActivityTestRule.getActivity(), dragEndX, dragEndY, downTime);
}
/**
* Flings the panel up to its expanded state.
*/
private void flingPanelUp() {
fling(0.5f, 0.95f, 0.5f, 0.55f, 1000);
}
/**
* Swipes the panel down to its peeked state.
*/
private void swipePanelDown() {
swipe(0.5f, 0.55f, 0.5f, 0.95f, 100);
}
/**
* Scrolls the base page.
*/
private void scrollBasePage() {
fling(0.f, 0.75f, 0.f, 0.7f, 100);
}
/**
* Taps the base page near the top.
*/
private void tapBasePageToClosePanel() {
// TODO(donnd): This is not reliable. Find a better approach.
// This taps on the panel in an area that will be selected if the "intelligence" node has
// been tap-selected, and that will cause it to be long-press selected.
// We use the far right side to prevent simulating a tap on top of an
// existing long-press selection (the pins are a tap target). This might not work on RTL.
// We are using y == 0.35f because otherwise it will fail for long press cases.
// It might be better to get the position of the Panel and tap just about outside
// the Panel. I suspect some Flaky tests are caused by this problem (ones involving
// long press and trying to close with the bar peeking, with a long press selection
// established).
tapBasePage(0.95f, 0.35f);
waitForPanelToClose();
}
/**
* Taps the base page at the given x, y position.
*/
private void tapBasePage(float x, float y) {
View root = sActivityTestRule.getActivity().getWindow().getDecorView().getRootView();
x *= root.getWidth();
y *= root.getHeight();
TouchCommon.singleClickView(root, (int) x, (int) y);
}
/**
* Click various places to cause the panel to show, expand, then close.
*/
private void clickToExpandAndClosePanel() throws TimeoutException {
clickWordNode("states");
tapBarToExpandAndClosePanel();
waitForSelectionEmpty();
}
/**
* Tap on the peeking Bar to expand the panel, then close it.
*/
private void tapBarToExpandAndClosePanel() throws TimeoutException {
tapPeekingBarToExpandAndAssert();
closePanel();
}
/**
* Generate a click in the middle of panel's bar.
* TODO(donnd): Replace this method with panelBarClick since this appears to be unreliable.
*/
private void clickPanelBar() {
View root = sActivityTestRule.getActivity().getWindow().getDecorView().getRootView();
float tapX = ((mPanel.getOffsetX() + mPanel.getWidth()) / 2f) * mDpToPx;
float tapY = (mPanel.getOffsetY() + (mPanel.getBarContainerHeight() / 2f)) * mDpToPx;
TouchCommon.singleClickView(root, (int) tapX, (int) tapY);
}
/**
* Taps the peeking bar to expand the panel
*/
private void tapPeekingBarToExpandAndAssert() throws TimeoutException {
retryPanelBarInteractions(() -> {
clickPanelBar();
waitForPanelToExpand();
}, false);
}
/**
* Simple sequence useful for checking if a Search Request is prefetched.
* Resets the fake server and clicks near to cause a search, then closes the panel,
* which takes us back to the starting state except that the fake server knows
* if a prefetch occurred.
*/
private void clickToTriggerPrefetch() throws Exception {
mFakeServer.reset();
simulateResolveSearch("search");
closePanel();
waitForPanelToCloseAndSelectionEmpty();
}
/**
* Simple sequence to trigger, resolve, and prefetch. Verifies a prefetch occurred.
*/
private void triggerToResolveAndAssertPrefetch() throws Exception {
simulateSlowResolveSearch("states");
assertLoadedNoUrl();
assertSearchTermRequested();
simulateSlowResolveFinished();
}
/**
* Fakes a response to the Resolve request.
*/
private void fakeAResponse() {
fakeResponse(false, 200, "states", "United States Intelligence", "alternate-term", false);
waitForPanelToPeek();
assertLoadedLowPriorityUrl();
assertContainsParameters("states", "alternate-term");
}
/**
* Resets all the counters used, by resetting all shared preferences.
*/
private void resetCounters() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
boolean freStatus =
prefs.getBoolean(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, false);
prefs.edit()
.clear()
.putBoolean(ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, freStatus)
.apply();
});
}
/**
* Force the Panel to handle a click on open-in-a-new-tab icon.
*/
private void forceOpenTabIconClick() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mPanel.handleBarClick(mPanel.getOpenTabIconX() + mPanel.getOpenTabIconDimension() / 2,
mPanel.getBarHeight() / 2);
});
}
/**
* Force the Panel to close.
*/
private void closePanel() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> { mPanel.closePanel(StateChangeReason.UNKNOWN, false); });
}
/**
* Force the Panel to maximize.
*/
private void maximizePanel() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> { mPanel.maximizePanel(StateChangeReason.UNKNOWN); });
}
/**
* Force the Panel to peek.
*/
private void peekPanel() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> { mPanel.peekPanel(StateChangeReason.UNKNOWN); });
waitForPanelToPeek();
}
/**
* Waits for the Action Bar to be visible in response to a selection.
*/
private void waitForSelectActionBarVisible() {
assertWaitForSelectActionBarVisible(true);
}
/** Gets the Ranker Logger and asserts if we can't. **/
private ContextualSearchRankerLoggerImpl getRankerLogger() {
ContextualSearchRankerLoggerImpl rankerLogger =
(ContextualSearchRankerLoggerImpl) mManager.getRankerLogger();
Assert.assertNotNull(rankerLogger);
return rankerLogger;
}
/** @return The value of the given logged feature, or {@code null} if not logged. */
private Object loggedToRanker(@ContextualSearchInteractionRecorder.Feature int feature) {
return getRankerLogger().getFeaturesLogged().get(feature);
}
/** Asserts that all the expected features have been logged to Ranker. **/
private void assertLoggedAllExpectedFeaturesToRanker() {
for (int feature = 0; feature < ContextualSearchInteractionRecorder.Feature.NUM_ENTRIES;
feature++) {
if (expectedFeatureName(feature) != null) Assert.assertNotNull(loggedToRanker(feature));
}
}
/** Asserts that all the expected outcomes have been logged to Ranker. **/
private void assertLoggedAllExpectedOutcomesToRanker() {
for (int feature = 0; feature < ContextualSearchInteractionRecorder.Feature.NUM_ENTRIES;
feature++) {
if (expectedOutcomeName(feature) != null) {
Assert.assertNotNull("Expected this outcome to be logged: " + feature,
getRankerLogger().getOutcomesLogged().get(feature));
}
}
}
/**
* Monitor user action UMA recording operations.
*/
private static class UserActionMonitor extends UserActionTester {
// TODO(donnd): merge into UserActionTester. See https://crbug.com/1103757.
private Set<String> mUserActionPrefixes;
private Map<String, Integer> mUserActionCounts;
/** @param userActionPrefixes A set of plain prefix strings for user actions to monitor. */
UserActionMonitor(Set<String> userActionPrefixes) {
mUserActionPrefixes = userActionPrefixes;
mUserActionCounts = new HashMap<String, Integer>();
for (String action : mUserActionPrefixes) {
mUserActionCounts.put(action, 0);
}
}
@Override
public void onActionRecorded(String action) {
for (String entry : mUserActionPrefixes) {
if (action.startsWith(entry)) {
mUserActionCounts.put(entry, mUserActionCounts.get(entry) + 1);
}
}
}
/**
* Gets the count of user actions recorded for the given prefix.
* @param actionPrefix The plain string prefix to lookup (must match a constructed entry)
* @return The count of user actions recorded for that prefix.
*/
int get(String actionPrefix) {
return mUserActionCounts.get(actionPrefix);
}
}
//============================================================================================
// UMA assertions
//============================================================================================
private void assertUserActionRecorded(String userActionFullName) throws Exception {
Assert.assertTrue(mActionTester.getActions().contains(userActionFullName));
}
/**
* Returns whether all the supported gestures for opted-in users trigger a Resolve request,
* aka intelligent search.
*/
private boolean isConfigurationForResolvingGesturesOnly() {
// The current interpretation of the ability to resolve Longpress (which is forced by the
// Translations Feature as well as the LongpressResolve Feature) preserves a resolving Tap
// so there is no non-resolving gesture for opted-in users.
return mPolicy.canResolveLongpress();
}
//============================================================================================
// Test Cases
//============================================================================================
/**
* Tests whether the contextual search panel hides when omnibox is clicked.
*/
//@SmallTest
//@Feature({"ContextualSearch"})
@Test
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Flaked in 2017. https://crbug.com/707529")
public void testHidesWhenOmniboxFocused() throws Exception {
clickWordNode("intelligence");
Assert.assertEquals("Intelligence", mFakeServer.getSearchTermRequested());
fakeResponse(false, 200, "Intelligence", "display-text", "alternate-term", false);
assertContainsParameters("Intelligence", "alternate-term");
waitForPanelToPeek();
OmniboxTestUtils.toggleUrlBarFocus(
(UrlBar) sActivityTestRule.getActivity().findViewById(R.id.url_bar), true);
assertPanelClosedOrUndefined();
}
/**
* Tests the doesContainAWord method.
* TODO(donnd): Change to a unit test.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testDoesContainAWord() {
Assert.assertTrue(mSelectionController.doesContainAWord("word"));
Assert.assertTrue(mSelectionController.doesContainAWord("word "));
Assert.assertFalse("Emtpy string should not be considered a word!",
mSelectionController.doesContainAWord(""));
Assert.assertFalse("Special symbols should not be considered a word!",
mSelectionController.doesContainAWord("@"));
Assert.assertFalse("White space should not be considered a word",
mSelectionController.doesContainAWord(" "));
Assert.assertTrue(mSelectionController.doesContainAWord("Q2"));
Assert.assertTrue(mSelectionController.doesContainAWord("123"));
}
/**
* Tests the isValidSelection method.
* TODO(donnd): Change to a unit test.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testIsValidSelection() {
StubbedSelectionPopupController c = new StubbedSelectionPopupController();
Assert.assertTrue(mSelectionController.isValidSelection("valid", c));
Assert.assertFalse(mSelectionController.isValidSelection(" ", c));
c.setIsFocusedNodeEditableForTest(true);
Assert.assertFalse(mSelectionController.isValidSelection("editable", c));
c.setIsFocusedNodeEditableForTest(false);
String numberString = "0123456789";
Assert.assertTrue(mSelectionController.isValidSelection(numberString, c));
StringBuilder longStringBuilder = new StringBuilder().append(numberString);
for (int i = 0; i < 10; i++) {
longStringBuilder.append(longStringBuilder.toString());
if (longStringBuilder.toString().length() < 1000) {
Assert.assertTrue(
mSelectionController.isValidSelection(longStringBuilder.toString(), c));
} else {
Assert.assertFalse(
mSelectionController.isValidSelection(longStringBuilder.toString(), c));
break;
}
}
}
/**
* Tests Ranker logging for a simple trigger that resolves.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
// Ranker is only used for Tap triggering.
public void testResolvingSearchRankerLogging() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
simulateResolveSearch("intelligence");
assertLoadedLowPriorityUrl();
assertLoggedAllExpectedFeaturesToRanker();
Assert.assertEquals(
true, loggedToRanker(ContextualSearchInteractionRecorder.Feature.IS_LONG_WORD));
// The panel must be closed for outcomes to be logged.
// Close the panel by clicking far away in order to make sure the outcomes get logged by
// the hideContextualSearchUi call to writeRankerLoggerOutcomesAndReset.
clickWordNode("states-far");
waitForPanelToClose();
assertLoggedAllExpectedOutcomesToRanker();
}
/**
* Tests a simple non-resolving gesture, without opening the panel.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNonResolveTrigger(@EnabledFeature int enabledFeature) throws Exception {
if (isConfigurationForResolvingGesturesOnly()) return;
triggerNonResolve("states");
Assert.assertNull(mFakeServer.getSearchTermRequested());
waitForPanelToPeek();
assertLoadedNoUrl();
assertNoWebContents();
}
/**
* Tests swiping the overlay open, after an initial trigger that activates the peeking card.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@DisableIf.Build(supported_abis_includes = "arm64-v8a", message = "crbug.com/765403")
public void testSwipeExpand() throws Exception {
// TODO(donnd): enable for all features.
FeatureList.setTestFeatures(ENABLE_NONE);
assertNoSearchesLoaded();
triggerResolve("intelligence");
assertNoSearchesLoaded();
// Fake a search term resolution response.
fakeResponse(false, 200, "Intelligence", "United States Intelligence", "alternate-term",
false);
assertContainsParameters("Intelligence", "alternate-term");
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
assertLoadedLowPriorityUrl();
waitForPanelToPeek();
flingPanelUp();
waitForPanelToExpand();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
assertLoadedLowPriorityUrl();
}
/**
* Tests swiping the overlay open, after an initial non-resolve search that activates the
* peeking card, followed by closing the panel.
* This test also verifies that we don't create any {@link WebContents} or load any URL
* until the panel is opened.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNonResolveSwipeExpand(@EnabledFeature int enabledFeature) throws Exception {
simulateNonResolveSearch("search");
assertNoWebContents();
assertLoadedNoUrl();
tapPeekingBarToExpandAndAssert();
assertWebContentsCreated();
assertLoadedNormalPriorityUrl();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// tap the base page to close.
closePanel();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
assertNoWebContents();
}
/**
* Tests that only a single low-priority request is issued for a trigger/open sequence.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "High priority test. See https://crbug.com/1058297")
public void testResolveCausesOneLowPriorityRequest(@EnabledFeature int enabledFeature)
throws Exception {
mFakeServer.reset();
simulateResolveSearch("states");
// We should not make a second-request until we get a good response from the first-request.
assertLoadedNoUrl();
Assert.assertEquals(0, mFakeServer.getLoadedUrlCount());
fakeResponse(false, 200, "states", "United States Intelligence", "alternate-term", false);
assertLoadedLowPriorityUrl();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// When the second request succeeds, we should not issue a new request.
fakeContentViewDidNavigate(false);
assertLoadedLowPriorityUrl();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// When the bar opens, we should not make any additional request.
tapPeekingBarToExpandAndAssert();
assertLoadedLowPriorityUrl();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
assertLoadedLowPriorityUrl();
}
/**
* Tests that a failover for a prefetch request is issued after the panel is opened.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testPrefetchFailoverRequestMadeAfterOpen(@EnabledFeature int enabledFeature)
throws Exception {
mFakeServer.reset();
triggerResolve("states");
// We should not make a SERP request until we get a good response from the resolve request.
assertLoadedNoUrl();
Assert.assertEquals(0, mFakeServer.getLoadedUrlCount());
fakeResponse(false, 200, "states", "United States Intelligence", "alternate-term", false);
assertLoadedLowPriorityUrl();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// When the second request fails, we should not automatically issue a new request.
fakeContentViewDidNavigate(true);
assertLoadedLowPriorityUrl();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Once the bar opens, we make a new request at normal priority.
tapPeekingBarToExpandAndAssert();
assertLoadedNormalPriorityUrl();
Assert.assertEquals(2, mFakeServer.getLoadedUrlCount());
}
/**
* Tests that a live request that fails (for an invalid URL) does a failover to a
* normal priority request once the user triggers the failover by opening the panel.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testLivePrefetchFailoverRequestMadeAfterOpen(@EnabledFeature int enabledFeature)
throws Exception {
// Test fails with out-of-process network service. crbug.com/1071721
if (!ChromeFeatureList.isEnabled("NetworkServiceInProcess")) return;
mFakeServer.reset();
mFakeServer.setLowPriorityPathInvalid();
mFakeServer.setActuallyLoadALiveSerp();
simulateResolveSearch("search");
assertLoadedLowPriorityInvalidUrl();
Assert.assertTrue(mFakeServer.didAttemptLoadInvalidUrl());
// we should not automatically issue a new request.
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Fake a navigation error if offline.
// When connected to the Internet this error may already have happened due to actually
// trying to load the invalid URL. But on test bots that are not online we need to
// fake that a navigation happened with an error. See crbug.com/682953 for details.
if (!mManager.isOnline()) {
boolean isFailure = true;
fakeContentViewDidNavigate(isFailure);
}
// Once the bar opens, we make a new request at normal priority.
tapPeekingBarToExpandAndAssert();
waitForNormalPriorityUrlLoaded();
Assert.assertEquals(2, mFakeServer.getLoadedUrlCount());
}
/**
* Tests a simple triggering getsture with disable-preload set.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testResolveDisablePreload(@EnabledFeature int enabledFeature) throws Exception {
simulateSlowResolveSearch("intelligence");
assertSearchTermRequested();
boolean doPreventPreload = true;
fakeResponse(
false, 200, "Intelligence", "display-text", "alternate-term", doPreventPreload);
assertLoadedNoUrl();
waitForPanelToPeek();
assertLoadedNoUrl();
}
/**
* Tests that long-press selects text, and a subsequent tap will unselect text.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testLongPressGestureSelects(@EnabledFeature int enabledFeature) throws Exception {
longPressNode("intelligence");
Assert.assertEquals("Intelligence", getSelectedText());
waitForPanelToPeek();
assertLoadedNoUrl(); // No load (preload) after long-press until opening panel.
clickNode("question-mark");
waitForPanelToCloseAndSelectionEmpty();
Assert.assertTrue(TextUtils.isEmpty(getSelectedText()));
assertLoadedNoUrl();
}
/**
* Tests that a Resolve gesture selects the expected text.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285, https://crbug.com/1192561")
public void testResolveGestureSelects(@EnabledFeature int enabledFeature) throws Exception {
simulateResolveSearch("intelligence");
Assert.assertEquals("Intelligence", getSelectedText());
assertLoadedLowPriorityUrl();
clickNode("question-mark");
waitForPanelToClose();
Assert.assertTrue(getSelectedText() == null || getSelectedText().isEmpty());
}
//============================================================================================
// Tap=gesture Tests
//============================================================================================
/**
* Tests that a Tap gesture on a special character does not select or show the panel.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1180304")
public void testTapGestureOnSpecialCharacterDoesntSelect() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
clickNode("question-mark");
Assert.assertNull(getSelectedText());
assertPanelClosedOrUndefined();
assertLoadedNoUrl();
}
/**
* Tests that a Tap gesture followed by scrolling clears the selection.
*/
@Test
@DisableIf.
Build(sdk_is_greater_than = Build.VERSION_CODES.LOLLIPOP, message = "crbug.com/841017")
@SmallTest
@Feature({"ContextualSearch"})
public void testTapGestureFollowedByScrollClearsSelection() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
clickWordNode("intelligence");
fakeResponse(false, 200, "Intelligence", "Intelligence", "alternate-term", false);
assertContainsParameters("Intelligence", "alternate-term");
waitForPanelToPeek();
assertLoadedLowPriorityUrl();
scrollBasePage();
assertPanelClosedOrUndefined();
Assert.assertTrue(TextUtils.isEmpty(mSelectionController.getSelectedText()));
}
/**
* Tests that a Tap gesture followed by tapping an invalid character doesn't select.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testTapGestureFollowedByInvalidTextTapCloses() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
clickWordNode("states-far");
waitForPanelToPeek();
clickNode("question-mark");
waitForPanelToClose();
Assert.assertNull(mSelectionController.getSelectedText());
}
/**
* Tests that a Tap gesture followed by tapping a non-text character doesn't select.
* @SmallTest
* @Feature({"ContextualSearch"})
* crbug.com/665633
*/
@Test
@DisabledTest
public void testTapGestureFollowedByNonTextTap() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
clickWordNode("states-far");
waitForPanelToPeek();
clickNode("button");
waitForPanelToCloseAndSelectionEmpty();
}
/**
* Tests that a Tap gesture far away toggles selecting text.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testTapGestureFarAwayTogglesSelecting() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
clickWordNode("states");
Assert.assertEquals("States", getSelectedText());
waitForPanelToPeek();
clickNode("states-far");
waitForPanelToClose();
Assert.assertNull(getSelectedText());
clickNode("states-far");
waitForPanelToPeek();
Assert.assertEquals("States", getSelectedText());
}
/**
* Tests a "retap" -- that sequential Tap gestures nearby keep selecting.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisabledTest(message = "https://crbug.com/1075895")
public void testTapGesturesNearbyKeepSelecting() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
clickWordNode("states");
Assert.assertEquals("States", getSelectedText());
waitForPanelToPeek();
assertLoggedAllExpectedFeaturesToRanker();
// Avoid issues with double-tap detection by ensuring sequential taps
// aren't treated as such. Double-tapping can also select words much as
// longpress, in turn showing the pins and preventing contextual tap
// refinement from nearby taps. The double-tap timeout is sufficiently
// short that this shouldn't conflict with tap refinement by the user.
Thread.sleep(ViewConfiguration.getDoubleTapTimeout());
// Because sequential taps never hide the bar, we we can't wait for it to peek.
// Instead we use clickNode (which doesn't wait) instead of clickWordNode and wait
// for the selection to change.
clickNode("states-near");
waitForSelectionToBe("StatesNear");
assertLoggedAllExpectedOutcomesToRanker();
assertLoggedAllExpectedFeaturesToRanker();
Thread.sleep(ViewConfiguration.getDoubleTapTimeout());
clickNode("states");
waitForSelectionToBe("States");
assertLoggedAllExpectedOutcomesToRanker();
}
//============================================================================================
// Long-press non-triggering gesture tests.
//============================================================================================
/**
* Tests that a long-press gesture followed by scrolling does not clear the selection.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.O, message = "crbug.com/1071080")
public void testLongPressGestureFollowedByScrollMaintainsSelection(
@EnabledFeature int enabledFeature) throws Exception {
longPressNode("intelligence");
waitForPanelToPeek();
scrollBasePage();
assertPanelClosedOrUndefined();
Assert.assertEquals("Intelligence", getSelectedText());
assertLoadedNoUrl();
}
/**
* Tests that a long-press gesture followed by a tap does not select.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@DisabledTest(message = "See https://crbug.com/837998")
public void testLongPressGestureFollowedByTapDoesntSelect() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
longPressNode("intelligence");
waitForPanelToPeek();
clickWordNode("states-far");
waitForGestureToClosePanelAndAssertNoSelection();
assertLoadedNoUrl();
}
//============================================================================================
// Various Tests
//============================================================================================
/**
* Tests that the panel closes when its base page crashes.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled in 2018 due to flakes. See https://crbug.com/832539.")
public void testContextualSearchDismissedOnForegroundTabCrash(
@EnabledFeature int enabledFeature) throws Exception {
triggerResolve("states");
Assert.assertEquals("States", getSelectedText());
waitForPanelToPeek();
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
ChromeTabUtils.simulateRendererKilledForTesting(
sActivityTestRule.getActivity().getActivityTab(), true);
});
// Give the panelState time to change
CriteriaHelper.pollInstrumentationThread(() -> {
Criteria.checkThat(mPanel.getPanelState(), Matchers.not(PanelState.PEEKED));
});
assertPanelClosedOrUndefined();
}
/**
* Test the the panel does not close when some background tab crashes.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285, https://crbug.com/1192561")
public void testContextualSearchNotDismissedOnBackgroundTabCrash(
@EnabledFeature int enabledFeature) throws Exception {
ChromeTabUtils.newTabFromMenu(
InstrumentationRegistry.getInstrumentation(), sActivityTestRule.getActivity());
final Tab tab2 =
TabModelUtils.getCurrentTab(sActivityTestRule.getActivity().getCurrentTabModel());
TestThreadUtils.runOnUiThreadBlocking(() -> {
TabModelUtils.setIndex(sActivityTestRule.getActivity().getCurrentTabModel(), 0, false);
});
triggerResolve("states");
Assert.assertEquals("States", getSelectedText());
waitForPanelToPeek();
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT,
() -> { ChromeTabUtils.simulateRendererKilledForTesting(tab2, false); });
waitForPanelToPeek();
}
/*
* Test that tapping on the open-new-tab icon before having a resolved search term does not
* promote to a tab, and that after the resolution it does promote to a tab.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testPromotesToTab(@EnabledFeature int enabledFeature) throws Exception {
// -------- SET UP ---------
// Track Tab creation with this helper.
final CallbackHelper tabCreatedHelper = new CallbackHelper();
int tabCreatedHelperCallCount = tabCreatedHelper.getCallCount();
TabModelSelectorObserver observer = new TabModelSelectorObserver() {
@Override
public void onNewTabCreated(Tab tab, @TabCreationState int creationState) {
tabCreatedHelper.notifyCalled();
}
};
TestThreadUtils.runOnUiThreadBlocking(
() -> sActivityTestRule.getActivity().getTabModelSelector().addObserver(observer));
// Track User Actions
mActionTester = new UserActionTester();
// -------- TEST ---------
// Start a slow-resolve search and maximize the Panel.
simulateSlowResolveSearch("search");
maximizePanel();
waitForPanelToMaximize();
// A click should not promote since we are still waiting to Resolve.
forceOpenTabIconClick();
// Assert that the Panel is still maximized.
waitForPanelToMaximize();
// Let the Search Term Resolution finish.
simulateSlowResolveFinished();
// Now a click to promote to a separate tab.
forceOpenTabIconClick();
// The Panel should now be closed.
waitForPanelToClose();
// Make sure a tab was created.
tabCreatedHelper.waitForCallback(tabCreatedHelperCallCount);
// Make sure we captured the promotion in UMA.
assertUserActionRecorded("ContextualSearch.TabPromotion");
// -------- CLEAN UP ---------
TestThreadUtils.runOnUiThreadBlocking(() -> {
sActivityTestRule.getActivity().getTabModelSelector().removeObserver(observer);
});
}
//============================================================================================
// Tap-non-triggering when ARIA annotated as interactive.
//============================================================================================
/**
* Tests that a Tap gesture on an element with an ARIA role does not trigger.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testTapOnRoleIgnored() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
@PanelState
int initialState = mPanel.getPanelState();
clickNode("role");
assertPanelStillInState(initialState);
}
/**
* Tests that a Tap gesture on an element with an ARIA attribute does not trigger.
* http://crbug.com/542874
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testTapOnARIAIgnored() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
@PanelState
int initialState = mPanel.getPanelState();
clickNode("aria");
assertPanelStillInState(initialState);
}
/**
* Tests that a Tap gesture on an element that is focusable does not trigger.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testTapOnFocusableIgnored() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
@PanelState
int initialState = mPanel.getPanelState();
clickNode("focusable");
assertPanelStillInState(initialState);
}
//============================================================================================
// Search-term resolution (server request to determine a search).
//============================================================================================
/**
* Tests expanding the panel before the search term has resolved, verifies that nothing
* loads until the resolve completes and that it's now a normal priority URL.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testExpandBeforeSearchTermResolution(@EnabledFeature int enabledFeature)
throws Exception {
simulateSlowResolveSearch("states");
assertNoWebContents();
// Expanding before the search term resolves should not load anything.
tapPeekingBarToExpandAndAssert();
assertLoadedNoUrl();
// Once the response comes in, it should load.
simulateSlowResolveFinished();
assertContainsParameters("States");
assertLoadedNormalPriorityUrl();
assertWebContentsCreated();
assertWebContentsVisible();
}
/**
* Tests that an error from the Search Term Resolution request causes a fallback to a
* search request for the literal selection.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@DisableIf.Build(supported_abis_includes = "arm64-v8a", message = "crbug.com/765403")
public void testSearchTermResolutionError(@EnabledFeature int enabledFeature) throws Exception {
simulateSlowResolveSearch("states");
assertSearchTermRequested();
fakeResponse(false, 403, "", "", "", false);
assertLoadedNoUrl();
tapPeekingBarToExpandAndAssert();
assertLoadedNormalPriorityUrl();
}
//============================================================================================
// Undecided/Decided users.
//============================================================================================
/**
* Tests that we do not resolve or preload when the privacy Opt-in has not been accepted.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testUnacceptedPrivacy(@EnabledFeature int enabledFeature) throws Exception {
mPolicy.overrideDecidedStateForTesting(false);
simulateResolvableSearchAndAssertResolveAndPreload("states", false);
}
/**
* Tests that we do resolve and preload when the privacy Opt-in has been accepted.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testAcceptedPrivacy(@EnabledFeature int enabledFeature) throws Exception {
mPolicy.overrideDecidedStateForTesting(true);
simulateResolvableSearchAndAssertResolveAndPreload("states", true);
}
//============================================================================================
// App Menu Suppression
//============================================================================================
/**
* Simulates pressing the App Menu button.
*/
private void pressAppMenuKey() {
pressKey(KeyEvent.KEYCODE_MENU);
}
private void closeAppMenu() {
TestThreadUtils.runOnUiThreadBlocking(
() -> sActivityTestRule.getAppMenuCoordinator().getAppMenuHandler().hideAppMenu());
}
/**
* Asserts whether the App Menu is visible.
*/
private void assertAppMenuVisibility(final boolean isVisible) {
CriteriaHelper.pollInstrumentationThread(() -> {
Criteria.checkThat(sActivityTestRule.getAppMenuCoordinator()
.getAppMenuHandler()
.isAppMenuShowing(),
Matchers.is(isVisible));
});
}
/**
* Tests that the App Menu gets suppressed when Search Panel is expanded.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@DisableIf.Build(supported_abis_includes = "arm64-v8a", message = "crbug.com/596533")
public void testAppMenuSuppressedWhenExpanded(@EnabledFeature int enabledFeature)
throws Exception {
triggerResolve("states");
tapPeekingBarToExpandAndAssert();
pressAppMenuKey();
assertAppMenuVisibility(false);
closePanel();
pressAppMenuKey();
assertAppMenuVisibility(true);
closeAppMenu();
}
/**
* Tests that the App Menu gets suppressed when Search Panel is maximized.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testAppMenuSuppressedWhenMaximized(@EnabledFeature int enabledFeature)
throws Exception {
triggerResolve("states");
maximizePanel();
waitForPanelToMaximize();
pressAppMenuKey();
assertAppMenuVisibility(false);
pressBackButton();
waitForPanelToClose();
pressAppMenuKey();
assertAppMenuVisibility(true);
closeAppMenu();
}
// --------------------------------------------------------------------------------------------
// Promo open count - watches if the promo has never been opened.
// --------------------------------------------------------------------------------------------
/**
* Tests the promo open counter for users that have not opted-in to privacy.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@DisableIf.Build(supported_abis_includes = "arm64-v8a", message = "crbug.com/596533")
// Only useful for disabling Tap triggering.
@DisabledTest(message = "crbug.com/965706")
public void testPromoOpenCountForUndecided() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
mPolicy.overrideDecidedStateForTesting(false);
// A simple click / resolve / prefetch sequence without open should not change the counter.
clickToTriggerPrefetch();
Assert.assertEquals(0, mPolicy.getPromoOpenCount());
// An open should count.
clickToExpandAndClosePanel();
Assert.assertEquals(1, mPolicy.getPromoOpenCount());
// Another open should count.
clickToExpandAndClosePanel();
Assert.assertEquals(2, mPolicy.getPromoOpenCount());
// Once the user has decided, we should stop counting.
mPolicy.overrideDecidedStateForTesting(true);
clickToExpandAndClosePanel();
Assert.assertEquals(2, mPolicy.getPromoOpenCount());
}
/**
* Tests the promo open counter for users that have already opted-in to privacy.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@DisableIf.Build(supported_abis_includes = "arm64-v8a", message = "crbug.com/596533")
public void testPromoOpenCountForDecided() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
mPolicy.overrideDecidedStateForTesting(true);
// An open should not count for decided users.
clickToExpandAndClosePanel();
Assert.assertEquals(0, mPolicy.getPromoOpenCount());
}
// --------------------------------------------------------------------------------------------
// Tap count - number of taps between opens.
// --------------------------------------------------------------------------------------------
/**
* Tests the counter for the number of taps between opens.
*/
@Test
@DisabledTest(message = "crbug.com/800334")
@SmallTest
@Feature({"ContextualSearch"})
public void testTapCount() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
resetCounters();
Assert.assertEquals(0, mPolicy.getTapCount());
// A simple Tap should change the counter.
clickToTriggerPrefetch();
Assert.assertEquals(1, mPolicy.getTapCount());
// Another Tap should increase the counter.
clickToTriggerPrefetch();
Assert.assertEquals(2, mPolicy.getTapCount());
// An open should reset the counter.
clickToExpandAndClosePanel();
Assert.assertEquals(0, mPolicy.getTapCount());
}
//============================================================================================
// Calls to ContextualSearchObserver.
//============================================================================================
private static class TestContextualSearchObserver implements ContextualSearchObserver {
private int mShowCount;
private int mShowRedactedCount;
private int mHideCount;
private int mFirstShownLength;
private int mLastShownLength;
@Override
public void onShowContextualSearch(@Nullable GSAContextDisplaySelection selectionContext) {
mShowCount++;
if (selectionContext != null
&& selectionContext.startOffset < selectionContext.endOffset) {
mLastShownLength = selectionContext.endOffset - selectionContext.startOffset;
if (mFirstShownLength == 0) mFirstShownLength = mLastShownLength;
} else {
mShowRedactedCount++;
}
}
@Override
public void onHideContextualSearch() {
mHideCount++;
}
/**
* @return The count of Hide notifications sent to observers.
*/
int getHideCount() {
return mHideCount;
}
/**
* @return The count of Show notifications sent to observers.
*/
int getShowCount() {
return mShowCount;
}
/**
* @return The count of Show notifications sent to observers that had the data redacted due
* to our policy on privacy.
*/
int getShowRedactedCount() {
return mShowRedactedCount;
}
/**
* @return The length of the selection for the first Show notification.
*/
int getFirstShownLength() {
return mFirstShownLength;
}
/**
* @return The length of the selection for the last Show notification.
*/
int getLastShownLength() {
return mLastShownLength;
}
}
/**
* Tests that a ContextualSearchObserver gets notified when the user brings up The Contextual
* Search panel via long press and then dismisses the panel by tapping on the base page.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNotifyObserversAfterNonResolve(@EnabledFeature int enabledFeature)
throws Exception {
if (isConfigurationForResolvingGesturesOnly()) return;
TestContextualSearchObserver observer = new TestContextualSearchObserver();
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
triggerNonResolve("states");
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(0, observer.getHideCount());
tapBasePageToClosePanel();
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(1, observer.getHideCount());
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
}
/**
* Tests that a ContextualSearchObserver gets notified without any page context when the user
* is Undecided and our policy disallows sending surrounding text.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1180304")
public void testNotifyObserversAfterLongPressWithoutSurroundings(
@EnabledFeature int enabledFeature) throws Exception {
// Mark the user undecided so we won't allow sending surroundings.
mPolicy.overrideDecidedStateForTesting(false);
TestContextualSearchObserver observer = new TestContextualSearchObserver();
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
triggerNonResolve("states");
Assert.assertEquals(1, observer.getShowRedactedCount());
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(0, observer.getHideCount());
tapBasePageToClosePanel();
Assert.assertEquals(1, observer.getShowRedactedCount());
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(1, observer.getHideCount());
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
}
/**
* Tests that ContextualSearchObserver gets notified when user brings up contextual search
* panel and then dismisses the panel by tapping on the base page.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNotifyObserversAfterResolve(@EnabledFeature int enabledFeature)
throws Exception {
TestContextualSearchObserver observer = new TestContextualSearchObserver();
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
simulateResolveSearch("states");
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(0, observer.getHideCount());
tapBasePageToClosePanel();
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(1, observer.getHideCount());
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
}
/**
* Asserts that the action bar does or does not become visible in response to a selection.
* @param visible Whether the Action Bar must become visible or not.
*/
private void assertWaitForSelectActionBarVisible(final boolean visible) {
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(
getSelectionPopupController().isSelectActionBarShowing(), Matchers.is(visible));
});
}
private SelectionPopupController getSelectionPopupController() {
return SelectionPopupController.fromWebContents(sActivityTestRule.getWebContents());
}
/**
* Tests that ContextualSearchObserver gets notified when the user brings up the contextual
* search panel via long press and then dismisses the panel by tapping copy (hide select action
* mode).
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNotifyObserversOnClearSelectionAfterLongpress(
@EnabledFeature int enabledFeature) throws Exception {
TestContextualSearchObserver observer = new TestContextualSearchObserver();
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
longPressNode("states");
Assert.assertEquals(0, observer.getHideCount());
// Dismiss select action mode.
assertWaitForSelectActionBarVisible(true);
TestThreadUtils.runOnUiThreadBlocking(
() -> getSelectionPopupController().destroySelectActionMode());
assertWaitForSelectActionBarVisible(false);
waitForPanelToClose();
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(1, observer.getHideCount());
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
}
/**
* Tests that the Contextual Search panel does not reappear when a long-press selection is
* modified after the user has taken an action to explicitly dismiss the panel. Also tests
* that the panel reappears when a new selection is made.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testPreventHandlingCurrentSelectionModification() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
simulateNonResolveSearch("search");
// Dismiss the Contextual Search panel.
closePanel();
Assert.assertEquals("Search", getSelectedText());
// Simulate a selection change event and assert that the panel has not reappeared.
TestThreadUtils.runOnUiThreadBlocking(() -> {
SelectionClient selectionClient = mManager.getContextualSearchSelectionClient();
selectionClient.onSelectionEvent(
SelectionEventType.SELECTION_HANDLE_DRAG_STARTED, 333, 450);
selectionClient.onSelectionEvent(
SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED, 303, 450);
});
assertPanelClosedOrUndefined();
// Select a different word and assert that the panel has appeared.
simulateNonResolveSearch("resolution");
// The simulateNonResolveSearch call will verify that the panel peeks.
}
/**
* Tests a bunch of taps in a row.
* We've had reliability problems with a sequence of simple taps, due to async dissolving
* of selection bounds, so this helps prevent a regression with that.
*/
@Test
@LargeTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "crbug.com/1036414, crbug.com/1039488")
public void testTapALot() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
for (int i = 0; i < 50; i++) {
clickToTriggerPrefetch();
assertSearchTermRequested();
}
}
/**
* Tests ContextualSearchManager#shouldInterceptNavigation for a case that an external
* navigation has a user gesture.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q, message = "crbug.com/1037667")
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testExternalNavigationWithUserGesture(@EnabledFeature int enabledFeature) {
final ExternalNavigationDelegateImpl delegate =
TestThreadUtils.runOnUiThreadBlockingNoException(
()
-> new ExternalNavigationDelegateImpl(
sActivityTestRule.getActivity().getActivityTab()));
final ExternalNavigationHandler externalNavHandler =
new ExternalNavigationHandler(delegate);
final NavigationParams navigationParams = new NavigationParams(
new GURL("intent://test/#Intent;scheme=test;package=com.chrome.test;end"),
GURL.emptyGURL(), 0 /* navigationId */, false /* isPost */,
true /* hasUserGesture */, PageTransition.LINK, false /* isRedirect */,
true /* isExternalProtocol */, true /* isMainFrame */,
true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
null /* initiatorOrigin */);
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
sActivityTestRule.getActivity().onUserInteraction();
Assert.assertFalse(mManager.getOverlayContentDelegate().shouldInterceptNavigation(
externalNavHandler, navigationParams));
}
});
Assert.assertEquals(1, mActivityMonitor.getHits());
}
/**
* Tests ContextualSearchManager#shouldInterceptNavigation for a case that an initial
* navigation has a user gesture but the redirected external navigation doesn't.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q, message = "crbug.com/1037667")
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testRedirectedExternalNavigationWithUserGesture(
@EnabledFeature int enabledFeature) {
final ExternalNavigationDelegateImpl delegate =
TestThreadUtils.runOnUiThreadBlockingNoException(
()
-> new ExternalNavigationDelegateImpl(
sActivityTestRule.getActivity().getActivityTab()));
final ExternalNavigationHandler externalNavHandler =
new ExternalNavigationHandler(delegate);
final NavigationParams initialNavigationParams =
new NavigationParams(new GURL("http://test.com"), GURL.emptyGURL(),
0 /* navigationId */, false /* isPost */, true /* hasUserGesture */,
PageTransition.LINK, false /* isRedirect */, false /* isExternalProtocol */,
true /* isMainFrame */, true /* isRendererInitiated */,
false /* hasUserGestureCarryover */, null /* initiatorOrigin */);
final NavigationParams redirectedNavigationParams = new NavigationParams(
new GURL("intent://test/#Intent;scheme=test;package=com.chrome.test;end"),
GURL.emptyGURL(), 0 /* navigationId */, false /* isPost */,
false /* hasUserGesture */, PageTransition.LINK, true /* isRedirect */,
true /* isExternalProtocol */, true /* isMainFrame */,
true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
null /* initiatorOrigin */);
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
sActivityTestRule.getActivity().onUserInteraction();
OverlayContentDelegate delegate = mManager.getOverlayContentDelegate();
Assert.assertTrue(delegate.shouldInterceptNavigation(
externalNavHandler, initialNavigationParams));
Assert.assertFalse(delegate.shouldInterceptNavigation(
externalNavHandler, redirectedNavigationParams));
}
});
Assert.assertEquals(1, mActivityMonitor.getHits());
}
/**
* Tests ContextualSearchManager#shouldInterceptNavigation for a case that an external
* navigation doesn't have a user gesture.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testExternalNavigationWithoutUserGesture(@EnabledFeature int enabledFeature) {
final ExternalNavigationDelegateImpl delegate =
TestThreadUtils.runOnUiThreadBlockingNoException(
()
-> new ExternalNavigationDelegateImpl(
sActivityTestRule.getActivity().getActivityTab()));
final ExternalNavigationHandler externalNavHandler =
new ExternalNavigationHandler(delegate);
final NavigationParams navigationParams = new NavigationParams(
new GURL("intent://test/#Intent;scheme=test;package=com.chrome.test;end"),
GURL.emptyGURL(), 0 /* navigationId */, false /* isPost */,
false /* hasUserGesture */, PageTransition.LINK, false /* isRedirect */,
true /* isExternalProtocol */, true /* isMainFrame */,
true /* isRendererInitiated */, false /* hasUserGestureCarryover */,
null /* initiatorOrigin */);
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
sActivityTestRule.getActivity().onUserInteraction();
Assert.assertFalse(mManager.getOverlayContentDelegate().shouldInterceptNavigation(
externalNavHandler, navigationParams));
}
});
Assert.assertEquals(0, mActivityMonitor.getHits());
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1180304")
public void testSelectionExpansionOnSearchTermResolution(@EnabledFeature int enabledFeature)
throws Exception {
mFakeServer.reset();
triggerResolve("intelligence");
waitForPanelToPeek();
ResolvedSearchTerm resolvedSearchTerm =
new ResolvedSearchTerm
.Builder(false, 200, "Intelligence", "United States Intelligence")
.setSelectionStartAdjust(-14)
.build();
fakeResponse(resolvedSearchTerm);
waitForSelectionToBe("United States Intelligence");
}
//============================================================================================
// Content Tests
//============================================================================================
/**
* Tests that resolve followed by expand makes Content visible.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testResolveContentVisibility(@EnabledFeature int enabledFeature) throws Exception {
// Simulate a resolving search and make sure Content is not visible.
simulateResolveSearch("search");
assertWebContentsCreatedButNeverMadeVisible();
// Expanding the Panel should make the Content visible.
tapPeekingBarToExpandAndAssert();
assertWebContentsVisible();
// Closing the Panel should destroy the Content.
tapBasePageToClosePanel();
assertNoWebContents();
}
/**
* Tests that a non-resolving trigger followed by panel-expand creates Content and makes it
* visible.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNonResolveContentVisibility(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a non-resolve search and make sure no Content is created.
simulateNonResolveSearch("search");
assertNoWebContents();
assertNoSearchesLoaded();
// Expanding the Panel should make the Content visible.
tapPeekingBarToExpandAndAssert();
assertWebContentsCreated();
assertWebContentsVisible();
// Closing the Panel should destroy the Content.
tapBasePageToClosePanel();
assertNoWebContents();
}
/**
* Tests swiping panel up and down after a tap search will only load the Content once.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "crbug.com/1032955")
public void testResolveMultipleSwipeOnlyLoadsContentOnce(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a resolving search and make sure Content is not visible.
simulateResolveSearch("search");
assertWebContentsCreatedButNeverMadeVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Expanding the Panel should make the Content visible.
tapPeekingBarToExpandAndAssert();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Swiping the Panel down should not change the visibility or load content again.
swipePanelDown();
waitForPanelToPeek();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Expanding the Panel should not change the visibility or load content again.
tapPeekingBarToExpandAndAssert();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Closing the Panel should destroy the Content.
tapBasePageToClosePanel();
assertNoWebContents();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
}
/**
* Tests swiping panel up and down after a non-resolving search will only load the Content
* once.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.P, message = "crbug.com/1032760")
public void testNonResolveMultipleSwipeOnlyLoadsContentOnce(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a non-resolve search and make sure no Content is created.
simulateNonResolveSearch("search");
assertNoWebContents();
assertNoSearchesLoaded();
// Expanding the Panel should load the URL and make the Content visible.
tapPeekingBarToExpandAndAssert();
assertWebContentsCreated();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Swiping the Panel down should not change the visibility or load content again.
swipePanelDown();
waitForPanelToPeek();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Expanding the Panel should not change the visibility or load content again.
tapPeekingBarToExpandAndAssert();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Closing the Panel should destroy the Content.
tapBasePageToClosePanel();
assertNoWebContents();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
}
/**
* Tests that chained tap searches create new Content.
* Chained Tap searches allow immediate triggering of a tap when quite close to a previous tap
* selection since the user may have just missed the intended target.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testChainedSearchCreatesNewContent(@EnabledFeature int enabledFeature)
throws Exception {
// This test depends on preloading the content - which is loaded and not made visible.
// We only preload when the user has decided to accept the privacy opt-in.
mPolicy.overrideDecidedStateForTesting(true);
// Simulate a resolving search and make sure Content is not visible.
simulateResolveSearch("search");
assertWebContentsCreatedButNeverMadeVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
WebContents wc1 = getPanelWebContents();
waitToPreventDoubleTapRecognition();
// Simulate a new resolving search and make sure new Content is created.
simulateResolveSearch("term");
assertWebContentsCreatedButNeverMadeVisible();
Assert.assertEquals(2, mFakeServer.getLoadedUrlCount());
WebContents wc2 = getPanelWebContents();
Assert.assertNotSame(wc1, wc2);
waitToPreventDoubleTapRecognition();
// Simulate a new resolving search and make sure new Content is created.
simulateResolveSearch("resolution");
assertWebContentsCreatedButNeverMadeVisible();
Assert.assertEquals(3, mFakeServer.getLoadedUrlCount());
WebContents wc3 = getPanelWebContents();
Assert.assertNotSame(wc2, wc3);
// Closing the Panel should destroy the Content.
closePanel();
assertNoWebContents();
Assert.assertEquals(3, mFakeServer.getLoadedUrlCount());
}
/**
* Tests that chained searches load correctly.
*/
@Test
@DisabledTest(message = "crbug.com/551711")
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testChainedSearchLoadsCorrectSearchTerm(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a resolving search and make sure Content is not visible.
simulateResolveSearch("search");
assertWebContentsCreatedButNeverMadeVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
WebContents wc1 = getPanelWebContents();
// Expanding the Panel should make the Content visible.
tapPeekingBarToExpandAndAssert();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Swiping the Panel down should not change the visibility or load content again.
swipePanelDown();
waitForPanelToPeek();
assertWebContentsVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
waitToPreventDoubleTapRecognition();
// Now simulate a non-resolve search, leaving the Panel peeking.
simulateNonResolveSearch("resolution");
// Expanding the Panel should load and display the new search.
tapPeekingBarToExpandAndAssert();
assertWebContentsCreated();
assertWebContentsVisible();
Assert.assertEquals(2, mFakeServer.getLoadedUrlCount());
assertLoadedSearchTermMatches("Resolution");
WebContents wc2 = getPanelWebContents();
Assert.assertNotSame(wc1, wc2);
// Closing the Panel should destroy the Content.
tapBasePageToClosePanel();
assertNoWebContents();
Assert.assertEquals(2, mFakeServer.getLoadedUrlCount());
}
/**
* Tests that chained searches make Content visible when opening the Panel.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testChainedSearchContentVisibility() throws Exception {
// Chained searches are tap-triggered very close to existing tap-triggered searches.
FeatureList.setTestFeatures(ENABLE_NONE);
// Simulate a resolving search and make sure Content is not visible.
simulateResolveSearch("search");
assertWebContentsCreatedButNeverMadeVisible();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
WebContents wc1 = getPanelWebContents();
waitToPreventDoubleTapRecognition();
// Now simulate a non-resolve search, leaving the Panel peeking.
simulateNonResolveSearch("resolution");
assertNeverCalledWebContentsOnShow();
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
// Expanding the Panel should load and display the new search.
tapPeekingBarToExpandAndAssert();
assertWebContentsCreated();
assertWebContentsVisible();
Assert.assertEquals(2, mFakeServer.getLoadedUrlCount());
assertLoadedSearchTermMatches("Resolution");
WebContents wc2 = getPanelWebContents();
Assert.assertNotSame(wc1, wc2);
}
//============================================================================================
// History Removal Tests. These are important for privacy, and are not easy to test manually.
//============================================================================================
/**
* Tests that a tap followed by closing the Panel removes the loaded URL from history.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testTapCloseRemovedFromHistory(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a resolving search and make sure a URL was loaded.
simulateResolveSearch("search");
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
String url = mFakeServer.getLoadedUrl();
// Close the Panel without seeing the Content.
closePanel();
// Now check that the URL has been removed from history.
Assert.assertTrue(mFakeServer.hasRemovedUrl(url));
}
/**
* Tests that a tap followed by opening the Panel does not remove the loaded URL from history.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.O, message = "crbug.com/1184410")
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testTapExpandNotRemovedFromHistory(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a resolving search and make sure a URL was loaded.
simulateResolveSearch("search");
Assert.assertEquals(1, mFakeServer.getLoadedUrlCount());
String url = mFakeServer.getLoadedUrl();
// Expand Panel so that the Content becomes visible.
tapPeekingBarToExpandAndAssert();
// Close the Panel.
tapBasePageToClosePanel();
// Now check that the URL has not been removed from history, since the Content was seen.
Assert.assertFalse(mFakeServer.hasRemovedUrl(url));
}
/**
* Tests that chained searches without opening the Panel removes all loaded URLs from history.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisableIf.Build(sdk_is_less_than = Build.VERSION_CODES.P, message = "crbug.com/1161540")
public void testChainedTapsRemovedFromHistory() throws Exception {
// Make sure we use tap for the simulateResolveSearch since only tap chains.
FeatureList.setTestFeatures(ENABLE_NONE);
// Simulate a resolving search and make sure a URL was loaded.
simulateResolveSearch("search");
String url1 = mFakeServer.getLoadedUrl();
Assert.assertNotNull(url1);
waitToPreventDoubleTapRecognition();
// Simulate another resolving search and make sure another URL was loaded.
simulateResolveSearch("term");
String url2 = mFakeServer.getLoadedUrl();
Assert.assertNotSame(url1, url2);
waitToPreventDoubleTapRecognition();
// Simulate another resolving search and make sure another URL was loaded.
simulateResolveSearch("resolution");
String url3 = mFakeServer.getLoadedUrl();
Assert.assertNotSame(url2, url3);
// Close the Panel without seeing any Content.
closePanel();
// Now check that all three URLs have been removed from history.
Assert.assertEquals(3, mFakeServer.getLoadedUrlCount());
Assert.assertTrue(mFakeServer.hasRemovedUrl(url1));
Assert.assertTrue(mFakeServer.hasRemovedUrl(url2));
Assert.assertTrue(mFakeServer.hasRemovedUrl(url3));
}
//============================================================================================
// Translate Tests
//============================================================================================
/**
* Tests that a simple Tap without language determination does not trigger translation.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testTapWithoutLanguage(@EnabledFeature int enabledFeature) throws Exception {
// Resolving an English word should NOT trigger translation.
simulateResolveSearch("search");
// Make sure we did not try to trigger translate.
Assert.assertFalse(mManager.getRequest().isTranslationForced());
}
/**
* Tests that a non-resolve search does trigger translation.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNonResolveTranslates(@EnabledFeature int enabledFeature) throws Exception {
// A non-resolving gesture on any word should trigger a forced translation.
simulateNonResolveSearch("search");
// Make sure we did try to trigger translate.
Assert.assertTrue(mManager.getRequest().isTranslationForced());
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testSerpTranslationDisabledWhenPartialTranslationEnabled(
@EnabledFeature int enabledFeature) throws Exception {
// Resolving a German word should trigger translation.
simulateResolveSearch("german");
// Simulate a JavaScript translate message from the SERP to the manager
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.onSetCaption("caption", true));
if (ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_SEARCH_TRANSLATIONS)) {
Assert.assertFalse(
"The SERP Translation caption should not show when Partial Translations "
+ "is enabled!",
mPanel.getSearchBarControl().getCaptionVisible());
} else {
Assert.assertTrue(
"The SERP Translation caption should show without Partial Translations "
+ "enabled!",
mPanel.getSearchBarControl().getCaptionVisible());
}
}
/**
* Tests the Translate Caption on a resolve gesture.
* This test is disabled because it relies on the network and a live search result,
* which would be flaky for bots.
* TODO(donnd) Load a fake SERP into the panel to trigger SERP-translation and similar
* features.
*/
@DisabledTest(message = "Useful for manual testing when a network is connected.")
@Test
@LargeTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testTranslateCaption(@EnabledFeature int enabledFeature) throws Exception {
// Resolving a German word should trigger translation.
simulateResolveSearch("german");
// Make sure we tried to trigger translate.
Assert.assertTrue("Translation was not forced with the current request URL: "
+ mManager.getRequest().getSearchUrl(),
mManager.getRequest().isTranslationForced());
// Wait for the translate caption to be shown in the Bar.
int waitFactor = 5; // We need to wait an extra long time for the panel content to render.
CriteriaHelper.pollUiThread(() -> {
ContextualSearchBarControl barControl = mPanel.getSearchBarControl();
Criteria.checkThat(barControl, Matchers.notNullValue());
Criteria.checkThat(barControl.getCaptionVisible(), Matchers.is(true));
Criteria.checkThat(barControl.getCaptionText(), Matchers.notNullValue());
Criteria.checkThat(
barControl.getCaptionText().toString(), Matchers.not(Matchers.isEmptyString()));
}, 3000 * waitFactor, DEFAULT_POLLING_INTERVAL * waitFactor);
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testTranslationsFeatureCanResolveLongpressGesture() throws Exception {
FeatureList.setTestFeatures(ENABLE_TRANSLATIONS);
Assert.assertTrue(mPolicy.canResolveLongpress());
}
//============================================================================================
// END Translate Tests
//============================================================================================
/**
* Tests that Contextual Search works in fullscreen. Specifically, tests that tapping a word
* peeks the panel, expanding the bar results in the bar ending at the correct spot in the page
* and tapping the base page closes the panel.
*/
@Test
@SmallTest
@FlakyTest(message = "Disabled 4/2021. See https://crbug.com/1197102")
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testTapContentAndExpandPanelInFullscreen(@EnabledFeature int enabledFeature)
throws Exception {
// Toggle tab to fulllscreen.
FullscreenTestUtils.togglePersistentFullscreenAndAssert(
sActivityTestRule.getActivity().getActivityTab(), true,
sActivityTestRule.getActivity());
// Simulate a resolving search and assert that the panel peeks.
simulateResolveSearch("search");
// Expand the panel and assert that it ends up in the right place.
tapPeekingBarToExpandAndAssert();
final ContextualSearchPanel panel =
(ContextualSearchPanel) mManager.getContextualSearchPanel();
Assert.assertEquals(
panel.getHeight(), panel.getPanelHeightFromState(PanelState.EXPANDED), 0);
// Tap the base page and assert that the panel is closed.
tapBasePageToClosePanel();
}
/**
* Tests that the Contextual Search panel is dismissed when entering or exiting fullscreen.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@DisableIf.Device(type = {UiDisableIf.PHONE}) // Flaking on phones crbug.com/765796
public void testPanelDismissedOnToggleFullscreen(@EnabledFeature int enabledFeature)
throws Exception {
// Simulate a resolving search and assert that the panel peeks.
simulateResolveSearch("search");
// Toggle tab to fullscreen.
Tab tab = sActivityTestRule.getActivity().getActivityTab();
FullscreenTestUtils.togglePersistentFullscreenAndAssert(
tab, true, sActivityTestRule.getActivity());
// Assert that the panel is closed.
waitForPanelToClose();
// Simulate a resolving search and assert that the panel peeks.
simulateResolveSearch("search");
// Toggle tab to non-fullscreen.
FullscreenTestUtils.togglePersistentFullscreenAndAssert(
tab, false, sActivityTestRule.getActivity());
// Assert that the panel is closed.
waitForPanelToClose();
}
/**
* Tests that ContextualSearchImageControl correctly sets either the icon sprite or thumbnail
* as visible.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testImageControl(@EnabledFeature int enabledFeature) throws Exception {
simulateResolveSearch("search");
final ContextualSearchImageControl imageControl = mPanel.getImageControl();
Assert.assertFalse(imageControl.getThumbnailVisible());
Assert.assertTrue(TextUtils.isEmpty(imageControl.getThumbnailUrl()));
TestThreadUtils.runOnUiThreadBlocking(() -> {
imageControl.setThumbnailUrl("http://someimageurl.com/image.png");
imageControl.onThumbnailFetched(true);
});
Assert.assertTrue(imageControl.getThumbnailVisible());
Assert.assertEquals(imageControl.getThumbnailUrl(), "http://someimageurl.com/image.png");
TestThreadUtils.runOnUiThreadBlocking(() -> imageControl.hideCustomImage(false));
Assert.assertFalse(imageControl.getThumbnailVisible());
Assert.assertTrue(TextUtils.isEmpty(imageControl.getThumbnailUrl()));
}
// TODO(twellington): Add an end-to-end integration test for fetching a thumbnail based on a
// a URL that is included with the resolution response.
/**
* Tests that Contextual Search is fully disabled when offline.
*/
@Test
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled in 2017. https://crbug.com/761946")
// @SmallTest
// @Feature({"ContextualSearch"})
// // NOTE: Remove the flag so we will run just this test with onLine detection enabled.
// @CommandLineFlags.Remove(ContextualSearchFieldTrial.ONLINE_DETECTION_DISABLED)
public void testNetworkDisconnectedDeactivatesSearch(@EnabledFeature int enabledFeature)
throws Exception {
setOnlineStatusAndReload(false);
// We use the longpress gesture here because unlike Tap it's never suppressed.
longPressNodeWithoutWaiting("states");
waitForSelectActionBarVisible();
// Verify the panel didn't open. It should open by now if CS has not been disabled.
// TODO(donnd): Consider waiting for some condition to be sure we'll catch all failures,
// e.g. in case the Bar is about to show but has not yet appeared. Currently catches ~90%.
assertPanelClosedOrUndefined();
// Similar sequence with network connected should peek for Longpress.
setOnlineStatusAndReload(true);
longPressNodeWithoutWaiting("states");
waitForSelectActionBarVisible();
waitForPanelToPeek();
}
/**
* Tests that the quick action caption is set correctly when one is available. Also tests that
* the caption gets changed when the panel is expanded and reset when the panel is closed.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testQuickActionCaptionAndImage(@EnabledFeature int enabledFeature)
throws Exception {
CompositorAnimationHandler.setTestingMode(true);
// Simulate a resolving search to show the Bar, then set the quick action data.
simulateResolveSearch("search");
TestThreadUtils.runOnUiThreadBlocking(
()
-> mPanel.onSearchTermResolved("search", null, "tel:555-555-5555",
QuickActionCategory.PHONE, CardTag.CT_CONTACT,
null /* relatedSearchesInBar */, false /* showDefaultSearchInBar */,
null /* relatedSearchesInContent */,
false /* showDefaultSearchInContent */));
ContextualSearchBarControl barControl = mPanel.getSearchBarControl();
ContextualSearchQuickActionControl quickActionControl = barControl.getQuickActionControl();
ContextualSearchImageControl imageControl = mPanel.getImageControl();
// Check that the peeking bar is showing the quick action data.
Assert.assertTrue(quickActionControl.hasQuickAction());
Assert.assertTrue(barControl.getCaptionVisible());
Assert.assertEquals(sActivityTestRule.getActivity().getResources().getString(
R.string.contextual_search_quick_action_caption_phone),
barControl.getCaptionText());
Assert.assertEquals(1.f, imageControl.getCustomImageVisibilityPercentage(), 0);
// Expand the bar.
TestThreadUtils.runOnUiThreadBlocking(() -> mPanel.simulateTapOnEndButton());
waitForPanelToExpand();
// Check that the expanded bar is showing the correct image.
Assert.assertEquals(0.f, imageControl.getCustomImageVisibilityPercentage(), 0);
// Go back to peeking.
swipePanelDown();
waitForPanelToPeek();
// Assert that the quick action data is showing.
Assert.assertTrue(barControl.getCaptionVisible());
Assert.assertEquals(sActivityTestRule.getActivity().getResources().getString(
R.string.contextual_search_quick_action_caption_phone),
barControl.getCaptionText());
// TODO(donnd): figure out why we get ~0.65 on Oreo rather than 1. https://crbug.com/818515.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Assert.assertEquals(1.f, imageControl.getCustomImageVisibilityPercentage(), 0);
} else {
Assert.assertTrue(0.5f < imageControl.getCustomImageVisibilityPercentage());
}
CompositorAnimationHandler.setTestingMode(false);
}
/**
* Tests that an intent is sent when the bar is tapped and a quick action is available.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testQuickActionIntent(@EnabledFeature int enabledFeature) throws Exception {
// Add a new filter to the activity monitor that matches the intent that should be fired.
IntentFilter quickActionFilter = new IntentFilter(Intent.ACTION_VIEW);
quickActionFilter.addDataScheme("tel");
// Note that we don't reuse mActivityMonitor here or we would leak the one already added
// (unless we removed it here first). When ActivityMonitors leak, Instrumentation silently
// ignores matching ones added after and tests will fail.
ActivityMonitor activityMonitor =
InstrumentationRegistry.getInstrumentation().addMonitor(quickActionFilter,
new Instrumentation.ActivityResult(Activity.RESULT_OK, null), true);
// Simulate a resolving search to show the Bar, then set the quick action data.
simulateResolveSearch("search");
TestThreadUtils.runOnUiThreadBlocking(
()
-> mPanel.onSearchTermResolved("search", null, "tel:555-555-5555",
QuickActionCategory.PHONE, CardTag.CT_CONTACT,
null /* relatedSearchesInBar */, false /* showDefaultSearchInBar */,
null /* relatedSearchesInContent */,
false /* showDefaultSearchInContent */));
sActivityTestRule.getActivity().onUserInteraction();
retryPanelBarInteractions(() -> {
// Tap on the portion of the bar that should trigger the quick action intent to be
// fired.
clickPanelBar();
// Assert that an intent was fired.
Assert.assertEquals(1, activityMonitor.getHits());
}, false);
InstrumentationRegistry.getInstrumentation().removeMonitor(activityMonitor);
}
/**
* Tests that the current tab is navigated to the quick action URI for
* QuickActionCategory#WEBSITE.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.O, message = "crbug.com/1075895")
@DisabledTest(message = "Flaky https://crbug.com/1127796")
public void testQuickActionUrl(@EnabledFeature int enabledFeature) throws Exception {
final String testUrl = mTestServer.getURL("/chrome/test/data/android/google.html");
// Simulate a resolving search to show the Bar, then set the quick action data.
simulateResolveSearch("search");
TestThreadUtils.runOnUiThreadBlocking(
()
-> mPanel.onSearchTermResolved("search", null, testUrl,
QuickActionCategory.WEBSITE, CardTag.CT_URL,
null /* relatedSearchesInBar */, false /* showDefaultSearchInBar */,
null /* relatedSearchesInContent */,
false /* showDefaultSearchInContent */));
retryPanelBarInteractions(() -> {
// Tap on the portion of the bar that should trigger the quick action.
clickPanelBar();
// Assert that the URL was loaded.
ChromeTabUtils.waitForTabPageLoaded(
sActivityTestRule.getActivity().getActivityTab(), testUrl);
}, false);
}
private void runDictionaryCardTest(@CardTag int cardTag) throws Exception {
// Simulate a resolving search to show the Bar, then set the quick action data.
simulateResolveSearch("search");
TestThreadUtils.runOnUiThreadBlocking(
()
-> mPanel.onSearchTermResolved("obscure · əbˈskyo͝or", null, null,
QuickActionCategory.NONE, cardTag, null /* relatedSearchesInBar */,
false /* showDefaultSearchInBar */,
null /* relatedSearchesInContent */,
false /* showDefaultSearchInContent */));
tapPeekingBarToExpandAndAssert();
}
/**
* Tests that the flow for showing dictionary definitions works, and that tapping in the
* bar just opens the panel instead of taking some action.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testDictionaryDefinitions(@EnabledFeature int enabledFeature) throws Exception {
runDictionaryCardTest(CardTag.CT_DEFINITION);
}
/**
* Tests that the flow for showing dictionary definitions works, and that tapping in the
* bar just opens the panel instead of taking some action.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testContextualDictionaryDefinitions(@EnabledFeature int enabledFeature)
throws Exception {
runDictionaryCardTest(CardTag.CT_CONTEXTUAL_DEFINITION);
}
/**
* Tests accessibility mode: Tap and Long-press don't activate CS.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testAccesibilityMode(@EnabledFeature int enabledFeature) throws Exception {
mManager.onAccessibilityModeChanged(true);
// Simulate a tap that resolves to show the Bar.
clickNode("intelligence");
assertNoWebContents();
assertNoSearchesLoaded();
// Simulate a Long-press.
longPressNodeWithoutWaiting("states");
assertNoWebContents();
assertNoSearchesLoaded();
mManager.onAccessibilityModeChanged(false);
}
/**
* Tests when FirstRun is not completed: Tap and Long-press don't activate CS.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testFirstRunNotCompleted(@EnabledFeature int enabledFeature) throws Exception {
// Store the original value in a temp, and mark the first run as not completed
// for this test case.
// Getting value from shared preference rather than FirstRunStatus#getFirstRunFlowComplete
// to get rid of the impact from commandline switch. See https://crbug.com/1158467
boolean originalIsFirstRunComplete = SharedPreferencesManager.getInstance().readBoolean(
ChromePreferenceKeys.FIRST_RUN_FLOW_COMPLETE, false);
FirstRunStatus.setFirstRunFlowComplete(false);
// Simulate a tap that resolves to show the Bar.
clickNode("intelligence");
assertNoWebContents();
assertNoSearchesLoaded();
// Simulate a Long-press.
longPressNodeWithoutWaiting("states");
assertNoWebContents();
assertNoSearchesLoaded();
// Restore the original shared preference value before this test case ends.
FirstRunStatus.setFirstRunFlowComplete(originalIsFirstRunComplete);
}
//============================================================================================
// Internal State Controller tests, which ensure that the internal logic flows as expected for
// each type of triggering gesture.
//============================================================================================
/**
* Tests that the Manager cycles through all the expected Internal States on Tap that Resolves.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1058297")
public void testAllInternalStatesVisitedResolvingTap() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
// Set up a tracking version of the Internal State Controller.
ContextualSearchInternalStateControllerWrapper internalStateControllerWrapper =
ContextualSearchInternalStateControllerWrapper
.makeNewInternalStateControllerWrapper(mManager);
mManager.setContextualSearchInternalStateController(internalStateControllerWrapper);
// Simulate a gesture that resolves to show the Bar.
simulateResolveSearch("search");
Assert.assertEquals("Some states were started but never finished",
internalStateControllerWrapper.getStartedStates(),
internalStateControllerWrapper.getFinishedStates());
Assert.assertEquals(
"The resolving Tap gesture did not sequence through the expected states.",
ContextualSearchInternalStateControllerWrapper.EXPECTED_TAP_RESOLVE_SEQUENCE,
internalStateControllerWrapper.getFinishedStates());
Assert.assertEquals(
"The Tap gesture did not trigger a resolved search, or the resolve sequence did "
+ "not complete.",
InternalState.SHOWING_TAP_SEARCH, internalStateControllerWrapper.getState());
}
/**
* Tests that the Manager cycles through all the expected Internal States on Long-press that
* Resolves.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testAllInternalStatesVisitedResolvingLongpress(@EnabledFeature int enabledFeature)
throws Exception {
if (!mPolicy.canResolveLongpress()) return;
// Set up a tracking version of the Internal State Controller.
ContextualSearchInternalStateControllerWrapper internalStateControllerWrapper =
ContextualSearchInternalStateControllerWrapper
.makeNewInternalStateControllerWrapper(mManager);
mManager.setContextualSearchInternalStateController(internalStateControllerWrapper);
// Simulate a resolving search to show the Bar.
simulateResolveSearch("search");
Assert.assertEquals("Some states were started but never finished",
internalStateControllerWrapper.getStartedStates(),
internalStateControllerWrapper.getFinishedStates());
Assert.assertEquals(
"The resolving Long-press gesture did not sequence through the expected states.",
ContextualSearchInternalStateControllerWrapper.EXPECTED_LONGPRESS_RESOLVE_SEQUENCE,
internalStateControllerWrapper.getFinishedStates());
Assert.assertEquals(
"The Long-press gesturedid not trigger a resolved search, or the resolve sequence "
+ "did not complete.",
InternalState.SHOWING_RESOLVED_LONG_PRESS_SEARCH,
internalStateControllerWrapper.getState());
}
/**
* Tests that the Manager cycles through all the expected Internal States on a Long-press.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1192285")
public void testAllInternalStatesVisitedNonResolveLongpress() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
// Set up a tracking version of the Internal State Controller.
ContextualSearchInternalStateControllerWrapper internalStateControllerWrapper =
ContextualSearchInternalStateControllerWrapper
.makeNewInternalStateControllerWrapper(mManager);
mManager.setContextualSearchInternalStateController(internalStateControllerWrapper);
// Simulate a Long-press to show the Bar.
simulateNonResolveSearch("search");
Assert.assertEquals("Some states were started but never finished",
internalStateControllerWrapper.getStartedStates(),
internalStateControllerWrapper.getFinishedStates());
Assert.assertEquals(
"The non-resolving Long-press gesture didn't sequence through all of the expected "
+ " states.",
ContextualSearchInternalStateControllerWrapper.EXPECTED_LONGPRESS_SEQUENCE,
internalStateControllerWrapper.getFinishedStates());
}
//============================================================================================
// Various tests
//============================================================================================
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
@FlakyTest(message = "Disabled 4/2021. https://crbug.com/1180304")
public void testTriggeringContextualSearchHidesFindInPageOverlay(
@EnabledFeature int enabledFeature) throws Exception {
MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
sActivityTestRule.getActivity(), R.id.find_in_page_id);
CriteriaHelper.pollUiThread(() -> {
FindToolbar findToolbar =
(FindToolbar) sActivityTestRule.getActivity().findViewById(R.id.find_toolbar);
Criteria.checkThat(findToolbar, Matchers.notNullValue());
Criteria.checkThat(findToolbar.isShown(), Matchers.is(true));
Criteria.checkThat(findToolbar.isAnimating(), Matchers.is(false));
});
// Don't type anything to Find because that may cause scrolling which makes clicking in the
// page flaky.
View findToolbar = sActivityTestRule.getActivity().findViewById(R.id.find_toolbar);
Assert.assertTrue(findToolbar.isShown());
simulateResolveSearch("search");
waitForPanelToPeek();
Assert.assertFalse(
"Find Toolbar should no longer be shown once Contextual Search Panel appeared",
findToolbar.isShown());
}
/**
* Tests that expanding the selection during a Search Term Resolve notifies the observers before
* and after the expansion.
* TODO(donnd): move to the section for observer tests.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testNotifyObserversOnExpandSelection(@EnabledFeature int enabledFeature)
throws Exception {
mPolicy.overrideDecidedStateForTesting(true);
TestContextualSearchObserver observer = new TestContextualSearchObserver();
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
simulateSlowResolveSearch("states");
simulateSlowResolveFinished();
closePanel();
Assert.assertEquals("States".length(), observer.getFirstShownLength());
Assert.assertEquals("United States".length(), observer.getLastShownLength());
Assert.assertEquals(2, observer.getShowCount());
Assert.assertEquals(1, observer.getHideCount());
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
}
/** Asserts that the given value is either 1 or 2. Helpful for flaky tests. */
private void assertValueIs1or2(int value) {
if (value != 1) Assert.assertEquals(2, value);
}
/**
* Tests a second Tap: a Tap on an existing tap-selection.
* TODO(donnd): move to the section for observer tests.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testSecondTap() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
TestContextualSearchObserver observer = new TestContextualSearchObserver();
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.addObserver(observer));
clickWordNode("search");
Assert.assertEquals(1, observer.getShowCount());
Assert.assertEquals(0, observer.getHideCount());
clickNode("search");
waitForSelectActionBarVisible();
closePanel();
// Sometimes we get an additional Show notification on the second Tap, but not reliably in
// tests. See crbug.com/776541.
assertValueIs1or2(observer.getShowCount());
Assert.assertEquals(1, observer.getHideCount());
TestThreadUtils.runOnUiThreadBlocking(() -> mManager.removeObserver(observer));
}
/**
* Tests Tab reparenting. When a tab moves from one activity to another the
* ContextualSearchTabHelper should detect the change and handle gestures for it too. This
* happens with multiwindow modes.
*/
@Test
@LargeTest
@Feature({"ContextualSearch"})
@CommandLineFlags.Add(ChromeSwitches.DISABLE_TAB_MERGING_FOR_TESTING)
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
@ParameterAnnotations.UseMethodParameter(FeatureParamProvider.class)
public void testTabReparenting(@EnabledFeature int enabledFeature) throws Exception {
// Move our "tap_test" tab to another activity.
final ChromeActivity ca = sActivityTestRule.getActivity();
// Create a new tab so |ca| isn't destroyed.
ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(), ca);
ChromeTabUtils.switchTabInCurrentTabModel(ca, 0);
int testTabId = ca.getActivityTab().getId();
MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(), ca,
R.id.move_to_other_window_menu_id);
// Wait for the second activity to start up and be ready for interaction.
final ChromeTabbedActivity2 activity2 = waitForSecondChromeTabbedActivity();
waitForTabs("CTA2", activity2, 1, testTabId);
// Trigger on a word and wait for the selection to be established.
triggerNode(activity2.getActivityTab(), "search");
CriteriaHelper.pollUiThread(() -> {
String selection = activity2.getContextualSearchManager()
.getSelectionController()
.getSelectedText();
Criteria.checkThat(selection, Matchers.is("Search"));
});
TestThreadUtils.runOnUiThreadBlocking(() -> activity2.getCurrentTabModel().closeAllTabs());
ApplicationTestUtils.finishActivity(activity2);
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
// TODO(donnd): Investigate support for logging user interactions for Long-press.
public void testLoggedEventId() throws Exception {
FeatureList.setTestFeatures(ENABLE_NONE);
mFakeServer.reset();
simulateResolveSearch("intelligence-logged-event-id");
tapPeekingBarToExpandAndAssert();
closePanel();
// Now the event and outcome should be in local storage.
simulateResolveSearch("search");
// Check that we sent the logged event ID and outcome with the request.
Assert.assertEquals(ContextualSearchFakeServer.LOGGED_EVENT_ID,
mManager.getContext().getPreviousEventId());
Assert.assertEquals(1, mManager.getContext().getPreviousUserInteractions());
closePanel();
// Now that we've sent them to the server, the local storage should be clear.
simulateResolveSearch("search");
Assert.assertEquals(0, mManager.getContext().getPreviousEventId());
Assert.assertEquals(0, mManager.getContext().getPreviousUserInteractions());
closePanel();
// Make sure a duration was recorded in bucket 0 (due to 0 days duration running this test).
Assert.assertEquals(1,
RecordHistogram.getHistogramValueCountForTesting(
"Search.ContextualSearch.OutcomesDuration", 0));
}
// --------------------------------------------------------------------------------------------
// Longpress-resolve Feature tests: force long-press experiment and make sure that triggers.
// --------------------------------------------------------------------------------------------
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testTapIsIgnoredWithLongpressResolveEnabled() throws Exception {
// Enabling Translations implicitly enables Longpress too.
FeatureList.setTestFeatures(ENABLE_TRANSLATIONS);
clickNode("states");
Assert.assertNull(getSelectedText());
assertPanelClosedOrUndefined();
assertLoadedNoUrl();
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testLongpressResolveEnabled() throws Exception {
// Enabling Translations implicitly enables Longpress too.
FeatureList.setTestFeatures(ENABLE_TRANSLATIONS);
longPressNode("states");
assertLoadedNoUrl();
assertSearchTermRequested();
fakeResponse(false, 200, "states", "United States Intelligence", "alternate-term", false);
waitForPanelToPeek();
assertLoadedLowPriorityUrl();
assertContainsParameters("states", "alternate-term");
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisabledTest(message = "https://crbug.com/1048827, https://crbug.com/1181088")
public void testLongpressExtendingSelectionExactResolve() throws Exception {
// Enabling Translations implicitly enables Longpress too.
FeatureList.setTestFeatures(ENABLE_TRANSLATIONS);
// Set up UserAction monitoring.
Set<String> userActions = new HashSet();
userActions.add("ContextualSearch.SelectionEstablished");
userActions.add("ContextualSearch.ManualRefine");
UserActionMonitor userActionMonitor = new UserActionMonitor(userActions);
// First test regular long-press. It should not require an exact resolve.
longPressNode("search");
fakeAResponse();
assertSearchTermRequested();
assertExactResolve(false);
// Long press a node without release so we can simulate the user extending the selection.
long downTime = longPressNodeWithoutUp("search");
// Extend the selection to the nearby word.
longPressExtendSelection("term", "resolution", downTime);
waitForSelectActionBarVisible();
fakeAResponse();
assertSearchTermRequested();
assertExactResolve(true);
// Check UMA metrics recorded.
Assert.assertEquals(2, userActionMonitor.get("ContextualSearch.ManualRefine"));
Assert.assertEquals(2, userActionMonitor.get("ContextualSearch.SelectionEstablished"));
}
// --------------------------------------------------------------------------------------------
// Related Searches Feature tests: base feature enables requests, UI feature allows results.
// --------------------------------------------------------------------------------------------
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testRelatedSearchesInBar() throws Exception {
FeatureList.setTestFeatures(ENABLE_RELATED_SEARCHES_IN_BAR);
mFakeServer.reset();
FakeResolveSearch fakeSearch = simulateResolveSearch("intelligence");
ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
Assert.assertTrue("Related Searches results should have been returned but were not!",
!resolvedSearchTerm.relatedSearchesJson().isEmpty());
// Select a chip in the Bar, which should expand the panel.
final int chipToSelect = 1;
TestThreadUtils.runOnUiThreadBlocking(
() -> mPanel.getRelatedSearchesInBarControl().selectChipForTest(chipToSelect));
waitForPanelToExpand();
// Close the panel
closePanel();
// TODO(donnd): Validate UMA metrics once we log in-bar selections.
}
/**
* Tests that the offset of the SERP is unaffected by whether we are showing Related Searches
* in the Bar or not. See https://crbug.com/1250546.
* @throws Exception
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testRelatedSearchesInBarSerpOffset() throws Exception {
FeatureList.setTestFeatures(ENABLE_RELATED_SEARCHES_IN_BAR);
mFakeServer.reset();
simulateResolveSearch(SIMPLE_SEARCH_NODE_ID);
float plainSearchBarHeight = mPanel.getBarHeight();
float plainSearchContentY = mPanel.getContentY();
closePanel();
// Bring up a panel with Related Searches in order to expand the Bar
simulateResolveSearch(RELATED_SEARCHES_NODE_ID);
// Wait for the animation to start growing the Bar.
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(
mPanel.getInBarRelatedSearchesAnimatedHeightDps(), Matchers.greaterThan(0f));
});
// We should have a taller Bar, but that should not affect the Y offset of the content.
Assert.assertNotEquals(
"Test code failure - unable to open panels with differing Bar heights!",
plainSearchBarHeight, mPanel.getBarHeight(), 0.1f);
Assert.assertEquals("SERP content offsets with and without Related Searches should match!",
plainSearchContentY, mPanel.getContentY(), 0.1f);
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testRelatedSearchesInBarWithDefaultQuery() throws Exception {
FeatureList.TestValues testValues = new FeatureList.TestValues();
testValues.setFeatureFlagsOverride(ENABLE_RELATED_SEARCHES_IN_BAR);
testValues.addFieldTrialParamOverride(ChromeFeatureList.RELATED_SEARCHES_IN_BAR,
ContextualSearchFieldTrial.RELATED_SEARCHES_SHOW_DEFAULT_QUERY_CHIP_PARAM_NAME,
"true");
FeatureList.setTestValues(testValues);
mFakeServer.reset();
FakeResolveSearch fakeSearch = simulateResolveSearch("intelligence");
ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
Assert.assertTrue("Related Searches results should have been returned but were not!",
!resolvedSearchTerm.relatedSearchesJson().isEmpty());
// Select a chip in the Bar, which should expand the panel.
final int chipToSelect = 0;
TestThreadUtils.runOnUiThreadBlocking(
() -> mPanel.getRelatedSearchesInBarControl().selectChipForTest(chipToSelect));
waitForPanelToExpand();
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(
mPanel.getSearchBarControl().getSearchTerm(), Matchers.is("Intelligence"));
});
// Close the panel
closePanel();
// TODO(donnd): Validate UMA metrics once we log in-bar selections.
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisabledTest(message = "https://crbug.com/1244089")
public void testRelatedSearchesInBarWithDefaultQuery_HighlightDefaultQuery() throws Exception {
FeatureList.TestValues testValues = new FeatureList.TestValues();
testValues.setFeatureFlagsOverride(ENABLE_RELATED_SEARCHES_IN_BAR);
testValues.addFieldTrialParamOverride(ChromeFeatureList.RELATED_SEARCHES_IN_BAR,
ContextualSearchFieldTrial.RELATED_SEARCHES_SHOW_DEFAULT_QUERY_CHIP_PARAM_NAME,
"true");
FeatureList.setTestValues(testValues);
mFakeServer.reset();
FakeResolveSearch fakeSearch = simulateResolveSearch("intelligence");
ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
Assert.assertTrue("Related Searches results should have been returned but were not!",
!resolvedSearchTerm.relatedSearchesJson().isEmpty());
// Select a chip in the Bar, which should expand the panel.
tapPeekingBarToExpandAndAssert();
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(
mPanel.getSearchBarControl().getSearchTerm(), Matchers.is("Intelligence"));
Criteria.checkThat(mPanel.getRelatedSearchesInBarControl().getSelectedChipForTest(),
Matchers.is(0));
});
// Close the panel
closePanel();
// TODO(donnd): Validate UMA metrics once we log in-bar selections.
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testRelatedSearchesInBarWithDefaultQuery_Ellipsize() throws Exception {
FeatureList.TestValues testValues = new FeatureList.TestValues();
testValues.setFeatureFlagsOverride(ENABLE_RELATED_SEARCHES_IN_BAR);
testValues.addFieldTrialParamOverride(ChromeFeatureList.RELATED_SEARCHES_IN_BAR,
ContextualSearchFieldTrial.RELATED_SEARCHES_SHOW_DEFAULT_QUERY_CHIP_PARAM_NAME,
"true");
testValues.addFieldTrialParamOverride(ChromeFeatureList.RELATED_SEARCHES_IN_BAR,
ContextualSearchFieldTrial
.RELATED_SEARCHES_DEFAULT_QUERY_CHIP_MAX_WIDTH_SP_PARAM_NAME,
"60");
FeatureList.setTestValues(testValues);
mFakeServer.reset();
FakeResolveSearch fakeSearch = simulateResolveSearch("intelligence");
ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
Assert.assertTrue("Related Searches results should have been returned but were not!",
!resolvedSearchTerm.relatedSearchesJson().isEmpty());
// Select a chip in the Bar, which should expand the panel.
tapPeekingBarToExpandAndAssert();
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(
mPanel.getRelatedSearchesInBarControl().getChipsForTest().get(0).model.get(
ChipProperties.TEXT_MAX_WIDTH_PX),
Matchers.not(ChipProperties.SHOW_WHOLE_TEXT));
});
// Close the panel
closePanel();
// TODO(donnd): Validate UMA metrics once we log in-bar selections.
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
public void testRelatedSearchesInBarForDefinitionCard() throws Exception {
CompositorAnimationHandler.setTestingMode(true);
FeatureList.setTestFeatures(ENABLE_RELATED_SEARCHES_IN_BAR);
mFakeServer.reset();
// Do a normal search without Related Searches or Definition cards.
simulateResolveSearch("search");
float normalHeight = mPanel.getHeight();
// Simulate a response that includes both a definition and Related Searches
List<String> inBarSuggestions = new ArrayList<String>();
inBarSuggestions.add("Related Suggestion 1");
inBarSuggestions.add("Related Suggestion 2");
TestThreadUtils.runOnUiThreadBlocking(
()
-> mPanel.onSearchTermResolved("obscure · əbˈskyo͝or", null, null,
QuickActionCategory.NONE, CardTag.CT_DEFINITION, inBarSuggestions,
false /* showDefaultSearchInBar */,
null /* relatedSearchesInContent */,
false /* showDefaultSearchInContent */));
boolean didPanelGetTaller = mPanel.getHeight() > normalHeight;
Assert.assertTrue(
"Related Searches should show in a taller Bar when there's a definition card, "
+ "but they did not!",
didPanelGetTaller);
// Clean up
closePanel();
CompositorAnimationHandler.setTestingMode(false);
}
@Test
@SmallTest
@Feature({"ContextualSearch"})
@DisabledTest(message = "https://crbug.com/1251774")
public void testRelatedSearchesDismissDuringAnimation() throws Exception {
FeatureList.setTestFeatures(ENABLE_RELATED_SEARCHES_IN_BAR);
mFakeServer.reset();
// Use the "intelligence" node to generate Related Searches suggestions.
simulateResolveSearch("intelligence");
// Wait for the animation to start growing the Bar.
CriteriaHelper.pollUiThread(() -> {
Criteria.checkThat(
mPanel.getInBarRelatedSearchesAnimatedHeightDps(), Matchers.greaterThan(0f));
});
// Wait for the animation to change to make sure that doesn't bring the Bar back
final boolean[] didAnimationChange = {false};
mPanel.getSearchBarControl().setInBarAnimationTestNotifier(
() -> { didAnimationChange[0] = true; });
CriteriaHelper.pollUiThread(
() -> { Criteria.checkThat(didAnimationChange[0], Matchers.is(true)); });
// Repeatedly closing the panel should not bring it back even during ongoing animation.
closePanel();
Assert.assertFalse("The panel is showing again due to Animation!", mPanel.isShowing());
// Another scroll might try to close the panel when it thinks it's already closed, which
// could fail due to inconsistencies in internal logic, so test that too.
closePanel();
Assert.assertFalse("Expected the panel to not be showing after a close! "
+ "Animation of the Bar height is the likely cause.",
mPanel.isShowing());
}
// --------------------------------------------------------------------------------------------
// Forced Caption Feature tests.
// --------------------------------------------------------------------------------------------
/**
* Tests that a caption is shown on a non intelligent search when the force-caption feature is
* enabled.
*/
@Test
@SmallTest
@Feature({"ContextualSearch"})
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@DisabledTest(message = "Enable when rolling out the Forced Caption Features")
public void testNonResolveCaption() throws Exception {
// Simulate a non-resolve search and make sure no Caption is shown.
FeatureList.setTestFeatures(DISABLE_FORCE_CAPTION);
simulateNonResolveSearch("search");
Assert.assertFalse(mPanel.getSearchBarControl().getCaptionVisible());
closePanel();
// Now try again with Caption-forcing.
FeatureList.setTestFeatures(ENABLE_FORCE_CAPTION);
simulateNonResolveSearch("search");
Assert.assertTrue(mPanel.getSearchBarControl().getCaptionVisible());
closePanel();
}
}