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.

Files changed (29) hide show
  1. {uiautodev-0.3.6 → uiautodev-0.3.7}/PKG-INFO +2 -1
  2. {uiautodev-0.3.6 → uiautodev-0.3.7}/pyproject.toml +2 -1
  3. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/android.py +25 -35
  4. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/model.py +8 -0
  5. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/router/device.py +8 -4
  6. {uiautodev-0.3.6 → uiautodev-0.3.7}/LICENSE +0 -0
  7. {uiautodev-0.3.6 → uiautodev-0.3.7}/README.md +0 -0
  8. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/__init__.py +0 -0
  9. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/__main__.py +0 -0
  10. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/app.py +0 -0
  11. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/appium_proxy.py +0 -0
  12. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/case.py +0 -0
  13. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/cli.py +0 -0
  14. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/command_proxy.py +0 -0
  15. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/command_types.py +0 -0
  16. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/common.py +0 -0
  17. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/appium.py +0 -0
  18. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/base_driver.py +0 -0
  19. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/ios.py +0 -0
  20. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/mock.py +0 -0
  21. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
  22. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/driver/udt/udt.py +0 -0
  23. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/exceptions.py +0 -0
  24. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/provider.py +0 -0
  25. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/router/xml.py +0 -0
  26. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/static/demo.html +0 -0
  27. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/utils/common.py +0 -0
  28. {uiautodev-0.3.6 → uiautodev-0.3.7}/uiautodev/utils/exceptions.py +0 -0
  29. {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.6
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.6"
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
- return parse_xml_element(root, wsize)
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
- elem.children.append(parse_xml_element(child, wsize, indexes + [index]))
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
- return hierarchy
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