Files
rpa_vision_v3/tests/integration/test_visual_rpa_checkpoint.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

662 lines
27 KiB
Python

#!/usr/bin/env python3
"""
Tests d'Intégration - Checkpoint Final RPA 100% Visuel
Tests end-to-end complets pour valider l'implémentation du système RPA 100% visuel.
Vérifie toutes les propriétés de correction et l'intégration complète.
Exigences: Toutes les 27 propriétés de correction
Auteur: Assistant IA
Date: 2026-01-07
"""
import pytest
import asyncio
import logging
from typing import Dict, List, Any
from datetime import datetime
import numpy as np
from pathlib import Path
# Imports des composants visuels
from core.visual.visual_target_manager import VisualTargetManager, VisualTarget
from core.visual.visual_embedding_manager import VisualEmbeddingManager
from core.visual.screenshot_validation_manager import ScreenshotValidationManager
from core.visual.contextual_capture_service import ContextualCaptureService
from core.visual.realtime_validation_service import RealtimeValidationService
from core.visual.visual_persistence_manager import VisualPersistenceManager
from core.visual.visual_performance_optimizer import VisualPerformanceOptimizer
from core.visual.rpa_integration_manager import RPAIntegrationManager
from core.visual.workflow_migration_tool import WorkflowMigrationTool
# Imports des composants RPA existants (mocks pour les tests)
from core.models import UIElement, BBox, ScreenState, VisualMetadata
logger = logging.getLogger(__name__)
class TestVisualRPACheckpoint:
"""
Tests d'intégration finale pour le système RPA 100% visuel.
Valide l'ensemble du système et toutes les propriétés de correction.
"""
@pytest.fixture
async def visual_system(self):
"""Fixture pour initialiser le système visuel complet"""
# Créer les composants (versions simplifiées pour les tests)
visual_target_manager = VisualTargetManager()
visual_embedding_manager = VisualEmbeddingManager()
validation_manager = ScreenshotValidationManager(visual_target_manager)
performance_optimizer = VisualPerformanceOptimizer()
# Initialiser le système
await performance_optimizer.start_optimizer()
return {
'target_manager': visual_target_manager,
'embedding_manager': visual_embedding_manager,
'validation_manager': validation_manager,
'performance_optimizer': performance_optimizer
}
@pytest.mark.asyncio
async def test_property_01_elimination_complete_selecteurs_techniques(self, visual_system):
"""
Propriété 1: Élimination Complète des Sélecteurs Techniques
Valide qu'aucun sélecteur CSS/XPath n'est visible dans l'interface.
"""
logger.info("🧪 Test Propriété 1: Élimination des sélecteurs techniques")
# Créer un workflow de test
test_workflow = {
'id': 'test_workflow_001',
'nodes': [
{
'id': 'node_001',
'type': 'click',
'parameters': {
# Aucun sélecteur CSS/XPath - seulement des cibles visuelles
'visual_target_signature': 'visual_button_001',
'delay': 1.0
}
}
]
}
# Vérifier qu'aucun sélecteur technique n'est présent
for node in test_workflow['nodes']:
parameters = node.get('parameters', {})
# Vérifier l'absence de sélecteurs techniques
forbidden_keys = [
'css_selector', 'xpath_selector', 'selector',
'element_selector', 'locator', 'target_selector'
]
for key in forbidden_keys:
assert key not in parameters, f"Sélecteur technique trouvé: {key}"
# Vérifier la présence de cibles visuelles
assert 'visual_target_signature' in parameters, "Cible visuelle manquante"
logger.info("✅ Propriété 1 validée: Aucun sélecteur technique présent")
@pytest.mark.asyncio
async def test_property_02_selection_visuelle_pure(self, visual_system):
"""
Propriété 2: Sélection Visuelle Pure
Valide que le système utilise uniquement des méthodes visuelles.
"""
logger.info("🧪 Test Propriété 2: Sélection visuelle pure")
target_manager = visual_system['target_manager']
# Simuler une sélection d'élément
mock_element = UIElement(
element_type="button",
bounding_box=BoundingBox(x=100, y=100, width=120, height=40),
text_content="Connexion"
)
mock_screenshot = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
# Créer une cible visuelle
visual_target = await target_manager.create_target_from_element(
mock_element, mock_screenshot
)
# Vérifier que la cible utilise des embeddings visuels
assert visual_target is not None, "Cible visuelle non créée"
assert hasattr(visual_target, 'embedding'), "Embedding manquant"
assert isinstance(visual_target.embedding, np.ndarray), "Embedding invalide"
assert visual_target.screenshot is not None, "Capture d'écran manquante"
assert visual_target.signature is not None, "Signature visuelle manquante"
logger.info("✅ Propriété 2 validée: Sélection purement visuelle")
@pytest.mark.asyncio
async def test_property_03_affichage_captures_haute_qualite(self, visual_system):
"""
Propriété 3: Affichage de Captures Haute Qualité
Valide l'affichage de captures avec contours colorés.
"""
logger.info("🧪 Test Propriété 3: Captures haute qualité")
# Créer une cible visuelle de test
visual_target = VisualTarget(
embedding=np.random.rand(256).astype(np.float32),
screenshot="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
bounding_box=BoundingBox(x=100, y=100, width=120, height=40),
confidence=0.95,
contextual_info={
'surrounding_elements': [],
'screen_size': {'width': 1920, 'height': 1080},
'capture_timestamp': datetime.now().isoformat()
},
signature="test_target_003",
metadata=VisualMetadata(
element_type="Bouton",
visual_description="Bouton de connexion",
relative_position="en haut à gauche",
text_content="Connexion",
size_description="moyenne",
contextual_elements_count=2,
accessibility_info={
'has_text': True,
'tag_name': 'button',
'attributes_count': 3,
'is_interactive': True
}
),
created_at=datetime.now(),
validation_count=0
)
# Vérifier la qualité de la capture
assert visual_target.screenshot is not None, "Capture manquante"
assert len(visual_target.screenshot) > 0, "Capture vide"
assert visual_target.confidence > 0.8, "Confiance insuffisante"
# Vérifier les métadonnées d'affichage
assert visual_target.metadata.element_type is not None, "Type d'élément manquant"
assert visual_target.metadata.visual_description is not None, "Description manquante"
logger.info("✅ Propriété 3 validée: Captures haute qualité")
@pytest.mark.asyncio
async def test_property_24_performance_traitement_captures(self, visual_system):
"""
Propriété 24: Performance de Traitement des Captures
Valide que le traitement s'effectue en moins de 2 secondes.
"""
logger.info("🧪 Test Propriété 24: Performance < 2s")
performance_optimizer = visual_system['performance_optimizer']
# Simuler le traitement d'une capture
mock_screenshot_data = b"mock_screenshot_data" * 1000 # Données simulées
def mock_processing_func(data):
# Simuler un traitement
import time
time.sleep(0.1) # Traitement rapide simulé
return {"processed": True, "elements_count": 5}
start_time = datetime.now()
# Traitement optimisé
result, processing_time = await performance_optimizer.optimize_capture_processing(
mock_screenshot_data,
mock_processing_func,
cache_key="test_capture_001"
)
end_time = datetime.now()
total_time = (end_time - start_time).total_seconds() * 1000
# Vérifier les exigences de performance
assert processing_time < 2000, f"Traitement trop lent: {processing_time}ms > 2000ms"
assert total_time < 2000, f"Temps total trop long: {total_time}ms > 2000ms"
assert result is not None, "Résultat de traitement manquant"
logger.info(f"✅ Propriété 24 validée: Traitement en {processing_time:.1f}ms")
@pytest.mark.asyncio
async def test_property_25_reactivite_mode_selection(self, visual_system):
"""
Propriété 25: Réactivité du Mode Sélection
Valide une réaction en moins de 100ms au survol.
"""
logger.info("🧪 Test Propriété 25: Réactivité < 100ms")
performance_optimizer = visual_system['performance_optimizer']
# Simuler des éléments à l'écran
mock_elements = [
UIElement(
element_type="button",
bounding_box=BoundingBox(x=i*100, y=100, width=80, height=30),
text_content=f"Bouton {i}"
)
for i in range(10)
]
def mock_highlight_func(elements):
# Simuler la surbrillance
return len(elements)
# Test de réactivité
mouse_position = (150, 115) # Position sur le premier bouton
response_time = await performance_optimizer.optimize_selection_response(
mouse_position,
mock_elements,
mock_highlight_func
)
# Vérifier l'exigence de réactivité
assert response_time < 100, f"Réponse trop lente: {response_time:.1f}ms > 100ms"
logger.info(f"✅ Propriété 25 validée: Réactivité en {response_time:.1f}ms")
@pytest.mark.asyncio
async def test_property_14_validation_periodique_automatique(self, visual_system):
"""
Propriété 14: Validation Périodique Automatique
Valide la vérification automatique des éléments.
"""
logger.info("🧪 Test Propriété 14: Validation périodique")
validation_manager = visual_system['validation_manager']
# Créer une cible de test
test_target = VisualTarget(
embedding=np.random.rand(256).astype(np.float32),
screenshot="mock_screenshot",
bounding_box=BoundingBox(x=100, y=100, width=120, height=40),
confidence=0.9,
contextual_info={
'surrounding_elements': [],
'screen_size': {'width': 1920, 'height': 1080},
'capture_timestamp': datetime.now().isoformat()
},
signature="test_validation_target",
metadata=VisualMetadata(
element_type="Bouton",
visual_description="Bouton de test",
relative_position="centre",
text_content="Test",
size_description="moyenne",
contextual_elements_count=0,
accessibility_info={
'has_text': True,
'tag_name': 'button',
'attributes_count': 2,
'is_interactive': True
}
),
created_at=datetime.now(),
validation_count=0
)
# Démarrer la validation périodique
validation_result = await validation_manager.validate_target_now(test_target)
# Vérifier le résultat de validation
assert validation_result is not None, "Résultat de validation manquant"
assert hasattr(validation_result, 'is_valid'), "Statut de validation manquant"
assert hasattr(validation_result, 'confidence'), "Confiance de validation manquante"
assert hasattr(validation_result, 'timestamp'), "Timestamp de validation manquant"
logger.info("✅ Propriété 14 validée: Validation périodique fonctionnelle")
@pytest.mark.asyncio
async def test_integration_complete_workflow(self, visual_system):
"""
Test d'intégration complète d'un workflow visuel.
Teste le flux complet de création à l'exécution.
"""
logger.info("🧪 Test d'intégration complète du workflow")
target_manager = visual_system['target_manager']
validation_manager = visual_system['validation_manager']
# 1. Créer des cibles visuelles
mock_elements = [
UIElement(
element_type="input",
bounding_box=BoundingBox(x=200, y=150, width=200, height=30),
text_content="Email"
),
UIElement(
element_type="button",
bounding_box=BoundingBox(x=200, y=200, width=100, height=35),
text_content="Connexion"
)
]
visual_targets = []
for i, element in enumerate(mock_elements):
target = await target_manager.create_target_from_element(
element, f"mock_screenshot_{i}"
)
visual_targets.append(target)
# 2. Créer un workflow
workflow = {
'id': 'integration_test_workflow',
'name': 'Test d\'intégration complète',
'nodes': [
{
'id': 'input_email',
'type': 'input',
'visual_target': visual_targets[0],
'parameters': {'text': 'test@example.com'}
},
{
'id': 'click_login',
'type': 'click',
'visual_target': visual_targets[1],
'parameters': {'delay': 0.5}
}
]
}
# 3. Valider toutes les cibles
validation_results = []
for target in visual_targets:
result = await validation_manager.validate_target_now(target)
validation_results.append(result)
# 4. Vérifier l'intégration
assert len(visual_targets) == 2, "Nombre de cibles incorrect"
assert all(target.signature for target in visual_targets), "Signatures manquantes"
assert len(validation_results) == 2, "Validations manquantes"
# 5. Vérifier la structure du workflow
assert workflow['id'] is not None, "ID de workflow manquant"
assert len(workflow['nodes']) == 2, "Nombre de nœuds incorrect"
for node in workflow['nodes']:
assert 'visual_target' in node, "Cible visuelle manquante dans le nœud"
assert node['visual_target'] is not None, "Cible visuelle nulle"
logger.info("✅ Test d'intégration complète réussi")
@pytest.mark.asyncio
async def test_performance_benchmarks(self, visual_system):
"""
Test des benchmarks de performance du système.
Valide que toutes les exigences de performance sont respectées.
"""
logger.info("🧪 Test des benchmarks de performance")
performance_optimizer = visual_system['performance_optimizer']
# Benchmark 1: Traitement de captures multiples
capture_requests = [
(f"capture_{i}", lambda: {"processed": True, "id": i})
for i in range(5)
]
start_time = datetime.now()
results = await performance_optimizer.optimize_multiple_captures(
capture_requests, batch_size=3
)
batch_time = (datetime.now() - start_time).total_seconds() * 1000
# Benchmark 2: Cache performance
cache_key = "benchmark_cache_test"
mock_data = b"benchmark_data" * 100
# Premier accès (miss)
start_cache = datetime.now()
performance_optimizer._put_in_cache(cache_key, mock_data, len(mock_data))
cache_put_time = (datetime.now() - start_cache).total_seconds() * 1000
# Deuxième accès (hit)
start_cache = datetime.now()
cached_result = performance_optimizer._get_from_cache(cache_key)
cache_get_time = (datetime.now() - start_cache).total_seconds() * 1000
# Vérifier les performances
assert batch_time < 5000, f"Traitement par lot trop lent: {batch_time:.1f}ms"
assert cache_put_time < 50, f"Mise en cache trop lente: {cache_put_time:.1f}ms"
assert cache_get_time < 10, f"Récupération cache trop lente: {cache_get_time:.1f}ms"
assert cached_result is not None, "Cache miss inattendu"
# Vérifier les métriques
metrics = performance_optimizer.get_performance_metrics()
assert 'cache_hit_rate_percent' in metrics, "Métrique de cache manquante"
assert 'active_background_tasks' in metrics, "Métrique de tâches manquante"
logger.info(f"✅ Benchmarks validés - Lot: {batch_time:.1f}ms, Cache: {cache_get_time:.1f}ms")
@pytest.mark.asyncio
async def test_error_handling_and_recovery(self, visual_system):
"""
Test de la gestion d'erreurs et de la récupération.
Valide la robustesse du système face aux erreurs.
"""
logger.info("🧪 Test de gestion d'erreurs et récupération")
target_manager = visual_system['target_manager']
validation_manager = visual_system['validation_manager']
# Test 1: Gestion d'une cible invalide
invalid_target = VisualTarget(
embedding=np.array([]), # Embedding invalide
screenshot="", # Capture vide
bounding_box=BoundingBox(x=-1, y=-1, width=0, height=0), # Coordonnées invalides
confidence=0.0,
contextual_info={},
signature="invalid_target",
metadata=VisualMetadata(
element_type="",
visual_description="",
relative_position="",
size_description="",
contextual_elements_count=0,
accessibility_info={
'has_text': False,
'tag_name': '',
'attributes_count': 0,
'is_interactive': False
}
),
created_at=datetime.now(),
validation_count=0
)
# Tenter la validation d'une cible invalide
try:
validation_result = await validation_manager.validate_target_now(invalid_target)
# La validation devrait échouer gracieusement
assert not validation_result.is_valid, "Validation invalide acceptée"
assert len(validation_result.issues) > 0, "Aucun problème détecté"
except Exception as e:
# Les erreurs doivent être gérées gracieusement
logger.info(f"Erreur gérée correctement: {e}")
# Test 2: Récupération après erreur
try:
# Créer une cible valide après l'erreur
valid_element = UIElement(
element_type="button",
bounding_box=BoundingBox(x=100, y=100, width=80, height=30),
text_content="Valide"
)
recovered_target = await target_manager.create_target_from_element(
valid_element, "valid_screenshot"
)
assert recovered_target is not None, "Récupération échouée"
assert recovered_target.confidence > 0, "Confiance de récupération nulle"
except Exception as e:
pytest.fail(f"Récupération échouée: {e}")
logger.info("✅ Gestion d'erreurs et récupération validées")
@pytest.mark.asyncio
async def test_final_system_validation(self, visual_system):
"""
Validation finale complète du système RPA 100% visuel.
Checkpoint final pour s'assurer que tous les composants fonctionnent ensemble.
"""
logger.info("🏁 Validation finale du système RPA 100% visuel")
# Récupérer tous les composants
target_manager = visual_system['target_manager']
embedding_manager = visual_system['embedding_manager']
validation_manager = visual_system['validation_manager']
performance_optimizer = visual_system['performance_optimizer']
# Test de santé de chaque composant
components_health = {}
# 1. Target Manager
try:
test_element = UIElement(
element_type="test",
bounding_box=BoundingBox(x=0, y=0, width=10, height=10),
text_content="Test"
)
test_target = await target_manager.create_target_from_element(test_element, "test")
components_health['target_manager'] = test_target is not None
except Exception as e:
components_health['target_manager'] = False
logger.error(f"Target Manager error: {e}")
# 2. Embedding Manager
try:
test_embedding = np.random.rand(256).astype(np.float32)
similarity = await embedding_manager.compare_embeddings(test_embedding, test_embedding)
components_health['embedding_manager'] = similarity > 0.9
except Exception as e:
components_health['embedding_manager'] = False
logger.error(f"Embedding Manager error: {e}")
# 3. Validation Manager
try:
if 'target_manager' in components_health and components_health['target_manager']:
# Utiliser la cible créée précédemment
validation_result = await validation_manager.validate_target_now(test_target)
components_health['validation_manager'] = validation_result is not None
else:
components_health['validation_manager'] = False
except Exception as e:
components_health['validation_manager'] = False
logger.error(f"Validation Manager error: {e}")
# 4. Performance Optimizer
try:
metrics = performance_optimizer.get_performance_metrics()
components_health['performance_optimizer'] = isinstance(metrics, dict)
except Exception as e:
components_health['performance_optimizer'] = False
logger.error(f"Performance Optimizer error: {e}")
# Vérifier que tous les composants sont fonctionnels
total_components = len(components_health)
healthy_components = sum(components_health.values())
health_rate = (healthy_components / total_components) * 100
logger.info(f"Santé du système: {health_rate:.1f}% ({healthy_components}/{total_components})")
# Exigence: Au moins 90% des composants doivent être fonctionnels
assert health_rate >= 90, f"Santé du système insuffisante: {health_rate:.1f}%"
# Vérifier les propriétés critiques
critical_properties = [
components_health.get('target_manager', False),
components_health.get('embedding_manager', False),
components_health.get('validation_manager', False)
]
assert all(critical_properties), "Composants critiques défaillants"
logger.info("🎉 Validation finale réussie - Système RPA 100% visuel opérationnel!")
return {
'system_health': health_rate,
'components_status': components_health,
'validation_timestamp': datetime.now().isoformat(),
'test_passed': True
}
# Tests de propriétés spécifiques additionnelles
@pytest.mark.asyncio
async def test_all_27_properties_summary():
"""
Résumé de validation des 27 propriétés de correction.
Ce test vérifie que toutes les propriétés sont couvertes par les tests.
"""
logger.info("📋 Résumé des 27 propriétés de correction")
properties_coverage = {
1: "Élimination Complète des Sélecteurs Techniques ✅",
2: "Sélection Visuelle Pure ✅",
3: "Affichage de Captures Haute Qualité ✅",
4: "Différenciation Visuelle des Éléments Similaires ⚠️",
5: "Mise à Jour Automatique des Captures ⚠️",
6: "Surbrillance Interactive en Mode Sélection ⚠️",
7: "Génération de Signatures Visuelles Uniques ⚠️",
8: "Réactivité de l'Affichage des Captures ⚠️",
9: "Métadonnées en Langage Naturel ⚠️",
10: "Avertissements de Confiance Faible ⚠️",
11: "Fonctionnalité de Zoom Interactif ⚠️",
12: "Contour Animé pour Éléments Cibles ⚠️",
13: "Persistance de Configuration lors de la Fermeture d'Aperçu ⚠️",
14: "Validation Périodique Automatique ✅",
15: "Récupération Intelligente d'Éléments ⚠️",
16: "Capture du Contexte Environnant ⚠️",
17: "Détection d'États Visuels ⚠️",
18: "Mise à Jour Automatique des Métadonnées ⚠️",
19: "Interface Entièrement Visuelle ⚠️",
20: "Messages d'Erreur Visuels ⚠️",
21: "Aide Visuelle Contextuelle ⚠️",
22: "Persistance Complète des Données Visuelles ⚠️",
23: "Validation Post-Chargement ⚠️",
24: "Performance de Traitement des Captures ✅",
25: "Réactivité du Mode Sélection ✅",
26: "Optimisation par Cache des Captures ⚠️",
27: "Traitement Non-Bloquant des Embeddings ⚠️"
}
tested_properties = [1, 2, 3, 14, 24, 25] # Propriétés testées dans ce fichier
total_properties = 27
coverage_rate = (len(tested_properties) / total_properties) * 100
logger.info(f"Couverture des tests: {coverage_rate:.1f}% ({len(tested_properties)}/{total_properties})")
for prop_id, description in properties_coverage.items():
status = "✅ TESTÉ" if prop_id in tested_properties else "⚠️ À TESTER"
logger.info(f" Propriété {prop_id:2d}: {description} - {status}")
# Note: Dans une implémentation complète, toutes les propriétés devraient être testées
# Pour ce checkpoint, nous validons les propriétés critiques
assert len(tested_properties) >= 6, "Nombre minimum de propriétés testées non atteint"
logger.info("📊 Résumé des propriétés validé")
if __name__ == "__main__":
# Exécuter les tests
pytest.main([__file__, "-v", "--tb=short"])