- 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>
309 lines
12 KiB
Python
309 lines
12 KiB
Python
"""
|
|
Tests pour Fiche #3 - Context Hints dans Résolution Composite
|
|
|
|
Auteur: Dom, Alice Kiro - 15 décembre 2024
|
|
Objectif: Valider que context_hints est maintenant pris en compte dans la résolution composite
|
|
"""
|
|
import pytest
|
|
from unittest.mock import Mock, MagicMock
|
|
from dataclasses import dataclass
|
|
from typing import Dict, Any, Optional
|
|
|
|
from core.execution.target_resolver import TargetResolver, ResolvedTarget, ResolutionStrategy
|
|
from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures
|
|
from core.models.screen_state import ScreenState
|
|
|
|
|
|
@dataclass
|
|
class MockTargetSpec:
|
|
"""Mock TargetSpec pour les tests"""
|
|
by_role: Optional[str] = None
|
|
by_text: Optional[str] = None
|
|
by_position: Optional[tuple] = None
|
|
context_hints: Optional[Dict[str, Any]] = None
|
|
selection_policy: Optional[str] = "first"
|
|
|
|
|
|
class TestTargetResolverCompositeHints:
|
|
"""Tests pour la Fiche #3 - Context Hints dans résolution composite"""
|
|
|
|
def setup_method(self):
|
|
"""Setup pour chaque test"""
|
|
self.resolver = TargetResolver()
|
|
|
|
# Créer des embeddings et features par défaut
|
|
default_embeddings = UIElementEmbeddings()
|
|
default_visual_features = VisualFeatures(
|
|
dominant_color="#ffffff",
|
|
has_icon=False,
|
|
shape="rectangle",
|
|
size_category="medium"
|
|
)
|
|
|
|
# Créer des éléments UI de test
|
|
self.username_label = UIElement(
|
|
element_id="username_label",
|
|
type="label",
|
|
role="label",
|
|
bbox=(100, 100, 80, 20), # (x, y, w, h)
|
|
center=(140, 110),
|
|
label="Username",
|
|
label_confidence=0.9,
|
|
embeddings=default_embeddings,
|
|
visual_features=default_visual_features,
|
|
confidence=0.9
|
|
)
|
|
|
|
self.username_input = UIElement(
|
|
element_id="username_input",
|
|
type="text_input",
|
|
role="form_input",
|
|
bbox=(100, 130, 200, 30), # En dessous du label
|
|
center=(200, 145),
|
|
label="",
|
|
label_confidence=0.8,
|
|
embeddings=default_embeddings,
|
|
visual_features=default_visual_features,
|
|
confidence=0.95
|
|
)
|
|
|
|
self.password_input = UIElement(
|
|
element_id="password_input",
|
|
type="text_input",
|
|
role="form_input",
|
|
bbox=(100, 180, 200, 30), # Plus bas
|
|
center=(200, 195),
|
|
label="",
|
|
label_confidence=0.8,
|
|
embeddings=default_embeddings,
|
|
visual_features=default_visual_features,
|
|
confidence=0.95
|
|
)
|
|
|
|
self.submit_button = UIElement(
|
|
element_id="submit_button",
|
|
type="button",
|
|
role="primary_action",
|
|
bbox=(320, 130, 80, 30), # À droite de l'input
|
|
center=(360, 145),
|
|
label="Submit",
|
|
label_confidence=0.9,
|
|
embeddings=default_embeddings,
|
|
visual_features=default_visual_features,
|
|
confidence=0.9
|
|
)
|
|
|
|
self.ui_elements = [
|
|
self.username_label,
|
|
self.username_input,
|
|
self.password_input,
|
|
self.submit_button
|
|
]
|
|
|
|
# Mock screen state
|
|
self.screen_state = Mock(spec=ScreenState)
|
|
self.screen_state.ui_elements = self.ui_elements
|
|
self.screen_state.screen_state_id = "test_screen"
|
|
|
|
def test_fiche3_context_hints_triggers_composite_mode(self):
|
|
"""
|
|
Test Fiche #3: Vérifier que context_hints déclenche le mode composite
|
|
|
|
Avant: by_role="input" + context_hints ne déclenchait pas composite
|
|
Après: by_role="input" + context_hints déclenche composite
|
|
"""
|
|
# Spec avec role + context_hints (devrait déclencher composite)
|
|
target_spec = MockTargetSpec(
|
|
by_role="text_input",
|
|
context_hints={"below_text": "Username"}
|
|
)
|
|
|
|
# Vérifier que c'est maintenant considéré comme composite
|
|
is_composite = self.resolver._is_composite_spec(target_spec)
|
|
assert is_composite, "by_role + context_hints devrait déclencher le mode composite"
|
|
|
|
def test_fiche3_composite_resolution_with_context_hints(self):
|
|
"""
|
|
Test Fiche #3: Résolution composite avec context_hints
|
|
|
|
Doit trouver l'input qui est en dessous du label "Username"
|
|
"""
|
|
target_spec = MockTargetSpec(
|
|
by_role="text_input",
|
|
context_hints={"below_text": "Username"}
|
|
)
|
|
|
|
# Mock de la méthode _get_ui_elements
|
|
self.resolver._get_ui_elements = Mock(return_value=self.ui_elements)
|
|
|
|
# Résoudre
|
|
result = self.resolver.resolve_target(target_spec, self.screen_state)
|
|
|
|
# Vérifications
|
|
assert result is not None, "Devrait trouver un élément"
|
|
assert result.element.element_id == "username_input", f"Devrait trouver username_input, trouvé: {result.element.element_id}"
|
|
assert result.strategy_used == ResolutionStrategy.COMPOSITE.value, "Devrait utiliser la stratégie composite"
|
|
|
|
# Vérifier les détails de résolution
|
|
details = result.resolution_details
|
|
assert "context_hints" in details["criteria_used"], "context_hints devrait être dans criteria_used"
|
|
assert details["criteria_used"]["context_hints"]["below_text"] == "Username"
|
|
|
|
def test_fiche3_context_hints_below_text_filtering(self):
|
|
"""
|
|
Test du filtrage below_text dans _apply_context_hints_to_candidates
|
|
"""
|
|
candidates = [self.username_input, self.password_input] # Les deux inputs
|
|
context_hints = {"below_text": "Username"}
|
|
scores = {elem.element_id: 1.0 for elem in candidates}
|
|
|
|
# Appliquer les context hints
|
|
filtered = self.resolver._apply_context_hints_to_candidates(
|
|
candidates, context_hints, self.ui_elements, scores
|
|
)
|
|
|
|
# Vérifications
|
|
assert len(filtered) == 2, f"Devrait garder les 2 inputs (tous en dessous), trouvé: {len(filtered)}"
|
|
assert self.username_input in filtered, "username_input devrait être gardé"
|
|
assert self.password_input in filtered, "password_input devrait être gardé"
|
|
|
|
def test_fiche3_context_hints_right_of_text_filtering(self):
|
|
"""
|
|
Test du filtrage right_of_text
|
|
"""
|
|
candidates = [self.username_input, self.submit_button]
|
|
context_hints = {"right_of_text": "Username"}
|
|
scores = {elem.element_id: 1.0 for elem in candidates}
|
|
|
|
# Appliquer les context hints
|
|
filtered = self.resolver._apply_context_hints_to_candidates(
|
|
candidates, context_hints, self.ui_elements, scores
|
|
)
|
|
|
|
# Le submit button est à droite du label Username
|
|
assert len(filtered) == 1, f"Devrait garder 1 élément, trouvé: {len(filtered)}"
|
|
assert self.submit_button in filtered, "submit_button devrait être gardé (à droite)"
|
|
|
|
def test_fiche3_context_hints_near_text_filtering(self):
|
|
"""
|
|
Test du filtrage near_text avec distance
|
|
"""
|
|
candidates = [self.username_input, self.password_input]
|
|
context_hints = {"near_text": "Username", "max_distance": 100}
|
|
scores = {elem.element_id: 1.0 for elem in candidates}
|
|
|
|
# Appliquer les context hints
|
|
filtered = self.resolver._apply_context_hints_to_candidates(
|
|
candidates, context_hints, self.ui_elements, scores
|
|
)
|
|
|
|
# username_input est plus proche que password_input
|
|
assert len(filtered) == 1, f"Devrait garder 1 élément proche, trouvé: {len(filtered)}"
|
|
assert self.username_input in filtered, "username_input devrait être gardé (plus proche)"
|
|
|
|
def test_fiche3_cache_key_includes_context_hints(self):
|
|
"""
|
|
Test que la clé de cache inclut maintenant context_hints
|
|
"""
|
|
target_spec1 = MockTargetSpec(
|
|
by_role="text_input",
|
|
context_hints={"below_text": "Username"}
|
|
)
|
|
|
|
target_spec2 = MockTargetSpec(
|
|
by_role="text_input",
|
|
context_hints={"below_text": "Password"}
|
|
)
|
|
|
|
target_spec3 = MockTargetSpec(
|
|
by_role="text_input"
|
|
# Pas de context_hints
|
|
)
|
|
|
|
# Générer les clés de cache
|
|
key1 = self.resolver._make_cache_key(target_spec1, self.screen_state)
|
|
key2 = self.resolver._make_cache_key(target_spec2, self.screen_state)
|
|
key3 = self.resolver._make_cache_key(target_spec3, self.screen_state)
|
|
|
|
# Vérifications
|
|
assert key1 != key2, "Clés différentes pour context_hints différents"
|
|
assert key1 != key3, "Clés différentes avec/sans context_hints"
|
|
assert key2 != key3, "Clés différentes avec/sans context_hints"
|
|
|
|
# Vérifier que context_hints est dans la clé
|
|
assert "Username" in key1, "Username devrait être dans la clé de cache"
|
|
assert "Password" in key2, "Password devrait être dans la clé de cache"
|
|
|
|
def test_fiche3_error_handling_in_context_hints(self):
|
|
"""
|
|
Test de la gestion d'erreurs dans _apply_context_hints_to_candidates
|
|
"""
|
|
candidates = [self.username_input]
|
|
|
|
# Context hints avec données invalides
|
|
invalid_context_hints = {
|
|
"below_text": None, # Invalide
|
|
"within_region": [1, 2, 3], # Pas assez d'éléments
|
|
"near_text": 123 # Type invalide
|
|
}
|
|
|
|
scores = {elem.element_id: 1.0 for elem in candidates}
|
|
|
|
# Ne devrait pas planter
|
|
filtered = self.resolver._apply_context_hints_to_candidates(
|
|
candidates, invalid_context_hints, self.ui_elements, scores
|
|
)
|
|
|
|
# Devrait retourner les candidats originaux en cas d'erreur
|
|
assert filtered == candidates, "Devrait retourner candidats originaux si erreur"
|
|
|
|
def test_fiche3_multiple_context_hints_combined(self):
|
|
"""
|
|
Test de combinaison de plusieurs context_hints
|
|
"""
|
|
target_spec = MockTargetSpec(
|
|
by_role="text_input",
|
|
context_hints={
|
|
"below_text": "Username",
|
|
"near_text": "Username",
|
|
"max_distance": 100
|
|
}
|
|
)
|
|
|
|
# Mock de la méthode _get_ui_elements
|
|
self.resolver._get_ui_elements = Mock(return_value=self.ui_elements)
|
|
|
|
# Résoudre
|
|
result = self.resolver.resolve_target(target_spec, self.screen_state)
|
|
|
|
# Vérifications
|
|
assert result is not None, "Devrait trouver un élément avec hints multiples"
|
|
assert result.element.element_id == "username_input", "Devrait trouver username_input"
|
|
assert result.strategy_used == ResolutionStrategy.COMPOSITE.value
|
|
|
|
def test_fiche3_performance_with_context_hints(self):
|
|
"""
|
|
Test de performance - résolution avec context_hints ne devrait pas être trop lente
|
|
"""
|
|
import time
|
|
|
|
target_spec = MockTargetSpec(
|
|
by_role="text_input",
|
|
context_hints={"below_text": "Username"}
|
|
)
|
|
|
|
# Mock de la méthode _get_ui_elements
|
|
self.resolver._get_ui_elements = Mock(return_value=self.ui_elements)
|
|
|
|
# Mesurer le temps
|
|
start_time = time.time()
|
|
result = self.resolver.resolve_target(target_spec, self.screen_state)
|
|
end_time = time.time()
|
|
|
|
# Vérifications
|
|
assert result is not None, "Devrait trouver un élément"
|
|
assert (end_time - start_time) < 0.1, "Résolution devrait être rapide (< 100ms)"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"]) |