Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / test / pyautolib / chromotinglib.py
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                                   timeout=-1):
44     """Waits until the Javascript condition is true.
45
46     This is different from a naive self.WaitUntil(lambda: self.GetDOMValue())
47     because it uses Javascript to check the condition instead of Python.
48
49     Returns: True if condition is satisfied or otherwise False.
50     """
51     try:
52       return self.WaitUntil(lambda: self.GetDOMValue(
53           '(%s) ? "1" : ""' % condition, tab_index, windex), timeout)
54     except JSONInterfaceError:
55       print '_WaitForJavascriptCondition threw JSONInterfaceError'
56       return False
57
58   def _ExecuteAndWaitForMode(self, command, mode, tab_index, windex):
59     """ Executes JavaScript and wait for remoting app mode equal to
60     the given mode.
61
62     Returns: True if condition is satisfied or otherwise False.
63     """
64     if not self._ExecuteJavascript(command, tab_index, windex):
65       return False
66     return self._WaitForJavascriptCondition(
67         'remoting.currentMode == remoting.AppMode.%s' % mode,
68         tab_index, windex)
69
70   def _ExecuteAndWaitForMajorMode(self, command, mode, tab_index, windex):
71     """ Executes JavaScript and wait for remoting app major mode equal to
72     the given mode.
73
74     Returns: True if condition is satisfied or otherwise False.
75     """
76     if not self._ExecuteJavascript(command, tab_index, windex):
77       return False
78     return self._WaitForJavascriptCondition(
79         'remoting.getMajorMode() == remoting.AppMode.%s' % mode,
80         tab_index, windex)
81
82   def GetWebappPath(self):
83     """Returns the path to the webapp.
84
85     Expects the webapp to be in the same place as the pyautolib binaries.
86     """
87     return os.path.join(self.BrowserPath(), 'remoting', 'remoting.webapp')
88
89   def _GetHelperRunner(self):
90     """Returns the python binary name that runs chromoting_helper.py."""
91     if sys.platform.startswith('win'):
92       return 'python'
93     else:
94       return 'suid-python'
95
96   def _GetHelper(self):
97     """Get chromoting_helper.py."""
98     return os.path.join(os.path.dirname(__file__), 'chromoting_helper.py')
99
100   def InstallHostDaemon(self):
101     """Installs the host daemon."""
102     subprocess.call([self._GetHelperRunner(), self._GetHelper(),
103                      'install', self.BrowserPath()])
104
105   def UninstallHostDaemon(self):
106     """Uninstalls the host daemon."""
107     subprocess.call([self._GetHelperRunner(), self._GetHelper(),
108                      'uninstall', self.BrowserPath()])
109
110   def ContinueAuth(self, tab_index=1, windex=0):
111     """Starts authentication."""
112     self.assertTrue(
113         self._WaitForJavascriptCondition('window.remoting && remoting.oauth2',
114                                          tab_index, windex),
115         msg='Timed out while waiting for remoting app to finish loading.')
116     self._ExecuteJavascript('remoting.oauth2.doAuthRedirect();',
117                             tab_index, windex)
118
119   def SignIn(self, email=None, password=None, otp=None,
120                    tab_index=1, windex=0):
121     """Logs a user in.
122
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.
126     """
127     self.assertTrue(
128         self._WaitForJavascriptCondition('document.getElementById("signIn")',
129                                          tab_index, windex),
130         msg='Unable to redirect for authentication.')
131
132     if email:
133       self._ExecuteJavascript('document.getElementById("Email").value = "%s";'
134                               'document.getElementById("Passwd").focus();'
135                               % email, tab_index, windex)
136
137     if password:
138       self._ExecuteJavascript('document.getElementById("Passwd").value = "%s";'
139                               'document.getElementById("signIn").click();'
140                               % password, tab_index, windex)
141
142     if otp:
143       self.assertTrue(
144           self._WaitForJavascriptCondition(
145               'document.getElementById("smsVerifyPin")',
146               tab_index, windex),
147           msg='Invalid username or password.')
148       self._ExecuteJavascript(
149           'document.getElementById("smsUserPin").value = "%s";'
150           'document.getElementById("smsVerifyPin").click();' % otp,
151           tab_index, windex)
152
153     # If the account adder screen appears, then skip it.
154     self.assertTrue(
155         self._WaitForJavascriptCondition(
156             'document.getElementById("skip") || '
157             'document.getElementById("submit_approve_access")',
158             tab_index, windex),
159         msg='No "skip adding account" or "approve access" link.')
160     self._ExecuteJavascript(
161         'if (document.getElementById("skip")) '
162         '{ document.getElementById("skip").click(); }',
163         tab_index, windex)
164
165   def AllowAccess(self, tab_index=1, windex=0):
166     """Allows access to chromoting webapp."""
167     # Approve access.
168     self.assertTrue(
169         self._WaitForJavascriptCondition(
170             'document.getElementById("submit_approve_access")',
171             tab_index, windex),
172         msg='Did not go to permission page.')
173     self._WaitForJavascriptCondition(
174         '!document.getElementById("submit_approve_access").disabled',
175         tab_index, windex)
176     self._ExecuteJavascript(
177         'document.getElementById("submit_approve_access").click();',
178         tab_index, windex)
179
180     # Wait for some things to be ready.
181     self.assertTrue(
182         self._WaitForJavascriptCondition(
183             'window.remoting && remoting.oauth2 && ' \
184             'remoting.oauth2.isAuthenticated()',
185             tab_index, windex),
186         msg='OAuth2 authentication failed.')
187     self.assertTrue(
188         self._WaitForJavascriptCondition(
189             'window.localStorage.getItem("remoting-email")',
190             tab_index, windex),
191         msg='Chromoting app did not reload after authentication.')
192
193   def DenyAccess(self, tab_index=1, windex=0):
194     """Deny and then allow access to chromoting webapp."""
195     self.assertTrue(
196         self._WaitForJavascriptCondition(
197             'document.getElementById("submit_deny_access")',
198             tab_index, windex),
199         msg='Did not go to permission page.')
200     self._WaitForJavascriptCondition(
201         '!document.getElementById("submit_deny_access").disabled',
202         tab_index, windex)
203     self._ExecuteJavascript(
204         'document.getElementById("submit_deny_access").click();',
205         tab_index, windex)
206
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)
212
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,
218                     tab_index, windex)
219     self.host.AllowAccess(tab_index, windex)
220
221   def StartMe2Me(self, tab_index=1, windex=0):
222     """Starts Me2Me. """
223     self._ExecuteJavascript(
224         'document.getElementById("get-started-me2me").click();',
225         tab_index, windex)
226     self.assertTrue(
227         self._WaitForJavascriptCondition(
228             'document.getElementById("me2me-content").hidden == false',
229             tab_index, windex),
230         msg='No me2me content')
231
232   def Share(self, tab_index=1, windex=0):
233     """Generates an access code and waits for incoming connections.
234
235     Returns:
236       The access code on success; None otherwise.
237     """
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',
243         tab_index, windex)
244
245   def CancelShare(self, tab_index=1, windex=0):
246     """Stops sharing the desktop on the host side."""
247     self.assertTrue(
248         self._ExecuteAndWaitForMode(
249             'remoting.cancelShare();',
250             'HOST_SHARE_FINISHED', tab_index, windex),
251         msg='Stopping sharing from the host side failed')
252
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',
258         tab_index, windex)
259     if this_host_name.endswith(' (offline)'):
260       this_host_name = this_host_name[:-10]
261     self.DisableConnections()
262
263     total_hosts = self.GetDOMValue(
264         'document.getElementById("host-list").childNodes.length',
265         tab_index, windex)
266
267     # Start from the end while deleting bogus hosts
268     index = total_hosts
269     while index > 0:
270       index -= 1
271       try:
272         hostname = self.GetDOMValue(
273             'document.getElementById("host-list")'
274             '.childNodes[%s].textContent' % index,
275             tab_index, windex)
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,
281               tab_index, windex)
282           self._ExecuteJavascript(
283               'document.getElementById("confirm-host-delete").click()',
284               tab_index, windex)
285       except JSONInterfaceError:
286         print 'Ignore the error on deleting host'
287
288     if self._WaitForJavascriptCondition(
289             'document.getElementById("this-host-connect")'
290             '.getAttribute("data-daemon-state") == "enabled"',
291             tab_index, windex, 1):
292       self.DisableConnections()
293
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'])
299
300     self.assertTrue(
301         self._ExecuteAndWaitForMode(
302             'document.getElementById("start-daemon").click();',
303             'HOST_SETUP_ASK_PIN', tab_index, windex),
304         msg='Cannot start host setup')
305     self.assertTrue(
306         self._WaitForJavascriptCondition(
307             'document.getElementById("ask-pin-form").hidden == false',
308             tab_index, windex),
309         msg='No ask pin dialog')
310
311     if pin_exercise:
312       # Cancels the pin prompt
313       self._ExecuteJavascript(
314           'document.getElementById("daemon-pin-cancel").click();',
315           tab_index, windex)
316
317       # Enables again
318       self.assertTrue(
319           self._ExecuteAndWaitForMode(
320               'document.getElementById("start-daemon").click();',
321               'HOST_SETUP_ASK_PIN', tab_index, windex),
322           msg='Cannot start host setup')
323
324       # Click ok without typing in pins
325       self._ExecuteJavascript(
326           'document.getElementById("daemon-pin-ok").click();',
327           tab_index, windex)
328       self.assertTrue(
329           self._WaitForJavascriptCondition(
330               'document.getElementById("daemon-pin-error-message")',
331               tab_index, windex),
332           msg='No pin error message')
333
334       # Mis-matching pins
335       self._ExecuteJavascript(
336           'document.getElementById("daemon-pin-entry").value = "111111";',
337           tab_index, windex)
338       self._ExecuteJavascript(
339           'document.getElementById("daemon-pin-confirm").value = "123456";',
340           tab_index, windex)
341       self.assertTrue(
342           self._WaitForJavascriptCondition(
343               'document.getElementById("daemon-pin-error-message")',
344               tab_index, windex),
345           msg='No pin error message')
346
347     # Types in correct pins
348     self._ExecuteJavascript(
349         'document.getElementById("daemon-pin-entry").value = "111111";',
350         tab_index, windex)
351     self._ExecuteJavascript(
352         'document.getElementById("daemon-pin-confirm").value = "111111";',
353         tab_index, windex)
354     self.assertTrue(
355         self._ExecuteAndWaitForMode(
356             'document.getElementById("daemon-pin-ok").click();',
357             'HOST_SETUP_PROCESSING', tab_index, windex),
358         msg='Host setup was not started')
359
360     # Handles preference panes
361     self.assertTrue(
362         self._WaitForJavascriptCondition(
363             'remoting.currentMode == remoting.AppMode.HOST_SETUP_DONE',
364             tab_index, windex),
365         msg='Host setup was not done')
366
367     # Dismisses the host config done dialog
368     self.assertTrue(
369         self._WaitForJavascriptCondition(
370             'document.getElementById("host-setup-dialog")'
371             '.childNodes[5].hidden == false',
372             tab_index, windex),
373         msg='No host setup done dialog')
374     self.assertTrue(
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')
379
380   def EnableConnectionsUninstalledAndCancel(self, tab_index=1, windex=0):
381     """Enables remote connections while host is not installed yet."""
382     self.assertTrue(
383         self._ExecuteAndWaitForMode(
384             'document.getElementById("start-daemon").click();',
385             'HOST_SETUP_INSTALL', tab_index, windex),
386         msg='Cannot start host install')
387     self.assertTrue(
388         self._ExecuteAndWaitForMode(
389             'document.getElementById("host-config-install-dismiss").click();',
390             'HOME', tab_index, windex),
391         msg='Failed to dismiss host install dialog')
392
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'])
397
398     # Re-try to make disabling connection more stable
399     for _ in range (1, 4):
400       self._ExecuteJavascript(
401           'document.getElementById("stop-daemon").click();',
402           tab_index, windex)
403
404       # Immediately waiting for host-setup-dialog hidden sometimes times out
405       # even though visually it is hidden. Add some sleep here
406       time.sleep(2)
407
408       if self._WaitForJavascriptCondition(
409           'document.getElementById("host-setup-dialog")'
410           '.childNodes[3].hidden == true',
411           tab_index, windex, 1):
412         break;
413
414     self.assertTrue(
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')
419
420   def Connect(self, access_code, tab_index=1, windex=0):
421     """Connects to a Chromoting host and starts the session."""
422     self.assertTrue(
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')
428
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'])
433
434     self.assertTrue(
435         self._ExecuteAndWaitForMode(
436             'document.getElementById("change-daemon-pin").click();',
437             'HOST_SETUP_ASK_PIN', tab_index, windex),
438         msg='Cannot change daemon pin')
439     self.assertTrue(
440         self._WaitForJavascriptCondition(
441             'document.getElementById("ask-pin-form").hidden == false',
442             tab_index, windex),
443         msg='No ask pin dialog')
444
445     self._ExecuteJavascript(
446         'document.getElementById("daemon-pin-entry").value = "' + pin + '";',
447         tab_index, windex)
448     self._ExecuteJavascript(
449         'document.getElementById("daemon-pin-confirm").value = "' +
450         pin + '";', tab_index, windex)
451     self.assertTrue(
452         self._ExecuteAndWaitForMode(
453             'document.getElementById("daemon-pin-ok").click();',
454             'HOST_SETUP_PROCESSING', tab_index, windex),
455         msg='Host setup was not started')
456
457     # Handles preference panes
458     self.assertTrue(
459         self._WaitForJavascriptCondition(
460             'remoting.currentMode == remoting.AppMode.HOST_SETUP_DONE',
461             tab_index, windex),
462         msg='Host setup was not done')
463
464     # Dismisses the host config done dialog
465     self.assertTrue(
466         self._WaitForJavascriptCondition(
467             'document.getElementById("host-setup-dialog")'
468             '.childNodes[5].hidden == false',
469             tab_index, windex),
470         msg='No host setup done dialog')
471     self.assertTrue(
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')
476
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();',
481         tab_index, windex)
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();',
487         tab_index, windex)
488
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."""
492
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();',
500           tab_index, windex)
501
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.
507       time.sleep(2)
508
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:
522           break
523       if this_host_connect_enabled:
524         break;
525
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();',
531           tab_index, windex)
532
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.
536       time.sleep(2)
537
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)
545         if pin_form_exposed:
546           break
547
548       if pin_form_exposed:
549         break
550
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")'
558             '.click();',
559             tab_index, windex)
560
561     self._ExecuteJavascript(
562         'document.getElementById("pin-entry").value = "' + pin + '";',
563         tab_index, windex)
564     self.assertTrue(
565         self._ExecuteAndWaitForMode(
566             'document.getElementById("pin-form").childNodes[5].click();',
567             mode, tab_index, windex),
568         msg='Session was not started')
569
570   def Disconnect(self, tab_index=1, windex=0):
571     """Disconnects from the Chromoting it2me session on the client side."""
572     self.assertTrue(
573         self._ExecuteAndWaitForMode(
574             'remoting.disconnect();',
575             'CLIENT_SESSION_FINISHED_IT2ME', tab_index, windex),
576         msg='Disconnecting it2me session from the client side failed')
577
578   def DisconnectMe2Me(self, confirmation=True, tab_index=1, windex=0):
579     """Disconnects from the Chromoting me2me session on the client side."""
580     self.assertTrue(
581         self._ExecuteAndWaitForMode(
582             'remoting.disconnect();',
583             'CLIENT_SESSION_FINISHED_ME2ME', tab_index, windex),
584         msg='Disconnecting me2me session from the client side failed')
585
586     if confirmation:
587       self.assertTrue(
588           self._ExecuteAndWaitForMode(
589               'document.getElementById("client-finished-me2me-button")'
590               '.click();', 'HOME', tab_index, windex),
591           msg='Failed to dismiss session finished dialog')
592
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();',
597         tab_index, windex)
598
599     # pyauto _GetResultFromJSONRequest throws JSONInterfaceError after
600     # a long time out if WaitUntil is called right after click.
601     time.sleep(2)
602
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)
609       if pin_form_exposed:
610         break
611
612     self._ExecuteJavascript(
613         'document.getElementById("pin-entry").value = "' + pin + '";',
614         tab_index, windex)
615     self.assertTrue(
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')