| // Copyright 2019 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.autofill_assistant; |
| |
| import static androidx.test.espresso.Espresso.onView; |
| import static androidx.test.espresso.assertion.ViewAssertions.matches; |
| import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; |
| import static androidx.test.espresso.matcher.ViewMatchers.isRoot; |
| |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.hamcrest.Matchers.is; |
| import static org.hamcrest.Matchers.notNullValue; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.checkElementExists; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.checkElementOnScreen; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.getBoundingRectForElement; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.scrollIntoViewIfNeeded; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitForElementRemoved; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntil; |
| import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.support.test.InstrumentationRegistry; |
| import android.view.View; |
| |
| import androidx.annotation.Nullable; |
| import androidx.test.filters.MediumTest; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import org.chromium.base.test.util.CommandLineFlags; |
| import org.chromium.chrome.browser.app.ChromeActivity; |
| import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayCoordinator; |
| import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayImage; |
| import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel; |
| import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel.AssistantOverlayRect; |
| import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState; |
| import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule; |
| import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils; |
| import org.chromium.chrome.browser.flags.ChromeSwitches; |
| import org.chromium.chrome.test.ChromeJUnit4ClassRunner; |
| import org.chromium.chrome.test.util.ChromeTabUtils; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.browser.test.util.TestThreadUtils; |
| |
| import java.util.Collections; |
| import java.util.concurrent.ExecutionException; |
| |
| /** |
| * Tests for the Autofill Assistant overlay. |
| */ |
| @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) |
| @RunWith(ChromeJUnit4ClassRunner.class) |
| public class AutofillAssistantOverlayUiTest { |
| @Rule |
| public CustomTabActivityTestRule mTestRule = new CustomTabActivityTestRule(); |
| |
| // TODO(crbug.com/806868): Create a more specific test site for overlay testing. |
| private static final String TEST_PAGE = |
| "/components/test/data/autofill_assistant/html/autofill_assistant_target_website.html"; |
| |
| @Before |
| public void setUp() { |
| mTestRule.startCustomTabActivityWithIntent(CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| mTestRule.getTestServer().getURL(TEST_PAGE))); |
| mTestRule.getActivity() |
| .getRootUiCoordinatorForTesting() |
| .getScrimCoordinator() |
| .disableAnimationForTesting(true); |
| } |
| |
| private WebContents getWebContents() { |
| return mTestRule.getWebContents(); |
| } |
| |
| private AssistantOverlayModel createModel() { |
| return TestThreadUtils.runOnUiThreadBlockingNoException(AssistantOverlayModel::new); |
| } |
| |
| /** Creates a coordinator for use in UI tests with a default, non-null overlay image. */ |
| private AssistantOverlayCoordinator createCoordinator(AssistantOverlayModel model) |
| throws ExecutionException { |
| return createCoordinator(model, |
| BitmapFactory.decodeResource(mTestRule.getActivity().getResources(), |
| org.chromium.chrome.autofill_assistant.R.drawable.btn_close)); |
| } |
| |
| /** Creates a coordinator for use in UI tests with a custom overlay image. */ |
| private AssistantOverlayCoordinator createCoordinator( |
| AssistantOverlayModel model, @Nullable Bitmap overlayImage) throws ExecutionException { |
| ChromeActivity activity = mTestRule.getActivity(); |
| return runOnUiThreadBlocking(() |
| -> new AssistantOverlayCoordinator(activity, |
| activity.getBrowserControlsManager(), |
| activity.getCompositorViewHolder(), |
| mTestRule.getActivity() |
| .getRootUiCoordinatorForTesting() |
| .getScrimCoordinator(), |
| model)); |
| } |
| |
| /** Tests assumptions about the initial state of the infobox. */ |
| @Test |
| @MediumTest |
| public void testInitialState() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| AssistantOverlayCoordinator coordinator = createCoordinator(model); |
| |
| assertScrimDisplayed(false); |
| tapElement("touch_area_one"); |
| waitForElementRemoved(getWebContents(), "touch_area_one"); |
| } |
| |
| /** Tests assumptions about the full overlay. */ |
| @Test |
| @MediumTest |
| public void testFullOverlay() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| AssistantOverlayCoordinator coordinator = createCoordinator(model); |
| |
| runOnUiThreadBlocking( |
| () -> model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL)); |
| assertScrimDisplayed(true); |
| tapElement("touch_area_one"); |
| assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(true)); |
| |
| runOnUiThreadBlocking( |
| () -> model.set(AssistantOverlayModel.STATE, AssistantOverlayState.HIDDEN)); |
| assertScrimDisplayed(false); |
| tapElement("touch_area_one"); |
| waitForElementRemoved(getWebContents(), "touch_area_one"); |
| } |
| |
| /** Tests assumptions about the full overlay. */ |
| @Test |
| @MediumTest |
| public void testFullOverlayWithImage() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| AssistantOverlayCoordinator coordinator = createCoordinator(model); |
| |
| AssistantOverlayImage image = new AssistantOverlayImage( |
| 64, 64, 40, "example.com", Color.parseColor("#B3FFFFFF"), 40); |
| runOnUiThreadBlocking(() -> { |
| model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL); |
| model.set(AssistantOverlayModel.OVERLAY_IMAGE, image); |
| }); |
| assertScrimDisplayed(true); |
| // TODO(b/143452916): Test that the overlay image is actually being displayed. |
| } |
| |
| /** Tests assumptions about the partial overlay. */ |
| @Test |
| @MediumTest |
| public void testPartialOverlay() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| AssistantOverlayCoordinator coordinator = createCoordinator(model); |
| |
| // Partial overlay, no touchable areas: equivalent to full overlay. |
| runOnUiThreadBlocking( |
| () -> model.set(AssistantOverlayModel.STATE, AssistantOverlayState.PARTIAL)); |
| assertScrimDisplayed(true); |
| tapElement("touch_area_one"); |
| assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(true)); |
| |
| Rect rect = getBoundingRectForElement(getWebContents(), "touch_area_one"); |
| runOnUiThreadBlocking( |
| () |
| -> model.set(AssistantOverlayModel.TOUCHABLE_AREA, |
| Collections.singletonList(new AssistantOverlayRect(rect)))); |
| |
| // Touchable area set, but no viewport given: equivalent to full overlay. |
| tapElement("touch_area_one"); |
| assertThat(checkElementExists(getWebContents(), "touch_area_one"), is(true)); |
| |
| // Set WebContents. |
| runOnUiThreadBlocking( |
| () -> model.set(AssistantOverlayModel.WEB_CONTENTS, getWebContents())); |
| |
| // Now the partial overlay allows tapping the highlighted touch area. |
| tapElement("touch_area_one"); |
| waitForElementRemoved(getWebContents(), "touch_area_one"); |
| |
| runOnUiThreadBlocking( |
| () -> model.set(AssistantOverlayModel.TOUCHABLE_AREA, Collections.emptyList())); |
| tapElement("touch_area_four"); |
| assertThat(checkElementExists(getWebContents(), "touch_area_four"), is(true)); |
| } |
| |
| /** Scrolls a touchable area into view and then taps it. */ |
| @Test |
| @MediumTest |
| public void testSimpleScrollPartialOverlay() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| createCoordinator(model); |
| |
| ChromeTabUtils.waitForInteractable(mTestRule.getActivity().getActivityTab()); |
| scrollIntoViewIfNeeded(mTestRule.getWebContents(), "touch_area_five"); |
| waitUntil(() -> checkElementOnScreen(mTestRule, "touch_area_five")); |
| Rect rect = getBoundingRectForElement(getWebContents(), "touch_area_five"); |
| runOnUiThreadBlocking(() -> { |
| model.set(AssistantOverlayModel.STATE, AssistantOverlayState.PARTIAL); |
| model.set(AssistantOverlayModel.WEB_CONTENTS, getWebContents()); |
| model.set(AssistantOverlayModel.TOUCHABLE_AREA, |
| Collections.singletonList(new AssistantOverlayRect(rect))); |
| }); |
| assertScrimDisplayed(true); |
| tapElement("touch_area_five"); |
| waitForElementRemoved(getWebContents(), "touch_area_five"); |
| } |
| |
| /** |
| * Regular overlay image test. Since there is no easy way to test whether the image is actually |
| * rendered, this is simply checking that nothing crashes. |
| */ |
| @Test |
| @MediumTest |
| public void testOverlayImageDoesNotCrashIfValid() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| Bitmap bitmap = BitmapFactory.decodeResource(mTestRule.getActivity().getResources(), |
| org.chromium.chrome.autofill_assistant.R.drawable.btn_close); |
| assertThat(bitmap, notNullValue()); |
| AssistantOverlayCoordinator coordinator = |
| createCoordinator(model, /* overlayImage = */ bitmap); |
| |
| runOnUiThreadBlocking(() -> { |
| model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL); |
| model.set(AssistantOverlayModel.OVERLAY_IMAGE, |
| new AssistantOverlayImage(32, 32, 12, "Text", Color.RED, 20)); |
| }); |
| |
| assertScrimDisplayed(true); |
| } |
| |
| /** Simulates what would happen if the overlay image fetcher returned null. */ |
| @Test |
| @MediumTest |
| public void testOverlayDoesNotCrashIfImageFailsToLoad() throws Exception { |
| AssistantOverlayModel model = createModel(); |
| AssistantOverlayCoordinator coordinator = |
| createCoordinator(model, /* overlayImage = */ null); |
| |
| runOnUiThreadBlocking(() -> { |
| model.set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL); |
| model.set(AssistantOverlayModel.OVERLAY_IMAGE, |
| new AssistantOverlayImage(32, 32, 12, "Text", Color.RED, 20)); |
| }); |
| |
| assertScrimDisplayed(true); |
| } |
| |
| private void assertScrimDisplayed(boolean expected) throws Exception { |
| // Wait for UI thread to be idle. |
| onView(isRoot()).check(matches(isDisplayed())); |
| |
| View scrim = mTestRule.getActivity() |
| .getRootUiCoordinatorForTesting() |
| .getScrimCoordinator() |
| .getViewForTesting(); |
| |
| // The scrim view is only attached to the view hierarchy when needed, preventing us from |
| // using regular espresso facilities. |
| boolean scrimInHierarchy = |
| runOnUiThreadBlocking(() -> scrim != null && scrim.getParent() != null); |
| |
| if (expected) { |
| assertTrue( |
| "The scrim wasn't in the hierarchy but was expected to be!", scrimInHierarchy); |
| onView(is(scrim)).check(matches(isDisplayed())); |
| } else { |
| assertFalse( |
| "The scrim was in the hierarchy but wasn't expected to be!", scrimInHierarchy); |
| } |
| } |
| |
| void tapElement(String elementId) throws Exception { |
| AutofillAssistantUiTestUtil.tapElement(mTestRule, elementId); |
| } |
| } |