uiautodev 0.9.0__tar.gz → 0.11.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.

Files changed (40) hide show
  1. {uiautodev-0.9.0 → uiautodev-0.11.0}/PKG-INFO +3 -26
  2. {uiautodev-0.9.0 → uiautodev-0.11.0}/README.md +1 -24
  3. {uiautodev-0.9.0 → uiautodev-0.11.0}/pyproject.toml +2 -2
  4. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/__init__.py +1 -1
  5. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/app.py +2 -1
  6. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/command_proxy.py +10 -2
  7. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/command_types.py +7 -1
  8. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/android.py +8 -2
  9. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/base_driver.py +8 -1
  10. uiautodev-0.11.0/uiautodev/router/proxy.py +57 -0
  11. {uiautodev-0.9.0 → uiautodev-0.11.0}/LICENSE +0 -0
  12. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/__main__.py +0 -0
  13. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/appium_proxy.py +0 -0
  14. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/binaries/scrcpy_server.jar +0 -0
  15. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/case.py +0 -0
  16. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/cli.py +0 -0
  17. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/common.py +0 -0
  18. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/appium.py +0 -0
  19. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/harmony.py +0 -0
  20. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/ios.py +0 -0
  21. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/mock.py +0 -0
  22. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/testdata/layout.json +0 -0
  23. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
  24. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/driver/udt/udt.py +0 -0
  25. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/exceptions.py +0 -0
  26. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/model.py +0 -0
  27. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/provider.py +0 -0
  28. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/remote/android_input.py +0 -0
  29. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/remote/harmony_mjpeg.py +0 -0
  30. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/remote/keycode.py +0 -0
  31. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/remote/scrcpy.py +0 -0
  32. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/remote/touch_controller.py +0 -0
  33. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/router/android.py +0 -0
  34. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/router/device.py +0 -0
  35. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/router/xml.py +0 -0
  36. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/static/demo.html +0 -0
  37. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/utils/common.py +0 -0
  38. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/utils/envutils.py +0 -0
  39. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/utils/exceptions.py +0 -0
  40. {uiautodev-0.9.0 → uiautodev-0.11.0}/uiautodev/utils/usbmux.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: uiautodev
3
- Version: 0.9.0
3
+ Version: 0.11.0
4
4
  Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
5
5
  License: MIT
6
6
  Author: codeskyblue
@@ -18,7 +18,7 @@ Requires-Dist: Pillow
18
18
  Requires-Dist: adbutils (>=2.8.10,<3)
19
19
  Requires-Dist: click (>=8.1.7,<9.0.0)
20
20
  Requires-Dist: construct
21
- Requires-Dist: fastapi (==0.115.12)
21
+ Requires-Dist: fastapi (>=0.115.12,<1)
22
22
  Requires-Dist: httpx
23
23
  Requires-Dist: lxml
24
24
  Requires-Dist: pydantic (>=2.6,<3.0)
@@ -76,31 +76,8 @@ uiauto.dev
76
76
  ```
77
77
 
78
78
  # DEVELOP
79
- ```bash
80
- # install poetry (python package manager)
81
- pip install poetry # pipx install poetry
82
-
83
- # install deps
84
- poetry install
85
-
86
- # format import
87
- make format
88
-
89
- # run server
90
- make dev
91
-
92
- # If you encounter the error NameError: name 'int2byte' is not defined,
93
- # try installing a stable version of the construct package to resolve it:
94
- # and restart: make dev
95
- pip install construct==2.9.45
96
79
 
97
- ```
98
-
99
- 运行测试
100
-
101
- ```sh
102
- make test
103
- ```
80
+ see [DEVELOP.md](DEVELOP.md)
104
81
 
105
82
  # Links
106
83
  - https://app.tangoapp.dev/ 基于webadb的手机远程控制项目
@@ -42,31 +42,8 @@ uiauto.dev
42
42
  ```
43
43
 
44
44
  # DEVELOP
45
- ```bash
46
- # install poetry (python package manager)
47
- pip install poetry # pipx install poetry
48
-
49
- # install deps
50
- poetry install
51
-
52
- # format import
53
- make format
54
-
55
- # run server
56
- make dev
57
-
58
- # If you encounter the error NameError: name 'int2byte' is not defined,
59
- # try installing a stable version of the construct package to resolve it:
60
- # and restart: make dev
61
- pip install construct==2.9.45
62
45
 
63
- ```
64
-
65
- 运行测试
66
-
67
- ```sh
68
- make test
69
- ```
46
+ see [DEVELOP.md](DEVELOP.md)
70
47
 
71
48
  # Links
72
49
  - https://app.tangoapp.dev/ 基于webadb的手机远程控制项目
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiautodev"
3
- version = "0.9.0"
3
+ version = "0.11.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>"]
@@ -17,7 +17,7 @@ adbutils = ">=2.8.10,<3"
17
17
  click = "^8.1.7"
18
18
  pygments = ">=2"
19
19
  uiautomator2 = ">=3.2.0,<4"
20
- fastapi = "0.115.12"
20
+ fastapi = ">=0.115.12,<1"
21
21
  pydantic = "^2.6"
22
22
  wdapy = ">0.2.2,<1"
23
23
  websockets = ">=10.4"
@@ -5,4 +5,4 @@
5
5
  """
6
6
 
7
7
  # version is auto managed by poetry
8
- __version__ = "0.9.0"
8
+ __version__ = "0.11.0"
@@ -27,6 +27,7 @@ from uiautodev.provider import AndroidProvider, HarmonyProvider, IOSProvider, Mo
27
27
  from uiautodev.remote.scrcpy import ScrcpyServer
28
28
  from uiautodev.router.android import router as android_device_router
29
29
  from uiautodev.router.device import make_router
30
+ from uiautodev.router.proxy import router as proxy_router
30
31
  from uiautodev.router.xml import router as xml_router
31
32
  from uiautodev.utils.envutils import Environment
32
33
 
@@ -70,7 +71,7 @@ else:
70
71
 
71
72
  app.include_router(xml_router, prefix="/api/xml", tags=["xml"])
72
73
  app.include_router(android_device_router, prefix="/api/android", tags=["android"])
73
-
74
+ app.include_router(proxy_router, prefix="/proxy", tags=["proxy"])
74
75
 
75
76
  @app.get('/api/{platform}/features')
76
77
  def get_features(platform: str) -> Dict[str, bool]:
@@ -13,7 +13,7 @@ from typing import Callable, Dict, List, Optional, Union
13
13
  from pydantic import BaseModel
14
14
 
15
15
  from uiautodev.command_types import AppLaunchRequest, AppTerminateRequest, By, Command, CurrentAppResponse, \
16
- DumpResponse, FindElementRequest, FindElementResponse, InstallAppRequest, InstallAppResponse, TapRequest, \
16
+ DumpResponse, FindElementRequest, FindElementResponse, InstallAppRequest, InstallAppResponse, SendKeysRequest, TapRequest, \
17
17
  WindowSizeResponse
18
18
  from uiautodev.driver.base_driver import BaseDriver
19
19
  from uiautodev.exceptions import ElementNotFoundError
@@ -39,7 +39,7 @@ 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: Union[str, Command], params=None):
42
+ def send_command(driver: BaseDriver, command: Command, params=None):
43
43
  if command not in COMMANDS:
44
44
  raise NotImplementedError(f"command {command} not implemented")
45
45
  func = COMMANDS[command]
@@ -142,6 +142,14 @@ def dump(driver: BaseDriver) -> DumpResponse:
142
142
  def wake_up(driver: BaseDriver):
143
143
  driver.wake_up()
144
144
 
145
+ @register(Command.SEND_KEYS)
146
+ def send_keys(driver: BaseDriver, params: SendKeysRequest):
147
+ driver.send_keys(params.text)
148
+
149
+ @register(Command.CLEAR_TEXT)
150
+ def clear_text(driver: BaseDriver):
151
+ driver.clear_text()
152
+
145
153
 
146
154
  def node_match(node: Node, by: By, value: str) -> bool:
147
155
  if by == By.ID:
@@ -39,6 +39,8 @@ class Command(str, enum.Enum):
39
39
  VOLUME_UP = "volumeUp"
40
40
  VOLUME_DOWN = "volumeDown"
41
41
  VOLUME_MUTE = "volumeMute"
42
+ SEND_KEYS = "sendKeys"
43
+ CLEAR_TEXT = "clearText"
42
44
 
43
45
 
44
46
  class TapRequest(BaseModel):
@@ -94,4 +96,8 @@ class FindElementRequest(BaseModel):
94
96
 
95
97
  class FindElementResponse(BaseModel):
96
98
  count: int
97
- value: List[Node]
99
+ value: List[Node]
100
+
101
+
102
+ class SendKeysRequest(BaseModel):
103
+ text: str
@@ -127,7 +127,7 @@ class AndroidDriver(BaseDriver):
127
127
  def volume_mute(self):
128
128
  self.adb_device.keyevent("VOLUME_MUTE")
129
129
 
130
- def get_app_version(self, package_name: str) -> Optional[dict]:
130
+ def get_app_version(self, package_name: str) -> dict:
131
131
  """
132
132
  Get the version information of an app, including mainVersion and subVersion.
133
133
 
@@ -172,11 +172,17 @@ class AndroidDriver(BaseDriver):
172
172
 
173
173
  def open_app_file(self, package: str) -> Iterator[bytes]:
174
174
  line = self.adb_device.shell(f"pm path {package}")
175
+ assert isinstance(line, str)
175
176
  if not line.startswith("package:"):
176
177
  raise AndroidDriverException(f"Failed to get package path: {line}")
177
178
  remote_path = line.split(':', 1)[1]
178
179
  yield from self.adb_device.sync.iter_content(remote_path)
179
-
180
+
181
+ def send_keys(self, text: str):
182
+ self.ud.send_keys(text)
183
+
184
+ def clear_text(self):
185
+ self.ud.clear_text()
180
186
 
181
187
 
182
188
  def parse_xml(xml_data: str, wsize: WindowSize, display_id: Optional[int] = None) -> Node:
@@ -102,4 +102,11 @@ class BaseDriver(abc.ABC):
102
102
  def open_app_file(self, package: str) -> Iterator[bytes]:
103
103
  """ open app file """
104
104
  raise NotImplementedError()
105
-
105
+
106
+ def send_keys(self, text: str):
107
+ """ send keys to device """
108
+ raise NotImplementedError()
109
+
110
+ def clear_text(self):
111
+ """ clear text input on device """
112
+ raise NotImplementedError()
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ import httpx
5
+ import websockets
6
+ from fastapi import APIRouter, HTTPException, Request, WebSocket, WebSocketDisconnect
7
+ from fastapi.responses import Response
8
+
9
+ logger = logging.getLogger(__name__)
10
+ router = APIRouter()
11
+
12
+
13
+ # HTTP 转发
14
+ @router.api_route("/http/{target_url:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
15
+ async def proxy_http(request: Request, target_url: str):
16
+ logger.info(f"HTTP target_url: {target_url}")
17
+
18
+ async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as client:
19
+ body = await request.body()
20
+ resp = await client.request(
21
+ request.method,
22
+ target_url,
23
+ content=body,
24
+ headers={k: v for k, v in request.headers.items() if k.lower() != "host" and k.lower() != "x-target-url"}
25
+ )
26
+ return Response(content=resp.content, status_code=resp.status_code, headers=dict(resp.headers))
27
+
28
+ # WebSocket 转发
29
+ @router.websocket("/ws/{target_url:path}")
30
+ async def proxy_ws(websocket: WebSocket, target_url: str):
31
+ await websocket.accept()
32
+ logger.info(f"WebSocket target_url: {target_url}")
33
+
34
+ try:
35
+ async with websockets.connect(target_url) as target_ws:
36
+ async def from_client():
37
+ while True:
38
+ msg = await websocket.receive_text()
39
+ await target_ws.send(msg)
40
+
41
+ async def from_server():
42
+ while True:
43
+ msg = await target_ws.recv()
44
+ if isinstance(msg, bytes):
45
+ await websocket.send_bytes(msg)
46
+ elif isinstance(msg, str):
47
+ await websocket.send_text(msg)
48
+ else:
49
+ raise RuntimeError("Unknown message type", msg)
50
+
51
+ await asyncio.gather(from_client(), from_server())
52
+
53
+ except WebSocketDisconnect:
54
+ pass
55
+ except Exception as e:
56
+ logger.error(f"WS Error: {e}")
57
+ await websocket.close()
File without changes
File without changes
File without changes
File without changes