blob: 9c4540d69a186d823c33ccf6c1b81d55550e9370 [file] [log] [blame]
// Copyright 2013 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;
import android.support.test.filters.MediumTest;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import org.junit.Assert;
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.base.test.util.Feature;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.autofill.AutofillPopup;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.browser.input.ChromiumBaseInputConnection;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.DOMUtils;
import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
import org.chromium.content.browser.test.util.TouchCommon;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.R;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/**
* Integration tests for the AutofillPopup.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@RetryOnFailure
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG})
public class AutofillPopupTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
private static final String FIRST_NAME = "John";
private static final String LAST_NAME = "Smith";
private static final String COMPANY_NAME = "Acme Inc.";
private static final String ADDRESS_LINE1 = "1 Main";
private static final String ADDRESS_LINE2 = "Apt A";
private static final String STREET_ADDRESS_TEXTAREA = ADDRESS_LINE1 + "\n" + ADDRESS_LINE2;
private static final String CITY = "San Francisco";
private static final String DEPENDENT_LOCALITY = "";
private static final String STATE = "CA";
private static final String ZIP_CODE = "94102";
private static final String SORTING_CODE = "";
private static final String COUNTRY = "US";
private static final String PHONE_NUMBER = "4158889999";
private static final String EMAIL = "john@acme.inc";
private static final String LANGUAGE_CODE = "";
private static final String ORIGIN = "https://www.example.com";
private static final String BASIC_PAGE_DATA = UrlUtils.encodeHtmlDataUri("<html><head>"
+ "<meta name=\"viewport\""
+ "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>"
+ "<body><form method=\"POST\">"
+ "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" /><br>"
+ "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\" /><br>"
+ "<textarea id=\"sa\" autocomplete=\"street-address\"></textarea><br>"
+ "<input type=\"text\" id=\"a1\" autocomplete=\"address-line1\" /><br>"
+ "<input type=\"text\" id=\"a2\" autocomplete=\"address-line2\" /><br>"
+ "<input type=\"text\" id=\"ct\" autocomplete=\"locality\" /><br>"
+ "<input type=\"text\" id=\"zc\" autocomplete=\"postal-code\" /><br>"
+ "<input type=\"text\" id=\"em\" autocomplete=\"email\" /><br>"
+ "<input type=\"text\" id=\"ph\" autocomplete=\"tel\" /><br>"
+ "<input type=\"text\" id=\"fx\" autocomplete=\"fax\" /><br>"
+ "<select id=\"co\" autocomplete=\"country\"><br>"
+ "<option value=\"BR\">Brazil</option>"
+ "<option value=\"US\">United States</option>"
+ "</select>"
+ "<input type=\"submit\" />"
+ "</form></body></html>");
private static final String INITIATING_ELEMENT_FILLED = UrlUtils.encodeHtmlDataUri("<html>"
+ "<head><meta name=\"viewport\""
+ "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>"
+ "<body><form method=\"POST\">"
+ "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" value=\"J\"><br>"
+ "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\"><br>"
+ "<input type=\"text\" id=\"em\" autocomplete=\"email\"><br>"
+ "<select id=\"co\" autocomplete=\"country\"><br>"
+ "<option value=\"US\">United States</option>"
+ "<option value=\"BR\">Brazil</option>"
+ "</select>"
+ "<input type=\"submit\" />"
+ "</form></body></html>");
private static final String ANOTHER_ELEMENT_FILLED = UrlUtils.encodeHtmlDataUri("<html><head>"
+ "<meta name=\"viewport\""
+ "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>"
+ "<body><form method=\"POST\">"
+ "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\"><br>"
+ "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\"><br>"
+ "<input type=\"text\" id=\"em\" autocomplete=\"email\" value=\"foo@example.com\"><br>"
+ "<select id=\"co\" autocomplete=\"country\"><br>"
+ "<option></option>"
+ "<option value=\"BR\">Brazil</option>"
+ "<option value=\"US\">United States</option>"
+ "</select>"
+ "<input type=\"submit\" />"
+ "</form></body></html>");
private static final String INVALID_OPTION = UrlUtils.encodeHtmlDataUri("<html><head>"
+ "<meta name=\"viewport\""
+ "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>"
+ "<body><form method=\"POST\">"
+ "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" value=\"J\"><br>"
+ "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\"><br>"
+ "<input type=\"text\" id=\"em\" autocomplete=\"email\"><br>"
+ "<select id=\"co\" autocomplete=\"country\"><br>"
+ "<option value=\"GB\">Great Britain</option>"
+ "<option value=\"BR\">Brazil</option>"
+ "</select>"
+ "<input type=\"submit\" />"
+ "</form></body></html>");
private AutofillTestHelper mHelper;
private List<AutofillLogger.LogEntry> mAutofillLoggedEntries;
@Before
public void setUp() throws Exception {
mAutofillLoggedEntries = new ArrayList<AutofillLogger.LogEntry>();
AutofillLogger.setLoggerForTesting(
new AutofillLogger.Logger() {
@Override
public void didFillField(AutofillLogger.LogEntry logEntry) {
mAutofillLoggedEntries.add(logEntry);
}
}
);
}
private void loadAndFillForm(
final String formDataUrl, final String inputText)
throws InterruptedException, ExecutionException, TimeoutException {
mActivityTestRule.startMainActivityWithURL(formDataUrl);
mHelper = new AutofillTestHelper();
// The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is never
// brought up.
final ContentViewCore viewCore =
mActivityTestRule.getActivity().getCurrentContentViewCore();
final WebContents webContents = viewCore.getWebContents();
final ViewGroup view = viewCore.getContainerView();
final TestInputMethodManagerWrapper immw =
new TestInputMethodManagerWrapper(viewCore);
viewCore.getImeAdapterForTest().setInputMethodManagerWrapperForTest(immw);
// Add an Autofill profile.
AutofillProfile profile = new AutofillProfile(
"" /* guid */, ORIGIN, FIRST_NAME + " " + LAST_NAME, COMPANY_NAME,
STREET_ADDRESS_TEXTAREA,
STATE, CITY, DEPENDENT_LOCALITY,
ZIP_CODE, SORTING_CODE, COUNTRY, PHONE_NUMBER, EMAIL,
LANGUAGE_CODE);
mHelper.setProfile(profile);
Assert.assertEquals(1, mHelper.getNumberOfProfilesToSuggest());
// Click the input field for the first name.
DOMUtils.waitForNonZeroNodeBounds(webContents, "fn");
DOMUtils.clickNode(viewCore, "fn");
waitForKeyboardShowRequest(immw, 1);
final ChromiumBaseInputConnection inputConnection =
viewCore.getImeAdapterForTest().getInputConnectionForTest();
inputConnection.getHandler().post(new Runnable() {
@Override
public void run() {
inputConnection.setComposingText(inputText, 1);
}
});
waitForAnchorViewAdd(view);
View anchorView = view.findViewById(R.id.dropdown_popup_window);
Assert.assertTrue(anchorView.getTag() instanceof AutofillPopup);
final AutofillPopup popup = (AutofillPopup) anchorView.getTag();
waitForAutofillPopopShow(popup);
TouchCommon.singleClickView(popup.getListView(), 10, 10);
waitForInputFieldFill(webContents);
}
/**
* Tests that bringing up an Autofill and clicking on the first entry fills out the expected
* Autofill information.
*/
@Test
@MediumTest
@Feature({"autofill"})
public void testClickAutofillPopupSuggestion()
throws InterruptedException, ExecutionException, TimeoutException {
loadAndFillForm(BASIC_PAGE_DATA, "J");
final ContentViewCore viewCore =
mActivityTestRule.getActivity().getCurrentContentViewCore();
final WebContents webContents = viewCore.getWebContents();
Assert.assertEquals(
"First name did not match", FIRST_NAME, DOMUtils.getNodeValue(webContents, "fn"));
Assert.assertEquals(
"Last name did not match", LAST_NAME, DOMUtils.getNodeValue(webContents, "ln"));
Assert.assertEquals("Street address (textarea) did not match", STREET_ADDRESS_TEXTAREA,
DOMUtils.getNodeValue(webContents, "sa"));
Assert.assertEquals("Address line 1 did not match", ADDRESS_LINE1,
DOMUtils.getNodeValue(webContents, "a1"));
Assert.assertEquals("Address line 2 did not match", ADDRESS_LINE2,
DOMUtils.getNodeValue(webContents, "a2"));
Assert.assertEquals("City did not match", CITY, DOMUtils.getNodeValue(webContents, "ct"));
Assert.assertEquals(
"Zip code did not match", ZIP_CODE, DOMUtils.getNodeValue(webContents, "zc"));
Assert.assertEquals(
"Country did not match", COUNTRY, DOMUtils.getNodeValue(webContents, "co"));
Assert.assertEquals("Email did not match", EMAIL, DOMUtils.getNodeValue(webContents, "em"));
Assert.assertEquals("Phone number did not match", PHONE_NUMBER,
DOMUtils.getNodeValue(webContents, "ph"));
final String profileFullName = FIRST_NAME + " " + LAST_NAME;
final int loggedEntries = 10;
Assert.assertEquals("Mismatched number of logged entries", loggedEntries,
mAutofillLoggedEntries.size());
assertLogged(FIRST_NAME, profileFullName);
assertLogged(LAST_NAME, profileFullName);
assertLogged(STREET_ADDRESS_TEXTAREA, profileFullName);
assertLogged(ADDRESS_LINE1, profileFullName);
assertLogged(ADDRESS_LINE2, profileFullName);
assertLogged(CITY, profileFullName);
assertLogged(ZIP_CODE, profileFullName);
assertLogged(COUNTRY, profileFullName);
assertLogged(EMAIL, profileFullName);
assertLogged(PHONE_NUMBER, profileFullName);
}
/**
* Tests that bringing up an Autofill and clicking on the partially filled first
* element will still fill the entire form (including the initiating element itself).
*/
@Test
@MediumTest
@Feature({"autofill"})
public void testLoggingInitiatedElementFilled()
throws InterruptedException, ExecutionException, TimeoutException {
loadAndFillForm(INITIATING_ELEMENT_FILLED, "o");
final String profileFullName = FIRST_NAME + " " + LAST_NAME;
final int loggedEntries = 4;
Assert.assertEquals("Mismatched number of logged entries", loggedEntries,
mAutofillLoggedEntries.size());
assertLogged(FIRST_NAME, profileFullName);
assertLogged(LAST_NAME, profileFullName);
assertLogged(EMAIL, profileFullName);
assertLogged(COUNTRY, profileFullName);
}
/**
* Tests that bringing up an Autofill and clicking on the empty first element
* will fill the all other elements except the previously filled email.
*/
@Test
@MediumTest
@Feature({"autofill"})
public void testLoggingAnotherElementFilled()
throws InterruptedException, ExecutionException, TimeoutException {
loadAndFillForm(ANOTHER_ELEMENT_FILLED, "J");
final String profileFullName = FIRST_NAME + " " + LAST_NAME;
final int loggedEntries = 3;
Assert.assertEquals("Mismatched number of logged entries", loggedEntries,
mAutofillLoggedEntries.size());
assertLogged(FIRST_NAME, profileFullName);
assertLogged(LAST_NAME, profileFullName);
assertLogged(COUNTRY, profileFullName);
// Email will not be logged since it already had some data.
}
/**
* Tests that selecting a value not present in <option> will not be filled.
*/
@Test
@MediumTest
@Feature({"autofill"})
public void testNotLoggingInvalidOption()
throws InterruptedException, ExecutionException, TimeoutException {
loadAndFillForm(INVALID_OPTION, "o");
final String profileFullName = FIRST_NAME + " " + LAST_NAME;
final int loggedEntries = 3;
Assert.assertEquals("Mismatched number of logged entries", loggedEntries,
mAutofillLoggedEntries.size());
assertLogged(FIRST_NAME, profileFullName);
assertLogged(LAST_NAME, profileFullName);
assertLogged(EMAIL, profileFullName);
// Country will not be logged since "US" is not a valid <option>.
}
// Wait and assert helper methods -------------------------------------------------------------
private void waitForKeyboardShowRequest(final TestInputMethodManagerWrapper immw,
final int count) {
CriteriaHelper.pollUiThread(
Criteria.equals(count, new Callable<Integer>() {
@Override
public Integer call() {
return immw.getShowSoftInputCounter();
}
}));
}
private void waitForAnchorViewAdd(final ViewGroup view) {
CriteriaHelper.pollUiThread(new Criteria(
"Autofill Popup anchor view was never added.") {
@Override
public boolean isSatisfied() {
return view.findViewById(R.id.dropdown_popup_window) != null;
}
});
}
private void waitForAutofillPopopShow(final AutofillPopup popup) {
CriteriaHelper.pollUiThread(
new Criteria("Autofill Popup anchor view was never added.") {
@Override
public boolean isSatisfied() {
// Wait until the popup is showing and onLayout() has happened.
return popup.isShowing() && popup.getListView() != null
&& popup.getListView().getHeight() != 0;
}
});
}
private void waitForInputFieldFill(final WebContents webContents) {
CriteriaHelper.pollInstrumentationThread(
new Criteria("First name field was never filled.") {
@Override
public boolean isSatisfied() {
try {
return TextUtils.equals(FIRST_NAME,
DOMUtils.getNodeValue(webContents, "fn"));
} catch (InterruptedException e) {
return false;
} catch (TimeoutException e) {
return false;
}
}
});
}
private void assertLogged(String autofilledValue, String profileFullName) {
for (AutofillLogger.LogEntry entry : mAutofillLoggedEntries) {
if (entry.getAutofilledValue().equals(autofilledValue)
&& entry.getProfileFullName().equals(profileFullName)) {
return;
}
}
Assert.fail("Logged entry not found [" + autofilledValue + "," + profileFullName + "]");
}
}