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.
88 lines
3.1 KiB
Python
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"])
|