""" Tests de validation - Fiche #2 : Validation des calculs de centre BBOX (format XYWH) Vérifie que tous les calculs de centre utilisent les bons calculs pour le format XYWH : - BBOX format: (x, y, w, h) - Centre correct = (x + w/2, y + h/2) Auteur: Dom, Alice Kiro - 15 décembre 2024 """ import pytest from unittest.mock import Mock, patch from dataclasses import dataclass from typing import Tuple, List, Optional from core.execution.action_executor import ActionExecutor from core.execution.target_resolver import TargetResolver from core.models.screen_state import ScreenState from core.models.ui_element import UIElement from core.models.workflow_graph import Action, ActionType, TargetSpec @dataclass class MockUIElement: """Mock UIElement pour les tests""" element_id: str bbox: Tuple[int, int, int, int] # (x, y, w, h) label: str = "" role: str = "" confidence: float = 0.9 class TestBBoxCenterCalculations: """Tests pour vérifier que le format BBOX est cohérent dans tout le système""" def test_bbox_format_consistency(self): """Test que le format BBOX utilise le bon calcul de centre""" # Définir un BBOX de test test_bbox = (50, 75, 100, 150) # x=50, y=75, w=100, h=150 # Centre attendu expected_center = (100, 150) # (50 + 100/2, 75 + 150/2) # Vérifier que nos calculs sont cohérents center_x = test_bbox[0] + test_bbox[2] / 2 # x + w/2 center_y = test_bbox[1] + test_bbox[3] / 2 # y + h/2 expected_area = test_bbox[2] * test_bbox[3] # w * h = 100 * 150 = 15000 area = test_bbox[2] * test_bbox[3] assert (center_x, center_y) == expected_center assert area == expected_area # Vérifier que l'ancien calcul (incorrect) donne des résultats différents old_incorrect_center_x = (test_bbox[0] + test_bbox[2]) / 2 # (x+w)/2 ❌ old_incorrect_center_y = (test_bbox[1] + test_bbox[3]) / 2 # (y+h)/2 ❌ # L'ancien calcul ('incorrect) donnerait un résultat différent assert center_x != old_incorrect_center_x # 100 != 75 ❌ assert center_y != old_incorrect_center_y # 150 != 112.5 ❌ def test_action_executor_click_position(): """Test que ActionExecutor calcule correctement la position de clic""" # Créer une action de clic action = Mock() action.type = ActionType.MOUSE_CLICK action.target = Mock() action.parameters = {} action.params = {} # Mock screen state screen_state = Mock() # Créer un élément avec BBOX XYWH mock_element = MockUIElement( element_id="test_button", bbox=(100, 200, 50, 30), # x=100, y=200, w=50, h=30 label="Test Button", role="button" ) # Mock du resolved target mock_resolved = Mock() mock_resolved.element = mock_element # Mock du target resolver pour retourner notre élément with patch('core.execution.action_executor.TargetResolver') as mock_resolver_class: mock_resolver = Mock() mock_resolver.resolve_target.return_value = mock_resolved mock_resolver_class.return_value = mock_resolver # Mock pyautogui pour capturer les coordonnées de clic with patch('core.execution.action_executor.pyautogui') as mock_pyautogui: # Exécuter l'action executor = ActionExecutor() result = executor._execute_click(action, screen_state) # Vérifier que pyautogui.click a été appelé avec les bonnes coordonnées mock_pyautogui.click.assert_called_once() call_args = mock_pyautogui.click.call_args[0] click_x, click_y = call_args # Centre attendu: (125, 215) expected_x = 100 + 50 / 2 # 125 expected_y = 200 + 30 / 2 # 215 assert click_x == expected_x assert click_y == expected_y def test_target_resolver_position_matching(): """Test que TargetResolver utilise les bons calculs de centre pour la recherche de position""" # Créer des éléments de test elements = [ MockUIElement("elem1", (100, 100, 50, 50)), # centre: (125, 125) MockUIElement("elem2", (200, 200, 30, 30)), # centre: (215, 215) MockUIElement("elem3", (140, 140, 40, 40)), # centre: (160, 160) ] # Position de recherche proche de elem3 search_position = (170, 170) # Mock context avec spatial_index=None pour forcer le fallback linéaire mock_context = Mock() mock_context.workflow_context = {"spatial_index": None} # Mock _get_ui_elements pour retourner nos éléments resolver = TargetResolver(position_tolerance=50) with patch.object(resolver, '_get_ui_elements', return_value=elements): # Résoudre par position result = resolver._resolve_by_position(search_position, elements, mock_context) # Devrait trouver elem3 (distance ≈ 14) assert result is not None assert result.element.element_id == "elem3" def test_target_resolver_proximity_filter(): """Test que le filtre de proximité utilise les bons calculs de centre""" # Élément ancre: bbox (90, 110, 20, 20) -> centre (100, 120) anchor = MockUIElement("anchor", (90, 110, 20, 20)) # Éléments à tester (distances au centre de l'ancre (100, 120)): # near: centre (125, 125), distance = sqrt(25² + 5²) ≈ 25.5 # medium: centre (130, 130), distance = sqrt(30² + 10²) ≈ 31.6 # far: centre (205, 205), distance = sqrt(105² + 85²) ≈ 135.1 elements = [ MockUIElement("near", (120, 120, 10, 10)), MockUIElement("medium", (125, 125, 10, 10)), MockUIElement("far", (200, 200, 10, 10)), ] resolver = TargetResolver() # Filtrer avec distance max = 50 filtered = resolver._filter_by_proximity(elements, anchor, max_distance=50) # Seuls "near" et "medium" devraient être dans le résultat filtered_ids = [elem.element_id for elem in filtered] assert "near" in filtered_ids assert "medium" in filtered_ids assert "far" not in filtered_ids if __name__ == "__main__": pytest.main([__file__, "-v"])