Commits

Fred Grott committed fe5c7fe

added a viewserver integration what is left to add is Sikulichimp

Comments (0)

Files changed (14)

 <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
 <path>/AmblrMonkeyRunnerTest/src</path>
 <path>/AmblrMonkeyRunnerTest/mytests</path>
+<path>/AmblrMonkeyRunnerTest/testssrc</path>
+</pydev_pathproperty>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">jython 2.7</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">MonkeyRunner</pydev_property>
+<pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
+<path>/home/fredgrott/opt/android/android-sdk-linux/tools/lib/monkeyrunner.jar</path>
 </pydev_pathproperty>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">jython 3.0</pydev_property>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Jython-AndroidSDK</pydev_property>
 </pydev_project>
 
 Right now use MonkeyRunner bare, however may integrate the
 aster framework(googlecode) to integrate with more features later in testing.
+
+Architecture
+============
+
+We wire ViewServer integration, ideas borrowed from DT Milano. 
+
+Cautions
+========
+
+There currently is a bug in EasyMonkeyDevice MonkeyDevice.getText:
+http://dtmilano.blogspot.com/2012/01/monkeyrunner-testing-views-properties.html
+
+EasyMonkeyDevice will look for text:mText when we actually have mText.
+Patch has not been fully applied to a SDK release so will use a work around.
+
+Credits
+=======
+
+DT Milano came up with the python code to integrate with ViewServer:
+http://dtmilano.blogspot.com/2012/02/monkeyrunner-interacting-with-views.html
+

src/org/__init__.py

Empty file added.

src/org/bitbucket/__init__.py

Empty file added.

src/org/bitbucket/fredgrott/__init__.py

Empty file added.

src/org/bitbucket/fredgrott/amblrmonkeyrunnertest/__init__.py

Empty file added.

src/org/bitbucket/fredgrott/amblrmonkeyrunnertest/viewclient.py

+'''
+
+Original Copyright 2012 DT Milano,
+see: http://dtmilano.blogspot.com/2012/02/monkeyrunner-interacting-with-views.html
+Copyright 2012, Apache 2.0 License, Fred Grott
+Feb 19, 2012
+
+ideas borrowed from DT Milano
+
+@author fredgrott
+
+'''
+
+# imports
+
+import sys
+import subprocess
+import re
+import socket
+import os
+
+DEBUG = False
+
+ANDROID_HOME = os.environ['ANDROID_HOME'] if os.environ.has_key('ANDROID_HOME') else '/opt/android-sdk'
+VIEW_SERVER_HOST = 'localhost'
+VIEW_SERVER_PORT = 4939
+
+STATUS_BAR = 38
+TITLE = 40
+CHECK_BOX = 50
+
+
+class View:
+    '''
+    View class
+    '''
+    
+    def __init__(self, map, device):
+        '''
+        Constructor
+        '''
+        
+        self.map = map
+        self.device = device
+        
+    def __getitem__(self, key):
+        return self.map[key]
+        
+    def __getattr__(self, name):
+        if DEBUG:
+            print >>sys.stderr, "__getattr__(%s)" % (name)
+        
+        # I should try to see if 'name' is a defined method
+        # but it seems that if I call locals() here an infinite loop is entered
+        
+        if self.map.has_key(name):
+            r = self.map[name]
+        elif self.map.has_key(name + '()'):
+            # the method names are stored in the map with their trailing '()'
+            r = self.map[name + '()']
+        elif name.count("_") > 0:
+            mangledList = self.allPossibleNamesWithColon(name)
+            mangledName = self.intersection(mangledList, self.map.keys())
+            if len(mangledName) > 0:
+                r = self.map[mangledName[0]]
+            else:
+                # Default behavior
+                raise AttributeError, name
+        else:
+            # Default behavior
+            raise AttributeError, name
+        
+        # if the method name starts with 'is' let's assume its return value is boolean
+        if name[:2] == 'is':
+            r = True if r == 'true' else False
+        
+        # this should not cached in some way
+        def innerMethod():
+            if DEBUG:
+                print >>sys.stderr, "innerMethod: %s returning %s" % (innerMethod.__name__, r)
+            return r
+        
+        innerMethod.__name__ = name
+        
+        # this should work, but then there's problems with the arguments of innerMethod 
+        # even if innerMethod(self) is added
+        #setattr(View, innerMethod.__name__, innerMethod)
+        #setattr(self, innerMethod.__name__, innerMethod)
+        
+        return innerMethod
+    
+    def __call__(self, *args, **kwargs):
+        if DEBUG:
+            print "__call__(%s)" % (args if args else None)
+            
+    def getXY(self):
+        '''
+        Returns the coordinates of this View
+        '''
+        
+        # FIXME: it's not always a CheckBox
+        x = int(self.map['layout:mLeft']) + int(self.map['layout:layout_leftMargin']) + CHECK_BOX/2
+        y = int(self.map['layout:mTop']) + int(self.map['layout:layout_topMargin']) + STATUS_BAR + TITLE + CHECK_BOX/2
+        return (x, y)
+
+    # FIXME: should be MonkeyDevice.DOWN_AND_UP
+    def touch(self, type="DOWN_AND_UP"):
+        '''
+        Touches this View
+        '''
+        
+        (x, y) = self.getXY()
+        if DEBUG:
+            print >>sys.stderr, "should click @ (%d, %d)" % (x, y)
+        self.device.touch(x, y, type)
+        
+    def allPossibleNamesWithColon(self, name):
+        l = []
+        for i in range(name.count("_")):
+            name = name.replace("_", ":", 1)
+            l.append(name)
+        return l
+
+    def intersection(self, l1, l2):
+        return list(set(l1) & set(l2))
+
+ 
+class ViewClient:
+    '''
+    ViewClient is a ViewServer client.
+    
+    If not running the ViewServer is started on the target device or emulator and then the port
+    mapping is created.
+    '''
+
+    def __init__(self, device, adb=ANDROID_HOME+'/platform-tools/adb'):
+        '''
+        Constructor
+        '''
+        
+        if not device:
+            raise Exception('Device is not connected')
+        if not self.serviceResponse(device.shell('service call window 3')):
+            self.assertServiceResponse(device.shell('service call window 1 i32 %d' %
+                VIEW_SERVER_PORT))
+
+        subprocess.check_call([adb, 'forward', 'tcp:%d' % VIEW_SERVER_PORT,
+                               'tcp:%d' % VIEW_SERVER_PORT])
+
+        self.device = device
+        self.viewsById = {}
+    
+    def assertServiceResponse(self, response):
+        if not self.serviceResponse(response):
+            raise Exception('Invalid response received from service.')
+
+    def serviceResponse(self, response):
+        return response == "Result: Parcel(00000000 00000001   '........')\r\n"
+
+    def dump(self, windowId=-1):
+        '''
+        Dumps the window content
+        '''
+        
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.connect((VIEW_SERVER_HOST, VIEW_SERVER_PORT))
+        s.send('dump %d\r\n' % windowId)
+        received = ""
+        doneRE = re.compile("DONE")
+        while True:
+            received += s.recv(1024)
+            if doneRE.search(received[-7:]):
+                break
+
+        s.close()
+        self.views = received.split("\n")
+        if DEBUG:
+            print "there are %d views in this dump" % len(self.views)
+
+        idRE = re.compile("(?P<viewId>id/\S+)")
+        attrRE = re.compile("(?P<attr>\S+)(\(\))?=\d+,(?P<val>\S+)")
+        hashRE = re.compile("(?P<class>\S+)@(?P<oid>[0-9a-f]+)")
+
+        for v in self.views:
+            attrs = {}
+            m = idRE.search(v)
+            if m:
+                viewId = m.group('viewId')
+                if DEBUG:
+                    print "found %s" % viewId
+                for attr in v.split():
+                    m = attrRE.match(attr)
+                    if m:
+                        attrs[m.group('attr')] = m.group('val')                    
+                    else:
+                        m = hashRE.match(attr)
+                        if m:
+                            attrs['class'] = m.group('class')
+                            attrs['oid'] = m.group('oid')
+                        else:
+                            if DEBUG:
+                                print attr, "doesn't match"
+
+                    
+                if viewId in self.viewsById:
+                    # sometimes the view ids are not unique, so let's generate a unique id here
+                    i = 1
+                    while True:
+                        newId = viewId + '/%d' % i
+                        if not newId in self.viewsById:
+                            break
+                        i += 1
+                    viewId = newId
+                if DEBUG:
+                    print "adding viewById %s" % viewId
+                self.viewsById[viewId] = attrs
+
+        return self.views
+
+    def findViewById(self, viewId):
+        '''
+        Finds the View with the specified viewId.
+        '''
+        return View(self.viewsById[viewId], self.device)
+
+    def getViewIds(self):
+        '''
+        Returns the Views map.
+        '''
+        return self.viewsById
+
+
+if __name__ == "__main__":
+    try:
+        vc = ViewClient(None)
+    except:
+        print "Don't expect this to do anything"
+
+        

testssrc/org/__init__.py

Empty file added.

testssrc/org/allTests.py

+'''
+Original Copyright DT Milano
+see;
+Modifications Copyright 2012 Fred Grott
+
+
+'''
+
+import unittest
+
+
+if __name__ == "__main__":
+    import sys
+    sys.argv = ['', 'ViewTest.testName']
+    import org.bitbucket.fredgrott.amblrmonkeyrunnertest.viewclient
+    unittest.main()

testssrc/org/bitbucket/__init__.py

Empty file added.

testssrc/org/bitbucket/fredgrott/__init__.py

Empty file added.

testssrc/org/bitbucket/fredgrott/amblrmonkeyrunnertest/__init__.py

Empty file added.

testssrc/org/bitbucket/fredgrott/amblrmonkeyrunnertest/mocks.py

+'''
+Original Code Copyright DT Milano 2012
+See: http://dtmilano.blogspot.com/2012/02/monkeyrunner-interacting-with-views.html
+Modifications Copyright 2012 apche License 2.0 Fred Grott
+
+'''
+
+import re
+
+TRUE_PARCEL = "Result: Parcel(00000000 00000001   '........')\r\n"
+FALSE_PARCEL = "Result: Parcel(00000000 00000000   '........')\r\n"
+
+class MockDevice(object):
+    '''
+    Mocks an Android device
+    '''
+
+
+    def __init__(self):
+        '''
+        Constructor
+        '''
+        
+        pass
+        
+    def shell(self, cmd):
+        if cmd == 'service call window 3':
+            return FALSE_PARCEL
+        elif re.compile('service call window 1 i32 \d+').match(cmd):
+            return TRUE_PARCEL
+        

testssrc/org/bitbucket/fredgrott/amblrmonkeyrunnertest/viewclientme.py

+'''
+  Oringal Copright by DT Milano, see:
+  http://dtmilano.blogspot.com/2012/02/monkeyrunner-interacting-with-views.html
+  Copyright 2012 by Fred Grott Apache 2.0 License
+  ideas borrowed from DT Milano
+  
+  @author fredgrott
+  
+'''
+
+import sys
+import os
+import unittest
+try:
+    ANDROID_VIEW_CLIENT_HOME = os.environ['ANDROID_VIEW_CLIENT_HOME']
+except KeyError:
+    print >>sys.stderr, "%s: ERROR: ANDROID_VIEW_CLIENT_HOME not set in environment" % __file__
+    sys.exit(1)
+sys.path.append(ANDROID_VIEW_CLIENT_HOME + '/src')
+from org.bitbucket.fredgrott.amblrmonkeyrunnertest.viewclient import  *
+from org.bitbucket.fredgrott.amblrmonkeyrunnertest.mocks import MockDevice
+
+
+class ViewTest(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def testInnerMethod(self):
+        v = View({'isChecked()':'true'}, None)
+        self.assertTrue(v.isChecked())
+        v.map['isChecked()'] = 'false'
+        self.assertFalse(v.isChecked(), "Expected False but is %s {%s}" % (v.isChecked(), v.map['isChecked()']) )
+        self.assertFalse(v.isChecked())
+        v.map['other'] = 1
+        self.assertEqual(1, v.other())
+        v.map['evenMore'] = "ABC"
+        self.assertEqual("ABC", v.evenMore())
+        v.map['more'] = "abc"
+        v.map['more'] = v.evenMore()
+        self.assertEqual("ABC", v.more())
+        v.map['isMore()'] = 'true'
+        self.assertTrue(v.isMore())
+
+    def testName_Layout_mLeft(self):
+        v = View({'layout:mLeft':200}, None)
+        self.assertEqual(200, v.layout_mLeft())
+        
+    def testNameWithColon_this_is_a_fake_name(self):
+        v = View({'this:is_a_fake_name':1}, None)
+        self.assertEqual(1, v.this_is_a_fake_name())
+
+    def testNameWith2Colons_this_is_another_fake_name(self):
+        v = View({'this:is:another_fake_name':1}, None)
+        self.assertEqual(1, v.this_is_another_fake_name())
+        
+    def testInexistentMethodName(self):
+        v = View({'foo':1}, None)
+        try:
+            v.bar()
+            raise Exception("AttributeError not raised")
+        except AttributeError:
+            pass
+
+
+class ViewClientTest(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+    
+    def testExceptionDeviceNotConnected(self):
+        try:
+            vc = ViewClient(None)
+        except Exception, e:
+            self.assertEqual('Device is not connected', e.message)
+            
+    def testConstructor(self):
+        vc = ViewClient(MockDevice(), adb='/usr/bin/true')
+        self.assertNotEquals(None, vc)
+
+         
+if __name__ == "__main__":
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()