Skip to content

Commit d94fa0b

Browse files
committed
feat: little change
1 parent 84708cd commit d94fa0b

File tree

7 files changed

+134
-64
lines changed

7 files changed

+134
-64
lines changed

uiviewer/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
11
# -*- coding: utf-8 -*-
2+
3+
import logging
4+
5+
formatter = logging.Formatter('[%(asctime)s] %(filename)15s[line:%(lineno)4d] \
6+
[%(levelname)s] %(message)s',
7+
datefmt='%Y-%m-%d %H:%M:%S')
8+
9+
logger = logging.getLogger('hmdriver2')
10+
logger.setLevel(logging.DEBUG)
11+
12+
console_handler = logging.StreamHandler()
13+
console_handler.setLevel(logging.DEBUG)
14+
console_handler.setFormatter(formatter)
15+
16+
logger.addHandler(console_handler)
17+
18+
19+
__all__ = ['logger']

uiviewer/_device.py

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# -*- coding: utf-8 -*-
22

33
import abc
4+
import traceback
45
import tempfile
56
from typing import List, Dict, Union, Tuple
67
from functools import cached_property # python3.8+
78

89
from PIL import Image
10+
from requests import request
911
import tidevice
1012
import adbutils
1113
import wda
@@ -27,10 +29,8 @@ def list_serials(platform: str) -> List[str]:
2729
elif platform == Platform.IOS:
2830
raw = tidevice.Usbmux().device_list()
2931
devices = [d.udid for d in raw]
30-
elif platform == Platform.HARMONY:
31-
devices = hdc.list_devices()
3232
else:
33-
raise HTTPException(status_code=200, detail="Unsupported platform")
33+
devices = hdc.list_devices()
3434

3535
return devices
3636

@@ -51,7 +51,7 @@ def __init__(self, serial: str):
5151
self.client = Driver(serial)
5252

5353
@cached_property
54-
def display_size(self) -> Tuple:
54+
def _display_size(self) -> Tuple:
5555
return self.client.display_size
5656

5757
def take_screenshot(self) -> str:
@@ -68,18 +68,19 @@ def dump_hierarchy(self) -> BaseHierarchy:
6868
jsonHierarchy=hierarchy,
6969
activityName=pageName,
7070
packageName=packageName,
71-
windowSize=self.display_size,
71+
windowSize=self._display_size,
7272
scale=1
7373
)
7474

7575

7676
class AndroidDevice(DeviceMeta):
7777
def __init__(self, serial: str):
7878
self.serial = serial
79+
adbutils.AdbClient()
7980
self.d: u2.Device = u2.connect(serial)
8081

8182
@cached_property
82-
def window_size(self) -> Tuple:
83+
def _window_size(self) -> Tuple:
8384
return self.d.window_size()
8485

8586
def take_screenshot(self) -> str:
@@ -94,58 +95,79 @@ def dump_hierarchy(self) -> BaseHierarchy:
9495
jsonHierarchy=page_json,
9596
activityName=current['activity'],
9697
packageName=current['package'],
97-
windowSize=self.window_size,
98+
windowSize=self._window_size,
9899
scale=1
99100
)
100101

101102

102103
class IosDevice(DeviceMeta):
103-
def __init__(self, udid: str, wda_url: str) -> None:
104+
def __init__(self, udid: str, wda_url: str, max_depth: int) -> None:
104105
self.udid = udid
106+
self.wda_url = wda_url
107+
self.max_depth = max_depth
105108
self.client = wda.Client(wda_url)
106109

107110
@cached_property
108111
def scale(self) -> int:
109112
return self.client.scale
110113

111114
@cached_property
112-
def window_size(self) -> Tuple:
115+
def _window_size(self) -> Tuple:
113116
return self.client.window_size()
114117

115118
def take_screenshot(self) -> str:
116119
img: Image.Image = self.client.screenshot()
117120
return image_to_base64(img)
118121

122+
def _current_bundle_id(self) -> str:
123+
resp = request("GET", f"{self.wda_url}/wda/activeAppInfo", timeout=10).json()
124+
bundleId = resp.get("value", {}).get("bundleId", None)
125+
return bundleId
126+
119127
def dump_hierarchy(self) -> BaseHierarchy:
128+
self.client.appium_settings({"snapshotMaxDepth": self.max_depth})
120129
data: Dict = self.client.source(format="json")
121130
hierarchy: Dict = ios_hierarchy.convert_ios_hierarchy(data, self.scale)
122131
return BaseHierarchy(
123132
jsonHierarchy=hierarchy,
124133
activityName=None,
125-
packageName=self.client.bundle_id,
126-
windowSize=self.window_size,
134+
packageName=self._current_bundle_id(),
135+
windowSize=self._window_size,
127136
scale=self.scale
128137
)
129138

139+
def wda_health(self) -> bool:
140+
resp = request("GET", f"{self.wda_url}/status", timeout=5).json()
141+
state = resp.get("value", {}).get("state")
142+
return state == "success"
130143

131-
def get_device(platform: str, serial: str, wda_url: str = None) -> Union[HarmonyDevice, AndroidDevice]:
132-
if serial not in list_serials(platform):
133-
raise HTTPException(status_code=200, detail="Device not found")
144+
145+
def get_device(platform: str, serial: str, wda_url: str, max_depth: int) -> Union[HarmonyDevice, AndroidDevice]:
134146
if platform == Platform.HARMONY:
135147
return HarmonyDevice(serial)
136148
elif platform == Platform.ANDROID:
137149
return AndroidDevice(serial)
138-
elif platform == Platform.IOS:
139-
return IosDevice(serial, wda_url)
140150
else:
141-
raise HTTPException(status_code=200, detail="Unsupported platform")
151+
return IosDevice(serial, wda_url, max_depth)
142152

143153

144154
# Global cache for devices
145155
cached_devices = {}
146156

147157

148-
def init_device(platform: str, serial: str, wda_url: str = None):
149-
device = get_device(platform, serial, wda_url)
150-
cached_devices[(platform, serial)] = device
151-
return platform, serial
158+
def init_device(platform: str, serial: str, wda_url: str = None, max_depth: int = 30) -> bool:
159+
160+
if serial not in list_serials(platform):
161+
raise HTTPException(status_code=500, detail=f"Device<{serial}> not found")
162+
163+
try:
164+
device: Union[HarmonyDevice, AndroidDevice] = get_device(platform, serial, wda_url, max_depth)
165+
cached_devices[(platform, serial)] = device
166+
167+
if isinstance(device, IosDevice):
168+
return device.wda_health()
169+
except Exception:
170+
error = traceback.format_exc()
171+
raise HTTPException(status_code=500, detail=error)
172+
173+
return True

uiviewer/_utils.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,3 @@ def image_to_base64(image: Image.Image, format: str = "PNG") -> str:
1818
buffered = BytesIO()
1919
image.save(buffered, format=format)
2020
return base64.b64encode(buffered.getvalue()).decode('utf-8')
21-
22-

uiviewer/app.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import os
44
import webbrowser
55
import uvicorn
6-
from typing import Union,Optional
6+
import threading
7+
from typing import Union, Optional
78

89
from fastapi import FastAPI, Request, Query, HTTPException
910
from fastapi.staticfiles import StaticFiles
@@ -30,15 +31,15 @@
3031

3132

3233
@app.exception_handler(Exception)
33-
async def global_exception_handler(request: Request, exc: Exception):
34+
def global_exception_handler(request: Request, exc: Exception):
3435
return JSONResponse(
3536
status_code=500,
3637
content=ApiResponse(success=False, message=str(exc)).dict()
3738
)
3839

3940

4041
@app.exception_handler(HTTPException)
41-
async def http_exception_handler(request: Request, exc: HTTPException):
42+
def http_exception_handler(request: Request, exc: HTTPException):
4243
return JSONResponse(
4344
status_code=exc.status_code,
4445
content=ApiResponse(success=False, message=exc.detail).dict(),
@@ -50,42 +51,44 @@ def open_browser():
5051

5152

5253
@app.get("/")
53-
async def root():
54+
def root():
5455
return RedirectResponse(url="/static/index.html")
5556

5657

5758
@app.get("/health")
58-
async def health():
59+
def health():
5960
return "ok"
6061

6162

6263
@app.get("/{platform}/serials", response_model=ApiResponse)
63-
async def get_serials(platform: str):
64+
def get_serials(platform: str):
6465
serials = list_serials(platform)
6566
return ApiResponse.doSuccess(serials)
6667

6768

68-
@app.post("/{platform}/{serial}/init", response_model=ApiResponse)
69-
async def init(platform: str, serial: str, wdaUrl: Optional[str] = Query(None)):
70-
device = init_device(platform, serial, wdaUrl)
71-
return ApiResponse.doSuccess(device)
69+
@app.post("/{platform}/{serial}/connect", response_model=ApiResponse)
70+
def connect(platform: str, serial: str, wdaUrl: Optional[str] = Query(None), maxDepth: Optional[int] = Query(None)):
71+
ret = init_device(platform, serial, wdaUrl, maxDepth)
72+
return ApiResponse.doSuccess(ret)
7273

7374

7475
@app.get("/{platform}/{serial}/screenshot", response_model=ApiResponse)
75-
async def screenshot(platform: str, serial: str):
76+
def screenshot(platform: str, serial: str):
7677
device: Union[AndroidDevice, IosDevice, HarmonyDevice] = cached_devices.get((platform, serial))
7778
data = device.take_screenshot()
7879
return ApiResponse.doSuccess(data)
7980

8081

8182
@app.get("/{platform}/{serial}/hierarchy", response_model=ApiResponse)
82-
async def dump_hierarchy(platform: str, serial: str):
83+
def dump_hierarchy(platform: str, serial: str):
8384
device: Union[AndroidDevice, IosDevice, HarmonyDevice] = cached_devices.get((platform, serial))
8485
data = device.dump_hierarchy()
8586
return ApiResponse.doSuccess(data)
8687

8788

8889
if __name__ == "__main__":
89-
import threading
90-
threading.Timer(1.0, open_browser).start()
90+
timer = threading.Timer(1.0, open_browser)
91+
timer.daemon = True
92+
timer.start()
93+
9194
uvicorn.run(app, host="127.0.0.1", port=8000)

uiviewer/static/index.html

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
99
</head>
1010
<body>
11-
<div id="app">
11+
<div
12+
id="app"
13+
v-loading="isConnecting || isDumping"
14+
element-loading-spinner="el-icon-loading"
15+
element-loading-background="rgba(0, 0, 0, 0.7)">
16+
1217
<div class="header">
1318
<div style="margin-right: 20px;">
1419
<span style="font-weight: bold; font-size: 20px;">UI Viewer</span>
@@ -27,7 +32,7 @@
2732
<el-select
2833
v-model="serial"
2934
placeholder="Select Device"
30-
style="margin-right: 20px;"
35+
style="margin-right: 20px; width: 250px"
3136
@visible-change="listDevice">
3237
<el-option
3338
v-for="d in devices"
@@ -37,13 +42,24 @@
3742
</el-option>
3843
</el-select>
3944

40-
<el-input
41-
class="custom-input"
42-
v-if="platform === 'ios'"
43-
v-model="wdaUrl"
44-
style="width: 250px; margin-right: 20px;"
45-
placeholder="Please input WDA url">
46-
</el-input>
45+
<el-tooltip v-if="platform === 'ios'" content="Set WDA url, default value is http://localhost:8100" placement="top">
46+
<el-input
47+
class="custom-input"
48+
v-model="wdaUrl"
49+
style="width: 180px; margin-right: 20px;"
50+
placeholder="WDA server url">
51+
</el-input>
52+
</el-tooltip>
53+
54+
<el-tooltip v-if="platform === 'ios'" content="Set the maximum depth for iOS dump hierarchy, default value is 30" placement="top">
55+
<el-input
56+
class="custom-input"
57+
v-model="snapshotMaxDepth"
58+
style="width: 100px; margin-right: 20px;"
59+
placeholder="maxDepth">
60+
</el-input>
61+
</el-tooltip>
62+
4763

4864
<el-button
4965
:disabled="isConnecting"
@@ -67,7 +83,7 @@
6783
<el-button
6884
v-else :disabled="isDumping"
6985
style="margin-right: 10px; width: 160px;"
70-
v-on:click="dumpHierarchyWithScreen">
86+
v-on:click="screenshotAndDumpHierarchy">
7187
<span
7288
v-if="isDumping"
7389
class="el-icon-loading">
@@ -84,12 +100,7 @@
84100
</el-link>
85101

86102
</div>
87-
<div
88-
class="main"
89-
v-loading="isConnecting || isDumping"
90-
element-loading-spinner="el-icon-loading"
91-
element-loading-background="rgba(0, 0, 0, 0.7)">
92-
103+
<div class="main">
93104
<div class="left">
94105
<canvas id="screenshotCanvas"></canvas>
95106
<canvas id="hierarchyCanvas"></canvas>

uiviewer/static/js/api.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
import { API_HOST } from './config.js';
22

3+
async function checkResponse(response) {
4+
if (response.status === 500) {
5+
throw new Error('Server error: 500');
6+
}
7+
return response.json();
8+
}
9+
310
export async function listDevices(platform) {
411
const response = await fetch(`${API_HOST}${platform}/serials`);
5-
return response.json();
12+
return checkResponse(response);
613
}
714

8-
export async function connectDevice(platform, serial, wdaUrl) {
9-
const response = await fetch(`${API_HOST}${platform}/${serial}/init?wdaUrl=${wdaUrl}`, {
15+
export async function connectDevice(platform, serial, wdaUrl, maxDepth) {
16+
const response = await fetch(`${API_HOST}${platform}/${serial}/connect?wdaUrl=${wdaUrl}&maxDepth=${maxDepth}`, {
1017
method: 'POST'
1118
});
12-
return response.json();
19+
return checkResponse(response);
1320
}
1421

1522
export async function fetchScreenshot(platform, serial) {
1623
const response = await fetch(`${API_HOST}${platform}/${serial}/screenshot`);
17-
return response.json();
24+
return checkResponse(response);
1825
}
1926

2027
export async function fetchHierarchy(platform, serial) {
2128
const response = await fetch(`${API_HOST}${platform}/${serial}/hierarchy`);
22-
return response.json();
29+
return checkResponse(response);
2330
}

0 commit comments

Comments
 (0)