- 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>
168 lines
6.0 KiB
Python
168 lines
6.0 KiB
Python
"""
|
|
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.params = None
|
|
|
|
# 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 screen state avec nos éléments
|
|
screen_state = Mock()
|
|
screen_state.ui_elements = elements
|
|
|
|
# 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())
|
|
|
|
# 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 au centre (100, 120) -> centre (100, 120)
|
|
anchor = MockUIElement("anchor", (100, 120, 0, 0))
|
|
|
|
# Éléments à tester
|
|
elements = [
|
|
MockUIElement("near", (120, 120, 10, 10)), # centre: (125, 125), distance ≈ 25
|
|
MockUIElement("medium", (140, 140, 10, 10)), # centre: (145, 145), distance ≈ 35
|
|
MockUIElement("far", (200, 200, 10, 10)), # centre: (205, 205), distance ≈ 120
|
|
]
|
|
|
|
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"]) |