Skip to content

Commit

Permalink
add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
codeskyblue committed Apr 4, 2024
1 parent 0f9b191 commit 4776bf7
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 119 deletions.
12 changes: 4 additions & 8 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
[run]
branch = True

omit =
"tests/*"
"docs/*"

[report]
; Regexes for lines to exclude from consideration
exclude_also =
Expand All @@ -20,11 +24,3 @@ exclude_also =
@(abc\.)?abstractmethod

ignore_errors = True

[paths]
source =
uiautomator2/

omit =
"tests/*"
"docs/*"
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ test:
poetry run pytest -v tests

cov:
poetry run pytest -v tests/unittests --cov=. --cov-report xml --cov-report term
poetry run pytest -v tests/unittests \
--cov-config=.coveragerc \
--cov uiautomator2 \
--cov-report xml \
--cov-report term

build:
poetry self add "poetry-dynamic-versioning[plugin]"
Expand Down
7 changes: 5 additions & 2 deletions docs/2to3.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ enable_pretty_logging()
- 移除 property u2.xpath.XPath.logger
- 移除 property d.watcher.debug
- 移除 d.settings["xpath_debug"]
- 移除 function XPath.apply_watch_from_yaml
- connect_usb函数中的init参数移除,这个参数目前没什么用了

- 移除 class AdbUI
- 移除 function XPath.apply_watch_from_yaml
- 移除 module uiautomator2.ext.xpath
- 移除 connect_usb函数中的init参数移除,这个参数目前没什么用了

TODO: atx-agent不在依赖外网下载,直接打包到内部
41 changes: 39 additions & 2 deletions tests/unittests/test_xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
from unittest.mock import Mock
from PIL import Image
from uiautomator2.xpath import XMLElement, XPathSelector, XPath, XPathElementNotFoundError
from uiautomator2.xpath import XMLElement, XPathSelector, XPath, XPathElementNotFoundError, is_xpath_syntax_ok


mock = Mock()
Expand All @@ -24,6 +24,28 @@

x = XPath(mock)


def test_is_xpath_syntax_ok():
assert is_xpath_syntax_ok("/a")
assert is_xpath_syntax_ok("//a")
assert is_xpath_syntax_ok("//a[@text='b]") is False
assert is_xpath_syntax_ok("//a[") is False


def test_xpath_selector():
assert isinstance(x("n1"), XPathSelector)
assert isinstance(x("//TextView"), XPathSelector)
xp1 = x("n1")
xp2 = xp1.child("n2")
# xp1 should not be changed
assert xp1.get(timeout=0).text == "n1"
assert xp1.get_text() == "n1"

# match return None or XMLElement
assert xp1.match() is not None
assert xp2.match() is None


def test_xpath_click():
x("n1").click()
assert mock.click.called
Expand Down Expand Up @@ -73,11 +95,26 @@ def test_xpath_element():
el = x("n1").get(timeout=0)
assert el.text == "n1"
assert el.center() == (540, 50)
assert el.offset(0, 0) == (0, 0)
assert el.offset(1, 1) == (1080, 100)
assert el.screenshot().size == (1080, 100)
assert el.bounds == (0, 0, 1080, 100)
assert el.get_xpath() == "/hierarchy/FrameLayout/TextView[1]"
assert el.rect == (0, 0, 1080, 100)
assert isinstance(el.info, dict)
assert el.get_xpath(strip_index=True) == "/hierarchy/FrameLayout/TextView"

mock.click.reset_mock()
el.click()
assert mock.click.called
assert mock.click.call_args[0] == (540, 50)

mock.long_click.reset_mock()
el.long_click()
assert mock.long_click.called
assert mock.long_click.call_args[0] == (540, 50)

mock.swipe.reset_mock()
el.swipe("up")
assert mock.swipe.called


3 changes: 0 additions & 3 deletions uiautomator2/ext/xpath/README.md

This file was deleted.

11 changes: 0 additions & 11 deletions uiautomator2/ext/xpath/__init__.py

This file was deleted.

98 changes: 6 additions & 92 deletions uiautomator2/xpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
from __future__ import absolute_import

import functools
import io
import json
import logging
import re
import threading
import time
from collections import defaultdict
from typing import Callable, List, Optional, Union
from typing import Any, Callable, Dict, List, Optional, Union

import adbutils
from deprecated import deprecated
from PIL import Image
from lxml import etree
Expand Down Expand Up @@ -41,7 +38,7 @@ def str2bytes(v: Union[str, bytes]) -> bytes:
return v.encode("utf-8")


def is_xpath_syntax_ok(xpath_expression) -> bool:
def is_xpath_syntax_ok(xpath_expression: str) -> bool:
try:
etree.XPath(xpath_expression)
return True # No error means the XPath syntax is likely okay
Expand Down Expand Up @@ -465,7 +462,7 @@ def get(self, timeout=None):
def get_last_match(self):
return self.all(self._last_source)[0]

def get_text(self):
def get_text(self) -> Optional[str]:
"""
get element text
Expand All @@ -475,7 +472,7 @@ def get_text(self):
Raises:
XPathElementNotFoundError
"""
return self.get().attrib.get("text", "")
return self.get().text

def set_text(self, text: str = ""):
el = self.get()
Expand Down Expand Up @@ -758,7 +755,7 @@ def attrib(self):
return self.elem.attrib

@property
def info(self):
def info(self) -> Dict[str, Any]:
ret = {}
for key in (
"text",
Expand All @@ -781,87 +778,4 @@ def info(self):
"resource-id"
) # this is better than resourceName
ret["childCount"] = len(self.elem.getchildren())
return ret


class AdbUI(DeviceInterface):
"""
Use adb command to run ui test
"""

def __init__(self, d: adbutils.AdbDevice):
self._d = d

def click(self, x, y):
self._d.click(x, y)

def swipe(self, sx, sy, ex, ey, duration):
self._d.swipe(sx, sy, ex, ey, duration)

def window_size(self):
w, h = self._d.window_size()
return w, h

def dump_hierarchy(self):
return self._d.dump_hierarchy()

def screenshot(self):
d = self._d
json_output = d.shell(
[
"LD_LIBRARY_PATH=/data/local/tmp",
"/data/local/tmp/minicap",
"-i",
"2&>/dev/null",
]
).strip()
data = json.loads(json_output)
w, h, r = data["width"], data["height"], data["rotation"]
remote_image_path = "/sdcard/minicap.jpg"
d.shell(["rm", remote_image_path])
d.shell(
[
"LD_LIBRARY_PATH=/data/local/tmp",
"/data/local/tmp/minicap",
"-P",
"{0}x{1}@{0}x{1}/{2}".format(w, h, r),
"-s",
">" + remote_image_path,
]
) # yapf: disable

if d.sync.stat(remote_image_path).size == 0:
raise RuntimeError("screenshot using minicap error")

buf = io.BytesIO()
for data in d.sync.iter_content(remote_image_path):
buf.write(data)
return Image.open(buf)


if __name__ == "__main__":
d = AdbUI(adbutils.adb.device())
xpath = XPath(d)
# print(d.screenshot())
# print(d.dump_hierarchy()[:20])
xpath("App").click()
xpath("Alarm").click()
# init()
# import uiautomator2.ext.htmlreport as htmlreport

# d = uiautomator2.connect()
# hrp = htmlreport.HTMLReport(d)

# # take screenshot before each click
# hrp.patch_click()
# d.app_start("com.netease.cloudmusic", stop=True)

# # watchers
# d.ext_xpath.when("跳过").click()
# # d.ext_xpath.when("知道了").click()

# # steps
# d.ext_xpath("//*[@text='私人FM']/../android.widget.ImageView").click()
# d.ext_xpath("下一首").click()
# d.ext_xpath.sleep_watch(2)
# d.ext_xpath("转到上一层级").click()
return ret

0 comments on commit 4776bf7

Please sign in to comment.