Home | History | Annotate | Download | only in autofill
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.chrome.browser.autofill;
      6 
      7 import android.test.suitebuilder.annotation.MediumTest;
      8 import android.text.TextUtils;
      9 import android.view.View;
     10 import android.view.ViewGroup;
     11 
     12 import org.chromium.base.ThreadUtils;
     13 import org.chromium.base.test.util.Feature;
     14 import org.chromium.base.test.util.UrlUtils;
     15 import org.chromium.chrome.R;
     16 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
     17 import org.chromium.chrome.shell.ChromeShellTestBase;
     18 import org.chromium.content.browser.ContentViewCore;
     19 import org.chromium.content.browser.test.util.Criteria;
     20 import org.chromium.content.browser.test.util.CriteriaHelper;
     21 import org.chromium.content.browser.test.util.DOMUtils;
     22 import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
     23 import org.chromium.content.browser.test.util.TouchCommon;
     24 import org.chromium.ui.autofill.AutofillPopup;
     25 
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 import java.util.concurrent.ExecutionException;
     29 import java.util.concurrent.TimeoutException;
     30 
     31 /**
     32  * Integration tests for the AutofillPopup.
     33  */
     34 public class AutofillPopupTest extends ChromeShellTestBase {
     35 
     36     private static final String FIRST_NAME = "John";
     37     private static final String LAST_NAME = "Smith";
     38     private static final String COMPANY_NAME = "Acme Inc.";
     39     private static final String ADDRESS_LINE1 = "1 Main";
     40     private static final String ADDRESS_LINE2 = "Apt A";
     41     private static final String STREET_ADDRESS_TEXTAREA = ADDRESS_LINE1 + "\n" + ADDRESS_LINE2;
     42     private static final String CITY = "San Francisco";
     43     private static final String DEPENDENT_LOCALITY = "";
     44     private static final String STATE = "CA";
     45     private static final String ZIP_CODE = "94102";
     46     private static final String SORTING_CODE = "";
     47     private static final String COUNTRY = "US";
     48     private static final String PHONE_NUMBER = "4158889999";
     49     private static final String EMAIL = "john (at) acme.inc";
     50     private static final String LANGUAGE_CODE = "";
     51     private static final String ORIGIN = "https://www.example.com";
     52 
     53     private static final String BASIC_PAGE_DATA = UrlUtils.encodeHtmlDataUri(
     54             "<html><head><meta name=\"viewport\"" +
     55             "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>" +
     56             "<body><form method=\"POST\">" +
     57             "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" /><br>" +
     58             "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\" /><br>" +
     59             "<textarea id=\"sa\" autocomplete=\"street-address\"></textarea><br>" +
     60             "<input type=\"text\" id=\"a1\" autocomplete=\"address-line1\" /><br>" +
     61             "<input type=\"text\" id=\"a2\" autocomplete=\"address-line2\" /><br>" +
     62             "<input type=\"text\" id=\"ct\" autocomplete=\"locality\" /><br>" +
     63             "<input type=\"text\" id=\"zc\" autocomplete=\"postal-code\" /><br>" +
     64             "<input type=\"text\" id=\"em\" autocomplete=\"email\" /><br>" +
     65             "<input type=\"text\" id=\"ph\" autocomplete=\"tel\" /><br>" +
     66             "<input type=\"text\" id=\"fx\" autocomplete=\"fax\" /><br>" +
     67             "<select id=\"co\" autocomplete=\"country\"><br>" +
     68             "<option value=\"BR\">Brazil</option>" +
     69             "<option value=\"US\">United States</option>" +
     70             "</select>" +
     71             "<input type=\"submit\" />" +
     72             "</form></body></html>");
     73 
     74     private static final String INITIATING_ELEMENT_FILLED = UrlUtils.encodeHtmlDataUri(
     75             "<html><head><meta name=\"viewport\"" +
     76             "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>" +
     77             "<body><form method=\"POST\">" +
     78             "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" value=\"J\"><br>" +
     79             "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\"><br>" +
     80             "<input type=\"text\" id=\"em\" autocomplete=\"email\"><br>" +
     81             "<select id=\"co\" autocomplete=\"country\"><br>" +
     82             "<option value=\"US\">United States</option>" +
     83             "<option value=\"BR\">Brazil</option>" +
     84             "</select>" +
     85             "<input type=\"submit\" />" +
     86             "</form></body></html>");
     87 
     88     private static final String ANOTHER_ELEMENT_FILLED = UrlUtils.encodeHtmlDataUri(
     89             "<html><head><meta name=\"viewport\"" +
     90             "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>" +
     91             "<body><form method=\"POST\">" +
     92             "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\"><br>" +
     93             "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\"><br>" +
     94             "<input type=\"text\" id=\"em\" autocomplete=\"email\" value=\"foo (at) example.com\"><br>" +
     95             "<select id=\"co\" autocomplete=\"country\"><br>" +
     96             "<option></option>" +
     97             "<option value=\"BR\">Brazil</option>" +
     98             "<option value=\"US\">United States</option>" +
     99             "</select>" +
    100             "<input type=\"submit\" />" +
    101             "</form></body></html>");
    102 
    103     private static final String INVALID_OPTION = UrlUtils.encodeHtmlDataUri(
    104             "<html><head><meta name=\"viewport\"" +
    105             "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>" +
    106             "<body><form method=\"POST\">" +
    107             "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" value=\"J\"><br>" +
    108             "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\"><br>" +
    109             "<input type=\"text\" id=\"em\" autocomplete=\"email\"><br>" +
    110             "<select id=\"co\" autocomplete=\"country\"><br>" +
    111             "<option value=\"GB\">Great Britain</option>" +
    112             "<option value=\"BR\">Brazil</option>" +
    113             "</select>" +
    114             "<input type=\"submit\" />" +
    115             "</form></body></html>");
    116 
    117     private AutofillTestHelper mHelper;
    118     private List<AutofillLogger.LogEntry> mAutofillLoggedEntries;
    119 
    120     @Override
    121     public void setUp() throws Exception {
    122         super.setUp();
    123         clearAppData();
    124         mAutofillLoggedEntries = new ArrayList<AutofillLogger.LogEntry>();
    125         AutofillLogger.setLogger(
    126             new AutofillLogger.Logger() {
    127                 @Override
    128                 public void didFillField(AutofillLogger.LogEntry logEntry) {
    129                     mAutofillLoggedEntries.add(logEntry);
    130                 }}
    131         );
    132     }
    133 
    134     private void loadAndFillForm(
    135             final String formDataUrl, final String inputText)
    136             throws InterruptedException, ExecutionException, TimeoutException {
    137         launchChromeShellWithUrl(formDataUrl);
    138         assertTrue(waitForActiveShellToBeDoneLoading());
    139         mHelper = new AutofillTestHelper();
    140 
    141         // The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is never
    142         // brought up.
    143         final ContentViewCore viewCore = getActivity().getActiveContentViewCore();
    144         final ViewGroup view = viewCore.getContainerView();
    145         final TestInputMethodManagerWrapper immw =
    146                 new TestInputMethodManagerWrapper(viewCore);
    147         viewCore.getImeAdapterForTest().setInputMethodManagerWrapper(immw);
    148 
    149         // Add an Autofill profile.
    150         AutofillProfile profile = new AutofillProfile(
    151                 "" /* guid */, ORIGIN, FIRST_NAME + " " + LAST_NAME, COMPANY_NAME,
    152                 STREET_ADDRESS_TEXTAREA,
    153                 STATE, CITY, DEPENDENT_LOCALITY,
    154                 ZIP_CODE, SORTING_CODE, COUNTRY, PHONE_NUMBER, EMAIL,
    155                 LANGUAGE_CODE);
    156         mHelper.setProfile(profile);
    157         assertEquals(1, mHelper.getNumberOfProfiles());
    158 
    159         // Click the input field for the first name.
    160         assertTrue(DOMUtils.waitForNonZeroNodeBounds(viewCore, "fn"));
    161         DOMUtils.clickNode(this, viewCore, "fn");
    162 
    163         waitForKeyboardShowRequest(immw, 1);
    164 
    165         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
    166             @Override
    167             public void run() {
    168                 viewCore.getInputConnectionForTest().setComposingText(
    169                         inputText, 1);
    170             }
    171         });
    172 
    173         waitForAnchorViewAdd(view);
    174         View anchorView = view.findViewById(R.id.dropdown_popup_window);
    175 
    176         assertTrue(anchorView.getTag() instanceof AutofillPopup);
    177         final AutofillPopup popup = (AutofillPopup) anchorView.getTag();
    178 
    179         waitForAutofillPopopShow(popup);
    180 
    181         TouchCommon touchCommon = new TouchCommon(this);
    182         touchCommon.singleClickViewRelative(popup.getListView(), 10, 10);
    183 
    184         waitForInputFieldFill(viewCore);
    185     }
    186 
    187     /**
    188      * Tests that bringing up an Autofill and clicking on the first entry fills out the expected
    189      * Autofill information.
    190      */
    191     @MediumTest
    192     @Feature({"autofill"})
    193     public void testClickAutofillPopupSuggestion()
    194             throws InterruptedException, ExecutionException, TimeoutException {
    195         loadAndFillForm(BASIC_PAGE_DATA, "J");
    196         final ContentViewCore viewCore = getActivity().getActiveContentViewCore();
    197 
    198         assertEquals("First name did not match",
    199                 FIRST_NAME, DOMUtils.getNodeValue(viewCore, "fn"));
    200         assertEquals("Last name did not match",
    201                 LAST_NAME, DOMUtils.getNodeValue(viewCore, "ln"));
    202         assertEquals("Street address (textarea) did not match",
    203                 STREET_ADDRESS_TEXTAREA, DOMUtils.getNodeValue(viewCore, "sa"));
    204         assertEquals("Address line 1 did not match",
    205                 ADDRESS_LINE1, DOMUtils.getNodeValue(viewCore, "a1"));
    206         assertEquals("Address line 2 did not match",
    207                 ADDRESS_LINE2, DOMUtils.getNodeValue(viewCore, "a2"));
    208         assertEquals("City did not match",
    209                 CITY, DOMUtils.getNodeValue(viewCore, "ct"));
    210         assertEquals("Zip code did not match",
    211                 ZIP_CODE, DOMUtils.getNodeValue(viewCore, "zc"));
    212         assertEquals("Country did not match",
    213                 COUNTRY, DOMUtils.getNodeValue(viewCore, "co"));
    214         assertEquals("Email did not match",
    215                 EMAIL, DOMUtils.getNodeValue(viewCore, "em"));
    216         assertEquals("Phone number did not match",
    217                 PHONE_NUMBER, DOMUtils.getNodeValue(viewCore, "ph"));
    218 
    219         final String profileFullName = FIRST_NAME + " " + LAST_NAME;
    220         final int loggedEntries = 10;
    221         assertEquals("Mismatched number of logged entries",
    222                 loggedEntries, mAutofillLoggedEntries.size());
    223         assertLogged(FIRST_NAME, profileFullName);
    224         assertLogged(LAST_NAME, profileFullName);
    225         assertLogged(STREET_ADDRESS_TEXTAREA, profileFullName);
    226         assertLogged(ADDRESS_LINE1, profileFullName);
    227         assertLogged(ADDRESS_LINE2, profileFullName);
    228         assertLogged(CITY, profileFullName);
    229         assertLogged(ZIP_CODE, profileFullName);
    230         assertLogged(COUNTRY, profileFullName);
    231         assertLogged(EMAIL, profileFullName);
    232         assertLogged(PHONE_NUMBER, profileFullName);
    233     }
    234 
    235     /**
    236      * Tests that bringing up an Autofill and clicking on the partially filled first
    237      * element will still fill the entire form (including the initiating element itself).
    238      */
    239     @MediumTest
    240     @Feature({"autofill"})
    241     public void testLoggingInitiatedElementFilled()
    242             throws InterruptedException, ExecutionException, TimeoutException {
    243         loadAndFillForm(INITIATING_ELEMENT_FILLED, "o");
    244         final String profileFullName = FIRST_NAME + " " + LAST_NAME;
    245         final int loggedEntries = 4;
    246         assertEquals("Mismatched number of logged entries",
    247                 loggedEntries, mAutofillLoggedEntries.size());
    248         assertLogged(FIRST_NAME, profileFullName);
    249         assertLogged(LAST_NAME, profileFullName);
    250         assertLogged(EMAIL, profileFullName);
    251         assertLogged(COUNTRY, profileFullName);
    252     }
    253 
    254     /**
    255      * Tests that bringing up an Autofill and clicking on the empty first element
    256      * will fill the all other elements except the previously filled email.
    257      */
    258     @MediumTest
    259     @Feature({"autofill"})
    260     public void testLoggingAnotherElementFilled()
    261             throws InterruptedException, ExecutionException, TimeoutException {
    262         loadAndFillForm(ANOTHER_ELEMENT_FILLED, "J");
    263         final String profileFullName = FIRST_NAME + " " + LAST_NAME;
    264         final int loggedEntries = 3;
    265         assertEquals("Mismatched number of logged entries",
    266                 loggedEntries, mAutofillLoggedEntries.size());
    267         assertLogged(FIRST_NAME, profileFullName);
    268         assertLogged(LAST_NAME, profileFullName);
    269         assertLogged(COUNTRY, profileFullName);
    270         // Email will not be logged since it already had some data.
    271     }
    272 
    273     /**
    274      * Tests that selecting a value not present in <option> will not be filled.
    275      */
    276     @MediumTest
    277     @Feature({"autofill"})
    278     public void testNotLoggingInvalidOption()
    279             throws InterruptedException, ExecutionException, TimeoutException {
    280         loadAndFillForm(INVALID_OPTION, "o");
    281         final String profileFullName = FIRST_NAME + " " + LAST_NAME;
    282         final int loggedEntries = 3;
    283         assertEquals("Mismatched number of logged entries",
    284                 loggedEntries, mAutofillLoggedEntries.size());
    285         assertLogged(FIRST_NAME, profileFullName);
    286         assertLogged(LAST_NAME, profileFullName);
    287         assertLogged(EMAIL, profileFullName);
    288         // Country will not be logged since "US" is not a valid <option>.
    289     }
    290 
    291     // Wait and assert helper methods -------------------------------------------------------------
    292 
    293     private void waitForKeyboardShowRequest(final TestInputMethodManagerWrapper immw,
    294             final int count) throws InterruptedException {
    295         assertTrue("Keyboard was never requested to be shown.",
    296                 CriteriaHelper.pollForCriteria(new Criteria() {
    297                     @Override
    298                     public boolean isSatisfied() {
    299                         return immw.getShowSoftInputCounter() == count;
    300                     }
    301                 }));
    302     }
    303 
    304     private void waitForAnchorViewAdd(final ViewGroup view) throws InterruptedException {
    305         assertTrue("Autofill Popup anchor view was never added.",
    306                 CriteriaHelper.pollForCriteria(new Criteria() {
    307                     @Override
    308                     public boolean isSatisfied() {
    309                         return view.findViewById(R.id.dropdown_popup_window) != null;
    310                     }
    311                 }));
    312     }
    313 
    314     private void waitForAutofillPopopShow(final AutofillPopup popup) throws InterruptedException {
    315         assertTrue("Autofill Popup anchor view was never added.",
    316                 CriteriaHelper.pollForCriteria(new Criteria() {
    317                     @Override
    318                     public boolean isSatisfied() {
    319                         return popup.isShowing();
    320                     }
    321                 }));
    322     }
    323 
    324     private void waitForInputFieldFill(final ContentViewCore viewCore) throws InterruptedException {
    325         assertTrue("First name field was never filled.",
    326                 CriteriaHelper.pollForCriteria(new Criteria() {
    327                     @Override
    328                     public boolean isSatisfied() {
    329                         try {
    330                             return TextUtils.equals(FIRST_NAME,
    331                                     DOMUtils.getNodeValue(viewCore, "fn"));
    332                         } catch (InterruptedException e) {
    333                             return false;
    334                         } catch (TimeoutException e) {
    335                             return false;
    336                         }
    337 
    338                     }
    339                 }));
    340     }
    341 
    342     private void assertLogged(String autofilledValue, String profileFullName) {
    343         for (AutofillLogger.LogEntry entry : mAutofillLoggedEntries) {
    344             if (entry.getAutofilledValue().equals(autofilledValue) &&
    345                 entry.getProfileFullName().equals(profileFullName)) {
    346                 return;
    347             }
    348         }
    349         fail("Logged entry not found [" + autofilledValue + "," + profileFullName + "]");
    350     }
    351 }
    352