chromium/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/ScreenshotCaptureTest.java

// Copyright 2024 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.gesturenav;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

import android.graphics.Bitmap;
import android.os.Build.VERSION_CODES;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.TestAnimations;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManagerTestUtils;
import org.chromium.chrome.browser.homepage.HomepageTestRule;
import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
import org.chromium.chrome.browser.tab.TabStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.chrome.test.util.NewTabPageTestUtils;
import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
import org.chromium.chrome.test.util.browser.suggestions.mostvisited.FakeMostVisitedSites;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.test.util.UiUtils;
import org.chromium.content_public.browser.test.util.WebContentsUtils;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.test.util.NightModeTestUtils;
import org.chromium.ui.test.util.RenderTestRule;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/** Test that the screenshot was successfully taken when navigating as expected. */
@RunWith(ParameterizedRunner.class)
@CommandLineFlags.Add({
    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
    "hide-scrollbars",
    "enable-features=BackForwardTransitions<Study",
    "force-fieldtrials=Study/Group",
    "force-fieldtrial-params=Study.Group:transition_from_native_pages/true/"
            + "transition_to_native_pages/true"
})
@DoNotBatch(reason = "Affect nav settings")
@EnableFeatures(ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)
public class ScreenshotCaptureTest {
    @Rule
    public final SuggestionsDependenciesRule mSuggestionsDeps = new SuggestionsDependenciesRule();

    @Rule
    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();

    @Rule public HomepageTestRule mHomepageTestRule = new HomepageTestRule();

    @Rule
    public ChromeRenderTestRule mRenderTestRule =
            ChromeRenderTestRule.Builder.withPublicCorpus()
                    .setRevision(2)
                    .setBugComponent(RenderTestRule.Component.UI_BROWSER_NAVIGATION_GESTURENAV)
                    .build();

    private static final String TEST_PAGE = "/chrome/test/data/android/simple.html";
    private static final String TEST_PAGE_2 = "/chrome/test/data/android/google.html";
    private static final String LONG_HTML_TEST_PAGE =
            UrlUtils.encodeHtmlDataUri(
                    "<html>"
                            + "<head>"
                            + "  <meta name=\"viewport\" content=\"width=device-width\">"
                            + "</head>"
                            + "<body style='height:100000px;'>"
                            + "</body>"
                            + "</html>");

    private EmbeddedTestServer mTestServer;
    private Bitmap mCapturedBitmap;
    private ScreenshotCaptureTestHelper mScreenshotCaptureTestHelper;

    @ParameterAnnotations.UseMethodParameterBefore(NightModeTestUtils.NightModeParams.class)
    public void setupNightMode(boolean nightModeEnabled) {
        ChromeNightModeTestUtils.setUpNightModeForChromeActivity(nightModeEnabled);
        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
    }

    @BeforeClass
    public static void setUpBeforeActivityLaunched() {
        ChromeNightModeTestUtils.setUpNightModeBeforeChromeActivityLaunched();
    }

    @AfterClass
    public static void tearDownAfterActivityDestroyed() {
        ChromeNightModeTestUtils.tearDownNightModeAfterChromeActivityDestroyed();
    }

    @Before
    public void setUp() {
        TestAnimations.setEnabled(true);
        // Fix the port cause the screenshot includes the url bar
        mTestServer =
                EmbeddedTestServer.createAndStartServerWithPort(
                        ApplicationProvider.getApplicationContext(), 46985);

        var mSiteSuggestions = NewTabPageTestUtils.createFakeSiteSuggestions(mTestServer);
        var mMostVisitedSites = new FakeMostVisitedSites();
        mMostVisitedSites.setTileSuggestions(mSiteSuggestions);
        mSuggestionsDeps.getFactory().mostVisitedSites = mMostVisitedSites;
        mScreenshotCaptureTestHelper = new ScreenshotCaptureTestHelper();
        mHomepageTestRule.useChromeNtpForTest();
    }

    @After
    public void tearDown() {
        mScreenshotCaptureTestHelper.setNavScreenshotCallbackForTesting(null);
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
    public void testNavigatingAwayFromNtpToNormalPage(boolean nightModeEnabled)
            throws IOException, TimeoutException, InterruptedException {
        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
        NewTabPageTestUtils.waitForNtpLoaded(mActivityTestRule.getActivity().getActivityTab());

        CallbackHelper callbackHelper = new CallbackHelper();
        int currentNavIndex =
                mActivityTestRule
                        .getActivity()
                        .getCurrentWebContents()
                        .getNavigationController()
                        .getNavigationHistory()
                        .getCurrentEntryIndex();

        mScreenshotCaptureTestHelper.setNavScreenshotCallbackForTesting(
                new ScreenshotCaptureTestHelper.NavScreenshotCallback() {
                    @Override
                    public Bitmap onAvailable(int navIndex, Bitmap bitmap, boolean requested) {
                        Assert.assertEquals(
                                "Should capture the screenshot of the previous page.",
                                currentNavIndex,
                                navIndex);
                        Assert.assertTrue(requested);
                        mCapturedBitmap = bitmap;
                        callbackHelper.notifyCalled();
                        return null;
                    }
                });

        mActivityTestRule.loadUrl(mTestServer.getURL(TEST_PAGE));

        callbackHelper.waitForOnly();
        mRenderTestRule.compareForResult(mCapturedBitmap, "navigate_away_from_ntp_to_normal_page");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
    public void testNavigatingAwayFromNtpToWebUiPage(boolean nightModeEnabled)
            throws IOException, TimeoutException, InterruptedException {
        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
        NewTabPageTestUtils.waitForNtpLoaded(mActivityTestRule.getActivity().getActivityTab());

        CallbackHelper callbackHelper = new CallbackHelper();
        int currentNavIndex =
                mActivityTestRule
                        .getActivity()
                        .getCurrentWebContents()
                        .getNavigationController()
                        .getNavigationHistory()
                        .getCurrentEntryIndex();

        mScreenshotCaptureTestHelper.setNavScreenshotCallbackForTesting(
                new ScreenshotCaptureTestHelper.NavScreenshotCallback() {
                    @Override
                    public Bitmap onAvailable(int navIndex, Bitmap bitmap, boolean requested) {
                        Assert.assertEquals(
                                "Should capture the screenshot of the previous page.",
                                currentNavIndex,
                                navIndex);
                        Assert.assertTrue(requested);
                        mCapturedBitmap = bitmap;
                        callbackHelper.notifyCalled();
                        return null;
                    }
                });

        mActivityTestRule.loadUrl(UrlConstants.GPU_URL);

        callbackHelper.waitForOnly();
        mRenderTestRule.compareForResult(mCapturedBitmap, "navigate_away_from_ntp_to_webui_page");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    // The test is based on 3-button mode. The newer version defaults to gesture mode.
    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.Q)
    @DisabledTest(message = "https://crbug.com/357833738")
    @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
    public void testNavigatingBackToNtpFromNormalPage(boolean nightModeEnabled)
            throws IOException, TimeoutException, InterruptedException {
        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
        NewTabPageTestUtils.waitForNtpLoaded(mActivityTestRule.getActivity().getActivityTab());

        mActivityTestRule.loadUrl(mTestServer.getURL(TEST_PAGE));

        GestureNavigationTestUtils mNavUtils = new GestureNavigationTestUtils(mActivityTestRule);
        mNavUtils.swipeFromEdgeAndHold(/* leftEdge= */ true);

        CallbackHelper callbackHelper = new CallbackHelper();
        mActivityTestRule
                .getWebContents()
                .captureContentAsBitmapForTesting(
                        bitmap -> {
                            try {
                                mRenderTestRule.compareForResult(
                                        bitmap, "navigate_back_to_ntp_from_normal_page");
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                            callbackHelper.notifyCalled();
                        });
        callbackHelper.waitForOnly();
        ThreadUtils.runOnUiThreadBlocking(() -> mNavUtils.getNavigationHandler().release(true));
        // Wait animation to be finished. Reduce flakiness caused by being destroyed during a
        // running animation.
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    // The test is based on 3-button mode. The newer version defaults to gesture mode.
    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.Q)
    @DisabledTest(message = "https://crbug.com/357833738")
    @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
    public void testNavigatingBackToNtpFromNormalPageWithoutTopControls(boolean nightModeEnabled)
            throws Throwable {
        ThreadUtils.runOnUiThreadBlocking(
                TabStateBrowserControlsVisibilityDelegate::disablePageLoadDelayForTests);
        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
        NewTabPageTestUtils.waitForNtpLoaded(mActivityTestRule.getActivity().getActivityTab());
        FullscreenManagerTestUtils.disableBrowserOverrides();
        mActivityTestRule.loadUrl(LONG_HTML_TEST_PAGE);
        BrowserControlsManager browserControlManager =
                mActivityTestRule.getActivity().getBrowserControlsManager();
        int browserControlsHeight = browserControlManager.getTopControlsHeight();
        FullscreenManagerTestUtils.waitForBrowserControlsToBeMoveable(
                mActivityTestRule, mActivityTestRule.getActivity().getActivityTab());
        FullscreenManagerTestUtils.scrollBrowserControls(mActivityTestRule, false);

        FullscreenManagerTestUtils.waitForBrowserControlsPosition(
                mActivityTestRule, -browserControlsHeight);
        GestureNavigationTestUtils mNavUtils = new GestureNavigationTestUtils(mActivityTestRule);
        mNavUtils.swipeFromEdgeAndHold(/* leftEdge= */ true);

        CallbackHelper callbackHelper = new CallbackHelper();
        mActivityTestRule
                .getWebContents()
                .captureContentAsBitmapForTesting(
                        bitmap -> {
                            try {
                                mRenderTestRule.compareForResult(
                                        bitmap,
                                        "navigate_back_to_ntp_from_normal_page_without_top_controls");
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                            callbackHelper.notifyCalled();
                        });
        callbackHelper.waitForOnly();
        ThreadUtils.runOnUiThreadBlocking(() -> mNavUtils.getNavigationHandler().release(true));
        // Wait animation to be finished. Reduce flakiness caused by being destroyed during a
        // running animation.
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
    }

    @Test
    @MediumTest
    public void testNotCaptureSadTab() throws TimeoutException, InterruptedException {
        mActivityTestRule.startMainActivityWithURL(mTestServer.getURL(TEST_PAGE));
        WebContentsUtils.crashTabAndWait(mActivityTestRule.getWebContents());
        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());

        // Sad tab is not considered as a native page.
        Assert.assertFalse(mActivityTestRule.getActivity().getActivityTab().isNativePage());
        CallbackHelper callbackHelper = new CallbackHelper();
        int currentNavIndex =
                mActivityTestRule
                        .getActivity()
                        .getCurrentWebContents()
                        .getNavigationController()
                        .getNavigationHistory()
                        .getCurrentEntryIndex();

        mScreenshotCaptureTestHelper.setNavScreenshotCallbackForTesting(
                new ScreenshotCaptureTestHelper.NavScreenshotCallback() {
                    @Override
                    public Bitmap onAvailable(int navIndex, Bitmap bitmap, boolean requested) {
                        Assert.assertEquals(
                                "Should attempt to capture the screenshot of the previous page.",
                                currentNavIndex,
                                navIndex);
                        Assert.assertFalse("No screenshot should be captured", requested);
                        Assert.assertNull("No screenshot should be captured", bitmap);
                        callbackHelper.notifyCalled();
                        return null;
                    }
                });

        mActivityTestRule.loadUrl(mTestServer.getURL(TEST_PAGE_2));
        callbackHelper.waitForOnly();
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @DisabledTest(message = "https://crbug.com/357833738")
    public void testNavigateToNTPByHomeButton()
            throws InterruptedException, IOException, TimeoutException {
        mActivityTestRule.startMainActivityWithURL(mTestServer.getURL(TEST_PAGE));

        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());

        CallbackHelper callbackHelper = new CallbackHelper();
        int currentNavIndex =
                mActivityTestRule
                        .getActivity()
                        .getCurrentWebContents()
                        .getNavigationController()
                        .getNavigationHistory()
                        .getCurrentEntryIndex();

        mScreenshotCaptureTestHelper.setNavScreenshotCallbackForTesting(
                new ScreenshotCaptureTestHelper.NavScreenshotCallback() {
                    @Override
                    public Bitmap onAvailable(int navIndex, Bitmap bitmap, boolean requested) {
                        Assert.assertEquals(
                                "Should capture the screenshot of the previous page.",
                                currentNavIndex,
                                navIndex);
                        Assert.assertTrue(requested);
                        mCapturedBitmap = bitmap;
                        callbackHelper.notifyCalled();
                        return null;
                    }
                });

        onView(withId(R.id.home_button)).perform(click());
        NewTabPageTestUtils.waitForNtpLoaded(mActivityTestRule.getActivity().getActivityTab());

        // Expect to capture a screenshot of TEST_PAGE
        callbackHelper.waitForOnly();
        mRenderTestRule.compareForResult(mCapturedBitmap, "navigate_to_ntp_by_home_button");
    }
}