uiautodev 0.9.0__py3-none-any.whl → 0.11.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 +1 -1
- uiautodev/app.py +2 -1
- uiautodev/command_proxy.py +10 -2
- uiautodev/command_types.py +7 -1
- uiautodev/driver/android.py +8 -2
- uiautodev/driver/base_driver.py +8 -1
- uiautodev/router/proxy.py +57 -0
- {uiautodev-0.9.0.dist-info → uiautodev-0.11.0.dist-info}/METADATA +3 -26
- {uiautodev-0.9.0.dist-info → uiautodev-0.11.0.dist-info}/RECORD +12 -11
- {uiautodev-0.9.0.dist-info → uiautodev-0.11.0.dist-info}/LICENSE +0 -0
- {uiautodev-0.9.0.dist-info → uiautodev-0.11.0.dist-info}/WHEEL +0 -0
- {uiautodev-0.9.0.dist-info → uiautodev-0.11.0.dist-info}/entry_points.txt +0 -0
uiautodev/__init__.py
CHANGED
uiautodev/app.py
CHANGED
|
@@ -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]:
|
uiautodev/command_proxy.py
CHANGED
|
@@ -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:
|
|
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:
|
uiautodev/command_types.py
CHANGED
|
@@ -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
|
uiautodev/driver/android.py
CHANGED
|
@@ -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) ->
|
|
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:
|
uiautodev/driver/base_driver.py
CHANGED
|
@@ -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()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: uiautodev
|
|
3
|
-
Version: 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 (
|
|
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的手机远程控制项目
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
uiautodev/__init__.py,sha256=
|
|
1
|
+
uiautodev/__init__.py,sha256=hgy1K91dPHmTwpLsj45Kim-m9Ghsd0tRvdB3H-FPdDo,165
|
|
2
2
|
uiautodev/__main__.py,sha256=0WZHyHW-M7FG5RexANNoIB5pkCX8xwQbTnmaOA9Y1kg,176
|
|
3
|
-
uiautodev/app.py,sha256=
|
|
3
|
+
uiautodev/app.py,sha256=K3u6WCobH3Q5HmXvrZff0Cq44L0j1J4eBVhSxnSNKiE,6600
|
|
4
4
|
uiautodev/appium_proxy.py,sha256=yMzPnIDo50hYSaq0g5bXUpgRrFa_849wNa2o7ZpxGNY,1773
|
|
5
5
|
uiautodev/binaries/scrcpy_server.jar,sha256=ojxWWfNsJg8QXAItJ7yz6v_6JgcOe6qe2mbQE3ehrbo,71200
|
|
6
6
|
uiautodev/case.py,sha256=Jk2_5X2F-XIPnGuYTCqOVQiwwchwOhF7uKK5oKv5shg,3919
|
|
7
7
|
uiautodev/cli.py,sha256=FL9PBGo0UTttRhGW7THrWqNn2ujsA-728y0O46h2Qjk,6240
|
|
8
|
-
uiautodev/command_proxy.py,sha256=
|
|
9
|
-
uiautodev/command_types.py,sha256=
|
|
8
|
+
uiautodev/command_proxy.py,sha256=Od8jSVDOUJWWcni-Jq8_IHd34VbfWECi0mL1wl86lIo,5446
|
|
9
|
+
uiautodev/command_types.py,sha256=wZa-yQpxzQO79iFMHShJELuZgYJm-yweG-TeSBvzBvs,1867
|
|
10
10
|
uiautodev/common.py,sha256=1A0kXfxVrp_i5mc_aRjuqSDWFFZ7DwZR9qpRLu2GMMg,1488
|
|
11
|
-
uiautodev/driver/android.py,sha256=
|
|
11
|
+
uiautodev/driver/android.py,sha256=k4Mjq2gmsokwCUN68VGCuhbl7HHEEyeowuTzQFXjg30,8181
|
|
12
12
|
uiautodev/driver/appium.py,sha256=U3TGpOXmu3tEa3E1ttTFoXehOfFyjavJQ3XA4CtqeBE,5308
|
|
13
|
-
uiautodev/driver/base_driver.py,sha256=
|
|
13
|
+
uiautodev/driver/base_driver.py,sha256=6MBNfEg_CtS4ed90SaSGbdqmf-JNaypqiBNaZBRKeAo,3027
|
|
14
14
|
uiautodev/driver/harmony.py,sha256=93pwlg04wazey8MQM6DEvcBkr52REYVw4bwz321fK38,8031
|
|
15
15
|
uiautodev/driver/ios.py,sha256=EOi9k-Y4qQS6Htdz_ycBf5jYCnOkruB-mR0Sc8J-coo,4204
|
|
16
16
|
uiautodev/driver/mock.py,sha256=0VtxBkZRMbHiQGHjqm8Ih2Ld6baXiVxX8yUTwWJQlnE,2422
|
|
@@ -27,14 +27,15 @@ uiautodev/remote/scrcpy.py,sha256=JNSwC35f4os-IQW4ixx7ky9clpqRWvA8X_obUkwuOh8,74
|
|
|
27
27
|
uiautodev/remote/touch_controller.py,sha256=dYl5XTLaYEyZiNJmKwHQpw9QhPSkN3iUetJSaiQJBHg,4255
|
|
28
28
|
uiautodev/router/android.py,sha256=xwzaZduMRJsyml-7eZjNtQMg_c22ZpFAq6TErc_-tlA,1358
|
|
29
29
|
uiautodev/router/device.py,sha256=iNJUexRLUmgGT_n1UgWybX8PvRSDinpoUpvKX-NZi10,4298
|
|
30
|
+
uiautodev/router/proxy.py,sha256=Kp_w1KfbKMp9RTERtLAQjn1_U3tUSC4ZWAliHeaJuug,2006
|
|
30
31
|
uiautodev/router/xml.py,sha256=MKVLhjMBqE4qbEraQxvdrVp_OBnylEL9Wti5lnmBDk4,891
|
|
31
32
|
uiautodev/static/demo.html,sha256=qC7qUZP5Af9T3V5EuFGbovzv8mArwiGMWsX_vcs_Bt0,1240
|
|
32
33
|
uiautodev/utils/common.py,sha256=L1qBBBS6jRgkXlGy5o6Xafo49auLXKRWyX9x8U_IKjc,4821
|
|
33
34
|
uiautodev/utils/envutils.py,sha256=Clyt2Hz9PXpK_fT0yWbMmixXyGvCaJO3LAgamM7aUVc,197
|
|
34
35
|
uiautodev/utils/exceptions.py,sha256=lL_G_E41KWvfXnl32-E4Vgr3_HyTboxq_EwzdQMuvK4,637
|
|
35
36
|
uiautodev/utils/usbmux.py,sha256=LYupLDn7U4KFKhYQJrmIroS-3040gqZQVDRDB_FNDJM,17386
|
|
36
|
-
uiautodev-0.
|
|
37
|
-
uiautodev-0.
|
|
38
|
-
uiautodev-0.
|
|
39
|
-
uiautodev-0.
|
|
40
|
-
uiautodev-0.
|
|
37
|
+
uiautodev-0.11.0.dist-info/LICENSE,sha256=RyeW676gBYO7AVVP2zQgfEx5rPSt46vR47xXZe7TlX4,1068
|
|
38
|
+
uiautodev-0.11.0.dist-info/METADATA,sha256=r_WqmWQoTPFhK64k6wK3wxptY3MbP-G6Ec09tiJ1WyY,2555
|
|
39
|
+
uiautodev-0.11.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
40
|
+
uiautodev-0.11.0.dist-info/entry_points.txt,sha256=zBY8GgseYAAzPFA5Cf4rCCS9ivdyWsNxMVVYIaGAHJU,88
|
|
41
|
+
uiautodev-0.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|