v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution
- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
125
tests/unit/test_target_resolver_sniper_ranking.py
Normal file
125
tests/unit/test_target_resolver_sniper_ranking.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
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
|
||||
assert res.element.element_id == "b_elem" # max() with tie_key uses element_id as last key
|
||||
|
||||
|
||||
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"])
|
||||
Reference in New Issue
Block a user