Files
rpa_vision_v3/visual_workflow_builder/test_accessibility.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

426 lines
14 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)