| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """The testing Environment class.""" | 5 """The testing Environment class.""" |
| 6 | 6 |
| 7 import logging | 7 import os |
| 8 import shutil | 8 import shutil |
| 9 import sys | |
| 10 import time | 9 import time |
| 11 import traceback | |
| 12 from xml.etree import ElementTree | 10 from xml.etree import ElementTree |
| 13 from xml.sax.saxutils import escape | |
| 14 | |
| 15 sys.path.insert(0, '../../../../third_party/webdriver/pylib/') | |
| 16 | 11 |
| 17 from selenium import webdriver | 12 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 | 13 from selenium.webdriver.chrome.options import Options |
| 21 | 14 |
| 22 | 15 |
| 23 # Message strings to look for in chrome://password-manager-internals | 16 # Message strings to look for in chrome://password-manager-internals |
| 24 MESSAGE_ASK = "Message: Decision: ASK the user" | 17 MESSAGE_ASK = "Message: Decision: ASK the user" |
| 25 MESSAGE_SAVE = "Message: Decision: SAVE the password" | 18 MESSAGE_SAVE = "Message: Decision: SAVE the password" |
| 26 | 19 |
| 27 | 20 |
| 28 class TestResult: | 21 class TestResult: |
| 29 """Stores the information related to a test result. """ | 22 """Stores the information related to a test result. """ |
| 30 def __init__(self, name, test_type, successful, message): | 23 def __init__(self, name, test_type, successful, message): |
| 31 """Creates a new TestResult. | 24 """Creates a new TestResult. |
| 32 | 25 |
| 33 Args: | 26 Args: |
| 34 name: The tested website name. | 27 name: The tested website name. |
| 35 test_type: The test type. | 28 test_type: The test type. |
| 36 successful: Whether or not the test was successful. | 29 successful: Whether or not the test was successful. |
| 37 message: The error message of the test. | 30 message: The error message of the test. |
| 38 """ | 31 """ |
| 39 self.name = name | 32 self.name = name |
| 40 self.test_type = test_type | 33 self.test_type = test_type |
| 41 self.successful = successful | 34 self.successful = successful |
| 42 self.message = message | 35 self.message = message |
| 43 | 36 |
| 44 | 37 |
| 45 class Environment: | 38 class Environment: |
| 46 """Sets up the testing Environment. """ | 39 """Sets up the testing Environment. """ |
| 47 | 40 |
| 48 def __init__(self, chrome_path, chromedriver_path, profile_path, | 41 def __init__(self, chrome_path, chromedriver_path, profile_path, |
| 49 passwords_path, enable_automatic_password_saving, | 42 passwords_path, enable_automatic_password_saving): |
| 50 numeric_level=None, log_to_console=False, log_file=""): | |
| 51 """Creates a new testing Environment. | 43 """Creates a new testing Environment. |
| 52 | 44 |
| 53 Args: | 45 Args: |
| 54 chrome_path: The chrome binary file. | 46 chrome_path: The chrome binary file. |
| 55 chromedriver_path: The chromedriver binary file. | 47 chromedriver_path: The chromedriver binary file. |
| 56 profile_path: The chrome testing profile folder. | 48 profile_path: The chrome testing profile folder. |
| 57 passwords_path: The usernames and passwords file. | 49 passwords_path: The usernames and passwords file. |
| 58 enable_automatic_password_saving: If True, the passwords are going to be | 50 enable_automatic_password_saving: If True, the passwords are going to be |
| 59 saved without showing the prompt. | 51 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 | 52 |
| 65 Raises: | 53 Raises: |
| 66 Exception: An exception is raised if |profile_path| folder could not be | 54 Exception: An exception is raised if |profile_path| folder could not be |
| 67 removed. | 55 removed. |
| 68 """ | 56 """ |
| 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 | 57 |
| 86 # Cleaning the chrome testing profile folder. | 58 # Cleaning the chrome testing profile folder. |
| 87 try: | 59 if os.path.exists(profile_path): |
| 88 shutil.rmtree(profile_path) | 60 shutil.rmtree(profile_path) |
| 89 except Exception, e: | 61 options = Options() |
| 90 pass | 62 self.enable_automatic_password_saving = enable_automatic_password_saving |
| 91 # If |chrome_path| is not defined, this means that we are in the dashboard | 63 if enable_automatic_password_saving: |
| 92 # website, and we just need to get the list of all websites. In this case, | 64 options.add_argument("enable-automatic-password-saving") |
| 93 # we don't need to initilize the webdriver. | 65 # Chrome path. |
| 94 if chrome_path: | 66 options.binary_location = chrome_path |
| 95 options = Options() | 67 # Chrome testing profile path. |
| 96 self.enable_automatic_password_saving = enable_automatic_password_saving | 68 options.add_argument("user-data-dir=%s" % profile_path) |
| 97 if enable_automatic_password_saving: | |
| 98 options.add_argument("enable-automatic-password-saving") | |
| 99 # Chrome path. | |
| 100 options.binary_location = chrome_path | |
| 101 # Chrome testing profile path. | |
| 102 options.add_argument("user-data-dir=%s" % profile_path) | |
| 103 | 69 |
| 104 # The webdriver. It's possible to choose the port the service is going to | 70 # The webdriver. It's possible to choose the port the service is going to |
| 105 # run on. If it's left to 0, a free port will be found. | 71 # run on. If it's left to 0, a free port will be found. |
| 106 self.driver = webdriver.Chrome(chromedriver_path, 0, options) | 72 self.driver = webdriver.Chrome(chromedriver_path, 0, options) |
| 107 # The password internals window. | 73 # The password internals window. |
| 108 self.internals_window = self.driver.current_window_handle | 74 self.internals_window = self.driver.current_window_handle |
| 109 if passwords_path: | 75 if passwords_path: |
| 110 # An xml tree filled with logins and passwords. | 76 # An xml tree filled with logins and passwords. |
| 111 self.passwords_tree = ElementTree.parse(passwords_path).getroot() | 77 self.passwords_tree = ElementTree.parse(passwords_path).getroot() |
| 112 else: | 78 else: |
| 113 raise Exception("Error: |passwords_path| needs to be provided if" | 79 raise Exception("Error: |passwords_path| needs to be provided if" |
| 114 "|chrome_path| is provided, otherwise the tests could not be run") | 80 "|chrome_path| is provided, otherwise the tests could not be run") |
| 115 # Password internals page. | 81 # Password internals page. |
| 116 self.internals_page = "chrome://password-manager-internals/" | 82 self.internals_page = "chrome://password-manager-internals/" |
| 117 # The Website window. | 83 # The Website window. |
| 118 self.website_window = None | 84 self.website_window = None |
| 119 # The WebsiteTests list. | 85 # The WebsiteTests list. |
| 120 self.websitetests = [] | 86 self.websitetests = [] |
| 121 # Map messages to the number of their appearance in the log. | 87 # Map messages to the number of their appearance in the log. |
| 122 self.message_count = dict() | 88 self.message_count = { MESSAGE_ASK: 0, MESSAGE_SAVE: 0 } |
| 123 self.message_count[MESSAGE_ASK] = 0 | |
| 124 self.message_count[MESSAGE_SAVE] = 0 | |
| 125 # The tests needs two tabs to work. A new tab is opened with the first | 89 # The tests needs two tabs to work. A new tab is opened with the first |
| 126 # GoTo. This is why we store here whether or not it's the first time to | 90 # GoTo. This is why we store here whether or not it's the first time to |
| 127 # execute GoTo. | 91 # execute GoTo. |
| 128 self.first_go_to = True | 92 self.first_go_to = True |
| 129 # List of all tests results. | 93 # List of all tests results. |
| 130 self.tests_results = [] | 94 self.tests_results = [] |
| 131 | 95 |
| 132 def AddWebsiteTest(self, websitetest): | 96 def AddWebsiteTest(self, websitetest): |
| 133 """Adds a WebsiteTest to the testing Environment. | 97 """Adds a WebsiteTest to the testing Environment. |
| 134 | 98 |
| 99 TODO(vabr): Currently, this is only called at most once for each |
| 100 Environment instance. That is because to run all tests efficiently in |
| 101 parallel, each test gets its own process spawned (outside of Python). |
| 102 That makes sense, but then we should flatten the hierarchy of calls |
| 103 and consider making the 1:1 relation of environment to tests more |
| 104 explicit. |
| 105 |
| 135 Args: | 106 Args: |
| 136 websitetest: The WebsiteTest instance to be added. | 107 websitetest: The WebsiteTest instance to be added. |
| 137 """ | 108 """ |
| 138 websitetest.environment = self | 109 websitetest.environment = self |
| 139 if hasattr(self, "driver"): | 110 # TODO(vabr): Make driver a property of WebsiteTest. |
| 140 websitetest.driver = self.driver | 111 websitetest.driver = self.driver |
| 141 if hasattr(self, "passwords_tree") and self.passwords_tree is not None: | 112 if not websitetest.username: |
| 142 if not websitetest.username: | 113 username_tag = ( |
| 143 username_tag = ( | 114 self.passwords_tree.find( |
| 144 self.passwords_tree.find( | 115 ".//*[@name='%s']/username" % websitetest.name)) |
| 145 ".//*[@name='%s']/username" % websitetest.name)) | 116 websitetest.username = username_tag.text |
| 146 if username_tag.text: | 117 if not websitetest.password: |
| 147 websitetest.username = username_tag.text | 118 password_tag = ( |
| 148 if not websitetest.password: | 119 self.passwords_tree.find( |
| 149 password_tag = ( | 120 ".//*[@name='%s']/password" % websitetest.name)) |
| 150 self.passwords_tree.find( | 121 websitetest.password = password_tag.text |
| 151 ".//*[@name='%s']/password" % websitetest.name)) | |
| 152 if password_tag.text: | |
| 153 websitetest.password = password_tag.text | |
| 154 self.websitetests.append(websitetest) | 122 self.websitetests.append(websitetest) |
| 155 | 123 |
| 156 def ClearCache(self, clear_passwords): | 124 def ClearCache(self, clear_passwords): |
| 157 """Clear the browser cookies. If |clear_passwords| is true, clear all the | 125 """Clear the browser cookies. If |clear_passwords| is true, clear all the |
| 158 saved passwords too. | 126 saved passwords too. |
| 159 | 127 |
| 160 Args: | 128 Args: |
| 161 clear_passwords : Clear all the passwords if the bool value is true. | 129 clear_passwords : Clear all the passwords if the bool value is true. |
| 162 """ | 130 """ |
| 163 logging.info("\nClearCache\n") | |
| 164 self.driver.get("chrome://settings/clearBrowserData") | 131 self.driver.get("chrome://settings/clearBrowserData") |
| 165 self.driver.switch_to_frame("settings") | 132 self.driver.switch_to_frame("settings") |
| 166 script = ( | 133 script = ( |
| 167 "if (!document.querySelector('#delete-cookies-checkbox').checked)" | 134 "if (!document.querySelector('#delete-cookies-checkbox').checked)" |
| 168 " document.querySelector('#delete-cookies-checkbox').click();" | 135 " document.querySelector('#delete-cookies-checkbox').click();" |
| 169 ) | 136 ) |
| 170 negation = "" | 137 negation = "" |
| 171 if clear_passwords: | 138 if clear_passwords: |
| 172 negation = "!" | 139 negation = "!" |
| 173 script += ( | 140 script += ( |
| 174 "if (%sdocument.querySelector('#delete-passwords-checkbox').checked)" | 141 "if (%sdocument.querySelector('#delete-passwords-checkbox').checked)" |
| 175 " document.querySelector('#delete-passwords-checkbox').click();" | 142 " document.querySelector('#delete-passwords-checkbox').click();" |
| 176 % negation) | 143 % negation) |
| 177 script += "document.querySelector('#clear-browser-data-commit').click();" | 144 script += "document.querySelector('#clear-browser-data-commit').click();" |
| 178 self.driver.execute_script(script) | 145 self.driver.execute_script(script) |
| 179 time.sleep(2) | 146 time.sleep(2) |
| 180 # Every time we do something to the cache let's enable password saving. | 147 # Every time we do something to the cache let's enable password saving. |
| 181 # TODO(melandory): We should check why it's off in a first place. | 148 # TODO(melandory): We should check why it's off in a first place. |
| 182 # TODO(melandory): Investigate, maybe there is no need to enable it that | 149 # TODO(melandory): Investigate, maybe there is no need to enable it that |
| 183 # often. | 150 # often. |
| 184 self.EnablePasswordsSaving() | 151 self.EnablePasswordsSaving() |
| 185 | 152 |
| 186 def EnablePasswordsSaving(self): | 153 def EnablePasswordsSaving(self): |
| 187 logging.info("\nEnablePasswordSaving\n") | |
| 188 self.driver.get("chrome://settings") | 154 self.driver.get("chrome://settings") |
| 189 self.driver.switch_to_frame("settings") | 155 self.driver.switch_to_frame("settings") |
| 190 script = "document.getElementById('advanced-settings-expander').click();" | 156 script = "document.getElementById('advanced-settings-expander').click();" |
| 191 self.driver.execute_script(script) | 157 self.driver.execute_script(script) |
| 192 time.sleep(2) | 158 time.sleep(2) |
| 193 script = ( | 159 script = ( |
| 194 "if (!document.querySelector('#password-manager-enabled').checked)" | 160 "if (!document.querySelector('#password-manager-enabled').checked)" |
| 195 "{ document.querySelector('#password-manager-enabled').click();}") | 161 "{ document.querySelector('#password-manager-enabled').click();}") |
| 196 self.driver.execute_script(script) | 162 self.driver.execute_script(script) |
| 197 time.sleep(2) | 163 time.sleep(2) |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 287 Exception: An exception is raised in case the result does not match the | 253 Exception: An exception is raised in case the result does not match the |
| 288 expectation | 254 expectation |
| 289 """ | 255 """ |
| 290 if (self._DidMessageAppearUntilTimeout(log_message, timeout) != | 256 if (self._DidMessageAppearUntilTimeout(log_message, timeout) != |
| 291 message_should_show_up): | 257 message_should_show_up): |
| 292 raise Exception(error_message) | 258 raise Exception(error_message) |
| 293 | 259 |
| 294 def AllTests(self, prompt_test): | 260 def AllTests(self, prompt_test): |
| 295 """Runs the tests on all the WebsiteTests. | 261 """Runs the tests on all the WebsiteTests. |
| 296 | 262 |
| 263 TODO(vabr): Currently, "all tests" always means one. |
| 264 |
| 297 Args: | 265 Args: |
| 298 prompt_test: If True, tests caring about showing the save-password | 266 prompt_test: If True, tests caring about showing the save-password |
| 299 prompt are going to be run, otherwise tests which don't care about | 267 prompt are going to be run, otherwise tests which don't care about |
| 300 the prompt are going to be run. | 268 the prompt are going to be run. |
| 301 | 269 |
| 302 Raises: | 270 Raises: |
| 303 Exception: An exception is raised if the tests fail. | 271 Exception: An exception is raised if the tests fail. |
| 304 """ | 272 """ |
| 305 if prompt_test: | 273 if prompt_test: |
| 306 self.PromptTestList(self.websitetests) | 274 self.PromptTestList(self.websitetests) |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 380 except Exception as e: | 348 except Exception as e: |
| 381 successful = False | 349 successful = False |
| 382 error = e.message | 350 error = e.message |
| 383 self.tests_results.append(TestResult(websitetest.name, "prompt", | 351 self.tests_results.append(TestResult(websitetest.name, "prompt", |
| 384 successful, error)) | 352 successful, error)) |
| 385 | 353 |
| 386 def Quit(self): | 354 def Quit(self): |
| 387 """Closes the tests.""" | 355 """Closes the tests.""" |
| 388 # Close the webdriver. | 356 # Close the webdriver. |
| 389 self.driver.quit() | 357 self.driver.quit() |
| OLD | NEW |