uiautodev 0.3.6__tar.gz → 0.4.0__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.4.0}/PKG-INFO +4 -2
- {uiautodev-0.3.6 → uiautodev-0.4.0}/pyproject.toml +4 -2
- uiautodev-0.4.0/uiautodev/__init__.py +8 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/command_proxy.py +25 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/command_types.py +7 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/android.py +40 -35
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/base_driver.py +20 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/ios.py +20 -11
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/model.py +8 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/router/device.py +8 -4
- uiautodev-0.3.6/uiautodev/__init__.py +0 -12
- {uiautodev-0.3.6 → uiautodev-0.4.0}/LICENSE +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/README.md +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/__main__.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/app.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/appium_proxy.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/case.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/cli.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/common.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/appium.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/mock.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/driver/udt/udt.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/exceptions.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/provider.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/router/xml.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/static/demo.html +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/utils/common.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/utils/exceptions.py +0 -0
- {uiautodev-0.3.6 → uiautodev-0.4.0}/uiautodev/utils/usbmux.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uiautodev
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
|
|
5
5
|
Home-page: https://uiauto.dev
|
|
6
6
|
License: MIT
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Provides-Extra: appium
|
|
18
|
-
Requires-Dist: adbutils (>=2.
|
|
18
|
+
Requires-Dist: adbutils (>=2.7.0,<3.0.0)
|
|
19
19
|
Requires-Dist: appium-python-client (>=4.0.0,<5.0.0) ; extra == "appium"
|
|
20
20
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
21
21
|
Requires-Dist: construct
|
|
@@ -25,9 +25,11 @@ 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]
|
|
32
|
+
Requires-Dist: wdapy (>=0.2.2,<0.3.0)
|
|
31
33
|
Description-Content-Type: text/markdown
|
|
32
34
|
|
|
33
35
|
# uiautodev
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "uiautodev"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
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>"]
|
|
@@ -10,7 +10,7 @@ readme = "README.md"
|
|
|
10
10
|
[tool.poetry.dependencies]
|
|
11
11
|
python = "^3.8"
|
|
12
12
|
pillow = "*"
|
|
13
|
-
adbutils = "^2.
|
|
13
|
+
adbutils = "^2.7.0"
|
|
14
14
|
construct = "*"
|
|
15
15
|
lxml = "*"
|
|
16
16
|
click = "^8.1.7"
|
|
@@ -22,6 +22,8 @@ httpx = "*"
|
|
|
22
22
|
fastapi = "^0.111.0"
|
|
23
23
|
uvicorn = {version = "*", extras = ["standard"]}
|
|
24
24
|
poetry = "^1.8.2"
|
|
25
|
+
pydantic = "^2.6"
|
|
26
|
+
wdapy = "^0.2.2"
|
|
25
27
|
|
|
26
28
|
[tool.poetry.extras]
|
|
27
29
|
appium = ["appium-python-client", "httppretty"]
|
|
@@ -105,6 +105,31 @@ def home(driver: BaseDriver):
|
|
|
105
105
|
driver.home()
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
@register(Command.BACK)
|
|
109
|
+
def back(driver: BaseDriver):
|
|
110
|
+
driver.back()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@register(Command.APP_SWITCH)
|
|
114
|
+
def app_switch(driver: BaseDriver):
|
|
115
|
+
driver.app_switch()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@register(Command.VOLUME_UP)
|
|
119
|
+
def volume_up(driver: BaseDriver):
|
|
120
|
+
driver.volume_up()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@register(Command.VOLUME_DOWN)
|
|
124
|
+
def volume_down(driver: BaseDriver):
|
|
125
|
+
driver.volume_down()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@register(Command.VOLUME_MUTE)
|
|
129
|
+
def volume_mute(driver: BaseDriver):
|
|
130
|
+
driver.volume_mute()
|
|
131
|
+
|
|
132
|
+
|
|
108
133
|
@register(Command.DUMP)
|
|
109
134
|
def dump(driver: BaseDriver) -> DumpResponse:
|
|
110
135
|
source, _ = driver.dump_hierarchy()
|
|
@@ -32,6 +32,13 @@ class Command(str, enum.Enum):
|
|
|
32
32
|
|
|
33
33
|
LIST = "list"
|
|
34
34
|
|
|
35
|
+
# 0.4.0
|
|
36
|
+
BACK = "back"
|
|
37
|
+
APP_SWITCH = "appSwitch"
|
|
38
|
+
VOLUME_UP = "volumeUp"
|
|
39
|
+
VOLUME_DOWN = "volumeDown"
|
|
40
|
+
VOLUME_MUTE = "volumeMute"
|
|
41
|
+
|
|
35
42
|
|
|
36
43
|
class TapRequest(BaseModel):
|
|
37
44
|
x: Union[int, float]
|
|
@@ -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)
|
|
@@ -166,28 +144,51 @@ class AndroidDriver(BaseDriver):
|
|
|
166
144
|
|
|
167
145
|
def wake_up(self):
|
|
168
146
|
self.adb_device.keyevent("WAKEUP")
|
|
147
|
+
|
|
148
|
+
def back(self):
|
|
149
|
+
self.adb_device.keyevent("BACK")
|
|
150
|
+
|
|
151
|
+
def app_switch(self):
|
|
152
|
+
self.adb_device.keyevent("APP_SWITCH")
|
|
153
|
+
|
|
154
|
+
def volume_up(self):
|
|
155
|
+
self.adb_device.keyevent("VOLUME_UP")
|
|
156
|
+
|
|
157
|
+
def volume_down(self):
|
|
158
|
+
self.adb_device.keyevent("VOLUME_DOWN")
|
|
159
|
+
|
|
160
|
+
def volume_mute(self):
|
|
161
|
+
self.adb_device.keyevent("VOLUME_MUTE")
|
|
169
162
|
|
|
170
163
|
|
|
171
|
-
def parse_xml(xml_data: str, wsize: WindowSize) -> Node:
|
|
164
|
+
def parse_xml(xml_data: str, wsize: WindowSize, display_id: Optional[int] = None) -> Node:
|
|
172
165
|
root = ElementTree.fromstring(xml_data)
|
|
173
|
-
|
|
166
|
+
node = parse_xml_element(root, wsize, display_id)
|
|
167
|
+
if node is None:
|
|
168
|
+
raise AndroidDriverException("Failed to parse xml")
|
|
169
|
+
return node
|
|
174
170
|
|
|
175
171
|
|
|
176
|
-
def parse_xml_element(
|
|
177
|
-
element, wsize: WindowSize, indexes: List[int] = [0]
|
|
178
|
-
) -> Node:
|
|
172
|
+
def parse_xml_element(element, wsize: WindowSize, display_id: Optional[int], indexes: List[int] = [0]) -> Optional[Node]:
|
|
179
173
|
"""
|
|
180
174
|
Recursively parse an XML element into a dictionary format.
|
|
181
175
|
"""
|
|
182
176
|
name = element.tag
|
|
183
177
|
if name == "node":
|
|
184
178
|
name = element.attrib.get("class", "node")
|
|
179
|
+
if display_id is not None:
|
|
180
|
+
elem_display_id = int(element.attrib.get("display-id", display_id))
|
|
181
|
+
if elem_display_id != display_id:
|
|
182
|
+
return
|
|
183
|
+
|
|
185
184
|
bounds = None
|
|
185
|
+
rect = None
|
|
186
186
|
# eg: bounds="[883,2222][1008,2265]"
|
|
187
187
|
if "bounds" in element.attrib:
|
|
188
188
|
bounds = element.attrib["bounds"]
|
|
189
189
|
bounds = list(map(int, re.findall(r"\d+", bounds)))
|
|
190
190
|
assert len(bounds) == 4
|
|
191
|
+
rect = Rect(x=bounds[0], y=bounds[1], width=bounds[2] - bounds[0], height=bounds[3] - bounds[1])
|
|
191
192
|
bounds = (
|
|
192
193
|
bounds[0] / wsize.width,
|
|
193
194
|
bounds[1] / wsize.height,
|
|
@@ -195,16 +196,20 @@ def parse_xml_element(
|
|
|
195
196
|
bounds[3] / wsize.height,
|
|
196
197
|
)
|
|
197
198
|
bounds = map(partial(round, ndigits=4), bounds)
|
|
199
|
+
|
|
198
200
|
elem = Node(
|
|
199
201
|
key="-".join(map(str, indexes)),
|
|
200
202
|
name=name,
|
|
201
203
|
bounds=bounds,
|
|
204
|
+
rect=rect,
|
|
202
205
|
properties={key: element.attrib[key] for key in element.attrib},
|
|
203
206
|
children=[],
|
|
204
207
|
)
|
|
205
208
|
|
|
206
209
|
# Construct xpath for children
|
|
207
210
|
for index, child in enumerate(element):
|
|
208
|
-
|
|
211
|
+
child_node = parse_xml_element(child, wsize, display_id, indexes + [index])
|
|
212
|
+
if child_node:
|
|
213
|
+
elem.children.append(child_node)
|
|
209
214
|
|
|
210
215
|
return elem
|
|
@@ -70,6 +70,26 @@ class BaseDriver(abc.ABC):
|
|
|
70
70
|
def home(self):
|
|
71
71
|
""" press home button """
|
|
72
72
|
raise NotImplementedError()
|
|
73
|
+
|
|
74
|
+
def back(self):
|
|
75
|
+
""" press back button """
|
|
76
|
+
raise NotImplementedError()
|
|
77
|
+
|
|
78
|
+
def app_switch(self):
|
|
79
|
+
""" switch app """
|
|
80
|
+
raise NotImplementedError()
|
|
81
|
+
|
|
82
|
+
def volume_up(self):
|
|
83
|
+
""" volume up """
|
|
84
|
+
raise NotImplementedError()
|
|
85
|
+
|
|
86
|
+
def volume_down(self):
|
|
87
|
+
""" volume down """
|
|
88
|
+
raise NotImplementedError()
|
|
89
|
+
|
|
90
|
+
def volume_mute(self):
|
|
91
|
+
""" volume mute """
|
|
92
|
+
raise NotImplementedError()
|
|
73
93
|
|
|
74
94
|
def wake_up(self):
|
|
75
95
|
""" wake up the device """
|
|
@@ -13,13 +13,14 @@ from functools import partial
|
|
|
13
13
|
from typing import List, Optional, Tuple
|
|
14
14
|
from xml.etree import ElementTree
|
|
15
15
|
|
|
16
|
+
import wdapy
|
|
16
17
|
from PIL import Image
|
|
17
18
|
|
|
18
19
|
from uiautodev.command_types import CurrentAppResponse
|
|
19
20
|
from uiautodev.driver.base_driver import BaseDriver
|
|
20
21
|
from uiautodev.exceptions import IOSDriverException
|
|
21
22
|
from uiautodev.model import Node, WindowSize
|
|
22
|
-
from uiautodev.utils.usbmux import
|
|
23
|
+
from uiautodev.utils.usbmux import select_device
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class IOSDriver(BaseDriver):
|
|
@@ -27,6 +28,7 @@ class IOSDriver(BaseDriver):
|
|
|
27
28
|
""" serial is the udid of the ios device """
|
|
28
29
|
super().__init__(serial)
|
|
29
30
|
self.device = select_device(serial)
|
|
31
|
+
self.wda = wdapy.AppiumUSBClient(self.device.serial)
|
|
30
32
|
|
|
31
33
|
def _request(self, method: str, path: str, payload: Optional[dict] = None) -> bytes:
|
|
32
34
|
conn = self.device.make_http_connection(port=8100)
|
|
@@ -56,29 +58,36 @@ class IOSDriver(BaseDriver):
|
|
|
56
58
|
return self._request_json("GET", "/status")
|
|
57
59
|
|
|
58
60
|
def screenshot(self, id: int = 0) -> Image.Image:
|
|
59
|
-
|
|
60
|
-
png_data = base64.b64decode(png_base64)
|
|
61
|
-
return Image.open(io.BytesIO(png_data))
|
|
61
|
+
return self.wda.screenshot()
|
|
62
62
|
|
|
63
63
|
def window_size(self):
|
|
64
|
-
return self.
|
|
64
|
+
return self.wda.window_size()
|
|
65
65
|
|
|
66
66
|
def dump_hierarchy(self) -> Tuple[str, Node]:
|
|
67
67
|
"""returns xml string and hierarchy object"""
|
|
68
|
-
|
|
68
|
+
t = self.wda.sourcetree()
|
|
69
|
+
xml_data = t.value
|
|
69
70
|
root = ElementTree.fromstring(xml_data)
|
|
70
71
|
return xml_data, parse_xml_element(root, WindowSize(width=1, height=1))
|
|
71
72
|
|
|
72
73
|
def tap(self, x: int, y: int):
|
|
73
|
-
self.
|
|
74
|
+
self.wda.tap(x, y)
|
|
74
75
|
|
|
75
76
|
def app_current(self) -> CurrentAppResponse:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return CurrentAppResponse(package=value["bundleId"], pid=value["pid"])
|
|
77
|
+
info = self.wda.app_current()
|
|
78
|
+
return CurrentAppResponse(package=info.bundle_id, pid=info.pid)
|
|
79
79
|
|
|
80
80
|
def home(self):
|
|
81
|
-
self.
|
|
81
|
+
self.wda.homescreen()
|
|
82
|
+
|
|
83
|
+
def app_switch(self):
|
|
84
|
+
raise NotImplementedError()
|
|
85
|
+
|
|
86
|
+
def volume_up(self):
|
|
87
|
+
self.wda.volume_up()
|
|
88
|
+
|
|
89
|
+
def volume_down(self):
|
|
90
|
+
self.wda.volume_down()
|
|
82
91
|
|
|
83
92
|
|
|
84
93
|
def parse_xml_element(element, wsize: WindowSize, indexes: List[int]=[0]) -> Node:
|
|
@@ -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)
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
"""Created on Mon Mar 04 2024 14:28:53 by codeskyblue
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
-
|
|
9
|
-
try:
|
|
10
|
-
__version__ = version("uiautodev")
|
|
11
|
-
except PackageNotFoundError:
|
|
12
|
-
__version__ = "0.0.0"
|
|
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.4.0}/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
|