uiautodev 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -5,4 +5,4 @@
5
5
  """
6
6
 
7
7
  # version is auto managed by poetry
8
- __version__ = "0.4.0"
8
+ __version__ = "0.5.0"
uiautodev/cli.py CHANGED
@@ -140,7 +140,7 @@ def self_update():
140
140
  @click.option("-f", "--force", is_flag=True, default=False, help="shutdown alrealy runningserver")
141
141
  @click.option("-s", "--no-browser", is_flag=True, default=False, help="silent mode, do not open browser")
142
142
  def server(port: int, host: str, reload: bool, force: bool, no_browser: bool):
143
- logger.info("version: %s", __version__)
143
+ print("uiautodev version:", __version__)
144
144
  if force:
145
145
  try:
146
146
  httpx.get(f"http://{host}:{port}/shutdown", timeout=3)
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  import time
10
10
  import typing
11
- from typing import Callable, Dict, Optional
11
+ from typing import Callable, Dict, List, Optional, Union
12
12
 
13
13
  from pydantic import BaseModel
14
14
 
@@ -17,7 +17,7 @@ from uiautodev.command_types import AppLaunchRequest, AppTerminateRequest, By, C
17
17
  WindowSizeResponse
18
18
  from uiautodev.driver.base_driver import BaseDriver
19
19
  from uiautodev.exceptions import ElementNotFoundError
20
- from uiautodev.model import Node
20
+ from uiautodev.model import Node, AppInfo
21
21
  from uiautodev.utils.common import node_travel
22
22
 
23
23
  COMMANDS: Dict[Command, Callable] = {}
@@ -39,17 +39,21 @@ def get_command_params_type(command: Command) -> Optional[BaseModel]:
39
39
  return type_hints.get("params")
40
40
 
41
41
 
42
- def send_command(driver: BaseDriver, command: Command, params=None):
42
+ def send_command(driver: BaseDriver, command: Union[str, Command], params=None):
43
43
  if command not in COMMANDS:
44
44
  raise NotImplementedError(f"command {command} not implemented")
45
45
  func = COMMANDS[command]
46
- type_hints = typing.get_type_hints(func)
47
- if type_hints.get("params"):
46
+ params_model = get_command_params_type(command)
47
+ if params_model:
48
48
  if params is None:
49
49
  raise ValueError(f"params is required for {command}")
50
- if not isinstance(params, type_hints["params"]):
51
- raise TypeError(f"params should be {type_hints['params']}")
52
- if params is None:
50
+ if isinstance(params, dict):
51
+ params = params_model.model_validate(params)
52
+ elif isinstance(params, params_model):
53
+ pass
54
+ else:
55
+ raise TypeError(f"params should be {params_model}", params)
56
+ if not params:
53
57
  return func(driver)
54
58
  return func(driver, params)
55
59
 
@@ -177,3 +181,10 @@ def click_element(driver: BaseDriver, params: FindElementRequest):
177
181
  center_x = (node.bounds[0] + node.bounds[2]) / 2
178
182
  center_y = (node.bounds[1] + node.bounds[3]) / 2
179
183
  tap(driver, TapRequest(x=center_x, y=center_y, isPercent=True))
184
+
185
+
186
+ @register(Command.APP_LIST)
187
+ def app_list(driver: BaseDriver) -> List[AppInfo]:
188
+ # added in v0.5.0
189
+ return driver.app_list()
190
+
@@ -22,6 +22,7 @@ class Command(str, enum.Enum):
22
22
  APP_CURRENT = "currentApp"
23
23
  APP_LAUNCH = "appLaunch"
24
24
  APP_TERMINATE = "appTerminate"
25
+ APP_LIST = "appList"
25
26
 
26
27
  GET_WINDOW_SIZE = "getWindowSize"
27
28
  HOME = "home"
@@ -4,12 +4,11 @@
4
4
  """Created on Fri Mar 01 2024 14:19:29 by codeskyblue
5
5
  """
6
6
 
7
- import json
8
7
  import logging
9
8
  import re
10
9
  import time
11
10
  from functools import cached_property, partial
12
- from typing import List, Optional, Tuple
11
+ from typing import Iterator, List, Optional, Tuple
13
12
  from xml.etree import ElementTree
14
13
 
15
14
  import adbutils
@@ -18,9 +17,8 @@ from PIL import Image
18
17
 
19
18
  from uiautodev.command_types import CurrentAppResponse
20
19
  from uiautodev.driver.base_driver import BaseDriver
21
- from uiautodev.driver.udt.udt import UDT, UDTError
22
20
  from uiautodev.exceptions import AndroidDriverException, RequestError
23
- from uiautodev.model import Node, Rect, ShellResponse, WindowSize
21
+ from uiautodev.model import Node, AppInfo, Rect, ShellResponse, WindowSize
24
22
  from uiautodev.utils.common import fetch_through_socket
25
23
 
26
24
  logger = logging.getLogger(__name__)
@@ -29,28 +27,15 @@ class AndroidDriver(BaseDriver):
29
27
  def __init__(self, serial: str):
30
28
  super().__init__(serial)
31
29
  self.adb_device = adbutils.device(serial)
32
- self._try_dump_list = [
33
- self._get_u2_hierarchy,
34
- self._get_udt_dump_hierarchy,
35
- # self._get_appium_hierarchy,
36
- ]
37
-
38
- @cached_property
39
- def udt(self) -> UDT:
40
- return UDT(self.adb_device)
41
30
 
42
31
  @cached_property
43
32
  def ud(self) -> u2.Device:
44
33
  return u2.connect_usb(self.serial)
45
34
 
46
35
  def screenshot(self, id: int) -> Image.Image:
47
- try:
48
- return self.adb_device.screenshot() # display_id is not OK now
49
- except adbutils.AdbError as e:
50
- logger.warning("screenshot error: %s", str(e))
51
- if id > 0:
52
- raise AndroidDriverException("multi-display is not supported yet for uiautomator2")
53
- return self.ud.screenshot()
36
+ if id > 0:
37
+ raise AndroidDriverException("multi-display is not supported yet for uiautomator2")
38
+ return self.ud.screenshot()
54
39
 
55
40
  def shell(self, command: str) -> ShellResponse:
56
41
  try:
@@ -82,38 +67,12 @@ class AndroidDriver(BaseDriver):
82
67
 
83
68
  uiautomator dump errors:
84
69
  - ERROR: could not get idle state.
85
-
86
70
  """
87
- for dump_func in self._try_dump_list[:]:
88
- try:
89
- logger.debug(f"try to dump with %s", dump_func.__name__)
90
- result = dump_func()
91
- logger.debug("dump success")
92
- self._try_dump_list.remove(dump_func)
93
- self._try_dump_list.insert(0, dump_func)
94
- return result
95
- except Exception as e:
96
- logger.exception("unexpected dump error: %s", e)
97
- raise AndroidDriverException("Failed to dump hierarchy")
98
-
99
- def _get_u2_hierarchy(self) -> str:
100
- d = u2.connect_usb(self.serial)
101
- return d.dump_hierarchy()
102
-
103
- def _get_appium_hierarchy(self) -> str:
104
- c = self.adb_device.create_connection(adbutils.Network.TCP, 6790)
105
71
  try:
106
- content = fetch_through_socket(c, "/wd/hub/session/0/source", timeout=10)
107
- return json.loads(content)["value"]
108
- except (adbutils.AdbError, RequestError) as e:
109
- raise AndroidDriverException(
110
- f"Failed to get hierarchy from appium server: {str(e)}"
111
- )
112
- finally:
113
- c.close()
114
-
115
- def _get_udt_dump_hierarchy(self) -> str:
116
- return self.udt.dump_hierarchy()
72
+ return self.ud.dump_hierarchy()
73
+ except Exception as e:
74
+ logger.exception("unexpected dump error: %s", e)
75
+ raise AndroidDriverException("Failed to dump hierarchy")
117
76
 
118
77
  def tap(self, x: int, y: int):
119
78
  self.adb_device.click(x, y)
@@ -159,6 +118,22 @@ class AndroidDriver(BaseDriver):
159
118
 
160
119
  def volume_mute(self):
161
120
  self.adb_device.keyevent("VOLUME_MUTE")
121
+
122
+ def app_list(self) -> List[AppInfo]:
123
+ results = []
124
+ output = self.adb_device.shell(["pm", "list", "packages", '-3'])
125
+ for m in re.finditer(r"^package:([^\s]+)\r?$", output, re.M):
126
+ packageName = m.group(1)
127
+ results.append(AppInfo(packageName=packageName))
128
+ return results
129
+
130
+ def open_app_file(self, package: str) -> Iterator[bytes]:
131
+ line = self.adb_device.shell(f"pm path {package}")
132
+ if not line.startswith("package:"):
133
+ raise AndroidDriverException(f"Failed to get package path: {line}")
134
+ remote_path = line.split(':', 1)[1]
135
+ yield from self.adb_device.sync.iter_content(remote_path)
136
+
162
137
 
163
138
 
164
139
  def parse_xml(xml_data: str, wsize: WindowSize, display_id: Optional[int] = None) -> Node:
@@ -4,14 +4,14 @@
4
4
  """Created on Fri Mar 01 2024 14:18:30 by codeskyblue
5
5
  """
6
6
  import abc
7
- import enum
8
- from typing import Tuple
7
+ from io import FileIO
8
+ from typing import Iterator, List, Tuple
9
9
 
10
10
  from PIL import Image
11
11
  from pydantic import BaseModel
12
12
 
13
13
  from uiautodev.command_types import CurrentAppResponse
14
- from uiautodev.model import Node, ShellResponse, WindowSize
14
+ from uiautodev.model import Node, AppInfo, ShellResponse, WindowSize
15
15
 
16
16
 
17
17
  class BaseDriver(abc.ABC):
@@ -93,4 +93,13 @@ class BaseDriver(abc.ABC):
93
93
 
94
94
  def wake_up(self):
95
95
  """ wake up the device """
96
- raise NotImplementedError()
96
+ raise NotImplementedError()
97
+
98
+ def app_list(self) -> List[AppInfo]:
99
+ """ list installed packages """
100
+ raise NotImplementedError()
101
+
102
+ def open_app_file(self, package: str) -> Iterator[bytes]:
103
+ """ open app file """
104
+ raise NotImplementedError()
105
+
uiautodev/driver/ios.py CHANGED
@@ -5,10 +5,7 @@
5
5
  """
6
6
 
7
7
 
8
- import base64
9
- import io
10
8
  import json
11
- import re
12
9
  from functools import partial
13
10
  from typing import List, Optional, Tuple
14
11
  from xml.etree import ElementTree
uiautodev/model.py CHANGED
@@ -42,4 +42,8 @@ class Node(BaseModel):
42
42
 
43
43
  class WindowSize(typing.NamedTuple):
44
44
  width: int
45
- height: int
45
+ height: int
46
+
47
+
48
+ class AppInfo(BaseModel):
49
+ packageName: str
@@ -6,9 +6,10 @@
6
6
 
7
7
  import io
8
8
  import logging
9
- from typing import Any, List
9
+ from typing import Any, Dict, List
10
10
 
11
11
  from fastapi import APIRouter, Response
12
+ from fastapi.responses import StreamingResponse
12
13
  from pydantic import BaseModel
13
14
 
14
15
  from uiautodev import command_proxy
@@ -102,14 +103,25 @@ def make_router(provider: BaseProvider) -> APIRouter:
102
103
  return command_proxy.app_current(driver)
103
104
 
104
105
  @router.post('/{serial}/command/{command}')
105
- def _command_proxy_other(serial: str, command: Command, params: Any = None):
106
+ def _command_proxy_other(serial: str, command: Command, params: Dict[str, Any] = None):
106
107
  """Run a command on the device"""
107
108
  driver = provider.get_device_driver(serial)
108
- func = command_proxy.COMMANDS[command]
109
- if params is None:
110
- response = func(driver)
111
- else:
112
- response = func(driver, params)
109
+ response = command_proxy.send_command(driver, command, params)
113
110
  return response
111
+
112
+ @router.get('/{serial}/backupApp')
113
+ def _backup_app(serial: str, packageName: str):
114
+ """Backup app
115
+
116
+ Added in 0.5.0
117
+ """
118
+ driver = provider.get_device_driver(serial)
119
+ file_name = f"{packageName}.apk"
120
+ headers = {
121
+ 'Content-Disposition': f'attachment; filename="{file_name}"'
122
+ }
123
+ return StreamingResponse(driver.open_app_file(packageName), headers=headers)
124
+
125
+
114
126
 
115
127
  return router
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiautodev
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
5
5
  Home-page: https://uiauto.dev
6
6
  License: MIT
@@ -14,13 +14,10 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
- Provides-Extra: appium
18
17
  Requires-Dist: adbutils (>=2.7.0,<3.0.0)
19
- Requires-Dist: appium-python-client (>=4.0.0,<5.0.0) ; extra == "appium"
20
18
  Requires-Dist: click (>=8.1.7,<9.0.0)
21
19
  Requires-Dist: construct
22
20
  Requires-Dist: fastapi (>=0.111.0,<0.112.0)
23
- Requires-Dist: httpretty (>=1.1.4,<2.0.0)
24
21
  Requires-Dist: httpx
25
22
  Requires-Dist: lxml
26
23
  Requires-Dist: pillow
@@ -38,6 +35,8 @@ Description-Content-Type: text/markdown
38
35
 
39
36
  https://uiauto.dev
40
37
 
38
+ > backup site: https://uiauto.devsleep.com
39
+
41
40
  UI Inspector for Android and iOS, help inspector element properties, and auto generate XPath, script.
42
41
 
43
42
  # Install
@@ -1,30 +1,30 @@
1
- uiautodev/__init__.py,sha256=qyg24JoYZ2bIaFcBNFyeZA8faOTV87Gk978H-l29GWE,164
1
+ uiautodev/__init__.py,sha256=uBLcbZ_b1lvAbHh0kXPeO4VWJvl-Zc4XvUMhg6PmzyY,164
2
2
  uiautodev/__main__.py,sha256=0WZHyHW-M7FG5RexANNoIB5pkCX8xwQbTnmaOA9Y1kg,176
3
3
  uiautodev/app.py,sha256=R7AV5uuh4VLkrF9Kl_JJWUiPQnuIeJ02CN1W7tNGPKE,2498
4
4
  uiautodev/appium_proxy.py,sha256=yMzPnIDo50hYSaq0g5bXUpgRrFa_849wNa2o7ZpxGNY,1773
5
5
  uiautodev/case.py,sha256=Jk2_5X2F-XIPnGuYTCqOVQiwwchwOhF7uKK5oKv5shg,3919
6
- uiautodev/cli.py,sha256=VBs5wDfmdBIyQQ8Nda0ACe84RRIAd8KRttrjAxm9YJc,6234
7
- uiautodev/command_proxy.py,sha256=87n1s27xoslKKJ0jkfGWHuUhsJsCANAYx6v_plgKDW0,5007
8
- uiautodev/command_types.py,sha256=Kf_55jrUr2nxrrZorg4urlruKx0cnsuXQBEzKeUXpJA,1736
6
+ uiautodev/cli.py,sha256=K4CEvGJSDWLAFR5tvls2Qp4evQ2lApLoUxA-4DI2-Sc,6235
7
+ uiautodev/command_proxy.py,sha256=gP4oTCjA1uKteWq5NOcHQLrI34el0ag5LUHNEq_j7Ko,5280
8
+ uiautodev/command_types.py,sha256=pWdnCS4FRnEIiR1ynd4cjXX8zeFndjPztacBlukt61E,1761
9
9
  uiautodev/common.py,sha256=t1jmG7S0LVXAigtg86J3vM2XXf1xYPfDV_HwSwyl3OI,552
10
- uiautodev/driver/android.py,sha256=xtMAPWHrc53MbZyRj1d9mqaqaL1eyVI6ORv3BEd0Epg,7207
10
+ uiautodev/driver/android.py,sha256=aKx6mW0eUMejv2uItkvDQg6o5nxtAOJ3m0gqomRXC_g,6394
11
11
  uiautodev/driver/appium.py,sha256=U3TGpOXmu3tEa3E1ttTFoXehOfFyjavJQ3XA4CtqeBE,5308
12
- uiautodev/driver/base_driver.py,sha256=f-C8vfE1F6EgDTUeBsv7UBTynmqhYq5BNMi4wawUIvM,2517
13
- uiautodev/driver/ios.py,sha256=U0zozuYs-hCqKCb9O5-Vyuku08gkgQuoUF4-xVWkg1U,4238
12
+ uiautodev/driver/base_driver.py,sha256=8uo7DgEKfgW_wf-HpkmFxjNRkElpFEDjjg45Q2pgiWA,2816
13
+ uiautodev/driver/ios.py,sha256=EOi9k-Y4qQS6Htdz_ycBf5jYCnOkruB-mR0Sc8J-coo,4204
14
14
  uiautodev/driver/mock.py,sha256=0VtxBkZRMbHiQGHjqm8Ih2Ld6baXiVxX8yUTwWJQlnE,2422
15
15
  uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk,sha256=cKUVKpqEiGRXODeqpwzVWllyjdSLyowFm94a6jDTvhI,3675062
16
16
  uiautodev/driver/udt/udt.py,sha256=p6opbUtYxEGTINIX83F6m2CtzB42iSSBYRv1SjXCEFg,8351
17
17
  uiautodev/exceptions.py,sha256=TuRD5SWQk5N2_KjrcDuXG_p84LBhLa2QEEXyFNFm0yQ,465
18
- uiautodev/model.py,sha256=qDKJyssJYIv_7IJwYHUtFbk-qF7ZquTaNAnhm6J8WcE,827
18
+ uiautodev/model.py,sha256=ij9Ct_OboSygyU_cQHoXzQ9czcwGogoezwXjyY-Cr0U,876
19
19
  uiautodev/provider.py,sha256=HDD_Jj-cJVfBceunzCYU9zJvGVya7Jd35GG9h85BY-0,2370
20
- uiautodev/router/device.py,sha256=lbHmoV--qBDP-lV9UVDYKF669WwxMZCDp-OCpoQJKQU,4425
20
+ uiautodev/router/device.py,sha256=9lLN7L1lExWUZ39uKz3fd56s3dkgdTfrKgcs9jQDD0I,4846
21
21
  uiautodev/router/xml.py,sha256=MKVLhjMBqE4qbEraQxvdrVp_OBnylEL9Wti5lnmBDk4,891
22
22
  uiautodev/static/demo.html,sha256=qC7qUZP5Af9T3V5EuFGbovzv8mArwiGMWsX_vcs_Bt0,1240
23
23
  uiautodev/utils/common.py,sha256=HuXJvipkg1QQg6vCD7OxH6JQtqbSVbXNzI1X2OVcEcU,4785
24
24
  uiautodev/utils/exceptions.py,sha256=lL_G_E41KWvfXnl32-E4Vgr3_HyTboxq_EwzdQMuvK4,637
25
25
  uiautodev/utils/usbmux.py,sha256=LYupLDn7U4KFKhYQJrmIroS-3040gqZQVDRDB_FNDJM,17386
26
- uiautodev-0.4.0.dist-info/LICENSE,sha256=RyeW676gBYO7AVVP2zQgfEx5rPSt46vR47xXZe7TlX4,1068
27
- uiautodev-0.4.0.dist-info/METADATA,sha256=tY-frsy05wnBaJfTOmISViH1v20e7xq8xPnw6LmtzYE,2404
28
- uiautodev-0.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- uiautodev-0.4.0.dist-info/entry_points.txt,sha256=zBY8GgseYAAzPFA5Cf4rCCS9ivdyWsNxMVVYIaGAHJU,88
30
- uiautodev-0.4.0.dist-info/RECORD,,
26
+ uiautodev-0.5.0.dist-info/LICENSE,sha256=RyeW676gBYO7AVVP2zQgfEx5rPSt46vR47xXZe7TlX4,1068
27
+ uiautodev-0.5.0.dist-info/METADATA,sha256=jcCkmA85d8iUEKD7yO-JbHnykMP-gkiSy6zHHCULv34,2310
28
+ uiautodev-0.5.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
+ uiautodev-0.5.0.dist-info/entry_points.txt,sha256=zBY8GgseYAAzPFA5Cf4rCCS9ivdyWsNxMVVYIaGAHJU,88
30
+ uiautodev-0.5.0.dist-info/RECORD,,