From 0108c9240daad2281df91ba6a1261f74a68fb9b6 Mon Sep 17 00:00:00 2001 From: codeskyblue Date: Tue, 9 Apr 2024 22:31:31 +0800 Subject: [PATCH] * fix #941, add more to .info (eg. checked) --- XPATH.md | 23 ++++++++++++++++++++ tests/test_xpath.py | 8 ++++++- uiautomator2/abcd.py | 2 +- uiautomator2/xpath.py | 50 ++++++++++++++++++++++++------------------- 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/XPATH.md b/XPATH.md index 2e80c27c..32cef810 100644 --- a/XPATH.md +++ b/XPATH.md @@ -146,6 +146,29 @@ el.screenshot() # 控件滑动 el.swipe("right") # left, right, up, down el.swipe("right", scale=0.9) # scale默认0.9, 意思是滑动距离为控件宽度的90%, 上滑则为高度的90% + +print(el.info) +# output example +{'index': '0', + 'text': '', + 'resourceId': 'com.example:id/home_searchedit', + 'checkable': 'true', + 'checked': 'true', + 'clickable': 'true', + 'enabled': 'true', + 'focusable': 'false', + 'focused': 'false', + 'scrollable': 'false', + 'longClickable': 'false', + 'password': 'false', + 'selected': 'false', + 'visibleToUser': 'true', + 'childCount': 0, + 'className': 'android.widget.Switch', + 'bounds': {'left': 882, 'top': 279, 'right': 1026, 'bottom': 423}, + 'packageName': 'com.android.settings', + 'contentDescription': '', + 'resourceName': 'android:id/switch_widget'} ``` ### 滑动到指定位置 diff --git a/tests/test_xpath.py b/tests/test_xpath.py index 07c23b0d..5a14a618 100644 --- a/tests/test_xpath.py +++ b/tests/test_xpath.py @@ -44,7 +44,13 @@ def test_xpath_selector(): # match return None or XMLElement assert xp1.match() is not None assert xp2.match() is None - + + +def test_xpath_with_instance(): + # issue: https://github.com/openatx/uiautomator2/issues/941 + el = x('(//TextView)[2]').get(0) + assert el.text == "n2" + def test_xpath_click(): x("n1").click() diff --git a/uiautomator2/abcd.py b/uiautomator2/abcd.py index 74090eb8..22f10c73 100644 --- a/uiautomator2/abcd.py +++ b/uiautomator2/abcd.py @@ -5,7 +5,7 @@ import abc -class DeviceInterface(metaclass=abc.ABCMeta): +class AbstractDevice(metaclass=abc.ABCMeta): @abc.abstractmethod def click(self, x: int, y: int): pass diff --git a/uiautomator2/xpath.py b/uiautomator2/xpath.py index 0c1a3619..297532b8 100644 --- a/uiautomator2/xpath.py +++ b/uiautomator2/xpath.py @@ -16,18 +16,18 @@ from lxml import etree from uiautomator2._proto import Direction -from uiautomator2.abcd import DeviceInterface +from uiautomator2.abcd import AbstractDevice from uiautomator2.exceptions import XPathElementNotFoundError from uiautomator2.utils import inject_call, swipe_in_bounds logger = logging.getLogger(__name__) -def safe_xmlstr(s): +def safe_xmlstr(s: str) -> str: return s.replace("$", "-") -def string_quote(s): +def string_quote(s: str) -> str: """quick way to quote string""" return "{!r}".format(s) @@ -46,11 +46,24 @@ def is_xpath_syntax_ok(xpath_expression: str) -> bool: return False # Indicates a syntax error in the XPath expression +def convert_to_camel_case(s: str) -> str: + """ + Convert a string from kebab-case to camelCase. + + :param s: A string in kebab-case format. + :return: A string converted to camelCase format. + """ + parts = s.split('-') + # Convert the first letter of each part to uppercase, except for the first part + camel_case_str = parts[0] + ''.join(part.capitalize() for part in parts[1:]) + return camel_case_str + + def strict_xpath(xpath: str) -> str: """make xpath to be computer recognized xpath""" orig_xpath = xpath - if xpath.startswith("/"): + if xpath.startswith("/") or xpath.startswith("(/"): pass elif xpath.startswith("@"): xpath = "//*[@resource-id={!r}]".format(xpath[1:]) @@ -96,7 +109,7 @@ class XPathError(Exception): """basic error for xpath plugin""" class XPath(object): - def __init__(self, d: DeviceInterface): + def __init__(self, d: AbstractDevice): """ Args: d (uiautomator2 instance) @@ -275,7 +288,7 @@ def scroll_to( direction = Direction.DOWN elif direction == Direction.HORIZ_FORWARD: # Horizontal direction = Direction.LEFT - elif direction == Direction.HBACKWORD: + elif direction == Direction.HORIZ_BACKWARD: direction = Direction.RIGHT # FIXME(ssx): 还差一个检测是否到底的功能 @@ -757,25 +770,18 @@ def attrib(self): @property def info(self) -> Dict[str, Any]: ret = {} - for key in ( - "text", - "focusable", - "enabled", - "focused", - "scrollable", - "selected", - "clickable", - ): - ret[key] = self.attrib.get(key) + for k, v in dict(self.attrib).items(): + if k in ("bounds", "class", "package", "content-desc"): + continue + ret[convert_to_camel_case(k)] = v + + ret["childCount"] = len(self.elem.getchildren()) ret["className"] = self.elem.tag lx, ly, rx, ry = self.bounds ret["bounds"] = {"left": lx, "top": ly, "right": rx, "bottom": ry} - ret["contentDescription"] = self.attrib.get("content-desc") - ret["longClickable"] = self.attrib.get("long-clickable") + + # 名字命名的有点奇怪,为了兼容性暂时保留 ret["packageName"] = self.attrib.get("package") + ret["contentDescription"] = self.attrib.get("content-desc") ret["resourceName"] = self.attrib.get("resource-id") - ret["resourceId"] = self.attrib.get( - "resource-id" - ) # this is better than resourceName - ret["childCount"] = len(self.elem.getchildren()) return ret \ No newline at end of file