Skip to content

Commit

Permalink
Add Until and wait
Browse files Browse the repository at this point in the history
- Remove "Stopping uiAutomatorHelper" message
- Use `resource_id` in traverse
- Use `find_object` in `findViewById` if uiAutomatorHelper is used
- Use `find_object` in `__findViewWithAttributeInTree` if uiAutomatorHelper is used
- Add examples
  • Loading branch information
dtmilano committed Jan 16, 2022
1 parent 421b86e commit 8a31ac4
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 18 deletions.
70 changes: 70 additions & 0 deletions examples/calculator-simple-uiautomator-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright (C) 2013-2019 Diego Torres Milano
Created on 2022-01-15 by Culebra v20.4.5
__ __ __ __
/ \ / \ / \ / \
____________________/ __\/ __\/ __\/ __\_____________________________
___________________/ /__/ /__/ /__/ /________________________________
| / \ / \ / \ / \ \___
|/ \_/ \_/ \_/ \ o \
\_____/--<
@author: Diego Torres Milano
@author: Jennifer E. Swofford (ascii art snake)
"""

from com.dtmilano.android.viewclient import ViewClient

TAG = 'CULEBRA'

kwargs1 = {'verbose': False}
device, serialno = ViewClient.connectToDeviceOrExit(**kwargs1)

kwargs2 = {'useuiautomatorhelper': True, 'debug': {}}
vc = ViewClient(device, serialno, **kwargs2)


vc.uiAutomatorHelper.target_context.start_activity('com.google.android.calculator', 'com.android.calculator2.Calculator')

com_android_systemui___id_status_bar_container = vc.findViewByIdOrRaise("com.android.systemui:id/status_bar_container")
com_android_systemui___id_status_bar = vc.findViewByIdOrRaise("com.android.systemui:id/status_bar")
com_android_systemui___id_status_bar_contents = vc.findViewByIdOrRaise("com.android.systemui:id/status_bar_contents")
com_android_systemui___id_status_bar_contents_left = vc.findViewByIdOrRaise(
"com.android.systemui:id/status_bar_contents_left")
com_android_systemui___id_status_bar_left_side = vc.findViewByIdOrRaise("com.android.systemui:id/status_bar_left_side")
com_android_systemui___id_notification_icon_area = vc.findViewByIdOrRaise(
"com.android.systemui:id/notification_icon_area")
com_android_systemui___id_notification_icon_area_inner = vc.findViewByIdOrRaise(
"com.android.systemui:id/notification_icon_area_inner")
com_android_systemui___id_notificationIcons = vc.findViewByIdOrRaise("com.android.systemui:id/notificationIcons")

com_android_systemui___id_system_icon_area = vc.findViewByIdOrRaise("com.android.systemui:id/system_icon_area")
com_android_systemui___id_system_icons = vc.findViewByIdOrRaise("com.android.systemui:id/system_icons")
com_android_systemui___id_statusIcons = vc.findViewByIdOrRaise("com.android.systemui:id/statusIcons")
com_android_systemui___id_wifi_combo = vc.findViewByIdOrRaise("com.android.systemui:id/wifi_combo")
com_android_systemui___id_wifi_group = vc.findViewByIdOrRaise("com.android.systemui:id/wifi_group")
com_android_systemui___id_wifi_combo = vc.findViewByIdOrRaise("com.android.systemui:id/wifi_combo")
com_android_systemui___id_wifi_signal = vc.findViewByIdOrRaise("com.android.systemui:id/wifi_signal")
com_android_systemui___id_battery = vc.findViewByIdOrRaise("com.android.systemui:id/battery")
com_google_android_calculator___id_action_bar_root = vc.findViewByIdOrRaise("com.google.android.calculator:id/action_bar_root")
android___id_content = vc.findViewByIdOrRaise("android:id/content")
com_google_android_calculator___id_main_calculator = vc.findViewByIdOrRaise("com.google.android.calculator:id/main_calculator")
com_google_android_calculator___id_display = vc.findViewByIdOrRaise("com.google.android.calculator:id/display")
com_google_android_calculator___id_toolbar = vc.findViewByIdOrRaise("com.google.android.calculator:id/toolbar")
com_google_android_calculator___id_formula_container = vc.findViewByIdOrRaise("com.google.android.calculator:id/formula_container")
com_google_android_calculator___id_formula_scroll_view = vc.findViewByIdOrRaise("com.google.android.calculator:id/formula_scroll_view")
com_google_android_calculator___id_formula = vc.findViewByIdOrRaise("com.google.android.calculator:id/formula")
com_google_android_calculator___id_result_container = vc.findViewByIdOrRaise("com.google.android.calculator:id/result_container")
com_google_android_calculator___id_result_preview = vc.findViewByIdOrRaise("com.google.android.calculator:id/result_preview")
com_google_android_calculator___id_result_preview = vc.findViewWithContentDescriptionOrRaise(u'''No result''')
com_google_android_calculator___id_drag_handle_view = vc.findViewByIdOrRaise("com.google.android.calculator:id/drag_handle_view")
com_google_android_calculator___id_op_sqrt = vc.findViewByIdOrRaise("com.google.android.calculator:id/op_sqrt")
com_google_android_calculator___id_op_sqrt = vc.findViewWithTextOrRaise(u'√')
com_google_android_calculator___id_op_sqrt = vc.findViewWithContentDescriptionOrRaise(u'''square root''')
com_google_android_calculator___id_const_pi = vc.findViewByIdOrRaise("com.google.android.calculator:id/const_pi")
com_google_android_calculator___id_const_pi = vc.findViewWithTextOrRaise(u'π')
com_google_android_calculator___id_const_pi = vc.findViewWithContentDescriptionOrRaise(u'''pi''')
com_google_android_calculator___id_op_pow = vc.findViewByIdOrRaise("com.google.android.calculator:id/op_pow")
com_google_android_calculator___id_op_pow = vc.findViewWithTextOrRaise(u'^')
com_google_android_calculator___id_op_pow = vc.findViewWithContentDescriptionOrRaise(u'''power''')
40 changes: 40 additions & 0 deletions examples/calculator-wait-uiautomator-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright (C) 2013-2019 Diego Torres Milano
Created on 2022-01-15 by Culebra v20.4.5
__ __ __ __
/ \ / \ / \ / \
____________________/ __\/ __\/ __\/ __\_____________________________
___________________/ /__/ /__/ /__/ /________________________________
| / \ / \ / \ / \ \___
|/ \_/ \_/ \_/ \ o \
\_____/--<
@author: Diego Torres Milano
@author: Jennifer E. Swofford (ascii art snake)
"""
import threading
import time

from com.dtmilano.android.viewclient import ViewClient

TAG = 'CULEBRA'


def start_calculator():
time.sleep(10)
vc.uiAutomatorHelper.target_context.start_activity('com.google.android.calculator',
'com.android.calculator2.Calculator')


kwargs1 = {'verbose': False}
device, serialno = ViewClient.connectToDeviceOrExit(**kwargs1)

kwargs2 = {'useuiautomatorhelper': True, 'debug': {}}
vc = ViewClient(device, serialno, **kwargs2)

threading.Thread(target=start_calculator).start()

search_condition_ref = vc.uiAutomatorHelper.until.find_object(by_selector='text@π').oid
print("Let's wait until PI appears on the screen (Calculator)...")
print(vc.uiAutomatorHelper.ui_device.wait(search_condition_ref=search_condition_ref, timeout=30000))
10 changes: 5 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
author_email='[email protected]',
url='https://github.com/dtmilano/AndroidViewClient/',
packages=find_packages('src'),
package_dir={'':'src'},
package_data={'':['*.png']},
package_dir={'': 'src'},
package_data={'': ['*.png']},
include_package_data=True,
scripts=['tools/culebra', 'tools/dump'],
classifiers=['Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License'],
install_requires=['setuptools', 'requests', 'numpy', 'matplotlib', 'culebratester-client >= 2.0.17'],
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License'],
install_requires=['setuptools', 'requests', 'numpy', 'matplotlib', 'culebratester-client >= 2.0.19'],
)
24 changes: 23 additions & 1 deletion src/com/dtmilano/android/uiautomator/uiautomatorhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from abc import ABC

import culebratester_client
from culebratester_client import Text
from culebratester_client import Text, ObjectRef

from com.dtmilano.android.adb.adbclient import AdbClient
from com.dtmilano.android.common import obtainAdbPath
Expand Down Expand Up @@ -128,6 +128,7 @@ def __init__(self, adbclient, adb=None, localport=9987, remoteport=9987, hostnam
self.object_store: UiAutomatorHelper.ObjectStore = UiAutomatorHelper.ObjectStore(self)
self.ui_device: UiAutomatorHelper.UiDevice = UiAutomatorHelper.UiDevice(self)
self.ui_object2: UiAutomatorHelper.UiObject2 = UiAutomatorHelper.UiObject2(self)
self.until: UiAutomatorHelper.Until = UiAutomatorHelper.Until(self)

def __connectSession(self):
if DEBUG:
Expand Down Expand Up @@ -421,6 +422,16 @@ def take_screenshot(self, scale=1.0, quality=90, **kwargs):
return self.uiAutomatorHelper.api_instance.ui_device_screenshot_get(scale=scale, quality=quality,
_preload_content=False, **kwargs)

def wait(self, search_condition_ref: int, timeout=10000):
"""
:param search_condition_ref: the search condition ref (oid)
:param timeout: the timeout in ms
:return:
"""
return self.uiAutomatorHelper.api_instance.ui_device_wait_get(search_condition_ref=search_condition_ref,
timeout=timeout)

def wait_for_idle(self, **kwargs):
"""
Waits for idle.
Expand Down Expand Up @@ -499,6 +510,16 @@ def set_text(self, oid, text):
"""
return self.uiAutomatorHelper.api_instance.ui_object2_set_text_post(oid=oid, body=Text(text))

#
# Until
#
class Until(ApiBase):
def __init__(self, uiAutomatorHelper) -> None:
super().__init__(uiAutomatorHelper)

def find_object(self, by_selector: str) -> ObjectRef:
return self.uiAutomatorHelper.api_instance.until_find_object_get(by_selector=by_selector)

def click(self, **kwargs):
"""
:deprecated:
Expand Down Expand Up @@ -682,6 +703,7 @@ class UiObject:
"""
A UiObject is a representation of a view.
"""

def __init__(self, uiAutomatorHelper, oid, response):
self.uiAutomatorHelper = uiAutomatorHelper
self.oid = oid
Expand Down
39 changes: 27 additions & 12 deletions src/com/dtmilano/android/viewclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from culebratester_client import WindowHierarchyChild, WindowHierarchy

__version__ = '20.4.5'
__version__ = '20.5.0'

import sys
import warnings
Expand Down Expand Up @@ -2701,8 +2701,6 @@ def __init__(self, device, serialno, adb=None, autodump=True, forceviewserveruse
def __del__(self):
# should clean up some things
if hasattr(self, 'uiAutomatorHelper') and self.uiAutomatorHelper:
if DEBUG or True:
print("Stopping UiAutomatorHelper...", file=sys.stderr)
self.uiAutomatorHelper.quit()

@staticmethod
Expand Down Expand Up @@ -2890,6 +2888,9 @@ def traverseShowClassIdAndText(view, extraInfo=None, noExtraInfo=None, extraActi
@return: the string containing class, id, and text if available
'''

if type(view) == WindowHierarchy:
return ''

try:
eis = ''
if extraInfo:
Expand All @@ -2907,11 +2908,11 @@ def traverseShowClassIdAndText(view, extraInfo=None, noExtraInfo=None, extraActi
_str += ' '
_str += view.getText() if view.getText() else ''
except AttributeError:
_str = view.clazz
_str += ' '
_str += '%s' % view.id
_str += ' '
_str += view.text
_str = f'{view.clazz}'
if view.resource_id:
_str += f' {view.resource_id}'
if view.text:
_str += f' {view.text}'
if eis:
_str += eis
return _str
Expand Down Expand Up @@ -3668,7 +3669,7 @@ def list(self, sleep=1):
return self.windows

def findViewById(self, viewId, root="ROOT", viewFilter=None):
'''
"""
Finds the View with the specified viewId.
@type viewId: str
Expand All @@ -3683,7 +3684,11 @@ def findViewById(self, viewId, root="ROOT", viewFilter=None):
This can be C{None} and no extra filtering is applied.
@return: the C{View} found or C{None}
'''
"""

if self.uiAutomatorHelper:
# If we are using uiAutomatorHelper let's find object directly without traversing the tree
return self.uiAutomatorHelper.ui_device.find_object(by_selector=f'res@{viewId}')

if not root:
return None
Expand Down Expand Up @@ -3732,7 +3737,7 @@ def findViewById(self, viewId, root="ROOT", viewFilter=None):
return foundView

def findViewByIdOrRaise(self, viewId, root="ROOT", viewFilter=None):
'''
"""
Finds the View or raise a ViewNotFoundException.
@type viewId: str
Expand All @@ -3747,7 +3752,7 @@ def findViewByIdOrRaise(self, viewId, root="ROOT", viewFilter=None):
This can be C{None} and no extra filtering is applied.
@return: the View found
@raise ViewNotFoundException: raise the exception if View not found
'''
"""

view = self.findViewById(viewId, root, viewFilter)
if view:
Expand Down Expand Up @@ -3797,6 +3802,12 @@ def __findViewsWithAttributeInTree(self, attr, val, root):
return matchingViews

def __findViewWithAttributeInTree(self, attr, val, root):
if self.uiAutomatorHelper:
if attr == 'content-desc':
attr = 'desc'
by_selector = f'{attr}@{val}'
return self.uiAutomatorHelper.ui_device.find_object(by_selector=by_selector)

if DEBUG:
print(" __findViewWithAttributeInTree: type(val)=", type(val), file=sys.stderr)
if type(val) != str and type(val) != re._pattern_type:
Expand Down Expand Up @@ -4856,6 +4867,8 @@ def setUp(self):
for serialno in __devices:
device, serialno = ViewClient.connectToDeviceOrExit(serialno=serialno, **self.kwargs1)
if self.options[CulebraOptions.START_ACTIVITY]:
# FIXME: we are not considering the uiAutomatorHelper case
# see if in lines 4832..4836
device.startActivity(component=self.options[CulebraOptions.START_ACTIVITY])
vc = ViewClient(device, serialno, **self.kwargs2)
connectedDevice = ConnectedDevice(serialno=serialno, device=device, vc=vc)
Expand All @@ -4873,6 +4886,8 @@ def setUp(self):
self.serialno = __devices[0]
self.device, self.serialno = ViewClient.connectToDeviceOrExit(serialno=self.serialno, **self.kwargs1)
if CulebraOptions.START_ACTIVITY in self.options and self.options[CulebraOptions.START_ACTIVITY]:
# FIXME: we are not considering the uiAutomatorHelper case
# see if in lines 4832..4836
self.device.startActivity(component=self.options[CulebraOptions.START_ACTIVITY])
self.vc = ViewClient(self.device, self.serialno, **self.kwargs2)
# Set the default device, to be consistent with multi-devices case
Expand Down

0 comments on commit 8a31ac4

Please sign in to comment.