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>
75 lines
2.3 KiB
Python
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
|