• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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"""The testing Environment class."""
6
7import logging
8import shutil
9import time
10
11from selenium import webdriver
12from selenium.common.exceptions import NoSuchElementException
13from selenium.common.exceptions import WebDriverException
14from selenium.webdriver.chrome.options import Options
15from xml.etree import ElementTree
16
17# Message strings to look for in chrome://password-manager-internals
18MESSAGE_ASK = "Message: Decision: ASK the user"
19MESSAGE_SAVE = "Message: Decision: SAVE the password"
20
21
22class Environment:
23  """Sets up the testing Environment. """
24
25  def __init__(self, chrome_path, chromedriver_path, profile_path,
26               passwords_path, enable_automatic_password_saving,
27               numeric_level=None, log_to_console=False, log_file=""):
28    """Creates a new testing Environment.
29
30    Args:
31      chrome_path: The chrome binary file.
32      chromedriver_path: The chromedriver binary file.
33      profile_path: The chrome testing profile folder.
34      passwords_path: The usernames and passwords file.
35      enable_automatic_password_saving: If True, the passwords are going to be
36          saved without showing the prompt.
37      numeric_level: The log verbosity.
38      log_to_console: If True, the debug logs will be shown on the console.
39      log_file: The file where to store the log. If it's empty, the log will
40            not be stored.
41
42    Raises:
43      Exception: An exception is raised if |profile_path| folder could not be
44      removed.
45    """
46    # Setting up the login.
47    if numeric_level is not None:
48      if log_file:
49        # Set up logging to file.
50        logging.basicConfig(level=numeric_level,
51                            filename=log_file,
52                            filemode='w')
53
54        if log_to_console:
55          console = logging.StreamHandler()
56          console.setLevel(numeric_level)
57          # Add the handler to the root logger.
58          logging.getLogger('').addHandler(console)
59
60      elif log_to_console:
61        logging.basicConfig(level=numeric_level)
62
63    # Cleaning the chrome testing profile folder.
64    try:
65      shutil.rmtree(profile_path)
66    except Exception, e:
67      # The tests execution can continue, but this make them less stable.
68      logging.error("Error: Could not wipe the chrome profile directory (%s). \
69          This affects the stability of the tests. Continuing to run tests."
70          % e)
71    options = Options()
72    if enable_automatic_password_saving:
73      options.add_argument("enable-automatic-password-saving")
74    # Chrome path.
75    options.binary_location = chrome_path
76    # Chrome testing profile path.
77    options.add_argument("user-data-dir=%s" % profile_path)
78
79    # The webdriver. It's possible to choose the port the service is going to
80    # run on. If it's left to 0, a free port will be found.
81    self.driver = webdriver.Chrome(chromedriver_path, 0, options)
82    # The password internals window.
83    self.internals_window = self.driver.current_window_handle
84    # Password internals page.
85    self.internals_page = "chrome://password-manager-internals/"
86    # The Website window.
87    self.website_window = None
88    # The WebsiteTests list.
89    self.websitetests = []
90    # An xml tree filled with logins and passwords.
91    self.passwords_tree = ElementTree.parse(passwords_path).getroot()
92    # The enabled WebsiteTests list.
93    self.working_tests = []
94    # Map messages to the number of their appearance in the log.
95    self.message_count = dict()
96    self.message_count[MESSAGE_ASK] = 0
97    self.message_count[MESSAGE_SAVE] = 0
98    # The tests needs two tabs to work. A new tab is opened with the first
99    # GoTo. This is why we store here whether or not it's the first time to
100    # execute GoTo.
101    self.first_go_to = True
102
103  def AddWebsiteTest(self, websitetest, disabled=False):
104    """Adds a WebsiteTest to the testing Environment.
105
106    Args:
107      websitetest: The WebsiteTest instance to be added.
108      disabled: Whether test is disabled.
109    """
110    websitetest.environment = self
111    websitetest.driver = self.driver
112    if self.passwords_tree is not None:
113      if not websitetest.username:
114        username_tag = (
115            self.passwords_tree.find(
116                ".//*[@name='%s']/username" % websitetest.name))
117        if username_tag.text:
118          websitetest.username = username_tag.text
119      if not websitetest.password:
120        password_tag = (
121            self.passwords_tree.find(
122                ".//*[@name='%s']/password" % websitetest.name))
123        if password_tag.text:
124          websitetest.password = password_tag.text
125    self.websitetests.append(websitetest)
126    if not disabled:
127      self.working_tests.append(websitetest.name)
128
129  def RemoveAllPasswords(self):
130    """Removes all the stored passwords."""
131    logging.info("\nRemoveAllPasswords\n")
132    self.driver.get("chrome://settings/passwords")
133    self.driver.switch_to_frame("settings")
134    while True:
135      try:
136        self.driver.execute_script("document.querySelector('"
137          "#saved-passwords-list .row-delete-button').click()")
138        time.sleep(1)
139      except NoSuchElementException:
140        break
141      except WebDriverException:
142        break
143
144  def OpenTabAndGoToInternals(self, url):
145    """If there is no |self.website_window|, opens a new tab and navigates to
146    |url| in the new tab. Navigates to the passwords internals page in the
147    first tab. Raises an exception otherwise.
148
149    Args:
150      url: Url to go to in the new tab.
151
152    Raises:
153      Exception: An exception is raised if |self.website_window| already
154          exists.
155    """
156    if self.website_window:
157      raise Exception("Error: The window was already opened.")
158
159    self.driver.get("chrome://newtab")
160    # There is no straightforward way to open a new tab with chromedriver.
161    # One work-around is to go to a website, insert a link that is going
162    # to be opened in a new tab, click on it.
163    a = self.driver.execute_script(
164        "var a = document.createElement('a');"
165        "a.target = '_blank';"
166        "a.href = arguments[0];"
167        "a.innerHTML = '.';"
168        "document.body.appendChild(a);"
169        "return a;",
170        url)
171
172    a.click()
173    time.sleep(1)
174
175    self.website_window = self.driver.window_handles[-1]
176    self.driver.get(self.internals_page)
177    self.driver.switch_to_window(self.website_window)
178
179  def SwitchToInternals(self):
180    """Switches from the Website window to internals tab."""
181    self.driver.switch_to_window(self.internals_window)
182
183  def SwitchFromInternals(self):
184    """Switches from internals tab to the Website window."""
185    self.driver.switch_to_window(self.website_window)
186
187  def _DidMessageAppearUntilTimeout(self, log_message, timeout):
188    """Checks whether the save password prompt is shown.
189
190    Args:
191      log_message: Log message to look for in the password internals.
192      timeout: There is some delay between the login and the password
193          internals update. The method checks periodically during the first
194          |timeout| seconds if the internals page reports the prompt being
195          shown. If the prompt is not reported shown within the first
196          |timeout| seconds, it is considered not shown at all.
197
198    Returns:
199      True if the save password prompt is shown.
200      False otherwise.
201    """
202    log = self.driver.find_element_by_css_selector("#log-entries")
203    count = log.text.count(log_message)
204
205    if count > self.message_count[log_message]:
206      self.message_count[log_message] = count
207      return True
208    elif timeout > 0:
209      time.sleep(1)
210      return self._DidMessageAppearUntilTimeout(log_message, timeout - 1)
211    else:
212      return False
213
214  def CheckForNewMessage(self, log_message, message_should_show_up,
215                         error_message, timeout=3):
216    """Detects whether the save password prompt is shown.
217
218    Args:
219      log_message: Log message to look for in the password internals. The
220          only valid values are the constants MESSAGE_* defined at the
221          beginning of this file.
222      message_should_show_up: Whether or not the message is expected to be
223          shown.
224      error_message: Error message for the exception.
225      timeout: There is some delay between the login and the password
226          internals update. The method checks periodically during the first
227          |timeout| seconds if the internals page reports the prompt being
228          shown. If the prompt is not reported shown within the first
229          |timeout| seconds, it is considered not shown at all.
230
231    Raises:
232      Exception: An exception is raised in case the result does not match the
233          expectation
234    """
235    if (self._DidMessageAppearUntilTimeout(log_message, timeout) !=
236        message_should_show_up):
237      raise Exception(error_message)
238
239  def AllTests(self, prompt_test):
240    """Runs the tests on all the WebsiteTests.
241
242    Args:
243      prompt_test: If True, tests caring about showing the save-password
244          prompt are going to be run, otherwise tests which don't care about
245          the prompt are going to be run.
246
247    Raises:
248      Exception: An exception is raised if the tests fail.
249    """
250    if prompt_test:
251      self.PromptTestList(self.websitetests)
252    else:
253      self.TestList(self.websitetests)
254
255  def WorkingTests(self, prompt_test):
256    """Runs the tests on all the enabled WebsiteTests.
257
258    Args:
259      prompt_test: If True, tests caring about showing the save-password
260          prompt are going to be run, otherwise tests which don't care about
261          the prompt are going to be executed.
262
263    Raises:
264      Exception: An exception is raised if the tests fail.
265    """
266    self.Test(self.working_tests, prompt_test)
267
268  def Test(self, tests, prompt_test):
269    """Runs the tests on websites named in |tests|.
270
271    Args:
272      tests: A list of the names of the WebsiteTests that are going to be
273          tested.
274      prompt_test: If True, tests caring about showing the save-password
275          prompt are going to be run, otherwise tests which don't care about
276          the prompt are going to be executed.
277
278    Raises:
279      Exception: An exception is raised if the tests fail.
280    """
281    websitetests = []
282    for websitetest in self.websitetests:
283      if websitetest.name in tests:
284        websitetests.append(websitetest)
285
286    if prompt_test:
287      self.PromptTestList(websitetests)
288    else:
289      self.TestList(websitetests)
290
291  def TestList(self, websitetests):
292    """Runs the tests on the websites in |websitetests|.
293
294    Args:
295      websitetests: A list of WebsiteTests that are going to be tested.
296
297    Raises:
298      Exception: An exception is raised if the tests fail.
299    """
300    self.RemoveAllPasswords()
301
302    for websitetest in websitetests:
303      websitetest.WrongLoginTest()
304      websitetest.SuccessfulLoginTest()
305      websitetest.SuccessfulLoginWithAutofilledPasswordTest()
306
307    self.RemoveAllPasswords()
308    for websitetest in websitetests:
309      websitetest.SuccessfulLoginTest()
310
311  def PromptTestList(self, websitetests):
312    """Runs the prompt tests on the websites in |websitetests|.
313
314    Args:
315      websitetests: A list of WebsiteTests that are going to be tested.
316
317    Raises:
318      Exception: An exception is raised if the tests fail.
319    """
320    self.RemoveAllPasswords()
321
322    for websitetest in websitetests:
323      websitetest.PromptTest()
324
325  def Quit(self):
326    """Closes the tests."""
327    # Close the webdriver.
328    self.driver.quit()
329