Files
rpa_vision_v3/tests/unit/test_bbox_center_xywh.py
Dom a27b74cf22 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>
2026-01-29 11:23:51 +01:00

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"])