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>
This commit is contained in:
2026-08-07 11:19:14 +08:00
parent 46bc573777
commit 633ecff4e2
92 changed files with 13704 additions and 288 deletions

View File

@@ -1,4 +1,4 @@
"""Smoke test for flow document API + step labeling + kit_wake helpers (v0.8.0)."""
"""Smoke test for flow document API + surface routing + kit helpers (v0.10)."""
from __future__ import annotations
@@ -11,7 +11,30 @@ 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:
@@ -23,6 +46,204 @@ def _assert_kit_wake_detect() -> None:
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": "某群"},
@@ -83,7 +304,13 @@ def _assert_label() -> None:
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)
@@ -111,7 +338,15 @@ def main() -> int:
)
assert idx == 1
assert "陈德炼" in name, name
art.write_json(step_dir / "meta.json", {"ok": True})
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,
@@ -120,9 +355,25 @@ def main() -> int:
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