Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |