Files
skill-template/tools/ui_inspector/locators/base.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

75 lines
2.3 KiB
Python

"""Shared locator compiler helpers."""
from __future__ import annotations
from typing import Any, Optional
from models.locator import LocatorCandidate
def rank_and_select_primary(
candidates: list[LocatorCandidate],
) -> tuple[Optional[LocatorCandidate], list[LocatorCandidate]]:
"""
Pick production primary only if unique and not coordinate-based.
Fallback list excludes primary.
"""
ranked = sorted(
candidates,
key=lambda c: (
0 if c.production_eligible else 1,
0 if c.unique else 1,
0 if c.uses_coordinates else 0,
1 if c.uses_coordinates else 0,
1 if c.uses_index else 0,
-float(c.stability_score or 0.0),
),
)
primary: Optional[LocatorCandidate] = None
for c in ranked:
if c.production_eligible and not c.uses_coordinates:
primary = c
break
fallbacks = [c for c in ranked if c is not primary]
return primary, fallbacks
def count_matches(
rows: list[dict[str, Any]],
*,
properties: dict[str, Any],
scope: Optional[dict[str, Any]] = None,
) -> int:
"""Count rows matching structured properties (snapshot / live tree)."""
props = {k: v for k, v in (properties or {}).items() if v not in (None, "")}
if not props:
return 0
scope = scope or {}
n = 0
for r in rows or []:
if not isinstance(r, dict):
continue
if scope.get("window_class"):
# optional soft filter if row carries window info
rc = r.get("window_class") or r.get("window_class_name")
if rc and rc != scope.get("window_class"):
continue
ok = True
for k, v in props.items():
if k == "control_type":
rv = r.get("control_type") or r.get("role") or ""
elif k == "role":
rv = r.get("role") or r.get("control_type") or ""
elif k == "text":
rv = r.get("text") or r.get("value") or r.get("name") or ""
elif k == "test_id":
rv = r.get("test_id") or r.get("automation_id") or ""
else:
rv = r.get(k)
if str(rv or "") != str(v):
ok = False
break
if ok:
n += 1
return n