OLD | NEW |
(Empty) | |
| 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 time |
| 10 |
| 11 from selenium import webdriver |
| 12 from selenium.common.exceptions import NoSuchElementException |
| 13 from selenium.common.exceptions import WebDriverException |
| 14 from selenium.webdriver.chrome.options import Options |
| 15 from xml.etree import ElementTree |
| 16 |
| 17 # Message strings to look for in chrome://password-manager-internals |
| 18 MESSAGE_ASK = "Message: Decision: ASK the user" |
| 19 MESSAGE_SAVE = "Message: Decision: SAVE the password" |
| 20 |
| 21 |
| 22 class 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() |
OLD | NEW |