Commit bc520eee authored by gogerald's avatar gogerald Committed by Commit Bot

[Autofill Assistant] Add initial UI tests

This CL depends on https://chromium-review.googlesource.com/c/chromium/src/+/1366696

Bug: 806868
Change-Id: I9b7093418ebf7d270a333deca572deea59166681
Reviewed-on: https://chromium-review.googlesource.com/c/1359552
Commit-Queue: Ganggui Tang <gogerald@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Reviewed-by: default avatarStephane Zermatten <szermatt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#615622}
parent 69017ce4
// Copyright 2018 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;
/**
* Abstract AutofillAssistantUiController, this is created for testing UI.
*
* TODO(crbug.com/806868): Refactor this class to AutofillAssistantUiController interface and the
* original AutofillAssistantUiController to AutofillAssistantUiControllerImpl after done necessary
* merges to M72.
*/
abstract class AbstractAutofillAssistantUiController
implements AutofillAssistantUiDelegate.Client, UiDelegateHolder.Listener {
/** Interface called to initiate controller. */
public void init(UiDelegateHolder delegateHolder, Details details) {}
}
\ No newline at end of file
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.autofill_assistant; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.autofill_assistant;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.EmptyTabObserver;
...@@ -36,18 +37,28 @@ public class AutofillAssistantFacade { ...@@ -36,18 +37,28 @@ public class AutofillAssistantFacade {
/** Starts Autofill Assistant on the given {@code activity}. */ /** Starts Autofill Assistant on the given {@code activity}. */
public static void start(ChromeActivity activity) { public static void start(ChromeActivity activity) {
startInternal(activity, /* controller= */ null);
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static void startInternal(
ChromeActivity activity, @Nullable AbstractAutofillAssistantUiController controller) {
Map<String, String> parameters = extractParameters(activity.getInitialIntent().getExtras()); Map<String, String> parameters = extractParameters(activity.getInitialIntent().getExtras());
parameters.remove(PARAMETER_ENABLED); parameters.remove(PARAMETER_ENABLED);
final AbstractAutofillAssistantUiController targetController = controller == null
? new AutofillAssistantUiController(activity, parameters)
: controller;
if (!AutofillAssistantPreferencesUtil.getSkipInitScreenPreference()) { if (!AutofillAssistantPreferencesUtil.getSkipInitScreenPreference()) {
FirstRunScreen.show(activity, (result) -> { FirstRunScreen.show(activity, (result) -> {
if (result) initiateAutofillAssistant(activity, parameters); if (result) initiateAutofillAssistant(activity, parameters, targetController);
}); });
return; return;
} }
if (AutofillAssistantPreferencesUtil.isAutofillAssistantSwitchOn() if (AutofillAssistantPreferencesUtil.isAutofillAssistantSwitchOn()
&& AutofillAssistantPreferencesUtil.getSkipInitScreenPreference()) { && AutofillAssistantPreferencesUtil.getSkipInitScreenPreference()) {
initiateAutofillAssistant(activity, parameters); initiateAutofillAssistant(activity, parameters, targetController);
} }
// We don't have consent to start Autofill Assistant and cannot show initial screen. // We don't have consent to start Autofill Assistant and cannot show initial screen.
// Do nothing. // Do nothing.
...@@ -56,12 +67,10 @@ public class AutofillAssistantFacade { ...@@ -56,12 +67,10 @@ public class AutofillAssistantFacade {
/** /**
* Instantiates all essential Autofill Assistant components and starts it. * Instantiates all essential Autofill Assistant components and starts it.
*/ */
private static void initiateAutofillAssistant( private static void initiateAutofillAssistant(ChromeActivity activity,
ChromeActivity activity, Map<String, String> parameters) { Map<String, String> parameters, AbstractAutofillAssistantUiController controller) {
AutofillAssistantUiController controller =
new AutofillAssistantUiController(activity, parameters);
UiDelegateHolder delegateHolder = new UiDelegateHolder( UiDelegateHolder delegateHolder = new UiDelegateHolder(
controller, new AutofillAssistantUiDelegate(activity, controller)); new AutofillAssistantUiDelegate(activity, controller), controller);
initTabObservers(activity, delegateHolder); initTabObservers(activity, delegateHolder);
controller.init(delegateHolder, Details.makeFromParameters(parameters)); controller.init(delegateHolder, Details.makeFromParameters(parameters));
......
...@@ -34,7 +34,7 @@ import java.util.Map; ...@@ -34,7 +34,7 @@ import java.util.Map;
* Autofill Assistant related UIs and forward UI events to native side. * Autofill Assistant related UIs and forward UI events to native side.
*/ */
@JNINamespace("autofill_assistant") @JNINamespace("autofill_assistant")
public class AutofillAssistantUiController implements AutofillAssistantUiDelegate.Client { public class AutofillAssistantUiController extends AbstractAutofillAssistantUiController {
/** OAuth2 scope that RPCs require. */ /** OAuth2 scope that RPCs require. */
private static final String AUTH_TOKEN_TYPE = private static final String AUTH_TOKEN_TYPE =
"oauth2:https://www.googleapis.com/auth/userinfo.profile"; "oauth2:https://www.googleapis.com/auth/userinfo.profile";
...@@ -94,6 +94,7 @@ public class AutofillAssistantUiController implements AutofillAssistantUiDelegat ...@@ -94,6 +94,7 @@ public class AutofillAssistantUiController implements AutofillAssistantUiDelegat
mUiControllerAndroid = 0; mUiControllerAndroid = 0;
} }
@Override
public void init(UiDelegateHolder delegateHolder, Details details) { public void init(UiDelegateHolder delegateHolder, Details details) {
mUiDelegateHolder = delegateHolder; mUiDelegateHolder = delegateHolder;
maybeUpdateDetails(details); maybeUpdateDetails(details);
...@@ -163,17 +164,8 @@ public class AutofillAssistantUiController implements AutofillAssistantUiDelegat ...@@ -163,17 +164,8 @@ public class AutofillAssistantUiController implements AutofillAssistantUiDelegat
return nativeOnRequestDebugContext(mUiControllerAndroid); return nativeOnRequestDebugContext(mUiControllerAndroid);
} }
/** @Override
* Immediately and unconditionally destroys the UI Controller. public void onCompleteShutdown() {
*
* <p>Call {@link UiDelegateHolder#shutdown} to shutdown Autofill Assistant properly and safely.
*
* <p>Destroy is different from shutdown in that {@code unsafeDestroy} just deletes the native
* controller and all the objects it owns, without changing the state of the UI. When that
* happens, everything stops irrevocably on the native side. Doing this at the wrong time will
* cause crashes.
*/
void unsafeDestroy() {
if (mUiControllerAndroid != 0) nativeDestroy(mUiControllerAndroid); if (mUiControllerAndroid != 0) nativeDestroy(mUiControllerAndroid);
} }
......
...@@ -19,8 +19,8 @@ class UiDelegateHolder { ...@@ -19,8 +19,8 @@ class UiDelegateHolder {
/** Display the final message for that long before shutting everything down. */ /** Display the final message for that long before shutting everything down. */
private static final long GRACEFUL_SHUTDOWN_DELAY_MS = 5_000; private static final long GRACEFUL_SHUTDOWN_DELAY_MS = 5_000;
private final AutofillAssistantUiController mUiController;
private final AutofillAssistantUiDelegate mUiDelegate; private final AutofillAssistantUiDelegate mUiDelegate;
private final Listener mListener;
private boolean mPaused; private boolean mPaused;
private boolean mHasBeenShutdown; private boolean mHasBeenShutdown;
...@@ -29,10 +29,14 @@ class UiDelegateHolder { ...@@ -29,10 +29,14 @@ class UiDelegateHolder {
private final Queue<Callback<AutofillAssistantUiDelegate>> mPendingUiOperations = private final Queue<Callback<AutofillAssistantUiDelegate>> mPendingUiOperations =
new ArrayDeque<>(); new ArrayDeque<>();
UiDelegateHolder( public interface Listener {
AutofillAssistantUiController controller, AutofillAssistantUiDelegate uiDelegate) { /** Called when UI completes change for shutdown. */
mUiController = controller; public void onCompleteShutdown();
}
UiDelegateHolder(AutofillAssistantUiDelegate uiDelegate, Listener listener) {
mUiDelegate = uiDelegate; mUiDelegate = uiDelegate;
mListener = listener;
} }
/** /**
...@@ -126,7 +130,7 @@ class UiDelegateHolder { ...@@ -126,7 +130,7 @@ class UiDelegateHolder {
mUiDelegate.dismissSnackbar(mDismissSnackbar); mUiDelegate.dismissSnackbar(mDismissSnackbar);
} }
mUiDelegate.hide(); mUiDelegate.hide();
mUiController.unsafeDestroy(); mListener.onCompleteShutdown();
} }
/** /**
......
...@@ -125,6 +125,7 @@ chrome_java_sources = [ ...@@ -125,6 +125,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetMediator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetMediator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AbstractAutofillAssistantUiController.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AnimatedProgressBar.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AnimatedProgressBar.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java",
"java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPaymentRequest.java", "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPaymentRequest.java",
...@@ -1880,6 +1881,7 @@ chrome_test_java_sources = [ ...@@ -1880,6 +1881,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java", "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java",
"javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java",
"javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java", "javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java",
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java", "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java",
"javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java", "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java",
......
// Copyright 2018 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 org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
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.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
import org.chromium.base.PathUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.net.test.EmbeddedTestServer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
/**
* Instrumentation tests for autofill assistant UI.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
public class AutofillAssistantUiTest {
private String mTestPage;
private EmbeddedTestServer mTestServer;
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private AbstractAutofillAssistantUiController mControllerMock;
@Captor
private ArgumentCaptor<UiDelegateHolder> mUiDelegateHolderCaptor;
@Captor
private ArgumentCaptor<String> mLastSelectedScriptPathCaptor;
@Rule
public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
@Before
public void setUp() throws Exception {
ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
mTestPage = mTestServer.getURL(UrlUtils.getIsolatedTestFilePath(
"components/test/data/autofill_assistant/autofill_assistant_target_website.html"));
PathUtils.setPrivateDataDirectorySuffix("chrome");
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
}
@After
public void tearDown() throws Exception {
ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
mTestServer.stopAndDestroyServer();
}
/**
* @see CustomTabsTestUtils#createMinimalCustomTabIntent(Context, String).
*/
private Intent createMinimalCustomTabIntent() {
return CustomTabsTestUtils.createMinimalCustomTabIntent(
InstrumentationRegistry.getTargetContext(), mTestPage);
}
private View findViewByIdInMainCoordinator(int id) {
return getActivity().findViewById(R.id.coordinator).findViewById(id);
}
private CustomTabActivity getActivity() {
return mCustomTabActivityTestRule.getActivity();
}
// TODO(crbug.com/806868): Add more UI details test and check, like payment request UI,
// highlight chips and so on.
@Test
@MediumTest
public void testStartAndDismiss() throws InterruptedException {
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
// Start autofill assistant UI. The first run screen must be shown first since the
// preference hasn't been set.
ThreadUtils.runOnUiThreadBlocking(
() -> AutofillAssistantFacade.startInternal(getActivity(), mControllerMock));
View firstRunScreen = findViewByIdInMainCoordinator(R.id.init_screen);
Assert.assertNotNull(firstRunScreen);
Assert.assertTrue(firstRunScreen.isShown());
verify(mControllerMock, never()).init(any(), any());
// Accept on the first run screen so as to continue.
View initOkButton = firstRunScreen.findViewById(R.id.button_init_ok);
ThreadUtils.runOnUiThreadBlocking(() -> { initOkButton.performClick(); });
verify(mControllerMock, times(1))
.init(mUiDelegateHolderCaptor.capture(), any(Details.class));
Assert.assertNotNull(mUiDelegateHolderCaptor.getValue());
// Show and check status message.
String testStatusMessage = "test message";
ThreadUtils.runOnUiThreadBlocking(
()
-> mUiDelegateHolderCaptor.getValue().performUiOperation(
uiDelegate -> uiDelegate.showStatusMessage(testStatusMessage)));
View bottomSheet = findViewByIdInMainCoordinator(R.id.autofill_assistant);
Assert.assertTrue(bottomSheet.isShown());
TextView statusMessageView = (TextView) bottomSheet.findViewById(R.id.status_message);
Assert.assertEquals(statusMessageView.getText(), testStatusMessage);
// Show overlay.
ThreadUtils.runOnUiThreadBlocking(
() -> mUiDelegateHolderCaptor.getValue().performUiOperation(uiDelegate -> {
uiDelegate.showOverlay();
uiDelegate.disableProgressBarPulsing();
}));
View overlay = bottomSheet.findViewById(R.id.touch_event_filter);
Assert.assertTrue(overlay.isShown());
// Show scripts.
List<AutofillAssistantUiDelegate.ScriptHandle> scriptHandles = new ArrayList<>();
scriptHandles.add(
new AutofillAssistantUiDelegate.ScriptHandle("testScript1", false, "path1"));
scriptHandles.add(
new AutofillAssistantUiDelegate.ScriptHandle("testScript2", false, "path2"));
ThreadUtils.runOnUiThreadBlocking(
()
-> mUiDelegateHolderCaptor.getValue().performUiOperation(
uiDelegate -> uiDelegate.updateScripts(scriptHandles)));
ViewGroup chipsViewContainer = (ViewGroup) bottomSheet.findViewById(R.id.carousel);
Assert.assertEquals(2, chipsViewContainer.getChildCount());
// choose the first script.
ThreadUtils.runOnUiThreadBlocking(
() -> { chipsViewContainer.getChildAt(0).performClick(); });
verify(mControllerMock, times(1)).onScriptSelected(mLastSelectedScriptPathCaptor.capture());
Assert.assertEquals("path1", mLastSelectedScriptPathCaptor.getValue());
// Show movie details.
String movieTitle = "testTitle";
String movieDescription = "This is a fancy test movie";
ThreadUtils.runOnUiThreadBlocking(
()
-> mUiDelegateHolderCaptor.getValue().performUiOperation(uiDelegate
-> uiDelegate.showDetails(new Details(movieTitle, /* url = */ "",
Calendar.getInstance().getTime(), movieDescription,
/* mId = */ "",
/* isFinal= */ true, Collections.emptySet()))));
TextView detailsTitle = (TextView) bottomSheet.findViewById(R.id.details_title);
TextView detailsText = (TextView) bottomSheet.findViewById(R.id.details_text);
Assert.assertEquals(detailsTitle.getText(), movieTitle);
Assert.assertTrue(detailsText.getText().toString().contains(movieDescription));
// Progress bar must be shown.
Assert.assertTrue(bottomSheet.findViewById(R.id.progress_bar).isShown());
// Click 'X' button to graceful shutdown.
doAnswer((Answer<Void>) invocation -> {
mUiDelegateHolderCaptor.getValue().dismiss(R.string.autofill_assistant_stopped);
return null;
})
.when(mControllerMock)
.onDismiss();
ThreadUtils.runOnUiThreadBlocking(
() -> { bottomSheet.findViewById(R.id.close_button).performClick(); });
Assert.assertFalse(bottomSheet.isShown());
}
}
\ No newline at end of file
file://components/autofill_assistant/OWNERS
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment