uiautodev 0.5.0__tar.gz → 0.13.3__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.
Files changed (47) hide show
  1. {uiautodev-0.5.0 → uiautodev-0.13.3}/PKG-INFO +52 -24
  2. uiautodev-0.13.3/README.md +76 -0
  3. {uiautodev-0.5.0 → uiautodev-0.13.3}/pyproject.toml +17 -14
  4. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/__init__.py +1 -1
  5. uiautodev-0.13.3/uiautodev/app.py +216 -0
  6. uiautodev-0.13.3/uiautodev/binaries/scrcpy_server.jar +0 -0
  7. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/cli.py +61 -33
  8. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/command_proxy.py +12 -6
  9. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/command_types.py +7 -1
  10. uiautodev-0.13.3/uiautodev/common.py +56 -0
  11. uiautodev-0.13.3/uiautodev/driver/android/__init__.py +2 -0
  12. uiautodev-0.5.0/uiautodev/driver/android.py → uiautodev-0.13.3/uiautodev/driver/android/adb_driver.py +80 -74
  13. uiautodev-0.13.3/uiautodev/driver/android/common.py +61 -0
  14. uiautodev-0.13.3/uiautodev/driver/android/u2_driver.py +68 -0
  15. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/driver/base_driver.py +9 -4
  16. uiautodev-0.13.3/uiautodev/driver/harmony.py +364 -0
  17. uiautodev-0.13.3/uiautodev/driver/testdata/layout.json +1 -0
  18. uiautodev-0.13.3/uiautodev/exceptions.py +26 -0
  19. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/model.py +10 -3
  20. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/provider.py +34 -11
  21. uiautodev-0.13.3/uiautodev/remote/android_input.py +74 -0
  22. uiautodev-0.13.3/uiautodev/remote/harmony_mjpeg.py +199 -0
  23. uiautodev-0.13.3/uiautodev/remote/keycode.py +350 -0
  24. uiautodev-0.13.3/uiautodev/remote/scrcpy.py +179 -0
  25. uiautodev-0.13.3/uiautodev/remote/touch_controller.py +123 -0
  26. uiautodev-0.13.3/uiautodev/router/android.py +42 -0
  27. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/router/device.py +6 -20
  28. uiautodev-0.13.3/uiautodev/router/proxy.py +178 -0
  29. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/utils/common.py +11 -7
  30. uiautodev-0.13.3/uiautodev/utils/envutils.py +9 -0
  31. uiautodev-0.5.0/README.md +0 -54
  32. uiautodev-0.5.0/uiautodev/app.py +0 -95
  33. uiautodev-0.5.0/uiautodev/common.py +0 -25
  34. uiautodev-0.5.0/uiautodev/exceptions.py +0 -32
  35. {uiautodev-0.5.0 → uiautodev-0.13.3}/LICENSE +0 -0
  36. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/__main__.py +0 -0
  37. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/appium_proxy.py +0 -0
  38. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/case.py +0 -0
  39. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/driver/appium.py +0 -0
  40. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/driver/ios.py +0 -0
  41. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/driver/mock.py +0 -0
  42. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
  43. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/driver/udt/udt.py +0 -0
  44. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/router/xml.py +0 -0
  45. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/static/demo.html +0 -0
  46. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/utils/exceptions.py +0 -0
  47. {uiautodev-0.5.0 → uiautodev-0.13.3}/uiautodev/utils/usbmux.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: uiautodev
3
- Version: 0.5.0
3
+ Version: 0.13.3
4
4
  Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
5
- Home-page: https://uiauto.dev
6
5
  License: MIT
6
+ License-File: LICENSE
7
7
  Author: codeskyblue
8
8
  Author-email: codeskyblue@gmail.com
9
9
  Requires-Python: >=3.8,<4.0
@@ -14,19 +14,24 @@ 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
- Requires-Dist: adbutils (>=2.7.0,<3.0.0)
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Dist: Pillow
20
+ Requires-Dist: adbutils (>=2.8.10,<3)
18
21
  Requires-Dist: click (>=8.1.7,<9.0.0)
19
22
  Requires-Dist: construct
20
- Requires-Dist: fastapi (>=0.111.0,<0.112.0)
23
+ Requires-Dist: fastapi (>=0.115.12,<1)
21
24
  Requires-Dist: httpx
22
25
  Requires-Dist: lxml
23
- Requires-Dist: pillow
24
- Requires-Dist: poetry (>=1.8.2,<2.0.0)
25
26
  Requires-Dist: pydantic (>=2.6,<3.0)
26
27
  Requires-Dist: pygments (>=2)
27
- Requires-Dist: uiautomator2 (>=2)
28
- Requires-Dist: uvicorn[standard]
29
- Requires-Dist: wdapy (>=0.2.2,<0.3.0)
28
+ Requires-Dist: python-multipart (>=0.0.18)
29
+ Requires-Dist: rich
30
+ Requires-Dist: uiautomator2 (>=3.2.0,<4)
31
+ Requires-Dist: uvicorn (>=0.33.0)
32
+ Requires-Dist: wdapy (>0.2.2,<1)
33
+ Requires-Dist: websockets (>=10.4)
34
+ Project-URL: Homepage, https://uiauto.dev
30
35
  Description-Content-Type: text/markdown
31
36
 
32
37
  # uiautodev
@@ -35,15 +40,21 @@ Description-Content-Type: text/markdown
35
40
 
36
41
  https://uiauto.dev
37
42
 
38
- > backup site: https://uiauto.devsleep.com
43
+ > ~~In China visit: https://uiauto.devsleep.com~~
39
44
 
40
- UI Inspector for Android and iOS, help inspector element properties, and auto generate XPath, script.
45
+ UI Inspector for Android, iOS and Harmony help inspector element properties, and auto generate XPath, script.
41
46
 
42
47
  # Install
43
48
  ```bash
44
49
  pip install uiautodev
45
50
  ```
46
51
 
52
+ To enable Harmony support, run the following command to install its dependencies:
53
+
54
+ ```sh
55
+ uiautodev install-harmony
56
+ ```
57
+
47
58
  # Usage
48
59
  ```bash
49
60
  Usage: uiauto.dev [OPTIONS] COMMAND [ARGS]...
@@ -53,12 +64,12 @@ Options:
53
64
  -h, --help Show this message and exit.
54
65
 
55
66
  Commands:
67
+ server start uiauto.dev local server [Default]
56
68
  android COMMAND: tap, tapElement, installApp, currentApp,...
57
- appium COMMAND: tap, tapElement, installApp, currentApp,...
58
69
  ios COMMAND: tap, tapElement, installApp, currentApp,...
59
70
  self-update Update uiautodev to latest version
60
- server start uiauto.dev local server [Default]
61
71
  version Print version
72
+ shutdown Shutdown server
62
73
  ```
63
74
 
64
75
  ```bash
@@ -66,20 +77,37 @@ Commands:
66
77
  uiauto.dev
67
78
  ```
68
79
 
69
- # DEVELOP
70
- ```bash
71
- # install poetry (python package manager)
72
- pip install poetry # pipx install poetry
80
+ # Environment
81
+
82
+ ```sh
83
+ # Default driver is uiautomator2
84
+ # Set the environment variable below to switch to adb driver
85
+ export UIAUTODEV_USE_ADB_DRIVER=1
86
+ ```
73
87
 
74
- # install deps
75
- poetry install
88
+ # Offline mode
76
89
 
77
- # format import
78
- make format
90
+ Start with
79
91
 
80
- # run server
81
- make dev
92
+ ```sh
93
+ uiautodev server --offline
94
+
95
+ # Specify server url (optional)
96
+ uiautodev server --offline --server-url https://uiauto.dev
82
97
  ```
83
98
 
99
+ Visit <http://localhost:20242> once, and then disconnecting from the internet will not affect usage.
100
+
101
+ > All frontend resources will be saved to cache/ dir.
102
+
103
+ # DEVELOP
104
+
105
+ see [DEVELOP.md](DEVELOP.md)
106
+
107
+ # Links
108
+ - https://app.tangoapp.dev/ 基于webadb的手机远程控制项目
109
+ - https://docs.tangoapp.dev/scrcpy/video/web-codecs/ H264解码器
110
+
84
111
  # LICENSE
85
112
  [MIT](LICENSE)
113
+
@@ -0,0 +1,76 @@
1
+ # uiautodev
2
+ [![codecov](https://codecov.io/gh/codeskyblue/appinspector/graph/badge.svg?token=aLTg4VOyQH)](https://codecov.io/gh/codeskyblue/appinspector)
3
+ [![PyPI version](https://badge.fury.io/py/uiautodev.svg)](https://badge.fury.io/py/uiautodev)
4
+
5
+ https://uiauto.dev
6
+
7
+ > ~~In China visit: https://uiauto.devsleep.com~~
8
+
9
+ UI Inspector for Android, iOS and Harmony help inspector element properties, and auto generate XPath, script.
10
+
11
+ # Install
12
+ ```bash
13
+ pip install uiautodev
14
+ ```
15
+
16
+ To enable Harmony support, run the following command to install its dependencies:
17
+
18
+ ```sh
19
+ uiautodev install-harmony
20
+ ```
21
+
22
+ # Usage
23
+ ```bash
24
+ Usage: uiauto.dev [OPTIONS] COMMAND [ARGS]...
25
+
26
+ Options:
27
+ -v, --verbose verbose mode
28
+ -h, --help Show this message and exit.
29
+
30
+ Commands:
31
+ server start uiauto.dev local server [Default]
32
+ android COMMAND: tap, tapElement, installApp, currentApp,...
33
+ ios COMMAND: tap, tapElement, installApp, currentApp,...
34
+ self-update Update uiautodev to latest version
35
+ version Print version
36
+ shutdown Shutdown server
37
+ ```
38
+
39
+ ```bash
40
+ # run local server and open browser
41
+ uiauto.dev
42
+ ```
43
+
44
+ # Environment
45
+
46
+ ```sh
47
+ # Default driver is uiautomator2
48
+ # Set the environment variable below to switch to adb driver
49
+ export UIAUTODEV_USE_ADB_DRIVER=1
50
+ ```
51
+
52
+ # Offline mode
53
+
54
+ Start with
55
+
56
+ ```sh
57
+ uiautodev server --offline
58
+
59
+ # Specify server url (optional)
60
+ uiautodev server --offline --server-url https://uiauto.dev
61
+ ```
62
+
63
+ Visit <http://localhost:20242> once, and then disconnecting from the internet will not affect usage.
64
+
65
+ > All frontend resources will be saved to cache/ dir.
66
+
67
+ # DEVELOP
68
+
69
+ see [DEVELOP.md](DEVELOP.md)
70
+
71
+ # Links
72
+ - https://app.tangoapp.dev/ 基于webadb的手机远程控制项目
73
+ - https://docs.tangoapp.dev/scrcpy/video/web-codecs/ H264解码器
74
+
75
+ # LICENSE
76
+ [MIT](LICENSE)
@@ -1,30 +1,33 @@
1
1
  [tool.poetry]
2
2
  name = "uiautodev"
3
- version = "0.5.0"
3
+ version = "0.13.3"
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>"]
7
7
  license = "MIT"
8
8
  readme = "README.md"
9
9
 
10
+ include = [
11
+ {path = "uiautodev/binaries/scrcpy.jar"}
12
+ ]
13
+
10
14
  [tool.poetry.dependencies]
11
15
  python = "^3.8"
12
- pillow = "*"
13
- adbutils = "^2.7.0"
14
- construct = "*"
15
- lxml = "*"
16
+ adbutils = ">=2.8.10,<3"
16
17
  click = "^8.1.7"
17
18
  pygments = ">=2"
18
- uiautomator2 = ">=2"
19
- httpx = "*"
20
- fastapi = "^0.111.0"
21
- uvicorn = {version = "*", extras = ["standard"]}
22
- poetry = "^1.8.2"
19
+ uiautomator2 = ">=3.2.0,<4"
20
+ fastapi = ">=0.115.12,<1"
23
21
  pydantic = "^2.6"
24
- wdapy = "^0.2.2"
25
-
26
- #[tool.poetry.extras]
27
- #appium = ["appium-python-client", "httppretty"]
22
+ wdapy = ">0.2.2,<1"
23
+ websockets = ">=10.4"
24
+ Pillow = "*"
25
+ construct = "*"
26
+ lxml = "*"
27
+ httpx = "*"
28
+ uvicorn = ">=0.33.0"
29
+ rich = "*"
30
+ python-multipart = ">=0.0.18"
28
31
 
29
32
  [tool.poetry.scripts]
30
33
  "uiauto.dev" = "uiautodev.__main__:main"
@@ -5,4 +5,4 @@
5
5
  """
6
6
 
7
7
  # version is auto managed by poetry
8
- __version__ = "0.5.0"
8
+ __version__ = "0.13.3"
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Created on Sun Feb 18 2024 13:48:55 by codeskyblue"""
5
+
6
+ import logging
7
+ import os
8
+ import platform
9
+ import signal
10
+ from pathlib import Path
11
+ from typing import Dict, List
12
+
13
+ import adbutils
14
+ import httpx
15
+ import uvicorn
16
+ from fastapi import FastAPI, File, Request, Response, UploadFile, WebSocket
17
+ from fastapi.middleware.cors import CORSMiddleware
18
+ from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
19
+ from pydantic import BaseModel
20
+ from starlette.websockets import WebSocketDisconnect
21
+
22
+ from uiautodev import __version__
23
+ from uiautodev.common import convert_bytes_to_image, get_webpage_url, ocr_image
24
+ from uiautodev.driver.android import ADBAndroidDriver, U2AndroidDriver
25
+ from uiautodev.model import Node
26
+ from uiautodev.provider import AndroidProvider, HarmonyProvider, IOSProvider, MockProvider
27
+ from uiautodev.remote.scrcpy import ScrcpyServer
28
+ from uiautodev.router.android import router as android_device_router
29
+ from uiautodev.router.device import make_router
30
+ from uiautodev.router.proxy import make_reverse_proxy
31
+ from uiautodev.router.proxy import router as proxy_router
32
+ from uiautodev.router.xml import router as xml_router
33
+ from uiautodev.utils.envutils import Environment
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ app = FastAPI()
38
+
39
+ app.add_middleware(
40
+ CORSMiddleware,
41
+ allow_origins=["*"],
42
+ allow_credentials=True,
43
+ allow_methods=["GET", "POST"],
44
+ allow_headers=["*"],
45
+ )
46
+
47
+ android_default_driver = U2AndroidDriver
48
+ if os.getenv("UIAUTODEV_USE_ADB_DRIVER") in ("1", "true", "True"):
49
+ android_default_driver = ADBAndroidDriver
50
+
51
+ android_router = make_router(AndroidProvider(driver_class=android_default_driver))
52
+ android_adb_router = make_router(AndroidProvider(driver_class=ADBAndroidDriver))
53
+ ios_router = make_router(IOSProvider())
54
+ harmony_router = make_router(HarmonyProvider())
55
+ mock_router = make_router(MockProvider())
56
+
57
+ app.include_router(mock_router, prefix="/api/mock", tags=["mock"])
58
+
59
+ if Environment.UIAUTODEV_MOCK:
60
+ app.include_router(mock_router, prefix="/api/android", tags=["mock"])
61
+ app.include_router(mock_router, prefix="/api/ios", tags=["mock"])
62
+ app.include_router(mock_router, prefix="/api/harmony", tags=["mock"])
63
+ else:
64
+ app.include_router(android_router, prefix="/api/android", tags=["android"])
65
+ app.include_router(android_adb_router, prefix="/api/android_adb", tags=["android_adb"])
66
+ app.include_router(ios_router, prefix="/api/ios", tags=["ios"])
67
+ app.include_router(harmony_router, prefix="/api/harmony", tags=["harmony"])
68
+
69
+ app.include_router(xml_router, prefix="/api/xml", tags=["xml"])
70
+ app.include_router(android_device_router, prefix="/api/android", tags=["android"])
71
+ app.include_router(proxy_router, tags=["proxy"])
72
+
73
+
74
+ @app.get("/api/{platform}/features")
75
+ def get_features(platform: str) -> Dict[str, bool]:
76
+ """Get features supported by the specified platform"""
77
+ features = {}
78
+ # 获取所有带有指定平台tag的路由
79
+ from starlette.routing import Route
80
+
81
+ for route in app.routes:
82
+ _route: Route = route # type: ignore
83
+ if hasattr(_route, "tags") and platform in _route.tags:
84
+ if _route.path.startswith(f"/api/{platform}/{{serial}}/"):
85
+ # 提取特性名称
86
+ parts = _route.path.split("/")
87
+ feature_name = parts[-1]
88
+ if not feature_name.startswith("{"):
89
+ features[feature_name] = True
90
+ return features
91
+
92
+
93
+ class InfoResponse(BaseModel):
94
+ version: str
95
+ description: str
96
+ platform: str
97
+ code_language: str
98
+ cwd: str
99
+ drivers: List[str]
100
+
101
+
102
+ @app.get("/api/info")
103
+ def info() -> InfoResponse:
104
+ """Information about the application"""
105
+ return InfoResponse(
106
+ version=__version__,
107
+ description="client for https://uiauto.dev",
108
+ platform=platform.system(), # Linux | Darwin | Windows
109
+ code_language="Python",
110
+ cwd=os.getcwd(),
111
+ drivers=["android", "ios", "harmony"],
112
+ )
113
+
114
+
115
+ @app.post("/api/ocr_image")
116
+ async def _ocr_image(file: UploadFile = File(...)) -> List[Node]:
117
+ """OCR an image"""
118
+ image_data = await file.read()
119
+ image = convert_bytes_to_image(image_data)
120
+ return ocr_image(image)
121
+
122
+
123
+ @app.get("/shutdown")
124
+ def shutdown() -> str:
125
+ """Shutdown the server"""
126
+ os.kill(os.getpid(), signal.SIGINT)
127
+ return "Server shutting down..."
128
+
129
+
130
+ @app.get("/demo")
131
+ def demo():
132
+ """Demo endpoint"""
133
+ static_dir = Path(__file__).parent / "static"
134
+ print(static_dir / "demo.html")
135
+ return FileResponse(static_dir / "demo.html")
136
+
137
+
138
+ @app.get("/redirect")
139
+ def index_redirect():
140
+ """redirect to official homepage"""
141
+ url = get_webpage_url()
142
+ logger.debug("redirect to %s", url)
143
+ return RedirectResponse(url)
144
+
145
+
146
+ @app.get("/api/auth/me")
147
+ def mock_auth_me():
148
+ # 401 {"detail":"Authentication required"}
149
+ return JSONResponse(status_code=401, content={"detail": "Authentication required"})
150
+
151
+ @app.websocket("/ws/android/scrcpy/{serial}")
152
+ async def handle_android_ws(websocket: WebSocket, serial: str):
153
+ """
154
+ Args:
155
+ serial: device serial
156
+ websocket: WebSocket
157
+ """
158
+ await websocket.accept()
159
+
160
+ try:
161
+ logger.info(f"WebSocket serial: {serial}")
162
+ device = adbutils.device(serial)
163
+ server = ScrcpyServer(device)
164
+ await server.handle_unified_websocket(websocket, serial)
165
+ except WebSocketDisconnect:
166
+ logger.info(f"WebSocket disconnected by client.")
167
+ except Exception as e:
168
+ logger.exception(f"WebSocket error for serial={serial}: {e}")
169
+ await websocket.close(code=1000, reason=str(e))
170
+ finally:
171
+ logger.info(f"WebSocket closed for serial={serial}")
172
+
173
+
174
+ def get_harmony_mjpeg_server(serial: str):
175
+ from hypium import UiDriver
176
+
177
+ from uiautodev.remote.harmony_mjpeg import HarmonyMjpegServer
178
+
179
+ driver = UiDriver.connect(device_sn=serial)
180
+ logger.info("create harmony mjpeg server for %s", serial)
181
+ logger.info(f"device wake_up_display: {driver.wake_up_display()}")
182
+ return HarmonyMjpegServer(driver)
183
+
184
+
185
+ @app.websocket("/ws/harmony/mjpeg/{serial}")
186
+ async def unified_harmony_ws(websocket: WebSocket, serial: str):
187
+ """
188
+ Args:
189
+ serial: device serial
190
+ websocket: WebSocket
191
+ """
192
+ await websocket.accept()
193
+
194
+ try:
195
+ logger.info(f"WebSocket serial: {serial}")
196
+
197
+ # 获取 HarmonyScrcpyServer 实例
198
+ server = get_harmony_mjpeg_server(serial)
199
+ server.start()
200
+ await server.handle_ws(websocket)
201
+ except ImportError as e:
202
+ logger.error(f"missing library for harmony: {e}")
203
+ await websocket.close(
204
+ code=1000, reason='missing library, fix by "pip install uiautodev[harmony]"'
205
+ )
206
+ except WebSocketDisconnect:
207
+ logger.info(f"WebSocket disconnected by client.")
208
+ except Exception as e:
209
+ logger.exception(f"WebSocket error for serial={serial}: {e}")
210
+ await websocket.close(code=1000, reason=str(e))
211
+ finally:
212
+ logger.info(f"WebSocket closed for serial={serial}")
213
+
214
+
215
+ if __name__ == "__main__":
216
+ uvicorn.run("uiautodev.app:app", port=4000, reload=True, use_colors=True)
@@ -7,6 +7,7 @@
7
7
  from __future__ import annotations
8
8
 
9
9
  import logging
10
+ import os
10
11
  import platform
11
12
  import subprocess
12
13
  import sys
@@ -18,6 +19,8 @@ import click
18
19
  import httpx
19
20
  import pydantic
20
21
  import uvicorn
22
+ from retry import retry
23
+ from rich.logging import RichHandler
21
24
 
22
25
  from uiautodev import __version__, command_proxy
23
26
  from uiautodev.command_types import Command
@@ -28,32 +31,29 @@ from uiautodev.utils.common import convert_params_to_model, print_json
28
31
  logger = logging.getLogger(__name__)
29
32
 
30
33
  CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
34
+ HARMONY_PACKAGES = [
35
+ "setuptools",
36
+ "https://public.uiauto.devsleep.com/harmony/xdevice-5.0.7.200.tar.gz",
37
+ "https://public.uiauto.devsleep.com/harmony/xdevice-devicetest-5.0.7.200.tar.gz",
38
+ "https://public.uiauto.devsleep.com/harmony/xdevice-ohos-5.0.7.200.tar.gz",
39
+ "https://public.uiauto.devsleep.com/harmony/hypium-5.0.7.200.tar.gz",
40
+ ]
41
+
42
+
43
+ def enable_logger_to_console(level):
44
+ _logger = logging.getLogger("uiautodev")
45
+ _logger.setLevel(level)
46
+ _logger.addHandler(RichHandler(enable_link_path=False))
47
+
31
48
 
32
49
  @click.group(context_settings=CONTEXT_SETTINGS)
33
50
  @click.option("--verbose", "-v", is_flag=True, default=False, help="verbose mode")
34
51
  def cli(verbose: bool):
35
52
  if verbose:
36
- # try to enable logger is not very easy
37
- # you have to setup logHandler(logFormatter) for the root logger
38
- # and set all children logger to DEBUG
39
- # that's why it is not easy to use it with logging
40
- root_logger = logging.getLogger(__name__.split(".")[0])
41
- root_logger.setLevel(logging.DEBUG)
42
-
43
- console_handler = logging.StreamHandler()
44
- console_handler.setLevel(logging.DEBUG)
45
-
46
- formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
47
- console_handler.setFormatter(formatter)
48
-
49
- root_logger.addHandler(console_handler)
50
-
51
- # set all children logger to DEBUG
52
- for k in root_logger.manager.loggerDict.keys():
53
- if k.startswith(root_logger.name+"."):
54
- logging.getLogger(k).setLevel(logging.DEBUG)
55
-
53
+ enable_logger_to_console(level=logging.DEBUG)
56
54
  logger.debug("Verbose mode enabled")
55
+ else:
56
+ enable_logger_to_console(level=logging.INFO)
57
57
 
58
58
 
59
59
  def run_driver_command(provider: BaseProvider, command: Command, params: list[str] = None):
@@ -113,7 +113,7 @@ def case():
113
113
  def appium(command: Command, params: list[str] = None):
114
114
  from uiautodev.driver.appium import AppiumProvider
115
115
  from uiautodev.exceptions import AppiumDriverException
116
-
116
+
117
117
  provider = AppiumProvider()
118
118
  try:
119
119
  run_driver_command(provider, command, params)
@@ -133,14 +133,29 @@ def self_update():
133
133
  subprocess.run([sys.executable, '-m', "pip", "install", "--upgrade", "uiautodev"])
134
134
 
135
135
 
136
+ @cli.command('install-harmony')
137
+ def install_harmony():
138
+ for lib_url in HARMONY_PACKAGES:
139
+ click.echo(f"Installing {lib_url} ...")
140
+ pip_install(lib_url)
141
+
142
+ @retry(tries=2, delay=3, backoff=2)
143
+ def pip_install(package: str):
144
+ """Install a package using pip."""
145
+ subprocess.run([sys.executable, '-m', "pip", "install", package], check=True)
146
+ click.echo(f"Successfully installed {package}")
147
+
148
+
136
149
  @cli.command(help="start uiauto.dev local server [Default]")
137
150
  @click.option("--port", default=20242, help="port number", show_default=True)
138
151
  @click.option("--host", default="127.0.0.1", help="host", show_default=True)
139
152
  @click.option("--reload", is_flag=True, default=False, help="auto reload, dev only")
140
- @click.option("-f", "--force", is_flag=True, default=False, help="shutdown alrealy runningserver")
153
+ @click.option("-f", "--force", is_flag=True, default=False, help="shutdown already running server")
141
154
  @click.option("-s", "--no-browser", is_flag=True, default=False, help="silent mode, do not open browser")
142
- def server(port: int, host: str, reload: bool, force: bool, no_browser: bool):
143
- print("uiautodev version:", __version__)
155
+ @click.option("--offline", is_flag=True, default=False, help="offline mode, do not use internet")
156
+ @click.option("--server-url", default="https://uiauto.dev", help="uiauto.dev server url", show_default=True)
157
+ def server(port: int, host: str, reload: bool, force: bool, no_browser: bool, offline: bool, server_url: str):
158
+ click.echo(f"uiautodev version: {__version__}")
144
159
  if force:
145
160
  try:
146
161
  httpx.get(f"http://{host}:{port}/shutdown", timeout=3)
@@ -150,32 +165,45 @@ def server(port: int, host: str, reload: bool, force: bool, no_browser: bool):
150
165
  use_color = True
151
166
  if platform.system() == 'Windows':
152
167
  use_color = False
153
-
168
+
169
+ server_url = server_url.rstrip('/')
170
+ from uiautodev.router import proxy
171
+ proxy.base_url = server_url
172
+
173
+ if offline:
174
+ proxy.cache_dir.mkdir(parents=True, exist_ok=True)
175
+ logger.info("offline mode enabled, cache dir: %s, server url: %s", proxy.cache_dir, proxy.base_url)
176
+
154
177
  if not no_browser:
155
- th = threading.Thread(target=open_browser_when_server_start, args=(f"http://{host}:{port}",))
178
+ th = threading.Thread(target=open_browser_when_server_start, args=(f"http://{host}:{port}", offline))
156
179
  th.daemon = True
157
180
  th.start()
158
181
  uvicorn.run("uiautodev.app:app", host=host, port=port, reload=reload, use_colors=use_color)
159
182
 
183
+ @cli.command(help="shutdown uiauto.dev local server")
184
+ @click.option("--port", default=20242, help="port number", show_default=True)
185
+ def shutdown(port: int):
186
+ try:
187
+ httpx.get(f"http://127.0.0.1:{port}/shutdown", timeout=3)
188
+ except httpx.HTTPError:
189
+ pass
190
+
160
191
 
161
- def open_browser_when_server_start(server_url: str):
192
+ def open_browser_when_server_start(local_server_url: str, offline: bool = False):
162
193
  deadline = time.time() + 10
163
194
  while time.time() < deadline:
164
195
  try:
165
- httpx.get(f"{server_url}/api/info", timeout=1)
196
+ httpx.get(f"{local_server_url}/api/info", timeout=1)
166
197
  break
167
198
  except Exception as e:
168
199
  time.sleep(0.5)
169
200
  import webbrowser
170
- web_url = get_webpage_url()
201
+ web_url = get_webpage_url(local_server_url if offline else None)
171
202
  logger.info("open browser: %s", web_url)
172
203
  webbrowser.open(web_url)
173
204
 
174
- def main():
175
- # set logger level to INFO
176
- # logging.basicConfig(level=logging.INFO)
177
- logger.setLevel(logging.INFO)
178
205
 
206
+ def main():
179
207
  has_command = False
180
208
  for name in sys.argv[1:]:
181
209
  if not name.startswith("-"):
@@ -13,11 +13,11 @@ 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, \
17
- WindowSizeResponse
16
+ DumpResponse, FindElementRequest, FindElementResponse, InstallAppRequest, InstallAppResponse, SendKeysRequest, \
17
+ TapRequest, WindowSizeResponse
18
18
  from uiautodev.driver.base_driver import BaseDriver
19
19
  from uiautodev.exceptions import ElementNotFoundError
20
- from uiautodev.model import Node, AppInfo
20
+ from uiautodev.model import AppInfo, Node
21
21
  from uiautodev.utils.common import node_travel
22
22
 
23
23
  COMMANDS: Dict[Command, Callable] = {}
@@ -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]
@@ -61,8 +61,6 @@ def send_command(driver: BaseDriver, command: Union[str, Command], params=None):
61
61
  @register(Command.TAP)
62
62
  def tap(driver: BaseDriver, params: TapRequest):
63
63
  """Tap on the screen
64
- :param x: x coordinate
65
- :param y: y coordinate
66
64
  """
67
65
  x = params.x
68
66
  y = params.y
@@ -144,6 +142,14 @@ def dump(driver: BaseDriver) -> DumpResponse:
144
142
  def wake_up(driver: BaseDriver):
145
143
  driver.wake_up()
146
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
+
147
153
 
148
154
  def node_match(node: Node, by: By, value: str) -> bool:
149
155
  if by == By.ID: