Files
Artemis-Eapil/artemis/utils/input_state.py
T
chenmingxuan be6e8c2abd feat: release 2.2.0 with PC-only tests and execution reliability fixes
Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
2026-10-27 01:13:41 +08:00

88 lines
3.1 KiB
Python

"""Keep editable values separate from accessibility hints and visual labels."""
from typing import Any
def _boolean(node: dict, *keys: str) -> bool | None:
for key in keys:
value = node.get(key)
if isinstance(value, bool):
return value
if isinstance(value, str) and value.lower() in {"true", "false"}:
return value.lower() == "true"
return None
def input_state(node: dict[str, Any]) -> dict[str, Any]:
"""Return value evidence only for an actual editable node, never OCR text.
Equality with a hint is ambiguous on older backends: an application can
legitimately contain that exact value. Only an explicit showing-hint flag
proves that nonempty text is a placeholder.
"""
editable = _boolean(node, "editable", "isEditable")
class_name = str(node.get("class") or node.get("className") or "").lower()
if editable is None and "edittext" in class_name:
editable = True
if editable is not True:
return {}
hint = str(node.get("hint") or node.get("hintText") or node.get("hint-text") or "")
showing = _boolean(node, "showing_hint", "showing-hint-text", "isShowingHintText", "is_hint")
raw = node.get("text")
value = raw if isinstance(raw, str) else None
if showing is True:
hint = hint or value or ""
value = ""
status = "empty"
elif value is None:
status = "unknown"
elif not value:
status = "empty"
elif hint and value == hint and showing is not False:
value = None
status = "unknown"
else:
status = "present"
return {
"editable": True,
"input_value": value,
"input_value_state": status,
"hint": hint,
"showing_hint": showing,
"focused": _boolean(node, "focused"),
"password": _boolean(node, "password"),
}
def expected_input_state(text: str, clear_existing: bool) -> str | None:
"""Empty replacement means clear; empty append has no value postcondition."""
if text:
return "present"
return "empty" if clear_existing else None
def verify_input(elements: list[dict], target: dict | None = None) -> str:
"""Verify value presence against a fresh tree, without returning its value.
Resource identity wins over focus. Unidentifiable, masked, or unsupported
inputs remain unknown; visual text elsewhere cannot establish input success.
"""
candidates = []
target = target or {}
resource_id = target.get("target_resource_id")
for element in elements:
state = input_state(element) if "input_value_state" not in element else element
if not state:
continue
if resource_id:
if (element.get("resource_id") or element.get("resource-id")) == resource_id:
candidates.append(state)
elif state.get("focused") is True:
candidates.append(state)
if len(candidates) != 1:
return "unknown"
state = candidates[0]
if state.get("password") is True and state.get("input_value_state") != "empty":
return "unknown"
return str(state["input_value_state"])