feat(M2): 语义层工具 de_ui_tree/de_tap_element——UI 树元素定位点击(text/id/desc,多命中 index),无需坐标更可靠;坐标 tap 保留兜底(WebView/画布元素)
This commit is contained in:
@@ -167,6 +167,67 @@ _KEYS = ("back", "home", "recent", "menu", "power", "volume_up",
|
|||||||
"volume_down", "enter", "delete", "search", "camera")
|
"volume_down", "enter", "delete", "search", "camera")
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def de_ui_tree(serial: str) -> dict:
|
||||||
|
"""获取当前界面元素树(文本 JSON):每元素含 text/resource_id/description/class/bounds。
|
||||||
|
|
||||||
|
优先用它定位元素(元素驱动操作),比纯坐标点击更可靠。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
serial = _check_serial(serial)
|
||||||
|
els = platform().ui_elements(serial)
|
||||||
|
except PlatformError as e:
|
||||||
|
return _err(e)
|
||||||
|
# 精简输出:去掉 suggested/深度噪音,保留可定位属性
|
||||||
|
slim = []
|
||||||
|
for e in els:
|
||||||
|
slim.append({
|
||||||
|
"text": e.get("text", "")[:50],
|
||||||
|
"id": e.get("resource_id", "")[:80],
|
||||||
|
"desc": e.get("description", "")[:50],
|
||||||
|
"class": e.get("class", "").split(".")[-1],
|
||||||
|
"bounds": e.get("bounds", ""),
|
||||||
|
})
|
||||||
|
audit.audit("de_ui_tree", serial, "", f"{len(slim)} 元素")
|
||||||
|
return _ok({"count": len(slim), "elements": slim[:300]})
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def de_tap_element(serial: str, by: str, value: str, index: int = 1) -> dict:
|
||||||
|
"""按元素点击(不需要坐标):by=text|id|desc,value 为匹配文本/资源 id/描述。
|
||||||
|
|
||||||
|
元素驱动操作比坐标可靠(界面变化自适应);元素不存在时返回错误,
|
||||||
|
可改用 de_ui_tree 查元素或 de_tap 坐标兜底。index 用于多命中取第几个(默认 1)。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_check_write()
|
||||||
|
serial = _check_serial(serial)
|
||||||
|
if by not in ("text", "id", "desc"):
|
||||||
|
raise PlatformError("invalid_param", "by 可选 text/id/desc")
|
||||||
|
if not value or index < 1:
|
||||||
|
raise PlatformError("invalid_param", "value 不能为空且 index>=1")
|
||||||
|
import uiautomator2 as u2
|
||||||
|
d = u2.connect(serial)
|
||||||
|
kw = {"text": value} if by == "text" else (
|
||||||
|
{"resourceId": value} if by == "id" else {"description": value})
|
||||||
|
if index > 1:
|
||||||
|
kw["instance"] = index - 1
|
||||||
|
el = d(**kw)
|
||||||
|
if not el.exists:
|
||||||
|
raise PlatformError("device_offline",
|
||||||
|
f"未找到元素({by}={value},index={index})——"
|
||||||
|
f"建议 de_ui_tree 查看实际元素或 de_tap 用坐标")
|
||||||
|
el.click()
|
||||||
|
except PlatformError as e:
|
||||||
|
return _err(e)
|
||||||
|
except Exception as e:
|
||||||
|
return _err(PlatformError("platform_unavailable",
|
||||||
|
f"元素点击失败: {type(e).__name__}: {str(e)[:100]}"))
|
||||||
|
audit.audit("de_tap_element", serial, f"{by}={value}#{index}", "ok")
|
||||||
|
return _ok({"action": "tap_element", "serial": serial,
|
||||||
|
"by": by, "value": value, "index": index})
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def de_wake(serial: str) -> dict:
|
def de_wake(serial: str) -> dict:
|
||||||
"""点亮设备屏幕并解锁(熄屏时先调用它再截图)。"""
|
"""点亮设备屏幕并解锁(熄屏时先调用它再截图)。"""
|
||||||
|
|||||||
@@ -107,6 +107,19 @@ class PlatformClient:
|
|||||||
"duration": float(duration)})
|
"duration": float(duration)})
|
||||||
return self._check_op(r, "swipe")
|
return self._check_op(r, "swipe")
|
||||||
|
|
||||||
|
def ui_elements(self, serial):
|
||||||
|
"""UI 元素树(GET /api/uiauto/elements,uiautodev 服务)。"""
|
||||||
|
r = self._get("/api/uiauto/elements", serial=serial)
|
||||||
|
if r.status_code == 503:
|
||||||
|
raise PlatformError("device_offline", r.text[:120])
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise PlatformError("platform_unavailable",
|
||||||
|
f"元素树 HTTP {r.status_code}")
|
||||||
|
j = r.json() or {}
|
||||||
|
if not j.get("ok"):
|
||||||
|
raise PlatformError("device_offline", str(j.get("error", "取元素失败"))[:120])
|
||||||
|
return j.get("elements", [])
|
||||||
|
|
||||||
def wake(self, serial):
|
def wake(self, serial):
|
||||||
"""亮屏并解锁(POST /api/device/screen_all mode=on)。"""
|
"""亮屏并解锁(POST /api/device/screen_all mode=on)。"""
|
||||||
r = self._post("/api/device/screen_all",
|
r = self._post("/api/device/screen_all",
|
||||||
|
|||||||
Reference in New Issue
Block a user