Files
skill-template/tools/ui_inspector/tests/contract/test_capture_contract.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

208 lines
8.5 KiB
Python

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"