mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-28 00:22:41 -08:00
update
This commit is contained in:
180
.agents/skills/ios-simulator-skill/scripts/common/idb_utils.py
Normal file
180
.agents/skills/ios-simulator-skill/scripts/common/idb_utils.py
Normal file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Shared IDB utility functions.
|
||||
|
||||
This module provides common IDB operations used across multiple scripts.
|
||||
Follows Jackson's Law - only shared code that's truly reused, not speculative.
|
||||
|
||||
Used by:
|
||||
- navigator.py - Accessibility tree navigation
|
||||
- screen_mapper.py - UI element analysis
|
||||
- accessibility_audit.py - WCAG compliance checking
|
||||
- test_recorder.py - Test documentation
|
||||
- app_state_capture.py - State snapshots
|
||||
- gesture.py - Touch gesture operations
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def get_accessibility_tree(udid: str | None = None, nested: bool = True) -> dict:
|
||||
"""
|
||||
Fetch accessibility tree from IDB.
|
||||
|
||||
The accessibility tree represents the complete UI hierarchy of the current
|
||||
screen, with all element properties needed for semantic navigation.
|
||||
|
||||
Args:
|
||||
udid: Device UDID (uses booted simulator if None)
|
||||
nested: Include nested structure (default True). If False, returns flat array.
|
||||
|
||||
Returns:
|
||||
Root element of accessibility tree as dict.
|
||||
Structure: {
|
||||
"type": "Window",
|
||||
"AXLabel": "App Name",
|
||||
"frame": {"x": 0, "y": 0, "width": 390, "height": 844},
|
||||
"children": [...]
|
||||
}
|
||||
|
||||
Raises:
|
||||
SystemExit: If IDB command fails or returns invalid JSON
|
||||
|
||||
Example:
|
||||
tree = get_accessibility_tree("UDID123")
|
||||
# Root is Window element with all children nested
|
||||
"""
|
||||
cmd = ["idb", "ui", "describe-all", "--json"]
|
||||
if nested:
|
||||
cmd.append("--nested")
|
||||
if udid:
|
||||
cmd.extend(["--udid", udid])
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
tree_data = json.loads(result.stdout)
|
||||
|
||||
# IDB returns array format, extract first element (root)
|
||||
if isinstance(tree_data, list) and len(tree_data) > 0:
|
||||
return tree_data[0]
|
||||
return tree_data
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error: Failed to get accessibility tree: {e.stderr}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print("Error: Invalid JSON from idb", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def flatten_tree(node: dict, depth: int = 0, elements: list[dict] | None = None) -> list[dict]:
|
||||
"""
|
||||
Flatten nested accessibility tree into list of elements.
|
||||
|
||||
Converts the hierarchical accessibility tree into a flat list where each
|
||||
element includes its depth for context.
|
||||
|
||||
Used by:
|
||||
- navigator.py - Element finding
|
||||
- screen_mapper.py - Element analysis
|
||||
- accessibility_audit.py - Audit scanning
|
||||
|
||||
Args:
|
||||
node: Root node of tree (typically from get_accessibility_tree)
|
||||
depth: Current depth (used internally, start at 0)
|
||||
elements: Accumulator list (used internally, start as None)
|
||||
|
||||
Returns:
|
||||
Flat list of elements, each with "depth" key indicating nesting level.
|
||||
Structure of each element: {
|
||||
"type": "Button",
|
||||
"AXLabel": "Login",
|
||||
"frame": {...},
|
||||
"depth": 2,
|
||||
...
|
||||
}
|
||||
|
||||
Example:
|
||||
tree = get_accessibility_tree()
|
||||
flat = flatten_tree(tree)
|
||||
for elem in flat:
|
||||
print(f"{' ' * elem['depth']}{elem.get('type')}: {elem.get('AXLabel')}")
|
||||
"""
|
||||
if elements is None:
|
||||
elements = []
|
||||
|
||||
# Add current node with depth tracking
|
||||
node_copy = node.copy()
|
||||
node_copy["depth"] = depth
|
||||
elements.append(node_copy)
|
||||
|
||||
# Process children recursively
|
||||
for child in node.get("children", []):
|
||||
flatten_tree(child, depth + 1, elements)
|
||||
|
||||
return elements
|
||||
|
||||
|
||||
def count_elements(node: dict) -> int:
|
||||
"""
|
||||
Count total elements in tree (recursive).
|
||||
|
||||
Traverses entire tree counting all elements for reporting purposes.
|
||||
|
||||
Used by:
|
||||
- test_recorder.py - Element counting per step
|
||||
- screen_mapper.py - Summary statistics
|
||||
|
||||
Args:
|
||||
node: Root node of tree
|
||||
|
||||
Returns:
|
||||
Total element count including root and all descendants
|
||||
|
||||
Example:
|
||||
tree = get_accessibility_tree()
|
||||
total = count_elements(tree)
|
||||
print(f"Screen has {total} elements")
|
||||
"""
|
||||
count = 1
|
||||
for child in node.get("children", []):
|
||||
count += count_elements(child)
|
||||
return count
|
||||
|
||||
|
||||
def get_screen_size(udid: str | None = None) -> tuple[int, int]:
|
||||
"""
|
||||
Get screen dimensions from accessibility tree.
|
||||
|
||||
Extracts the screen size from the root element's frame. Useful for
|
||||
gesture calculations and coordinate normalization.
|
||||
|
||||
Used by:
|
||||
- gesture.py - Gesture positioning
|
||||
- Potentially: screenshot positioning, screen-aware scaling
|
||||
|
||||
Args:
|
||||
udid: Device UDID (uses booted if None)
|
||||
|
||||
Returns:
|
||||
(width, height) tuple. Defaults to (390, 844) if detection fails
|
||||
or tree cannot be accessed.
|
||||
|
||||
Example:
|
||||
width, height = get_screen_size()
|
||||
center_x = width // 2
|
||||
center_y = height // 2
|
||||
"""
|
||||
DEFAULT_WIDTH = 390 # iPhone 14
|
||||
DEFAULT_HEIGHT = 844
|
||||
|
||||
try:
|
||||
tree = get_accessibility_tree(udid, nested=False)
|
||||
frame = tree.get("frame", {})
|
||||
width = int(frame.get("width", DEFAULT_WIDTH))
|
||||
height = int(frame.get("height", DEFAULT_HEIGHT))
|
||||
return (width, height)
|
||||
except Exception:
|
||||
# Silently fall back to defaults if tree access fails
|
||||
return (DEFAULT_WIDTH, DEFAULT_HEIGHT)
|
||||
Reference in New Issue
Block a user