- 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>
779 lines
31 KiB
Python
779 lines
31 KiB
Python
#!/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") |