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 os
7from urlparse import urlparse
8
9import pyauto_functional  # Must be imported before pyauto
10import pyauto
11import test_utils
12from webdriver_pages import settings
13
14
15class PasswordTest(pyauto.PyUITest):
16  """Tests that passwords work correctly."""
17
18  INFOBAR_TYPE = 'password_infobar'
19  URL = 'https://accounts.google.com/ServiceLogin'
20  URL_HTTPS = 'https://accounts.google.com/Login'
21  URL_LOGOUT = 'https://accounts.google.com/Logout'
22  HOSTNAME = 'https://' + urlparse(URL).netloc + '/'
23  USERNAME_ELEM = 'Email'
24  PASSWORD_ELEM = 'Passwd'
25  USERNAME = 'test@google.com'
26  PASSWORD = 'test.password'
27
28  def Debug(self):
29    """Test method for experimentation.
30
31    This method will not run automatically.
32    """
33    while True:
34      raw_input('Interact with the browser and hit <enter> to dump passwords. ')
35      print '*' * 20
36      self.pprint(self.GetSavedPasswords())
37
38  def setUp(self):
39    pyauto.PyUITest.setUp(self)
40    self.assertFalse(self.GetSavedPasswords())
41
42  def _AssertWithinOneSecond(self, time1, time2):
43    self.assertTrue(abs(time1 - time2) < 1.0,
44                    'Times not within an acceptable range. '
45                    'First was %lf, second was %lf' % (time1, time2))
46
47  def _ConstructPasswordDictionary(self, username_value, password_value,
48                                   signon_realm, origin_url, username_element,
49                                   password_element, action_target,
50                                   time=1279650942.0, submit_element='submit',
51                                   blacklist=False):
52    """Construct a password dictionary with all the required fields."""
53    return {'username_value': username_value,
54            'password_value': password_value,
55            'signon_realm': signon_realm,
56            'time': time,
57            'origin_url': origin_url,
58            'username_element': username_element,
59            'password_element': password_element,
60            'submit_element': submit_element,
61            'action_target': action_target,
62            'blacklist': blacklist}
63
64  def _ClickOnLoginPage(self, window_index, tab_index):
65    # In some cases (such as on Windows) the current page displays an account
66    # name and e-mail, rather than an e-mail and password.  Clicking on a
67    # particular DOM element causes the e-mail and password to be displayed.
68    click_js = """
69      var elements = document.getElementsByClassName("accounts");
70      if (elements && elements.length > 0) {
71        elements = elements[0].getElementsByTagName("p");
72        if (elements && elements.length > 0)
73          elements[0].onclick();
74      }
75      window.domAutomationController.send("done");
76    """
77    self.ExecuteJavascript(click_js, tab_index, window_index)
78
79    # Wait until username/password is filled by the Password manager on the
80    # login page.
81    js_template = """
82      var value = "";
83      var element = document.getElementById("%s");
84      if (element)
85        value = element.value;
86      window.domAutomationController.send(value);
87    """
88    self.assertTrue(self.WaitUntil(
89        lambda: self.ExecuteJavascript(js_template % self.USERNAME_ELEM,
90                                      tab_index, window_index) != '' and
91                self.ExecuteJavascript(js_template % self.PASSWORD_ELEM,
92                                      tab_index, window_index) != ''))
93
94  def testSavePassword(self):
95    """Test saving a password and getting saved passwords."""
96    password1 = self._ConstructPasswordDictionary(
97        'user@example.com', 'test.password',
98        'https://www.example.com/', 'https://www.example.com/login',
99        'username', 'password', 'https://www.example.com/login/')
100    self.assertTrue(self.AddSavedPassword(password1))
101    self.assertEqual(self.GetSavedPasswords(), [password1])
102
103  def testRemovePasswords(self):
104    """Verify that saved passwords can be removed."""
105    password1 = self._ConstructPasswordDictionary(
106        'user1@example.com', 'test1.password',
107        'https://www.example.com/', 'https://www.example.com/login',
108        'username1', 'password', 'https://www.example.com/login/')
109    password2 = self._ConstructPasswordDictionary(
110        'user2@example.com', 'test2.password',
111        'https://www.example.com/', 'https://www.example.com/login',
112        'username2', 'password2', 'https://www.example.com/login/')
113    self.AddSavedPassword(password1)
114    self.AddSavedPassword(password2)
115    self.assertEquals(2, len(self.GetSavedPasswords()))
116    self.assertEquals([password1, password2], self.GetSavedPasswords())
117    self.RemoveSavedPassword(password1)
118    self.assertEquals(1, len(self.GetSavedPasswords()))
119    self.assertEquals([password2], self.GetSavedPasswords())
120    self.RemoveSavedPassword(password2)
121    # TODO: GetSavedPasswords() doesn't return anything when empty.
122    # http://crbug.com/64603
123    # self.assertFalse(self.GetSavedPasswords())
124
125  def testDisplayAndSavePasswordInfobar(self):
126    """Verify password infobar displays and able to save password."""
127    creds = self.GetPrivateInfo()['test_google_account']
128    username = creds['username']
129    password = creds['password']
130    # Disable one-click login infobar for sync.
131    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
132    test_utils.GoogleAccountsLogin(self, username, password)
133    # Wait until page completes loading.
134    self.WaitUntil(
135        lambda: self.GetDOMValue('document.readyState'),
136        expect_retval='complete')
137    self.PerformActionOnInfobar(
138        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
139            self, self.INFOBAR_TYPE))
140    self.NavigateToURL(self.URL_LOGOUT)
141    self.NavigateToURL(self.URL_HTTPS)
142    self._ClickOnLoginPage(0, 0)
143    test_utils.VerifyGoogleAccountCredsFilled(self, username, password,
144                                              tab_index=0, windex=0)
145
146  def testNeverSavePasswords(self):
147    """Verify passwords not saved/deleted when 'never for this site' chosen."""
148    creds1 = self.GetPrivateInfo()['test_google_account']
149    # Disable one-click login infobar for sync.
150    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
151    test_utils.GoogleAccountsLogin(
152        self, creds1['username'], creds1['password'])
153    self.PerformActionOnInfobar(
154        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
155            self, self.INFOBAR_TYPE))
156    self.assertEquals(1, len(self.GetSavedPasswords()))
157    self.AppendTab(pyauto.GURL(creds1['logout_url']))
158    creds2 = self.GetPrivateInfo()['test_google_account_2']
159    test_utils.GoogleAccountsLogin(
160        self, creds2['username'], creds2['password'], tab_index=1)
161    # Selecting 'Never for this site' option on password infobar.
162    self.PerformActionOnInfobar(
163        'cancel', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
164            self, self.INFOBAR_TYPE, tab_index=1), tab_index=1)
165
166    # TODO: GetSavedPasswords() doesn't return anything when empty.
167    # http://crbug.com/64603
168    # self.assertFalse(self.GetSavedPasswords())
169    # TODO: Check the exceptions list
170
171  def testSavedPasswordInTabsAndWindows(self):
172    """Verify saved username/password shows in window and tab."""
173    creds = self.GetPrivateInfo()['test_google_account']
174    username = creds['username']
175    password = creds['password']
176    # Disable one-click login infobar for sync.
177    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
178    # Login to Google a/c
179    test_utils.GoogleAccountsLogin(self, username, password)
180    self.PerformActionOnInfobar(
181        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
182            self, self.INFOBAR_TYPE))
183    self.NavigateToURL(self.URL_LOGOUT)
184    self.NavigateToURL(self.URL)
185    self._ClickOnLoginPage(0, 0)
186    test_utils.VerifyGoogleAccountCredsFilled(self, username, password,
187        tab_index=0, windex=0)
188    self.AppendTab(pyauto.GURL(self.URL))
189    self._ClickOnLoginPage(0, 1)
190    test_utils.VerifyGoogleAccountCredsFilled(self, username, password,
191        tab_index=1, windex=0)
192
193  def testLoginCredsNotShownInIncognito(self):
194    """Verify login creds are not shown in Incognito mode."""
195    creds = self.GetPrivateInfo()['test_google_account']
196    username = creds['username']
197    password = creds['password']
198    # Disable one-click login infobar for sync.
199    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
200    # Login to Google account.
201    test_utils.GoogleAccountsLogin(self, username, password)
202    self.PerformActionOnInfobar(
203        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
204            self, self.INFOBAR_TYPE))
205    self.NavigateToURL(self.URL_LOGOUT)
206    self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW)
207    self.NavigateToURL(self.URL, 1, 0)
208    email_value = self.GetDOMValue('document.getElementById("Email").value',
209                                   tab_index=0, windex=1)
210    passwd_value = self.GetDOMValue('document.getElementById("Passwd").value',
211                                    tab_index=0, windex=1)
212    self.assertEqual(email_value, '',
213                    msg='Email creds displayed %s.' % email_value)
214    self.assertEqual(passwd_value, '', msg='Password creds displayed.')
215
216  def testPasswordAutofilledInIncognito(self):
217    """Verify saved password is autofilled in Incognito mode.
218
219    Saved passwords should be autofilled once the username is entered in
220    incognito mode.
221    """
222    action_target = self.HOSTNAME
223
224    driver = self.NewWebDriver()
225    password_dict = self._ConstructPasswordDictionary(
226        self.USERNAME, self.PASSWORD, self.HOSTNAME, self.URL,
227        self.USERNAME_ELEM, self.PASSWORD_ELEM, action_target)
228    self.AddSavedPassword(password_dict)
229    self.RunCommand(pyauto.IDC_NEW_INCOGNITO_WINDOW)
230    self.NavigateToURL(self.URL, 1, 0)
231    # Switch to window 1.
232    driver.switch_to_window(driver.window_handles[1])
233    driver.find_element_by_id(
234        self.USERNAME_ELEM).send_keys(self.USERNAME + '\t')
235    incognito_passwd = self.GetDOMValue(
236        'document.getElementById("Passwd").value', tab_index=0, windex=1)
237    self.assertEqual(incognito_passwd, self.PASSWORD,
238                     msg='Password creds did not autofill in incognito mode.')
239
240  def testInfoBarDisappearByNavigatingPage(self):
241    """Test password infobar is dismissed when navigating to different page."""
242    creds = self.GetPrivateInfo()['test_google_account']
243    # Disable one-click login infobar for sync.
244    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
245    # Login to Google account.
246    test_utils.GoogleAccountsLogin(self, creds['username'], creds['password'])
247    self.PerformActionOnInfobar(
248        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
249            self, self.INFOBAR_TYPE))
250    self.NavigateToURL('chrome://version')
251    self.assertTrue(self.WaitForInfobarCount(0))
252    # To make sure user is navigated to Version page.
253    self.assertTrue(self.WaitUntil(self.GetActiveTabTitle,
254        expect_retval='About Version'))
255    test_utils.AssertInfobarTypeDoesNotAppear(self, self.INFOBAR_TYPE)
256
257  def testInfoBarDisappearByReload(self):
258    """Test that Password infobar disappears by the page reload."""
259    creds = self.GetPrivateInfo()['test_google_account']
260    # Disable one-click login infobar for sync.
261    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
262    # Login to Google a/c
263    test_utils.GoogleAccountsLogin(self, creds['username'], creds['password'])
264    self.PerformActionOnInfobar(
265        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
266            self, self.INFOBAR_TYPE))
267    self.ReloadTab()
268    test_utils.AssertInfobarTypeDoesNotAppear(self, self.INFOBAR_TYPE)
269
270  def testPasswdInfoNotStoredWhenAutocompleteOff(self):
271    """Verify that password infobar does not appear when autocomplete is off.
272
273    If the password field has autocomplete turned off, then the password infobar
274    should not offer to save the password info.
275    """
276    password_info = {'Email': self.USERNAME,
277                     'Passwd': self.PASSWORD}
278
279    # Disable one-click login infobar for sync.
280    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
281    url = self.GetHttpURLForDataPath(
282        os.path.join('password', 'password_autocomplete_off_test.html'))
283    self.NavigateToURL(url)
284    for key, value in password_info.iteritems():
285      script = ('document.getElementById("%s").value = "%s"; '
286                'window.domAutomationController.send("done");') % (key, value)
287      self.ExecuteJavascript(script, 0, 0)
288    self.assertTrue(self.SubmitForm('loginform'))
289    test_utils.AssertInfobarTypeDoesNotAppear(self, self.INFOBAR_TYPE)
290
291  def _SendCharToPopulateField(self, char, tab_index=0, windex=0):
292    """Simulate a char being typed into a field.
293
294    Args:
295      char: the char value to be typed into the field.
296      tab_index: tab index to work on. Defaults to 0 (first tab).
297      windex: window index to work on. Defaults to 0 (first window).
298    """
299    CHAR_KEYPRESS = ord((char).upper())  # ASCII char key press.
300    KEY_DOWN_TYPE = 0  # kRawKeyDownType
301    KEY_UP_TYPE = 3  # kKeyUpType
302
303    self.SendWebkitKeyEvent(KEY_DOWN_TYPE, CHAR_KEYPRESS, tab_index, windex)
304    self.SendWebkitCharEvent(char, tab_index, windex)
305    self.SendWebkitKeyEvent(KEY_UP_TYPE, CHAR_KEYPRESS, tab_index, windex)
306
307  def testClearFetchedCredForNewUserName(self):
308    """Verify that the fetched credentials are cleared for a new username.
309
310    This test requires sending key events rather than pasting a new username
311    into the Email field.
312    """
313    creds = self.GetPrivateInfo()['test_google_account']
314    username = creds['username']
315    password = creds['password']
316    # Disable one-click login infobar for sync.
317    self.SetPrefs(pyauto.kReverseAutologinEnabled, False)
318    # Login to Google a/c
319    test_utils.GoogleAccountsLogin(self, username, password)
320    self.PerformActionOnInfobar(
321        'accept', infobar_index=test_utils.WaitForInfobarTypeAndGetIndex(
322            self, self.INFOBAR_TYPE))
323    self.NavigateToURL(self.URL_LOGOUT)
324    self.NavigateToURL(self.URL)
325    self._ClickOnLoginPage(0, 0)
326    test_utils.VerifyGoogleAccountCredsFilled(self, username, password,
327        tab_index=0, windex=0)
328    clear_username_field = (
329        'document.getElementById("Email").value = ""; '
330        'window.domAutomationController.send("done");')
331    set_focus = (
332        'document.getElementById("Email").focus(); '
333        'window.domAutomationController.send("done");')
334    self.ExecuteJavascript(clear_username_field, 0, 0)
335    self.ExecuteJavascript(set_focus, 0, 0)
336    self._SendCharToPopulateField('t', tab_index=0, windex=0)
337    passwd_value = self.GetDOMValue('document.getElementById("Passwd").value')
338    self.assertFalse(passwd_value,
339                     msg='Password field not empty for new username.')
340
341  def testPasswordInfobarShowsForBlockedDomain(self):
342    """Verify that password infobar shows when cookies are blocked.
343
344    Password infobar should be shown if cookies are blocked for Google
345    accounts domain.
346    """
347    creds = self.GetPrivateInfo()['test_google_account']
348    username = creds['username']
349    password = creds['password']
350    # Block cookies for Google accounts domain.
351    self.SetPrefs(pyauto.kContentSettingsPatternPairs,
352                  {'https://accounts.google.com/': {'cookies': 2}})
353    test_utils.GoogleAccountsLogin(self, username, password)
354    test_utils.WaitForInfobarTypeAndGetIndex(self, self.INFOBAR_TYPE)
355
356
357if __name__ == '__main__':
358  pyauto_functional.Main()
359