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.
5 """Includes different methods to drive chromoting UI."""
12 from pyauto_errors import JSONInterfaceError
15 class ChromotingMixIn(object):
16 """MixIn for PyUITest that adds Chromoting-specific methods.
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.
22 class ChromotingExample(chromoting.ChromotingMixIn, pyauto.PyUITest):
24 app = self.InstallApp(self.GetWebappPath())
27 self.assertTrue(self.Share())
30 def _ExecuteJavascript(self, command, tab_index, windex):
31 """Helper that returns immediately after running a Javascript command.
34 self.ExecuteJavascript(
35 '%s; window.domAutomationController.send("done");' % command,
38 except JSONInterfaceError:
39 print '_ExecuteJavascript threw JSONInterfaceError'
42 def _WaitForJavascriptCondition(self, condition, tab_index, windex,
44 """Waits until the Javascript condition is true.
46 This is different from a naive self.WaitUntil(lambda: self.GetDOMValue())
47 because it uses Javascript to check the condition instead of Python.
49 Returns: True if condition is satisfied or otherwise False.
52 return self.WaitUntil(lambda: self.GetDOMValue(
53 '(%s) ? "1" : ""' % condition, tab_index, windex), timeout)
54 except JSONInterfaceError:
55 print '_WaitForJavascriptCondition threw JSONInterfaceError'
58 def _ExecuteAndWaitForMode(self, command, mode, tab_index, windex):
59 """ Executes JavaScript and wait for remoting app mode equal to
62 Returns: True if condition is satisfied or otherwise False.
64 if not self._ExecuteJavascript(command, tab_index, windex):
66 return self._WaitForJavascriptCondition(
67 'remoting.currentMode == remoting.AppMode.%s' % mode,
70 def _ExecuteAndWaitForMajorMode(self, command, mode, tab_index, windex):
71 """ Executes JavaScript and wait for remoting app major mode equal to
74 Returns: True if condition is satisfied or otherwise False.
76 if not self._ExecuteJavascript(command, tab_index, windex):
78 return self._WaitForJavascriptCondition(
79 'remoting.getMajorMode() == remoting.AppMode.%s' % mode,
82 def GetWebappPath(self):
83 """Returns the path to the webapp.
85 Expects the webapp to be in the same place as the pyautolib binaries.
87 return os.path.join(self.BrowserPath(), 'remoting', 'remoting.webapp')
89 def _GetHelperRunner(self):
90 """Returns the python binary name that runs chromoting_helper.py."""
91 if sys.platform.startswith('win'):
97 """Get chromoting_helper.py."""
98 return os.path.join(os.path.dirname(__file__), 'chromoting_helper.py')
100 def InstallHostDaemon(self):
101 """Installs the host daemon."""
102 subprocess.call([self._GetHelperRunner(), self._GetHelper(),
103 'install', self.BrowserPath()])
105 def UninstallHostDaemon(self):
106 """Uninstalls the host daemon."""
107 subprocess.call([self._GetHelperRunner(), self._GetHelper(),
108 'uninstall', self.BrowserPath()])
110 def ContinueAuth(self, tab_index=1, windex=0):
111 """Starts authentication."""
113 self._WaitForJavascriptCondition('window.remoting && remoting.oauth2',
115 msg='Timed out while waiting for remoting app to finish loading.')
116 self._ExecuteJavascript('remoting.oauth2.doAuthRedirect();',
119 def SignIn(self, email=None, password=None, otp=None,
120 tab_index=1, windex=0):
123 PyAuto tests start with a clean profile, so Chromoting tests should call
124 this for every run after launching the app. If email or password is
125 omitted, the user can type it into the browser window manually.
128 self._WaitForJavascriptCondition('document.getElementById("signIn")',
130 msg='Unable to redirect for authentication.')
133 self._ExecuteJavascript('document.getElementById("Email").value = "%s";'
134 'document.getElementById("Passwd").focus();'
135 % email, tab_index, windex)
138 self._ExecuteJavascript('document.getElementById("Passwd").value = "%s";'
139 'document.getElementById("signIn").click();'
140 % password, tab_index, windex)
144 self._WaitForJavascriptCondition(
145 'document.getElementById("smsVerifyPin")',
147 msg='Invalid username or password.')
148 self._ExecuteJavascript(
149 'document.getElementById("smsUserPin").value = "%s";'
150 'document.getElementById("smsVerifyPin").click();' % otp,
153 # If the account adder screen appears, then skip it.
155 self._WaitForJavascriptCondition(
156 'document.getElementById("skip") || '
157 'document.getElementById("submit_approve_access")',
159 msg='No "skip adding account" or "approve access" link.')
160 self._ExecuteJavascript(
161 'if (document.getElementById("skip")) '
162 '{ document.getElementById("skip").click(); }',
165 def AllowAccess(self, tab_index=1, windex=0):
166 """Allows access to chromoting webapp."""
169 self._WaitForJavascriptCondition(
170 'document.getElementById("submit_approve_access")',
172 msg='Did not go to permission page.')
173 self._WaitForJavascriptCondition(
174 '!document.getElementById("submit_approve_access").disabled',
176 self._ExecuteJavascript(
177 'document.getElementById("submit_approve_access").click();',
180 # Wait for some things to be ready.
182 self._WaitForJavascriptCondition(
183 'window.remoting && remoting.oauth2 && ' \
184 'remoting.oauth2.isAuthenticated()',
186 msg='OAuth2 authentication failed.')
188 self._WaitForJavascriptCondition(
189 'window.localStorage.getItem("remoting-email")',
191 msg='Chromoting app did not reload after authentication.')
193 def DenyAccess(self, tab_index=1, windex=0):
194 """Deny and then allow access to chromoting webapp."""
196 self._WaitForJavascriptCondition(
197 'document.getElementById("submit_deny_access")',
199 msg='Did not go to permission page.')
200 self._WaitForJavascriptCondition(
201 '!document.getElementById("submit_deny_access").disabled',
203 self._ExecuteJavascript(
204 'document.getElementById("submit_deny_access").click();',
207 def SignOut(self, tab_index=1, windex=0):
208 """Signs out from chromoting and signs back in."""
209 self._ExecuteAndWaitForMode(
210 'document.getElementById("sign-out").click();',
211 'UNAUTHENTICATED', tab_index, windex)
213 def Authenticate(self, tab_index=1, windex=0):
214 """Finishes authentication flow for user."""
215 self.ContinueAuth(tab_index, windex)
216 account = self.GetPrivateInfo()['test_chromoting_account']
217 self.host.SignIn(account['username'], account['password'], None,
219 self.host.AllowAccess(tab_index, windex)
221 def StartMe2Me(self, tab_index=1, windex=0):
223 self._ExecuteJavascript(
224 'document.getElementById("get-started-me2me").click();',
227 self._WaitForJavascriptCondition(
228 'document.getElementById("me2me-content").hidden == false',
230 msg='No me2me content')
232 def Share(self, tab_index=1, windex=0):
233 """Generates an access code and waits for incoming connections.
236 The access code on success; None otherwise.
238 self._ExecuteAndWaitForMode(
239 'remoting.tryShare();',
240 'HOST_WAITING_FOR_CONNECTION', tab_index, windex)
241 return self.GetDOMValue(
242 'document.getElementById("access-code-display").innerText',
245 def CancelShare(self, tab_index=1, windex=0):
246 """Stops sharing the desktop on the host side."""
248 self._ExecuteAndWaitForMode(
249 'remoting.cancelShare();',
250 'HOST_SHARE_FINISHED', tab_index, windex),
251 msg='Stopping sharing from the host side failed')
253 def CleanupHostList(self, tab_index=1, windex=0):
254 """Removes hosts due to failure on previous stop-daemon"""
255 self.EnableConnectionsInstalled()
256 this_host_name = self.GetDOMValue(
257 'document.getElementById("this-host-name").textContent',
259 if this_host_name.endswith(' (offline)'):
260 this_host_name = this_host_name[:-10]
261 self.DisableConnections()
263 total_hosts = self.GetDOMValue(
264 'document.getElementById("host-list").childNodes.length',
267 # Start from the end while deleting bogus hosts
272 hostname = self.GetDOMValue(
273 'document.getElementById("host-list")'
274 '.childNodes[%s].textContent' % index,
276 if hostname == this_host_name or \
277 hostname == this_host_name + ' (offline)':
278 self._ExecuteJavascript(
279 'document.getElementById("host-list")'
280 '.childNodes[%s].childNodes[3].click()' % index,
282 self._ExecuteJavascript(
283 'document.getElementById("confirm-host-delete").click()',
285 except JSONInterfaceError:
286 print 'Ignore the error on deleting host'
288 if self._WaitForJavascriptCondition(
289 'document.getElementById("this-host-connect")'
290 '.getAttribute("data-daemon-state") == "enabled"',
291 tab_index, windex, 1):
292 self.DisableConnections()
294 def EnableConnectionsInstalled(self, pin_exercise=False,
295 tab_index=1, windex=0):
296 """Enables the remote connections on the host side."""
297 if sys.platform.startswith('darwin'):
298 subprocess.call([self._GetHelperRunner(), self._GetHelper(), 'enable'])
301 self._ExecuteAndWaitForMode(
302 'document.getElementById("start-daemon").click();',
303 'HOST_SETUP_ASK_PIN', tab_index, windex),
304 msg='Cannot start host setup')
306 self._WaitForJavascriptCondition(
307 'document.getElementById("ask-pin-form").hidden == false',
309 msg='No ask pin dialog')
312 # Cancels the pin prompt
313 self._ExecuteJavascript(
314 'document.getElementById("daemon-pin-cancel").click();',
319 self._ExecuteAndWaitForMode(
320 'document.getElementById("start-daemon").click();',
321 'HOST_SETUP_ASK_PIN', tab_index, windex),
322 msg='Cannot start host setup')
324 # Click ok without typing in pins
325 self._ExecuteJavascript(
326 'document.getElementById("daemon-pin-ok").click();',
329 self._WaitForJavascriptCondition(
330 'document.getElementById("daemon-pin-error-message")',
332 msg='No pin error message')
335 self._ExecuteJavascript(
336 'document.getElementById("daemon-pin-entry").value = "111111";',
338 self._ExecuteJavascript(
339 'document.getElementById("daemon-pin-confirm").value = "123456";',
342 self._WaitForJavascriptCondition(
343 'document.getElementById("daemon-pin-error-message")',
345 msg='No pin error message')
347 # Types in correct pins
348 self._ExecuteJavascript(
349 'document.getElementById("daemon-pin-entry").value = "111111";',
351 self._ExecuteJavascript(
352 'document.getElementById("daemon-pin-confirm").value = "111111";',
355 self._ExecuteAndWaitForMode(
356 'document.getElementById("daemon-pin-ok").click();',
357 'HOST_SETUP_PROCESSING', tab_index, windex),
358 msg='Host setup was not started')
360 # Handles preference panes
362 self._WaitForJavascriptCondition(
363 'remoting.currentMode == remoting.AppMode.HOST_SETUP_DONE',
365 msg='Host setup was not done')
367 # Dismisses the host config done dialog
369 self._WaitForJavascriptCondition(
370 'document.getElementById("host-setup-dialog")'
371 '.childNodes[5].hidden == false',
373 msg='No host setup done dialog')
375 self._ExecuteAndWaitForMode(
376 'document.getElementById("host-config-done-dismiss").click();',
377 'HOME', tab_index, windex),
378 msg='Failed to dismiss host setup confirmation dialog')
380 def EnableConnectionsUninstalledAndCancel(self, tab_index=1, windex=0):
381 """Enables remote connections while host is not installed yet."""
383 self._ExecuteAndWaitForMode(
384 'document.getElementById("start-daemon").click();',
385 'HOST_SETUP_INSTALL', tab_index, windex),
386 msg='Cannot start host install')
388 self._ExecuteAndWaitForMode(
389 'document.getElementById("host-config-install-dismiss").click();',
390 'HOME', tab_index, windex),
391 msg='Failed to dismiss host install dialog')
393 def DisableConnections(self, tab_index=1, windex=0):
394 """Disables the remote connections on the host side."""
395 if sys.platform.startswith('darwin'):
396 subprocess.call([self._GetHelperRunner(), self._GetHelper(), 'disable'])
398 # Re-try to make disabling connection more stable
399 for _ in range (1, 4):
400 self._ExecuteJavascript(
401 'document.getElementById("stop-daemon").click();',
404 # Immediately waiting for host-setup-dialog hidden sometimes times out
405 # even though visually it is hidden. Add some sleep here
408 if self._WaitForJavascriptCondition(
409 'document.getElementById("host-setup-dialog")'
410 '.childNodes[3].hidden == true',
411 tab_index, windex, 1):
415 self._ExecuteAndWaitForMode(
416 'document.getElementById("host-config-done-dismiss").click();',
417 'HOME', tab_index, windex),
418 msg='Failed to dismiss host setup confirmation dialog')
420 def Connect(self, access_code, tab_index=1, windex=0):
421 """Connects to a Chromoting host and starts the session."""
423 self._ExecuteAndWaitForMode(
424 'document.getElementById("access-code-entry").value = "%s";'
425 'remoting.connectIt2Me();' % access_code,
426 'IN_SESSION', tab_index, windex),
427 msg='Cannot connect it2me session')
429 def ChangePin(self, pin='222222', tab_index=1, windex=0):
430 """Changes pin for enabled host."""
431 if sys.platform.startswith('darwin'):
432 subprocess.call([self._GetHelperRunner(), self._GetHelper(), 'changepin'])
435 self._ExecuteAndWaitForMode(
436 'document.getElementById("change-daemon-pin").click();',
437 'HOST_SETUP_ASK_PIN', tab_index, windex),
438 msg='Cannot change daemon pin')
440 self._WaitForJavascriptCondition(
441 'document.getElementById("ask-pin-form").hidden == false',
443 msg='No ask pin dialog')
445 self._ExecuteJavascript(
446 'document.getElementById("daemon-pin-entry").value = "' + pin + '";',
448 self._ExecuteJavascript(
449 'document.getElementById("daemon-pin-confirm").value = "' +
450 pin + '";', tab_index, windex)
452 self._ExecuteAndWaitForMode(
453 'document.getElementById("daemon-pin-ok").click();',
454 'HOST_SETUP_PROCESSING', tab_index, windex),
455 msg='Host setup was not started')
457 # Handles preference panes
459 self._WaitForJavascriptCondition(
460 'remoting.currentMode == remoting.AppMode.HOST_SETUP_DONE',
462 msg='Host setup was not done')
464 # Dismisses the host config done dialog
466 self._WaitForJavascriptCondition(
467 'document.getElementById("host-setup-dialog")'
468 '.childNodes[5].hidden == false',
470 msg='No host setup done dialog')
472 self._ExecuteAndWaitForMode(
473 'document.getElementById("host-config-done-dismiss").click();',
474 'HOME', tab_index, windex),
475 msg='Failed to dismiss host setup confirmation dialog')
477 def ChangeName(self, new_name='Changed', tab_index=1, windex=0):
478 """Changes the host name."""
479 self._ExecuteJavascript(
480 'document.getElementById("this-host-rename").click();',
482 self._ExecuteJavascript(
483 'document.getElementById("this-host-name").childNodes[0].value = "' +
484 new_name + '";', tab_index, windex)
485 self._ExecuteJavascript(
486 'document.getElementById("this-host-rename").click();',
489 def ConnectMe2Me(self, pin='111111', mode='IN_SESSION',
490 tab_index=1, windex=0):
491 """Connects to a Chromoting host and starts the session."""
493 # There is delay from the enabling remote connections to the host
494 # showing up in the host list. We need to reload the web app to get
495 # the host to show up. We will repeat this a few times to make sure
496 # eventually host appears.
497 for _ in range(1, 13):
498 self._ExecuteJavascript(
499 'window.location.reload();',
502 # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after
503 # 45 seconds if ExecuteJavascript is called right after reload.
504 # Waiting 2s here can avoid this. So instead of getting the error and
505 # wait 45s, we wait 2s here. If the error still happens, the following
506 # retry will handle that.
509 # If this-host-connect is still not enabled, let's retry one more time.
510 this_host_connect_enabled = False
511 for _ in range(1, 3):
512 daemon_state_enabled = self._WaitForJavascriptCondition(
513 'document.getElementById("this-host-connect")'
514 '.getAttribute("data-daemon-state") == "enabled"',
515 tab_index, windex, 1)
516 host_online = self._WaitForJavascriptCondition(
517 'document.getElementById("this-host-name")'
518 '.textContent.toString().indexOf("offline") == -1',
519 tab_index, windex, 1)
520 this_host_connect_enabled = daemon_state_enabled and host_online
521 if this_host_connect_enabled:
523 if this_host_connect_enabled:
526 # Clicking this-host-connect does work right after this-host-connect
527 # is enabled. Need to retry.
528 for _ in range(1, 4):
529 self._ExecuteJavascript(
530 'document.getElementById("this-host-connect").click();',
533 # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after
534 # a long time out if WaitUntil is called right after click.
535 # Waiting 2s here can avoid this.
538 # If cannot detect that pin-form appears, retry one more time.
539 pin_form_exposed = False
540 for _ in range(1, 3):
541 pin_form_exposed = self._WaitForJavascriptCondition(
542 'document.getElementById("client-dialog")'
543 '.childNodes[9].hidden == false',
544 tab_index, windex, 1)
551 # Dismiss connect failure dialog before retry
552 if self._WaitForJavascriptCondition(
553 'document.getElementById("client-dialog")'
554 '.childNodes[25].hidden == false',
555 tab_index, windex, 1):
556 self._ExecuteJavascript(
557 'document.getElementById("client-finished-me2me-button")'
561 self._ExecuteJavascript(
562 'document.getElementById("pin-entry").value = "' + pin + '";',
565 self._ExecuteAndWaitForMode(
566 'document.getElementById("pin-form").childNodes[5].click();',
567 mode, tab_index, windex),
568 msg='Session was not started')
570 def Disconnect(self, tab_index=1, windex=0):
571 """Disconnects from the Chromoting it2me session on the client side."""
573 self._ExecuteAndWaitForMode(
574 'remoting.disconnect();',
575 'CLIENT_SESSION_FINISHED_IT2ME', tab_index, windex),
576 msg='Disconnecting it2me session from the client side failed')
578 def DisconnectMe2Me(self, confirmation=True, tab_index=1, windex=0):
579 """Disconnects from the Chromoting me2me session on the client side."""
581 self._ExecuteAndWaitForMode(
582 'remoting.disconnect();',
583 'CLIENT_SESSION_FINISHED_ME2ME', tab_index, windex),
584 msg='Disconnecting me2me session from the client side failed')
588 self._ExecuteAndWaitForMode(
589 'document.getElementById("client-finished-me2me-button")'
590 '.click();', 'HOME', tab_index, windex),
591 msg='Failed to dismiss session finished dialog')
593 def ReconnectMe2Me(self, pin='111111', tab_index=1, windex=0):
594 """Reconnects the me2me session."""
595 self._ExecuteJavascript(
596 'document.getElementById("client-reconnect-button").click();',
599 # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after
600 # a long time out if WaitUntil is called right after click.
603 # If cannot detect that pin-form appears, retry one more time.
604 for _ in range(1, 3):
605 pin_form_exposed = self._WaitForJavascriptCondition(
606 'document.getElementById("client-dialog")'
607 '.childNodes[9].hidden == false',
608 tab_index, windex, 1)
612 self._ExecuteJavascript(
613 'document.getElementById("pin-entry").value = "' + pin + '";',
616 self._ExecuteAndWaitForMode(
617 'document.getElementById("pin-form").childNodes[5].click();',
618 'IN_SESSION', tab_index, windex),
619 msg='Session was not started when reconnecting')