chromium/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchHistogramTest.java

// Copyright 2023 The Chromium Authors
// 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.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;

import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.RelatedSearchesControl;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFakeServer.FakeResolveSearch;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;

/** Tests the Contextual Search histograms. */
// NOTE: Disable online detection so we we'll default to online on test bots with no network.
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures(ChromeFeatureList.CONTEXTUAL_SEARCH_DISABLE_ONLINE_DETECTION)
@Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
@Batch(Batch.PER_CLASS)
public class ContextualSearchHistogramTest extends ContextualSearchInstrumentationBase {
    private HistogramWatcher mResultsSeenHistogramWatcher;
    private HistogramWatcher mAllSearchesHistogramWatcher;
    private HistogramWatcher mTapResultsSeenHistogramWatcher;
    private HistogramWatcher mNumberOfSuggestionsClicked2HistogramWatcher;
    private HistogramWatcher mSelectedCarouselIndexHistogramWatcher;
    private HistogramWatcher mSelectedSuggestionIndexHistogramWatcher;
    private HistogramWatcher mCTRHistogramWatcher;
    private HistogramWatcher mCarouselScrolledHistogramWatcher;
    private HistogramWatcher mCarouselScrollAndClickHistogramWatcher;
    private HistogramWatcher mCarouselLastVisibleItemPositionHistogramWatcher;

    @Override
    @Before
    public void setUp() throws Exception {
        mTestPage = "/chrome/test/data/android/contextualsearch/tap_test.html";
        super.setUp();
    }

    // ============================================================================================
    // UMA assertions
    // ============================================================================================

    /**
     * Create HistogramWatcher for a sequence of user actions that peek and expand the panel with
     * Related Searches showing and then close the panel without selecting any suggestion.
     *
     * @param isUKMEnabled Whether UKM is enabled and whether related histograms should be recorded.
     */
    private void createHistogramWatcherForPeekAndExpandForRSearches(boolean isUKMEnabled) {
        createHistogramWatcherForPeekAndExpandForRSearches(-1, isUKMEnabled);
    }

    /**
     * UMA HistogramWatcher for a sequence of user actions that peek and expand the panel with
     * Related Searches showing and then close the panel.
     *
     * @param whichSuggestion Which suggestion was selected. A value of -1 means none.
     * @param isUKMEnabled Whether UKM is enabled and whether related histograms should be recorded.
     */
    private void createHistogramWatcherForPeekAndExpandForRSearches(
            int whichSuggestion, boolean isUKMEnabled) {
        final int relatedSearchesCount = whichSuggestion > -1 ? 1 : 0;
        mResultsSeenHistogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecordTimes(
                                "Search.ContextualSearch.All.ResultsSeen", true, 1)
                        .build();
        HistogramWatcher.Builder histogramWatcherBuilder =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecordTimes("Search.ContextualSearch.All.Searches", false, 1);
        if (relatedSearchesCount > 0) {
            histogramWatcherBuilder.expectBooleanRecordTimes(
                    "Search.ContextualSearch.All.Searches", true, relatedSearchesCount);
        }
        mAllSearchesHistogramWatcher = histogramWatcherBuilder.build();

        histogramWatcherBuilder =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecordTimes(
                                "Search.ContextualSearch.Tap.ResultsSeen", true, 1);
        if (isUKMEnabled) {
            histogramWatcherBuilder.expectBooleanRecordTimes(
                    "Search.ContextualSearch.Tap.SyncEnabled.ResultsSeen", true, 1);
        } else {
            histogramWatcherBuilder.expectNoRecords(
                    "Search.ContextualSearch.Tap.SyncEnabled.ResultsSeen");
        }
        mTapResultsSeenHistogramWatcher = histogramWatcherBuilder.build();

        if (relatedSearchesCount > 0) {
            mNumberOfSuggestionsClicked2HistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectBooleanRecordTimes(
                                    "Search.RelatedSearches.NumberOfSuggestionsClicked2", true, 1)
                            .build();

            mSelectedCarouselIndexHistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectIntRecordTimes(
                                    "Search.RelatedSearches.SelectedCarouselIndex",
                                    whichSuggestion,
                                    1)
                            .build();

            mSelectedSuggestionIndexHistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectIntRecordTimes(
                                    "Search.RelatedSearches.SelectedSuggestionIndex",
                                    whichSuggestion,
                                    1)
                            .build();

            mCTRHistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectIntRecordTimes("Search.RelatedSearches.CTR", 1, 1)
                            .build();
        } else {
            mNumberOfSuggestionsClicked2HistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectNoRecords("Search.RelatedSearches.NumberOfSuggestionsClicked2")
                            .build();

            mSelectedCarouselIndexHistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectNoRecords("Search.RelatedSearches.SelectedCarouselIndex")
                            .build();

            mSelectedSuggestionIndexHistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectNoRecords("Search.RelatedSearches.SelectedSuggestionIndex")
                            .build();

            mCTRHistogramWatcher =
                    HistogramWatcher.newBuilder()
                            .expectIntRecordTimes("Search.RelatedSearches.CTR", 0, 1)
                            .build();
        }

        mCarouselScrolledHistogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectBooleanRecordTimes(
                                "Search.RelatedSearches.CarouselScrolled", false, 1)
                        .build();

        mCarouselScrollAndClickHistogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectIntRecordTimes(
                                "Search.RelatedSearches.CarouselScrollAndClick",
                                relatedSearchesCount > 0 ? 1 : 0,
                                1)
                        .build();

        mCarouselLastVisibleItemPositionHistogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectAnyRecord("Search.RelatedSearches.CarouselLastVisibleItemPosition")
                        .allowExtraRecords("Search.RelatedSearches.CarouselLastVisibleItemPosition")
                        .build();
    }

    /** Assert HistogramWatcher to check the histograms are as expected. */
    private void assertHistogramWatcherForPeekAndExpandForRSearches() {
        mResultsSeenHistogramWatcher.assertExpected(
                "Some entry in the Search.ContextualSearch.All.Searches histogram was not logged as"
                        + " expected!");
        mAllSearchesHistogramWatcher.assertExpected(
                "Failed to log if a search was seen in the Search.ContextualSearch.All.Searches"
                        + " histogram!");
        mTapResultsSeenHistogramWatcher.assertExpected(
                "Some entry in the Search.ContextualSearch.Tap histograms was not logged as"
                        + " expected!");
        mNumberOfSuggestionsClicked2HistogramWatcher.assertExpected(
                "Failed to log the correct count of Related Searches suggestions clicked in the"
                        + " Search.RelatedSearches.NumberOfSuggestionsClicked2 histogram!");
        mSelectedCarouselIndexHistogramWatcher.assertExpected(
                "Failed to find the expected Related Searches chip logged as clicked in the"
                    + " Search.RelatedSearches.SelectedCarouselIndex histogram that tracks which"
                    + " chip was clicked!");
        mSelectedSuggestionIndexHistogramWatcher.assertExpected(
                "Failed to find the expected Related Searches suggestion logged as selected in the"
                    + " Search.RelatedSearches.SelectedSuggestionIndex histogram that tracks which"
                    + " suggestion was selected!");
        mCTRHistogramWatcher.assertExpected(
                "Failed to log that Related Searches were shown and if one was selected in the"
                        + " Search.RelatedSearches.CTR histogram!");
        mCarouselScrolledHistogramWatcher.assertExpected(
                "Failed to log that the carousel is shown and it was not scrolled in the"
                        + " Search.RelatedSearches.CarouselScrolled histogram!");
        mCarouselScrollAndClickHistogramWatcher.assertExpected(
                "Failed to log that the carousel is shown and it was not scrolled and clicked in"
                        + " the Search.RelatedSearches.CarouselScrollAndClick histogram!");
        mCarouselLastVisibleItemPositionHistogramWatcher.assertExpected(
                "Failed to log the last visible position index for a chip in the carousel in the"
                        + " Search.RelatedSearches.CarouselLastVisibleItemPosition histogram!");
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    @EnableFeatures({ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS})
    public void testRelatedSearchesItemNotSelected() throws Exception {
        mPolicy.overrideAllowSendingPageUrlForTesting(true);
        createHistogramWatcherForPeekAndExpandForRSearches(/* isUKMEnabled= */ false);
        FakeResolveSearch fakeSearch = simulateResolveSearch("intelligence");
        Assert.assertFalse(
                "Related Searches should have been requested but were not!",
                mFakeServer.getSearchContext().getRelatedSearchesStamp().isEmpty());
        ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
        Assert.assertTrue(
                "Related Searches results should have been returned but were not!",
                !resolvedSearchTerm.relatedSearchesJson().isEmpty());
        // Expand the panel and assert that it ends up in the right place.
        expandPanelAndAssert();

        // Don't select any Related Searches suggestion, and close the panel
        closePanel();
        assertHistogramWatcherForPeekAndExpandForRSearches();
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    @EnableFeatures({ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS})
    public void testRelatedSearchesItemNotSelectedUKMEnabled() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(
                                ProfileManager.getLastUsedRegularProfile(), true));
        mPolicy.overrideAllowSendingPageUrlForTesting(true);
        createHistogramWatcherForPeekAndExpandForRSearches(/* isUKMEnabled= */ true);
        simulateResolveSearch("intelligence");
        // Expand the panel and assert that it ends up in the right place.
        expandPanelAndAssert();
        // Don't select any Related Searches suggestion, and close the panel
        closePanel();
        assertHistogramWatcherForPeekAndExpandForRSearches();
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesItemSelected() throws Exception {
        FakeResolveSearch fakeSearch = simulateResolveSearch("intelligence");
        ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
        Assert.assertTrue(
                "Related Searches results should have been returned but were not!",
                !resolvedSearchTerm.relatedSearchesJson().isEmpty());
        // Expand the panel and assert that it ends up in the right place.
        expandPanelAndAssert();

        // Select a Related Searches suggestion.
        RelatedSearchesControl relatedSearchesControl = mPanel.getRelatedSearchesInBarControl();
        final int chipToSelect = 3;
        createHistogramWatcherForPeekAndExpandForRSearches(chipToSelect, /* isUKMEnabled= */ false);
        ThreadUtils.runOnUiThreadBlocking(
                () -> relatedSearchesControl.selectChipForTest(chipToSelect));
        Assert.assertEquals(
                "The Related Searches query was not shown in the Bar!",
                "Selection Related 3",
                mPanel.getSearchBarControl().getSearchTerm());

        // Collapse the panel back to the peeking state
        peekPanel();
        Assert.assertEquals(
                "The default query was not shown in the Bar after returning to peeking state!",
                "Intelligence",
                mPanel.getSearchBarControl().getSearchTerm());

        // Close the panel
        closePanel();
        assertHistogramWatcherForPeekAndExpandForRSearches();
    }
}