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 |