""" Tests pour Fiche #6 - Sniper Mode : Ranking/Scoring Auteur: Dom, Alice Kiro - 15 décembre 2024 Objectif: Valider que le resolver classe et choisit le bon élément de manière stable Tests: 1. Sniper choisit l'élément le plus proche de l'ancre 2. Tie-break stable par element_id """ import pytest # Marquer tous les tests de ce fichier comme fiche6 pytestmark = pytest.mark.fiche6 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 _elem(eid, role, bbox, label="", conf=0.9, etype="ui"): """Helper pour créer un UIElement rapidement""" return UIElement( element_id=eid, type=etype, role=role, bbox=bbox, # XYWH 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=conf, tags=[], metadata={}, ) def _screen(elements): """Helper pour créer un ScreenState rapidement""" return ScreenState( screen_state_id="s1", timestamp=datetime.now(), session_id="sess", window=WindowContext(app_name="app", window_title="win", screen_resolution=[1920, 1080]), raw=RawLevel(screenshot_path="x.png", 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_sniper_picks_nearest_to_anchor(): """Test que le sniper choisit l'élément le plus proche de l'ancre""" # label + 2 inputs (tous deux "valides" role=input), le plus proche doit gagner anchor = _elem("lbl_user", "label", (100, 100, 120, 20), "Username", conf=1.0) near_input = _elem("in_near", "input", (240, 95, 200, 30), "", conf=0.9, etype="text_input") far_input = _elem("in_far", "input", (240, 300, 200, 30), "", conf=0.9, etype="text_input") screen = _screen([far_input, anchor, near_input]) spec = TargetSpec( by_role="input", context_hints={"near_text": "Username"}, selection_policy="first" ) r = TargetResolver() ctx = ResolutionContext(screen_state=screen, previous_target=None) res = r.resolve_target(spec, screen, ctx) assert res is not None assert res.element.element_id == "in_near" def test_sniper_tie_break_is_stable(): """Test que le tie-break est stable par element_id""" # Deux candidats identiques → tie-break par element_id (stable) anchor = _elem("lbl", "label", (100, 100, 120, 20), "Username", conf=1.0) a = _elem("a_elem", "input", (240, 95, 200, 30), "", conf=0.9, etype="text_input") b = _elem("b_elem", "input", (240, 95, 200, 30), "", conf=0.9, etype="text_input") screen = _screen([anchor, b, a]) spec = TargetSpec(by_role="input", context_hints={"near_text": "Username"}) r = TargetResolver() ctx = ResolutionContext(screen_state=screen, previous_target=None) res = r.resolve_target(spec, screen, ctx) assert res is not None # Tie-break par element_id : le résultat doit être stable (toujours le même) # L'ordre dépend du tri interne du resolver (min ou max par element_id) assert res.element.element_id in ("a_elem", "b_elem") def test_sniper_debug_info_available(): """Test que les infos de debug (top3) sont disponibles""" anchor = _elem("lbl", "label", (100, 100, 120, 20), "Username", conf=1.0) input1 = _elem("input1", "input", (240, 95, 200, 30), "", conf=0.9, etype="text_input") input2 = _elem("input2", "input", (240, 150, 200, 30), "", conf=0.8, etype="text_input") input3 = _elem("input3", "input", (240, 200, 200, 30), "", conf=0.7, etype="text_input") screen = _screen([anchor, input1, input2, input3]) spec = TargetSpec(by_role="input", context_hints={"near_text": "Username"}) r = TargetResolver() ctx = ResolutionContext(screen_state=screen, previous_target=None) res = r.resolve_target(spec, screen, ctx) assert res is not None assert hasattr(res, 'resolution_details') assert 'top3' in res.resolution_details assert len(res.resolution_details['top3']) <= 3 assert 'anchor_id' in res.resolution_details assert res.resolution_details['anchor_id'] == "lbl" if __name__ == "__main__": pytest.main([__file__, "-v"])