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

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: Aargh! Fixity fix. Created 5 years, 8 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:
54 Exception: An exception is raised if |profile_path| folder could not be 43 Exception: An exception is raised if |profile_path| folder could not be
55 removed. 44 removed.
56 """ 45 """
57 46
58 # Cleaning the chrome testing profile folder. 47 # Cleaning the chrome testing profile folder.
59 if os.path.exists(profile_path): 48 if os.path.exists(profile_path):
60 shutil.rmtree(profile_path) 49 shutil.rmtree(profile_path)
50
61 options = Options() 51 options = Options()
62 self.enable_automatic_password_saving = enable_automatic_password_saving
63 if enable_automatic_password_saving: 52 if enable_automatic_password_saving:
64 options.add_argument("enable-automatic-password-saving") 53 options.add_argument("enable-automatic-password-saving")
65 # Chrome path. 54 # TODO(vabr): show_prompt is used in WebsiteTest for asserting that
55 # Chrome set-up corresponds to the test type. Remove that knowledge
56 # about Environment from the WebsiteTest.
57 self.show_prompt = not enable_automatic_password_saving
66 options.binary_location = chrome_path 58 options.binary_location = chrome_path
67 # Chrome testing profile path.
68 options.add_argument("user-data-dir=%s" % profile_path) 59 options.add_argument("user-data-dir=%s" % profile_path)
69 60
70 # The webdriver. It's possible to choose the port the service is going to 61 # 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. 62 # run on. If it's left to 0, a free port will be found.
72 self.driver = webdriver.Chrome(chromedriver_path, 0, options) 63 self.driver = webdriver.Chrome(chromedriver_path, 0, options)
73 # The password internals window. 64
65 # Password internals page tab/window handle.
74 self.internals_window = self.driver.current_window_handle 66 self.internals_window = self.driver.current_window_handle
75 if passwords_path: 67
76 # An xml tree filled with logins and passwords. 68 # An xml tree filled with logins and passwords.
77 self.passwords_tree = ElementTree.parse(passwords_path).getroot() 69 self.passwords_tree = ElementTree.parse(passwords_path).getroot()
msramek 2015/03/27 11:37:06 This can throw IOError or ParseError. Why don't we
vabr (Chromium) 2015/03/27 12:14:43 This is intentional. The tests cannot be run, and
78 else: 70
79 raise Exception("Error: |passwords_path| needs to be provided if" 71 self.website_window = self._OpenNewTab()
80 "|chrome_path| is provided, otherwise the tests could not be run") 72
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 = [] 73 self.websitetests = []
74
87 # Map messages to the number of their appearance in the log. 75 # Map messages to the number of their appearance in the log.
88 self.message_count = { MESSAGE_ASK: 0, MESSAGE_SAVE: 0 } 76 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 77
90 # GoTo. This is why we store here whether or not it's the first time to 78 # 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 = [] 79 self.tests_results = []
95 80
96 def AddWebsiteTest(self, websitetest): 81 def AddWebsiteTest(self, websitetest):
97 """Adds a WebsiteTest to the testing Environment. 82 """Adds a WebsiteTest to the testing Environment.
98 83
99 TODO(vabr): Currently, this is only called at most once for each 84 TODO(vabr): Currently, this is only called at most once for each
100 Environment instance. That is because to run all tests efficiently in 85 Environment instance. That is because to run all tests efficiently in
101 parallel, each test gets its own process spawned (outside of Python). 86 parallel, each test gets its own process spawned (outside of Python).
102 That makes sense, but then we should flatten the hierarchy of calls 87 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 88 and consider making the 1:1 relation of environment to tests more
104 explicit. 89 explicit.
105 90
106 Args: 91 Args:
107 websitetest: The WebsiteTest instance to be added. 92 websitetest: The WebsiteTest instance to be added.
108 """ 93 """
109 websitetest.environment = self 94 websitetest.environment = self
110 # TODO(vabr): Make driver a property of WebsiteTest. 95 # TODO(vabr): Make driver a property of WebsiteTest.
111 websitetest.driver = self.driver 96 websitetest.driver = self.driver
112 if not websitetest.username: 97 if not websitetest.username:
113 username_tag = ( 98 username_tag = (self.passwords_tree.find(
114 self.passwords_tree.find( 99 ".//*[@name='%s']/username" % websitetest.name))
115 ".//*[@name='%s']/username" % websitetest.name))
116 websitetest.username = username_tag.text 100 websitetest.username = username_tag.text
117 if not websitetest.password: 101 if not websitetest.password:
118 password_tag = ( 102 password_tag = (self.passwords_tree.find(
119 self.passwords_tree.find( 103 ".//*[@name='%s']/password" % websitetest.name))
120 ".//*[@name='%s']/password" % websitetest.name))
121 websitetest.password = password_tag.text 104 websitetest.password = password_tag.text
122 self.websitetests.append(websitetest) 105 self.websitetests.append(websitetest)
123 106
124 def ClearCache(self, clear_passwords): 107 def _ClearBrowserDataInit(self):
125 """Clear the browser cookies. If |clear_passwords| is true, clear all the 108 """Opens and resets the chrome://settings/clearBrowserData dialog.
126 saved passwords too. 109
110 It unchecks all checkboxes, and sets the time range to the "beginning of
111 time".
112 """
113
114 self.driver.get("chrome://settings/clearBrowserData")
115 self.driver.switch_to_frame("settings")
116
117 time_range_selector = "#clear-browser-data-time-period"
118 # TODO(vabr): Wait until time_range_selector is displayed instead.
119 time.sleep(2)
120 set_time_range = (
121 "var range = document.querySelector('{0}');".format(
122 time_range_selector) +
123 "range.value = 4" # 4 == the beginning of time
124 )
125 self.driver.execute_script(set_time_range)
126
127 all_cboxes_selector = (
128 "#clear-data-checkboxes > * > * >[type=\"checkbox\"]")
msramek 2015/03/27 11:37:07 Nit: Why not just #clear-data-checkboxes [type="c
vabr (Chromium) 2015/03/27 12:14:43 Agreed and done. (Learning CSS selectors on the fl
129 uncheck_all = (
130 "var checkboxes = document.querySelectorAll('{0}');".format(
131 all_cboxes_selector ) +
132 "for (var i = 0; i < checkboxes.length; ++i) {"
133 " if (checkboxes[i].checked)"
msramek 2015/03/27 11:37:06 Nit: checkboxes[i].checked = false
vabr (Chromium) 2015/03/27 12:14:43 Done.
134 " checkboxes[i].click();"
135 "}"
136 )
137 self.driver.execute_script(uncheck_all)
138
139 def _ClearDataForCheckbox(self, selector):
140 """Causes the data associated with |selector| to be cleared.
141
142 Opens chrome://settings/clearBrowserData, unchecks all checkboxes, then
143 checks the one described by |selector|, then clears the corresponding
144 browsing data for the full time range.
127 145
128 Args: 146 Args:
129 clear_passwords : Clear all the passwords if the bool value is true. 147 selector: describes the checkbox through which to delete the data.
130 """ 148 """
131 self.driver.get("chrome://settings/clearBrowserData") 149
132 self.driver.switch_to_frame("settings") 150 self._ClearBrowserDataInit()
133 script = ( 151 check_cookies_and_submit = (
134 "if (!document.querySelector('#delete-cookies-checkbox').checked)" 152 "var cbox = document.querySelector('{0}');".format(selector) +
135 " document.querySelector('#delete-cookies-checkbox').click();" 153 "if (!cbox.checked)"
msramek 2015/03/27 11:37:07 Nit: document.querySelector().checked = true
vabr (Chromium) 2015/03/27 12:14:43 Done.
154 " cbox.click();"
155 "document.querySelector('#clear-browser-data-commit').click();"
136 ) 156 )
137 negation = "" 157 self.driver.execute_script(check_cookies_and_submit)
138 if clear_passwords: 158
139 negation = "!" 159 def _EnablePasswordSaving(self):
140 script += ( 160 """Make sure that password manager is enabled."""
141 "if (%sdocument.querySelector('#delete-passwords-checkbox').checked)" 161
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. 162 # 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 163 # TODO(melandory): Investigate, maybe there is no need to enable it that
150 # often. 164 # often.
151 self.EnablePasswordsSaving()
152
153 def EnablePasswordsSaving(self):
154 self.driver.get("chrome://settings") 165 self.driver.get("chrome://settings")
155 self.driver.switch_to_frame("settings") 166 self.driver.switch_to_frame("settings")
156 script = "document.getElementById('advanced-settings-expander').click();" 167 script = "document.getElementById('advanced-settings-expander').click();"
157 self.driver.execute_script(script) 168 self.driver.execute_script(script)
169 # TODO(vabr): Wait until element is displayed instead.
158 time.sleep(2) 170 time.sleep(2)
159 script = ( 171 script = (
160 "if (!document.querySelector('#password-manager-enabled').checked)" 172 "if (!document.querySelector('#password-manager-enabled').checked)"
msramek 2015/03/27 11:37:06 Nit: As above.
vabr (Chromium) 2015/03/27 12:14:43 Done.
161 "{ document.querySelector('#password-manager-enabled').click();}") 173 "{ document.querySelector('#password-manager-enabled').click();}")
162 self.driver.execute_script(script) 174 self.driver.execute_script(script)
163 time.sleep(2) 175 time.sleep(2)
164 176
165 def OpenTabAndGoToInternals(self, url): 177 def _OpenNewTab(self):
166 """If there is no |self.website_window|, opens a new tab and navigates to 178 """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 179
170 Args: 180 Returns:
171 url: Url to go to in the new tab. 181 A handle to the new tab.
182 """
172 183
173 Raises: 184 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. 185 # 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 186 # 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. 187 # to be opened in a new tab, and click on it.
188 self.driver.get("about:blank")
184 a = self.driver.execute_script( 189 a = self.driver.execute_script(
185 "var a = document.createElement('a');" 190 "var a = document.createElement('a');"
186 "a.target = '_blank';" 191 "a.target = '_blank';"
187 "a.href = arguments[0];" 192 "a.href = 'about:blank';"
188 "a.innerHTML = '.';" 193 "a.innerHTML = '.';"
189 "document.body.appendChild(a);" 194 "document.body.appendChild(a);"
190 "return a;", 195 "return a;")
191 url) 196 a.click()
197 while number_old_tabs == len(self.driver.window_handles):
198 time.sleep(1) # Wait until the new tab is opened.
192 199
193 a.click() 200 new_tab = self.driver.window_handles[-1]
194 time.sleep(1) 201 self.driver.get(INTERNALS_PAGE_URL)
202 self.driver.switch_to_window(new_tab)
203 return new_tab
195 204
196 self.website_window = self.driver.window_handles[-1] 205 def _DidStringAppearUntilTimeout(self, strings, timeout):
197 self.driver.get(self.internals_page) 206 """Checks whether some of |strings| appeared in the current page.
198 self.driver.switch_to_window(self.website_window)
199 207
200 def SwitchToInternals(self): 208 Waits for up to |timeout| seconds until at least one of |strings| is
201 """Switches from the Website window to internals tab.""" 209 shown in the current page. Updates self.message_count with the current
202 self.driver.switch_to_window(self.internals_window) 210 number of occurrences of the shown string. Assumes that at most
203 211 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 212
211 Args: 213 Args:
212 log_message: Log message to look for in the password internals. 214 strings: A list of strings to look for.
213 timeout: There is some delay between the login and the password 215 timeout: If any such string does not appear within the first |timeout|
214 internals update. The method checks periodically during the first 216 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 217
219 Returns: 218 Returns:
220 True if the save password prompt is shown. 219 True if one of |strings| is observed until |timeout|, False otherwise.
221 False otherwise.
222 """ 220 """
221
223 log = self.driver.find_element_by_css_selector("#log-entries") 222 log = self.driver.find_element_by_css_selector("#log-entries")
224 count = log.text.count(log_message) 223 while timeout:
msramek 2015/03/27 11:37:06 Why don't we just sleep for |timeout| and then cou
vabr (Chromium) 2015/03/27 12:14:43 No, but in the case when they appear, they mostly
224 for string in strings:
225 count = log.text.count(string)
226 if count > self.message_count[string]:
227 self.message_count[string] = count
228 return True
229 time.sleep(1)
230 timeout -= 1
231 return False
225 232
226 if count > self.message_count[log_message]: 233 def CheckForNewString(self, strings, string_should_show_up, error):
227 self.message_count[log_message] = count 234 """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 235
235 def CheckForNewMessage(self, log_message, message_should_show_up, 236 Switches to the internals page and looks for a new instances of |strings|
236 error_message, timeout=15): 237 being shown up there. It checks that |string_should_show_up| is true if
237 """Detects whether the save password prompt is shown. 238 and only if at leas one string from |strings| shows up, and throws an
239 Exception if that check fails.
238 240
239 Args: 241 Args:
240 log_message: Log message to look for in the password internals. The 242 strings: A list of strings to look for in the internals page.
241 only valid values are the constants MESSAGE_* defined at the 243 string_should_show_up: Whether or not at least one string from |strings|
242 beginning of this file. 244 is expected to be shown.
243 message_should_show_up: Whether or not the message is expected to be 245 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 246
252 Raises: 247 Raises:
253 Exception: An exception is raised in case the result does not match the 248 Exception: (See above.)
254 expectation
255 """ 249 """
256 if (self._DidMessageAppearUntilTimeout(log_message, timeout) !=
257 message_should_show_up):
258 raise Exception(error_message)
259 250
260 def AllTests(self, prompt_test): 251 self.driver.switch_to_window(self.internals_window)
261 """Runs the tests on all the WebsiteTests. 252 try:
253 if (self._DidStringAppearUntilTimeout(strings, 15) !=
254 string_should_show_up):
255 raise Exception(error)
256 finally:
257 self.driver.switch_to_window(self.website_window)
262 258
263 TODO(vabr): Currently, "all tests" always means one. 259 def DeleteCookies(self):
260 """Deletes cookies via the settings page."""
261
262 self._ClearDataForCheckbox("#delete-cookies-checkbox")
263
264 def RunTestsOnSites(self, test_type):
265 """Runs the specified test on the known websites.
266
267 Also saves the test results in the environment. Note that test types
268 differ in their requirements on whether the save password prompt
269 should be displayed. Make sure that such requirements are consistent
270 with the enable_automatic_password_saving argument passed to |self|
271 on construction.
264 272
265 Args: 273 Args:
266 prompt_test: If True, tests caring about showing the save-password 274 test_type: A test identifier understood by WebsiteTest.run_test().
267 prompt are going to be run, otherwise tests which don't care about 275 """
268 the prompt are going to be run.
269 276
270 Raises: 277 self.DeleteCookies()
271 Exception: An exception is raised if the tests fail. 278 self._ClearDataForCheckbox("#delete-passwords-checkbox")
272 """ 279 self._EnablePasswordSaving()
273 if prompt_test:
274 self.PromptTestList(self.websitetests)
275 else:
276 self.TestList(self.websitetests)
277 280
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: 281 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 282 successful = True
314 error = "" 283 error = ""
315 try: 284 try:
316 websitetest.was_run = True 285 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: 286 except Exception as e:
325 successful = False 287 successful = False
326 error = e.message 288 error = e.message
327 self.tests_results.append(TestResult(websitetest.name, "normal", 289 self.tests_results.append(
328 successful, error)) 290 (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 291
354 def Quit(self): 292 def Quit(self):
355 """Closes the tests.""" 293 """Shuts down the driver."""
356 # Close the webdriver. 294
357 self.driver.quit() 295 self.driver.quit()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698