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

Side by Side Diff: components/proximity_auth/e2e_test/cros.py

Issue 1004283002: Add end-to-end testing tool for Smart Lock. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: 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
(Empty)
1 # Copyright 2015 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 import logging
6 import os
7 import subprocess
8 import sys
9 import time
10
11 # Add the telemetry directory to Python's search paths.
12 curr_dir = os.path.dirname(os.path.realpath(__file__))
Ilya Sherman 2015/03/24 01:25:13 nit: s/curr_dir/current_directory or at least curr
Tim Song 2015/03/25 23:08:43 Done.
13 telemetry_dir = os.path.realpath(
14 os.path.join(curr_dir, '..', '..', '..', 'tools', 'telemetry'))
15 if telemetry_dir not in sys.path:
16 sys.path.append(telemetry_dir)
17
18 from telemetry.core import browser_options
19 from telemetry.core import browser_finder
20 from telemetry.core import extension_to_load
21 from telemetry.core import exceptions
22 from telemetry.core import util
23 from telemetry.core.platform import cros_interface
24
25 logger = logging.getLogger('proximity_auth.%s' % __name__)
26
27 class AccountPickerScreen(object):
28 """ Wrapper for the ChromeOS account picker screen used on both the lock
29 screen and signin screen.
Ilya Sherman 2015/03/24 01:25:14 nit: The first line is supposed to fit on a single
Tim Song 2015/03/25 23:08:43 Done.
30 """
31
32 class AuthType:
33 """ The authentication type expected for a user pod.
34 """
Ilya Sherman 2015/03/24 01:25:13 nit: No need to wrap the closing """ (applies thro
Tim Song 2015/03/25 23:08:43 Done.
35 OFFLINE_PASSWORD = 0
36 ONLINE_SIGN_IN = 1
37 NUMERIC_PIN = 2
38 USER_CLICK = 3
39 EXPAND_THEN_USER_CLICK = 4
40 FORCE_OFFLINE_PASSWORD = 5
41
42 class SmartLockState:
43 """ The state of the Smart Lock icon on a user pod.
44 """
45 NOT_SHOWN = 'not_shown'
46 AUTHENTICATED = 'authenticated'
47 LOCKED = 'locked'
48 HARD_LOCKED = 'hardlocked'
49 TO_BE_ACTIVATED = 'to_be_activated'
50 SPINNER = 'spinner'
51
52 def __init__(self, oobe, chromeos):
53 """
54 Args:
55 oobe: Inspector page of the OOBE WebContents.
56 chromeos: The parent Chrome wrapper.
57 """
58 self._oobe = oobe
59 self._chromeos = chromeos
60
61 @property
62 def is_lockscreen(self):
63 return self._oobe.EvaluateJavaScript(
Ilya Sherman 2015/03/24 01:25:13 nit: Extra space after "return"
Tim Song 2015/03/25 23:08:43 Done.
64 '!document.getElementById("sign-out-user-item").hidden')
65
66 @property
67 def auth_type(self):
68 return self._oobe.EvaluateJavaScript(
69 'document.getElementById("pod-row").pods[0].authType')
Ilya Sherman 2015/03/24 01:25:14 nit: Maybe define a string constant for "document.
Tim Song 2015/03/25 23:08:43 Done.
70
71 @property
72 def smart_lock_state(self):
73 icon_shown = self._oobe.EvaluateJavaScript(
74 '!document.getElementById("pod-row").pods[0].customIconElement.hidden')
75 if not icon_shown:
76 return self.SmartLockState.NOT_SHOWN
77 class_list_dict = self._oobe.EvaluateJavaScript(
78 'document.getElementById("pod-row").pods[0].customIconElement'
79 '.querySelector(".custom-icon").classList')
80 class_list = [v for k,v in class_list_dict.items() if k != 'length']
81
82 if 'custom-icon-unlocked' in class_list:
83 return self.SmartLockState.AUTHENTICATED
84 if 'custom-icon-locked' in class_list:
85 return self.SmartLockState.LOCKED
86 if 'custom-icon-hardlocked' in class_list:
87 return self.SmartLockState.HARD_LOCKED
88 if 'custom-icon-locked-to-be-activated' in class_list:
89 return self.SmartLockState.TO_BE_ACTIVATED
90 if 'custom-icon-spinner' in class_list:
91 return self.SmartLockState.SPINNER
92
93 def WaitForSmartLockState(self, state, wait_time_secs=60):
94 """ Waits for the Smart Lock icon to reach the given state.
Ilya Sherman 2015/03/24 01:25:13 nit: Please leave a blank line after this one. (Ap
Tim Song 2015/03/25 23:08:43 Done.
95 Note: The first user pod on the screen is used.
Ilya Sherman 2015/03/24 01:25:13 nit: This note seems to apply to most of the metho
Tim Song 2015/03/25 23:08:44 Done.
96
97 Args:
98 state: A value in AccountPickerScreen.SmartLockState
99 wait_time_secs: The time to wait
100 Returns:
101 True if the state is reached within the wait time, else False.
102 """
103 try:
104 util.WaitFor(lambda: self.smart_lock_state == state, wait_time_secs)
105 return True
106 except exceptions.TimeoutException:
107 return False
108
109 def EnterPassword(self):
110 """ Enters the password to unlock or sign-in.
111 Note: The first user pod on the screen is used.
Ilya Sherman 2015/03/24 01:25:14 nit: Please document that this can throw an except
Tim Song 2015/03/25 23:08:43 Done.
112 """
113 assert(self.auth_type == self.AuthType.OFFLINE_PASSWORD or
114 self.auth_type == self.AuthType.FORCE_OFFLINE_PASSWORD)
115 oobe = self._oobe
116 oobe.EvaluateJavaScript(
117 'document.getElementById("pod-row").pods[0].passwordElement.value = '
118 '"%s"' % self._chromeos.password)
119 oobe.EvaluateJavaScript(
120 'document.getElementById("pod-row").pods[0].activate()')
121 util.WaitFor(lambda: self._chromeos.session_state == \
122 ChromeOS.SessionState.IN_SESSION, 5)
123
124 def UnlockWithClick(self):
125 """ Clicks the user pod to unlock or sign-in.
126 Note: The first user pod on the screen is used.
127 """
128 assert(self.auth_type == self.AuthType.USER_CLICK)
129 self._oobe.EvaluateJavaScript(
130 'document.getElementById("pod-row").pods[0].activate()')
131
Ilya Sherman 2015/03/24 01:25:14 nit: I believe that top-level definitions are supp
Tim Song 2015/03/25 23:08:43 Done.
132 class SmartLockSettings(object):
133 """ Wrapper for the Smart Lock settings in chromeos://settings.
134 """
135 def __init__(self, tab, chromeos):
136 """
137 Args:
138 tab: Inspector page of the chromeos://settings tag.
139 chromeos: The parent Chrome wrapper.
140 """
141 self._tab = tab
142 self._chromeos = chromeos
143
144 @property
145 def smart_lock_enabled(self):
Ilya Sherman 2015/03/24 01:25:13 nit: Doc string
Ilya Sherman 2015/03/24 01:25:13 nit: Maybe prepend "is_" to the method name?
Tim Song 2015/03/25 23:08:43 I have an assert in the test class to make sure th
Tim Song 2015/03/25 23:08:43 Done.
Ilya Sherman 2015/03/25 23:17:29 I just meant, "is_smart_lock_enabled" sounds more
Tim Song 2015/03/27 18:05:26 Done.
146 return self._tab.EvaluateJavaScript(
147 '!document.getElementById("easy-unlock-enabled").hidden')
148
149 def TurnOffSmartLock(self):
150 """ Turns off Smart Lock by clicking the turn-off button and navigating
151 through the overlay.
152 """
Ilya Sherman 2015/03/24 01:25:14 nit: Please document that this can throw an except
Tim Song 2015/03/25 23:08:43 Done.
153 assert(self.smart_lock_enabled)
154 tab = self._tab
155 tab.EvaluateJavaScript(
156 'document.getElementById("easy-unlock-turn-off-button").click()')
157 util.WaitFor(lambda: tab.EvaluateJavaScript(
158 '!document.getElementById("easy-unlock-turn-off-overlay").hidden && '
159 'document.getElementById("easy-unlock-turn-off-confirm") != null'),
160 10)
161 tab.EvaluateJavaScript(
162 'document.getElementById("easy-unlock-turn-off-confirm").click()')
163 util.WaitFor(lambda: tab.EvaluateJavaScript(
164 '!document.getElementById("easy-unlock-disabled").hidden'), 15)
165
166 def StartSetup(self):
167 """ Starts the Smart Lock setup flow by clicking the button.
168 """
169 assert(not self.smart_lock_enabled)
170 self._tab.EvaluateJavaScript(
171 'document.getElementById("easy-unlock-setup-button").click()')
172
173 def StartSetupAndReturnApp(self):
174 """ Starts the Smart Lock setup flow and enters the password to
175 reauthenticate the user before the app launches.
176
177 Returns:
178 A SmartLockApp object of the app that was launched.
179 """
Ilya Sherman 2015/03/24 01:25:14 nit: Please document that this can throw an except
Tim Song 2015/03/25 23:08:44 Done.
180 self.StartSetup()
181 util.WaitFor(lambda: self._chromeos.session_state == \
182 ChromeOS.SessionState.LOCK_SCREEN, 5)
183 lock_screen = self._chromeos.GetAccountPickerScreen()
184 lock_screen.EnterPassword()
185 util.WaitFor(lambda: self._chromeos.GetSmartLockApp() is not None, 10)
186 return self._chromeos.GetSmartLockApp()
187
188 class SmartLockApp(object):
189 """ Wrapper for the Smart Lock setup dialog.
190 Note: This does not include the app's background page.
191 """
192
193 class PairingState:
194 """ The current state of the setup flow.
195 """
196 SCAN = 'scan'
197 PAIR = 'pair'
198 CLICK_FOR_TRIAL_RUN = 'click_for_trial_run'
199 TRIAL_RUN_COMPLETED = 'trial_run_completed'
200
201 def __init__(self, app_page, chromeos):
202 """
203 Args:
204 app_page: Inspector page of the app window.
205 chromeos: The parent Chrome wrapper.
206 """
207 self._app_page = app_page
208 self._chromeos = chromeos
209
210 @property
211 def pairing_state(self):
Ilya Sherman 2015/03/24 01:25:14 nit: Doc string, including exception warning.
Tim Song 2015/03/25 23:08:44 Done.
212 state = self._app_page.EvaluateJavaScript(
213 'document.body.getAttribute("step")')
214 if state == 'scan':
215 return SmartLockApp.PairingState.SCAN
216 elif state == 'pair':
217 return SmartLockApp.PairingState.PAIR
218 elif state == 'complete':
219 button_text = self._app_page.EvaluateJavaScript(
220 'document.getElementById("pairing-button").textContent')
221 button_text = button_text.strip().lower()
222 if button_text == 'try it out':
223 return SmartLockApp.PairingState.CLICK_FOR_TRIAL_RUN
224 elif button_text == 'done':
225 return SmartLockApp.PairingState.TRIAL_RUN_COMPLETED
226 else:
227 raise ValueError('Unknown button text: %s', button_text)
228 else:
229 raise ValueError('Unknown pairing state: %s' % state)
230
231 def FindPhone(self, retries=3):
232 """ Starts the initial step to find nearby phones.
233 The app must be in the SCAN state.
234
235 Args:
236 retries: The number of times to retry if no phones are found.
237 Returns:
238 True if a phone is found, else False.
239 """
240 assert(self.pairing_state == self.PairingState.SCAN)
241 for _ in xrange(retries):
242 self._ClickPairingButton()
243 if self.pairing_state == self.PairingState.PAIR:
244 return True
245 # Wait a few seconds before retrying.
246 time.sleep(10)
Ilya Sherman 2015/03/24 01:25:14 Ick. What's the total runtime for the test?
Tim Song 2015/03/25 23:08:43 The test can take 30-60 seconds to run, depending
247 return False
248
249 def PairPhone(self):
250 """ Starts the step of finding nearby phones.
251 The app must be in the PAIR state.
252
253 Returns:
254 True if pairing succeeded, else False.
255 """
256 assert(self.pairing_state == self.PairingState.PAIR)
257 self._ClickPairingButton()
258 return self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN
259
260 def StartTrialRun(self):
261 """ Starts the trial run.
262 The app must be in the CLICK_FOR_TRIAL_RUN state.
263 """
264 assert(self.pairing_state == self.PairingState.CLICK_FOR_TRIAL_RUN)
265 self._ClickPairingButton()
266 util.WaitFor(lambda: self._chromeos.session_state == \
267 ChromeOS.SessionState.LOCK_SCREEN, 10)
268
269 def DismissApp(self):
270 """ Dismisses the app after setup is completed.
271 The app must be in the TRIAL_RUN_COMPLETED state.
272 """
273 assert(self.pairing_state == self.PairingState.TRIAL_RUN_COMPLETED)
274 self._app_page.EvaluateJavaScript(
275 'document.getElementById("pairing-button").click()')
276
277 def _ClickPairingButton(self):
278 self._app_page.EvaluateJavaScript(
279 'document.getElementById("pairing-button").click()')
280 util.WaitFor(lambda: self._app_page.EvaluateJavaScript(
281 '!document.getElementById("pairing-button").disabled'), 60)
282 util.WaitFor(lambda: self._app_page.EvaluateJavaScript(
283 '!document.getElementById("pairing-button-title")'
284 '.classList.contains("animated-fade-out")'), 5)
285 util.WaitFor(lambda: self._app_page.EvaluateJavaScript(
286 '!document.getElementById("pairing-button-title")'
287 '.classList.contains("animated-fade-in")'), 5)
288
289 class ChromeOS(object):
290 """ Wrapper for a remote ChromeOS device.
291 """
292
293 class SessionState:
294 """ The state of the user session.
295 """
296 SIGNIN_SCREEN = 'signin_screen'
297 IN_SESSION = 'in_session'
298 LOCK_SCREEN = 'lock_screen'
299
300 def __init__(self, remote_address, username, password):
301 """
302 Args:
303 remote_address: The remote address of the cros device.
304 username: The username of the account to test.
305 password: The password of the account to test.
306 """
307 self._remote_address = remote_address;
308 self._username = username
309 self._password = password
310 self._browser = None
311 self._cros_interface = None
312 self._background_page = None
313 self._processes = []
314
315 @property
316 def username(self):
Ilya Sherman 2015/03/24 01:25:14 nit: Doc string. (Applies to all other undocument
Tim Song 2015/03/25 23:08:43 Done.
317 return self._username
318
319 @property
320 def password(self):
321 return self._password
322
323 @property
324 def session_state(self):
325 assert(self._browser is not None)
326 if self._browser.oobe_exists:
327 return self.SessionState.LOCK_SCREEN if \
328 self._cros_interface.IsCryptohomeMounted(self.username, False) else \
329 self.SessionState.SIGNIN_SCREEN
Ilya Sherman 2015/03/24 01:25:13 nit: Please use a regular if/else since this spans
Tim Song 2015/03/25 23:08:44 Done.
330 else:
331 return self.SessionState.IN_SESSION;
332
333 @property
334 def cryptauth_access_token(self):
335 try:
336 util.WaitFor(lambda: self._background_page.EvaluateJavaScript(
337 'var __token = __token || null; '
338 'chrome.identity.getAuthToken(function(token) {'
339 ' __token = token;'
340 '}); '
341 '__token != null'), 5)
342 return self._background_page.EvaluateJavaScript('__token');
343 except exceptions.TimeoutException:
344 logger.error('Failed to get access token.');
345 return ''
346
347 def __enter__(self):
348 return self
349
350 def __exit__(self, *args):
351 if self._browser is not None:
352 self._browser.Close()
353 if self._cros_interface is not None:
354 self._cros_interface.CloseConnection()
355 for process in self._processes:
356 process.terminate()
357
358 def Start(self, local_app_path=None):
359 """ Connects to the ChromeOS device and logs in.
360 Args:
361 local_app_path: A path on the local device containing the Smart Lock app
362 to use instead of the app on the ChromeOS device.
363 Return:
364 |self| for using in a "with" statement.
365 """
366 assert(self._browser is None)
367
368 finder_opts = browser_options.BrowserFinderOptions('cros-chrome')
369 finder_opts.CreateParser().parse_args(args=[])
370 finder_opts.cros_remote = self._remote_address
371 finder_opts.verbosity = 1
372
373 browser_opts = finder_opts.browser_options
374 browser_opts.create_browser_with_oobe = True
375 browser_opts.disable_component_extensions_with_background_pages = False
376 browser_opts.gaia_login = True
377 browser_opts.username = self._username
378 browser_opts.password = self._password
379 browser_opts.auto_login = True
380
381 self._cros_interface = cros_interface.CrOSInterface(
382 finder_opts.cros_remote,
383 finder_opts.cros_remote_ssh_port,
384 finder_opts.cros_ssh_identity)
385
386 browser_opts.disable_default_apps = local_app_path is not None
387 if local_app_path is not None:
388 easy_unlock_app = extension_to_load.ExtensionToLoad(
389 path=local_app_path,
390 browser_type='cros-chrome',
391 is_component=True)
392 finder_opts.extensions_to_load.append(easy_unlock_app)
393
394 retries = 3
395 while self._browser is not None or retries > 0:
396 try:
397 browser_to_create = browser_finder.FindBrowser(finder_opts)
398 self._browser = browser_to_create.Create(finder_opts);
399 break;
400 except (exceptions.LoginException) as e:
401 logger.error('Timed out logging in: %s' % e);
402 if retries == 1:
403 raise
404
405 bg_page_path = '/_generated_background_page.html'
406 util.WaitFor(
407 lambda: self._FindSmartLockAppPage(bg_page_path) is not None,
408 10);
409 self._background_page = self._FindSmartLockAppPage(bg_page_path)
410 return self
411
412 def GetAccountPickerScreen(self):
413 """ Returns the wrapper for the lock screen or sign-in screen.
414
415 Return:
416 An instance of AccountPickerScreen.
417 An exception will be raised if the wrapper can't be created.
418 """
419 assert(self._browser is not None)
420 assert(self.session_state == self.SessionState.LOCK_SCREEN or
421 self.session_state == self.SessionState.SIGNIN_SCREEN)
422 oobe = self._browser.oobe
423 def IsLockScreenResponsive():
424 return (oobe.EvaluateJavaScript("typeof Oobe == 'function'") and
425 oobe.EvaluateJavaScript(
426 "typeof Oobe.authenticateForTesting == 'function'"))
427 util.WaitFor(IsLockScreenResponsive, 10)
428 util.WaitFor(lambda: oobe.EvaluateJavaScript(
429 'document.getElementById("pod-row") && '
430 'document.getElementById("pod-row").pods && '
431 'document.getElementById("pod-row").pods.length > 0'), 10)
432 return AccountPickerScreen(oobe, self)
433
434 def GetSmartLockSettings(self):
435 """ Returns the wrapper for the Smart Lock settings.
436 A tab will be navigated to chrome://settings if it does not exist.
437
438 Return:
439 An instance of SmartLockSettings.
440 An exception will be raised if the wrapper can't be created.
441 """
442 if not len(self._browser.tabs):
443 self._browser.New()
444 tab = self._browser.tabs[0]
445 url = tab.EvaluateJavaScript('document.location.href')
446 smart_lock_url = 'chrome://settings/search#Smart%20Lock'
Ilya Sherman 2015/03/24 01:25:13 nit: NAMED_CONSTANT
Tim Song 2015/03/25 23:08:43 Done.
447 if url != smart_lock_url:
448 tab.Navigate(smart_lock_url)
449
450 # Wait for settings page to be responsive.
451 util.WaitFor(lambda: tab.EvaluateJavaScript(
452 'document.getElementById("easy-unlock-disabled") && '
453 'document.getElementById("easy-unlock-enabled") && '
454 '(!document.getElementById("easy-unlock-disabled").hidden || '
455 ' !document.getElementById("easy-unlock-enabled").hidden)'), 10)
456 settings = SmartLockSettings(tab, self)
457 logger.info('Started Smart Lock settings: enabled=%s' %
458 settings.smart_lock_enabled)
459 return settings
460
461 def GetSmartLockApp(self):
462 """ Returns the wrapper for the Smart Lock setup app.
463
464 Return:
465 An instance of SmartLockApp or None if the app window does not exist.
466 """
467 app_page = self._FindSmartLockAppPage('/pairing.html')
468 if app_page is not None:
469 # Wait for app window to be responsive.
470 util.WaitFor(lambda: app_page.EvaluateJavaScript(
471 'document.getElementById("pairing-button") != null'), 10)
472 return SmartLockApp(app_page, self)
473 return None
474
475 def RunBtmon(self):
476 """ Runs the btmon command.
477 Return:
478 A subprocess.Popen object of the btmon process.
479 """
480 assert(self._cros_interface)
481 cmd = self._cros_interface.FormSSHCommandLine(['btmon'])
482 process = subprocess.Popen(args=cmd, stdout=subprocess.PIPE,
483 stderr=subprocess.PIPE)
484 self._processes.append(process)
485 return process
486
487 def _FindSmartLockAppPage(self, page_name):
488 extensions = self._browser.extensions.GetByExtensionId(
489 'mkaemigholebcgchlkbankmihknojeak')
490 for extension_page in extensions:
491 pathname = extension_page.EvaluateJavaScript('document.location.pathname')
492 if pathname == page_name:
493 return extension_page
494 return None
OLDNEW
« no previous file with comments | « no previous file | components/proximity_auth/e2e_test/cryptauth.py » ('j') | components/proximity_auth/e2e_test/cryptauth.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698