uiautodev 0.3.6__tar.gz → 0.3.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of uiautodev might be problematic. Click here for more details.
- {uiautodev-0.3.6 → uiautodev-0.3.7}/PKG-INFO +2 -1
- {uiautodev-0.3.6 → uiautodev-0.3.7}/pyproject.toml +2 -1
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/android.py +25 -35
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/model.py +8 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/router/device.py +8 -4
- {uiautodev-0.3.6 → uiautodev-0.3.7}/LICENSE +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/README.md +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/__init__.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/__main__.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/app.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/appium_proxy.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/case.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/cli.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/command_proxy.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/command_types.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/common.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/appium.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/base_driver.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/ios.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/mock.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/udt/udt.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/exceptions.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/provider.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/router/xml.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/static/demo.html +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/utils/common.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/utils/exceptions.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/utils/usbmux.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uiautodev
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
|
|
5
5
|
Home-page: https://uiauto.dev
|
|
6
6
|
License: MIT
|
|
@@ -25,6 +25,7 @@ Requires-Dist: httpx
|
|
|
25
25
|
Requires-Dist: lxml
|
|
26
26
|
Requires-Dist: pillow
|
|
27
27
|
Requires-Dist: poetry (>=1.8.2,<2.0.0)
|
|
28
|
+
Requires-Dist: pydantic (>=2.6,<3.0)
|
|
28
29
|
Requires-Dist: pygments (>=2)
|
|
29
30
|
Requires-Dist: uiautomator2 (>=2)
|
|
30
31
|
Requires-Dist: uvicorn[standard]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "uiautodev"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.7"
|
|
4
4
|
description = "Mobile UI Automation, include UI hierarchy inspector, script recorder"
|
|
5
5
|
homepage = "https://uiauto.dev"
|
|
6
6
|
authors = ["codeskyblue <codeskyblue@gmail.com>"]
|
|
@@ -22,6 +22,7 @@ httpx = "*"
|
|
|
22
22
|
fastapi = "^0.111.0"
|
|
23
23
|
uvicorn = {version = "*", extras = ["standard"]}
|
|
24
24
|
poetry = "^1.8.2"
|
|
25
|
+
pydantic = "^2.6"
|
|
25
26
|
|
|
26
27
|
[tool.poetry.extras]
|
|
27
28
|
appium = ["appium-python-client", "httppretty"]
|
|
@@ -9,7 +9,7 @@ import logging
|
|
|
9
9
|
import re
|
|
10
10
|
import time
|
|
11
11
|
from functools import cached_property, partial
|
|
12
|
-
from typing import List, Tuple
|
|
12
|
+
from typing import List, Optional, Tuple
|
|
13
13
|
from xml.etree import ElementTree
|
|
14
14
|
|
|
15
15
|
import adbutils
|
|
@@ -20,7 +20,7 @@ from uiautodev.command_types import CurrentAppResponse
|
|
|
20
20
|
from uiautodev.driver.base_driver import BaseDriver
|
|
21
21
|
from uiautodev.driver.udt.udt import UDT, UDTError
|
|
22
22
|
from uiautodev.exceptions import AndroidDriverException, RequestError
|
|
23
|
-
from uiautodev.model import Node, ShellResponse, WindowSize
|
|
23
|
+
from uiautodev.model import Node, Rect, ShellResponse, WindowSize
|
|
24
24
|
from uiautodev.utils.common import fetch_through_socket
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
@@ -31,8 +31,8 @@ class AndroidDriver(BaseDriver):
|
|
|
31
31
|
self.adb_device = adbutils.device(serial)
|
|
32
32
|
self._try_dump_list = [
|
|
33
33
|
self._get_u2_hierarchy,
|
|
34
|
-
self._get_appium_hierarchy,
|
|
35
34
|
self._get_udt_dump_hierarchy,
|
|
35
|
+
# self._get_appium_hierarchy,
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
@cached_property
|
|
@@ -64,16 +64,16 @@ class AndroidDriver(BaseDriver):
|
|
|
64
64
|
except Exception as e:
|
|
65
65
|
return ShellResponse(output="", error=f"adb error: {str(e)}")
|
|
66
66
|
|
|
67
|
-
def dump_hierarchy(self) -> Tuple[str, Node]:
|
|
67
|
+
def dump_hierarchy(self, display_id: Optional[int] = 0) -> Tuple[str, Node]:
|
|
68
68
|
"""returns xml string and hierarchy object"""
|
|
69
|
-
wsize = self.adb_device.window_size()
|
|
70
|
-
logger.debug("window size: %s", wsize)
|
|
71
69
|
start = time.time()
|
|
72
70
|
xml_data = self._dump_hierarchy_raw()
|
|
73
71
|
logger.debug("dump_hierarchy cost: %s", time.time() - start)
|
|
74
72
|
|
|
73
|
+
wsize = self.adb_device.window_size()
|
|
74
|
+
logger.debug("window size: %s", wsize)
|
|
75
75
|
return xml_data, parse_xml(
|
|
76
|
-
xml_data, WindowSize(width=wsize[0], height=wsize[1])
|
|
76
|
+
xml_data, WindowSize(width=wsize[0], height=wsize[1]), display_id
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
def _dump_hierarchy_raw(self) -> str:
|
|
@@ -99,28 +99,6 @@ class AndroidDriver(BaseDriver):
|
|
|
99
99
|
def _get_u2_hierarchy(self) -> str:
|
|
100
100
|
d = u2.connect_usb(self.serial)
|
|
101
101
|
return d.dump_hierarchy()
|
|
102
|
-
# c = self.device.create_connection(adbutils.Network.TCP, 9008)
|
|
103
|
-
# try:
|
|
104
|
-
# compressed = False
|
|
105
|
-
# payload = {
|
|
106
|
-
# "jsonrpc": "2.0",
|
|
107
|
-
# "method": "dumpWindowHierarchy",
|
|
108
|
-
# "params": [compressed],
|
|
109
|
-
# "id": 1,
|
|
110
|
-
# }
|
|
111
|
-
# content = fetch_through_socket(
|
|
112
|
-
# c, "/jsonrpc/0", method="POST", json=payload, timeout=5
|
|
113
|
-
# )
|
|
114
|
-
# json_resp = json.loads(content)
|
|
115
|
-
# if "error" in json_resp:
|
|
116
|
-
# raise AndroidDriverException(json_resp["error"])
|
|
117
|
-
# return json_resp["result"]
|
|
118
|
-
# except adbutils.AdbError as e:
|
|
119
|
-
# raise AndroidDriverException(
|
|
120
|
-
# f"Failed to get hierarchy from u2 server: {str(e)}"
|
|
121
|
-
# )
|
|
122
|
-
# finally:
|
|
123
|
-
# c.close()
|
|
124
102
|
|
|
125
103
|
def _get_appium_hierarchy(self) -> str:
|
|
126
104
|
c = self.adb_device.create_connection(adbutils.Network.TCP, 6790)
|
|
@@ -168,26 +146,34 @@ class AndroidDriver(BaseDriver):
|
|
|
168
146
|
self.adb_device.keyevent("WAKEUP")
|
|
169
147
|
|
|
170
148
|
|
|
171
|
-
def parse_xml(xml_data: str, wsize: WindowSize) -> Node:
|
|
149
|
+
def parse_xml(xml_data: str, wsize: WindowSize, display_id: Optional[int] = None) -> Node:
|
|
172
150
|
root = ElementTree.fromstring(xml_data)
|
|
173
|
-
|
|
151
|
+
node = parse_xml_element(root, wsize, display_id)
|
|
152
|
+
if node is None:
|
|
153
|
+
raise AndroidDriverException("Failed to parse xml")
|
|
154
|
+
return node
|
|
174
155
|
|
|
175
156
|
|
|
176
|
-
def parse_xml_element(
|
|
177
|
-
element, wsize: WindowSize, indexes: List[int] = [0]
|
|
178
|
-
) -> Node:
|
|
157
|
+
def parse_xml_element(element, wsize: WindowSize, display_id: Optional[int], indexes: List[int] = [0]) -> Optional[Node]:
|
|
179
158
|
"""
|
|
180
159
|
Recursively parse an XML element into a dictionary format.
|
|
181
160
|
"""
|
|
182
161
|
name = element.tag
|
|
183
162
|
if name == "node":
|
|
184
163
|
name = element.attrib.get("class", "node")
|
|
164
|
+
if display_id is not None:
|
|
165
|
+
elem_display_id = int(element.attrib.get("display-id", display_id))
|
|
166
|
+
if elem_display_id != display_id:
|
|
167
|
+
return
|
|
168
|
+
|
|
185
169
|
bounds = None
|
|
170
|
+
rect = None
|
|
186
171
|
# eg: bounds="[883,2222][1008,2265]"
|
|
187
172
|
if "bounds" in element.attrib:
|
|
188
173
|
bounds = element.attrib["bounds"]
|
|
189
174
|
bounds = list(map(int, re.findall(r"\d+", bounds)))
|
|
190
175
|
assert len(bounds) == 4
|
|
176
|
+
rect = Rect(x=bounds[0], y=bounds[1], width=bounds[2] - bounds[0], height=bounds[3] - bounds[1])
|
|
191
177
|
bounds = (
|
|
192
178
|
bounds[0] / wsize.width,
|
|
193
179
|
bounds[1] / wsize.height,
|
|
@@ -195,16 +181,20 @@ def parse_xml_element(
|
|
|
195
181
|
bounds[3] / wsize.height,
|
|
196
182
|
)
|
|
197
183
|
bounds = map(partial(round, ndigits=4), bounds)
|
|
184
|
+
|
|
198
185
|
elem = Node(
|
|
199
186
|
key="-".join(map(str, indexes)),
|
|
200
187
|
name=name,
|
|
201
188
|
bounds=bounds,
|
|
189
|
+
rect=rect,
|
|
202
190
|
properties={key: element.attrib[key] for key in element.attrib},
|
|
203
191
|
children=[],
|
|
204
192
|
)
|
|
205
193
|
|
|
206
194
|
# Construct xpath for children
|
|
207
195
|
for index, child in enumerate(element):
|
|
208
|
-
|
|
196
|
+
child_node = parse_xml_element(child, wsize, display_id, indexes + [index])
|
|
197
|
+
if child_node:
|
|
198
|
+
elem.children.append(child_node)
|
|
209
199
|
|
|
210
200
|
return elem
|
|
@@ -24,10 +24,18 @@ class ShellResponse(BaseModel):
|
|
|
24
24
|
error: Optional[str] = ""
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class Rect(BaseModel):
|
|
28
|
+
x: int
|
|
29
|
+
y: int
|
|
30
|
+
width: int
|
|
31
|
+
height: int
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
class Node(BaseModel):
|
|
28
35
|
key: str
|
|
29
36
|
name: str
|
|
30
37
|
bounds: Optional[Tuple[float, float, float, float]] = None
|
|
38
|
+
rect: Optional[Rect] = None
|
|
31
39
|
properties: Dict[str, Union[str, bool]] = []
|
|
32
40
|
children: List[Node] = []
|
|
33
41
|
|
|
@@ -16,7 +16,6 @@ from uiautodev.command_types import Command, CurrentAppResponse, InstallAppReque
|
|
|
16
16
|
from uiautodev.model import DeviceInfo, Node, ShellResponse
|
|
17
17
|
from uiautodev.provider import BaseProvider
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
logger = logging.getLogger(__name__)
|
|
21
20
|
|
|
22
21
|
class AndroidShellPayload(BaseModel):
|
|
@@ -58,7 +57,7 @@ def make_router(provider: BaseProvider) -> APIRouter:
|
|
|
58
57
|
"""Take a screenshot of device"""
|
|
59
58
|
try:
|
|
60
59
|
driver = provider.get_device_driver(serial)
|
|
61
|
-
pil_img = driver.screenshot(id)
|
|
60
|
+
pil_img = driver.screenshot(id).convert("RGB")
|
|
62
61
|
buf = io.BytesIO()
|
|
63
62
|
pil_img.save(buf, format="JPEG")
|
|
64
63
|
image_bytes = buf.getvalue()
|
|
@@ -68,12 +67,17 @@ def make_router(provider: BaseProvider) -> APIRouter:
|
|
|
68
67
|
return Response(content=str(e), media_type="text/plain", status_code=500)
|
|
69
68
|
|
|
70
69
|
@router.get("/{serial}/hierarchy")
|
|
71
|
-
def dump_hierarchy(serial: str) -> Node:
|
|
70
|
+
def dump_hierarchy(serial: str, format: str = "json") -> Node:
|
|
72
71
|
"""Dump the view hierarchy of an Android device"""
|
|
73
72
|
try:
|
|
74
73
|
driver = provider.get_device_driver(serial)
|
|
75
74
|
xml_data, hierarchy = driver.dump_hierarchy()
|
|
76
|
-
|
|
75
|
+
if format == "xml":
|
|
76
|
+
return Response(content=xml_data, media_type="text/xml")
|
|
77
|
+
elif format == "json":
|
|
78
|
+
return hierarchy
|
|
79
|
+
else:
|
|
80
|
+
return Response(content=f"Invalid format: {format}", media_type="text/plain", status_code=400)
|
|
77
81
|
except Exception as e:
|
|
78
82
|
logger.exception("dump_hierarchy failed")
|
|
79
83
|
return Response(content=str(e), media_type="text/plain", status_code=500)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|