Skip to content

Commit 368b957

Browse files
committed
feat: generate xpathLite
1 parent 7cca5d4 commit 368b957

File tree

16 files changed

+7122
-43
lines changed

16 files changed

+7122
-43
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,4 @@ cython_debug/
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161161
.DS_Store
162+
prompt.txt

docs/treeData/android.json

Lines changed: 2221 additions & 0 deletions
Large diffs are not rendered by default.

docs/treeData/harmony.json

Lines changed: 2797 additions & 0 deletions
Large diffs are not rendered by default.

docs/treeData/ios.json

Lines changed: 1838 additions & 0 deletions
Large diffs are not rendered by default.

uiviewer/_device.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from hmdriver2 import hdc
1515
from fastapi import HTTPException
1616

17-
from uiviewer._utils import file_to_base64, image_to_base64
17+
from uiviewer._utils import file2base64, image2base64
1818
from uiviewer._models import Platform, BaseHierarchy
1919
from uiviewer.parser import android_hierarchy, ios_hierarchy, harmony_hierarchy
2020

@@ -56,7 +56,7 @@ def take_screenshot(self) -> str:
5656
with tempfile.NamedTemporaryFile(delete=True, suffix=".png") as f:
5757
path = f.name
5858
self.hdc.screenshot(path)
59-
return file_to_base64(path)
59+
return file2base64(path)
6060

6161
def dump_hierarchy(self) -> BaseHierarchy:
6262
packageName, pageName = self.hdc.current_app()
@@ -82,12 +82,12 @@ def _window_size(self) -> Tuple:
8282

8383
def take_screenshot(self) -> str:
8484
img: Image.Image = self.d.screenshot()
85-
return image_to_base64(img)
85+
return image2base64(img)
8686

8787
def dump_hierarchy(self) -> BaseHierarchy:
8888
current = self.d.app_current()
8989
page_xml = self.d.dump_hierarchy()
90-
page_json = android_hierarchy.get_android_hierarchy(page_xml)
90+
page_json = android_hierarchy.convert_android_hierarchy(page_xml)
9191
return BaseHierarchy(
9292
jsonHierarchy=page_json,
9393
activityName=current['activity'],
@@ -123,7 +123,7 @@ def _check_wda_health(self) -> bool:
123123

124124
def take_screenshot(self) -> str:
125125
img: Image.Image = self.client.screenshot()
126-
return image_to_base64(img)
126+
return image2base64(img)
127127

128128
def _current_bundle_id(self) -> str:
129129
resp = request("GET", f"{self.wda_url}/wda/activeAppInfo", timeout=10).json()

uiviewer/_models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,9 @@ class BaseHierarchy(BaseModel):
3131
windowSize: Tuple[int, int]
3232
scale: int = 1
3333
activityName: Optional[str] = None
34-
packageName: Optional[str] = None
34+
packageName: Optional[str] = None
35+
36+
37+
class XPathLiteRequest(BaseModel):
38+
tree_data: Dict[str, Any]
39+
node_id: str

uiviewer/_utils.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
# -*- coding: utf-8 -*-
22

33
import base64
4+
import json
5+
from typing import Dict
46
from PIL import Image
57
from io import BytesIO
68

9+
from uiviewer._logger import logger
710

8-
def file_to_base64(path: str) -> str:
11+
12+
def file2base64(path: str) -> str:
913
with open(path, "rb") as file:
1014
base64_encoded = base64.b64encode(file.read())
1115
return base64_encoded.decode('utf-8')
1216

1317

14-
def image_to_base64(image: Image.Image, format: str = "PNG") -> str:
18+
def image2base64(image: Image.Image, format: str = "PNG") -> str:
1519
"""
1620
PIL Image to base64 string
1721
"""
1822
buffered = BytesIO()
1923
image.save(buffered, format=format)
2024
return base64.b64encode(buffered.getvalue()).decode('utf-8')
25+
26+
27+
def str2json(s: str) -> Dict:
28+
try:
29+
json_obj = json.loads(s)
30+
return json_obj
31+
except json.JSONDecodeError as e:
32+
logger.error(f"Invalid JSON data: {e}")
33+
return {}

uiviewer/parser/android_hierarchy.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,29 +63,31 @@ def _parse_uiautomator_node(node):
6363
return attributes
6464

6565

66-
def get_android_hierarchy(page_xml: str) -> Dict:
66+
def convert_android_hierarchy(page_xml: str) -> Dict:
6767
dom = xml.dom.minidom.parseString(page_xml)
6868
root = dom.documentElement
6969

70-
def travel(node):
70+
def __travel(node, parent_id=""):
7171
if node.attributes is None:
7272
return
7373
json_node = _parse_uiautomator_node(node)
7474
json_node['_id'] = str(uuid.uuid4())
75+
json_node['_parentId'] = parent_id
76+
json_node['xpath'] = ""
7577
json_node.pop("package", None)
7678
if node.childNodes:
7779
children = []
7880
for n in node.childNodes:
79-
child = travel(n)
81+
child = __travel(n, json_node['_id'])
8082
if child:
8183
children.append(child)
8284
json_node['children'] = children
8385

8486
# Sort the keys
85-
keys_order = ['_type', 'resourceId', 'text', 'description']
87+
keys_order = ['xpath', '_type', 'resourceId', 'text', 'description']
8688
sorted_node = {k: json_node[k] for k in keys_order if k in json_node}
8789
sorted_node.update({k: json_node[k] for k in json_node if k not in keys_order})
8890

8991
return sorted_node
9092

91-
return travel(root)
93+
return __travel(root)

uiviewer/parser/harmony_hierarchy.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66

77
def convert_harmony_hierarchy(data: Dict) -> Dict:
8-
ret = {"_id": str(uuid.uuid4()), "children": []}
8+
ret = {"_id": str(uuid.uuid4()), "children": [], "_parentId": ""}
99

10-
def __recursive_convert(node_a):
10+
def __travel(node_a, parent_id=""):
1111
node_b = {
1212
"index": 0,
1313
"text": "",
@@ -30,17 +30,20 @@ def __recursive_convert(node_a):
3030
"height": 0
3131
},
3232
"_id": str(uuid.uuid4()),
33+
"_parentId": parent_id,
34+
"xpath": ""
3335
}
3436

3537
attributes = node_a.get("attributes", {})
38+
node_b["xpath"] = attributes.get("xpath", "")
3639
node_b["_type"] = attributes.get("type", "")
3740
node_b["id"] = attributes.get("id", "")
3841
node_b["description"] = attributes.get("description", "")
3942
node_b["text"] = attributes.get("text", "")
4043
node_b["checkable"] = attributes.get("checkable", "").lower() == "true"
4144
node_b["clickable"] = attributes.get("clickable", "").lower() == "true"
4245
node_b["enabled"] = attributes.get("enabled", "").lower() == "true"
43-
node_b["focusable"] = attributes.get("focused", "").lower() == "true"
46+
node_b["focusable"] = attributes.get("focusable", "").lower() == "true"
4447
node_b["focused"] = attributes.get("focused", "").lower() == "true"
4548
node_b["scrollable"] = attributes.get("scrollable", "").lower() == "true"
4649
node_b["longClickable"] = attributes.get("longClickable", "").lower() == "true"
@@ -53,11 +56,11 @@ def __recursive_convert(node_a):
5356

5457
children = node_a.get("children", [])
5558
if children:
56-
node_b["children"] = [__recursive_convert(child) for child in children]
59+
node_b["children"] = [__travel(child, node_b["_id"]) for child in children]
5760

5861
return node_b
5962

6063
# Recursively convert children of a to match b's structure
61-
ret["children"] = [__recursive_convert(child) for child in data.get("children", [])]
64+
ret["children"] = [__travel(child, ret["_id"]) for child in data.get("children", [])]
6265

6366
return ret

uiviewer/parser/ios_hierarchy.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,25 @@
66

77
def convert_ios_hierarchy(data: Dict, scale: int) -> Dict:
88

9-
def __travel(node):
9+
def __travel(node, parent_id=""):
1010
node['_id'] = str(uuid.uuid4())
11+
node['_parentId'] = parent_id
1112
node['_type'] = node.pop('type', "null")
1213
node['id'] = node.pop('rawIdentifier', "null")
14+
node['xpath'] = ""
1315
if 'rect' in node:
1416
rect = node['rect']
1517
node['rect'] = {k: v * scale for k, v in rect.items()}
1618

1719
# Recursively process children nodes
1820
if 'children' in node:
19-
node['children'] = [__travel(child) for child in node['children']]
21+
node['children'] = [__travel(child, node['_id']) for child in node['children']]
2022

2123
# Sort the keys
22-
keys_order = ['_type', 'label', 'name', 'id', 'value']
24+
keys_order = ['xpath', '_type', 'label', 'name', 'id', 'value']
2325
sorted_node = {k: node[k] for k in keys_order if k in node}
2426
sorted_node.update({k: node[k] for k in node if k not in keys_order})
2527

2628
return sorted_node
2729

28-
return __travel(data)
30+
return __travel(data)

0 commit comments

Comments
 (0)