18
18
@author: Diego Torres Milano
19
19
'''
20
20
21
- __version__ = '3.2 .0'
21
+ __version__ = '4.0 .0'
22
22
23
23
import sys
24
24
import subprocess
25
25
import re
26
26
import socket
27
27
import os
28
- import java
29
28
import types
30
29
import time
31
30
import signal
32
31
import warnings
33
32
import copy
34
33
import pickle
34
+ import platform
35
35
import xml .parsers .expat
36
- import org .python .modules .sre .PatternObject
37
- from com .android .monkeyrunner import MonkeyDevice , MonkeyRunner
36
+ from com .dtmilano .android .adb import adbclient
38
37
39
38
DEBUG = False
40
39
DEBUG_DEVICE = DEBUG and False
60
59
''' This assumes the smallest touchable view on the screen is approximately 50px x 50px
61
60
and touches it at M{(x+OFFSET, y+OFFSET)} '''
62
61
63
- USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES = True
64
- ''' Use monkeyrunner (C{MonkeyDevice.getProperty()}) to obtain the needed properties. If this is
62
+ USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES = True
63
+ ''' Use C{AdbClient} to obtain the needed properties. If this is
65
64
C{False} then C{adb shell getprop} is used '''
66
65
67
66
SKIP_CERTAIN_CLASSES_IN_GET_XY_ENABLED = False
68
67
''' Skips some classes related with the Action Bar and the PhoneWindow$DecorView in the
69
68
coordinates calculation
70
69
@see: L{View.getXY()} '''
71
70
71
+ VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED = False
72
+ ''' Under some conditions the touch event should be longer [t(DOWN) << t(UP)]. C{True} enables a
73
+ workaround to delay the events.'''
74
+
72
75
# some device properties
73
- VERSION_SDK_PROPERTY = 'version.sdk'
76
+ VERSION_SDK_PROPERTY = 'ro.build.version.sdk'
77
+ VERSION_RELEASE_PROPERTY = 'ro.build.version.release'
74
78
75
79
# some constants for the attributes
76
80
ID_PROPERTY = 'mID'
95
99
INVISIBLE = 0x4
96
100
GONE = 0x8
97
101
102
+ RegexType = type (re .compile ('' ))
98
103
IP_RE = re .compile ('^(\d{1,3}\.){3}\d{1,3}$' )
99
104
ID_RE = re .compile ('id/([^/]*)(/(\d+))?' )
100
105
101
- def __nd (name ):
106
+ def _nd (name ):
102
107
'''
103
108
@return: Returns a named decimal regex
104
109
'''
105
110
return '(?P<%s>\d+)' % name
106
111
107
- def __nh (name ):
112
+ def _nh (name ):
108
113
'''
109
114
@return: Returns a named hex regex
110
115
'''
111
116
return '(?P<%s>[0-9a-f]+)' % name
112
117
113
- def __ns (name , greedy = False ):
118
+ def _ns (name , greedy = False ):
114
119
'''
115
120
NOTICE: this is using a non-greedy (or minimal) regex
116
121
@@ -179,7 +184,7 @@ class ViewNotFoundException(Exception):
179
184
'''
180
185
181
186
def __init__ (self , attr , value , root ):
182
- if isinstance (value , org . python . modules . sre . PatternObject ):
187
+ if isinstance (value , RegexType ):
183
188
msg = "Couldn't find View with %s that matches '%s' in tree with root=%s" % (attr , value .pattern , root )
184
189
else :
185
190
msg = "Couldn't find View with %s='%s' in tree with root=%s" % (attr , value , root )
@@ -242,8 +247,8 @@ def __init__(self, map, device, version=-1, forceviewserveruse=False):
242
247
self .build [VERSION_SDK_PROPERTY ] = version
243
248
else :
244
249
try :
245
- if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES :
246
- self .build [VERSION_SDK_PROPERTY ] = int (device .getProperty ('build.' + VERSION_SDK_PROPERTY ))
250
+ if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES :
251
+ self .build [VERSION_SDK_PROPERTY ] = int (device .getProperty (VERSION_SDK_PROPERTY ))
247
252
else :
248
253
self .build [VERSION_SDK_PROPERTY ] = int (device .shell ('getprop ro.build.' + VERSION_SDK_PROPERTY )[:- 2 ])
249
254
except :
@@ -689,20 +694,20 @@ def __dumpWindowsInformation(self, debug=False):
689
694
if DEBUG_WINDOWS or debug : print >> sys .stderr , dww
690
695
lines = dww .split ('\n ' )
691
696
widRE = re .compile ('^ *Window #%s Window{%s (u\d+ )?%s?.*}:' %
692
- (__nd ('num' ), __nh ('winId' ), __ns ('activity' , greedy = True )))
693
- currentFocusRE = re .compile ('^ mCurrentFocus=Window{%s .*' % __nh ('winId' ))
694
- viewVisibilityRE = re .compile (' mViewVisibility=0x%s ' % __nh ('visibility' ))
697
+ (_nd ('num' ), _nh ('winId' ), _ns ('activity' , greedy = True )))
698
+ currentFocusRE = re .compile ('^ mCurrentFocus=Window{%s .*' % _nh ('winId' ))
699
+ viewVisibilityRE = re .compile (' mViewVisibility=0x%s ' % _nh ('visibility' ))
695
700
# This is for 4.0.4 API-15
696
701
containingFrameRE = re .compile ('^ *mContainingFrame=\[%s,%s\]\[%s,%s\] mParentFrame=\[%s,%s\]\[%s,%s\]' %
697
- (__nd ('cx' ), __nd ('cy' ), __nd ('cw' ), __nd ('ch' ), __nd ('px' ), __nd ('py' ), __nd ('pw' ), __nd ('ph' )))
702
+ (_nd ('cx' ), _nd ('cy' ), _nd ('cw' ), _nd ('ch' ), _nd ('px' ), _nd ('py' ), _nd ('pw' ), _nd ('ph' )))
698
703
contentFrameRE = re .compile ('^ *mContentFrame=\[%s,%s\]\[%s,%s\] mVisibleFrame=\[%s,%s\]\[%s,%s\]' %
699
- (__nd ('x' ), __nd ('y' ), __nd ('w' ), __nd ('h' ), __nd ('vx' ), __nd ('vy' ), __nd ('vx1' ), __nd ('vy1' )))
704
+ (_nd ('x' ), _nd ('y' ), _nd ('w' ), _nd ('h' ), _nd ('vx' ), _nd ('vy' ), _nd ('vx1' ), _nd ('vy1' )))
700
705
# This is for 4.1 API-16
701
706
framesRE = re .compile ('^ *Frames: containing=\[%s,%s\]\[%s,%s\] parent=\[%s,%s\]\[%s,%s\]' %
702
- (__nd ('cx' ), __nd ('cy' ), __nd ('cw' ), __nd ('ch' ), __nd ('px' ), __nd ('py' ), __nd ('pw' ), __nd ('ph' )))
707
+ (_nd ('cx' ), _nd ('cy' ), _nd ('cw' ), _nd ('ch' ), _nd ('px' ), _nd ('py' ), _nd ('pw' ), _nd ('ph' )))
703
708
contentRE = re .compile ('^ *content=\[%s,%s\]\[%s,%s\] visible=\[%s,%s\]\[%s,%s\]' %
704
- (__nd ('x' ), __nd ('y' ), __nd ('w' ), __nd ('h' ), __nd ('vx' ), __nd ('vy' ), __nd ('vx1' ), __nd ('vy1' )))
705
- policyVisibilityRE = re .compile ('mPolicyVisibility=%s ' % __ns ('policyVisibility' , greedy = True ))
709
+ (_nd ('x' ), _nd ('y' ), _nd ('w' ), _nd ('h' ), _nd ('vx' ), _nd ('vy' ), _nd ('vx1' ), _nd ('vy1' )))
710
+ policyVisibilityRE = re .compile ('mPolicyVisibility=%s ' % _ns ('policyVisibility' , greedy = True ))
706
711
707
712
for l in range (len (lines )):
708
713
m = widRE .search (lines [l ])
@@ -783,20 +788,20 @@ def __dumpWindowsInformation(self, debug=False):
783
788
if DEBUG_COORDS : print >> sys .stderr , "__dumpWindowsInformation: (0,0)"
784
789
return (0 ,0 )
785
790
786
- def touch (self , type = MonkeyDevice .DOWN_AND_UP ):
791
+ def touch (self , type = adbclient .DOWN_AND_UP ):
787
792
'''
788
793
Touches the center of this C{View}
789
794
'''
790
795
791
796
(x , y ) = self .getCenter ()
792
797
if DEBUG_TOUCH :
793
798
print >> sys .stderr , "should touch @ (%d, %d)" % (x , y )
794
- if type == MonkeyDevice .DOWN_AND_UP :
799
+ if VIEW_CLIENT_TOUCH_WORKAROUND_ENABLED and type == adbclient .DOWN_AND_UP :
795
800
if WARNINGS :
796
801
print >> sys .stderr , "ViewClient: touch workaround enabled"
797
- self .device .touch (x , y , MonkeyDevice .DOWN )
802
+ self .device .touch (x , y , adbclient .DOWN )
798
803
time .sleep (50 / 1000.0 )
799
- self .device .touch (x + 10 , y + 10 , MonkeyDevice .UP )
804
+ self .device .touch (x + 10 , y + 10 , adbclient .UP )
800
805
else :
801
806
self .device .touch (x , y , type )
802
807
@@ -897,7 +902,12 @@ def __str__(self):
897
902
if "class" in self .map :
898
903
__str += " class=" + self .map ["class" ].__str__ () + " "
899
904
for a in self .map :
900
- __str += a + "=" + unicode (self .map [a ], 'utf-8' , 'replace' ) + " "
905
+ __str += a + "="
906
+ if isinstance (self .map [a ], unicode ):
907
+ __str += self .map [a ].encode ('utf-8' , 'replace' )
908
+ else :
909
+ __str += unicode (str (self .map [a ]), 'utf-8' , 'replace' )
910
+ __str += " "
901
911
__str += "] parent="
902
912
if self .parent :
903
913
if "class" in self .parent .map :
@@ -923,18 +933,18 @@ class EditText(TextView):
923
933
924
934
def type (self , text ):
925
935
self .touch ()
926
- MonkeyRunner .sleep (1 )
936
+ time .sleep (1 )
927
937
for c in text :
928
938
if c != ' ' :
929
939
self .device .type (c )
930
940
else :
931
- self .device .press ('KEYCODE_SPACE' , MonkeyDevice .DOWN_AND_UP )
932
- MonkeyRunner .sleep (1 )
941
+ self .device .press ('KEYCODE_SPACE' , adbclient .DOWN_AND_UP )
942
+ time .sleep (1 )
933
943
934
944
def backspace (self ):
935
945
self .touch ()
936
- MonkeyRunner .sleep (1 )
937
- self .device .press ('KEYCODE_DEL' , MonkeyDevice .DOWN_AND_UP )
946
+ time .sleep (1 )
947
+ self .device .press ('KEYCODE_DEL' , adbclient .DOWN_AND_UP )
938
948
939
949
class UiAutomator2AndroidViewClient ():
940
950
'''
@@ -1003,7 +1013,7 @@ def Parse(self, uiautomatorxml):
1003
1013
parser .CharacterDataHandler = self .CharacterData
1004
1014
# Parse the XML File
1005
1015
try :
1006
- parserStatus = parser .Parse (uiautomatorxml , 1 )
1016
+ parserStatus = parser .Parse (uiautomatorxml . encode ( encoding = 'utf-8' , errors = 'replace' ), True )
1007
1017
except xml .parsers .expat .ExpatError , ex :
1008
1018
print >> sys .stderr , "ERROR: Offending XML:\n " , repr (uiautomatorxml )
1009
1019
raise RuntimeError (ex )
@@ -1111,7 +1121,10 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
1111
1121
if not os .access (adb , os .X_OK ):
1112
1122
raise Exception ('adb="%s" is not executable' % adb )
1113
1123
else :
1114
- adb = ViewClient .__obtainAdbPath ()
1124
+ # Using adbclient we don't need adb executable yet (maybe it's needed if we want to
1125
+ # start adb if not running)
1126
+ #adb = ViewClient.__obtainAdbPath()
1127
+ adb = 'ADBCLIENT'
1115
1128
1116
1129
self .adb = adb
1117
1130
''' The adb command '''
@@ -1123,7 +1136,7 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
1123
1136
''' The map containing the device's display properties: width, height and density '''
1124
1137
for prop in [ 'width' , 'height' , 'density' ]:
1125
1138
self .display [prop ] = - 1
1126
- if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES :
1139
+ if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES :
1127
1140
try :
1128
1141
self .display [prop ] = int (device .getProperty ('display.' + prop ))
1129
1142
except :
@@ -1136,11 +1149,12 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
1136
1149
1137
1150
self .build = {}
1138
1151
''' The map containing the device's build properties: version.sdk, version.release '''
1139
- for prop in [VERSION_SDK_PROPERTY , 'version.release' ]:
1152
+
1153
+ for prop in [VERSION_SDK_PROPERTY , VERSION_RELEASE_PROPERTY ]:
1140
1154
self .build [prop ] = - 1
1141
1155
try :
1142
- if USE_MONKEYRUNNER_TO_GET_BUILD_PROPERTIES :
1143
- self .build [prop ] = device .getProperty ('build.' + prop )
1156
+ if USE_ADB_CLIENT_TO_GET_BUILD_PROPERTIES :
1157
+ self .build [prop ] = device .getProperty (prop )
1144
1158
else :
1145
1159
self .build [prop ] = device .shell ('getprop ro.build.' + prop )[:- 2 ]
1146
1160
except :
@@ -1164,14 +1178,16 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
1164
1178
self .forceViewServerUse = forceviewserveruse
1165
1179
''' Force the use of ViewServer even if the conditions to use UiAutomator are satisfied '''
1166
1180
self .useUiAutomator = (self .build [VERSION_SDK_PROPERTY ] >= 16 ) and not forceviewserveruse # jelly bean 4.1 & 4.2
1181
+ if DEBUG :
1182
+ print >> sys .stderr , " ViewClient.__init__: useUiAutomator=" , self .useUiAutomator , "sdk=" , self .build [VERSION_SDK_PROPERTY ], "forceviewserveruse=" , forceviewserveruse
1167
1183
''' If UIAutomator is supported by the device it will be used '''
1168
1184
self .ignoreUiAutomatorKilled = ignoreuiautomatorkilled
1169
1185
''' On some devices (i.e. Nexus 7 running 4.2.2) uiautomator is killed just after generating
1170
1186
the dump file. In many cases the file is already complete so we can ask to ignore the 'Killed'
1171
1187
message by setting L{ignoreuiautomatorkilled} to C{True}.
1172
1188
1173
- Changes in 2 .3.21 that uses C{/dev/tty} instead of a file may have turned this variable
1174
- unnnecessary , however it has been kept for backward compatibility.
1189
+ Changes in v2 .3.21 that uses C{/dev/tty} instead of a file may have turned this variable
1190
+ unnecessary , however it has been kept for backward compatibility.
1175
1191
'''
1176
1192
1177
1193
if self .useUiAutomator :
@@ -1218,7 +1234,7 @@ def __obtainAdbPath():
1218
1234
Obtains the ADB path attempting know locations for different OSs
1219
1235
'''
1220
1236
1221
- osName = java . lang . System . getProperty ( 'os.name' )
1237
+ osName = platform . system ( )
1222
1238
isWindows = False
1223
1239
if osName .startswith ('Windows' ):
1224
1240
adb = 'adb.exe'
@@ -1312,7 +1328,7 @@ def __obtainDeviceSerialNumber(device):
1312
1328
1313
1329
@staticmethod
1314
1330
def setAlarm (timeout ):
1315
- osName = java . lang . System . getProperty ( 'os.name' )
1331
+ osName = platform . system ( )
1316
1332
if osName .startswith ('Windows' ): # alarm is not implemented in Windows
1317
1333
return
1318
1334
signal .alarm (timeout )
@@ -1355,21 +1371,14 @@ def connectToDeviceOrExit(timeout=60, verbose=False, ignoresecuredevice=False, s
1355
1371
if verbose :
1356
1372
print >> sys .stderr , 'Connecting to a device with serialno=%s with a timeout of %d secs...' % \
1357
1373
(serialno , timeout )
1358
- # Sometimes MonkeyRunner doesn't even timeout (i.e. two connections from same process), so let's
1359
- # handle it here
1360
1374
ViewClient .setAlarm (timeout + 5 )
1361
- device = MonkeyRunner . waitForConnection ( timeout , serialno )
1375
+ device = adbclient . AdbClient ( serialno )
1362
1376
ViewClient .setAlarm (0 )
1363
- try :
1364
- device .wake ()
1365
- except java .lang .NullPointerException , e :
1366
- print >> sys .stderr , "%s: ERROR: Couldn't connect to %s: %s" % (progname , serialno , e )
1367
- sys .exit (3 )
1368
1377
if verbose :
1369
1378
print >> sys .stderr , 'Connected to device with serialno=%s' % serialno
1370
1379
secure = device .getSystemProperty ('ro.secure' )
1371
1380
debuggable = device .getSystemProperty ('ro.debuggable' )
1372
- versionProperty = device .getProperty ('build.' + VERSION_SDK_PROPERTY )
1381
+ versionProperty = device .getProperty (VERSION_SDK_PROPERTY )
1373
1382
if versionProperty :
1374
1383
version = int (versionProperty )
1375
1384
else :
@@ -1575,7 +1584,7 @@ def __splitAttrs(self, strArgs):
1575
1584
raise RuntimeError ("This method is not compatible with UIAutomator" )
1576
1585
# replace the spaces in text:mText to preserve them in later split
1577
1586
# they are translated back after the attribute matches
1578
- textRE = re .compile ('%s=%s,' % (self .textProperty , __nd ('len' )))
1587
+ textRE = re .compile ('%s=%s,' % (self .textProperty , _nd ('len' )))
1579
1588
m = textRE .search (strArgs )
1580
1589
if m :
1581
1590
__textStart = m .end ()
@@ -1586,8 +1595,8 @@ def __splitAttrs(self, strArgs):
1586
1595
strArgs = strArgs .replace (s1 , s2 , 1 )
1587
1596
1588
1597
idRE = re .compile ("(?P<viewId>id/\S+)" )
1589
- attrRE = re .compile ('%s(?P<parens>\(\))?=%s,(?P<val>[^ ]*)' % (__ns ('attr' ), __nd ('len' )), flags = re .DOTALL )
1590
- hashRE = re .compile ('%s@%s' % (__ns ('class' ), __nh ('oid' )))
1598
+ attrRE = re .compile ('%s(?P<parens>\(\))?=%s,(?P<val>[^ ]*)' % (_ns ('attr' ), _nd ('len' )), flags = re .DOTALL )
1599
+ hashRE = re .compile ('%s@%s' % (_ns ('class' ), _nh ('oid' )))
1591
1600
1592
1601
attrs = {}
1593
1602
viewId = None
@@ -1783,15 +1792,14 @@ def dump(self, window=-1, sleep=1):
1783
1792
'''
1784
1793
1785
1794
if sleep > 0 :
1786
- MonkeyRunner .sleep (sleep )
1795
+ time .sleep (sleep )
1787
1796
1788
1797
if self .useUiAutomator :
1789
1798
# NOTICE:
1790
1799
# Using /dev/tty this works even on devices with no sdcard
1791
- received = self .device .shell ('uiautomator dump /dev/tty >/dev/null' )
1800
+ received = unicode ( self .device .shell ('uiautomator dump /dev/tty >/dev/null' ), encoding = 'utf-8' , errors = 'replace ' )
1792
1801
if not received :
1793
1802
raise RuntimeError ('ERROR: Empty UiAutomator dump was received' )
1794
- received = received .encode ('utf-8' , 'ignore' )
1795
1803
if DEBUG :
1796
1804
self .received = received
1797
1805
if DEBUG_RECEIVED :
@@ -1900,7 +1908,7 @@ def list(self, sleep=1):
1900
1908
'''
1901
1909
1902
1910
if sleep > 0 :
1903
- MonkeyRunner .sleep (sleep )
1911
+ time .sleep (sleep )
1904
1912
1905
1913
if self .useUiAutomator :
1906
1914
raise Exception ("Not implemented yet: listing windows with UiAutomator" )
@@ -2039,7 +2047,7 @@ def __findViewWithAttributeInTree(self, attr, val, root):
2039
2047
if DEBUG : print >> sys .stderr , "__findViewWithAttributeInTree: type val=" , type (val )
2040
2048
if DEBUG : print >> sys .stderr , "__findViewWithAttributeInTree: checking if root=%s has attr=%s == %s" % (root .__smallStr__ (), attr , val )
2041
2049
2042
- if isinstance (val , org . python . modules . sre . PatternObject ):
2050
+ if isinstance (val , RegexType ):
2043
2051
return self .__findViewWithAttributeInTreeThatMatches (attr , val , root )
2044
2052
else :
2045
2053
if root and attr in root .map and root .map [attr ] == val :
@@ -2118,7 +2126,7 @@ def findViewWithAttributeThatMatches(self, attr, regex, root="ROOT"):
2118
2126
def findViewWithText (self , text , root = "ROOT" ):
2119
2127
if DEBUG :
2120
2128
print >> sys .stderr , "findViewWithText(%s, %s)" % (text , root )
2121
- if isinstance (text , org . python . modules . sre . PatternObject ):
2129
+ if isinstance (text , RegexType ):
2122
2130
return self .findViewWithAttributeThatMatches (self .textProperty , text , root )
2123
2131
#l = self.findViewWithAttributeThatMatches(TEXT_PROPERTY, text)
2124
2132
#ll = len(l)
0 commit comments