from __future__ import annotations from pathlib import Path from core.errors import DriverError from core.models import ActiveApp, Bounds, Scene from device.manager import DeviceManager from driver.base import Driver from perception.ocr import OCRBox from tools.describe_screen import describe_screen from tests.fakes import FakeDriver PNG_10X20 = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0a\x00\x00\x00\x14" class FakeOCREngine: def __init__(self, boxes: list[OCRBox]) -> None: self._boxes = boxes def extract(self, image: bytes | str | Path) -> list[OCRBox]: return self._boxes class TreeFailingDriver(Driver): def connect(self) -> None: return None def disconnect(self) -> None: return None def screenshot(self) -> bytes: return PNG_10X20 def tap(self, x: float, y: float) -> None: return None def long_press(self, x: float, y: float, duration_ms: int = 1200) -> None: return None def swipe( self, start_x: float, start_y: float, end_x: float, end_y: float, duration_ms: int = 500, ) -> None: return None def swipe_path( self, waypoints: list[tuple[float, float]], duration_ms: int ) -> None: return None def double_tap(self, x: float, y: float, interval_ms: int = 80) -> None: return None def input(self, text: str) -> None: return None def launch(self, app_id: str) -> None: return None def terminate(self, app_id: str) -> None: return None def tree(self): raise DriverError("ui tree retrieval failed") def home(self) -> None: return None def lock(self) -> None: return None def unlock(self) -> None: return None def test_describe_screen_degrades_to_ocr_only_when_ui_tree_unavailable() -> None: manager = DeviceManager() manager.register_device("phone-1", lambda: TreeFailingDriver()) manager.connect("phone-1") fake_box = OCRBox(text="Search", bounds=Bounds(1, 2, 4, 4), confidence=0.9) fake_engine = FakeOCREngine([fake_box]) scene = describe_screen( "phone-1", manager=manager, ocr_engine=fake_engine, # type: ignore[arg-type] ) assert [element.text for element in scene.elements] == ["Search"] assert all(element.source == "ocr" for element in scene.elements) def test_describe_screen_includes_live_active_app_metadata() -> None: active_app = ActiveApp( platform="android", package="com.example.mobile", activity=".MainActivity", ) manager = DeviceManager() manager.register_device("phone-1", lambda: FakeDriver(active_app=active_app)) manager.connect("phone-1") scene = describe_screen( "phone-1", manager=manager, ocr_engine=FakeOCREngine([]), # type: ignore[arg-type] ) assert scene.active_app == active_app assert scene.to_dict()["app"] == { "platform": "android", "package": "com.example.mobile", "activity": ".MainActivity", } assert Scene.from_dict(scene.to_dict()).active_app == active_app