#!/usr/bin/env python3 """ Tests d'accessibilité pour le Visual Workflow Builder Valide les fonctionnalités ARIA, navigation clavier et support screen readers """ import os import sys import time import subprocess import requests from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.chrome.options import Options from axe_selenium_python import Axe def setup_driver(): """Configure le driver Selenium avec les options d'accessibilité""" chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage') chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--window-size=1920,1080') # Options pour simuler les technologies d'assistance chrome_options.add_argument('--force-prefers-reduced-motion') chrome_options.add_argument('--enable-accessibility-logging') try: driver = webdriver.Chrome(options=chrome_options) return driver except Exception as e: print(f"❌ Erreur lors de la configuration du driver: {e}") return None def wait_for_server(url, timeout=30): """Attend que le serveur soit disponible""" print(f"🔄 Attente du serveur {url}...") start_time = time.time() while time.time() - start_time < timeout: try: response = requests.get(url, timeout=5) if response.status_code == 200: print(f"✅ Serveur disponible") return True except requests.exceptions.RequestException: pass time.sleep(1) print(f"❌ Serveur non disponible après {timeout}s") return False def test_aria_attributes(driver): """Test des attributs ARIA""" print("\n🧪 Test des attributs ARIA...") tests_passed = 0 total_tests = 0 # Test 1: Canvas principal total_tests += 1 try: canvas = driver.find_element(By.CSS_SELECTOR, '[role="application"]') aria_label = canvas.get_attribute('aria-label') if aria_label and 'canvas' in aria_label.lower(): print("✅ Canvas a un aria-label approprié") tests_passed += 1 else: print("❌ Canvas manque d'aria-label approprié") except Exception as e: print(f"❌ Erreur test canvas ARIA: {e}") # Test 2: Palette total_tests += 1 try: palette = driver.find_element(By.CSS_SELECTOR, '[role="region"]') aria_label = palette.get_attribute('aria-label') if aria_label and ('palette' in aria_label.lower() or 'boîte' in aria_label.lower()): print("✅ Palette a un aria-label approprié") tests_passed += 1 else: print("❌ Palette manque d'aria-label approprié") except Exception as e: print(f"❌ Erreur test palette ARIA: {e}") # Test 3: Boutons avec aria-pressed total_tests += 1 try: buttons = driver.find_elements(By.CSS_SELECTOR, 'button[aria-pressed]') if len(buttons) > 0: print(f"✅ {len(buttons)} boutons avec aria-pressed trouvés") tests_passed += 1 else: print("❌ Aucun bouton avec aria-pressed trouvé") except Exception as e: print(f"❌ Erreur test boutons ARIA: {e}") # Test 4: Éléments avec aria-live total_tests += 1 try: live_regions = driver.find_elements(By.CSS_SELECTOR, '[aria-live]') if len(live_regions) > 0: print(f"✅ {len(live_regions)} régions live trouvées") tests_passed += 1 else: print("❌ Aucune région aria-live trouvée") except Exception as e: print(f"❌ Erreur test régions live: {e}") return tests_passed, total_tests def test_keyboard_navigation(driver): """Test de la navigation clavier""" print("\n⌨️ Test de la navigation clavier...") tests_passed = 0 total_tests = 0 # Test 1: Navigation avec Tab total_tests += 1 try: body = driver.find_element(By.TAG_NAME, 'body') body.send_keys(Keys.TAB) active_element = driver.switch_to.active_element if active_element.tag_name in ['button', 'input', 'a'] or active_element.get_attribute('tabindex'): print("✅ Navigation Tab fonctionne") tests_passed += 1 else: print("❌ Navigation Tab ne fonctionne pas correctement") except Exception as e: print(f"❌ Erreur test navigation Tab: {e}") # Test 2: Raccourcis clavier total_tests += 1 try: # Test Alt+H pour high contrast body = driver.find_element(By.TAG_NAME, 'body') body.send_keys(Keys.ALT + 'h') time.sleep(0.5) # Vérifier si le mode high contrast est activé high_contrast = driver.execute_script( "return document.body.classList.contains('high-contrast')" ) if high_contrast: print("✅ Raccourci Alt+H fonctionne") tests_passed += 1 else: print("❌ Raccourci Alt+H ne fonctionne pas") except Exception as e: print(f"❌ Erreur test raccourcis: {e}") # Test 3: Navigation dans la palette total_tests += 1 try: # Chercher un élément de palette focusable palette_items = driver.find_elements(By.CSS_SELECTOR, '.node-item[tabindex]') if len(palette_items) > 0: palette_items[0].click() palette_items[0].send_keys(Keys.ARROW_DOWN) time.sleep(0.2) print("✅ Navigation flèches dans palette fonctionne") tests_passed += 1 else: print("❌ Aucun élément de palette navigable trouvé") except Exception as e: print(f"❌ Erreur test navigation palette: {e}") return tests_passed, total_tests def test_screen_reader_support(driver): """Test du support des lecteurs d'écran""" print("\n🔊 Test du support des lecteurs d'écran...") tests_passed = 0 total_tests = 0 # Test 1: Éléments sr-only total_tests += 1 try: sr_only_elements = driver.find_elements(By.CSS_SELECTOR, '.sr-only') if len(sr_only_elements) > 0: print(f"✅ {len(sr_only_elements)} éléments sr-only trouvés") tests_passed += 1 else: print("❌ Aucun élément sr-only trouvé") except Exception as e: print(f"❌ Erreur test sr-only: {e}") # Test 2: Skip links total_tests += 1 try: skip_links = driver.find_elements(By.CSS_SELECTOR, '.skip-link') if len(skip_links) > 0: print(f"✅ {len(skip_links)} skip links trouvés") tests_passed += 1 else: print("❌ Aucun skip link trouvé") except Exception as e: print(f"❌ Erreur test skip links: {e}") # Test 3: Landmarks total_tests += 1 try: landmarks = driver.find_elements(By.CSS_SELECTOR, '[role="main"], [role="navigation"], [role="region"], [role="banner"]') if len(landmarks) > 0: print(f"✅ {len(landmarks)} landmarks trouvés") tests_passed += 1 else: print("❌ Aucun landmark trouvé") except Exception as e: print(f"❌ Erreur test landmarks: {e}") # Test 4: Headings structure total_tests += 1 try: headings = driver.find_elements(By.CSS_SELECTOR, 'h1, h2, h3, h4, h5, h6') if len(headings) > 0: print(f"✅ {len(headings)} headings trouvés") tests_passed += 1 else: print("❌ Aucun heading trouvé") except Exception as e: print(f"❌ Erreur test headings: {e}") return tests_passed, total_tests def test_high_contrast_mode(driver): """Test du mode high contrast""" print("\n🎨 Test du mode high contrast...") tests_passed = 0 total_tests = 0 # Test 1: Activation du mode high contrast total_tests += 1 try: # Chercher le bouton de toggle high contrast contrast_button = driver.find_element(By.CSS_SELECTOR, '[aria-label*="contraste"], [title*="contraste"]') contrast_button.click() time.sleep(0.5) # Vérifier si le mode est activé high_contrast = driver.execute_script( "return document.body.classList.contains('high-contrast')" ) if high_contrast: print("✅ Mode high contrast activé") tests_passed += 1 else: print("❌ Mode high contrast non activé") except Exception as e: print(f"❌ Erreur activation high contrast: {e}") # Test 2: Styles high contrast appliqués total_tests += 1 try: # Vérifier que les styles high contrast sont appliqués elements_with_contrast = driver.find_elements(By.CSS_SELECTOR, '.high-contrast *') if len(elements_with_contrast) > 0: print("✅ Styles high contrast appliqués") tests_passed += 1 else: print("❌ Styles high contrast non appliqués") except Exception as e: print(f"❌ Erreur vérification styles: {e}") return tests_passed, total_tests def test_focus_management(driver): """Test de la gestion du focus""" print("\n🎯 Test de la gestion du focus...") tests_passed = 0 total_tests = 0 # Test 1: Focus visible total_tests += 1 try: # Naviguer avec Tab et vérifier le focus body = driver.find_element(By.TAG_NAME, 'body') body.send_keys(Keys.TAB) active_element = driver.switch_to.active_element outline = driver.execute_script( "return window.getComputedStyle(arguments[0]).outline", active_element ) if outline and outline != 'none': print("✅ Focus visible présent") tests_passed += 1 else: print("❌ Focus visible manquant") except Exception as e: print(f"❌ Erreur test focus visible: {e}") # Test 2: Focus trap dans les modales total_tests += 1 try: # Chercher des modales ou popups modals = driver.find_elements(By.CSS_SELECTOR, '[role="dialog"], .modal, .popup') if len(modals) == 0: print("ℹ️ Aucune modale trouvée (test skippé)") tests_passed += 1 # Pas d'erreur si pas de modale else: print("✅ Modales détectées pour test focus trap") tests_passed += 1 except Exception as e: print(f"❌ Erreur test focus trap: {e}") return tests_passed, total_tests def run_axe_audit(driver): """Exécute un audit d'accessibilité avec axe-core""" print("\n🔍 Audit d'accessibilité avec axe-core...") try: axe = Axe(driver) results = axe.run() violations = results.get('violations', []) passes = results.get('passes', []) print(f"✅ {len(passes)} règles respectées") print(f"❌ {len(violations)} violations trouvées") if violations: print("\n📋 Détail des violations:") for violation in violations[:5]: # Limiter à 5 pour la lisibilité print(f" • {violation['id']}: {violation['description']}") print(f" Impact: {violation['impact']}") print(f" Éléments affectés: {len(violation['nodes'])}") return len(violations) == 0, len(passes), len(violations) except Exception as e: print(f"❌ Erreur audit axe: {e}") return False, 0, 0 def main(): """Fonction principale des tests d'accessibilité""" print("🚀 Démarrage des tests d'accessibilité du Visual Workflow Builder") print("=" * 70) # Vérifier que le serveur est démarré if not wait_for_server("http://localhost:3000"): print("❌ Impossible de se connecter au serveur frontend") return False # Configurer le driver driver = setup_driver() if not driver: return False try: # Charger l'application print("🌐 Chargement de l'application...") driver.get("http://localhost:3000") # Attendre que l'application soit chargée WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.TAG_NAME, "body")) ) total_passed = 0 total_tests = 0 # Exécuter les tests passed, tests = test_aria_attributes(driver) total_passed += passed total_tests += tests passed, tests = test_keyboard_navigation(driver) total_passed += passed total_tests += tests passed, tests = test_screen_reader_support(driver) total_passed += passed total_tests += tests passed, tests = test_high_contrast_mode(driver) total_passed += passed total_tests += tests passed, tests = test_focus_management(driver) total_passed += passed total_tests += tests # Audit axe-core axe_passed, axe_passes, axe_violations = run_axe_audit(driver) # Résultats finaux print("\n" + "=" * 70) print("📊 RÉSULTATS DES TESTS D'ACCESSIBILITÉ") print("=" * 70) print(f"Tests manuels: {total_passed}/{total_tests} passés ({total_passed/total_tests*100:.1f}%)") print(f"Audit axe-core: {axe_passes} règles respectées, {axe_violations} violations") success_rate = total_passed / total_tests if total_tests > 0 else 0 overall_success = success_rate >= 0.8 and axe_violations <= 5 if overall_success: print("✅ TESTS D'ACCESSIBILITÉ RÉUSSIS") print("🎉 L'application respecte les standards d'accessibilité") else: print("❌ TESTS D'ACCESSIBILITÉ ÉCHOUÉS") print("⚠️ Des améliorations d'accessibilité sont nécessaires") return overall_success except Exception as e: print(f"❌ Erreur lors des tests: {e}") return False finally: driver.quit() if __name__ == "__main__": success = main() sys.exit(0 if success else 1)