diff --git a/DocBuilder/index.rst b/DocBuilder/index.rst index da3bf1bd..63ed1f9f 100644 --- a/DocBuilder/index.rst +++ b/DocBuilder/index.rst @@ -238,7 +238,7 @@ UI hierarchy. from poco.drivers.unity3d import UnityPoco as Poco poco = Poco() - ui = poco.agent.hierarchy.dump() + ui = poco.dump() # equivalent to poco.agent.hierarchy.dump() print(json.dumps(ui, indent=4)) diff --git a/poco/drivers/android/lib/pocoservice-debug.apk b/poco/drivers/android/lib/pocoservice-debug.apk index ac959e5d..b671d349 100644 Binary files a/poco/drivers/android/lib/pocoservice-debug.apk and b/poco/drivers/android/lib/pocoservice-debug.apk differ diff --git a/poco/drivers/android/uiautomation.py b/poco/drivers/android/uiautomation.py index 404d263a..9f283de1 100644 --- a/poco/drivers/android/uiautomation.py +++ b/poco/drivers/android/uiautomation.py @@ -178,9 +178,11 @@ def __init__(self, device=None, using_proxy=True, force_restart=False, use_airte self._install_service() # forward + self.forward_list = [] if using_proxy: p0, _ = self.adb_client.setup_forward("tcp:10080") p1, _ = self.adb_client.setup_forward("tcp:10081") + self.forward_list.extend(["tcp:%s" % p0, "tcp:%s" % p1]) else: p0 = 10080 p1 = 10081 @@ -323,8 +325,14 @@ def stop_running(self): print('[pocoservice.apk] stopping PocoService') self._keep_running_thread.stop() self._keep_running_thread.join(3) + self.remove_forwards() self.adb_client.shell(['am', 'force-stop', PocoServicePackage]) + def remove_forwards(self): + for p in self.forward_list: + self.adb_client.remove_forward(p) + self.forward_list = [] + class AndroidUiautomationHelper(object): _nuis = {} diff --git a/poco/pocofw.py b/poco/pocofw.py index 9b741f37..8c025bf1 100644 --- a/poco/pocofw.py +++ b/poco/pocofw.py @@ -257,7 +257,9 @@ def rclick(self, pos): raise NotImplementedError def double_click(self, pos): - raise NotImplementedError + ret = self.agent.input.double_click(pos[0], pos[1]) + self.wait_stable() + return ret def swipe(self, p1, p2=None, direction=None, duration=2.0): """ @@ -505,3 +507,13 @@ def use_render_resolution(self, use=True, resolution=None): ''' self._agent.input.use_render_resolution = use self._agent.input.render_resolution = resolution + + def dump(self): + """ + Dump the current UI tree of the target device. The output format depends on the agent implementation. + + Returns: + :obj:`str`: base64 encoded UI tree data + """ + + return self.agent.hierarchy.dump() diff --git a/poco/proxy.py b/poco/proxy.py index 77886b92..696f3f2f 100644 --- a/poco/proxy.py +++ b/poco/proxy.py @@ -191,7 +191,7 @@ def sibling(self, name=None, **attrs): def parent(self): """ - Select the direct child(ren) from the UI element(s) given by the query expression, see ``QueryCondition`` for + Select the direct parent from the UI element(s) given by the query expression, see ``QueryCondition`` for more details about the selectors. Warnings: @@ -269,7 +269,7 @@ def __len__(self): nodes = [] else: nodes = self._nodes - return len(nodes) + return len(nodes) if nodes else 0 def __iter__(self): """ @@ -882,7 +882,7 @@ def invalidate(self): def _do_query(self, multiple=True, refresh=False): if not self._evaluated or refresh: self._nodes = self.poco.agent.hierarchy.select(self.query, multiple) - if len(self._nodes) == 0: + if not self._nodes or len(self._nodes) == 0: # 找不到节点时,将当前节点状态重置,强制下一次访问时重新查询一次节点信息 self.invalidate() raise PocoNoSuchNodeException(self) diff --git a/poco/sdk/interfaces/input.py b/poco/sdk/interfaces/input.py index 90d0d397..ceb5ab88 100644 --- a/poco/sdk/interfaces/input.py +++ b/poco/sdk/interfaces/input.py @@ -24,6 +24,17 @@ def click(self, x, y): raise NotImplementedError + def double_click(self, x, y): + """ + Perform click action as simulated input on target device. Coordinates arguments are all in range of 0~1. + + Args: + y (:obj:`float`): y-coordinate + x (:obj:`float`): x-coordinate + """ + + raise NotImplementedError + def swipe(self, x1, y1, x2, y2, duration): """ Perform swipe action as simulated input on target device from point A to B within given time interval to diff --git a/poco/utils/airtest/input.py b/poco/utils/airtest/input.py index 34119167..55c62417 100644 --- a/poco/utils/airtest/input.py +++ b/poco/utils/airtest/input.py @@ -3,7 +3,7 @@ from functools import wraps from airtest.core.api import device as current_device -from airtest.core.api import touch, swipe +from airtest.core.api import touch, swipe, double_click from airtest.core.helper import device_platform, logwrap from poco.sdk.interfaces.input import InputInterface @@ -80,6 +80,10 @@ def click(self, x, y): pos = self.get_target_pos(x, y) touch(pos, duration=self.default_touch_down_duration) + def double_click(self, x, y): + pos = self.get_target_pos(x, y) + double_click(pos) + def swipe(self, x1, y1, x2, y2, duration=2.0): if duration <= 0: raise ValueError("Operation duration cannot be less equal 0. Please provide a positive number.") diff --git a/poco/utils/simplerpc/jsonrpc/dispatcher.py b/poco/utils/simplerpc/jsonrpc/dispatcher.py index 2bf5e430..14a061ca 100644 --- a/poco/utils/simplerpc/jsonrpc/dispatcher.py +++ b/poco/utils/simplerpc/jsonrpc/dispatcher.py @@ -3,10 +3,13 @@ For usage examples see :meth:`Dispatcher.add_method` """ -import collections +try: + from collections import MutableMapping +except AttributeError: + from collections.abc import MutableMapping -class Dispatcher(collections.MutableMapping): +class Dispatcher(MutableMapping): """ Dictionary like object which maps method_name to method.""" diff --git a/setup.py b/setup.py index 4e7aa620..d4e6ad6e 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def parse_requirements(filename='requirements.txt'): setup( name='pocoui', - version='1.0.89', + version='1.0.92', keywords="poco, automation test, ui automation", description='Poco cross-engine UI automated test framework.', long_description='Poco cross-engine UI automated test framework. 2018 present by NetEase Games',