diff --git a/examples/calculator-simple-uiautomator-helper b/examples/calculator-simple-uiautomator-helper new file mode 100755 index 00000000..b0ce47bf --- /dev/null +++ b/examples/calculator-simple-uiautomator-helper @@ -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''') \ No newline at end of file diff --git a/examples/calculator-wait-uiautomator-helper b/examples/calculator-wait-uiautomator-helper new file mode 100755 index 00000000..b0db63ae --- /dev/null +++ b/examples/calculator-wait-uiautomator-helper @@ -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)) diff --git a/setup.py b/setup.py index 4d23369e..23bf8bbf 100644 --- a/setup.py +++ b/setup.py @@ -15,12 +15,12 @@ author_email='dtmilano@gmail.com', 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'], ) diff --git a/src/com/dtmilano/android/uiautomator/uiautomatorhelper.py b/src/com/dtmilano/android/uiautomator/uiautomatorhelper.py index b536362b..7e45d29e 100644 --- a/src/com/dtmilano/android/uiautomator/uiautomatorhelper.py +++ b/src/com/dtmilano/android/uiautomator/uiautomatorhelper.py @@ -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 @@ -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: @@ -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. @@ -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: @@ -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 diff --git a/src/com/dtmilano/android/viewclient.py b/src/com/dtmilano/android/viewclient.py index 2c6dca96..b30fcb9a 100644 --- a/src/com/dtmilano/android/viewclient.py +++ b/src/com/dtmilano/android/viewclient.py @@ -24,7 +24,7 @@ from culebratester_client import WindowHierarchyChild, WindowHierarchy -__version__ = '20.4.5' +__version__ = '20.5.0' import sys import warnings @@ -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 @@ -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: @@ -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 @@ -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 @@ -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 @@ -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 @@ -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: @@ -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: @@ -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) @@ -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