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:
779
tests/property/test_vwb_action_properties_12jan2026.py
Normal file
779
tests/property/test_vwb_action_properties_12jan2026.py
Normal file
@@ -0,0 +1,779 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests de propriétés pour VWBActionProperties - Détection et gestion des actions VWB
|
||||
Auteur : Dom, Alice, Kiro - 12 janvier 2026
|
||||
|
||||
Ce module teste les propriétés universelles du composant VWBActionProperties,
|
||||
en particulier la détection correcte et la gestion des états de chargement.
|
||||
|
||||
Feature: interface-proprietes-etapes-complete
|
||||
Property 4: Détection correcte des actions VWB
|
||||
Property 5: Gestion des états de chargement VWB
|
||||
Validates: Requirements 2.1, 2.2, 2.4
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
from hypothesis import given, strategies as st, settings, assume, note
|
||||
from hypothesis.stateful import RuleBasedStateMachine, Bundle, rule, initialize, invariant
|
||||
|
||||
# Configuration des tests de propriétés
|
||||
PROPERTY_TEST_SETTINGS = settings(
|
||||
max_examples=100,
|
||||
deadline=30000, # 30 secondes par test
|
||||
suppress_health_check=[],
|
||||
)
|
||||
|
||||
# Stratégies de génération de données
|
||||
@st.composite
|
||||
def vwb_action_strategy(draw):
|
||||
"""Génère des actions VWB valides"""
|
||||
action_types = [
|
||||
'click_anchor', 'type_text', 'type_secret', 'wait_for_anchor',
|
||||
'extract_text', 'screenshot_evidence', 'scroll_to_anchor',
|
||||
'focus_anchor', 'hotkey', 'navigate_to_url'
|
||||
]
|
||||
|
||||
action_type = draw(st.sampled_from(action_types))
|
||||
|
||||
return {
|
||||
'id': action_type,
|
||||
'name': draw(st.text(min_size=5, max_size=50)),
|
||||
'description': draw(st.text(min_size=10, max_size=200)),
|
||||
'category': draw(st.sampled_from(['interaction', 'navigation', 'extraction', 'validation'])),
|
||||
'parameters': draw(vwb_parameters_strategy()),
|
||||
'examples': draw(st.lists(vwb_example_strategy(), max_size=3)),
|
||||
'version': draw(st.text(min_size=3, max_size=10)),
|
||||
'tags': draw(st.lists(st.text(min_size=3, max_size=20), max_size=5))
|
||||
}
|
||||
|
||||
@st.composite
|
||||
def vwb_parameters_strategy(draw):
|
||||
"""Génère des paramètres d'action VWB"""
|
||||
param_count = draw(st.integers(min_value=1, max_value=6))
|
||||
parameters = {}
|
||||
|
||||
for i in range(param_count):
|
||||
param_name = draw(st.sampled_from([
|
||||
'target_anchor', 'text_content', 'confidence_threshold',
|
||||
'timeout_seconds', 'retry_count', 'scroll_direction'
|
||||
]))
|
||||
|
||||
param_type = draw(st.sampled_from(['string', 'number', 'boolean', 'VWBVisualAnchor']))
|
||||
|
||||
parameters[param_name] = {
|
||||
'type': param_type,
|
||||
'required': draw(st.booleans()),
|
||||
'description': draw(st.text(min_size=10, max_size=100)),
|
||||
'default': draw(get_default_value_strategy(param_type))
|
||||
}
|
||||
|
||||
if param_type == 'number':
|
||||
parameters[param_name]['min'] = draw(st.one_of(st.none(), st.integers(min_value=0, max_value=100)))
|
||||
parameters[param_name]['max'] = draw(st.one_of(st.none(), st.integers(min_value=100, max_value=1000)))
|
||||
|
||||
return parameters
|
||||
|
||||
@st.composite
|
||||
def vwb_example_strategy(draw):
|
||||
"""Génère des exemples d'utilisation VWB"""
|
||||
return {
|
||||
'name': draw(st.text(min_size=5, max_size=30)),
|
||||
'description': draw(st.text(min_size=10, max_size=100)),
|
||||
'parameters': draw(st.dictionaries(
|
||||
st.text(min_size=3, max_size=20),
|
||||
st.one_of(st.text(), st.integers(), st.booleans()),
|
||||
max_size=5
|
||||
)),
|
||||
'expectedResult': draw(st.one_of(st.none(), st.text(min_size=10, max_size=100)))
|
||||
}
|
||||
|
||||
def get_default_value_strategy(param_type: str):
|
||||
"""Retourne une stratégie pour les valeurs par défaut selon le type"""
|
||||
if param_type == 'string':
|
||||
return st.one_of(st.none(), st.text(max_size=50))
|
||||
elif param_type == 'number':
|
||||
return st.one_of(st.none(), st.integers(min_value=0, max_value=100))
|
||||
elif param_type == 'boolean':
|
||||
return st.one_of(st.none(), st.booleans())
|
||||
elif param_type == 'VWBVisualAnchor':
|
||||
return st.none() # Les ancres visuelles n'ont pas de valeur par défaut
|
||||
else:
|
||||
return st.none()
|
||||
|
||||
@st.composite
|
||||
def loading_state_strategy(draw):
|
||||
"""Génère des états de chargement"""
|
||||
return {
|
||||
'isLoading': draw(st.booleans()),
|
||||
'hasError': draw(st.booleans()),
|
||||
'errorMessage': draw(st.one_of(st.none(), st.text(min_size=10, max_size=100))),
|
||||
'stepType': draw(st.one_of(st.none(), st.sampled_from([
|
||||
'click_anchor', 'type_text', 'wait_for_anchor', 'extract_text'
|
||||
])))
|
||||
}
|
||||
|
||||
@st.composite
|
||||
def variable_strategy(draw):
|
||||
"""Génère des variables"""
|
||||
return {
|
||||
'id': draw(st.text(min_size=1, max_size=20)),
|
||||
'name': draw(st.text(min_size=1, max_size=30, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')))),
|
||||
'value': draw(st.one_of(st.text(), st.integers(), st.booleans())),
|
||||
'type': draw(st.sampled_from(['string', 'number', 'boolean']))
|
||||
}
|
||||
|
||||
class VWBActionPropertiesTestHelper:
|
||||
"""Helper pour tester le composant VWBActionProperties via Node.js"""
|
||||
|
||||
def __init__(self):
|
||||
self.project_root = Path(__file__).parent.parent.parent
|
||||
self.frontend_path = self.project_root / "visual_workflow_builder" / "frontend"
|
||||
|
||||
def create_test_script(self, action: Optional[Dict], loading_state: Dict, parameters: Dict, variables: List[Dict]) -> str:
|
||||
"""Crée un script de test Node.js pour VWBActionProperties"""
|
||||
|
||||
test_script = f"""
|
||||
const React = require('react');
|
||||
|
||||
// Configuration du test
|
||||
const vwbAction = {json.dumps(action)};
|
||||
const loadingState = {json.dumps(loading_state)};
|
||||
const parameters = {json.dumps(parameters)};
|
||||
const variables = {json.dumps(variables)};
|
||||
|
||||
// Simulation du composant VWBActionProperties
|
||||
class VWBActionPropertiesSimulator {{
|
||||
constructor(action, isLoading, error, stepType, parameters, variables) {{
|
||||
this.action = action;
|
||||
this.isLoading = isLoading;
|
||||
this.error = error;
|
||||
this.stepType = stepType;
|
||||
this.parameters = parameters;
|
||||
this.variables = variables;
|
||||
this.validationResults = [];
|
||||
this.parameterChanges = [];
|
||||
}}
|
||||
|
||||
// Simulation de la détection d'action VWB
|
||||
detectVWBAction() {{
|
||||
const detection = {{
|
||||
isVWBAction: false,
|
||||
detectionMethods: {{}},
|
||||
confidence: 0
|
||||
}};
|
||||
|
||||
// Méthodes de détection
|
||||
if (this.action) {{
|
||||
detection.detectionMethods.hasAction = true;
|
||||
detection.isVWBAction = true;
|
||||
detection.confidence += 0.4;
|
||||
}}
|
||||
|
||||
if (this.stepType) {{
|
||||
const vwbPatterns = ['_anchor', '_text', '_secret', 'click_', 'type_', 'wait_', 'extract_'];
|
||||
detection.detectionMethods.hasVWBPattern = vwbPatterns.some(pattern =>
|
||||
this.stepType.includes(pattern)
|
||||
);
|
||||
if (detection.detectionMethods.hasVWBPattern) {{
|
||||
detection.isVWBAction = true;
|
||||
detection.confidence += 0.3;
|
||||
}}
|
||||
}}
|
||||
|
||||
if (this.parameters && Object.keys(this.parameters).length > 0) {{
|
||||
const vwbParamNames = ['target_anchor', 'confidence_threshold', 'visual_anchor'];
|
||||
detection.detectionMethods.hasVWBParams = Object.keys(this.parameters).some(param =>
|
||||
vwbParamNames.some(vwbParam => param.includes(vwbParam))
|
||||
);
|
||||
if (detection.detectionMethods.hasVWBParams) {{
|
||||
detection.isVWBAction = true;
|
||||
detection.confidence += 0.2;
|
||||
}}
|
||||
}}
|
||||
|
||||
detection.detectionMethods.isLoadingState = this.isLoading;
|
||||
detection.detectionMethods.hasError = Boolean(this.error);
|
||||
|
||||
return detection;
|
||||
}}
|
||||
|
||||
// Simulation de la gestion des états de chargement
|
||||
handleLoadingStates() {{
|
||||
const stateHandling = {{
|
||||
currentState: 'unknown',
|
||||
canRender: false,
|
||||
showsAppropriateUI: false,
|
||||
providesUserFeedback: false,
|
||||
hasRecoveryOptions: false
|
||||
}};
|
||||
|
||||
if (this.isLoading) {{
|
||||
stateHandling.currentState = 'loading';
|
||||
stateHandling.canRender = true;
|
||||
stateHandling.showsAppropriateUI = true;
|
||||
stateHandling.providesUserFeedback = true;
|
||||
}} else if (this.error) {{
|
||||
stateHandling.currentState = 'error';
|
||||
stateHandling.canRender = true;
|
||||
stateHandling.showsAppropriateUI = true;
|
||||
stateHandling.providesUserFeedback = true;
|
||||
stateHandling.hasRecoveryOptions = true; // Bouton retry, suggestions alternatives
|
||||
}} else if (!this.action) {{
|
||||
stateHandling.currentState = 'not_found';
|
||||
stateHandling.canRender = true;
|
||||
stateHandling.showsAppropriateUI = true;
|
||||
stateHandling.providesUserFeedback = true;
|
||||
stateHandling.hasRecoveryOptions = true; // Actions alternatives, config manuelle
|
||||
}} else {{
|
||||
stateHandling.currentState = 'loaded';
|
||||
stateHandling.canRender = true;
|
||||
stateHandling.showsAppropriateUI = true;
|
||||
stateHandling.providesUserFeedback = true;
|
||||
}}
|
||||
|
||||
return stateHandling;
|
||||
}}
|
||||
|
||||
// Simulation de la validation des paramètres
|
||||
validateParameters() {{
|
||||
const validation = {{
|
||||
is_valid: true,
|
||||
errors: [],
|
||||
warnings: [],
|
||||
suggestions: []
|
||||
}};
|
||||
|
||||
if (!this.action) {{
|
||||
// Pas de validation possible sans action
|
||||
return validation;
|
||||
}}
|
||||
|
||||
// Validation des paramètres requis
|
||||
Object.entries(this.action.parameters || {{}}).forEach(([paramName, paramConfig]) => {{
|
||||
const value = this.parameters[paramName];
|
||||
|
||||
if (paramConfig.required && (value === undefined || value === null || value === '')) {{
|
||||
validation.is_valid = false;
|
||||
validation.errors.push({{
|
||||
parameter: paramName,
|
||||
message: `Le paramètre "${{paramName}}" est requis`,
|
||||
code: 'REQUIRED_PARAMETER',
|
||||
severity: 'error'
|
||||
}});
|
||||
}}
|
||||
|
||||
// Validation par type
|
||||
if (value !== undefined && value !== null && value !== '') {{
|
||||
switch (paramConfig.type) {{
|
||||
case 'number':
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {{
|
||||
validation.is_valid = false;
|
||||
validation.errors.push({{
|
||||
parameter: paramName,
|
||||
message: `"${{paramName}}" doit être un nombre`,
|
||||
code: 'INVALID_TYPE',
|
||||
severity: 'error'
|
||||
}});
|
||||
}} else {{
|
||||
if (paramConfig.min !== undefined && numValue < paramConfig.min) {{
|
||||
validation.is_valid = false;
|
||||
validation.errors.push({{
|
||||
parameter: paramName,
|
||||
message: `"${{paramName}}" doit être >= ${{paramConfig.min}}`,
|
||||
code: 'MIN_VALUE',
|
||||
severity: 'error'
|
||||
}});
|
||||
}}
|
||||
if (paramConfig.max !== undefined && numValue > paramConfig.max) {{
|
||||
validation.is_valid = false;
|
||||
validation.errors.push({{
|
||||
parameter: paramName,
|
||||
message: `"${{paramName}}" doit être <= ${{paramConfig.max}}`,
|
||||
code: 'MAX_VALUE',
|
||||
severity: 'error'
|
||||
}});
|
||||
}}
|
||||
}}
|
||||
break;
|
||||
|
||||
case 'VWBVisualAnchor':
|
||||
if (typeof value !== 'object' || !value.anchor_id) {{
|
||||
validation.warnings.push({{
|
||||
parameter: paramName,
|
||||
message: `"${{paramName}}" nécessite une sélection visuelle valide`,
|
||||
impact: 'medium'
|
||||
}});
|
||||
}}
|
||||
break;
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
|
||||
return validation;
|
||||
}}
|
||||
|
||||
// Simulation du rendu des alternatives
|
||||
getAlternativeActions() {{
|
||||
const alternatives = [];
|
||||
|
||||
if (!this.action || this.error) {{
|
||||
// Suggérer des alternatives basées sur le type d'étape
|
||||
const stepTypeAlternatives = {{
|
||||
'click_anchor': [
|
||||
{{ name: 'click', description: 'Clic standard sur élément' }},
|
||||
{{ name: 'type', description: 'Saisie de texte' }}
|
||||
],
|
||||
'type_text': [
|
||||
{{ name: 'type', description: 'Saisie de texte standard' }},
|
||||
{{ name: 'click', description: 'Clic pour focus puis saisie' }}
|
||||
],
|
||||
'wait_for_anchor': [
|
||||
{{ name: 'wait', description: 'Attente temporelle' }},
|
||||
{{ name: 'condition', description: 'Attente conditionnelle' }}
|
||||
]
|
||||
}};
|
||||
|
||||
const typeAlternatives = stepTypeAlternatives[this.stepType] || [
|
||||
{{ name: 'click', description: 'Clic standard' }},
|
||||
{{ name: 'type', description: 'Saisie standard' }}
|
||||
];
|
||||
|
||||
alternatives.push(...typeAlternatives);
|
||||
}}
|
||||
|
||||
return alternatives;
|
||||
}}
|
||||
}}
|
||||
|
||||
// Test des propriétés VWBActionProperties
|
||||
function testVWBActionProperties() {{
|
||||
const results = {{}};
|
||||
|
||||
try {{
|
||||
const simulator = new VWBActionPropertiesSimulator(
|
||||
vwbAction,
|
||||
loadingState.isLoading,
|
||||
loadingState.hasError ? new Error(loadingState.errorMessage || 'Test error') : null,
|
||||
loadingState.stepType,
|
||||
parameters,
|
||||
variables
|
||||
);
|
||||
|
||||
// 1. Test de détection d'action VWB (Property 4)
|
||||
const detection = simulator.detectVWBAction();
|
||||
results.vwbDetection = {{
|
||||
isVWBAction: detection.isVWBAction,
|
||||
detectionMethods: detection.detectionMethods,
|
||||
confidence: detection.confidence,
|
||||
methodCount: Object.values(detection.detectionMethods).filter(Boolean).length
|
||||
}};
|
||||
|
||||
// 2. Test de gestion des états de chargement (Property 5)
|
||||
const stateHandling = simulator.handleLoadingStates();
|
||||
results.loadingStateHandling = {{
|
||||
currentState: stateHandling.currentState,
|
||||
canRender: stateHandling.canRender,
|
||||
showsAppropriateUI: stateHandling.showsAppropriateUI,
|
||||
providesUserFeedback: stateHandling.providesUserFeedback,
|
||||
hasRecoveryOptions: stateHandling.hasRecoveryOptions
|
||||
}};
|
||||
|
||||
// 3. Test de validation des paramètres
|
||||
const validation = simulator.validateParameters();
|
||||
results.parameterValidation = {{
|
||||
is_valid: validation.is_valid,
|
||||
errorCount: validation.errors.length,
|
||||
warningCount: validation.warnings.length,
|
||||
suggestionCount: validation.suggestions.length,
|
||||
validationPossible: Boolean(vwbAction)
|
||||
}};
|
||||
|
||||
// 4. Test des alternatives
|
||||
const alternatives = simulator.getAlternativeActions();
|
||||
results.alternatives = {{
|
||||
count: alternatives.length,
|
||||
hasAlternatives: alternatives.length > 0,
|
||||
alternatives: alternatives
|
||||
}};
|
||||
|
||||
// 5. Test de cohérence globale
|
||||
results.consistency = {{
|
||||
stateMatchesData: (
|
||||
(loadingState.isLoading && stateHandling.currentState === 'loading') ||
|
||||
(loadingState.hasError && stateHandling.currentState === 'error') ||
|
||||
(!vwbAction && !loadingState.isLoading && !loadingState.hasError && stateHandling.currentState === 'not_found') ||
|
||||
(vwbAction && !loadingState.isLoading && !loadingState.hasError && stateHandling.currentState === 'loaded')
|
||||
),
|
||||
detectionMatchesAction: (
|
||||
(vwbAction && detection.isVWBAction) ||
|
||||
(!vwbAction && loadingState.stepType && detection.isVWBAction) ||
|
||||
(!vwbAction && !loadingState.stepType)
|
||||
),
|
||||
validationMatchesState: (
|
||||
(!vwbAction && !validation.is_valid) ||
|
||||
(vwbAction && validation !== null)
|
||||
)
|
||||
}};
|
||||
|
||||
results.success = true;
|
||||
|
||||
}} catch (error) {{
|
||||
results.success = false;
|
||||
results.error = error.message;
|
||||
}}
|
||||
|
||||
return results;
|
||||
}}
|
||||
|
||||
// Exécuter le test
|
||||
const testResults = testVWBActionProperties();
|
||||
console.log(JSON.stringify(testResults, null, 2));
|
||||
"""
|
||||
return test_script
|
||||
|
||||
def run_test_script(self, script_content: str) -> Dict[str, Any]:
|
||||
"""Exécute un script de test Node.js et retourne les résultats"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
|
||||
f.write(script_content)
|
||||
script_path = f.name
|
||||
|
||||
try:
|
||||
# Exécuter le script dans le contexte du frontend
|
||||
result = subprocess.run(
|
||||
['node', script_path],
|
||||
cwd=self.frontend_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
try:
|
||||
return json.loads(result.stdout)
|
||||
except json.JSONDecodeError:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Invalid JSON output: {result.stdout}',
|
||||
'stderr': result.stderr
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Script failed with code {result.returncode}',
|
||||
'stdout': result.stdout,
|
||||
'stderr': result.stderr
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Test script timeout'
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Execution error: {str(e)}'
|
||||
}
|
||||
finally:
|
||||
# Nettoyer le fichier temporaire
|
||||
try:
|
||||
os.unlink(script_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
class TestVWBActionPropertiesProperties:
|
||||
"""Tests de propriétés pour VWBActionProperties"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Configuration avant chaque test"""
|
||||
self.helper = VWBActionPropertiesTestHelper()
|
||||
|
||||
@given(
|
||||
action=st.one_of(st.none(), vwb_action_strategy()),
|
||||
loading_state=loading_state_strategy(),
|
||||
parameters=st.dictionaries(st.text(min_size=3, max_size=20), st.one_of(st.text(), st.integers(), st.booleans()), max_size=5),
|
||||
variables=st.lists(variable_strategy(), max_size=3)
|
||||
)
|
||||
@PROPERTY_TEST_SETTINGS
|
||||
def test_property_4_vwb_action_detection(self, action, loading_state, parameters, variables):
|
||||
"""
|
||||
Property 4: Détection correcte des actions VWB
|
||||
|
||||
Pour toute étape identifiée comme action VWB par le StepTypeResolver,
|
||||
le PropertiesPanel doit utiliser le composant VWBActionProperties.
|
||||
"""
|
||||
note(f"Testing VWB detection - Action: {action is not None}, Loading: {loading_state}")
|
||||
note(f"Parameters: {len(parameters)}, Variables: {len(variables)}")
|
||||
|
||||
# Créer et exécuter le test
|
||||
script = self.helper.create_test_script(action, loading_state, parameters, variables)
|
||||
results = self.helper.run_test_script(script)
|
||||
|
||||
# Vérifications des propriétés
|
||||
assert results.get('success', False), f"Test failed: {results.get('error', 'Unknown error')}"
|
||||
|
||||
# Property 4.1: Détection basée sur l'action
|
||||
detection = results.get('vwbDetection', {})
|
||||
|
||||
if action is not None:
|
||||
assert detection.get('isVWBAction', False), "Action VWB non détectée malgré la présence d'une action"
|
||||
assert detection.get('confidence', 0) > 0, "Confiance de détection nulle avec action présente"
|
||||
|
||||
# Property 4.2: Détection basée sur le type d'étape
|
||||
step_type = loading_state.get('stepType')
|
||||
if step_type and any(pattern in step_type for pattern in ['_anchor', '_text', 'click_', 'type_']):
|
||||
assert detection.get('isVWBAction', False), f"Type VWB non détecté: {step_type}"
|
||||
|
||||
# Property 4.3: Méthodes de détection multiples
|
||||
detection_methods = detection.get('detectionMethods', {})
|
||||
method_count = detection.get('methodCount', 0)
|
||||
|
||||
assert isinstance(detection_methods, dict), "Méthodes de détection invalides"
|
||||
assert method_count >= 0, "Nombre de méthodes de détection invalide"
|
||||
|
||||
# Property 4.4: Cohérence de la détection
|
||||
consistency = results.get('consistency', {})
|
||||
assert consistency.get('detectionMatchesAction', False), "Détection incohérente avec l'action"
|
||||
|
||||
@given(
|
||||
action=st.one_of(st.none(), vwb_action_strategy()),
|
||||
loading_state=loading_state_strategy(),
|
||||
parameters=st.dictionaries(st.text(min_size=3, max_size=20), st.one_of(st.text(), st.integers()), max_size=3),
|
||||
variables=st.lists(variable_strategy(), max_size=2)
|
||||
)
|
||||
@PROPERTY_TEST_SETTINGS
|
||||
def test_property_5_loading_state_management(self, action, loading_state, parameters, variables):
|
||||
"""
|
||||
Property 5: Gestion des états de chargement VWB
|
||||
|
||||
Pour toute action VWB en cours de chargement, le système doit afficher
|
||||
un indicateur de chargement approprié.
|
||||
"""
|
||||
note(f"Testing loading states - Loading: {loading_state.get('isLoading')}, Error: {loading_state.get('hasError')}")
|
||||
|
||||
script = self.helper.create_test_script(action, loading_state, parameters, variables)
|
||||
results = self.helper.run_test_script(script)
|
||||
|
||||
assert results.get('success', False), f"Test failed: {results.get('error')}"
|
||||
|
||||
# Property 5.1: Gestion de l'état de chargement
|
||||
state_handling = results.get('loadingStateHandling', {})
|
||||
|
||||
assert state_handling.get('canRender', False), "Composant ne peut pas se rendre"
|
||||
assert state_handling.get('showsAppropriateUI', False), "Interface utilisateur inappropriée"
|
||||
assert state_handling.get('providesUserFeedback', False), "Pas de feedback utilisateur"
|
||||
|
||||
# Property 5.2: États spécifiques
|
||||
current_state = state_handling.get('currentState', 'unknown')
|
||||
|
||||
if loading_state.get('isLoading', False):
|
||||
assert current_state == 'loading', f"État incorrect pendant le chargement: {current_state}"
|
||||
elif loading_state.get('hasError', False):
|
||||
assert current_state == 'error', f"État incorrect en cas d'erreur: {current_state}"
|
||||
assert state_handling.get('hasRecoveryOptions', False), "Options de récupération manquantes"
|
||||
elif action is None:
|
||||
assert current_state == 'not_found', f"État incorrect pour action manquante: {current_state}"
|
||||
assert state_handling.get('hasRecoveryOptions', False), "Options de récupération manquantes"
|
||||
else:
|
||||
assert current_state == 'loaded', f"État incorrect pour action chargée: {current_state}"
|
||||
|
||||
# Property 5.3: Cohérence globale des états
|
||||
consistency = results.get('consistency', {})
|
||||
assert consistency.get('stateMatchesData', False), "État incohérent avec les données"
|
||||
|
||||
@given(
|
||||
action=vwb_action_strategy(),
|
||||
parameters=st.dictionaries(st.text(min_size=3, max_size=20), st.one_of(st.text(), st.integers(), st.booleans()), max_size=4),
|
||||
variables=st.lists(variable_strategy(), max_size=3)
|
||||
)
|
||||
@PROPERTY_TEST_SETTINGS
|
||||
def test_property_vwb_parameter_validation(self, action, parameters, variables):
|
||||
"""
|
||||
Test de validation des paramètres VWB
|
||||
|
||||
Pour toute action VWB avec paramètres, le système doit :
|
||||
1. Valider les paramètres requis
|
||||
2. Valider les types de paramètres
|
||||
3. Fournir des messages d'erreur appropriés
|
||||
"""
|
||||
note(f"Testing parameter validation for action: {action['id']}")
|
||||
note(f"Parameters: {list(parameters.keys())}")
|
||||
|
||||
# État normal (pas de chargement, pas d'erreur)
|
||||
loading_state = {
|
||||
'isLoading': False,
|
||||
'hasError': False,
|
||||
'errorMessage': None,
|
||||
'stepType': action['id']
|
||||
}
|
||||
|
||||
script = self.helper.create_test_script(action, loading_state, parameters, variables)
|
||||
results = self.helper.run_test_script(script)
|
||||
|
||||
assert results.get('success', False), f"Test failed: {results.get('error')}"
|
||||
|
||||
# Vérifier la validation des paramètres
|
||||
validation = results.get('parameterValidation', {})
|
||||
|
||||
assert validation.get('validationPossible', False), "Validation impossible avec action présente"
|
||||
assert isinstance(validation.get('errorCount', -1), int), "Nombre d'erreurs invalide"
|
||||
assert isinstance(validation.get('warningCount', -1), int), "Nombre d'avertissements invalide"
|
||||
|
||||
# Vérifier la cohérence de la validation
|
||||
if validation.get('errorCount', 0) > 0:
|
||||
assert not validation.get('is_valid', True), "Validation marquée valide malgré les erreurs"
|
||||
|
||||
@given(
|
||||
loading_state=loading_state_strategy(),
|
||||
parameters=st.dictionaries(st.text(min_size=3, max_size=15), st.text(max_size=50), max_size=3)
|
||||
)
|
||||
@PROPERTY_TEST_SETTINGS
|
||||
def test_property_alternative_actions_suggestions(self, loading_state, parameters):
|
||||
"""
|
||||
Test des suggestions d'actions alternatives
|
||||
|
||||
Quand une action VWB n'est pas disponible, le système doit :
|
||||
1. Suggérer des actions alternatives appropriées
|
||||
2. Basées sur le type d'étape détecté
|
||||
3. Permettre la configuration manuelle
|
||||
"""
|
||||
note(f"Testing alternatives for step type: {loading_state.get('stepType')}")
|
||||
|
||||
# Forcer l'absence d'action pour tester les alternatives
|
||||
action = None
|
||||
variables = []
|
||||
|
||||
script = self.helper.create_test_script(action, loading_state, parameters, variables)
|
||||
results = self.helper.run_test_script(script)
|
||||
|
||||
assert results.get('success', False), f"Test failed: {results.get('error')}"
|
||||
|
||||
# Vérifier les alternatives
|
||||
alternatives = results.get('alternatives', {})
|
||||
|
||||
assert alternatives.get('count', 0) > 0, "Aucune alternative suggérée"
|
||||
assert alternatives.get('hasAlternatives', False), "Flag d'alternatives incorrect"
|
||||
|
||||
# Vérifier que les alternatives sont appropriées
|
||||
alternative_list = alternatives.get('alternatives', [])
|
||||
assert len(alternative_list) > 0, "Liste d'alternatives vide"
|
||||
|
||||
for alt in alternative_list:
|
||||
assert 'name' in alt, "Alternative sans nom"
|
||||
assert 'description' in alt, "Alternative sans description"
|
||||
|
||||
class VWBActionPropertiesStateMachine(RuleBasedStateMachine):
|
||||
"""Machine à états pour tester les propriétés de VWBActionProperties"""
|
||||
|
||||
actions = Bundle('actions')
|
||||
states = Bundle('states')
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.helper = VWBActionPropertiesTestHelper()
|
||||
self.test_results = []
|
||||
self.current_actions = []
|
||||
|
||||
@initialize()
|
||||
def setup(self):
|
||||
"""Initialisation de la machine à états"""
|
||||
pass
|
||||
|
||||
@rule(target=actions, action=vwb_action_strategy())
|
||||
def add_action(self, action):
|
||||
"""Ajoute une action VWB"""
|
||||
self.current_actions.append(action)
|
||||
return action
|
||||
|
||||
@rule(
|
||||
action=st.one_of(st.none(), actions),
|
||||
loading_state=loading_state_strategy(),
|
||||
parameters=st.dictionaries(st.text(min_size=3, max_size=15), st.text(max_size=30), max_size=3)
|
||||
)
|
||||
def test_action_properties(self, action, loading_state, parameters):
|
||||
"""Teste les propriétés avec une action"""
|
||||
script = self.helper.create_test_script(action, loading_state, parameters, [])
|
||||
results = self.helper.run_test_script(script)
|
||||
|
||||
self.test_results.append(results)
|
||||
|
||||
# Vérifications d'état
|
||||
if results.get('success'):
|
||||
detection = results.get('vwbDetection', {})
|
||||
state_handling = results.get('loadingStateHandling', {})
|
||||
|
||||
assert state_handling.get('canRender', False), "Rendu impossible"
|
||||
|
||||
if action:
|
||||
assert detection.get('isVWBAction', False), "Détection VWB échouée"
|
||||
|
||||
@invariant()
|
||||
def all_tests_successful(self):
|
||||
"""Invariant: tous les tests doivent réussir"""
|
||||
for result in self.test_results:
|
||||
if not result.get('success', False):
|
||||
assert False, f"Test failed: {result.get('error', 'Unknown error')}"
|
||||
|
||||
# Configuration de la machine à états
|
||||
TestVWBActionPropertiesStateMachine = VWBActionPropertiesStateMachine.TestCase
|
||||
|
||||
def test_vwb_action_properties_comprehensive():
|
||||
"""Test complet des propriétés de VWBActionProperties"""
|
||||
helper = VWBActionPropertiesTestHelper()
|
||||
|
||||
# Test de base avec action complète
|
||||
basic_action = {
|
||||
'id': 'click_anchor',
|
||||
'name': 'Clic sur ancre visuelle',
|
||||
'description': 'Clique sur un élément identifié visuellement',
|
||||
'category': 'interaction',
|
||||
'parameters': {
|
||||
'target_anchor': {
|
||||
'type': 'VWBVisualAnchor',
|
||||
'required': True,
|
||||
'description': 'Élément cible à cliquer'
|
||||
},
|
||||
'confidence_threshold': {
|
||||
'type': 'number',
|
||||
'required': False,
|
||||
'description': 'Seuil de confiance',
|
||||
'default': 0.8,
|
||||
'min': 0.5,
|
||||
'max': 1.0
|
||||
}
|
||||
},
|
||||
'examples': [],
|
||||
'version': '1.0.0',
|
||||
'tags': ['interaction', 'click']
|
||||
}
|
||||
|
||||
basic_loading_state = {
|
||||
'isLoading': False,
|
||||
'hasError': False,
|
||||
'errorMessage': None,
|
||||
'stepType': 'click_anchor'
|
||||
}
|
||||
|
||||
basic_parameters = {
|
||||
'target_anchor': None,
|
||||
'confidence_threshold': 0.8
|
||||
}
|
||||
|
||||
script = helper.create_test_script(basic_action, basic_loading_state, basic_parameters, [])
|
||||
results = helper.run_test_script(script)
|
||||
|
||||
assert results.get('success', False), f"Basic test failed: {results.get('error')}"
|
||||
assert results.get('vwbDetection', {}).get('isVWBAction', False), "VWB detection failed"
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Exécution directe pour tests rapides
|
||||
test_vwb_action_properties_comprehensive()
|
||||
print("✅ Tests de propriétés VWBActionProperties - Tous les tests passent")
|
||||
Reference in New Issue
Block a user