| // 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 android.support.test.espresso.Espresso.onView; |
| import static android.support.test.espresso.action.ViewActions.actionWithAssertions; |
| import static android.support.test.espresso.action.ViewActions.click; |
| import static android.support.test.espresso.action.ViewActions.scrollTo; |
| import static android.support.test.espresso.action.ViewActions.typeText; |
| import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; |
| import static android.support.test.espresso.assertion.ViewAssertions.matches; |
| import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; |
| import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; |
| import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; |
| import static android.support.test.espresso.matcher.ViewMatchers.withId; |
| import static android.support.test.espresso.matcher.ViewMatchers.withText; |
| |
| import static org.hamcrest.Matchers.not; |
| |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.getAbsoluteBoundingRect; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.startAutofillAssistant; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntilViewAssertionTrue; |
| import static org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiTestUtil.waitUntilViewMatchesCondition; |
| import static org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.PeekMode.HANDLE; |
| import static org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.PeekMode.HANDLE_HEADER; |
| import static org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.PeekMode.HANDLE_HEADER_CAROUSELS; |
| import static org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.ViewportResizing.NO_RESIZE; |
| import static org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.ViewportResizing.RESIZE_LAYOUT_VIEWPORT; |
| import static org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.ViewportResizing.RESIZE_VISUAL_VIEWPORT; |
| |
| import android.graphics.Rect; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.espresso.Espresso; |
| import android.support.test.espresso.ViewAction; |
| import android.support.test.espresso.action.GeneralLocation; |
| import android.support.test.espresso.action.GeneralSwipeAction; |
| import android.support.test.espresso.action.Press; |
| import android.support.test.espresso.action.Swipe; |
| import android.support.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.autofill_assistant.R; |
| import org.chromium.chrome.browser.ChromeSwitches; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ActionProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ChipIcon; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ChipProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ChipType; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ClientSettingsProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.CollectUserDataProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.PeekMode; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ConfigureBottomSheetProto.ViewportResizing; |
| import org.chromium.chrome.browser.autofill_assistant.proto.DetailsProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ElementReferenceProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.FocusElementProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.PromptProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.PromptProto.Choice; |
| import org.chromium.chrome.browser.autofill_assistant.proto.ShowDetailsProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.SupportedScriptProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.SupportedScriptProto.PresentationProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.TextInputProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.TextInputSectionProto; |
| import org.chromium.chrome.browser.autofill_assistant.proto.UserFormSectionProto; |
| import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule; |
| import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils; |
| import org.chromium.chrome.test.ChromeJUnit4ClassRunner; |
| import org.chromium.content_public.browser.test.util.Criteria; |
| import org.chromium.content_public.browser.test.util.CriteriaHelper; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Tests autofill assistant bottomsheet. |
| */ |
| @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) |
| @RunWith(ChromeJUnit4ClassRunner.class) |
| public class AutofillAssistantBottomsheetTest { |
| @Rule |
| public CustomTabActivityTestRule mTestRule = new CustomTabActivityTestRule(); |
| |
| private static final String TEST_PAGE = "/components/test/data/autofill_assistant/html/" |
| + "bottomsheet_behaviour_target_website.html"; |
| |
| @Before |
| public void setUp() { |
| AutofillAssistantPreferencesUtil.setInitialPreferences(true); |
| mTestRule.startCustomTabActivityWithIntent(CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| mTestRule.getTestServer().getURL(TEST_PAGE))); |
| mTestRule.getActivity().getScrim().disableAnimationForTesting(true); |
| } |
| |
| private AutofillAssistantTestScript makeScript( |
| ViewportResizing resizing, PeekMode peekMode, boolean withDetails) { |
| ArrayList<ActionProto> list = new ArrayList<>(); |
| // Prompt. |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setPrompt(PromptProto.newBuilder() |
| .setMessage("Hello world!") |
| .addChoices(Choice.newBuilder().setChip( |
| ChipProto.newBuilder() |
| .setType(ChipType.DONE_ACTION) |
| .setText("Focus element")))) |
| .build()); |
| // Set viewport resizing and peek mode. |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setConfigureBottomSheet(ConfigureBottomSheetProto.newBuilder() |
| .setViewportResizing(resizing) |
| .setPeekMode(peekMode)) |
| .build()); |
| // Focus on the bottom element. |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setFocusElement(FocusElementProto.newBuilder().setElement( |
| ElementReferenceProto.newBuilder().addSelectors("p.bottom"))) |
| .build()); |
| if (withDetails) { |
| // ShowDetails. |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setShowDetails(ShowDetailsProto.newBuilder().setDetails( |
| DetailsProto.newBuilder() |
| .setTitle("Details title") |
| .setShowImagePlaceholder(true))) |
| .build()); |
| } |
| // Add "Done" button. |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setPrompt(PromptProto.newBuilder().addChoices( |
| Choice.newBuilder().setChip(ChipProto.newBuilder() |
| .setType(ChipType.DONE_ACTION) |
| .setText("Done")))) |
| .build()); |
| |
| return new AutofillAssistantTestScript( |
| (SupportedScriptProto) SupportedScriptProto.newBuilder() |
| .setPath("bottomsheet_behaviour_target_website.html") |
| .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip( |
| ChipProto.newBuilder().setText("Bottomsheet behaviour"))) |
| .build(), |
| list); |
| } |
| |
| @Test |
| @MediumTest |
| public void testNoResize() { |
| AutofillAssistantTestService testService = new AutofillAssistantTestService( |
| Collections.singletonList(makeScript(NO_RESIZE, HANDLE, false))); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| |
| waitUntilViewMatchesCondition(withText("Focus element"), isCompletelyDisplayed()); |
| onView(withText("Focus element")).perform(click()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withId(R.id.swipe_indicator)).perform(swipeDownToMinimize()); |
| waitUntilViewMatchesCondition(withText("Hello world!"), not(isDisplayed())); |
| // Since no resizing of the viewport happens in this mode, the element is partially covered |
| // even when the bottomsheet is minimized |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withText("Done")).check(matches(not(isDisplayed()))); |
| onView(withId(R.id.swipe_indicator)).perform(swipeUpToExpand()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| waitUntilViewMatchesCondition(withText("Hello world!"), isDisplayed()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testResizeLayoutViewport() { |
| AutofillAssistantTestService testService = new AutofillAssistantTestService( |
| Collections.singletonList(makeScript(RESIZE_LAYOUT_VIEWPORT, HANDLE, false))); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| |
| waitUntilViewMatchesCondition(withText("Focus element"), isCompletelyDisplayed()); |
| onView(withText("Focus element")).perform(click()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withId(R.id.swipe_indicator)).perform(swipeDownToMinimize()); |
| // Minimizing the bottomsheet should completely uncover the bottom element. |
| waitUntilViewMatchesCondition(withText("Hello world!"), not(isDisplayed())); |
| checkElementIsCoveredByBottomsheet("bottom", false); |
| onView(withText("Done")).check(matches(not(isDisplayed()))); |
| onView(withId(R.id.swipe_indicator)).perform(swipeUpToExpand()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| waitUntilViewMatchesCondition(withText("Hello world!"), isDisplayed()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testResizeVisualViewport() { |
| AutofillAssistantTestService testService = new AutofillAssistantTestService( |
| Collections.singletonList(makeScript(RESIZE_VISUAL_VIEWPORT, HANDLE, false))); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| |
| waitUntilViewMatchesCondition(withText("Focus element"), isCompletelyDisplayed()); |
| onView(withText("Focus element")).perform(click()); |
| // The viewport should be resized so that the bottom element is not covered by the bottom |
| // sheet. |
| checkElementIsCoveredByBottomsheet("bottom", false); |
| onView(withId(R.id.swipe_indicator)).perform(swipeDownToMinimize()); |
| waitUntilViewMatchesCondition(withText("Hello world!"), not(isDisplayed())); |
| checkElementIsCoveredByBottomsheet("bottom", false); |
| onView(withText("Done")).check(matches(not(isDisplayed()))); |
| onView(withId(R.id.swipe_indicator)).perform(swipeUpToExpand()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| waitUntilViewMatchesCondition(withText("Hello world!"), isDisplayed()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testHandleHeader() { |
| AutofillAssistantTestService testService = new AutofillAssistantTestService( |
| Collections.singletonList(makeScript(RESIZE_LAYOUT_VIEWPORT, HANDLE_HEADER, true))); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| |
| waitUntilViewMatchesCondition(withText("Focus element"), isCompletelyDisplayed()); |
| onView(withText("Focus element")).perform(click()); |
| waitUntilViewMatchesCondition(withText("Details title"), isCompletelyDisplayed()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withId(R.id.swipe_indicator)).perform(swipeDownToMinimize()); |
| checkElementIsCoveredByBottomsheet("bottom", false); |
| // The header should be visible even when minimized |
| onView(withText("Hello world!")).check(matches(isCompletelyDisplayed())); |
| onView(withText("Details title")).check(matches(not(isDisplayed()))); |
| onView(withText("Done")).check(matches(not(isDisplayed()))); |
| onView(withId(R.id.swipe_indicator)).perform(swipeUpToExpand()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withText("Details title")).check(matches(isCompletelyDisplayed())); |
| } |
| |
| @Test |
| @MediumTest |
| public void testHandleHeaderCarousels() { |
| AutofillAssistantTestService testService = |
| new AutofillAssistantTestService(Collections.singletonList( |
| makeScript(RESIZE_LAYOUT_VIEWPORT, HANDLE_HEADER_CAROUSELS, true))); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| |
| waitUntilViewMatchesCondition(withText("Focus element"), isCompletelyDisplayed()); |
| onView(withText("Focus element")).perform(click()); |
| waitUntilViewMatchesCondition(withText("Details title"), isCompletelyDisplayed()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withId(R.id.swipe_indicator)).perform(swipeDownToMinimize()); |
| checkElementIsCoveredByBottomsheet("bottom", false); |
| // The header should be visible even when minimized |
| onView(withText("Hello world!")).check(matches(isCompletelyDisplayed())); |
| // The button gets initially hidden while swiping down but should reappear shortly after. |
| waitUntilViewMatchesCondition(withText("Done"), isCompletelyDisplayed()); |
| onView(withText("Details title")).check(matches(not(isDisplayed()))); |
| onView(withId(R.id.swipe_indicator)).perform(swipeUpToExpand()); |
| checkElementIsCoveredByBottomsheet("bottom", true); |
| onView(withText("Details title")).check(matches(isCompletelyDisplayed())); |
| } |
| |
| @Test |
| @MediumTest |
| public void testBottomSheetDoesNotObstructNavBar() { |
| // Create enough additional sections to fill up more than the height of the screen. |
| List<UserFormSectionProto> additionalSections = new ArrayList<>(); |
| for (int i = 0; i < 20; ++i) { |
| additionalSections.add( |
| (UserFormSectionProto) UserFormSectionProto.newBuilder() |
| .setTextInputSection(TextInputSectionProto.newBuilder().addInputFields( |
| TextInputProto.newBuilder() |
| .setHint("Text input " + i) |
| .setClientMemoryKey("input_" + i) |
| .setInputType(TextInputProto.InputType.INPUT_TEXT))) |
| .setTitle("Title " + i) |
| .build()); |
| } |
| |
| ArrayList<ActionProto> list = new ArrayList<>(); |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setCollectUserData( |
| CollectUserDataProto.newBuilder() |
| .addAllAdditionalAppendedSections(additionalSections) |
| .setRequestTermsAndConditions(false)) |
| .build()); |
| AutofillAssistantTestScript script = new AutofillAssistantTestScript( |
| (SupportedScriptProto) SupportedScriptProto.newBuilder() |
| .setPath("bottomsheet_behaviour_target_website.html") |
| .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip( |
| ChipProto.newBuilder().setText("Autostart"))) |
| .build(), |
| list); |
| |
| AutofillAssistantTestService testService = |
| new AutofillAssistantTestService(Collections.singletonList(script)); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| waitUntilViewMatchesCondition(withText("Continue"), isCompletelyDisplayed()); |
| |
| onView(withId(R.id.control_container)).check(matches(isCompletelyDisplayed())); |
| onView(withText("Title 0")).perform(click()); |
| waitUntilViewMatchesCondition( |
| withContentDescription("Text input 0"), isCompletelyDisplayed()); |
| // Typing text will show the soft keyboard, leading to resize of the Chrome window. |
| onView(withContentDescription("Text input 0")).perform(typeText("Hello World!")); |
| onView(withId(R.id.control_container)).check(matches(isCompletelyDisplayed())); |
| onView(withText("Continue")).check(matches(isCompletelyDisplayed())); |
| // Closing the soft keyboard will restore the window size. |
| Espresso.closeSoftKeyboard(); |
| onView(withContentDescription("Text input 0")).check(matches(isDisplayed())); |
| onView(withId(R.id.control_container)).check(matches(isCompletelyDisplayed())); |
| onView(withText("Continue")).check(matches(isCompletelyDisplayed())); |
| |
| // Scroll down. |
| onView(withText("Title 19")).check(matches(not(isDisplayed()))); |
| onView(withText("Title 19")).perform(scrollTo()).check(matches(isDisplayed())); |
| |
| // Scroll up. |
| onView(withText("Title 0")).check(matches(not(isDisplayed()))); |
| onView(withText("Title 0")).perform(scrollTo()).check(matches(isDisplayed())); |
| } |
| |
| @Test |
| @MediumTest |
| public void testCancelSnackbarUndo() { |
| ArrayList<ActionProto> list = new ArrayList<>(); |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setPrompt(PromptProto.newBuilder().addChoices(Choice.newBuilder().setChip( |
| ChipProto.newBuilder() |
| .setType(ChipType.CANCEL_ACTION) |
| .setIcon(ChipIcon.ICON_CLEAR) |
| .setText("Cancel")))) |
| .build()); |
| AutofillAssistantTestScript script = new AutofillAssistantTestScript( |
| (SupportedScriptProto) SupportedScriptProto.newBuilder() |
| .setPath("bottomsheet_behaviour_target_website.html") |
| .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip( |
| ChipProto.newBuilder().setText("Autostart"))) |
| .build(), |
| list); |
| |
| AutofillAssistantTestService testService = |
| new AutofillAssistantTestService(Collections.singletonList(script)); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| waitUntilViewMatchesCondition(withText("Cancel"), isCompletelyDisplayed()); |
| |
| onView(withText("Cancel")).perform(click()); |
| waitUntilViewMatchesCondition(withText(R.string.undo), isCompletelyDisplayed()); |
| onView(withText("Cancel")).check(doesNotExist()); |
| onView(withText(R.string.undo)).perform(click()); |
| waitUntilViewMatchesCondition(withText("Cancel"), isDisplayed()); |
| } |
| |
| @Test |
| @MediumTest |
| public void testCancelSnackbarTimeout() { |
| ClientSettingsProto clientSettings = (ClientSettingsProto) ClientSettingsProto.newBuilder() |
| .setCancelDelayMs(2000) |
| .build(); |
| ArrayList<ActionProto> list = new ArrayList<>(); |
| list.add((ActionProto) ActionProto.newBuilder() |
| .setPrompt(PromptProto.newBuilder().addChoices(Choice.newBuilder().setChip( |
| ChipProto.newBuilder() |
| .setType(ChipType.CANCEL_ACTION) |
| .setIcon(ChipIcon.ICON_CLEAR) |
| .setText("Cancel")))) |
| .build()); |
| AutofillAssistantTestScript script = new AutofillAssistantTestScript( |
| (SupportedScriptProto) SupportedScriptProto.newBuilder() |
| .setPath("bottomsheet_behaviour_target_website.html") |
| .setPresentation(PresentationProto.newBuilder().setAutostart(true).setChip( |
| ChipProto.newBuilder().setText("Autostart"))) |
| .build(), |
| list); |
| |
| AutofillAssistantTestService testService = |
| new AutofillAssistantTestService(Collections.singletonList(script), clientSettings); |
| startAutofillAssistant(mTestRule.getActivity(), testService); |
| waitUntilViewMatchesCondition(withText("Cancel"), isCompletelyDisplayed()); |
| |
| onView(withText("Cancel")).perform(click()); |
| waitUntilViewMatchesCondition(withText(R.string.undo), isCompletelyDisplayed()); |
| onView(withText("Cancel")).check(doesNotExist()); |
| waitUntilViewAssertionTrue(withText(R.string.undo), doesNotExist(), 3000L); |
| onView(withId(R.id.autofill_assistant)).check(doesNotExist()); |
| } |
| |
| private ViewAction swipeDownToMinimize() { |
| return actionWithAssertions( |
| new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER, view -> { |
| float[] coordinates = GeneralLocation.CENTER.calculateCoordinates(view); |
| coordinates[1] = |
| view.getContext().getResources().getDisplayMetrics().heightPixels; |
| return coordinates; |
| }, Press.FINGER)); |
| } |
| |
| private ViewAction swipeUpToExpand() { |
| return actionWithAssertions( |
| new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER, view -> { |
| float[] coordinates = GeneralLocation.CENTER.calculateCoordinates(view); |
| coordinates[1] = 0; |
| return coordinates; |
| }, Press.FINGER)); |
| } |
| |
| private void checkElementIsCoveredByBottomsheet(String elementId, boolean shouldBeCovered) { |
| CriteriaHelper.pollInstrumentationThread(new Criteria("Timeout while waiting for element '" |
| + elementId + "' to become " + (shouldBeCovered ? "covered" : "not covered") |
| + " by the bottomsheet") { |
| @Override |
| public boolean isSatisfied() { |
| try { |
| float y = GeneralLocation.TOP_CENTER.calculateCoordinates( |
| mTestRule.getActivity().findViewById( |
| R.id.autofill_assistant_bottom_sheet_toolbar))[1]; |
| Rect el = getAbsoluteBoundingRect(elementId, mTestRule); |
| return el.bottom > y == shouldBeCovered; |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| }); |
| } |
| } |