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>
This commit is contained in:
597
tests/unit/test_vwb_palette_extension_09jan2026.py
Normal file
597
tests/unit/test_vwb_palette_extension_09jan2026.py
Normal file
@@ -0,0 +1,597 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests unitaires pour l'extension de la Palette VWB avec actions du catalogue
|
||||
Auteur : Dom, Alice, Kiro - 09 janvier 2026
|
||||
|
||||
Ce module teste l'intégration des actions du catalogue VisionOnly dans la Palette VWB,
|
||||
incluant le chargement dynamique, la recherche unifiée, et l'affichage des catégories.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch, AsyncMock
|
||||
|
||||
# Configuration du chemin pour les imports
|
||||
import sys
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from visual_workflow_builder.backend.actions.registry import VWBActionRegistry
|
||||
from visual_workflow_builder.backend.contracts.visual_anchor import VWBVisualAnchor
|
||||
from visual_workflow_builder.backend.contracts.evidence import VWBEvidence
|
||||
from visual_workflow_builder.backend.contracts.error import VWBActionError
|
||||
|
||||
|
||||
class TestVWBPaletteExtension:
|
||||
"""Tests pour l'extension de la Palette VWB avec le catalogue d'actions"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Configuration avant chaque test"""
|
||||
self.registry = VWBActionRegistry()
|
||||
self.test_actions = [
|
||||
{
|
||||
'id': 'click_anchor',
|
||||
'name': 'Cliquer sur Ancre Visuelle',
|
||||
'description': 'Cliquer sur un élément identifié visuellement',
|
||||
'category': 'vision_ui',
|
||||
'icon': '🖱️',
|
||||
'parameters': {
|
||||
'visual_anchor': {
|
||||
'type': 'VWBVisualAnchor',
|
||||
'required': True,
|
||||
'description': 'Ancre visuelle à cliquer'
|
||||
},
|
||||
'click_type': {
|
||||
'type': 'string',
|
||||
'required': False,
|
||||
'default': 'left',
|
||||
'options': ['left', 'right', 'double'],
|
||||
'description': 'Type de clic à effectuer'
|
||||
}
|
||||
},
|
||||
'examples': [
|
||||
{
|
||||
'name': 'Clic simple sur bouton',
|
||||
'description': 'Cliquer sur un bouton avec reconnaissance visuelle',
|
||||
'parameters': {
|
||||
'visual_anchor': {
|
||||
'anchor_type': 'button',
|
||||
'description': 'Bouton de validation'
|
||||
},
|
||||
'click_type': 'left'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'type_text',
|
||||
'name': 'Saisir Texte',
|
||||
'description': 'Saisir du texte dans un champ identifié visuellement',
|
||||
'category': 'vision_ui',
|
||||
'icon': '⌨️',
|
||||
'parameters': {
|
||||
'visual_anchor': {
|
||||
'type': 'VWBVisualAnchor',
|
||||
'required': True,
|
||||
'description': 'Champ de saisie cible'
|
||||
},
|
||||
'text': {
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'description': 'Texte à saisir'
|
||||
}
|
||||
},
|
||||
'examples': [
|
||||
{
|
||||
'name': 'Saisie dans formulaire',
|
||||
'description': 'Saisir du texte dans un champ de formulaire',
|
||||
'parameters': {
|
||||
'visual_anchor': {
|
||||
'anchor_type': 'input',
|
||||
'description': 'Champ nom utilisateur'
|
||||
},
|
||||
'text': 'utilisateur@exemple.com'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'wait_for_anchor',
|
||||
'name': 'Attendre Ancre Visuelle',
|
||||
'description': 'Attendre qu\'un élément visuel apparaisse',
|
||||
'category': 'control',
|
||||
'icon': '⏳',
|
||||
'parameters': {
|
||||
'visual_anchor': {
|
||||
'type': 'VWBVisualAnchor',
|
||||
'required': True,
|
||||
'description': 'Élément à attendre'
|
||||
},
|
||||
'timeout_ms': {
|
||||
'type': 'number',
|
||||
'required': False,
|
||||
'default': 10000,
|
||||
'min': 1000,
|
||||
'max': 60000,
|
||||
'description': 'Délai d\'attente en millisecondes'
|
||||
}
|
||||
},
|
||||
'examples': [
|
||||
{
|
||||
'name': 'Attendre chargement page',
|
||||
'description': 'Attendre qu\'un élément de la page soit visible',
|
||||
'parameters': {
|
||||
'visual_anchor': {
|
||||
'anchor_type': 'text',
|
||||
'description': 'Texte "Chargement terminé"'
|
||||
},
|
||||
'timeout_ms': 15000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
def test_catalog_actions_structure(self):
|
||||
"""Test de la structure des actions du catalogue"""
|
||||
print("🧪 Test de la structure des actions du catalogue...")
|
||||
|
||||
for action in self.test_actions:
|
||||
# Vérifier les champs obligatoires
|
||||
assert 'id' in action, f"Action {action.get('name', 'inconnue')} manque l'ID"
|
||||
assert 'name' in action, f"Action {action['id']} manque le nom"
|
||||
assert 'description' in action, f"Action {action['id']} manque la description"
|
||||
assert 'category' in action, f"Action {action['id']} manque la catégorie"
|
||||
assert 'icon' in action, f"Action {action['id']} manque l'icône"
|
||||
assert 'parameters' in action, f"Action {action['id']} manque les paramètres"
|
||||
assert 'examples' in action, f"Action {action['id']} manque les exemples"
|
||||
|
||||
# Vérifier les catégories valides
|
||||
valid_categories = ['vision_ui', 'control', 'data', 'navigation', 'validation']
|
||||
assert action['category'] in valid_categories, f"Catégorie invalide: {action['category']}"
|
||||
|
||||
# Vérifier la structure des paramètres
|
||||
for param_name, param_config in action['parameters'].items():
|
||||
assert 'type' in param_config, f"Paramètre {param_name} manque le type"
|
||||
assert 'required' in param_config, f"Paramètre {param_name} manque required"
|
||||
assert 'description' in param_config, f"Paramètre {param_name} manque la description"
|
||||
|
||||
# Vérifier la structure des exemples
|
||||
for example in action['examples']:
|
||||
assert 'name' in example, f"Exemple manque le nom dans {action['id']}"
|
||||
assert 'description' in example, f"Exemple manque la description dans {action['id']}"
|
||||
assert 'parameters' in example, f"Exemple manque les paramètres dans {action['id']}"
|
||||
|
||||
print(f"✅ Structure validée pour {len(self.test_actions)} actions")
|
||||
|
||||
def test_category_metadata_mapping(self):
|
||||
"""Test du mapping des métadonnées de catégories"""
|
||||
print("🧪 Test du mapping des métadonnées de catégories...")
|
||||
|
||||
# Métadonnées attendues pour chaque catégorie
|
||||
expected_metadata = {
|
||||
'vision_ui': {
|
||||
'name': 'Vision UI',
|
||||
'description': 'Actions d\'interaction visuelle avec l\'interface utilisateur',
|
||||
'icon': '🖱️',
|
||||
'color': '#2196f3'
|
||||
},
|
||||
'control': {
|
||||
'name': 'Contrôle Vision',
|
||||
'description': 'Actions de contrôle et synchronisation visuelles',
|
||||
'icon': '⏳',
|
||||
'color': '#ff9800'
|
||||
},
|
||||
'data': {
|
||||
'name': 'Données Vision',
|
||||
'description': 'Actions de manipulation de données avec vision',
|
||||
'icon': '📊',
|
||||
'color': '#4caf50'
|
||||
}
|
||||
}
|
||||
|
||||
# Grouper les actions par catégorie
|
||||
actions_by_category = {}
|
||||
for action in self.test_actions:
|
||||
category = action['category']
|
||||
if category not in actions_by_category:
|
||||
actions_by_category[category] = []
|
||||
actions_by_category[category].append(action)
|
||||
|
||||
# Vérifier chaque catégorie
|
||||
for category_id, actions in actions_by_category.items():
|
||||
assert category_id in expected_metadata, f"Catégorie non mappée: {category_id}"
|
||||
|
||||
metadata = expected_metadata[category_id]
|
||||
assert len(metadata['name']) > 0, f"Nom vide pour catégorie {category_id}"
|
||||
assert len(metadata['description']) > 0, f"Description vide pour catégorie {category_id}"
|
||||
assert len(metadata['icon']) > 0, f"Icône vide pour catégorie {category_id}"
|
||||
assert metadata['color'].startswith('#'), f"Couleur invalide pour catégorie {category_id}"
|
||||
|
||||
print(f" ✅ Catégorie {category_id}: {len(actions)} actions, métadonnées OK")
|
||||
|
||||
print(f"✅ Mapping validé pour {len(actions_by_category)} catégories")
|
||||
|
||||
def test_step_template_conversion(self):
|
||||
"""Test de la conversion des actions du catalogue en StepTemplate"""
|
||||
print("🧪 Test de la conversion en StepTemplate...")
|
||||
|
||||
for action in self.test_actions:
|
||||
# Simuler la conversion (logique du frontend)
|
||||
required_parameters = [
|
||||
name for name, param in action['parameters'].items()
|
||||
if param['required']
|
||||
]
|
||||
|
||||
default_parameters = {
|
||||
name: param['default']
|
||||
for name, param in action['parameters'].items()
|
||||
if 'default' in param
|
||||
}
|
||||
|
||||
step_template = {
|
||||
'id': action['id'],
|
||||
'type': action['id'], # Utiliser l'ID comme type pour les actions du catalogue
|
||||
'name': action['name'],
|
||||
'description': action['description'],
|
||||
'icon': action['icon'],
|
||||
'defaultParameters': default_parameters,
|
||||
'requiredParameters': required_parameters
|
||||
}
|
||||
|
||||
# Vérifier la structure du StepTemplate
|
||||
assert step_template['id'] == action['id']
|
||||
assert step_template['name'] == action['name']
|
||||
assert step_template['description'] == action['description']
|
||||
assert step_template['icon'] == action['icon']
|
||||
assert isinstance(step_template['defaultParameters'], dict)
|
||||
assert isinstance(step_template['requiredParameters'], list)
|
||||
|
||||
# Vérifier que les paramètres requis sont corrects
|
||||
for param_name in step_template['requiredParameters']:
|
||||
assert param_name in action['parameters']
|
||||
assert action['parameters'][param_name]['required'] is True
|
||||
|
||||
# Vérifier que les paramètres par défaut sont corrects
|
||||
for param_name, default_value in step_template['defaultParameters'].items():
|
||||
assert param_name in action['parameters']
|
||||
assert action['parameters'][param_name].get('default') == default_value
|
||||
|
||||
print(f" ✅ Conversion OK pour {action['name']}")
|
||||
|
||||
print(f"✅ Conversion validée pour {len(self.test_actions)} actions")
|
||||
|
||||
def test_drag_data_format(self):
|
||||
"""Test du format des données de drag & drop"""
|
||||
print("🧪 Test du format des données de drag & drop...")
|
||||
|
||||
for action in self.test_actions:
|
||||
# Format pour les actions du catalogue
|
||||
catalog_drag_data = f"catalog:{action['id']}"
|
||||
|
||||
# Vérifier le format
|
||||
assert catalog_drag_data.startswith('catalog:')
|
||||
assert catalog_drag_data.endswith(action['id'])
|
||||
|
||||
# Vérifier que c'est différent des actions par défaut
|
||||
default_drag_data = action['id'] # Format pour actions par défaut
|
||||
assert catalog_drag_data != default_drag_data
|
||||
|
||||
print(f" ✅ Format drag OK pour {action['name']}: {catalog_drag_data}")
|
||||
|
||||
print("✅ Format de drag & drop validé")
|
||||
|
||||
def test_search_functionality(self):
|
||||
"""Test de la fonctionnalité de recherche unifiée"""
|
||||
print("🧪 Test de la fonctionnalité de recherche...")
|
||||
|
||||
# Termes de recherche à tester
|
||||
search_tests = [
|
||||
{
|
||||
'term': 'clic',
|
||||
'expected_matches': ['click_anchor'],
|
||||
'description': 'Recherche par nom partiel'
|
||||
},
|
||||
{
|
||||
'term': 'visuel',
|
||||
'expected_matches': ['click_anchor', 'type_text', 'wait_for_anchor'],
|
||||
'description': 'Recherche par description'
|
||||
},
|
||||
{
|
||||
'term': 'anchor',
|
||||
'expected_matches': ['click_anchor', 'wait_for_anchor'],
|
||||
'description': 'Recherche par type/ID'
|
||||
},
|
||||
{
|
||||
'term': 'inexistant',
|
||||
'expected_matches': [],
|
||||
'description': 'Recherche sans résultat'
|
||||
}
|
||||
]
|
||||
|
||||
for test in search_tests:
|
||||
term = test['term'].lower()
|
||||
matches = []
|
||||
|
||||
# Simuler la logique de recherche du frontend
|
||||
for action in self.test_actions:
|
||||
if (term in action['name'].lower() or
|
||||
term in action['description'].lower() or
|
||||
term in action['id'].lower()):
|
||||
matches.append(action['id'])
|
||||
|
||||
# Vérifier les résultats
|
||||
expected = test['expected_matches']
|
||||
assert len(matches) == len(expected), f"Nombre de résultats incorrect pour '{term}'"
|
||||
|
||||
for expected_id in expected:
|
||||
assert expected_id in matches, f"Action {expected_id} manquante pour '{term}'"
|
||||
|
||||
print(f" ✅ {test['description']}: '{term}' -> {len(matches)} résultats")
|
||||
|
||||
print("✅ Fonctionnalité de recherche validée")
|
||||
|
||||
def test_visual_indicators(self):
|
||||
"""Test des indicateurs visuels pour les actions du catalogue"""
|
||||
print("🧪 Test des indicateurs visuels...")
|
||||
|
||||
# Vérifier les éléments visuels attendus
|
||||
visual_elements = {
|
||||
'category_background': '#f8f9ff', # Fond des catégories catalogue
|
||||
'step_background': '#f0f4ff', # Fond des étapes catalogue
|
||||
'step_hover': '#e3f2fd', # Survol des étapes catalogue
|
||||
'border_color': '#2196f3', # Bordure gauche des étapes
|
||||
'chip_color': 'primary', # Couleur des chips "VisionOnly"
|
||||
'vision_label_color': '#2196f3' # Couleur du label "VISION"
|
||||
}
|
||||
|
||||
for element, expected_value in visual_elements.items():
|
||||
# Vérifier que les valeurs sont définies et cohérentes
|
||||
assert expected_value is not None, f"Valeur manquante pour {element}"
|
||||
|
||||
if element.endswith('_color') and expected_value.startswith('#'):
|
||||
# Vérifier le format hexadécimal des couleurs
|
||||
assert len(expected_value) == 7, f"Format couleur invalide: {expected_value}"
|
||||
assert all(c in '0123456789abcdefABCDEF' for c in expected_value[1:]), f"Couleur invalide: {expected_value}"
|
||||
|
||||
print(f" ✅ Indicateur {element}: {expected_value}")
|
||||
|
||||
print("✅ Indicateurs visuels validés")
|
||||
|
||||
def test_tooltip_content(self):
|
||||
"""Test du contenu des tooltips enrichis"""
|
||||
print("🧪 Test du contenu des tooltips...")
|
||||
|
||||
for action in self.test_actions:
|
||||
# Vérifier les éléments du tooltip
|
||||
tooltip_elements = {
|
||||
'name': action['name'],
|
||||
'description': action['description'],
|
||||
'required_params': [
|
||||
name for name, param in action['parameters'].items()
|
||||
if param['required']
|
||||
],
|
||||
'vision_indicator': '🎯 Action avec reconnaissance visuelle automatique'
|
||||
}
|
||||
|
||||
# Vérifier le nom
|
||||
assert len(tooltip_elements['name']) > 0, f"Nom vide pour {action['id']}"
|
||||
|
||||
# Vérifier la description
|
||||
assert len(tooltip_elements['description']) > 0, f"Description vide pour {action['id']}"
|
||||
|
||||
# Vérifier les paramètres requis
|
||||
assert len(tooltip_elements['required_params']) > 0, f"Aucun paramètre requis pour {action['id']}"
|
||||
|
||||
# Vérifier l'indicateur vision
|
||||
assert 'reconnaissance visuelle' in tooltip_elements['vision_indicator']
|
||||
|
||||
print(f" ✅ Tooltip OK pour {action['name']}")
|
||||
|
||||
print("✅ Contenu des tooltips validé")
|
||||
|
||||
def test_performance_considerations(self):
|
||||
"""Test des considérations de performance"""
|
||||
print("🧪 Test des considérations de performance...")
|
||||
|
||||
# Simuler un grand nombre d'actions
|
||||
large_action_set = self.test_actions * 10 # 30 actions
|
||||
|
||||
# Test de temps de filtrage
|
||||
start_time = time.time()
|
||||
|
||||
search_term = 'clic'
|
||||
filtered_actions = [
|
||||
action for action in large_action_set
|
||||
if (search_term.lower() in action['name'].lower() or
|
||||
search_term.lower() in action['description'].lower())
|
||||
]
|
||||
|
||||
filter_time = time.time() - start_time
|
||||
|
||||
# Vérifier que le filtrage est rapide (< 10ms pour 30 actions)
|
||||
assert filter_time < 0.01, f"Filtrage trop lent: {filter_time:.3f}s"
|
||||
|
||||
# Test de conversion en masse
|
||||
start_time = time.time()
|
||||
|
||||
step_templates = []
|
||||
for action in large_action_set:
|
||||
step_template = {
|
||||
'id': action['id'],
|
||||
'type': action['id'],
|
||||
'name': action['name'],
|
||||
'description': action['description'],
|
||||
'icon': action['icon']
|
||||
}
|
||||
step_templates.append(step_template)
|
||||
|
||||
conversion_time = time.time() - start_time
|
||||
|
||||
# Vérifier que la conversion est rapide (< 5ms pour 30 actions)
|
||||
assert conversion_time < 0.005, f"Conversion trop lente: {conversion_time:.3f}s"
|
||||
|
||||
print(f" ✅ Filtrage de {len(large_action_set)} actions: {filter_time:.3f}s")
|
||||
print(f" ✅ Conversion de {len(large_action_set)} actions: {conversion_time:.3f}s")
|
||||
print("✅ Performance validée")
|
||||
|
||||
def test_error_handling(self):
|
||||
"""Test de la gestion d'erreurs"""
|
||||
print("🧪 Test de la gestion d'erreurs...")
|
||||
|
||||
# Test avec action malformée
|
||||
malformed_action = {
|
||||
'id': 'malformed',
|
||||
'name': 'Action Malformée',
|
||||
# Manque description, category, etc.
|
||||
}
|
||||
|
||||
# Vérifier que les champs manquants sont détectés
|
||||
required_fields = ['description', 'category', 'icon', 'parameters', 'examples']
|
||||
missing_fields = [field for field in required_fields if field not in malformed_action]
|
||||
|
||||
assert len(missing_fields) > 0, "Aucun champ manquant détecté"
|
||||
print(f" ✅ Champs manquants détectés: {missing_fields}")
|
||||
|
||||
# Test avec catégorie invalide
|
||||
invalid_category_action = {
|
||||
'id': 'invalid_cat',
|
||||
'name': 'Catégorie Invalide',
|
||||
'description': 'Test',
|
||||
'category': 'invalid_category',
|
||||
'icon': '❌',
|
||||
'parameters': {},
|
||||
'examples': []
|
||||
}
|
||||
|
||||
valid_categories = ['vision_ui', 'control', 'data', 'navigation', 'validation']
|
||||
assert invalid_category_action['category'] not in valid_categories
|
||||
print(f" ✅ Catégorie invalide détectée: {invalid_category_action['category']}")
|
||||
|
||||
# Test avec paramètre malformé
|
||||
malformed_param_action = {
|
||||
'id': 'malformed_param',
|
||||
'name': 'Paramètre Malformé',
|
||||
'description': 'Test',
|
||||
'category': 'vision_ui',
|
||||
'icon': '❌',
|
||||
'parameters': {
|
||||
'bad_param': {
|
||||
# Manque 'type', 'required', 'description'
|
||||
'value': 'test'
|
||||
}
|
||||
},
|
||||
'examples': []
|
||||
}
|
||||
|
||||
param = malformed_param_action['parameters']['bad_param']
|
||||
param_required_fields = ['type', 'required', 'description']
|
||||
param_missing_fields = [field for field in param_required_fields if field not in param]
|
||||
|
||||
assert len(param_missing_fields) > 0, "Aucun champ de paramètre manquant détecté"
|
||||
print(f" ✅ Champs de paramètre manquants détectés: {param_missing_fields}")
|
||||
|
||||
print("✅ Gestion d'erreurs validée")
|
||||
|
||||
def test_integration_compatibility(self):
|
||||
"""Test de la compatibilité avec l'intégration VWB existante"""
|
||||
print("🧪 Test de la compatibilité d'intégration...")
|
||||
|
||||
# Vérifier que les actions du catalogue n'interfèrent pas avec les actions par défaut
|
||||
default_categories = [
|
||||
'actions-web',
|
||||
'logique',
|
||||
'donnees',
|
||||
'controle'
|
||||
]
|
||||
|
||||
catalog_categories = [
|
||||
'catalog_vision_ui',
|
||||
'catalog_control',
|
||||
'catalog_data'
|
||||
]
|
||||
|
||||
# Vérifier qu'il n'y a pas de collision d'IDs
|
||||
for default_cat in default_categories:
|
||||
for catalog_cat in catalog_categories:
|
||||
assert default_cat != catalog_cat, f"Collision d'ID de catégorie: {default_cat}"
|
||||
|
||||
print(f" ✅ Pas de collision entre {len(default_categories)} catégories par défaut et {len(catalog_categories)} catégories catalogue")
|
||||
|
||||
# Vérifier que les types d'actions sont distincts
|
||||
default_step_types = ['click', 'type', 'wait', 'condition', 'extract', 'scroll', 'navigate', 'screenshot']
|
||||
catalog_step_types = [action['id'] for action in self.test_actions]
|
||||
|
||||
# Vérifier qu'il n'y a pas de collision de types
|
||||
for default_type in default_step_types:
|
||||
for catalog_type in catalog_step_types:
|
||||
assert default_type != catalog_type, f"Collision de type d'étape: {default_type}"
|
||||
|
||||
print(f" ✅ Pas de collision entre {len(default_step_types)} types par défaut et {len(catalog_step_types)} types catalogue")
|
||||
|
||||
# Vérifier la compatibilité des formats de données
|
||||
for action in self.test_actions:
|
||||
# Format de drag pour actions catalogue
|
||||
catalog_drag = f"catalog:{action['id']}"
|
||||
|
||||
# Vérifier que le format est parsable
|
||||
assert ':' in catalog_drag
|
||||
parts = catalog_drag.split(':')
|
||||
assert len(parts) == 2
|
||||
assert parts[0] == 'catalog'
|
||||
assert parts[1] == action['id']
|
||||
|
||||
print(" ✅ Format de données compatible")
|
||||
print("✅ Compatibilité d'intégration validée")
|
||||
|
||||
|
||||
def run_tests():
|
||||
"""Exécuter tous les tests de l'extension Palette VWB"""
|
||||
print("🚀 Démarrage des tests d'extension Palette VWB...")
|
||||
print("=" * 60)
|
||||
|
||||
test_instance = TestVWBPaletteExtension()
|
||||
test_instance.setup_method()
|
||||
|
||||
tests = [
|
||||
test_instance.test_catalog_actions_structure,
|
||||
test_instance.test_category_metadata_mapping,
|
||||
test_instance.test_step_template_conversion,
|
||||
test_instance.test_drag_data_format,
|
||||
test_instance.test_search_functionality,
|
||||
test_instance.test_visual_indicators,
|
||||
test_instance.test_tooltip_content,
|
||||
test_instance.test_performance_considerations,
|
||||
test_instance.test_error_handling,
|
||||
test_instance.test_integration_compatibility,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
test()
|
||||
passed += 1
|
||||
print()
|
||||
except Exception as e:
|
||||
print(f"❌ ÉCHEC: {e}")
|
||||
failed += 1
|
||||
print()
|
||||
|
||||
print("=" * 60)
|
||||
print(f"📊 RÉSULTATS: {passed} réussis, {failed} échoués")
|
||||
|
||||
if failed == 0:
|
||||
print("🎉 TOUS LES TESTS RÉUSSIS - Extension Palette VWB validée !")
|
||||
return True
|
||||
else:
|
||||
print("⚠️ CERTAINS TESTS ONT ÉCHOUÉ - Corrections nécessaires")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_tests()
|
||||
exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user