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:
74
tools/ui_inspector/locators/base.py
Normal file
74
tools/ui_inspector/locators/base.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user