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:
1
tools/ui_inspector/tests/unit/__init__.py
Normal file
1
tools/ui_inspector/tests/unit/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
tools/ui_inspector/tests/unit/core/__init__.py
Normal file
1
tools/ui_inspector/tests/unit/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from core.pipeline.event_journal import EventJournal
|
||||
from core.policies.element_identity import element_identity_key
|
||||
|
||||
|
||||
def test_journal_is_append_only(tmp_path: Path) -> None:
|
||||
journal = EventJournal(tmp_path)
|
||||
assert journal.append({"kind": "pointerdown"}) == 1
|
||||
assert journal.append({"kind": "click"}) == 2
|
||||
rows = [json.loads(line) for line in journal.path.read_text(encoding="utf-8").splitlines()]
|
||||
assert [row["journal_sequence"] for row in rows] == [1, 2]
|
||||
assert [row["event"]["kind"] for row in rows] == ["pointerdown", "click"]
|
||||
|
||||
|
||||
def test_focus_classes_and_query_strings_do_not_split_identity() -> None:
|
||||
first = {
|
||||
"role": "textbox", "tag": "input", "placeholder": "Search",
|
||||
"css": "#header input.search",
|
||||
}
|
||||
focused = {
|
||||
**first,
|
||||
"class_name": "search focused hash-123",
|
||||
"css": "#header input.search.focused.hash-123",
|
||||
}
|
||||
one = element_identity_key(first, surface="browser", page_url="https://example.com/home?a=1")
|
||||
two = element_identity_key(focused, surface="browser", page_url="https://example.com/results?q=x")
|
||||
assert one == two
|
||||
|
||||
70
tools/ui_inspector/tests/unit/core/test_target_scoring.py
Normal file
70
tools/ui_inspector/tests/unit/core/test_target_scoring.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from core.policies.target_scoring import resolve_browser_target
|
||||
|
||||
|
||||
def test_huge_control_named_ancestor_never_beats_comment_hit() -> None:
|
||||
hit = {
|
||||
"tag": "path", "role": "path", "cursor": "pointer",
|
||||
"rect": {"left": 1047, "top": 545, "right": 1070, "bottom": 568, "width": 23, "height": 23},
|
||||
"css": "#comment-input-container .send > svg > path",
|
||||
}
|
||||
huge = {
|
||||
"tag": "div", "role": "div", "class_name": "playerControlHeight",
|
||||
"rect": {"left": 160, "top": -244, "right": 1440, "bottom": 6698, "width": 1280, "height": 6942},
|
||||
"css": "#root .playerControlHeight",
|
||||
}
|
||||
target, scored = resolve_browser_target(hit, [hit, huge], x=1056, y=558)
|
||||
assert target["tag"] == "path"
|
||||
assert any("oversized_ancestor" in row["reasons"] for row in scored)
|
||||
|
||||
|
||||
def test_nearest_semantic_wrapper_beats_svg_path() -> None:
|
||||
hit = {
|
||||
"tag": "path", "role": "path", "cursor": "pointer",
|
||||
"rect": {"left": 10, "top": 10, "right": 30, "bottom": 30, "width": 20, "height": 20},
|
||||
}
|
||||
wrapper = {
|
||||
"tag": "span", "role": "span", "cursor": "pointer", "class_name": "sendControl",
|
||||
"rect": {"left": 5, "top": 5, "right": 35, "bottom": 35, "width": 30, "height": 30},
|
||||
}
|
||||
target, _ = resolve_browser_target(hit, [hit, wrapper], x=20, y=20)
|
||||
assert target["class_name"] == "sendControl"
|
||||
|
||||
|
||||
def test_contenteditable_root_beats_transient_inner_block() -> None:
|
||||
inner = {
|
||||
"tag": "div", "role": "textbox", "contenteditable": True,
|
||||
"contenteditable_attr": False,
|
||||
"rect": {"left": 10, "top": 10, "right": 300, "bottom": 35, "width": 290, "height": 25},
|
||||
}
|
||||
root = {
|
||||
"tag": "div", "role": "combobox", "explicit_role": "combobox",
|
||||
"contenteditable": True, "contenteditable_attr": True,
|
||||
"rect": {"left": 10, "top": 10, "right": 300, "bottom": 35, "width": 290, "height": 25},
|
||||
}
|
||||
target, _ = resolve_browser_target(inner, [inner, root], x=20, y=20)
|
||||
assert target["contenteditable_attr"] is True
|
||||
|
||||
|
||||
def test_unique_clickable_wrapper_beats_ambiguous_graphic_leaf() -> None:
|
||||
rect_path = {"left": 1047, "top": 345, "right": 1070, "bottom": 368, "width": 23, "height": 23}
|
||||
rect_wrapper = {"left": 1041, "top": 338, "right": 1077, "bottom": 374, "width": 36, "height": 36}
|
||||
path = {
|
||||
"tag": "path", "role": "path", "cursor": "pointer",
|
||||
"rect": rect_path, "css_count": 2,
|
||||
}
|
||||
svg = {
|
||||
"tag": "svg", "role": "svg", "cursor": "pointer",
|
||||
"rect": rect_wrapper, "css_count": 1,
|
||||
}
|
||||
wrapper = {
|
||||
"tag": "span", "role": "span", "cursor": "pointer",
|
||||
"rect": rect_wrapper, "css_count": 1,
|
||||
"stable_css": "#comment-form > div.controls > span",
|
||||
"stable_css_count": 1,
|
||||
}
|
||||
target, scored = resolve_browser_target(
|
||||
path, [path, svg, wrapper], x=1055, y=352
|
||||
)
|
||||
assert target["tag"] == "span"
|
||||
assert any("graphic_leaf" in row["reasons"] for row in scored)
|
||||
assert any("event_time_unique_locator" in row["reasons"] for row in scored)
|
||||
120
tools/ui_inspector/tests/unit/test_app_layout.py
Normal file
120
tools/ui_inspector/tests/unit/test_app_layout.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_HERE = Path(__file__).resolve().parents[2]
|
||||
if str(_HERE) not in sys.path:
|
||||
sys.path.insert(0, str(_HERE))
|
||||
|
||||
from app import (
|
||||
_DEFAULT_FLOW_NAME_SLUGS,
|
||||
_default_window_geometry,
|
||||
_flow_name_for_mode,
|
||||
_format_geometry,
|
||||
_format_live_action,
|
||||
_merge_current_live_action,
|
||||
)
|
||||
import artifacts as art
|
||||
import target
|
||||
import user_prefs
|
||||
|
||||
|
||||
def test_default_geometry_is_narrow_tall_and_leaves_title_bar_clear() -> None:
|
||||
width, height, x, y = _default_window_geometry((0, 0, 1920, 1040))
|
||||
|
||||
assert (width, height) == (576, 951)
|
||||
assert (x, y) == (1328, 73)
|
||||
assert y >= 64
|
||||
assert x + width == 1920 - 16
|
||||
|
||||
|
||||
def test_default_geometry_supports_small_and_left_side_monitors() -> None:
|
||||
width, height, x, y = _default_window_geometry(
|
||||
(-1366, 0, 0, 728), side="left"
|
||||
)
|
||||
|
||||
assert (width, height) == (560, 648)
|
||||
assert (x, y) == (-1350, 64)
|
||||
assert _format_geometry(width, height, x, y) == "560x648-1350+64"
|
||||
|
||||
|
||||
def test_flow_name_defaults_follow_pick_mode() -> None:
|
||||
assert _flow_name_for_mode(target.SURFACE_BROWSER) == "browser"
|
||||
assert _flow_name_for_mode(target.SURFACE_WINDOWS) == "desktop"
|
||||
assert _flow_name_for_mode(target.SURFACE_ANDROID) == "mobile"
|
||||
assert _DEFAULT_FLOW_NAME_SLUGS == frozenset({"browser", "desktop", "mobile"})
|
||||
|
||||
|
||||
def test_local_ts_is_compact_yyyymmddhhmmss() -> None:
|
||||
stamp = art._local_ts()
|
||||
assert len(stamp) == 14
|
||||
assert stamp.isdigit()
|
||||
assert "T" not in stamp
|
||||
assert "+" not in stamp
|
||||
|
||||
|
||||
def test_create_flow_dir_uses_compact_timestamp(tmp_path: Path) -> None:
|
||||
flow_dir = art.create_flow(tmp_path, flow_name="browser")
|
||||
name = flow_dir.name
|
||||
assert name.endswith("_browser")
|
||||
stamp = name[: -len("_browser")]
|
||||
assert len(stamp) == 14
|
||||
assert stamp.isdigit()
|
||||
|
||||
|
||||
def test_hidden_legacy_recording_preference_migrates_to_auto(
|
||||
tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
prefs_path = tmp_path / "user_prefs.json"
|
||||
prefs_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"mode": "browser",
|
||||
"start_url": "https://example.com",
|
||||
"recording_mode": "legacy_interval",
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(user_prefs, "_PREFS_PATH", prefs_path)
|
||||
|
||||
assert user_prefs.load_prefs()["recording_mode"] == "auto"
|
||||
|
||||
|
||||
def test_live_action_formatter_is_surface_neutral_and_two_phase() -> None:
|
||||
text = _format_live_action(
|
||||
{
|
||||
"sequence": 3,
|
||||
"action_type": "click",
|
||||
"phase": "captured",
|
||||
"surface": "browser",
|
||||
"target_name": "提交",
|
||||
"target_type": "button",
|
||||
}
|
||||
)
|
||||
assert "#3 点击" in text
|
||||
assert "browser" in text
|
||||
assert "证据采集中" in text
|
||||
assert "等待异步证据" in text
|
||||
|
||||
|
||||
def test_current_live_action_overwrites_without_allowing_late_evidence_to_regress() -> None:
|
||||
first = {"id": "action-1", "sequence": 1, "phase": "captured"}
|
||||
first_evidence = {"id": "action-1", "sequence": 1, "phase": "evidence"}
|
||||
second = {"id": "action-2", "sequence": 2, "phase": "captured"}
|
||||
|
||||
current = _merge_current_live_action(None, first)
|
||||
current = _merge_current_live_action(current, first_evidence)
|
||||
assert current == first_evidence
|
||||
|
||||
current = _merge_current_live_action(current, second)
|
||||
assert current == second
|
||||
|
||||
current = _merge_current_live_action(current, first_evidence)
|
||||
assert current == second
|
||||
|
||||
|
||||
def test_current_live_action_ignores_updates_without_an_action_id() -> None:
|
||||
current = {"id": "action-2", "sequence": 2, "phase": "captured"}
|
||||
|
||||
assert _merge_current_live_action(current, {"sequence": 3}) is current
|
||||
Reference in New Issue
Block a user