← Files Test Android AppsARCHIVED FILE
skills/android-emulator-qa/scripts/ui_tree_summarize.py
3.24 KB · Oct 2, 2026 · 00:37 UTC
#!/usr/bin/env python3
import re
import sys
import xml.etree.ElementTree as ET
INTERACTIVE_ATTRS = ("clickable", "long-clickable", "scrollable", "focusable")
STATE_ATTRS = ("checked", "selected")
DISPLAY_ATTRS = ("text", "content-desc", "resource-id")
MAX_DEPTH = 20
def clean_text(value):
if not value:
return ""
return re.sub(r"\s+", " ", value).strip()
def is_true(value):
return value == "true"
def simplify_class(value):
if not value:
return ""
return value.split(".")[-1]
def simplify_resource_id(value):
if not value:
return ""
if ":id/" in value:
prefix, rest = value.split(":id/", 1)
if prefix and prefix != "android":
return "id/" + rest
return value
def extract_labels(node):
text = clean_text(node.attrib.get("text", ""))
desc = clean_text(node.attrib.get("content-desc", ""))
resource_id = simplify_resource_id(node.attrib.get("resource-id", ""))
return text, desc, resource_id
def is_interactive(node):
for key in INTERACTIVE_ATTRS:
if is_true(node.attrib.get(key, "false")):
return True
return False
def has_display(node):
text, desc, resource_id = extract_labels(node)
return bool(text or desc or resource_id)
def keep_node(node):
return has_display(node) or is_interactive(node) or is_true(node.attrib.get("scrollable", "false"))
def format_node(node):
class_name = simplify_class(node.attrib.get("class", ""))
text, desc, resource_id = extract_labels(node)
parts = [class_name]
if resource_id:
parts.append(f"id={resource_id}")
if text:
parts.append(f'text="{text}"')
if desc:
parts.append(f'desc="{desc}"')
flags = []
for key in INTERACTIVE_ATTRS:
if is_true(node.attrib.get(key, "false")):
flags.append(key)
for key in STATE_ATTRS:
if is_true(node.attrib.get(key, "false")):
flags.append(key)
if flags:
parts.append("flags=" + ",".join(flags))
bounds = node.attrib.get("bounds", "")
if bounds and (is_interactive(node) or has_display(node)):
parts.append(f"bounds={bounds}")
return " ".join(parts)
def build_lines(node, depth):
if depth > MAX_DEPTH:
return []
include = keep_node(node)
child_depth = depth + 1 if include else depth
lines = []
if include:
lines.append((" " * depth) + format_node(node))
for child in node:
lines.extend(build_lines(child, child_depth))
return lines
def main():
if len(sys.argv) != 3:
raise SystemExit("usage: ui_tree_summarize.py <input.xml> <output.txt>")
input_path = sys.argv[1]
output_path = sys.argv[2]
with open(input_path, "r", encoding="utf-8") as handle:
xml_text = handle.read()
end_marker = "</hierarchy>"
end_index = xml_text.rfind(end_marker)
if end_index == -1:
raise SystemExit("hierarchy end tag not found")
xml_text = xml_text[: end_index + len(end_marker)]
root = ET.fromstring(xml_text)
lines = []
for child in root:
lines.extend(build_lines(child, 0))
with open(output_path, "w", encoding="utf-8") as handle:
handle.write("\n".join(lines))
handle.write("\n")
if __name__ == "__main__":
main()
SHA-256: 9f3cb05ff7c67a5efa1bf29020a16b85dfb61afbac9fbe1c2dc775e01c45443b