OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2012 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 """Includes different methods to drive chromoting UI.""" |
| 6 |
| 7 import os |
| 8 import subprocess |
| 9 import sys |
| 10 import time |
| 11 |
| 12 from pyauto_errors import JSONInterfaceError |
| 13 |
| 14 |
| 15 class ChromotingMixIn(object): |
| 16 """MixIn for PyUITest that adds Chromoting-specific methods. |
| 17 |
| 18 Prepend it as a base class of a test to enable Chromoting functionality. |
| 19 This is a separate class from PyUITest to avoid namespace collisions. |
| 20 |
| 21 Example usage: |
| 22 class ChromotingExample(chromoting.ChromotingMixIn, pyauto.PyUITest): |
| 23 def testShare(self): |
| 24 app = self.InstallApp(self.GetWebappPath()) |
| 25 self.LaunchApp(app) |
| 26 self.Authenticate() |
| 27 self.assertTrue(self.Share()) |
| 28 """ |
| 29 |
| 30 def _ExecuteJavascript(self, command, tab_index, windex): |
| 31 """Helper that returns immediately after running a Javascript command. |
| 32 """ |
| 33 try: |
| 34 self.ExecuteJavascript( |
| 35 '%s; window.domAutomationController.send("done");' % command, |
| 36 tab_index, windex) |
| 37 return True |
| 38 except JSONInterfaceError: |
| 39 print '_ExecuteJavascript threw JSONInterfaceError' |
| 40 return False |
| 41 |
| 42 def _WaitForJavascriptCondition(self, condition, tab_index, windex): |
| 43 """Waits until the Javascript condition is true. |
| 44 |
| 45 This is different from a naive self.WaitUntil(lambda: self.GetDOMValue()) |
| 46 because it uses Javascript to check the condition instead of Python. |
| 47 |
| 48 Returns: True if condition is satisfied or otherwise False. |
| 49 """ |
| 50 try: |
| 51 return self.WaitUntil(lambda: self.GetDOMValue( |
| 52 '(%s) ? "1" : ""' % condition, tab_index, windex)) |
| 53 except JSONInterfaceError: |
| 54 print '_WaitForJavascriptCondition threw JSONInterfaceError' |
| 55 return False |
| 56 |
| 57 def _ExecuteAndWaitForMode(self, command, mode, tab_index, windex): |
| 58 """ Executes JavaScript and wait for remoting app mode equal to |
| 59 the given mode. |
| 60 |
| 61 Returns: True if condition is satisfied or otherwise False. |
| 62 """ |
| 63 if not self._ExecuteJavascript(command, tab_index, windex): |
| 64 return False |
| 65 return self._WaitForJavascriptCondition( |
| 66 'remoting.currentMode == remoting.AppMode.%s' % mode, |
| 67 tab_index, windex) |
| 68 |
| 69 def _ExecuteAndWaitForMajorMode(self, command, mode, tab_index, windex): |
| 70 """ Executes JavaScript and wait for remoting app major mode equal to |
| 71 the given mode. |
| 72 |
| 73 Returns: True if condition is satisfied or otherwise False. |
| 74 """ |
| 75 if not self._ExecuteJavascript(command, tab_index, windex): |
| 76 return False |
| 77 return self._WaitForJavascriptCondition( |
| 78 'remoting.getMajorMode() == remoting.AppMode.%s' % mode, |
| 79 tab_index, windex) |
| 80 |
| 81 def GetWebappPath(self): |
| 82 """Returns the path to the webapp. |
| 83 |
| 84 Expects the webapp to be in the same place as the pyautolib binaries. |
| 85 """ |
| 86 return os.path.join(self.BrowserPath(), 'remoting', 'remoting.webapp') |
| 87 |
| 88 def _GetHelperRunner(self): |
| 89 """Returns the python binary name that runs chromoting_helper.py.""" |
| 90 if sys.platform.startswith('win'): |
| 91 return 'python' |
| 92 else: |
| 93 return 'suid-python' |
| 94 |
| 95 def _GetHelper(self): |
| 96 """Get chromoting_helper.py.""" |
| 97 return os.path.join('chrome', 'test', 'pyautolib', 'chromoting_helper.py') |
| 98 |
| 99 def InstallHostDaemon(self): |
| 100 """Installs the host daemon.""" |
| 101 subprocess.call([self._GetHelperRunner(), self._GetHelper(), |
| 102 'install', self.BrowserPath()]) |
| 103 |
| 104 def UninstallHostDaemon(self): |
| 105 """Uninstalls the host daemon.""" |
| 106 subprocess.call([self._GetHelperRunner(), self._GetHelper(), |
| 107 'uninstall', self.BrowserPath()]) |
| 108 |
| 109 def ContinueAuth(self, tab_index=1, windex=0): |
| 110 """Starts authentication.""" |
| 111 self.assertTrue( |
| 112 self._WaitForJavascriptCondition('window.remoting && remoting.oauth2', |
| 113 tab_index, windex), |
| 114 msg='Timed out while waiting for remoting app to finish loading.') |
| 115 self._ExecuteJavascript('remoting.oauth2.doAuthRedirect();', |
| 116 tab_index, windex) |
| 117 |
| 118 def SignIn(self, email=None, password=None, otp=None, |
| 119 tab_index=1, windex=0): |
| 120 """Logs a user in. |
| 121 |
| 122 PyAuto tests start with a clean profile, so Chromoting tests should call |
| 123 this for every run after launching the app. If email or password is |
| 124 omitted, the user can type it into the browser window manually. |
| 125 """ |
| 126 self.assertTrue( |
| 127 self._WaitForJavascriptCondition('document.getElementById("signIn")', |
| 128 tab_index, windex), |
| 129 msg='Unable to redirect for authentication.') |
| 130 |
| 131 if email: |
| 132 self._ExecuteJavascript('document.getElementById("Email").value = "%s";' |
| 133 'document.getElementById("Passwd").focus();' |
| 134 % email, tab_index, windex) |
| 135 |
| 136 if password: |
| 137 self._ExecuteJavascript('document.getElementById("Passwd").value = "%s";' |
| 138 'document.getElementById("signIn").click();' |
| 139 % password, tab_index, windex) |
| 140 |
| 141 if otp: |
| 142 self.assertTrue( |
| 143 self._WaitForJavascriptCondition( |
| 144 'document.getElementById("smsVerifyPin")', |
| 145 tab_index, windex), |
| 146 msg='Invalid username or password.') |
| 147 self._ExecuteJavascript( |
| 148 'document.getElementById("smsUserPin").value = "%s";' |
| 149 'document.getElementById("smsVerifyPin").click();' % otp, |
| 150 tab_index, windex) |
| 151 |
| 152 # If the account adder screen appears, then skip it. |
| 153 self.assertTrue( |
| 154 self._WaitForJavascriptCondition( |
| 155 'document.getElementById("skip") || ' |
| 156 'document.getElementById("submit_approve_access")', |
| 157 tab_index, windex), |
| 158 msg='No "skip adding account" or "approve access" link.') |
| 159 self._ExecuteJavascript( |
| 160 'if (document.getElementById("skip")) ' |
| 161 '{ document.getElementById("skip").click(); }', |
| 162 tab_index, windex) |
| 163 |
| 164 def AllowAccess(self, tab_index=1, windex=0): |
| 165 """Allows access to chromoting webapp.""" |
| 166 # Approve access. |
| 167 self.assertTrue( |
| 168 self._WaitForJavascriptCondition( |
| 169 'document.getElementById("submit_approve_access")', |
| 170 tab_index, windex), |
| 171 msg='Did not go to permission page.') |
| 172 self._ExecuteJavascript( |
| 173 'document.getElementById("submit_approve_access").click();', |
| 174 tab_index, windex) |
| 175 |
| 176 # Wait for some things to be ready. |
| 177 self.assertTrue( |
| 178 self._WaitForJavascriptCondition( |
| 179 'window.remoting && remoting.oauth2 && ' \ |
| 180 'remoting.oauth2.isAuthenticated()', |
| 181 tab_index, windex), |
| 182 msg='OAuth2 authentication failed.') |
| 183 self.assertTrue( |
| 184 self._WaitForJavascriptCondition( |
| 185 'window.localStorage.getItem("remoting-email")', |
| 186 tab_index, windex), |
| 187 msg='Chromoting app did not reload after authentication.') |
| 188 |
| 189 def DenyAccess(self, tab_index=1, windex=0): |
| 190 """Deny and then allow access to chromoting webapp.""" |
| 191 self.assertTrue( |
| 192 self._WaitForJavascriptCondition( |
| 193 'document.getElementById("submit_deny_access")', |
| 194 tab_index, windex), |
| 195 msg='Did not go to permission page.') |
| 196 self._ExecuteJavascript( |
| 197 'document.getElementById("submit_deny_access").click();', |
| 198 tab_index, windex) |
| 199 |
| 200 def SignOut(self, tab_index=1, windex=0): |
| 201 """Signs out from chromoting and signs back in.""" |
| 202 self._ExecuteAndWaitForMode( |
| 203 'document.getElementById("sign-out").click();', |
| 204 'UNAUTHENTICATED', tab_index, windex) |
| 205 |
| 206 def Authenticate(self, tab_index=1, windex=0): |
| 207 """Finishes authentication flow for user.""" |
| 208 self.ContinueAuth(tab_index, windex) |
| 209 account = self.GetPrivateInfo()['test_chromoting_account'] |
| 210 self.host.SignIn(account['username'], account['password'], None, |
| 211 tab_index, windex) |
| 212 self.host.AllowAccess(tab_index, windex) |
| 213 |
| 214 def StartMe2Me(self, tab_index=1, windex=0): |
| 215 """Starts Me2Me. """ |
| 216 self._ExecuteJavascript( |
| 217 'document.getElementById("get-started-me2me").click();', |
| 218 tab_index, windex) |
| 219 |
| 220 def Share(self, tab_index=1, windex=0): |
| 221 """Generates an access code and waits for incoming connections. |
| 222 |
| 223 Returns: |
| 224 The access code on success; None otherwise. |
| 225 """ |
| 226 self._ExecuteAndWaitForMode( |
| 227 'remoting.tryShare();', |
| 228 'HOST_WAITING_FOR_CONNECTION', tab_index, windex) |
| 229 return self.GetDOMValue( |
| 230 'document.getElementById("access-code-display").innerText', |
| 231 tab_index, windex) |
| 232 |
| 233 def CancelShare(self, tab_index=1, windex=0): |
| 234 """Stops sharing the desktop on the host side.""" |
| 235 self.assertTrue( |
| 236 self._ExecuteAndWaitForMode( |
| 237 'remoting.cancelShare();', |
| 238 'HOST_SHARE_FINISHED', tab_index, windex), |
| 239 msg='Stopping sharing from the host side failed') |
| 240 |
| 241 def EnableConnectionsInstalled(self, pin_exercise=False, |
| 242 tab_index=1, windex=0): |
| 243 """Enables the remote connections on the host side.""" |
| 244 subprocess.call([self._GetHelperRunner(), self._GetHelper(), 'enable']) |
| 245 |
| 246 self.assertTrue( |
| 247 self._ExecuteAndWaitForMode( |
| 248 'document.getElementById("start-daemon").click();', |
| 249 'HOST_SETUP_ASK_PIN', tab_index, windex), |
| 250 msg='Cannot start host setup') |
| 251 self.assertTrue( |
| 252 self._WaitForJavascriptCondition( |
| 253 'document.getElementById("ask-pin-form").hidden == false', |
| 254 tab_index, windex), |
| 255 msg='No ask pin dialog') |
| 256 |
| 257 if pin_exercise: |
| 258 # Cancels the pin prompt |
| 259 self._ExecuteJavascript( |
| 260 'document.getElementById("daemon-pin-cancel").click();', |
| 261 tab_index, windex) |
| 262 |
| 263 # Enables again |
| 264 self.assertTrue( |
| 265 self._ExecuteAndWaitForMode( |
| 266 'document.getElementById("start-daemon").click();', |
| 267 'HOST_SETUP_ASK_PIN', tab_index, windex), |
| 268 msg='Cannot start host setup') |
| 269 |
| 270 # Click ok without typing in pins |
| 271 self._ExecuteJavascript( |
| 272 'document.getElementById("daemon-pin-ok").click();', |
| 273 tab_index, windex) |
| 274 self.assertTrue( |
| 275 self._WaitForJavascriptCondition( |
| 276 'document.getElementById("daemon-pin-error-message")', |
| 277 tab_index, windex), |
| 278 msg='No pin error message') |
| 279 |
| 280 # Mis-matching pins |
| 281 self._ExecuteJavascript( |
| 282 'document.getElementById("daemon-pin-entry").value = "111111";', |
| 283 tab_index, windex) |
| 284 self._ExecuteJavascript( |
| 285 'document.getElementById("daemon-pin-confirm").value = "123456";', |
| 286 tab_index, windex) |
| 287 self.assertTrue( |
| 288 self._WaitForJavascriptCondition( |
| 289 'document.getElementById("daemon-pin-error-message")', |
| 290 tab_index, windex), |
| 291 msg='No pin error message') |
| 292 |
| 293 # Types in correct pins |
| 294 self._ExecuteJavascript( |
| 295 'document.getElementById("daemon-pin-entry").value = "111111";', |
| 296 tab_index, windex) |
| 297 self._ExecuteJavascript( |
| 298 'document.getElementById("daemon-pin-confirm").value = "111111";', |
| 299 tab_index, windex) |
| 300 self.assertTrue( |
| 301 self._ExecuteAndWaitForMode( |
| 302 'document.getElementById("daemon-pin-ok").click();', |
| 303 'HOST_SETUP_PROCESSING', tab_index, windex), |
| 304 msg='Host setup was not started') |
| 305 |
| 306 # Handles preference panes |
| 307 self.assertTrue( |
| 308 self._WaitForJavascriptCondition( |
| 309 'remoting.currentMode == remoting.AppMode.HOST_SETUP_DONE', |
| 310 tab_index, windex), |
| 311 msg='Host setup was not done') |
| 312 |
| 313 # Dismisses the host config done dialog |
| 314 self.assertTrue( |
| 315 self._WaitForJavascriptCondition( |
| 316 'document.getElementById("host-setup-dialog")' |
| 317 '.childNodes[5].hidden == false', |
| 318 tab_index, windex), |
| 319 msg='No host setup done dialog') |
| 320 self.assertTrue( |
| 321 self._ExecuteAndWaitForMode( |
| 322 'document.getElementById("host-config-done-dismiss").click();', |
| 323 'HOME', tab_index, windex), |
| 324 msg='Failed to dismiss host setup confirmation dialog') |
| 325 |
| 326 def EnableConnectionsUninstalledAndCancel(self, tab_index=1, windex=0): |
| 327 """Enables remote connections while host is not installed yet.""" |
| 328 self.assertTrue( |
| 329 self._ExecuteAndWaitForMode( |
| 330 'document.getElementById("start-daemon").click();', |
| 331 'HOST_SETUP_INSTALL', tab_index, windex), |
| 332 msg='Cannot start host install') |
| 333 self.assertTrue( |
| 334 self._ExecuteAndWaitForMode( |
| 335 'document.getElementById("host-config-install-dismiss").click();', |
| 336 'HOME', tab_index, windex), |
| 337 msg='Failed to dismiss host install dialog') |
| 338 |
| 339 def DisableConnections(self, tab_index=1, windex=0): |
| 340 """Disables the remote connections on the host side.""" |
| 341 subprocess.call([self._GetHelperRunner(), self._GetHelper(), 'disable']) |
| 342 |
| 343 self._ExecuteJavascript( |
| 344 'document.getElementById("stop-daemon").click();', |
| 345 tab_index, windex) |
| 346 self.assertTrue( |
| 347 self._ExecuteAndWaitForMode( |
| 348 'document.getElementById("host-config-done-dismiss").click();', |
| 349 'HOME', tab_index, windex), |
| 350 msg='Failed to dismiss host setup confirmation dialog') |
| 351 |
| 352 def Connect(self, access_code, tab_index=1, windex=0): |
| 353 """Connects to a Chromoting host and starts the session.""" |
| 354 self.assertTrue( |
| 355 self._ExecuteAndWaitForMode( |
| 356 'document.getElementById("access-code-entry").value = "%s";' |
| 357 'remoting.connectIt2Me();' % access_code, |
| 358 'IN_SESSION', tab_index, windex), |
| 359 msg='Cannot connect it2me session') |
| 360 |
| 361 def ChangePin(self, pin='222222', tab_index=1, windex=0): |
| 362 """Changes pin for enabled host.""" |
| 363 subprocess.call([self._GetHelperRunner(), self._GetHelper(), 'changepin']) |
| 364 |
| 365 self.assertTrue( |
| 366 self._ExecuteAndWaitForMode( |
| 367 'document.getElementById("change-daemon-pin").click();', |
| 368 'HOST_SETUP_ASK_PIN', tab_index, windex), |
| 369 msg='Cannot change daemon pin') |
| 370 self.assertTrue( |
| 371 self._WaitForJavascriptCondition( |
| 372 'document.getElementById("ask-pin-form").hidden == false', |
| 373 tab_index, windex), |
| 374 msg='No ask pin dialog') |
| 375 |
| 376 self._ExecuteJavascript( |
| 377 'document.getElementById("daemon-pin-entry").value = "' + pin + '";', |
| 378 tab_index, windex) |
| 379 self._ExecuteJavascript( |
| 380 'document.getElementById("daemon-pin-confirm").value = "' + |
| 381 pin + '";', tab_index, windex) |
| 382 self.assertTrue( |
| 383 self._ExecuteAndWaitForMode( |
| 384 'document.getElementById("daemon-pin-ok").click();', |
| 385 'HOST_SETUP_PROCESSING', tab_index, windex), |
| 386 msg='Host setup was not started') |
| 387 |
| 388 # Handles preference panes |
| 389 self.assertTrue( |
| 390 self._WaitForJavascriptCondition( |
| 391 'remoting.currentMode == remoting.AppMode.HOST_SETUP_DONE', |
| 392 tab_index, windex), |
| 393 msg='Host setup was not done') |
| 394 |
| 395 # Dismisses the host config done dialog |
| 396 self.assertTrue( |
| 397 self._WaitForJavascriptCondition( |
| 398 'document.getElementById("host-setup-dialog")' |
| 399 '.childNodes[5].hidden == false', |
| 400 tab_index, windex), |
| 401 msg='No host setup done dialog') |
| 402 self.assertTrue( |
| 403 self._ExecuteAndWaitForMode( |
| 404 'document.getElementById("host-config-done-dismiss").click();', |
| 405 'HOME', tab_index, windex), |
| 406 msg='Failed to dismiss host setup confirmation dialog') |
| 407 |
| 408 def ChangeName(self, new_name='Changed', tab_index=1, windex=0): |
| 409 """Changes the host name.""" |
| 410 self._ExecuteJavascript( |
| 411 'document.getElementById("this-host-rename").click();', |
| 412 tab_index, windex) |
| 413 self._ExecuteJavascript( |
| 414 'document.getElementById("this-host-name").childNodes[0].value = "' + |
| 415 new_name + '";', tab_index, windex) |
| 416 self._ExecuteJavascript( |
| 417 'document.getElementById("this-host-rename").click();', |
| 418 tab_index, windex) |
| 419 |
| 420 def ConnectMe2Me(self, pin='111111', mode='IN_SESSION', |
| 421 tab_index=1, windex=0): |
| 422 """Connects to a Chromoting host and starts the session.""" |
| 423 |
| 424 # There is delay from the enabling remote connections to the host |
| 425 # showing up in the host list. We need to reload the web app to get |
| 426 # the host to show up. We will repeat this a few times to make sure |
| 427 # eventually host appears. |
| 428 for _ in range(1, 3): |
| 429 self._ExecuteJavascript( |
| 430 'window.location.reload();', |
| 431 tab_index, windex) |
| 432 |
| 433 # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after |
| 434 # ~60 seconds if ExecuteJavascript is called right after reload. |
| 435 # Waiting 2s here can avoid this. So instead of getting the error and |
| 436 # wait ~60s, we wait 2s here. If the error still happens, the following |
| 437 # retry will handle that. |
| 438 time.sleep(2) |
| 439 |
| 440 # If this-host-connect is still not enabled, let's retry 3 times here. |
| 441 this_host_connect_enabled = False |
| 442 for _ in range(1, 3): |
| 443 this_host_connect_enabled = self._WaitForJavascriptCondition( |
| 444 'document.getElementById("this-host-connect")' |
| 445 '.getAttribute("data-daemon-state") == "enabled"', |
| 446 tab_index, windex) |
| 447 if this_host_connect_enabled: |
| 448 break |
| 449 if this_host_connect_enabled: |
| 450 break; |
| 451 |
| 452 # Clicking this-host-connect does work right after this-host-connect |
| 453 # is enabled. Need to retry. |
| 454 for _ in range(1, 3): |
| 455 self._ExecuteJavascript( |
| 456 'document.getElementById("this-host-connect").click();', |
| 457 tab_index, windex) |
| 458 |
| 459 # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after |
| 460 # a long time out if WaitUntil is called right after click. |
| 461 # Waiting 2s here can avoid this. |
| 462 time.sleep(2) |
| 463 |
| 464 # If cannot detect that pin-form appears, try 3 times. |
| 465 pin_form_exposed = False |
| 466 for _ in range(1, 3): |
| 467 pin_form_exposed = self._WaitForJavascriptCondition( |
| 468 'document.getElementById("client-dialog")' |
| 469 '.childNodes[9].hidden == false', |
| 470 tab_index, windex) |
| 471 if pin_form_exposed: |
| 472 break |
| 473 if pin_form_exposed: |
| 474 break |
| 475 |
| 476 self._ExecuteJavascript( |
| 477 'document.getElementById("pin-entry").value = "' + pin + '";', |
| 478 tab_index, windex) |
| 479 self.assertTrue( |
| 480 self._ExecuteAndWaitForMode( |
| 481 'document.getElementById("pin-form").childNodes[5].click();', |
| 482 mode, tab_index, windex), |
| 483 msg='Session was not started') |
| 484 |
| 485 def Disconnect(self, tab_index=1, windex=0): |
| 486 """Disconnects from the Chromoting it2me session on the client side.""" |
| 487 self.assertTrue( |
| 488 self._ExecuteAndWaitForMode( |
| 489 'remoting.disconnect();', |
| 490 'CLIENT_SESSION_FINISHED_IT2ME', tab_index, windex), |
| 491 msg='Disconnecting it2me session from the client side failed') |
| 492 |
| 493 def DisconnectMe2Me(self, confirmation=True, tab_index=1, windex=0): |
| 494 """Disconnects from the Chromoting me2me session on the client side.""" |
| 495 self.assertTrue( |
| 496 self._ExecuteAndWaitForMode( |
| 497 'remoting.disconnect();', |
| 498 'CLIENT_SESSION_FINISHED_ME2ME', tab_index, windex), |
| 499 msg='Disconnecting me2me session from the client side failed') |
| 500 |
| 501 if confirmation: |
| 502 self.assertTrue( |
| 503 self._ExecuteAndWaitForMode( |
| 504 'document.getElementById("client-finished-me2me-button")' |
| 505 '.click();', 'HOME', tab_index, windex), |
| 506 msg='Failed to dismiss session finished dialog') |
| 507 |
| 508 def ReconnectMe2Me(self, pin='111111', tab_index=1, windex=0): |
| 509 """Reconnects the me2me session.""" |
| 510 self._ExecuteJavascript( |
| 511 'document.getElementById("client-reconnect-button").click();', |
| 512 tab_index, windex) |
| 513 |
| 514 # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after |
| 515 # a long time out if WaitUntil is called right after click. |
| 516 time.sleep(2) |
| 517 |
| 518 # If cannot detect that pin-form appears, try 3 times. |
| 519 for _ in range(1, 3): |
| 520 pin_form_exposed = self._WaitForJavascriptCondition( |
| 521 'document.getElementById("client-dialog")' |
| 522 '.childNodes[9].hidden == false', |
| 523 tab_index, windex) |
| 524 if pin_form_exposed: |
| 525 break |
| 526 |
| 527 self._ExecuteJavascript( |
| 528 'document.getElementById("pin-entry").value = "' + pin + '";', |
| 529 tab_index, windex) |
| 530 self.assertTrue( |
| 531 self._ExecuteAndWaitForMode( |
| 532 'document.getElementById("pin-form").childNodes[5].click();', |
| 533 'IN_SESSION', tab_index, windex), |
| 534 msg='Session was not started') |
OLD | NEW |