""" Fixtures pytest pour les tests E2E : serveur PHP local + page Playwright. Le serveur de développement PHP (`php -S`) est démarré une fois par session de test sur 127.0.0.1:8000 (ou sur un port libre si 8000 est déjà pris). Les appels aux API distantes (PeerTube, Castopod) passent par le vrai réseau : le premier chargement remplit le cache du site (cache/api), ce qui peut prendre un certain temps. Lancement : /tmp/test-venv/bin/python -m pytest tests/e2e/ """ import os import re import socket import subprocess import tempfile import time import urllib.request from pathlib import Path import pytest from playwright.sync_api import sync_playwright PROJECT_ROOT = Path(__file__).resolve().parents[2] SERVER_HOST = "127.0.0.1" DEFAULT_PORT = 8000 STARTUP_TIMEOUT = 30 # secondes pour que php -S accepte des connexions WARMUP_TIMEOUT = 90 # premier hit : remplit les caches API (Castopod/PeerTube) PAGE_TIMEOUT_MS = 60000 def _port_libre(port): """Retourne True si aucun service n'écoute sur le port donné.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex((SERVER_HOST, port)) != 0 def _trouver_port(): """Utilise le port 8000 si possible, sinon un port libre quelconque.""" if _port_libre(DEFAULT_PORT): return DEFAULT_PORT with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((SERVER_HOST, 0)) return s.getsockname()[1] def lire_constante_config(nom, defaut=None): """ Lit une constante define('NOM', 'valeur') dans la configuration PHP. Cherche dans config.default.php puis dans config.local.php (qui a le dernier mot, comme dans includes/config.php). Ne gère que les valeurs chaînes simples — suffisant pour SITE_NAME, PEERTUBE_URL, etc. """ valeur = defaut for fichier in ("config.default.php", "config.local.php"): chemin = PROJECT_ROOT / "includes" / fichier if not chemin.is_file(): continue texte = chemin.read_text(encoding="utf-8") motif = r"define\(\s*'" + re.escape(nom) + r"'\s*,\s*'((?:[^'\\]|\\.)*)'\s*\)" m = re.search(motif, texte) if m: valeur = m.group(1).replace("\\'", "'").replace("\\\\", "\\") return valeur @pytest.fixture(scope="session") def base_url(): """Démarre `php -S` à la racine du projet et fournit l'URL de base.""" port = _trouver_port() url = f"http://{SERVER_HOST}:{port}" # Les tokens CSRF stateless sont signés avec CSRF_SECRET. Sans configuration # locale, getCsrfSecret() génère un secret éphémère par processus, ce qui # rend les tokens invalides d'une requête à l'autre sous `php -S`. # On crée donc une configuration locale temporaire avec un secret fixe. config_local = PROJECT_ROOT / "includes" / "config.local.php" config_local_exists = config_local.exists() if not config_local_exists: config_local.write_text( "