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/contract/__init__.py
Normal file
1
tools/ui_inspector/tests/contract/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
207
tools/ui_inspector/tests/contract/test_capture_contract.py
Normal file
207
tools/ui_inspector/tests/contract/test_capture_contract.py
Normal file
@@ -0,0 +1,207 @@
|
||||
from pathlib import Path
|
||||
|
||||
from flow_session import FlowSession
|
||||
from recorder.base import RawUserEvent
|
||||
from diagnostics.capture_validator import validate_capture
|
||||
from diagnostics.replay_preflight import build_replay_preflight
|
||||
|
||||
|
||||
def test_session_always_creates_raw_event_journal(tmp_path: Path) -> None:
|
||||
session = FlowSession(tmp_path, flow_name="contract", surface="browser", dump_fn=None)
|
||||
session.manager.start()
|
||||
session.manager.status = "recording"
|
||||
event = RawUserEvent(kind="click", surface="browser", target={"role": "button", "name": "Go"})
|
||||
session.journal.append(event, channel="semantic")
|
||||
assert (tmp_path / "journal" / "raw_events.jsonl").exists()
|
||||
|
||||
|
||||
def test_validator_flags_oversized_click_target(tmp_path: Path) -> None:
|
||||
import json
|
||||
actions = tmp_path / "actions"
|
||||
actions.mkdir()
|
||||
(tmp_path / "elements").mkdir()
|
||||
(tmp_path / "observations").mkdir()
|
||||
(tmp_path / "journal").mkdir()
|
||||
(tmp_path / "journal" / "raw_events.jsonl").write_text("{}\n", encoding="utf-8")
|
||||
(actions / "a.json").write_text(
|
||||
json.dumps({
|
||||
"id": "a", "sequence": 1, "action_type": "click",
|
||||
"target_snapshot": {"rect": {"width": 1200, "height": 5000}},
|
||||
"evidence": {"eventual_observation_id": "obs"},
|
||||
}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = validate_capture(tmp_path)
|
||||
assert any(issue["code"] == "CLICK_TARGET_OVERSIZED" for issue in result["issues"])
|
||||
|
||||
|
||||
def test_validator_flags_missing_desktop_input_and_generic_targets(tmp_path: Path) -> None:
|
||||
import json
|
||||
|
||||
(tmp_path / "actions").mkdir()
|
||||
(tmp_path / "elements").mkdir()
|
||||
(tmp_path / "observations").mkdir()
|
||||
(tmp_path / "journal").mkdir()
|
||||
journal = {
|
||||
"event": {
|
||||
"kind": "typing_activity", "surface": "windows",
|
||||
"target": {"name": "searchedit", "control_type": "TreeControl"},
|
||||
}
|
||||
}
|
||||
(tmp_path / "journal" / "raw_events.jsonl").write_text(
|
||||
json.dumps(journal) + "\n", encoding="utf-8"
|
||||
)
|
||||
rows = [
|
||||
{
|
||||
"id": "a1", "sequence": 1, "surface": "windows", "action_type": "click",
|
||||
"target_snapshot": {
|
||||
"name": "search_result", "control_type": "TreeControl",
|
||||
"bounds": {"width": 600, "height": 500},
|
||||
},
|
||||
"evidence": {"eventual_observation_id": "obs"},
|
||||
},
|
||||
{
|
||||
"id": "a2", "sequence": 2, "surface": "windows", "action_type": "press_key",
|
||||
"target_snapshot": {
|
||||
"name": "WeCom", "control_type": "WindowControl",
|
||||
"bounds": {"width": 1440, "height": 900},
|
||||
},
|
||||
"input": {"key": "Enter"},
|
||||
"evidence": {"eventual_observation_id": "obs"},
|
||||
},
|
||||
]
|
||||
for row in rows:
|
||||
(tmp_path / "actions" / f"{row['id']}.json").write_text(
|
||||
json.dumps(row), encoding="utf-8"
|
||||
)
|
||||
codes = {issue["code"] for issue in validate_capture(tmp_path)["issues"]}
|
||||
assert "CLICK_TARGET_GENERIC_CONTAINER" in codes
|
||||
assert "KEY_TARGET_ROOT_WINDOW" in codes
|
||||
assert "TEXT_INPUT_TRANSACTION_MISSING" in codes
|
||||
replay = build_replay_preflight(tmp_path)
|
||||
assert replay["ready"] is False
|
||||
assert replay["quality_gate"]["passed"] is False
|
||||
assert replay["summary"]["quality_blockers"] >= 1
|
||||
|
||||
|
||||
def test_validator_flags_missing_production_locator_and_raw_link(tmp_path: Path) -> None:
|
||||
import json
|
||||
|
||||
actions = tmp_path / "actions"
|
||||
elements = tmp_path / "elements"
|
||||
actions.mkdir()
|
||||
elements.mkdir()
|
||||
(tmp_path / "observations").mkdir()
|
||||
(tmp_path / "journal").mkdir()
|
||||
(tmp_path / "journal" / "raw_events.jsonl").write_text("{}\n", encoding="utf-8")
|
||||
(elements / "el.json").write_text(
|
||||
json.dumps({"id": "el", "primary_locator": None}), encoding="utf-8"
|
||||
)
|
||||
(actions / "a.json").write_text(
|
||||
json.dumps({
|
||||
"id": "a", "sequence": 1, "surface": "browser",
|
||||
"action_type": "click", "target_element_id": "el",
|
||||
"target_snapshot": {"role": "link", "rect": {"width": 20, "height": 20}},
|
||||
"raw_event": {}, "evidence": {"eventual_observation_id": "obs"},
|
||||
}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = validate_capture(tmp_path)
|
||||
codes = {issue["code"] for issue in result["issues"]}
|
||||
assert "ACTION_PRODUCTION_LOCATOR_MISSING" in codes
|
||||
assert "ACTION_RAW_EVENT_LINK_MISSING" in codes
|
||||
|
||||
|
||||
def test_validator_flags_graphic_leaf_low_stability_and_split_fill(tmp_path: Path) -> None:
|
||||
import json
|
||||
|
||||
actions = tmp_path / "actions"
|
||||
elements = tmp_path / "elements"
|
||||
actions.mkdir()
|
||||
elements.mkdir()
|
||||
(tmp_path / "observations").mkdir()
|
||||
(tmp_path / "journal").mkdir()
|
||||
(tmp_path / "journal" / "raw_events.jsonl").write_text("{}\n", encoding="utf-8")
|
||||
locator = {
|
||||
"strategy": "css", "expression": "css:.hash", "actual_count": 1,
|
||||
"unique": True, "production_eligible": True, "stability_score": 0.32,
|
||||
"validation": "live",
|
||||
}
|
||||
(elements / "el.json").write_text(
|
||||
json.dumps({"id": "el", "primary_locator": locator}), encoding="utf-8"
|
||||
)
|
||||
rows = [
|
||||
{
|
||||
"id": "a1", "sequence": 1, "surface": "browser", "action_type": "fill",
|
||||
"target_element_id": "el", "target_snapshot": {"tag": "input"},
|
||||
"input": {"text": "a"},
|
||||
"raw_event": {"meta": {"event_id": "doc:1", "source_ts_ms": 1000}},
|
||||
"evidence": {"eventual_observation_id": "obs", "locator_unique": True},
|
||||
},
|
||||
{
|
||||
"id": "a2", "sequence": 2, "surface": "browser", "action_type": "fill",
|
||||
"target_element_id": "el", "target_snapshot": {"tag": "input"},
|
||||
"input": {"text": "ai"},
|
||||
"raw_event": {"meta": {"event_id": "doc:2", "source_ts_ms": 1100}},
|
||||
"evidence": {"eventual_observation_id": "obs", "locator_unique": True},
|
||||
},
|
||||
{
|
||||
"id": "a3", "sequence": 3, "surface": "browser", "action_type": "click",
|
||||
"target_element_id": "el", "target_snapshot": {"tag": "path"},
|
||||
"raw_event": {"meta": {"event_id": "doc:3"}},
|
||||
"evidence": {"eventual_observation_id": "obs", "locator_unique": True},
|
||||
},
|
||||
]
|
||||
for row in rows:
|
||||
(actions / f"{row['id']}.json").write_text(json.dumps(row), encoding="utf-8")
|
||||
codes = {issue["code"] for issue in validate_capture(tmp_path)["issues"]}
|
||||
assert "DUPLICATE_FILL_TRANSACTION" in codes
|
||||
assert "GRAPHIC_LEAF_TARGET" in codes
|
||||
assert "LOW_STABILITY_LOCATOR" in codes
|
||||
|
||||
|
||||
def test_replay_preflight_is_honest_about_event_time_validation(tmp_path: Path) -> None:
|
||||
import json
|
||||
|
||||
(tmp_path / "actions").mkdir()
|
||||
(tmp_path / "elements").mkdir()
|
||||
(tmp_path / "actions" / "a.json").write_text(json.dumps({
|
||||
"id": "a", "sequence": 1, "action_type": "click",
|
||||
"target_element_id": "el", "raw_event_ids": ["doc:1"],
|
||||
"raw_event": {"meta": {"page_id": "page", "document_id": "doc"}},
|
||||
}), encoding="utf-8")
|
||||
(tmp_path / "elements" / "el.json").write_text(json.dumps({
|
||||
"id": "el", "logical_name": "go", "primary_locator": {
|
||||
"strategy": "role", "expression": "role:button,Go",
|
||||
"actual_count": 1, "unique": True, "production_eligible": True,
|
||||
"stability_score": 0.95, "validation": "live",
|
||||
},
|
||||
}), encoding="utf-8")
|
||||
report = build_replay_preflight(tmp_path)
|
||||
assert report["mode"] == "static_non_mutating"
|
||||
assert report["summary"]["pass"] == 1
|
||||
assert report["actions"][0]["verification"] == "event_time_unique"
|
||||
|
||||
|
||||
def test_replay_preflight_rejects_unique_root_window_click(tmp_path: Path) -> None:
|
||||
import json
|
||||
|
||||
(tmp_path / "actions").mkdir()
|
||||
(tmp_path / "elements").mkdir()
|
||||
(tmp_path / "actions" / "a.json").write_text(json.dumps({
|
||||
"id": "a", "sequence": 1, "surface": "windows", "action_type": "click",
|
||||
"target_element_id": "root", "target_snapshot": {
|
||||
"control_type": "WindowControl", "name": "WeCom",
|
||||
"bounds": {"width": 1440, "height": 900},
|
||||
},
|
||||
}), encoding="utf-8")
|
||||
(tmp_path / "elements" / "root.json").write_text(json.dumps({
|
||||
"id": "root", "logical_name": "WeCom", "primary_locator": {
|
||||
"strategy": "window_name_type", "expression": "window:WeCom",
|
||||
"actual_count": 1, "unique": True, "production_eligible": True,
|
||||
"stability_score": 0.9, "validation": "snapshot_only",
|
||||
},
|
||||
}), encoding="utf-8")
|
||||
report = build_replay_preflight(tmp_path)
|
||||
assert report["summary"]["fail"] == 1
|
||||
assert report["actions"][0]["reason"] == "oversized_or_root_window_click_target"
|
||||
Reference in New Issue
Block a user