Skip to content

Commit

Permalink
* fix #941, add more to .info (eg. checked)
Browse files Browse the repository at this point in the history
  • Loading branch information
codeskyblue authored Apr 9, 2024
1 parent 01effab commit 0108c92
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 24 deletions.
23 changes: 23 additions & 0 deletions XPATH.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
```

### 滑动到指定位置
Expand Down
8 changes: 7 additions & 1 deletion tests/test_xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion uiautomator2/abcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 28 additions & 22 deletions uiautomator2/xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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:])
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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): 还差一个检测是否到底的功能
Expand Down Expand Up @@ -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

0 comments on commit 0108c92

Please sign in to comment.