Skip to content

Commit 17b1b76

Browse files
committed
Added concertina mode (EXPERIMENTAL)
1 parent 7fb4b7a commit 17b1b76

File tree

11 files changed

+335
-45
lines changed

11 files changed

+335
-45
lines changed

.idea/AndroidViewClient.iml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from setuptools import setup, find_packages
44

55
setup(name='androidviewclient',
6-
version='10.5.1',
6+
version='10.6.0',
77
description='''AndroidViewClient is a 100% pure python library and tools
88
that simplifies test script creation providing higher level
99
operations and the ability of obtaining the tree of Views present at

src/com/dtmilano/android/adb/adbclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
@author: Diego Torres Milano
1818
'''
1919

20-
__version__ = '10.5.1'
20+
__version__ = '10.6.0'
2121

2222
import sys
2323
import warnings

src/com/dtmilano/android/controlpanel.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
@author: Ahmed Kasem
2020
'''
2121

22-
__version__ = '10.5.1'
22+
__version__ = '10.6.0'
2323

2424
import sys, os
2525
import Tkinter, tkFileDialog, ttk

src/com/dtmilano/android/culebron.py

Lines changed: 106 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818
@author: Diego Torres Milano
1919
2020
'''
21+
import random
2122
import time
2223

23-
__version__ = '10.5.3'
24+
__version__ = '10.6.0'
2425

2526
import sys
2627
import threading
@@ -46,6 +47,7 @@
4647
import ScrolledText
4748
import ttk
4849
from Tkconstants import DISABLED, NORMAL
50+
4951
TKINTER_AVAILABLE = True
5052
except:
5153
TKINTER_AVAILABLE = False
@@ -60,6 +62,7 @@
6062
DEBUG_ISCCOF = DEBUG and False
6163
DEBUG_FIND_VIEW = DEBUG and False
6264
DEBUG_CONTEXT_MENU = DEBUG and False
65+
DEBUG_CONCERTINA = DEBUG and False
6366

6467

6568
class Color:
@@ -169,7 +172,7 @@ def checkDependencies():
169172
This is usually installed by python package. Check your distribution details.
170173
''')
171174

172-
def __init__(self, vc, device, serialno, printOperation, scale=1):
175+
def __init__(self, vc, device, serialno, printOperation, scale=1, concertina=False):
173176
'''
174177
Culebron constructor.
175178
@@ -183,13 +186,16 @@ def __init__(self, vc, device, serialno, printOperation, scale=1):
183186
@type printOperation: method
184187
@param scale: the scale of the device screen used to show it on the window
185188
@type scale: float
189+
@:param concertina: bool
190+
@:type concertina: enable concertina mode (see documentation)
186191
'''
187192

188193
self.vc = vc
189194
self.printOperation = printOperation
190195
self.device = device
191196
self.serialno = serialno
192197
self.scale = scale
198+
self.concertina = concertina
193199
self.window = Tkinter.Tk()
194200
icon = resource_filename(Requirement.parse("androidviewclient"),
195201
"share/pixmaps/culebra.png")
@@ -214,6 +220,7 @@ def __init__(self, vc, device, serialno, printOperation, scale=1):
214220
self.targetIds = []
215221
self.isTouchingPoint = self.vc is None
216222
self.coordinatesUnit = Unit.DIP
223+
self.permanentlyDisableEvents = False
217224
if DEBUG:
218225
try:
219226
self.printGridInfo()
@@ -579,12 +586,11 @@ def getViewContainingPointAndTouch(self, x, y):
579586
text = tkSimpleDialog.askstring(title, "Enter text to type into this field", **kwargs)
580587
self.canvas.focus_set()
581588
if text:
582-
# This is deleting the existing text, which should be asked in the dialog, but I would have to implement
583-
# the dialog myself
584-
v.setText(text)
585-
# This is not deleting the text, so appending if there's something
586-
#v.type(text)
587-
self.printOperation(v, Operation.TYPE, text)
589+
if self.vc.uiAutomatorHelper:
590+
oid = self.vc.uiAutomatorHelper.findObject(v.getId())
591+
self.vc.uiAutomatorHelper.setText(oid, text)
592+
else:
593+
self.setText(v, text)
588594
else:
589595
self.hideVignette()
590596
return
@@ -606,15 +612,28 @@ def findBestCandidate(view):
606612
if len(candidates) > 2:
607613
warnings.warn("We are in trouble, we have more than one candidate to touch", stacklevel=0)
608614
candidate = candidates[0]
609-
candidate.touch()
610-
# we pass root=v as an argument so the corresponding findView*() searches in this
611-
# subtree instead of the full tree
612-
self.printOperation(candidate, Operation.TOUCH_VIEW, v if candidate != v else None)
615+
self.touchView(candidate, v if candidate != v else None)
613616

614617
self.printOperation(None, Operation.SLEEP, Operation.DEFAULT)
615618
self.vc.sleep(5)
616619
self.takeScreenshotAndShowItOnWindow()
617620

621+
def setText(self, v, text):
622+
if DEBUG_CONCERTINA:
623+
print >> sys.stderr, "setText(%s, '%s')" % (v.__tinyStr__(), text)
624+
# This is deleting the existing text, which should be asked in the dialog, but I would have to implement
625+
# the dialog myself
626+
v.setText(text)
627+
# This is not deleting the text, so appending if there's something
628+
# v.type(text)
629+
self.printOperation(v, Operation.TYPE, text)
630+
631+
def touchView(self, v, root=None):
632+
v.touch()
633+
# we pass root=v as an argument so the corresponding findView*() searches in this
634+
# subtree instead of the full tree
635+
self.printOperation(v, Operation.TOUCH_VIEW, root)
636+
618637
def touchPoint(self, x, y):
619638
'''
620639
Touches a point in the device screen.
@@ -644,7 +663,7 @@ def touchPoint(self, x, y):
644663
time.sleep(5)
645664
self.isTouchingPoint = self.vc is None
646665
self.takeScreenshotAndShowItOnWindow()
647-
self.hideVignette()
666+
#self.hideVignette()
648667
self.statusBar.clear()
649668
return
650669

@@ -676,7 +695,7 @@ def longTouchPoint(self, x, y):
676695
time.sleep(5)
677696
self.isLongTouchingPoint = False
678697
self.takeScreenshotAndShowItOnWindow()
679-
self.hideVignette()
698+
#self.hideVignette()
680699
self.statusBar.clear()
681700
return
682701

@@ -756,7 +775,7 @@ def onKeyPressed(self, event):
756775
keysym = event.keysym
757776

758777
if len(char) == 0 and not (
759-
keysym in Culebron.KEYSYM_TO_KEYCODE_MAP or keysym in Culebron.KEYSYM_CULEBRON_COMMANDS):
778+
keysym in Culebron.KEYSYM_TO_KEYCODE_MAP or keysym in Culebron.KEYSYM_CULEBRON_COMMANDS):
760779
if DEBUG_KEY:
761780
print >> sys.stderr, "returning because len(char) == 0"
762781
return
@@ -961,6 +980,10 @@ def onCtrlQ(self, event):
961980
self.quit()
962981

963982
def quit(self):
983+
if self.vc.uiAutomatorHelper:
984+
if DEBUG or True:
985+
print >> sys.stderr, "Quitting UiAutomatorHelper..."
986+
self.vc.uiAutomatorHelper.quit()
964987
self.window.destroy()
965988

966989
def showSleepDialog(self):
@@ -1047,6 +1070,8 @@ def drag(self, start, end, duration, steps, units=Unit.DIP):
10471070
self.takeScreenshotAndShowItOnWindow()
10481071

10491072
def enableEvents(self):
1073+
if self.permanentlyDisableEvents:
1074+
return
10501075
self.canvas.update_idletasks()
10511076
self.canvas.bind("<Button-1>", self.onButton1Pressed)
10521077
self.canvas.bind("<Control-Button-1>", self.onCtrlButton1Pressed)
@@ -1057,7 +1082,8 @@ def enableEvents(self):
10571082
self.canvas.bind("<Key>", self.onKeyPressed)
10581083
self.areEventsDisabled = False
10591084

1060-
def disableEvents(self):
1085+
def disableEvents(self, permanently=False):
1086+
self.permanentlyDisableEvents = permanently
10611087
if self.canvas is not None:
10621088
self.canvas.update_idletasks()
10631089
self.areEventsDisabled = True
@@ -1185,8 +1211,71 @@ def mainloop(self):
11851211
self.window.title("%s v%s" % (Culebron.APPLICATION_NAME, __version__))
11861212
self.window.resizable(width=Tkinter.FALSE, height=Tkinter.FALSE)
11871213
self.window.lift()
1214+
if self.concertina:
1215+
self.concertinaLoop()
1216+
else:
1217+
self.window.mainloop()
1218+
1219+
def concertinaLoop(self):
1220+
self.disableEvents(permanently=True)
1221+
self.concertinaLoopCallback(dontinteract=True)
11881222
self.window.mainloop()
11891223

1224+
def concertinaLoopCallback(self, dontinteract=False):
1225+
if not dontinteract:
1226+
if DEBUG_CONCERTINA:
1227+
print >> sys.stderr, "CONCERTINA: should select one if these targets:"
1228+
for v in self.targetViews:
1229+
print >> sys.stderr, " ", unicode(v.__tinyStr__())
1230+
l = len(self.targetViews)
1231+
if l > 0:
1232+
i = random.randrange(len(self.targetViews))
1233+
t = self.targetViews[i]
1234+
z = self.targets[i]
1235+
if DEBUG_CONCERTINA:
1236+
print >> sys.stderr, "CONCERTINA: selected", unicode(t.__smallStr__())
1237+
print >> sys.stderr, "CONCERTINA: selected", z
1238+
self.markTarget(*z)
1239+
self.window.update_idletasks()
1240+
time.sleep(1)
1241+
# FIXME: we need self.unmakeTarget(*z)
1242+
self.unmarkTargets()
1243+
clazz = t.getClass()
1244+
parent = t.getParent()
1245+
if parent:
1246+
parentClass = parent.getClass()
1247+
else:
1248+
parentClass = None
1249+
isScrollable = t.isScrollable()
1250+
if DEBUG_CONCERTINA:
1251+
print >> sys.stderr, "CONCERTINA: is scrollable: ", isScrollable
1252+
if clazz == 'android.widget.EditText':
1253+
text = 'Some random text'
1254+
if DEBUG_CONCERTINA:
1255+
print >> sys.stderr, "Entering text: ", text
1256+
self.setText(t, text)
1257+
elif isScrollable or parentClass == 'android.widget.ScrollView':
1258+
# NOTE: The order here is important because some EditText are inside ScrollView's and we want to
1259+
# capture the case of other ScrollViews
1260+
print >> sys.stderr, ">>>>>>>>", t.getBounds()
1261+
((t, l), (b, r)) = t.getBounds()
1262+
sp = (t+50, (r-l)/2)
1263+
ep = (b-50, (r-l)/2)
1264+
d = 1
1265+
s = 20
1266+
units = Unit.DIP
1267+
if DEBUG_CONCERTINA:
1268+
print >> sys.stderr, "CONCERTINA: dragging %s %s %s %s %s" % (sp, ep, d, s, units)
1269+
self.drag(sp, ep, d, s, units)
1270+
else:
1271+
self.touchView(t)
1272+
self.printOperation(None, Operation.SLEEP, Operation.DEFAULT)
1273+
time.sleep(5)
1274+
self.takeScreenshotAndShowItOnWindow()
1275+
else:
1276+
print >> sys.stderr, "CONCERTINA: No target views"
1277+
self.window.after(5000, self.concertinaLoopCallback)
1278+
11901279

11911280
if TKINTER_AVAILABLE:
11921281
class MainMenu(Tkinter.Menu):
@@ -1612,7 +1701,7 @@ def __init__(self, culebron, view):
16121701
culebron.showSleepDialog))
16131702
if culebron.vc is not None:
16141703
items.append(ContextMenu.Command('Toggle generating Test Condition', 18, 'Ctrl+T', '<Control-T>',
1615-
culebron.toggleGenerateTestCondition))
1704+
culebron.toggleGenerateTestCondition))
16161705
items.append(ContextMenu.Command('Touch Zones', 6, 'Ctrl+Z', '<Control-Z>', culebron.toggleTargetZones))
16171706
items.append(ContextMenu.Command('Generates a startActivity()', 17, 'Ctrl+A', '<Control-A>',
16181707
culebron.printStartActivityAtTop))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__author__ = 'diego'

0 commit comments

Comments
 (0)