Results: - XGBoost (Optuna 100 trials): AUC=0.7856, Precision@3=0.5783 - LightGBM (Optuna 100 trials): AUC=0.7833, Precision@3=0.5736 - MLP (3 layers 256-128-64): AUC=0.7743, Precision@3=0.5643 - Ensemble (weighted voting): AUC=0.7840, Precision@3=0.5814 Baseline XGBoost: Precision@3=0.5287 Delta: +0.0527 (+5.3%) — DEPLOY threshold met (+5%) Latency: 35ms/race, 69ms/full-day (well under 200ms limit) SHAP: 31/43 features selected, top features: rang_cote, implied_prob, cote_direct, ratio_cote_field All 12 regression/latency tests passing. Co-Authored-By: Paperclip <noreply@paperclip.ing>
125 lines
4.0 KiB
Python
125 lines
4.0 KiB
Python
"""
|
|
conftest.py — Configuration pytest globale
|
|
SaaS Turf Prédictions IA — Sprint 8 QA
|
|
Ticket: HRT-34
|
|
"""
|
|
|
|
import os
|
|
import asyncio
|
|
import pytest
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# ============================================================
|
|
# Répertoires de sortie
|
|
# ============================================================
|
|
|
|
REPORTS_DIR = Path("tests/reports")
|
|
SCREENSHOTS_DIR = Path("tests/screenshots")
|
|
|
|
for d in [REPORTS_DIR, SCREENSHOTS_DIR]:
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
# ============================================================
|
|
# Variables d'environnement
|
|
# ============================================================
|
|
|
|
BASE_URL = os.environ.get("APP_URL", "http://localhost:8792")
|
|
|
|
|
|
# ============================================================
|
|
# Fixtures globales
|
|
# ============================================================
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def base_url():
|
|
return BASE_URL
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def event_loop():
|
|
"""Event loop partagé pour les tests async de la session."""
|
|
policy = asyncio.get_event_loop_policy()
|
|
loop = policy.new_event_loop()
|
|
yield loop
|
|
loop.close()
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def reports_dir():
|
|
return REPORTS_DIR
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def screenshots_dir():
|
|
return SCREENSHOTS_DIR
|
|
|
|
|
|
# ============================================================
|
|
# Hook : screenshot automatique sur échec
|
|
# ============================================================
|
|
|
|
|
|
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
|
def pytest_runtest_makereport(item, call):
|
|
"""Capture screenshot automatiquement sur tout test E2E en échec."""
|
|
outcome = yield
|
|
report = outcome.get_result()
|
|
|
|
if report.when == "call" and report.failed:
|
|
# Récupérer la page Playwright si disponible dans les fixtures
|
|
page = None
|
|
for fixture_name in ("page", "context_page"):
|
|
if fixture_name in item.funcargs:
|
|
val = item.funcargs[fixture_name]
|
|
if isinstance(val, tuple):
|
|
page = val[0] # (page, browser_name)
|
|
else:
|
|
page = val
|
|
break
|
|
|
|
if page is not None:
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
test_name = item.name.replace("/", "_").replace(":", "_")
|
|
screenshot_path = SCREENSHOTS_DIR / f"FAIL_{test_name}_{timestamp}.png"
|
|
try:
|
|
# Playwright page.screenshot est synchrone dans les fixtures sync
|
|
# Pour les fixtures async, on force la capture
|
|
import asyncio as _asyncio
|
|
|
|
if _asyncio.iscoroutinefunction(page.screenshot):
|
|
loop = _asyncio.get_event_loop()
|
|
loop.run_until_complete(page.screenshot(path=str(screenshot_path)))
|
|
else:
|
|
page.screenshot(path=str(screenshot_path))
|
|
report.sections.append(
|
|
("Screenshot", f"Sauvegardé : {screenshot_path}")
|
|
)
|
|
except Exception as e:
|
|
report.sections.append(
|
|
("Screenshot Error", f"Impossible de capturer : {e}")
|
|
)
|
|
|
|
|
|
# ============================================================
|
|
# Marqueurs personnalisés
|
|
# ============================================================
|
|
|
|
|
|
def pytest_configure(config):
|
|
config.addinivalue_line("markers", "e2e: Tests End-to-End Playwright")
|
|
config.addinivalue_line("markers", "load: Tests de charge Locust")
|
|
config.addinivalue_line("markers", "security: Tests de sécurité")
|
|
config.addinivalue_line(
|
|
"markers", "smoke: Tests rapides de smoke (sans infra complète)"
|
|
)
|
|
config.addinivalue_line("markers", "beta: Tests spécifiques beta fermée")
|
|
config.addinivalue_line(
|
|
"markers", "requires_billing: Nécessite HRT-31 (Billing Stripe)"
|
|
)
|
|
config.addinivalue_line(
|
|
"markers", "requires_infra: Nécessite HRT-33 (infra staging)"
|
|
)
|