1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import logging
7import os
8import pickle
9import re
10import simplejson
11
12import pyauto_functional  # Must be imported before pyauto
13import pyauto
14import test_utils
15from selenium.webdriver.common.keys import Keys
16from selenium.webdriver.common.action_chains import ActionChains
17from webdriver_pages import settings
18
19
20class AutofillTest(pyauto.PyUITest):
21  """Tests that autofill UI works correctly. Also contains a manual test for
22     the crowdsourcing server."""
23
24  def setUp(self):
25    pyauto.PyUITest.setUp(self)
26    self._driver = self.NewWebDriver()
27
28  def AutofillCrowdsourcing(self):
29    """Test able to send POST request of web form to Autofill server.
30
31    The Autofill server processes the data offline, so it can take a few days
32    for the result to be detectable. Manual verification is required.
33    """
34    # HTML file needs to be run from a specific http:// url to be able to verify
35    # the results a few days later by visiting the same url.
36    url = 'http://www.corp.google.com/~dyu/autofill/crowdsourcing-test.html'
37    # Autofill server captures 2.5% of the data posted.
38    # Looping 1000 times is a safe minimum to exceed the server's threshold or
39    # noise.
40    for i in range(1000):
41      fname = 'David'
42      lname = 'Yu'
43      email = 'david.yu@gmail.com'
44      # Submit form to collect crowdsourcing data for Autofill.
45      self.NavigateToURL(url, 0, 0)
46      profile = {'fn': fname, 'ln': lname, 'em': email}
47      js = ''.join(['document.getElementById("%s").value = "%s";' %
48                    (key, value) for key, value in profile.iteritems()])
49      js += 'document.getElementById("testform").submit();'
50      self.ExecuteJavascript(js)
51
52  def _SelectOptionXpath(self, value):
53    """Returns an xpath query used to select an item from a dropdown list.
54    Args:
55      value: Option selected for the drop-down list field.
56
57    Returns:
58      The value of the xpath query.
59    """
60    return '//option[@value="%s"]' % value
61
62  def testPostalCodeAndStateLabelsBasedOnCountry(self):
63    """Verify postal code and state labels based on selected country."""
64    data_file = os.path.join(self.DataDir(), 'autofill', 'functional',
65                             'state_zip_labels.txt')
66    test_data = simplejson.loads(open(data_file).read())
67
68    page = settings.AutofillEditAddressDialog.FromNavigation(self._driver)
69    # Initial check of State and ZIP labels.
70    self.assertEqual('State', page.GetStateLabel())
71    self.assertEqual('ZIP code', page.GetPostalCodeLabel())
72
73    for country_code in test_data:
74      page.Fill(country_code=country_code)
75
76      # Compare postal code labels.
77      actual_postal_label = page.GetPostalCodeLabel()
78      self.assertEqual(
79          test_data[country_code]['postalCodeLabel'],
80          actual_postal_label,
81          msg=('Postal code label "%s" does not match Country "%s"' %
82               (actual_postal_label, country_code)))
83
84      # Compare state labels.
85      actual_state_label = page.GetStateLabel()
86      self.assertEqual(
87          test_data[country_code]['stateLabel'],
88          actual_state_label,
89          msg=('State label "%s" does not match Country "%s"' %
90               (actual_state_label, country_code)))
91
92  def testNoDuplicatePhoneNumsInPrefs(self):
93    """Test duplicate phone numbers entered in prefs are removed."""
94    page = settings.AutofillEditAddressDialog.FromNavigation(self._driver)
95    non_duplicates = ['111-1111', '222-2222']
96    duplicates = ['111-1111']
97    page.Fill(phones=non_duplicates + duplicates)
98    self.assertEqual(non_duplicates, page.GetPhones(),
99        msg='Duplicate phone number in prefs unexpectedly saved.')
100
101  def testDisplayLineItemForEntriesWithNoCCNum(self):
102    """Verify Autofill creates a line item for CC entries with no CC number."""
103    self.NavigateToURL('chrome://settings-frame/autofillEditCreditCard')
104    self._driver.find_element_by_id('name-on-card').send_keys('Jane Doe')
105    query_month = self._SelectOptionXpath('12')
106    query_year = self._SelectOptionXpath('2014')
107    self._driver.find_element_by_id('expiration-month').find_element_by_xpath(
108        query_month).click()
109    self._driver.find_element_by_id('expiration-year').find_element_by_xpath(
110        query_year).click()
111    self._driver.find_element_by_id(
112        'autofill-edit-credit-card-apply-button').click()
113    # Refresh the page to ensure the UI is up-to-date.
114    self._driver.refresh()
115    list_entry = self._driver.find_element_by_class_name('autofill-list-item')
116    self.assertTrue(list_entry.is_displayed)
117    self.assertEqual('Jane Doe', list_entry.text,
118                     msg='Saved CC line item not same as what was entered.')
119
120  def _GetElementList(self, container_elem, fields_to_select):
121    """Returns all sub elements of specific characteristics.
122
123    Args:
124      container_elem: An element that contains other elements.
125      fields_to_select: A list of fields to select with strings that
126                        help create an xpath string, which in turn identifies
127                        the elements needed.
128                        For example: ['input', 'button']
129                        ['div[@id]', 'button[@disabled]']
130                        ['*[class="example"]']
131
132    Returns:
133      List of all subelements found in the container element.
134    """
135    self.assertTrue(fields_to_select, msg='No fields specified for selection.')
136    fields_to_select = ['.//' + field for field in fields_to_select]
137    xpath_arg = ' | '.join(fields_to_select)
138    field_elems = container_elem.find_elements_by_xpath(xpath_arg)
139    return field_elems
140
141  def _GetElementInfo(self, element):
142    """Returns visual comprehensive info about an element.
143
144    This function identifies the text of the correspoinding label when tab
145    ordering fails.
146    This info consists of:
147      The labels, buttons, ids, placeholder attribute values, or the element id.
148
149    Args:
150      element: The target element.
151
152    Returns:
153      A string that identifies the element in the page.
154    """
155    element_info = ''
156    if element.tag_name == 'button':
157      element_info = element.text
158    element_info = (element_info or element.get_attribute('id') or
159        element.get_attribute('placeholder') or
160        element.get_attribute('class') or element.id)
161    return '%s: %s' % (element.tag_name, element_info)
162
163  def _LoadPageAndGetFieldList(self):
164    """Navigate to autofillEditAddress page and finds the elements with focus.
165
166    These elements are of input, select, and button types.
167
168    Returns:
169      A list with all elements that can receive focus.
170    """
171    url = 'chrome://settings-frame/autofillEditAddress'
172    self._driver.get(url)
173    container_elem = self._driver.find_element_by_id(
174        'autofill-edit-address-overlay')
175    # The container element contains input, select and button fields. Some of
176    # the buttons are disabled so they are ignored.
177    field_list = self._GetElementList(container_elem,
178                                      ['input', 'select',
179                                       'button[not(@disabled)]'])
180    self.assertTrue(field_list, 'No fields found in "%s".' % url)
181    return field_list
182
183  def testTabOrderForEditAddress(self):
184    """Verify the TAB ordering for Edit Address page is correct."""
185    tab_press = ActionChains(self._driver).send_keys(Keys.TAB)
186    field_list = self._LoadPageAndGetFieldList()
187
188    # Creates a dictionary where a field key returns the value of the next field
189    # in the field list. The last field of the field list is mapped to the first
190    # field of the field list.
191    field_nextfield_dict = dict(
192        zip(field_list, field_list[1:] + field_list[:1]))
193
194    # Wait until a field of |field_list| has received the focus.
195    self.WaitUntil(lambda:
196                   self._driver.switch_to_active_element().id in
197                   [f.id for f in field_list])
198    # The first field is expected to receive the focus.
199    self.assertEqual(self._driver.switch_to_active_element().id,
200                     field_list[0].id,
201                     msg='The first field did not receive tab focus.')
202    for field in field_list:
203      tab_press.perform()
204      # Wait until a field of |field_list|, other than the current field, has
205      # received the focus.
206      self.WaitUntil(lambda:
207                     self._driver.switch_to_active_element().id != field.id and
208                     self._driver.switch_to_active_element().id in
209                     [f.id for f in field_list])
210
211      self.assertEqual(self._driver.switch_to_active_element().id,
212                       field_nextfield_dict[field].id,
213                       msg=('The TAB ordering is broken. Previous field: "%s"\n'
214                            'Field expected to receive focus: "%s"\n'
215                            'Field that received focus instead: "%s"')
216                       % (self._GetElementInfo(field),
217                          self._GetElementInfo(field_nextfield_dict[field]),
218                          self._GetElementInfo(
219                              self._driver.switch_to_active_element())))
220
221
222if __name__ == '__main__':
223  pyauto_functional.Main()
224