Files
skill-template/tools/ui_inspector/smoke_check.py
chendelian 633ecff4e2 chore: archive tools/ui_inspector snapshot before migration removal.
Keep the last in-template copy on main so history can restore it; product continues in jiangchang-ui-recorder.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 11:19:14 +08:00

384 lines
14 KiB
Python

"""Smoke test for flow document API + surface routing + kit helpers (v0.10)."""
from __future__ import annotations
import sys
import tempfile
from pathlib import Path
_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
sys.path.insert(0, str(_HERE))
import artifacts as art
import desktop_uia as uia
import kit_browser
import kit_wake
import skip_policy
import target
import user_prefs
from surfaces.registry import get_driver
from drivers.mobile import MobileDriver
from jiangchang_skill_core.rpa.page_dump import build_page_dump_result
def _assert_selection_api() -> None:
assert callable(uia.window_info_under_cursor)
assert callable(uia.find_wecom_search_result_window)
assert callable(uia.resolve_capture_target)
try:
_info, _cands, meta = uia.resolve_capture_target(
under_cursor=False,
prefer_wecom_search_popup=False,
)
assert "strategy" in meta
except RuntimeError as exc:
assert "未找到可拾取" in str(exc)
print("OK selection api")
def _assert_kit_wake_detect() -> None:
assert kit_wake.looks_like_wecom(class_name="WeWorkWindow", title="企业微信")
assert kit_wake.resolve_wake_profile(class_name="SearchResultWindow2") == "wecom_search"
assert not kit_wake.looks_like_wecom(class_name="Chrome_WidgetWin_1", title="Google")
r = kit_wake.auto_ensure_if_wecom(class_name="NotWeCom", title="x")
assert r.get("skipped") and r.get("reason") == "not_wecom"
print("OK kit_wake detect")
def _assert_surface_routing() -> None:
assert (
target.resolve_surface(process_name="chrome.exe", class_name="Chrome_WidgetWin_1")
== target.SURFACE_BROWSER
)
assert (
target.resolve_surface(process_name="msedge.exe", class_name="Chrome_WidgetWin_1")
== target.SURFACE_BROWSER
)
assert (
target.resolve_surface(process_name="WXWork.exe", class_name="WeWorkWindow")
== target.SURFACE_WINDOWS
)
assert get_driver(target.SURFACE_WINDOWS).driver_name == "uiautomation"
assert get_driver(target.SURFACE_BROWSER).driver_name == "playwright"
mobile = MobileDriver(surface=target.SURFACE_ANDROID)
try:
mobile.dump(info=None, selection_meta={})
raise AssertionError("mobile dump should raise")
except RuntimeError as exc:
assert "SURFACE_UNSUPPORTED" in str(exc)
print("OK surface routing")
def _assert_user_prefs() -> None:
with tempfile.TemporaryDirectory(prefix="ui_prefs_") as tmp:
path = Path(tmp) / "user_prefs.json"
# monkeypatch prefs path
old = user_prefs._PREFS_PATH
try:
user_prefs._PREFS_PATH = path
user_prefs.save_prefs(mode="windows", start_url="https://example.com")
loaded = user_prefs.load_prefs()
assert loaded["mode"] == "windows"
assert loaded["start_url"] == "https://example.com"
user_prefs.save_prefs(mode="browser", start_url="")
loaded_empty = user_prefs.load_prefs()
assert loaded_empty["start_url"] == ""
user_prefs.save_prefs(mode="mobile", start_url="https://a.test")
loaded2 = user_prefs.load_prefs()
assert loaded2["mode"] == target.SURFACE_ANDROID
finally:
user_prefs._PREFS_PATH = old
print("OK user_prefs")
def _assert_skip_policy() -> None:
# blank + empty locatable → skip
assert (
skip_policy.should_skip_empty_dump(
surface="browser",
url="about:blank",
locatable_count=0,
nodes_kept=1,
sparse=True,
)
== "blank_or_newtab_empty"
)
# blank URL but rich content (nav race like step05) → keep
assert (
skip_policy.should_skip_empty_dump(
surface="browser",
url="about:blank",
locatable_count=74,
nodes_kept=97,
sparse=False,
)
is None
)
# real page → keep
assert (
skip_policy.should_skip_empty_dump(
surface="browser",
url="https://www.douyin.com/jingxuan",
locatable_count=150,
nodes_kept=160,
sparse=False,
)
is None
)
# windows never skipped by this policy
assert (
skip_policy.should_skip_empty_dump(
surface="windows",
url="",
locatable_count=0,
nodes_kept=1,
sparse=True,
)
is None
)
# same URL with content is NOT skipped (scroll-safe)
assert (
skip_policy.should_skip_empty_dump(
surface="browser",
url="https://www.douyin.com/jingxuan",
locatable_count=20,
nodes_kept=30,
sparse=False,
)
is None
)
print("OK skip_policy")
def _assert_active_page_resolve() -> None:
"""Unit-level: visible tab wins over sticky pages[0]."""
class _FakePage:
def __init__(self, url: str, visible: bool, *, closed: bool = False) -> None:
self.url = url
self._visible = visible
self._closed = closed
def is_closed(self) -> bool:
return self._closed
def evaluate(self, _script: str) -> str:
return "visible" if self._visible else "hidden"
def title(self) -> str:
return self.url
class _FakeCtx:
def __init__(self, pages: list) -> None:
self.pages = pages
search = _FakePage("https://www.douyin.com/search/foo", visible=False)
detail = _FakePage("https://www.douyin.com/video/123", visible=True)
ctx = _FakeCtx([search, detail])
page, meta = kit_browser.resolve_active_page(ctx, preferred=search)
assert page is detail, (page.url, meta)
assert meta["resolve"] == "visibility"
assert meta["page_count"] == 2
assert meta["visible_count"] == 1
# No visible → preferred if still open
both_hidden = _FakeCtx(
[
_FakePage("https://a.test/1", visible=False),
_FakePage("https://a.test/2", visible=False),
]
)
preferred = both_hidden.pages[0]
page2, meta2 = kit_browser.resolve_active_page(both_hidden, preferred=preferred)
assert page2 is preferred
assert meta2["resolve"] == "preferred_fallback"
# Closed preferred → first open
closed_pref = _FakePage("https://gone", visible=False, closed=True)
open_only = _FakePage("https://alive", visible=False)
page3, meta3 = kit_browser.resolve_active_page(
_FakeCtx([closed_pref, open_only]), preferred=closed_pref
)
assert page3 is open_only
assert meta3["resolve"] == "first_open"
print("OK active page resolve")
def _assert_page_dump_kit() -> None:
if not kit_browser.kit_page_dump_available():
raise RuntimeError(
"jiangchang_skill_core.rpa.page_dump 不可用;"
"请 pip install -e jiangchang-platform-kit>=1.2.11"
)
result = build_page_dump_result(
url="https://example.com",
title="Example",
a11y_snapshot={
"role": "RootWebArea",
"name": "Example",
"children": [{"role": "button", "name": "OK", "children": []}],
},
dom_nodes=[
{
"tag": "button",
"role": "button",
"id": "",
"name_attr": "",
"testid": "",
"aria_label": "OK",
"placeholder": "",
"title": "",
"alt": "",
"href": "",
"type": "submit",
"text": "OK",
"disabled": False,
"rect": {},
}
],
)
assert result["stats"]["locatable_count"] >= 1
assert result["locatable"][0].get("primary_locator")
print("OK page_dump kit builder")
def _assert_label() -> None:
msg_page = [
{"control_type": "TreeItemControl", "name": "groupbuddyname", "value": "某群"},
{"control_type": "TreeItemControl", "name": "send_rich_edit", "value": "你好"},
{"control_type": "TreeItemControl", "name": "con_op", "value": "消息"},
]
searching = [
{"control_type": "TreeItemControl", "name": "groupbuddyname", "value": "徐书建"},
{"control_type": "TreeControl", "name": "searchedit", "value": "陈德炼"},
{"control_type": "TreeItemControl", "name": "send_rich_edit", "value": "陈德炼"},
]
selected = [
{
"control_type": "TreeItemControl",
"name": "groupbuddyname",
"value": "陈德炼<c #99ffffff>@微信</c>",
},
{"control_type": "TreeItemControl", "name": "send_rich_edit", "value": "您好"},
]
contacts = [
{"control_type": "TreeItemControl", "name": "panel_title_label", "value": "RPA之家"},
{"control_type": "TreeItemControl", "name": "main_chattext", "value": "新的客户"},
]
new_customer = [
{"control_type": "TreeItemControl", "name": "panel_title_label", "value": "新的客户"},
{"control_type": "TreeItemControl", "name": "ww_btn_text", "value": "添加"},
]
add_empty = [
{"control_type": "TreeItemControl", "name": "title", "value": "添加客户"},
{"control_type": "TreeControl", "name": "searchedit", "value": ""},
{"control_type": "WindowControl", "name": "添加客户", "value": ""},
]
add_result = [
{"control_type": "TreeItemControl", "name": "title", "value": "添加客户"},
{"control_type": "TreeControl", "name": "searchedit", "value": "13316463772"},
{"control_type": "TreeItemControl", "name": "main_chattext", "value": "Nancy"},
{"control_type": "TreeItemControl", "name": "ww_btn_text", "value": "添加为"},
{"control_type": "TreeControl", "name": "search_result", "value": ""},
]
a = art.derive_step_label(window_title="企业微信", locatable=msg_page)
b = art.derive_step_label(window_title="企业微信", locatable=searching)
c = art.derive_step_label(window_title="企业微信", locatable=selected)
d = art.derive_step_label(window_title="企业微信", locatable=contacts)
e = art.derive_step_label(window_title="企业微信", locatable=new_customer)
f = art.derive_step_label(window_title="添加客户", locatable=add_empty)
g = art.derive_step_label(window_title="添加客户", locatable=add_result)
assert "输入你好" in a or "某群" in a, a
assert "搜索陈德炼" in b, b
assert "陈德炼" in c and "输入您好" in c, c
assert "RPA之家" in d, d
assert "新的客户" in e and "添加" in e, e
assert f.startswith("添加客户"), f
assert "13316463772" in g or "搜索13316463772" in g, g
assert "会话列表" not in a and "会话列表" not in b and "会话列表" not in c
print(f"OK labels:\n {a}\n {b}\n {c}\n {d}\n {e}\n {f}\n {g}")
def main() -> int:
_assert_selection_api()
_assert_kit_wake_detect()
_assert_surface_routing()
_assert_user_prefs()
_assert_skip_policy()
_assert_active_page_resolve()
_assert_page_dump_kit()
_assert_label()
with tempfile.TemporaryDirectory(prefix="ui_flow_smoke_") as tmp:
root = Path(tmp)
flow_dir = art.create_flow(root, flow_name="demo-flow")
flow = art.load_flow(flow_dir)
loc = [
{
"control_type": "TreeItemControl",
"name": "groupbuddyname",
"value": "陈德炼<c #99ffffff>@微信</c>",
},
{"control_type": "TreeItemControl", "name": "send_rich_edit", "value": "您好"},
]
label = art.resolve_step_label(
flow, window_title="企业微信", window_class="WeWorkWindow", locatable=loc
)
step_dir, idx, name = art.add_step_dir(
flow_dir,
flow,
window_title="企业微信",
window_class="WeWorkWindow",
locatable=loc,
label=label,
)
assert idx == 1
assert "陈德炼" in name, name
art.write_json(
step_dir / "meta.json",
art.build_meta(
window={"title": "企业微信", "class_name": "WeWorkWindow", "pid": 1, "hwnd": 2},
scene="demo",
surface="windows",
driver="uiautomation",
),
)
art.register_step(
flow_dir,
flow,
step_index=idx,
step_name=name,
window={"title": "企业微信", "class_name": "WeWorkWindow", "pid": 1, "hwnd": 2},
stats={"findall_count": 10, "locatable_count": 5, "nodes_kept": 3, "uia_sparse": False},
label=label,
surface="windows",
)
# browser-shaped step registration
blow = art.load_flow(flow_dir)
art.register_step(
flow_dir,
blow,
step_index=2,
step_name="02_browser-demo",
window={"title": "精选", "class_name": "PlaywrightPage", "pid": 3, "hwnd": 4},
stats={"findall_count": 20, "locatable_count": 12, "nodes_kept": 20, "uia_sparse": False},
label="浏览器精选",
surface="browser",
url="https://www.douyin.com/jingxuan",
)
flow = art.end_flow(flow_dir, art.load_flow(flow_dir))
assert flow["status"] == "ended"
summary = (flow_dir / "SUMMARY.md").read_text(encoding="utf-8")
assert "surface: `browser`" in summary or "surface: `windows`" in summary
print(f"OK flow={flow_dir.name} step={name}")
print("OK ui_inspector flow smoke passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())