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