from datetime import datetime from core.execution.target_resolver import TargetResolver, ResolutionContext from core.models.workflow_graph import TargetSpec from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel, ContextLevel, WindowContext, EmbeddingRef from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures def E(eid, role, bbox, label="", etype="ui"): return UIElement( element_id=eid, type=etype, role=role, bbox=bbox, center=(bbox[0]+bbox[2]//2, bbox[1]+bbox[3]//2), label=label, label_confidence=1.0, embeddings=UIElementEmbeddings(image=None, text=None), visual_features=VisualFeatures(dominant_color="n/a", has_icon=False, shape="rectangle", size_category="medium"), confidence=0.95, tags=[], metadata={} ) def S(elements, state_id="s"): return ScreenState( screen_state_id=state_id, timestamp=datetime.now(), session_id="sess", window=WindowContext(app_name="app", window_title="Login", screen_resolution=[1920,1080]), raw=RawLevel(screenshot_path="x", capture_method="test", file_size_bytes=1), perception=PerceptionLevel(embedding=EmbeddingRef(provider="p", vector_id="v", dimensions=1), detected_text=[], text_detection_method="none", confidence_avg=0.0), context=ContextLevel(), ui_elements=elements ) def test_cross_frame_cache_near_bbox_finds_new_id(): r = TargetResolver() # Frame 1: input id = inp_1 ui1 = [ E("lbl", "label", (100,100,120,20), "Username", "label"), E("inp_1", "input", (240,95,260,30), "", "text_input"), ] s1 = S(ui1, "s1") spec = TargetSpec(by_role="input", context_hints={"field_for": "Username"}) res1 = r.resolve_target(spec, s1, ResolutionContext(screen_state=s1, previous_target=None)) assert res1 is not None assert res1.element.element_id == "inp_1" # Frame 2: même position mais nouvel id = inp_X (simule perception qui renumérote) ui2 = [ E("lbl2", "label", (102,98,120,20), "Username", "label"), E("inp_X", "input", (242,96,260,30), "", "text_input"), ] s2 = S(ui2, "s2") res2 = r.resolve_target(spec, s2, ResolutionContext(screen_state=s2, previous_target=None)) assert res2 is not None assert res2.element.element_id == "inp_X" assert res2.strategy_used in {"CROSS_FRAME_CACHE", "COMPOSITE"}