Skip to content

Commit 6cfd075

Browse files
committed
findViewWithAttributeThatMatches only get one result dtmilano#265
- Adds ViewClient.findViewsWithAttributeThatMatches() - Adds tests
1 parent 7e6e83f commit 6cfd075

3 files changed

Lines changed: 94 additions & 17 deletions

File tree

src/com/dtmilano/android/viewclient.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
'''
3-
Copyright (C) 2012-2018 Diego Torres Milano
3+
Copyright (C) 2012-2019 Diego Torres Milano
44
Created on Feb 2, 2012
55
66
Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,7 @@
1818
@author: Diego Torres Milano
1919
'''
2020

21-
__version__ = '15.6.0'
21+
__version__ = '15.7.0'
2222

2323
import sys
2424
import warnings
@@ -3579,9 +3579,8 @@ def __findViewsWithAttributeInTree(self, attr, val, root):
35793579
if root and attr in root.map and root.map[attr] == val:
35803580
if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTree: FOUND: %s" % root.__smallStr__()
35813581
matchingViews.append(root)
3582-
else:
3583-
for ch in root.children:
3584-
matchingViews += self.__findViewsWithAttributeInTree(attr, val, ch)
3582+
for ch in root.children:
3583+
matchingViews += self.__findViewsWithAttributeInTree(attr, val, ch)
35853584

35863585
return matchingViews
35873586

@@ -3665,18 +3664,33 @@ def __findViewWithAttributeInTreeThatMatches(self, attr, regex, root, rlist=[]):
36653664
if root and attr in root.map and regex.match(root.map[attr]):
36663665
if DEBUG: print >>sys.stderr, "__findViewWithAttributeInTreeThatMatches: FOUND: %s" % root.__smallStr__()
36673666
return root
3668-
#print >>sys.stderr, "appending root=%s to rlist=%s" % (root.__smallStr__(), rlist)
3669-
#return rlist.append(root)
36703667
else:
36713668
for ch in root.children:
36723669
v = self.__findViewWithAttributeInTreeThatMatches(attr, regex, ch, rlist)
36733670
if v:
36743671
return v
3675-
#print >>sys.stderr, "appending v=%s to rlist=%s" % (v.__smallStr__(), rlist)
3676-
#return rlist.append(v)
36773672

36783673
return None
3679-
#return rlist
3674+
3675+
def __findViewsWithAttributeInTreeThatMatches(self, attr, regex, root, rlist=[]):
3676+
# Note the plural in this method name
3677+
matchingViews = []
3678+
if not self.root:
3679+
print >>sys.stderr, "ERROR: no root, did you forget to call dump()?"
3680+
return matchingViews
3681+
3682+
if type(root) == types.StringType and root == "ROOT":
3683+
root = self.root
3684+
3685+
if DEBUG: print >>sys.stderr, "__findViewsWithAttributeInTreeThatMatches: checking if root=%s attr=%s matches %s" % (root.__smallStr__(), attr, regex)
3686+
3687+
if root and attr in root.map and regex.match(root.map[attr]):
3688+
if DEBUG: print >>sys.stderr, "__findViewsWithAttributeInTreeThatMatches: FOUND: %s" % root.__smallStr__()
3689+
matchingViews.append(root)
3690+
for ch in root.children:
3691+
matchingViews += self.__findViewsWithAttributeInTreeThatMatches(attr, regex, ch)
3692+
3693+
return matchingViews
36803694

36813695
def findViewWithAttribute(self, attr, val, root="ROOT"):
36823696
'''
@@ -3704,6 +3718,14 @@ def findViewsWithAttribute(self, attr, val, root="ROOT"):
37043718

37053719
return self.__findViewsWithAttributeInTree(attr, val, root)
37063720

3721+
def findViewsWithAttributeThatMatches(self, attr, regex, root="ROOT"):
3722+
'''
3723+
Finds the Views with the specified attribute matching regex.
3724+
This allows you to see all items that match your criteria in the view hierarchy
3725+
'''
3726+
3727+
return self.__findViewsWithAttributeInTreeThatMatches(attr, regex, root)
3728+
37073729
def findViewWithAttributeOrRaise(self, attr, val, root="ROOT"):
37083730
'''
37093731
Finds the View or raise a ViewNotFoundException.

tests/com/dtmilano/android/mocks.py

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,56 @@
401401
</hierarchy>
402402
"""
403403

404+
UIAUTOMATOR_DUMP_API27 = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
405+
<hierarchy rotation="0">
406+
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]">
407+
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]">
408+
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]">
409+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/launcher" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]">
410+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/drag_layer" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]">
411+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/workspace" class="com.android.launcher3.Workspace" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]">
412+
<node index="0" text="" resource-id="" class="android.view.ViewGroup" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[21,84][1059,1395]">
413+
<node index="0" text="" resource-id="" class="android.view.ViewGroup" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[35,84][1045,1395]">
414+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/search_container_workspace" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[35,84][1045,346]">
415+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/smartspace_content" class="android.widget.LinearLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[166,84][913,346]">
416+
<node index="0" text="Sunday, May 19" resource-id="com.google.android.apps.nexuslauncher:id/clock" class="android.widget.TextView" package="com.google.android.apps.nexuslauncher" content-desc="Sunday, May 19" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[166,84][655,346]"/>
417+
<node index="1" text="" resource-id="com.google.android.apps.nexuslauncher:id/title_sep" class="android.view.View" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[655,188][658,241]"/>
418+
<node index="2" text="" resource-id="com.google.android.apps.nexuslauncher:id/title_weather_content" class="android.widget.LinearLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[658,84][913,346]">
419+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/title_weather_icon" class="android.widget.ImageView" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[684,183][747,246]"/>
420+
<node index="1" text="56°F" resource-id="com.google.android.apps.nexuslauncher:id/title_weather_text" class="android.widget.TextView" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[758,172][887,257]"/>
421+
</node>
422+
</node>
423+
</node>
424+
</node>
425+
<node index="1" text="" resource-id="" class="android.view.View" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[35,84][268,377]"/>
426+
</node>
427+
</node>
428+
<node index="1" text="" resource-id="com.google.android.apps.nexuslauncher:id/gradient_bg" class="android.view.View" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1794]"/>
429+
<node index="2" text="" resource-id="com.google.android.apps.nexuslauncher:id/page_indicator" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,1395][1080,1479]">
430+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/all_apps_handle" class="android.widget.ImageView" package="com.google.android.apps.nexuslauncher" content-desc="Apps list" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[477,1395][603,1479]"/>
431+
</node>
432+
<node index="3" text="" resource-id="com.google.android.apps.nexuslauncher:id/hotseat" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,1479][1080,1794]">
433+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/layout" class="android.view.ViewGroup" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[0,1479][1080,1794]">
434+
<node index="0" text="" resource-id="" class="android.view.View" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[35,1479][268,1694]"/>
435+
<node index="1" text="" resource-id="" class="android.view.ViewGroup" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[35,1479][1045,1663]">
436+
<node index="0" text="Phone" resource-id="" class="android.widget.TextView" package="com.google.android.apps.nexuslauncher" content-desc="Phone" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[35,1479][237,1663]"/>
437+
<node index="1" text="Messages" resource-id="" class="android.widget.TextView" package="com.google.android.apps.nexuslauncher" content-desc="Messages" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[237,1479][439,1663]"/>
438+
<node index="2" text="Play Store" resource-id="" class="android.widget.TextView" package="com.google.android.apps.nexuslauncher" content-desc="Play Store" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[439,1479][641,1663]"/>
439+
<node index="3" text="Chrome" resource-id="" class="android.widget.TextView" package="com.google.android.apps.nexuslauncher" content-desc="Chrome" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[641,1479][843,1663]"/>
440+
</node>
441+
</node>
442+
<node index="1" text="" resource-id="com.google.android.apps.nexuslauncher:id/search_container_hotseat" class="android.widget.FrameLayout" package="com.google.android.apps.nexuslauncher" content-desc="Search" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[53,1664][1026,1794]">
443+
<node index="0" text="" resource-id="com.google.android.apps.nexuslauncher:id/g_icon" class="android.widget.ImageView" package="com.google.android.apps.nexuslauncher" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[93,1695][178,1780]"/>
444+
</node>
445+
</node>
446+
</node>
447+
</node>
448+
</node>
449+
</node>
450+
</node>
451+
</hierarchy>
452+
'''
453+
404454
UIAUTOMATOR_DUMP_API17_CHINESE = '''<?xml version=\'1.0\' encoding=\'UTF-8\' standalone=\'yes\' ?>
405455
<hierarchy rotation="0">
406456
<node index="0" text="" class="android.widget.FrameLayout"
@@ -672,7 +722,6 @@ class MockDevice(object):
672722
Mocks an Android device
673723
'''
674724

675-
676725
def __init__(self, serialno="MOCK12345678", version=15, startviewserver=False, uiautomatorkilled=False, language='en'):
677726
'''
678727
Constructor
@@ -690,13 +739,11 @@ def __init__(self, serialno="MOCK12345678", version=15, startviewserver=False, u
690739
self.viewServer = None
691740
self.uiAutomatorKilled = uiautomatorkilled
692741
self.language = language
693-
self.uiAutomatorDump = {}
694-
self.uiAutomatorDump['en'] = UIAUTOMATOR_DUMP
695742
# FIXME: MockDevice could not be API17
696-
self.uiAutomatorDump['zh'] = UIAUTOMATOR_DUMP_API17_CHINESE
697-
698-
# def __del__(self):
699-
# self.shutdownMockViewServer()
743+
if version < 27:
744+
self.uiAutomatorDump = {'en': UIAUTOMATOR_DUMP, 'zh': UIAUTOMATOR_DUMP_API17_CHINESE}
745+
else:
746+
self.uiAutomatorDump = {'en': UIAUTOMATOR_DUMP_API27}
700747

701748
def shell(self, cmd):
702749
if cmd == 'service call window 3':

tests/com/dtmilano/android/viewclienttests.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,14 @@ def testFindViewsContainingPoint_filterApi17(self):
11221122
self.assertNotEquals(None, list)
11231123
self.assertNotEquals(0, len(list))
11241124

1125+
def testFindViewsWithArreibuteThatMatches(self):
1126+
device = MockDevice(version=28)
1127+
vc = ViewClient(device, device.serialno, adb=TRUE)
1128+
list = vc.findViewsWithAttributeThatMatches("text", re.compile(r"\S+"))
1129+
self.assertNotEquals(None, list)
1130+
self.assertEquals(6, len(list))
1131+
1132+
11251133
if __name__ == "__main__":
11261134
print >> sys.stderr, "ViewClient.__main__:"
11271135
print >> sys.stderr, "argv=", sys.argv

0 commit comments

Comments
 (0)