Skip to content

Commit

Permalink
feat: show version on web page
Browse files Browse the repository at this point in the history
  • Loading branch information
codematrixer committed Oct 8, 2024
1 parent 8dd3954 commit 0c6b98d
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 98 deletions.
6 changes: 2 additions & 4 deletions DEVELOP.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@

This project is developed using FastAPI, Vue, and Element-UI. It starts the service locally and displays the UI hierarchy tree through a web browser.

**Tech Stack**
# Tech Stack

- python3
- html/css/js
Expand All @@ -21,7 +19,7 @@ poetry install
poetry build
```

## Run
# Run
```
poetry run python3 -m uiviewer
```
70 changes: 59 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
# ui-viewer
[![github actions](https://github.com/codematrixer/ui-viewer/actions/workflows/release.yml/badge.svg)](https://github.com/codematrixer/ui-viewer/actions)
[![pypi version](https://img.shields.io/pypi/v/uiviewer.svg)](https://pypi.python.org/pypi/uiviewer)
![python](https://img.shields.io/pypi/pyversions/uiviewer.svg)

UI hierarchy visualization tool, supporting Android, iOS, HarmonyOS NEXT.

![showcase](./docs/imgs/show.gif)
This project is developed using FastAPI and Vue. It starts the service locally and displays UI hierarchy tree through web browser.

![show](https://i.ibb.co/Phfm9Q1/show.gif)

# Installation
- python3.8+

```shell
pip3 install -U uiviewer
```

# Run
Start with a default port (8000)
Run the following command on the terminal. (default port `8000`)

```shell
uiviewer
# or
python3 -m uiviewer

INFO: Started server process [46814]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:55080 - "GET / HTTP/1.1" 307 Temporary Redirect
INFO: 127.0.0.1:55080 - "GET /static/index.html HTTP/1.1" 200 OK
INFO: 127.0.0.1:55080 - "GET /static/css/style.css HTTP/1.1" 200 OK
INFO: 127.0.0.1:55080 - "GET /static/js/index.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:55080 - "GET /static/js/api.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:55082 - "GET /static/js/utils.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:55082 - "GET /static/js/config.js HTTP/1.1" 200 OK
INFO: 127.0.0.1:55082 - "GET /version HTTP/1.1" 200 OK
```
Start with a custom port
```
And then open the browser to [http://localhost:8000](http://localhost:8000)

You can also customize port to start the service.
```shell
uiviewer -p <PORT>
# or
python3 -m uiviewer -p <PORT>

```
and then open the browser to [http://localhost:8000](http://localhost:8000)



# Tips
Expand All @@ -33,17 +58,40 @@ and then open the browser to [http://localhost:8000](http://localhost:8000)
```
- Second, Use `tidevice` or `iproxy` to forward the wda port,and keep it running.
```
tidevice relay <local_port> 8100
tidevice relay 8100 8100
```
- And then, To ensure the success of the browser to access `http://localhost:8100/status`, return like this:
```
{
"value": {
"build": {
"productBundleIdentifier": "com.facebook.WebDriverAgentRunner",
"time": "Mar 25 2024 15:17:30"
},
...
"state": "success",
"ready": true
},
"sessionId": null
}
```
- Finally, To ensure the success of the browser to access `http://localhost:<local_port>/status`
- Finally, Input the **`wdaUrl`** in the web page, such as `http://localhost:8100`

- On iOS,WDA can easily freeze when dumping high UI hierarchy. You can reduce the **`maxDepth`** on the web page. The default is 30.

# preview
# Preview
- HarmonyOS
![harmony](./docs/imgs/harmony.png)
![harmony](https://i.ibb.co/82BrJ1H/harmony.png)

- Android
![android](./docs/imgs/android.png)
![android](https://i.ibb.co/RySs497/android.png)

- iOS
![ios](./docs/imgs/ios.png)
![ios](https://i.ibb.co/VVWtTS3/ios.png)


# Relevant
- https://github.com/codematrixer/hmdriver2
- https://github.com/openatx/uiautomator2
- https://github.com/openatx/facebook-wda
- https://github.com/alibaba/web-editor
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "uiviewer"
version = "1.0.0"
version = "1.0.1"
description = "UI hierarchy visualization tool, supporting Android, iOS, HarmonyOS NEXT."
authors = ["codematrixer <[email protected]>"]
license = "MIT"
Expand Down
20 changes: 1 addition & 19 deletions uiviewer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1 @@
# -*- coding: utf-8 -*-

import logging

formatter = logging.Formatter('[%(asctime)s] %(filename)15s[line:%(lineno)4d] \
[%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')

logger = logging.getLogger('hmdriver2')
logger.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(formatter)

logger.addHandler(console_handler)


__all__ = ['logger']
# -*- coding: utf-8 -*-
54 changes: 6 additions & 48 deletions uiviewer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,12 @@
import webbrowser
import uvicorn
import threading
from typing import Union, Optional

from fastapi import FastAPI, Request, Query, HTTPException
from fastapi import FastAPI, Request, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import JSONResponse, RedirectResponse

from uiviewer._device import (
list_serials,
init_device,
cached_devices,
AndroidDevice,
IosDevice,
HarmonyDevice
)
from fastapi.responses import JSONResponse

from uiviewer.routers import api
from uiviewer._models import ApiResponse


Expand All @@ -29,6 +21,8 @@

app.mount("/static", StaticFiles(directory=static_dir), name="static")

app.include_router(api.router)


@app.exception_handler(Exception)
def global_exception_handler(request: Request, exc: Exception):
Expand All @@ -50,42 +44,6 @@ def open_browser(port):
webbrowser.open_new(f"http://127.0.0.1:{port}")


@app.get("/")
def root():
return RedirectResponse(url="/static/index.html")


@app.get("/health")
def health():
return "ok"


@app.get("/{platform}/serials", response_model=ApiResponse)
def get_serials(platform: str):
serials = list_serials(platform)
return ApiResponse.doSuccess(serials)


@app.post("/{platform}/{serial}/connect", response_model=ApiResponse)
def connect(platform: str, serial: str, wdaUrl: Optional[str] = Query(None), maxDepth: Optional[int] = Query(None)):
ret = init_device(platform, serial, wdaUrl, maxDepth)
return ApiResponse.doSuccess(ret)


@app.get("/{platform}/{serial}/screenshot", response_model=ApiResponse)
def screenshot(platform: str, serial: str):
device: Union[AndroidDevice, IosDevice, HarmonyDevice] = cached_devices.get((platform, serial))
data = device.take_screenshot()
return ApiResponse.doSuccess(data)


@app.get("/{platform}/{serial}/hierarchy", response_model=ApiResponse)
def dump_hierarchy(platform: str, serial: str):
device: Union[AndroidDevice, IosDevice, HarmonyDevice] = cached_devices.get((platform, serial))
data = device.dump_hierarchy()
return ApiResponse.doSuccess(data)


def run(port=8000):
timer = threading.Timer(1.0, open_browser, args=[port])
timer.daemon = True
Expand Down
10 changes: 7 additions & 3 deletions uiviewer/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import abc
import tempfile
from typing import List, Dict, Union, Tuple
from typing import List, Dict, Union, Tuple, Optional
from functools import cached_property # python3.8+

from PIL import Image
Expand Down Expand Up @@ -101,9 +101,13 @@ class IosDevice(DeviceMeta):
def __init__(self, udid: str, wda_url: str, max_depth: int) -> None:
self.udid = udid
self.wda_url = wda_url
self.max_depth = max_depth
self._max_depth = max_depth
self.client = wda.Client(wda_url)

@property
def max_depth(self) -> int:
return int(self._max_depth) if self._max_depth else 30

@cached_property
def scale(self) -> int:
return self.client.scale
Expand Down Expand Up @@ -152,7 +156,7 @@ def get_device(platform: str, serial: str, wda_url: str, max_depth: int) -> Unio
cached_devices = {}


def init_device(platform: str, serial: str, wda_url: str = None, max_depth: int = 30) -> bool:
def init_device(platform: str, serial: str, wda_url: str, max_depth: int):

if serial not in list_serials(platform):
raise HTTPException(status_code=500, detail=f"Device<{serial}> not found")
Expand Down
4 changes: 0 additions & 4 deletions uiviewer/_error.py

This file was deleted.

19 changes: 19 additions & 0 deletions uiviewer/_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-

import logging

formatter = logging.Formatter('[%(asctime)s] %(filename)15s[line:%(lineno)4d] \
[%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')

logger = logging.getLogger('hmdriver2')
logger.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(formatter)

logger.addHandler(console_handler)


__all__ = ['logger']
5 changes: 5 additions & 0 deletions uiviewer/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-

import importlib.metadata

__version__ = importlib.metadata.version('uiviewer')
1 change: 1 addition & 0 deletions uiviewer/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# -*- coding: utf-8 -*-
66 changes: 66 additions & 0 deletions uiviewer/routers/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-

from typing import Union, Optional

from fastapi import APIRouter, Query
from fastapi.responses import RedirectResponse

from uiviewer._device import (
list_serials,
init_device,
cached_devices,
AndroidDevice,
IosDevice,
HarmonyDevice
)
from uiviewer._models import ApiResponse
from uiviewer._version import __version__


router = APIRouter()


@router.get("/")
def root():
return RedirectResponse(url="/static/index.html")


@router.get("/health")
def health():
return "ok"


@router.get("/version", response_model=ApiResponse)
def get_version():
return ApiResponse.doSuccess(__version__)


@router.get("/{platform}/serials", response_model=ApiResponse)
def get_serials(platform: str):
serials = list_serials(platform)
return ApiResponse.doSuccess(serials)


@router.post("/{platform}/{serial}/connect", response_model=ApiResponse)
def connect(
platform: str,
serial: str,
wdaUrl: Union[str, None] = Query(None),
maxDepth: Union[int, None] = Query(None)
):
ret = init_device(platform, serial, wdaUrl, maxDepth)
return ApiResponse.doSuccess(ret)


@router.get("/{platform}/{serial}/screenshot", response_model=ApiResponse)
def screenshot(platform: str, serial: str):
device: Union[AndroidDevice, IosDevice, HarmonyDevice] = cached_devices.get((platform, serial))
data = device.take_screenshot()
return ApiResponse.doSuccess(data)


@router.get("/{platform}/{serial}/hierarchy", response_model=ApiResponse)
def dump_hierarchy(platform: str, serial: str):
device: Union[AndroidDevice, IosDevice, HarmonyDevice] = cached_devices.get((platform, serial))
data = device.dump_hierarchy()
return ApiResponse.doSuccess(data)
6 changes: 3 additions & 3 deletions uiviewer/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<div class="header">
<div style="margin-right: 20px;">
<span style="font-weight: bold; font-size: 20px;">UI Viewer</span>
<span> 1.0.0</span>
<span>{{version}}</span>
</div>
<el-select
v-model="platform"
Expand All @@ -42,7 +42,7 @@
</el-option>
</el-select>

<el-tooltip v-if="platform === 'ios'" content="Set WDA url, default value is http://localhost:8100" placement="top">
<el-tooltip v-if="platform === 'ios'" content="Set WDA url, such as http://localhost:8100" placement="top">
<el-input
class="custom-input"
v-model="wdaUrl"
Expand All @@ -55,7 +55,7 @@
<el-input
class="custom-input"
v-model="snapshotMaxDepth"
style="width: 100px; margin-right: 20px;"
style="width: 110px; margin-right: 20px;"
placeholder="maxDepth">
</el-input>
</el-tooltip>
Expand Down
Loading

0 comments on commit 0c6b98d

Please sign in to comment.