summaryrefslogtreecommitdiffstats
path: root/chrome/test/pyautolib
diff options
context:
space:
mode:
authordtu@chromium.org <dtu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-22 22:08:25 +0000
committerdtu@chromium.org <dtu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-22 22:08:25 +0000
commit0adf649c85353e90478a994dac74e9f1575b7e6e (patch)
tree1b342ab4087cadfdb8921a8dda8ee5925425c46b /chrome/test/pyautolib
parent1b0b20fe03d3546987ded8a46acb3e9212c4ec5f (diff)
downloadchromium_src-0adf649c85353e90478a994dac74e9f1575b7e6e.zip
chromium_src-0adf649c85353e90478a994dac74e9f1575b7e6e.tar.gz
chromium_src-0adf649c85353e90478a994dac74e9f1575b7e6e.tar.bz2
Adds a PyAuto test, chromoting_basic.ChromotingBasic.testChromoting. This test installs and launches the Chromoting extension on two machines, authenticates (with help from a user), establishes a Chromoting connection, and verifies that the Chromoting share started.
Adds a dependency, ChromotingMixIn, that contains Chromoting-specific methods. A PyUITest can add the MixIn as a base class to gain the additional functionality. ChromotingBasic is an example of this. Adds support for MixIns to PyAuto Remote. Any MixIns will be automatically shipped over to the RemoteHost, making its functionality available on both machines. Adds support for multiple RemoteHosts. You can connect to them by doing python test_name.py --remote-host=ip1,ip2,ip3 Additional tweaks to PyAuto Remote to make it more palatable for MixIns. BUG=chromium:92698,chromium:86890,chromium:92702 TEST=Run in first terminal/machine: "python chrome/test/functional/remote_host_functional.py". Run in second terminal: "python chrome/test/functional/chromoting_basic.py --remote-host=127.0.0.1" Review URL: http://codereview.chromium.org/7648046 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97749 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/pyautolib')
-rw-r--r--chrome/test/pyautolib/chromoting.py186
-rw-r--r--chrome/test/pyautolib/pyauto.py130
-rw-r--r--chrome/test/pyautolib/remote_host.py43
3 files changed, 272 insertions, 87 deletions
diff --git a/chrome/test/pyautolib/chromoting.py b/chrome/test/pyautolib/chromoting.py
new file mode 100644
index 0000000..1112223
--- /dev/null
+++ b/chrome/test/pyautolib/chromoting.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+
+
+class ChromotingMixIn(object):
+ """MixIn for PyUITest that adds Chromoting-specific methods.
+
+ Prepend it as a base class of a test to enable Chromoting functionality.
+ This is a separate class from PyUITest to avoid namespace collisions.
+
+ Example usage:
+ class ChromotingExample(chromoting.ChromotingMixIn, pyauto.PyUITest):
+ def testShare(self):
+ app = self.InstallApp(self.GetIT2MeAppPath())
+ self.LaunchApp(app)
+ self.Authenticate()
+ self.SetHostMode()
+ self.assertTrue(self.Share())
+ """
+
+ def _ExecuteJavascript(self, command, tab_index, windex):
+ """Helper that returns immediately after running a Javascript command."""
+ return self.ExecuteJavascript(
+ '%s; window.domAutomationController.send("done");' % command,
+ windex, tab_index)
+
+ def _WaitForJavascriptCondition(self, condition, tab_index, windex):
+ """Waits until the Javascript condition is true.
+
+ This is different from a naive self.WaitUntil(lambda: self.GetDOMValue())
+ because it uses Javascript to check the condition instead of Python.
+ """
+ return self.WaitUntil(lambda: self.GetDOMValue(
+ '(%s) ? "1" : ""' % condition, windex, tab_index))
+
+ def _ExecuteAndWaitForMode(self, command, mode, tab_index, windex):
+ self.assertTrue(self._ExecuteJavascript(command, tab_index, windex),
+ 'Javascript command did not return anything.')
+ return self._WaitForJavascriptCondition(
+ 'remoting.currentMode == remoting.AppMode.%s' % mode,
+ tab_index, windex)
+
+ def _ExecuteAndWaitForMajorMode(self, command, mode, tab_index, windex):
+ self.assertTrue(self._ExecuteJavascript(command, tab_index, windex),
+ 'Javascript command did not return anything.')
+ return self._WaitForJavascriptCondition(
+ 'remoting.getMajorMode() == remoting.AppMode.%s' % mode,
+ tab_index, windex)
+
+ def GetIT2MeAppPath(self):
+ """Returns the path to the IT2Me App.
+
+ Expects the IT2Me webapp to be in the same place as the pyautolib binaries.
+ """
+ return os.path.join(self.BrowserPath(), 'remoting', 'it2me.webapp')
+
+ def Authenticate(self, email=None, password=None, otp=None,
+ tab_index=1, windex=0):
+ """Logs a user in for Chromoting and accepts permissions for the app.
+
+ PyAuto tests start with a clean profile, so Chromoting tests should call
+ this for every run after launching the app.
+
+ Raises:
+ AssertionError if the authentication flow changes or
+ the credentials are incorrect.
+ """
+ self._ExecuteJavascript('remoting.oauth2.doAuthRedirect();',
+ tab_index, windex)
+ self.assertTrue(
+ self._WaitForJavascriptCondition('document.getElementById("signIn")',
+ tab_index, windex),
+ msg='Unable to redirect for authentication.')
+
+ if email:
+ self._ExecuteJavascript('document.getElementById("Email").value = "%s";'
+ 'document.getElementById("Passwd").focus();'
+ % email, tab_index, windex)
+
+ if password:
+ self._ExecuteJavascript('document.getElementById("Passwd").value = "%s";'
+ 'document.getElementById("signIn").click();'
+ % password, tab_index, windex)
+
+ if otp:
+ self.assertTrue(
+ self._WaitForJavascriptCondition(
+ 'document.getElementById("smsVerifyPin")',
+ tab_index, windex),
+ msg='Invalid username or password.')
+ self._ExecuteJavascript('document.getElementById("smsUserPin").value = '
+ '"%s";'
+ 'document.getElementById("smsVerifyPin").click();'
+ % otp, tab_index, windex)
+
+ # Approve access.
+ self.assertTrue(
+ self._WaitForJavascriptCondition(
+ 'document.getElementById("submit_approve_access")',
+ tab_index, windex),
+ msg='Authentication failed. The username, password, or otp is invalid.')
+ self._ExecuteJavascript(
+ 'document.getElementById("submit_approve_access").click();',
+ tab_index, windex)
+
+ # Wait for some things to be ready.
+ self.assertTrue(
+ self._WaitForJavascriptCondition(
+ 'window.remoting && remoting.oauth2 && '
+ 'remoting.oauth2.isAuthenticated()',
+ tab_index, windex),
+ msg='OAuth2 authentication failed.')
+ self.assertTrue(
+ self._WaitForJavascriptCondition(
+ 'window.localStorage.getItem("remoting-email")',
+ tab_index, windex),
+ msg='Chromoting app did not reload after authentication.')
+
+ def SetHostMode(self, tab_index=1, windex=0):
+ """Sets the Chromoting app to host mode, perfect for sharing!
+
+ Returns:
+ True on success; False otherwise.
+ """
+ return self._ExecuteAndWaitForMajorMode(
+ 'remoting.setAppMode(remoting.AppMode.HOST);',
+ 'HOST', tab_index, windex)
+
+ def SetClientMode(self, tab_index=1, windex=0):
+ """Sets the Chromoting app to client mode, perfect for connecting to a host!
+
+ Returns:
+ True on success; False otherwise.
+ """
+ return self._ExecuteAndWaitForMajorMode(
+ 'remoting.setAppMode(remoting.AppMode.CLIENT);',
+ 'CLIENT', tab_index, windex)
+
+ def Share(self, tab_index=1, windex=0):
+ """Generates an access code and waits for incoming connections.
+
+ Returns:
+ The access code on success; None otherwise.
+ """
+ self._ExecuteAndWaitForMode(
+ 'remoting.tryShare();',
+ 'HOST_WAITING_FOR_CONNECTION', tab_index, windex)
+ return self.GetDOMValue(
+ 'document.getElementById("access-code-display").innerText',
+ windex, tab_index)
+
+ def Connect(self, access_code, tab_index=1, windex=0):
+ """Connects to a Chromoting host and starts the session.
+
+ Returns:
+ True on success; False otherwise.
+ """
+ return self._ExecuteAndWaitForMode(
+ 'document.getElementById("access-code-entry").value = "%s";'
+ 'remoting.tryConnect();' % access_code,
+ 'IN_SESSION', tab_index, windex)
+
+ def CancelShare(self, tab_index=1, windex=0):
+ """Stops sharing the desktop on the host side.
+
+ Returns:
+ True on success; False otherwise.
+ """
+ return self._ExecuteAndWaitForMode(
+ 'remoting.cancelShare();',
+ 'HOST_UNSHARED', tab_index, windex)
+
+ def Disconnect(self, tab_index=1, windex=0):
+ """Disconnects from the Chromoting session on the client side.
+
+ Returns:
+ True on success; False otherwise.
+ """
+ return self._ExecuteAndWaitForMode(
+ 'remoting.disconnect();',
+ 'CLIENT_SESSION_FINISHED', tab_index, windex)
diff --git a/chrome/test/pyautolib/pyauto.py b/chrome/test/pyautolib/pyauto.py
index 89ef512..8a2e3d4 100644
--- a/chrome/test/pyautolib/pyauto.py
+++ b/chrome/test/pyautolib/pyauto.py
@@ -132,11 +132,17 @@ class PyUITest(pyautolib.PyUITestBase, unittest.TestCase):
homepage = kwargs.get('homepage', 'about:blank')
pyautolib.PyUITestBase.__init__(self, clear_profile, homepage)
- # Figure out path to chromium binaries
- browser_dir = os.path.normpath(os.path.dirname(pyautolib.__file__))
- self.Initialize(pyautolib.FilePath(browser_dir))
+ self.Initialize(pyautolib.FilePath(self.BrowserPath()))
unittest.TestCase.__init__(self, methodName)
+ # Set up remote proxies, if they were requested.
+ self.remotes = []
+ self.remote = None
+ global _REMOTE_PROXY
+ if _REMOTE_PROXY:
+ self.remotes = _REMOTE_PROXY
+ self.remote = _REMOTE_PROXY[0]
+
def __del__(self):
pyautolib.PyUITestBase.__del__(self)
@@ -166,7 +172,9 @@ class PyUITest(pyautolib.PyUITestBase, unittest.TestCase):
When using the named interface, it connects to an existing browser
instance.
"""
- named_channel_id = _OPTIONS.channel_id
+ named_channel_id = None
+ if _OPTIONS:
+ named_channel_id = _OPTIONS.channel_id
if self.IsChromeOS(): # Enable testing interface on ChromeOS.
if self.get_clear_profile():
self.CleanupBrowserProfileOnChromeOS()
@@ -186,9 +194,31 @@ class PyUITest(pyautolib.PyUITestBase, unittest.TestCase):
if self.IsChromeOS():
self.WaitUntil(lambda: not self.GetNetworkInfo()['offline_mode'])
+ # If we are connected to any RemoteHosts, create PyAuto
+ # instances on the remote sides and set them up too.
+ for remote in self.remotes:
+ remote.CreateTarget(self)
+ remote.setUp()
+
def tearDown(self):
+ for remote in self.remotes:
+ remote.tearDown()
+
self.TearDown() # Destroy browser
+ # Method required by the Python standard library unittest.TestCase.
+ def runTest(self):
+ pass
+
+ @staticmethod
+ def BrowserPath():
+ """Returns the path to Chromium binaries.
+
+ Expects the browser binaries to be in the
+ same location as the pyautolib binaries.
+ """
+ return os.path.normpath(os.path.dirname(pyautolib.__file__))
+
def ExtraChromeFlags(self):
"""Return a list of extra chrome flags to use with Chrome for testing.
@@ -3719,26 +3749,39 @@ class _RemoteProxy():
class RemoteException(Exception):
pass
- def __init__(self, address, port=7410, remote_class=PyUITest):
- # Make remote-call versions of all remote_class methods.
- for method_name, _ in inspect.getmembers(remote_class, inspect.ismethod):
- # Ignore private methods.
- if method_name[0] in string.letters:
- setattr(self, method_name, functools.partial(
- self.Call, method_name))
- self.Connect(address, port)
+ def __init__(self, address, port=7410):
+ self.RemoteConnect(address, port)
- def Connect(self, address, port=7410):
+ def RemoteConnect(self, address, port):
self._socket = socket.socket()
self._socket.connect((address, port))
- def Disconnect(self):
+ def RemoteDisconnect(self):
if self._socket:
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
self._socket = None
- def Call(self, method_name, *args, **kwargs):
+ def CreateTarget(self, target):
+ """Registers the methods and creates a remote instance of a target.
+
+ Any RPC calls will then be made on the remote target instance. Note that the
+ remote instance will be a brand new instance and will have none of the state
+ of the local instance. The target's class should have a constructor that
+ takes no arguments.
+ """
+ self._Call('CreateTarget', target.__class__)
+ self._RegisterClassMethods(target)
+
+ def _RegisterClassMethods(self, remote_class):
+ # Make remote-call versions of all remote_class methods.
+ for method_name, _ in inspect.getmembers(remote_class, inspect.ismethod):
+ # Ignore private methods and duplicates.
+ if method_name[0] in string.letters and \
+ getattr(self, method_name, None) is None:
+ setattr(self, method_name, functools.partial(self._Call, method_name))
+
+ def _Call(self, method_name, *args, **kwargs):
# Send request.
request = pickle.dumps((method_name, args, kwargs))
if self._socket.send(request) != len(request):
@@ -3759,41 +3802,6 @@ class _RemoteProxy():
return result
-class PyRemoteUITest(PyUITest):
- """Convenience class for tests that use a single RemoteProxy.
-
- This class automatically sets up and tears down a RemoteProxy PyAuto instance.
- setUp() fires off both remote and local Chrome instances in order.
- tearDown() closes both Chrome instances in the same order.
-
- Example usage:
- class MyTest(pyauto.PyUIRemoteTest):
- def testRemoteExample(self):
- self.remote.NavigateToURL('http://www.google.com')
- title = self.remote.GetActiveTabTitle()
- self.assertEqual(title, 'Google')
-
- $ # On remote machine:
- $ python remote_host.py remote_host.RemoteHost.RunHost
- $ # On local machine:
- $ python my_test.py --remote_host=127.0.0.1
- """
-
- def __init__(self, *args, **kwargs):
- global _REMOTE_PROXY
- assert _REMOTE_PROXY, 'Not connected to remote host.'
- self.remote = _REMOTE_PROXY
- PyUITest.__init__(self, *args, **kwargs)
-
- def setUp(self):
- self.remote.Call('RemoteSetUp')
- PyUITest.setUp(self)
-
- def tearDown(self):
- self.remote.Call('RemoteTearDown')
- PyUITest.tearDown(self)
-
-
class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite):
"""Base TestSuite for PyAuto UI tests."""
@@ -3813,10 +3821,10 @@ class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite):
# Start http server, if needed.
global _OPTIONS
- if not _OPTIONS.no_http_server:
+ if _OPTIONS and not _OPTIONS.no_http_server:
self._StartHTTPServer()
- if _OPTIONS.remote_host:
- self._ConnectToRemoteHost(_OPTIONS.remote_host)
+ if _OPTIONS and _OPTIONS.remote_host:
+ self._ConnectToRemoteHosts(_OPTIONS.remote_host.split(','))
def __del__(self):
# python unittest module is setup such that the suite gets deleted before
@@ -3853,13 +3861,15 @@ class PyUITestSuite(pyautolib.PyUITestSuiteBase, unittest.TestSuite):
_HTTP_SERVER = None
logging.debug('Stopped http server.')
- def _ConnectToRemoteHost(self, address):
- """Connect to a remote PyAuto instance using a RemoteProxy.
+ def _ConnectToRemoteHosts(self, addresses):
+ """Connect to remote PyAuto instances using a RemoteProxy.
- The RemoteHost instance must already be running."""
+ The RemoteHost instances must already be running."""
global _REMOTE_PROXY
assert not _REMOTE_PROXY, 'Already connected to a remote host.'
- _REMOTE_PROXY = _RemoteProxy(address)
+ _REMOTE_PROXY = []
+ for address in addresses:
+ _REMOTE_PROXY.append(_RemoteProxy(address))
class _GTestTextTestResult(unittest._TextTestResult):
@@ -3902,7 +3912,7 @@ class _GTestTextTestResult(unittest._TextTestResult):
self.stream.writeln('[ FAILED ] %s' % self._GetTestURI(test))
-class PyAutoTextTestRuner(unittest.TextTestRunner):
+class PyAutoTextTestRunner(unittest.TextTestRunner):
"""Test Runner for PyAuto tests that displays results in textual format.
Results are displayed in conformance with gtest output.
@@ -3970,7 +3980,7 @@ class Main(object):
help='Do not start an http server to serve files in data dir.')
parser.add_option(
'', '--remote-host', type='string', default=None,
- help='Connect to a remote host for remote automation.')
+ help='Connect to remote hosts for remote automation.')
parser.add_option(
'', '--http-data-dir', type='string',
default=os.path.join('chrome', 'test', 'data'),
@@ -4200,7 +4210,7 @@ class Main(object):
verbosity = 1
if self._options.verbose:
verbosity = 2
- result = PyAutoTextTestRuner(verbosity=verbosity).run(pyauto_suite)
+ result = PyAutoTextTestRunner(verbosity=verbosity).run(pyauto_suite)
del loaded_tests # Need to destroy test cases before the suite
del pyauto_suite
successful = result.wasSuccessful()
diff --git a/chrome/test/pyautolib/remote_host.py b/chrome/test/pyautolib/remote_host.py
index 3b168d1..9d9e2c9 100644
--- a/chrome/test/pyautolib/remote_host.py
+++ b/chrome/test/pyautolib/remote_host.py
@@ -4,14 +4,14 @@
# found in the LICENSE file.
import cStringIO
+import os
import pickle
import socket
import sys
import pyauto
-
-class RemoteHost(pyauto.PyUITest):
+class RemoteHost(object):
"""Class used as a host for tests that use the PyAuto RemoteProxy.
This class fires up a listener which waits for a connection from a RemoteProxy
@@ -20,24 +20,8 @@ class RemoteHost(pyauto.PyUITest):
can connect to and automate using pyauto.RemoteProxy.
"""
def __init__(self, *args, **kwargs):
- pyauto.PyUITest.__init__(self, *args, **kwargs)
self.StartSocketServer()
- # We give control of setUp and tearDown to the RemoteProxy, so disable them.
- # The RemoteProxy can call RemoteSetUp() and RemoteTearDown() to do the normal
- # setUp() and tearDown().
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def RemoteSetUp(self):
- pyauto.PyUITest.setUp(self)
-
- def RemoteTearDown(self):
- pyauto.PyUITest.tearDown(self)
-
def StartSocketServer(self, port=7410):
listening_socket = socket.socket()
listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -45,6 +29,9 @@ class RemoteHost(pyauto.PyUITest):
listening_socket.listen(1)
self._socket, _ = listening_socket.accept()
+ while self.Connected():
+ self._HandleRPC()
+
def StopSocketServer(self):
if self._socket:
self._socket.shutdown(socket.SHUT_RDWR)
@@ -54,6 +41,13 @@ class RemoteHost(pyauto.PyUITest):
def Connected(self):
return self._socket
+ def CreateTarget(self, target_class):
+ """Creates an instance of the specified class to serve as the RPC target.
+
+ RPC calls can be made on the target.
+ """
+ self.target = target_class()
+
def _HandleRPC(self):
"""Receives a method call request over the socket and executes the method.
@@ -78,7 +72,10 @@ class RemoteHost(pyauto.PyUITest):
result = None
exception = None
try:
- result = getattr(self, request[0])(*request[1], **request[2])
+ if getattr(self, request[0], None):
+ result = getattr(self, request[0])(*request[1], **request[2])
+ else:
+ result = getattr(self.target, request[0])(*request[1], **request[2])
except BaseException as e:
exception = (e.__class__.__name__, str(e))
@@ -91,11 +88,3 @@ class RemoteHost(pyauto.PyUITest):
exception))
if self._socket.send(response) != len(response):
self.StopSocketServer()
-
- def RunHost(self):
- while self.Connected():
- self._HandleRPC()
-
-
-if __name__ == '__main__':
- pyauto.Main()