// Copyright 2018 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.keyboard_accessory;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem;
import static androidx.test.espresso.contrib.RecyclerViewActions.scrollTo;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withChild;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertNotNull;
import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.selectTabAtPosition;
import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.selectTabWithDescription;
import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.waitToBeHidden;
import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.whenDisplayed;
import android.view.View;
import android.view.ViewGroup;
import androidx.test.espresso.Espresso;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
import org.chromium.chrome.browser.keyboard_accessory.button_group_component.KeyboardAccessoryButtonGroupView;
import org.chromium.chrome.browser.layouts.LayoutTestUtils;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.ui.messages.infobar.SimpleConfirmInfoBarBuilder;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.InfoBarTestAnimationListener;
import org.chromium.ui.test.util.UiDisableIf;
import org.chromium.ui.test.util.UiRestriction;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/** Integration tests for keyboard accessory and accessory sheet with other Chrome components. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class ManualFillingIntegrationTest {
@Rule
public final ChromeTabbedActivityTestRule mActivityTestRule =
new ChromeTabbedActivityTestRule();
private final ManualFillingTestHelper mHelper = new ManualFillingTestHelper(mActivityTestRule);
@After
public void tearDown() {
mHelper.clear();
}
@Test
@SmallTest
public void testAccessoryIsAvailable() {
mHelper.loadTestPage(false);
assertNotNull(
"Controller for Manual filling should be available.",
mHelper.getManualFillingCoordinator());
}
@Test
@SmallTest
public void testKeyboardAccessoryHiddenUntilKeyboardShows() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
onView(withId(R.id.keyboard_accessory)).check(doesNotExist());
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Check that ONLY the accessory is there but the sheet is still hidden.
whenDisplayed(withId(R.id.keyboard_accessory));
onView(withChild(withId(R.id.keyboard_accessory_sheet_frame))).check(doesNotExist());
}
@Test
@SmallTest
public void testKeyboardAccessoryDisappearsWithKeyboard() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
whenDisplayed(withId(R.id.keyboard_accessory));
// Dismiss the keyboard to hide the accessory again.
mHelper.clickSubmit();
mHelper.waitForKeyboardAccessoryToDisappear();
}
@Test
@SmallTest
public void testAccessorySheetHiddenUntilManuallyTriggered() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Check that ONLY the accessory is there but the sheet is still hidden.
whenDisplayed(withId(R.id.keyboard_accessory));
onView(withChild(withId(R.id.keyboard_accessory_sheet_frame))).check(doesNotExist());
// Trigger the sheet and wait for it to open and the keyboard to disappear.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
}
@Test
@SmallTest
public void testAccessorySheetShown() throws TimeoutException {
mHelper.loadTestPage(false);
// Register a sheet data provider so that sheet is available when needed.
mHelper.registerSheetDataProvider(AccessoryTabType.CREDIT_CARDS);
// Show the passwords accessory sheet without focusing on any fields.
ThreadUtils.runOnUiThreadBlocking(
() ->
mHelper.getManualFillingCoordinator()
.showAccessorySheetTab(AccessoryTabType.CREDIT_CARDS));
// Verify that the accessory sheet is shown.
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
}
@Test
@SmallTest
public void testAccessorySheetHiddenWhenRefocusingField() throws TimeoutException {
AtomicReference<ViewGroup.MarginLayoutParams> accessoryMargins = new AtomicReference<>();
AtomicReference<View> accessorySheetView = new AtomicReference<>();
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Check that ONLY the accessory is there but the sheet is still hidden.
whenDisplayed(withId(R.id.keyboard_accessory))
.check(
(view, e) -> {
accessoryMargins.set(
(ViewGroup.MarginLayoutParams) view.getLayoutParams());
assertThat(
accessoryMargins.get().bottomMargin,
is(0)); // Attached to keyboard.
});
onView(withChild(withId(R.id.keyboard_accessory_sheet_frame))).check(doesNotExist());
// Trigger the sheet and wait for it to open and the keyboard to disappear.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)))
.check(
(view, e) -> {
accessorySheetView.set(view);
});
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
CriteriaHelper.pollUiThread(() -> accessoryMargins.get().bottomMargin == 0);
}
@Test
@SmallTest
@DisabledTest(message = "https://crbug.com/1466461")
public void testAccessoryHiddenAfterTappingAutoGenerationButton() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory and add the generation button.
mHelper.focusPasswordField();
mHelper.signalAutoGenerationStatus(true);
mHelper.waitForKeyboardAccessoryToBeShown();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(isAssignableFrom(KeyboardAccessoryButtonGroupView.class))
.perform(selectTabAtPosition(0));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
// Click the generation button. This should hide the accessory sheet and bar.
onView(withText(R.string.password_generation_accessory_button)).perform(click());
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
waitToBeHidden(withId(R.id.keyboard_accessory));
}
@Test
@SmallTest
@DisabledTest(message = "https://crbug.com/1406328,https://crbug.com/1466461")
public void testHidingSheetBringsBackKeyboard() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(isAssignableFrom(KeyboardAccessoryButtonGroupView.class))
.perform(selectTabAtPosition(0));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
// Click the tab again to hide the sheet and show the keyboard.
whenDisplayed(isAssignableFrom(KeyboardAccessoryButtonGroupView.class))
.perform(selectTabAtPosition(0));
mHelper.waitForKeyboardAccessoryToBeShown();
onView(withId(R.id.keyboard_accessory)).check(matches(isDisplayed()));
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
}
@Test
@SmallTest
public void testSelectingNonPasswordInputDismissesAccessory() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the password field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
whenDisplayed(isAssignableFrom(KeyboardAccessoryButtonGroupView.class));
// Clicking a field without completion hides the accessory again.
mHelper.clickFieldWithoutCompletion();
mHelper.waitForKeyboardAccessoryToDisappear();
}
@Test
@SmallTest
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
public void testInvokingTabSwitcherHidesAccessory() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
LayoutTestUtils.startShowingAndWaitForLayout(
mActivityTestRule.getActivity().getLayoutManager(), LayoutType.TAB_SWITCHER, false);
LayoutTestUtils.startShowingAndWaitForLayout(
mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING, false);
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
}
@Test
@SmallTest
public void testResumingTheAppDismissesAllInputMethods() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
// Simulate backgrounding the main activity.
ThreadUtils.runOnUiThreadBlocking(
() -> {
mActivityTestRule.getActivity().onPauseWithNative();
});
// This should completely dismiss any input method.
mHelper.waitForKeyboardToDisappear();
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
mHelper.waitForKeyboardAccessoryToDisappear();
// Simulate foregrounding the main activity.
ThreadUtils.runOnUiThreadBlocking(
() -> {
mActivityTestRule.getActivity().onResumeWithNative();
});
// Clicking the field should bring the accessory back up.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
}
@Test
@SmallTest
public void testPressingBackButtonHidesAccessorySheet() throws TimeoutException {
mHelper.loadTestPage(false);
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
Espresso.pressBack();
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
mHelper.waitForKeyboardAccessoryToDisappear();
}
@Test
@SmallTest
@DisableIf.Device(type = {UiDisableIf.TABLET}) // crbug.com/362214348
public void testInfobarStaysHiddenWhileChangingFieldsWithOpenKeyboard()
throws TimeoutException {
mHelper.loadTestPage(false);
// Initialize and wait for the infobar.
InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener();
ThreadUtils.runOnUiThreadBlocking(
() -> mActivityTestRule.getInfoBarContainer().addAnimationListener(listener));
final String kInfoBarText = "SomeInfoBar";
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
SimpleConfirmInfoBarBuilder.create(
mActivityTestRule.getActivity().getActivityTab().getWebContents(),
InfoBarIdentifier.DUPLICATE_DOWNLOAD_INFOBAR_DELEGATE_ANDROID,
kInfoBarText,
false);
});
listener.addInfoBarAnimationFinished("InfoBar not added.");
whenDisplayed(withText(kInfoBarText));
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
assertThat(mActivityTestRule.getInfoBarContainer().getVisibility(), is(not(View.VISIBLE)));
// Clicking another field hides the accessory, but the InfoBar should remain invisible.
mHelper.clickEmailField(false);
assertThat(mActivityTestRule.getInfoBarContainer().getVisibility(), is(not(View.VISIBLE)));
// Close the keyboard to bring back the InfoBar.
ThreadUtils.runOnUiThreadBlocking(
() -> {
mActivityTestRule
.getKeyboardDelegate()
.hideKeyboard(mActivityTestRule.getActivity().getCurrentFocus());
mActivityTestRule
.getInfoBarContainer()
.getContainerViewForTesting()
.requestLayout();
});
mHelper.waitForKeyboardToDisappear();
mHelper.waitForKeyboardAccessoryToDisappear();
whenDisplayed(withText(kInfoBarText));
}
@Test
@SmallTest
public void testInfobarStaysHiddenWhenOpeningSheet() throws TimeoutException {
mHelper.loadTestPage(false);
// Initialize and wait for the infobar.
InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener();
ThreadUtils.runOnUiThreadBlocking(
() -> mActivityTestRule.getInfoBarContainer().addAnimationListener(listener));
final String kInfoBarText = "SomeInfoBar";
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
SimpleConfirmInfoBarBuilder.create(
mActivityTestRule.getActivity().getActivityTab().getWebContents(),
InfoBarIdentifier.DUPLICATE_DOWNLOAD_INFOBAR_DELEGATE_ANDROID,
kInfoBarText,
false);
});
listener.addInfoBarAnimationFinished("InfoBar not added.");
whenDisplayed(withText(kInfoBarText));
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
assertThat(mActivityTestRule.getInfoBarContainer().getVisibility(), is(not(View.VISIBLE)));
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
assertThat(mActivityTestRule.getInfoBarContainer().getVisibility(), is(not(View.VISIBLE)));
// Reopen the keyboard, then close it.
whenDisplayed(withId(R.id.show_keyboard)).perform(click());
mHelper.waitForKeyboardAccessoryToBeShown();
ThreadUtils.runOnUiThreadBlocking(
() -> {
mActivityTestRule
.getKeyboardDelegate()
.hideKeyboard(mActivityTestRule.getActivity().getCurrentFocus());
mActivityTestRule
.getInfoBarContainer()
.getContainerViewForTesting()
.requestLayout();
});
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
mHelper.waitForKeyboardAccessoryToDisappear();
whenDisplayed(withText(kInfoBarText));
}
@Test
@SmallTest
public void testMovesUpSnackbar() throws TimeoutException {
final String kSnackbarText = "snackbar";
mHelper.loadTestPage(false);
// Create a simple, persistent snackbar and verify it's displayed.
SnackbarManager manager = mActivityTestRule.getActivity().getSnackbarManager();
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() ->
manager.showSnackbar(
Snackbar.make(
kSnackbarText,
new SnackbarManager.SnackbarController() {},
Snackbar.TYPE_PERSISTENT,
Snackbar.UMA_TEST_SNACKBAR)));
CriteriaHelper.pollUiThread(manager::isShowing);
CriteriaHelper.pollUiThread(
mActivityTestRule.getActivity().getWindowAndroid()::haveAnimationsEnded);
// Click in a field to open keyboard and accessory -- this shouldn't hide the snackbar.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
whenDisplayed(withId(R.id.keyboard_accessory));
onView(withText(kSnackbarText)).check(matches(isCompletelyDisplayed()));
// Open a keyboard accessory sheet -- this also shouldn't hide the snackbar.
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
onView(withText(kSnackbarText)).check(matches(isCompletelyDisplayed()));
// Click into a field without completion to dismiss the keyboard accessory.
mHelper.clickFieldWithoutCompletion();
mHelper.waitForKeyboardAccessoryToDisappear();
onView(withText(kSnackbarText)).check(matches(isCompletelyDisplayed()));
}
@Test
@SmallTest
public void testInfobarReopensOnPressingBack() throws TimeoutException {
mHelper.loadTestPage(false);
// Initialize and wait for the infobar.
InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener();
ThreadUtils.runOnUiThreadBlocking(
() -> mActivityTestRule.getInfoBarContainer().addAnimationListener(listener));
final String kInfoBarText = "SomeInfoBar";
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
SimpleConfirmInfoBarBuilder.create(
mActivityTestRule.getActivity().getActivityTab().getWebContents(),
InfoBarIdentifier.DUPLICATE_DOWNLOAD_INFOBAR_DELEGATE_ANDROID,
kInfoBarText,
false);
});
listener.addInfoBarAnimationFinished("InfoBar not added.");
assertThat(mActivityTestRule.getInfoBarContainer().getVisibility(), is(View.VISIBLE));
// Focus the field to bring up the accessory.
mHelper.focusPasswordField();
mHelper.waitForKeyboardAccessoryToBeShown();
whenDisplayed(withId(R.id.bar_items_view))
.perform(
scrollTo(isAssignableFrom(KeyboardAccessoryButtonGroupView.class)),
actionOnItem(
isAssignableFrom(KeyboardAccessoryButtonGroupView.class),
selectTabWithDescription(
R.string.password_accessory_sheet_toggle)));
whenDisplayed(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
assertThat(mActivityTestRule.getInfoBarContainer().getVisibility(), is(not(View.VISIBLE)));
// Close the accessory using the back button. The Infobar should reappear.
Espresso.pressBack();
waitToBeHidden(withChild(withId(R.id.keyboard_accessory_sheet_frame)));
mHelper.waitForKeyboardAccessoryToDisappear();
whenDisplayed(withText(kInfoBarText));
}
}