chromium/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java

// Copyright 2020 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.android_webview.test.devui;

import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.assertNoUnverifiedIntents;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.getClipBoardTextOnUiThread;
import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.withCount;

import android.app.Activity;
import android.app.Instrumentation.ActivityResult;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.provider.Settings;

import androidx.test.espresso.intent.Intents;
import androidx.test.espresso.intent.matcher.IntentMatchers;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;

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

import org.chromium.android_webview.devui.HomeFragment;
import org.chromium.android_webview.devui.MainActivity;
import org.chromium.android_webview.devui.R;
import org.chromium.android_webview.devui.WebViewPackageError;
import org.chromium.android_webview.nonembedded_util.WebViewPackageHelper;
import org.chromium.android_webview.test.AwJUnit4ClassRunner;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseActivityTestRule;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.ui.test.util.ViewUtils;

import java.util.Locale;

/** UI tests for the developer UI's HomeFragment. */
@RunWith(AwJUnit4ClassRunner.class)
@DoNotBatch(reason = "Batching causes test failures")
public class HomeFragmentTest {
    public static final PackageInfo FAKE_WEBVIEW_PACKAGE = new PackageInfo();

    static {
        FAKE_WEBVIEW_PACKAGE.packageName = "org.chromium.fake_webview";
        FAKE_WEBVIEW_PACKAGE.versionCode = 123456789;
        FAKE_WEBVIEW_PACKAGE.versionName = "999.888.777.666";
    }

    @Rule
    public BaseActivityTestRule<MainActivity> mRule =
            new BaseActivityTestRule<>(MainActivity.class);

    @Before
    public void setUp() {
        // Mark popup permission as already requested to suppress the popup
        MainActivity.markPopupPermissionRequestedInPrefsForTesting();
    }

    @After
    public void tearDown() {
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(null);
        // Activity is launched, i.e the test is not skipped.
        if (mRule.getActivity() != null) {
            // Tests are responsible for verifying every Intent they trigger.
            assertNoUnverifiedIntents();
            Intents.release();
        }
    }

    private void launchHomeFragment() {
        mRule.launchActivity(null);
        ViewUtils.waitForVisibleView(withId(R.id.fragment_home));
        // Only start recording intents after launching the MainActivity.
        Intents.init();

        // Stub all external intents, to avoid launching other apps (ex. system browser), has to be
        // done after launching the activity.
        intending(not(IntentMatchers.isInternal()))
                .respondWith(new ActivityResult(Activity.RESULT_OK, null));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testHasPublicNoArgsConstructor() throws Throwable {
        HomeFragment fragment = new HomeFragment();
        Assert.assertNotNull(fragment);
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    // Test when the system WebView provider is the same package from which the developer UI is
    // launched.
    public void testSameWebViewPackage() throws Throwable {
        Context context = ContextUtils.getApplicationContext();
        // Inject test app package as the current WebView package.
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(
                WebViewPackageHelper.getContextPackageInfo(context));
        launchHomeFragment();

        // No error messages is displayed.
        onView(withId(R.id.main_error_view)).check(matches(not(isDisplayed())));

        onView(withId(R.id.main_info_list)).check(matches(withCount(2)));

        PackageInfo currentWebViewPackage = WebViewPackageHelper.getCurrentWebViewPackage(context);
        String expectedWebViewPackageInfo =
                String.format(
                        Locale.US,
                        "%s (%s/%s)",
                        currentWebViewPackage.packageName,
                        currentWebViewPackage.versionName,
                        currentWebViewPackage.versionCode);
        onData(anything())
                .atPosition(0)
                .onChildView(withId(android.R.id.text1))
                .check(matches(withText("WebView package")));
        onData(anything())
                .atPosition(0)
                .onChildView(withId(android.R.id.text2))
                .check(matches(withText(expectedWebViewPackageInfo)));

        String expectedDeviceInfo =
                String.format(Locale.US, "%s - %s", Build.MODEL, Build.FINGERPRINT);
        onData(anything())
                .atPosition(1)
                .onChildView(withId(android.R.id.text1))
                .check(matches(withText("Device info")));
        onData(anything())
                .atPosition(1)
                .onChildView(withId(android.R.id.text2))
                .check(matches(withText(expectedDeviceInfo)));
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    // Test when the system WebView provider is different from the package from which the developer
    // UI is launched.
    public void testDifferentWebViewPackage() throws Throwable {
        Context context = ContextUtils.getApplicationContext();
        // Inject a fake PackageInfo as the current WebView package to make sure it will always be
        // different from the test's app package.
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
        launchHomeFragment();

        onView(withId(R.id.main_info_list)).check(matches(withCount(3)));

        String expectedWebViewPackageInfo =
                String.format(
                        Locale.US,
                        "%s (%s/%s)",
                        FAKE_WEBVIEW_PACKAGE.packageName,
                        FAKE_WEBVIEW_PACKAGE.versionName,
                        FAKE_WEBVIEW_PACKAGE.versionCode);
        onData(anything())
                .atPosition(0)
                .onChildView(withId(android.R.id.text1))
                .check(matches(withText("WebView package")));
        onData(anything())
                .atPosition(0)
                .onChildView(withId(android.R.id.text2))
                .check(matches(withText(expectedWebViewPackageInfo)));

        PackageInfo devUiPackage = WebViewPackageHelper.getContextPackageInfo(context);
        String expectedDevUiInfo =
                String.format(
                        Locale.US,
                        "%s (%s/%s)",
                        devUiPackage.packageName,
                        devUiPackage.versionName,
                        devUiPackage.versionCode);
        onData(anything())
                .atPosition(1)
                .onChildView(withId(android.R.id.text1))
                .check(matches(withText("DevTools package")));
        onData(anything())
                .atPosition(1)
                .onChildView(withId(android.R.id.text2))
                .check(matches(withText(expectedDevUiInfo)));

        String expectedDeviceInfo =
                String.format(Locale.US, "%s - %s", Build.MODEL, Build.FINGERPRINT);
        onData(anything())
                .atPosition(2)
                .onChildView(withId(android.R.id.text1))
                .check(matches(withText("Device info")));
        onData(anything())
                .atPosition(2)
                .onChildView(withId(android.R.id.text2))
                .check(matches(withText(expectedDeviceInfo)));
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    @DisableIf.Build(
            sdk_is_greater_than = Build.VERSION_CODES.R,
            message = "https://crbug.com/1292197")
    public void testLongPressCopy() throws Throwable {
        Context context = ContextUtils.getApplicationContext();
        // Inject a fake PackageInfo as the current WebView package to make sure it will always be
        // different from the test's app package.
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
        launchHomeFragment();

        onView(withText("WebView package")).perform(longClick());
        String expectedWebViewInfo =
                String.format(
                        Locale.US,
                        "%s (%s/%s)",
                        FAKE_WEBVIEW_PACKAGE.packageName,
                        FAKE_WEBVIEW_PACKAGE.versionName,
                        FAKE_WEBVIEW_PACKAGE.versionCode);
        assertThat(getClipBoardTextOnUiThread(context), is(equalTo(expectedWebViewInfo)));

        onView(withText("DevTools package")).perform(longClick());
        PackageInfo devUiPackage = WebViewPackageHelper.getContextPackageInfo(context);
        String expectedDevUiInfo =
                String.format(
                        Locale.US,
                        "%s (%s/%s)",
                        devUiPackage.packageName,
                        devUiPackage.versionName,
                        devUiPackage.versionCode);
        assertThat(getClipBoardTextOnUiThread(context), is(equalTo(expectedDevUiInfo)));

        onView(withText("Device info")).perform(longClick());
        String expectedDeviceInfo =
                String.format(Locale.US, "%s - %s", Build.MODEL, Build.FINGERPRINT);
        assertThat(getClipBoardTextOnUiThread(context), is(equalTo(expectedDeviceInfo)));
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    public void testDifferentWebViewPackageError_bannerMessage_postNougat() throws Throwable {
        // Inject a fake PackageInfo as the current WebView package to make sure it will always be
        // different from the test's app package.
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
        launchHomeFragment();

        Context context = ContextUtils.getApplicationContext();
        String expectedErrorMessage =
                String.format(
                        Locale.US,
                        WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_ERROR_MESSAGE,
                        WebViewPackageHelper.loadLabel(context));
        ViewUtils.waitForVisibleView(withId(R.id.main_error_view));
        onView(withId(R.id.main_error_view)).check(matches(isDisplayed()));
        onView(withId(R.id.error_text)).check(matches(withText(expectedErrorMessage)));
        // Since the current provider is set to a fake package not an actual installed WebView
        // provider, the UI should only offer to change the system WebView provider and should not
        // offer to open the current WebView provider dev UI.

        onView(withId(R.id.action_button))
                .check(
                        matches(
                                allOf(
                                        isDisplayed(),
                                        withText(
                                                WebViewPackageError
                                                        .CHANGE_WEBVIEW_PROVIDER_BUTTON_TEXT))))
                .perform(click());
        intended(IntentMatchers.hasAction(Settings.ACTION_WEBVIEW_SETTINGS));
    }

    @Test
    @MediumTest
    @Feature({"AndroidWebView"})
    // Test the dialog shown when the WebView package error message is clicked.
    public void testDifferentWebViewPackageError_dialog_postNougat() throws Throwable {
        Context context = ContextUtils.getApplicationContext();
        // Inject a fake PackageInfo as the current WebView package to make sure it will always be
        // different from the test's app package.
        WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
        launchHomeFragment();

        String dialogExpectedMessage =
                String.format(
                        Locale.US,
                        WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_DIALOG_MESSAGE,
                        WebViewPackageHelper.loadLabel(context));
        onView(withId(R.id.main_error_view)).check(matches(isDisplayed())).perform(click());
        onView(withText(dialogExpectedMessage)).check(matches(isDisplayed()));
        // Since the current provider is set to a fake package not an actual installed WebView
        // provider, the UI should only offer to change the system WebView provider and should not
        // offer to open the current WebView provider dev UI.
        onView(withId(android.R.id.button1)).check(matches(not(isDisplayed()))); // positive button
        onView(withId(android.R.id.button2)).check(matches(not(isDisplayed()))); // negative button
        // botton3 is dialog neutral button
        onView(withId(android.R.id.button3))
                .check(matches(withText(WebViewPackageError.CHANGE_WEBVIEW_PROVIDER_BUTTON_TEXT)))
                .perform(click());
        intended(IntentMatchers.hasAction(Settings.ACTION_WEBVIEW_SETTINGS));
    }
}