Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(150)

Side by Side Diff: components/test/data/password_manager/automated_tests/environment.py

Issue 1026833003: [Password manager Python tests] Re-arrange tests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Comments addressed Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
7 It holds the WebsiteTest instances, provides them with credentials,
8 provides clean browser environment, runs the tests, and gathers the
9 results.
10 """
6 11
7 import os 12 import os
8 import shutil 13 import shutil
9 import time 14 import time
10 from xml.etree import ElementTree 15 from xml.etree import ElementTree
11 16
12 from selenium import webdriver 17 from selenium import webdriver
13 from selenium.webdriver.chrome.options import Options 18 from selenium.webdriver.chrome.options import Options
14 19
15 20
16 # Message strings to look for in chrome://password-manager-internals 21 # Message strings to look for in chrome://password-manager-internals.
17 MESSAGE_ASK = "Message: Decision: ASK the user" 22 MESSAGE_ASK = "Message: Decision: ASK the user"
18 MESSAGE_SAVE = "Message: Decision: SAVE the password" 23 MESSAGE_SAVE = "Message: Decision: SAVE the password"
19 24
20 25 INTERNALS_PAGE_URL = "chrome://password-manager-internals/"
21 class TestResult:
22 """Stores the information related to a test result. """
23 def __init__(self, name, test_type, successful, message):
24 """Creates a new TestResult.
25
26 Args:
27 name: The tested website name.
28 test_type: The test type.
29 successful: Whether or not the test was successful.
30 message: The error message of the test.
31 """
32 self.name = name
33 self.test_type = test_type
34 self.successful = successful
35 self.message = message
36
37 26
38 class Environment: 27 class Environment:
39 """Sets up the testing Environment. """ 28 """Sets up the testing Environment. """
40 29
41 def __init__(self, chrome_path, chromedriver_path, profile_path, 30 def __init__(self, chrome_path, chromedriver_path, profile_path,
42 passwords_path, enable_automatic_password_saving): 31 passwords_path, enable_automatic_password_saving):
43 """Creates a new testing Environment. 32 """Creates a new testing Environment, starts Chromedriver.
44 33
45 Args: 34 Args:
46 chrome_path: The chrome binary file. 35 chrome_path: The chrome binary file.
47 chromedriver_path: The chromedriver binary file. 36 chromedriver_path: The chromedriver binary file.
48 profile_path: The chrome testing profile folder. 37 profile_path: The chrome testing profile folder.
49 passwords_path: The usernames and passwords file. 38 passwords_path: The usernames and passwords file.
50 enable_automatic_password_saving: If True, the passwords are going to be 39 enable_automatic_password_saving: If True, the passwords are going to be
51 saved without showing the prompt. 40 saved without showing the prompt.
52 41
53 Raises: 42 Raises:
43 IOError: When the passwords file cannot be accessed.
44 ParseError: When the passwords file cannot be parsed.
54 Exception: An exception is raised if |profile_path| folder could not be 45 Exception: An exception is raised if |profile_path| folder could not be
55 removed. 46 removed.
56 """ 47 """
57 48
58 # Cleaning the chrome testing profile folder. 49 # Cleaning the chrome testing profile folder.
59 if os.path.exists(profile_path): 50 if os.path.exists(profile_path):
60 shutil.rmtree(profile_path) 51 shutil.rmtree(profile_path)
52
61 options = Options() 53 options = Options()
62 self.enable_automatic_password_saving = enable_automatic_password_saving
63 if enable_automatic_password_saving: 54 if enable_automatic_password_saving:
64 options.add_argument("enable-automatic-password-saving") 55 options.add_argument("enable-automatic-password-saving")
65 # Chrome path. 56 # TODO(vabr): show_prompt is used in WebsiteTest for asserting that
57 # Chrome set-up corresponds to the test type. Remove that knowledge
58 # about Environment from the WebsiteTest.
59 self.show_prompt = not enable_automatic_password_saving
66 options.binary_location = chrome_path 60 options.binary_location = chrome_path
67 # Chrome testing profile path.
68 options.add_argument("user-data-dir=%s" % profile_path) 61 options.add_argument("user-data-dir=%s" % profile_path)
69 62
70 # The webdriver. It's possible to choose the port the service is going to 63 # The webdriver. It's possible to choose the port the service is going to
71 # run on. If it's left to 0, a free port will be found. 64 # run on. If it's left to 0, a free port will be found.
72 self.driver = webdriver.Chrome(chromedriver_path, 0, options) 65 self.driver = webdriver.Chrome(chromedriver_path, 0, options)
73 # The password internals window. 66
67 # Password internals page tab/window handle.
74 self.internals_window = self.driver.current_window_handle 68 self.internals_window = self.driver.current_window_handle
75 if passwords_path: 69
76 # An xml tree filled with logins and passwords. 70 # An xml tree filled with logins and passwords.
77 self.passwords_tree = ElementTree.parse(passwords_path).getroot() 71 self.passwords_tree = ElementTree.parse(passwords_path).getroot()
78 else: 72
79 raise Exception("Error: |passwords_path| needs to be provided if" 73 self.website_window = self._OpenNewTab()
80 "|chrome_path| is provided, otherwise the tests could not be run") 74
81 # Password internals page.
82 self.internals_page = "chrome://password-manager-internals/"
83 # The Website window.
84 self.website_window = None
85 # The WebsiteTests list.
86 self.websitetests = [] 75 self.websitetests = []
76
87 # Map messages to the number of their appearance in the log. 77 # Map messages to the number of their appearance in the log.
88 self.message_count = { MESSAGE_ASK: 0, MESSAGE_SAVE: 0 } 78 self.message_count = { MESSAGE_ASK: 0, MESSAGE_SAVE: 0 }
89 # The tests needs two tabs to work. A new tab is opened with the first 79
90 # GoTo. This is why we store here whether or not it's the first time to 80 # A list of (test_name, test_type, test_success, failure_log).
91 # execute GoTo.
92 self.first_go_to = True
93 # List of all tests results.
94 self.tests_results = [] 81 self.tests_results = []
95 82
96 def AddWebsiteTest(self, websitetest): 83 def AddWebsiteTest(self, websitetest):
97 """Adds a WebsiteTest to the testing Environment. 84 """Adds a WebsiteTest to the testing Environment.
98 85
99 TODO(vabr): Currently, this is only called at most once for each 86 TODO(vabr): Currently, this is only called at most once for each
100 Environment instance. That is because to run all tests efficiently in 87 Environment instance. That is because to run all tests efficiently in
101 parallel, each test gets its own process spawned (outside of Python). 88 parallel, each test gets its own process spawned (outside of Python).
102 That makes sense, but then we should flatten the hierarchy of calls 89 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 90 and consider making the 1:1 relation of environment to tests more
104 explicit. 91 explicit.
105 92
106 Args: 93 Args:
107 websitetest: The WebsiteTest instance to be added. 94 websitetest: The WebsiteTest instance to be added.
108 """ 95 """
109 websitetest.environment = self 96 websitetest.environment = self
110 # TODO(vabr): Make driver a property of WebsiteTest. 97 # TODO(vabr): Make driver a property of WebsiteTest.
111 websitetest.driver = self.driver 98 websitetest.driver = self.driver
112 if not websitetest.username: 99 if not websitetest.username:
113 username_tag = ( 100 username_tag = (self.passwords_tree.find(
114 self.passwords_tree.find( 101 ".//*[@name='%s']/username" % websitetest.name))
115 ".//*[@name='%s']/username" % websitetest.name))
116 websitetest.username = username_tag.text 102 websitetest.username = username_tag.text
117 if not websitetest.password: 103 if not websitetest.password:
118 password_tag = ( 104 password_tag = (self.passwords_tree.find(
119 self.passwords_tree.find( 105 ".//*[@name='%s']/password" % websitetest.name))
120 ".//*[@name='%s']/password" % websitetest.name))
121 websitetest.password = password_tag.text 106 websitetest.password = password_tag.text
122 self.websitetests.append(websitetest) 107 self.websitetests.append(websitetest)
123 108
124 def ClearCache(self, clear_passwords): 109 def _ClearBrowserDataInit(self):
125 """Clear the browser cookies. If |clear_passwords| is true, clear all the 110 """Opens and resets the chrome://settings/clearBrowserData dialog.
126 saved passwords too. 111
112 It unchecks all checkboxes, and sets the time range to the "beginning of
113 time".
114 """
115
116 self.driver.get("chrome://settings/clearBrowserData")
117 self.driver.switch_to_frame("settings")
118
119 time_range_selector = "#clear-browser-data-time-period"
120 # TODO(vabr): Wait until time_range_selector is displayed instead.
121 time.sleep(2)
122 set_time_range = (
123 "var range = document.querySelector('{0}');".format(
124 time_range_selector) +
125 "range.value = 4" # 4 == the beginning of time
126 )
127 self.driver.execute_script(set_time_range)
128
129 all_cboxes_selector = (
130 "#clear-data-checkboxes [type=\"checkbox\"]")
131 uncheck_all = (
132 "var checkboxes = document.querySelectorAll('{0}');".format(
133 all_cboxes_selector ) +
134 "for (var i = 0; i < checkboxes.length; ++i) {"
135 " checkboxes[i].checked = false;"
136 "}"
137 )
138 self.driver.execute_script(uncheck_all)
139
140 def _ClearDataForCheckbox(self, selector):
141 """Causes the data associated with |selector| to be cleared.
142
143 Opens chrome://settings/clearBrowserData, unchecks all checkboxes, then
144 checks the one described by |selector|, then clears the corresponding
145 browsing data for the full time range.
127 146
128 Args: 147 Args:
129 clear_passwords : Clear all the passwords if the bool value is true. 148 selector: describes the checkbox through which to delete the data.
130 """ 149 """
131 self.driver.get("chrome://settings/clearBrowserData") 150
132 self.driver.switch_to_frame("settings") 151 self._ClearBrowserDataInit()
133 script = ( 152 check_cookies_and_submit = (
134 "if (!document.querySelector('#delete-cookies-checkbox').checked)" 153 "document.querySelector('{0}').checked = true;".format(selector) +
135 " document.querySelector('#delete-cookies-checkbox').click();" 154 "document.querySelector('#clear-browser-data-commit').click();"
136 ) 155 )
137 negation = "" 156 self.driver.execute_script(check_cookies_and_submit)
138 if clear_passwords: 157
139 negation = "!" 158 def _EnablePasswordSaving(self):
140 script += ( 159 """Make sure that password manager is enabled."""
141 "if (%sdocument.querySelector('#delete-passwords-checkbox').checked)" 160
142 " document.querySelector('#delete-passwords-checkbox').click();"
143 % negation)
144 script += "document.querySelector('#clear-browser-data-commit').click();"
145 self.driver.execute_script(script)
146 time.sleep(2)
147 # Every time we do something to the cache let's enable password saving.
148 # TODO(melandory): We should check why it's off in a first place. 161 # TODO(melandory): We should check why it's off in a first place.
149 # TODO(melandory): Investigate, maybe there is no need to enable it that 162 # TODO(melandory): Investigate, maybe there is no need to enable it that
150 # often. 163 # often.
151 self.EnablePasswordsSaving()
152
153 def EnablePasswordsSaving(self):
154 self.driver.get("chrome://settings") 164 self.driver.get("chrome://settings")
155 self.driver.switch_to_frame("settings") 165 self.driver.switch_to_frame("settings")
156 script = "document.getElementById('advanced-settings-expander').click();" 166 script = "document.getElementById('advanced-settings-expander').click();"
157 self.driver.execute_script(script) 167 self.driver.execute_script(script)
168 # TODO(vabr): Wait until element is displayed instead.
158 time.sleep(2) 169 time.sleep(2)
159 script = ( 170 script = (
160 "if (!document.querySelector('#password-manager-enabled').checked)" 171 "document.querySelector('#password-manager-enabled').checked = true;")
161 "{ document.querySelector('#password-manager-enabled').click();}")
162 self.driver.execute_script(script) 172 self.driver.execute_script(script)
163 time.sleep(2) 173 time.sleep(2)
164 174
165 def OpenTabAndGoToInternals(self, url): 175 def _OpenNewTab(self):
166 """If there is no |self.website_window|, opens a new tab and navigates to 176 """Open a new tab, and loads the internals page in the old tab.
167 |url| in the new tab. Navigates to the passwords internals page in the
168 first tab. Raises an exception otherwise.
169 177
170 Args: 178 Returns:
171 url: Url to go to in the new tab. 179 A handle to the new tab.
180 """
172 181
173 Raises: 182 number_old_tabs = len(self.driver.window_handles)
174 Exception: An exception is raised if |self.website_window| already
175 exists.
176 """
177 if self.website_window:
178 raise Exception("Error: The window was already opened.")
179
180 self.driver.get("chrome://newtab")
181 # There is no straightforward way to open a new tab with chromedriver. 183 # There is no straightforward way to open a new tab with chromedriver.
182 # One work-around is to go to a website, insert a link that is going 184 # One work-around is to go to a website, insert a link that is going
183 # to be opened in a new tab, click on it. 185 # to be opened in a new tab, and click on it.
186 self.driver.get("about:blank")
184 a = self.driver.execute_script( 187 a = self.driver.execute_script(
185 "var a = document.createElement('a');" 188 "var a = document.createElement('a');"
186 "a.target = '_blank';" 189 "a.target = '_blank';"
187 "a.href = arguments[0];" 190 "a.href = 'about:blank';"
188 "a.innerHTML = '.';" 191 "a.innerHTML = '.';"
189 "document.body.appendChild(a);" 192 "document.body.appendChild(a);"
190 "return a;", 193 "return a;")
191 url) 194 a.click()
195 while number_old_tabs == len(self.driver.window_handles):
196 time.sleep(1) # Wait until the new tab is opened.
192 197
193 a.click() 198 new_tab = self.driver.window_handles[-1]
194 time.sleep(1) 199 self.driver.get(INTERNALS_PAGE_URL)
200 self.driver.switch_to_window(new_tab)
201 return new_tab
195 202
196 self.website_window = self.driver.window_handles[-1] 203 def _DidStringAppearUntilTimeout(self, strings, timeout):
197 self.driver.get(self.internals_page) 204 """Checks whether some of |strings| appeared in the current page.
198 self.driver.switch_to_window(self.website_window)
199 205
200 def SwitchToInternals(self): 206 Waits for up to |timeout| seconds until at least one of |strings| is
201 """Switches from the Website window to internals tab.""" 207 shown in the current page. Updates self.message_count with the current
202 self.driver.switch_to_window(self.internals_window) 208 number of occurrences of the shown string. Assumes that at most
203 209 one of |strings| is newly shown.
204 def SwitchFromInternals(self):
205 """Switches from internals tab to the Website window."""
206 self.driver.switch_to_window(self.website_window)
207
208 def _DidMessageAppearUntilTimeout(self, log_message, timeout):
209 """Checks whether the save password prompt is shown.
210 210
211 Args: 211 Args:
212 log_message: Log message to look for in the password internals. 212 strings: A list of strings to look for.
213 timeout: There is some delay between the login and the password 213 timeout: If any such string does not appear within the first |timeout|
214 internals update. The method checks periodically during the first 214 seconds, it is considered a no-show.
215 |timeout| seconds if the internals page reports the prompt being
216 shown. If the prompt is not reported shown within the first
217 |timeout| seconds, it is considered not shown at all.
218 215
219 Returns: 216 Returns:
220 True if the save password prompt is shown. 217 True if one of |strings| is observed until |timeout|, False otherwise.
221 False otherwise.
222 """ 218 """
219
223 log = self.driver.find_element_by_css_selector("#log-entries") 220 log = self.driver.find_element_by_css_selector("#log-entries")
224 count = log.text.count(log_message) 221 while timeout:
222 for string in strings:
223 count = log.text.count(string)
224 if count > self.message_count[string]:
225 self.message_count[string] = count
226 return True
227 time.sleep(1)
228 timeout -= 1
229 return False
225 230
226 if count > self.message_count[log_message]: 231 def CheckForNewString(self, strings, string_should_show_up, error):
227 self.message_count[log_message] = count 232 """Checks that |strings| show up on the internals page as it should.
228 return True
229 elif timeout > 0:
230 time.sleep(1)
231 return self._DidMessageAppearUntilTimeout(log_message, timeout - 1)
232 else:
233 return False
234 233
235 def CheckForNewMessage(self, log_message, message_should_show_up, 234 Switches to the internals page and looks for a new instances of |strings|
236 error_message, timeout=15): 235 being shown up there. It checks that |string_should_show_up| is true if
237 """Detects whether the save password prompt is shown. 236 and only if at leas one string from |strings| shows up, and throws an
237 Exception if that check fails.
238 238
239 Args: 239 Args:
240 log_message: Log message to look for in the password internals. The 240 strings: A list of strings to look for in the internals page.
241 only valid values are the constants MESSAGE_* defined at the 241 string_should_show_up: Whether or not at least one string from |strings|
242 beginning of this file. 242 is expected to be shown.
243 message_should_show_up: Whether or not the message is expected to be 243 error: Error message for the exception.
244 shown.
245 error_message: Error message for the exception.
246 timeout: There is some delay between the login and the password
247 internals update. The method checks periodically during the first
248 |timeout| seconds if the internals page reports the prompt being
249 shown. If the prompt is not reported shown within the first
250 |timeout| seconds, it is considered not shown at all.
251 244
252 Raises: 245 Raises:
253 Exception: An exception is raised in case the result does not match the 246 Exception: (See above.)
254 expectation
255 """ 247 """
256 if (self._DidMessageAppearUntilTimeout(log_message, timeout) !=
257 message_should_show_up):
258 raise Exception(error_message)
259 248
260 def AllTests(self, prompt_test): 249 self.driver.switch_to_window(self.internals_window)
261 """Runs the tests on all the WebsiteTests. 250 try:
251 if (self._DidStringAppearUntilTimeout(strings, 15) !=
252 string_should_show_up):
253 raise Exception(error)
254 finally:
255 self.driver.switch_to_window(self.website_window)
262 256
263 TODO(vabr): Currently, "all tests" always means one. 257 def DeleteCookies(self):
258 """Deletes cookies via the settings page."""
259
260 self._ClearDataForCheckbox("#delete-cookies-checkbox")
261
262 def RunTestsOnSites(self, test_type):
263 """Runs the specified test on the known websites.
264
265 Also saves the test results in the environment. Note that test types
266 differ in their requirements on whether the save password prompt
267 should be displayed. Make sure that such requirements are consistent
268 with the enable_automatic_password_saving argument passed to |self|
269 on construction.
264 270
265 Args: 271 Args:
266 prompt_test: If True, tests caring about showing the save-password 272 test_type: A test identifier understood by WebsiteTest.run_test().
267 prompt are going to be run, otherwise tests which don't care about 273 """
268 the prompt are going to be run.
269 274
270 Raises: 275 self.DeleteCookies()
271 Exception: An exception is raised if the tests fail. 276 self._ClearDataForCheckbox("#delete-passwords-checkbox")
272 """ 277 self._EnablePasswordSaving()
273 if prompt_test:
274 self.PromptTestList(self.websitetests)
275 else:
276 self.TestList(self.websitetests)
277 278
278 def Test(self, tests, prompt_test):
279 """Runs the tests on websites named in |tests|.
280
281 Args:
282 tests: A list of the names of the WebsiteTests that are going to be
283 tested.
284 prompt_test: If True, tests caring about showing the save-password
285 prompt are going to be run, otherwise tests which don't care about
286 the prompt are going to be executed.
287
288 Raises:
289 Exception: An exception is raised if the tests fail.
290 """
291 websitetests = []
292 for websitetest in self.websitetests: 279 for websitetest in self.websitetests:
293 if websitetest.name in tests:
294 websitetests.append(websitetest)
295
296 if prompt_test:
297 self.PromptTestList(websitetests)
298 else:
299 self.TestList(websitetests)
300
301 def TestList(self, websitetests):
302 """Runs the tests on the websites in |websitetests|.
303
304 Args:
305 websitetests: A list of WebsiteTests that are going to be tested.
306
307 Raises:
308 Exception: An exception is raised if the tests fail.
309 """
310 self.ClearCache(True)
311
312 for websitetest in websitetests:
313 successful = True 280 successful = True
314 error = "" 281 error = ""
315 try: 282 try:
316 websitetest.was_run = True 283 websitetest.RunTest(test_type)
317 websitetest.WrongLoginTest()
318 websitetest.SuccessfulLoginTest()
319 self.ClearCache(False)
320 websitetest.SuccessfulLoginWithAutofilledPasswordTest()
321 self.ClearCache(True)
322 websitetest.SuccessfulLoginTest()
323 self.ClearCache(True)
324 except Exception as e: 284 except Exception as e:
325 successful = False 285 successful = False
326 error = e.message 286 error = e.message
327 self.tests_results.append(TestResult(websitetest.name, "normal", 287 self.tests_results.append(
328 successful, error)) 288 (websitetest.name, test_type, successful, error))
329
330
331 def PromptTestList(self, websitetests):
332 """Runs the prompt tests on the websites in |websitetests|.
333
334 Args:
335 websitetests: A list of WebsiteTests that are going to be tested.
336
337 Raises:
338 Exception: An exception is raised if the tests fail.
339 """
340 self.ClearCache(True)
341
342 for websitetest in websitetests:
343 successful = True
344 error = ""
345 try:
346 websitetest.was_run = True
347 websitetest.PromptTest()
348 except Exception as e:
349 successful = False
350 error = e.message
351 self.tests_results.append(TestResult(websitetest.name, "prompt",
352 successful, error))
353 289
354 def Quit(self): 290 def Quit(self):
355 """Closes the tests.""" 291 """Shuts down the driver."""
356 # Close the webdriver. 292
357 self.driver.quit() 293 self.driver.quit()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698