uiautodev 0.3.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.
Potentially problematic release.
This version of uiautodev might be problematic. Click here for more details.
- uiautodev-0.3.3/LICENSE +21 -0
- uiautodev-0.3.3/PKG-INFO +56 -0
- uiautodev-0.3.3/README.md +26 -0
- uiautodev-0.3.3/pyproject.toml +41 -0
- uiautodev-0.3.3/uiautodev/__init__.py +12 -0
- uiautodev-0.3.3/uiautodev/__main__.py +10 -0
- uiautodev-0.3.3/uiautodev/app.py +92 -0
- uiautodev-0.3.3/uiautodev/appium_proxy.py +53 -0
- uiautodev-0.3.3/uiautodev/case.py +137 -0
- uiautodev-0.3.3/uiautodev/cli.py +171 -0
- uiautodev-0.3.3/uiautodev/command_proxy.py +154 -0
- uiautodev-0.3.3/uiautodev/command_types.py +89 -0
- uiautodev-0.3.3/uiautodev/driver/android.py +228 -0
- uiautodev-0.3.3/uiautodev/driver/appium.py +136 -0
- uiautodev-0.3.3/uiautodev/driver/base_driver.py +76 -0
- uiautodev-0.3.3/uiautodev/driver/ios.py +114 -0
- uiautodev-0.3.3/uiautodev/driver/mock.py +74 -0
- uiautodev-0.3.3/uiautodev/driver/udt/appium-uiautomator2-v5.12.4-light.apk +0 -0
- uiautodev-0.3.3/uiautodev/driver/udt/udt.py +259 -0
- uiautodev-0.3.3/uiautodev/exceptions.py +32 -0
- uiautodev-0.3.3/uiautodev/model.py +37 -0
- uiautodev-0.3.3/uiautodev/provider.py +76 -0
- uiautodev-0.3.3/uiautodev/router/device.py +104 -0
- uiautodev-0.3.3/uiautodev/router/xml.py +28 -0
- uiautodev-0.3.3/uiautodev/static/demo.html +34 -0
- uiautodev-0.3.3/uiautodev/utils/common.py +166 -0
- uiautodev-0.3.3/uiautodev/utils/exceptions.py +43 -0
- uiautodev-0.3.3/uiautodev/utils/usbmux.py +485 -0
uiautodev-0.3.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 codeskyblue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
uiautodev-0.3.3/PKG-INFO
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: uiautodev
|
|
3
|
+
Version: 0.3.3
|
|
4
|
+
Summary: Mobile UI Automation, include UI hierarchy inspector, script recorder
|
|
5
|
+
Home-page: https://uiauto.dev
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: codeskyblue
|
|
8
|
+
Author-email: codeskyblue@gmail.com
|
|
9
|
+
Requires-Python: >=3.8,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Provides-Extra: appium
|
|
18
|
+
Requires-Dist: adbutils (>=2.6.0,<3.0.0)
|
|
19
|
+
Requires-Dist: appium-python-client (>=4.0.0,<5.0.0) ; extra == "appium"
|
|
20
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
21
|
+
Requires-Dist: construct
|
|
22
|
+
Requires-Dist: fastapi[all] (>=0.109.2,<0.110.0)
|
|
23
|
+
Requires-Dist: httpretty (>=1.1.4,<2.0.0)
|
|
24
|
+
Requires-Dist: lxml
|
|
25
|
+
Requires-Dist: pillow
|
|
26
|
+
Requires-Dist: pygments (>=2)
|
|
27
|
+
Requires-Dist: uiautomator2 (>=3.0.11,<4.0.0)
|
|
28
|
+
Requires-Dist: uvicorn[standard] (>=0.27.1,<0.28.0)
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# uiautodev
|
|
32
|
+
[](https://codecov.io/gh/codeskyblue/appinspector)
|
|
33
|
+
[](https://badge.fury.io/py/uiautodev)
|
|
34
|
+
|
|
35
|
+
https://uiauto.dev
|
|
36
|
+
|
|
37
|
+
UI Inspector for Android and iOS, help inspector element properties, and auto generate XPath, script.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# DEVELOP
|
|
41
|
+
```bash
|
|
42
|
+
# install poetry (python package manager)
|
|
43
|
+
pip install poetry # pipx install poetry
|
|
44
|
+
|
|
45
|
+
# install deps
|
|
46
|
+
poetry install
|
|
47
|
+
|
|
48
|
+
# format import
|
|
49
|
+
make format
|
|
50
|
+
|
|
51
|
+
# run server
|
|
52
|
+
make dev
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
# LICENSE
|
|
56
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# uiautodev
|
|
2
|
+
[](https://codecov.io/gh/codeskyblue/appinspector)
|
|
3
|
+
[](https://badge.fury.io/py/uiautodev)
|
|
4
|
+
|
|
5
|
+
https://uiauto.dev
|
|
6
|
+
|
|
7
|
+
UI Inspector for Android and iOS, help inspector element properties, and auto generate XPath, script.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# DEVELOP
|
|
11
|
+
```bash
|
|
12
|
+
# install poetry (python package manager)
|
|
13
|
+
pip install poetry # pipx install poetry
|
|
14
|
+
|
|
15
|
+
# install deps
|
|
16
|
+
poetry install
|
|
17
|
+
|
|
18
|
+
# format import
|
|
19
|
+
make format
|
|
20
|
+
|
|
21
|
+
# run server
|
|
22
|
+
make dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
# LICENSE
|
|
26
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "uiautodev"
|
|
3
|
+
version = "0.3.3"
|
|
4
|
+
description = "Mobile UI Automation, include UI hierarchy inspector, script recorder"
|
|
5
|
+
homepage = "https://uiauto.dev"
|
|
6
|
+
authors = ["codeskyblue <codeskyblue@gmail.com>"]
|
|
7
|
+
license = "MIT"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.8"
|
|
12
|
+
fastapi = {extras = ["all"], version = "^0.109.2"}
|
|
13
|
+
uvicorn = {extras = ["standard"], version = "^0.27.1"}
|
|
14
|
+
pillow = "*"
|
|
15
|
+
adbutils = "^2.6.0"
|
|
16
|
+
construct = "*"
|
|
17
|
+
lxml = "*"
|
|
18
|
+
click = "^8.1.7"
|
|
19
|
+
pygments = ">=2"
|
|
20
|
+
httpretty = {version = "^1.1.4", optional = true}
|
|
21
|
+
appium-python-client = {version = "^4.0.0", optional = true}
|
|
22
|
+
uiautomator2 = "^3.0.11"
|
|
23
|
+
|
|
24
|
+
[tool.poetry.extras]
|
|
25
|
+
appium = ["appium-python-client", "httppretty"]
|
|
26
|
+
|
|
27
|
+
[tool.poetry.scripts]
|
|
28
|
+
"uiauto.dev" = "uiautodev.__main__:main"
|
|
29
|
+
"uiautodev" = "uiautodev.__main__:main"
|
|
30
|
+
|
|
31
|
+
[tool.poetry.group.dev.dependencies]
|
|
32
|
+
pytest = "^8.0.1"
|
|
33
|
+
isort = "^5.13.2"
|
|
34
|
+
pytest-cov = "^4.1.0"
|
|
35
|
+
|
|
36
|
+
[tool.poetry-dynamic-versioning] # 根据tag来动态配置版本号
|
|
37
|
+
enable = false
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
|
|
41
|
+
build-backend = "poetry_dynamic_versioning.backend"
|
|
@@ -0,0 +1,12 @@
|
|
|
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"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""Created on Sun Feb 18 2024 13:48:55 by codeskyblue
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import signal
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
from fastapi import FastAPI
|
|
15
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
16
|
+
from fastapi.responses import FileResponse, RedirectResponse
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
from uiautodev import __version__
|
|
20
|
+
from uiautodev.provider import AndroidProvider, IOSProvider, MockProvider
|
|
21
|
+
from uiautodev.router.device import make_router
|
|
22
|
+
from uiautodev.router.xml import router as xml_router
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
app = FastAPI()
|
|
27
|
+
|
|
28
|
+
app.add_middleware(
|
|
29
|
+
CORSMiddleware,
|
|
30
|
+
allow_origins=["*"],
|
|
31
|
+
allow_credentials=True,
|
|
32
|
+
allow_methods=["GET", "POST"],
|
|
33
|
+
allow_headers=["*"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
android_router = make_router(AndroidProvider())
|
|
37
|
+
ios_router = make_router(IOSProvider())
|
|
38
|
+
mock_router = make_router(MockProvider())
|
|
39
|
+
|
|
40
|
+
app.include_router(mock_router, prefix="/api/mock", tags=["mock"])
|
|
41
|
+
|
|
42
|
+
if os.environ.get("UIAUTODEV_MOCK"):
|
|
43
|
+
app.include_router(mock_router, prefix="/api/android", tags=["mock"])
|
|
44
|
+
app.include_router(mock_router, prefix="/api/ios", tags=["mock"])
|
|
45
|
+
else:
|
|
46
|
+
app.include_router(android_router, prefix="/api/android", tags=["android"])
|
|
47
|
+
app.include_router(ios_router, prefix="/api/ios", tags=["ios"])
|
|
48
|
+
|
|
49
|
+
app.include_router(xml_router, prefix="/api/xml", tags=["xml"])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InfoResponse(BaseModel):
|
|
53
|
+
version: str
|
|
54
|
+
description: str
|
|
55
|
+
platform: str
|
|
56
|
+
code_language: str
|
|
57
|
+
cwd: str
|
|
58
|
+
drivers: List[str]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.get("/api/info")
|
|
62
|
+
def info() -> InfoResponse:
|
|
63
|
+
"""Information about the application"""
|
|
64
|
+
return InfoResponse(
|
|
65
|
+
version=__version__,
|
|
66
|
+
description="client for https://uiauto.dev",
|
|
67
|
+
platform=platform.system(), # Linux | Darwin | Windows
|
|
68
|
+
code_language="Python",
|
|
69
|
+
cwd=os.getcwd(),
|
|
70
|
+
drivers=["android"],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.get("/shutdown")
|
|
75
|
+
def shutdown() -> str:
|
|
76
|
+
"""Shutdown the server"""
|
|
77
|
+
os.kill(os.getpid(), signal.SIGINT)
|
|
78
|
+
return "Server shutting down..."
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@app.get("/demo")
|
|
82
|
+
def demo() -> str:
|
|
83
|
+
"""Demo endpoint"""
|
|
84
|
+
static_dir = Path(__file__).parent / "static"
|
|
85
|
+
print(static_dir / "demo.html")
|
|
86
|
+
return FileResponse(static_dir / "demo.html")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.get("/")
|
|
90
|
+
def index_redirect():
|
|
91
|
+
""" redirect to official homepage """
|
|
92
|
+
return RedirectResponse("https://uiauto.dev")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""Created on Tue Mar 19 2024 22:23:37 by codeskyblue
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from fastapi import FastAPI, Request, Response
|
|
11
|
+
|
|
12
|
+
app = FastAPI()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Retrieve the target URL from the command line arguments
|
|
16
|
+
try:
|
|
17
|
+
TARGET_URL = sys.argv[1]
|
|
18
|
+
except IndexError:
|
|
19
|
+
print("Usage: python proxy_server.py <target_url>")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
|
|
23
|
+
async def proxy(request: Request, path: str):
|
|
24
|
+
# Construct the full URL to forward the request to
|
|
25
|
+
if path.endswith('/execute/sync'):
|
|
26
|
+
# 旧版appium处理不好这个请求,直接返回404, unknown command
|
|
27
|
+
# 目前browserstack也不支持这个请求
|
|
28
|
+
return Response(content=b'{"value": {"error": "unknown command", "message": "unknown command", "stacktrace": "UnknownCommandError"}}', status_code=404)
|
|
29
|
+
full_url = f"{TARGET_URL}/{path}"
|
|
30
|
+
body = await request.body()
|
|
31
|
+
print("Forwarding to", request.method, full_url)
|
|
32
|
+
print("==> BODY <==")
|
|
33
|
+
print(body)
|
|
34
|
+
# Include original headers in the request
|
|
35
|
+
headers = {k: v for k, v in request.headers.items() if k != 'host'}
|
|
36
|
+
|
|
37
|
+
# Forward the request to the target server
|
|
38
|
+
async with httpx.AsyncClient(timeout=120) as client:
|
|
39
|
+
resp = await client.request(
|
|
40
|
+
method=request.method,
|
|
41
|
+
url=full_url,
|
|
42
|
+
headers=headers,
|
|
43
|
+
data=body,
|
|
44
|
+
follow_redirects=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Return the response received from the target server
|
|
48
|
+
return Response(content=resp.content, status_code=resp.status_code, headers=dict(resp.headers))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
import uvicorn
|
|
53
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""Created on Sat Apr 13 2024 22:35:03 by codeskyblue
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import enum
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, Union
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from uiautodev import command_proxy
|
|
14
|
+
from uiautodev.command_types import Command
|
|
15
|
+
from uiautodev.driver.base_driver import BaseDriver
|
|
16
|
+
from uiautodev.provider import AndroidProvider
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
class CommandStep(BaseModel):
|
|
21
|
+
method: Union[str, Command]
|
|
22
|
+
params: Dict[str, str]
|
|
23
|
+
skip: bool = False
|
|
24
|
+
ignore_error: bool = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CompareEnum(str, enum.Enum):
|
|
28
|
+
EQUAL = "equal"
|
|
29
|
+
CONTAINS = "contains"
|
|
30
|
+
NOT_EQUAL = "not_equal"
|
|
31
|
+
NOT_CONTAINS = "not_contains"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CompareCheckStep(BaseModel):
|
|
35
|
+
method: CompareEnum
|
|
36
|
+
value_a: str
|
|
37
|
+
value_b: str
|
|
38
|
+
skip: bool = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_driver_command(driver: BaseDriver, command: Command, params: dict):
|
|
42
|
+
model = command_proxy.get_command_params_type(command)
|
|
43
|
+
params_obj = model.model_validate(params) if params else None
|
|
44
|
+
# print("Params:", params, params_obj)
|
|
45
|
+
result = command_proxy.send_command(driver, command, params_obj)
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def run():
|
|
49
|
+
# all params key and value should be string
|
|
50
|
+
# Step中
|
|
51
|
+
# 入参类型在为前端保存一份,后端需要同步兼容
|
|
52
|
+
# params所有的key和value都是string类型
|
|
53
|
+
# 出参类型支持重命名 result
|
|
54
|
+
# - key: string, old_key: string, desc: string
|
|
55
|
+
# eg. "WIDTH", "width", "屏幕宽度"
|
|
56
|
+
steps = [
|
|
57
|
+
CommandStep(
|
|
58
|
+
method=Command.APP_LAUNCH,
|
|
59
|
+
params= {
|
|
60
|
+
"package": "com.saucelabs.mydemoapp.android",
|
|
61
|
+
"stop": "true" # bool
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
CommandStep(
|
|
65
|
+
method=Command.GET_WINDOW_SIZE,
|
|
66
|
+
result_trans=[
|
|
67
|
+
dict(key="WIDTH", result_key="width", desc="屏幕宽度"),
|
|
68
|
+
]
|
|
69
|
+
),
|
|
70
|
+
CommandStep(
|
|
71
|
+
method=Command.ECHO,
|
|
72
|
+
params={
|
|
73
|
+
"message": "WindowWidth is {{WIDTH}}",
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
CommandStep(
|
|
77
|
+
method=Command.CLICK_ELEMENT,
|
|
78
|
+
params={
|
|
79
|
+
"by": "id",
|
|
80
|
+
"value": "com.saucelabs.mydemoapp.android:id/productIV",
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
CommandStep(
|
|
84
|
+
method=Command.CLICK_ELEMENT,
|
|
85
|
+
params={
|
|
86
|
+
"by": "id",
|
|
87
|
+
"value": "com.saucelabs.mydemoapp.android:id/plusIV",
|
|
88
|
+
}
|
|
89
|
+
),
|
|
90
|
+
CommandStep(
|
|
91
|
+
method=Command.CLICK_ELEMENT,
|
|
92
|
+
params={
|
|
93
|
+
"by": "id",
|
|
94
|
+
"value": "com.saucelabs.mydemoapp.android:id/cartBt",
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
CommandStep(
|
|
98
|
+
method=Command.CLICK_ELEMENT,
|
|
99
|
+
params={
|
|
100
|
+
"by": "id",
|
|
101
|
+
"value": "com.saucelabs.mydemoapp.android:id/cartIV",
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
CommandStep(
|
|
105
|
+
method=Command.FIND_ELEMENT,
|
|
106
|
+
params={
|
|
107
|
+
"by": "text",
|
|
108
|
+
"value": "Proceed To Checkout",
|
|
109
|
+
},
|
|
110
|
+
skip=True,
|
|
111
|
+
),
|
|
112
|
+
CompareCheckStep(
|
|
113
|
+
method=CompareEnum.EQUAL,
|
|
114
|
+
value_a="$.name",
|
|
115
|
+
value_b="com.saucelabs.mydemoapp.android:id/cartIV",
|
|
116
|
+
),
|
|
117
|
+
CommandStep(
|
|
118
|
+
method=Command.CLICK_ELEMENT,
|
|
119
|
+
params={
|
|
120
|
+
"by": "text",
|
|
121
|
+
"value": "Proceed To Checkout",
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
]
|
|
125
|
+
provider = AndroidProvider()
|
|
126
|
+
driver = provider.get_single_device_driver()
|
|
127
|
+
local_vars: Dict[str, str] = {}
|
|
128
|
+
for step in steps:
|
|
129
|
+
if not isinstance(step, CommandStep):
|
|
130
|
+
continue
|
|
131
|
+
command = Command(step.method)
|
|
132
|
+
params = step.params
|
|
133
|
+
print(step.method, params)
|
|
134
|
+
if step.skip:
|
|
135
|
+
logger.debug("Skip step: %s", step.method)
|
|
136
|
+
continue
|
|
137
|
+
run_driver_command(driver, command, params)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""Created on Tue Mar 19 2024 10:53:03 by codeskyblue
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import platform
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
from pprint import pprint
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
import httpx
|
|
18
|
+
import pydantic
|
|
19
|
+
import uvicorn
|
|
20
|
+
|
|
21
|
+
from uiautodev import __version__, command_proxy
|
|
22
|
+
from uiautodev.command_types import Command
|
|
23
|
+
from uiautodev.provider import AndroidProvider, BaseProvider, IOSProvider
|
|
24
|
+
from uiautodev.utils.common import convert_params_to_model, print_json
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.group()
|
|
30
|
+
@click.option("--verbose", "-v", is_flag=True, default=False, help="verbose mode")
|
|
31
|
+
def cli(verbose: bool):
|
|
32
|
+
if verbose:
|
|
33
|
+
root_logger = logging.getLogger(__name__.split(".")[0])
|
|
34
|
+
root_logger.setLevel(logging.DEBUG)
|
|
35
|
+
|
|
36
|
+
console_handler = logging.StreamHandler()
|
|
37
|
+
console_handler.setLevel(logging.DEBUG)
|
|
38
|
+
|
|
39
|
+
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
|
|
40
|
+
console_handler.setFormatter(formatter)
|
|
41
|
+
|
|
42
|
+
root_logger.addHandler(console_handler)
|
|
43
|
+
logger.debug("Verbose mode enabled")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_driver_command(provider: BaseProvider, command: Command, params: list[str] = None):
|
|
47
|
+
if command == Command.LIST:
|
|
48
|
+
devices = provider.list_devices()
|
|
49
|
+
print("==> Devices <==")
|
|
50
|
+
pprint(devices)
|
|
51
|
+
return
|
|
52
|
+
driver = provider.get_single_device_driver()
|
|
53
|
+
params_obj = None
|
|
54
|
+
model = command_proxy.get_command_params_type(command)
|
|
55
|
+
if model:
|
|
56
|
+
if not params:
|
|
57
|
+
print(f"params is required for {command}")
|
|
58
|
+
pprint(model.model_json_schema())
|
|
59
|
+
return
|
|
60
|
+
params_obj = convert_params_to_model(params, model)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
print("Command:", command.value)
|
|
64
|
+
print("Params ↓")
|
|
65
|
+
print_json(params_obj)
|
|
66
|
+
result = command_proxy.send_command(driver, command, params_obj)
|
|
67
|
+
print("Result ↓")
|
|
68
|
+
print_json(result)
|
|
69
|
+
except pydantic.ValidationError as e:
|
|
70
|
+
print(f"params error: {e}")
|
|
71
|
+
print(f"\n--- params should be match schema ---")
|
|
72
|
+
pprint(model.model_json_schema()["properties"])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@cli.command(help="COMMAND: " + ", ".join(c.value for c in Command))
|
|
76
|
+
@click.argument("command", type=Command, required=True)
|
|
77
|
+
@click.argument("params", required=False, nargs=-1)
|
|
78
|
+
def android(command: Command, params: list[str] = None):
|
|
79
|
+
provider = AndroidProvider()
|
|
80
|
+
run_driver_command(provider, command, params)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@cli.command(help="COMMAND: " + ", ".join(c.value for c in Command))
|
|
84
|
+
@click.argument("command", type=Command, required=True)
|
|
85
|
+
@click.argument("params", required=False, nargs=-1)
|
|
86
|
+
def ios(command: Command, params: list[str] = None):
|
|
87
|
+
provider = IOSProvider()
|
|
88
|
+
run_driver_command(provider, command, params)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@cli.command(help="run case (beta)")
|
|
92
|
+
def case():
|
|
93
|
+
from uiautodev.case import run
|
|
94
|
+
run()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@cli.command(help="COMMAND: " + ", ".join(c.value for c in Command))
|
|
98
|
+
@click.argument("command", type=Command, required=True)
|
|
99
|
+
@click.argument("params", required=False, nargs=-1)
|
|
100
|
+
def appium(command: Command, params: list[str] = None):
|
|
101
|
+
from uiautodev.driver.appium import AppiumProvider
|
|
102
|
+
from uiautodev.exceptions import AppiumDriverException
|
|
103
|
+
|
|
104
|
+
provider = AppiumProvider()
|
|
105
|
+
try:
|
|
106
|
+
run_driver_command(provider, command, params)
|
|
107
|
+
except AppiumDriverException as e:
|
|
108
|
+
print(f"Error: {e}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@cli.command('version')
|
|
112
|
+
def print_version():
|
|
113
|
+
print(__version__)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@cli.command(help="start uiauto.dev local server [default]")
|
|
117
|
+
@click.option("--port", default=20242, help="port number", show_default=True)
|
|
118
|
+
@click.option("--host", default="127.0.0.1", help="host", show_default=True)
|
|
119
|
+
@click.option("--reload", is_flag=True, default=False, help="auto reload, dev only")
|
|
120
|
+
@click.option("-f", "--force", is_flag=True, default=False, help="shutdown alrealy runningserver")
|
|
121
|
+
@click.option("--no-browser", is_flag=True, default=False, help="do not open browser")
|
|
122
|
+
def server(port: int, host: str, reload: bool, force: bool, no_browser: bool):
|
|
123
|
+
logger.info("version: %s", __version__)
|
|
124
|
+
if force:
|
|
125
|
+
try:
|
|
126
|
+
httpx.get(f"http://{host}:{port}/shutdown", timeout=3)
|
|
127
|
+
except httpx.HTTPError:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
use_color = True
|
|
131
|
+
if platform.system() == 'Windows':
|
|
132
|
+
use_color = False
|
|
133
|
+
|
|
134
|
+
if not no_browser:
|
|
135
|
+
th = threading.Thread(target=open_browser_when_server_start, args=(f"http://{host}:{port}",))
|
|
136
|
+
th.daemon = True
|
|
137
|
+
th.start()
|
|
138
|
+
uvicorn.run("uiautodev.app:app", host=host, port=port, reload=reload, use_colors=use_color)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def open_browser_when_server_start(server_url: str):
|
|
142
|
+
deadline = time.time() + 10
|
|
143
|
+
while time.time() < deadline:
|
|
144
|
+
try:
|
|
145
|
+
httpx.get(f"{server_url}/api/info", timeout=1)
|
|
146
|
+
break
|
|
147
|
+
except Exception as e:
|
|
148
|
+
time.sleep(0.5)
|
|
149
|
+
import webbrowser
|
|
150
|
+
web_url = "https://uiauto.dev"
|
|
151
|
+
logger.info("open browser: %s", web_url)
|
|
152
|
+
webbrowser.open(web_url)
|
|
153
|
+
|
|
154
|
+
def main():
|
|
155
|
+
# set logger level to INFO
|
|
156
|
+
# logging.basicConfig(level=logging.INFO)
|
|
157
|
+
logger.setLevel(logging.INFO)
|
|
158
|
+
|
|
159
|
+
has_command = False
|
|
160
|
+
for name in sys.argv[1:]:
|
|
161
|
+
if not name.startswith("-"):
|
|
162
|
+
has_command = True
|
|
163
|
+
|
|
164
|
+
if not has_command:
|
|
165
|
+
cli.main(args=sys.argv[1:] + ["server"], prog_name="uiauto.dev")
|
|
166
|
+
else:
|
|
167
|
+
cli()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
main()
|