fix: harnais E2E local anti-blocage BUG-080
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Garde-fous anti-blocage du harnais E2E local (BUG-080).
|
||||
|
||||
Contexte : un run `npm run test:e2e:ps` est resté pendu toute la nuit —
|
||||
serveurs orphelins sur le port 2029, `npx` sans `--yes` (prompt interactif
|
||||
qui attend indéfiniment), installation des navigateurs systématique et suite
|
||||
Playwright (~130 tests, workers: 1) sans aucun timeout global.
|
||||
|
||||
Ces tests statiques vérifient que chaque couche du harnais possède son
|
||||
garde-fou, afin qu'un run E2E échoue vite au lieu de bloquer indéfiniment.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _read(rel: str) -> str:
|
||||
return (REPO_ROOT / rel).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
class TestE2ELocalPs:
|
||||
SCRIPT = "scripts/run-e2e-local.ps1"
|
||||
|
||||
def test_npx_never_prompts(self):
|
||||
"""`npx --yes` partout : aucun prompt « Ok to proceed? » qui pend."""
|
||||
content = _read(self.SCRIPT)
|
||||
# Aucune invocation nue `npx ...` / `& npx ...` (toujours via le helper
|
||||
# avec `--yes`) ; les mentions dans commentaires/Write-Host sont OK.
|
||||
bare = [
|
||||
line.strip()
|
||||
for line in content.splitlines()
|
||||
if re.match(r"^\s*(?:&\s*)?npx\s", line)
|
||||
]
|
||||
assert not bare, f"invocations npx nues (sans --yes) : {bare}"
|
||||
assert content.count("--yes") >= 2, "au moins install + test en --yes"
|
||||
|
||||
def test_browser_install_skippable(self):
|
||||
"""Install navigateurs sautée si chromium déjà présent (sauf forçage)."""
|
||||
content = _read(self.SCRIPT)
|
||||
assert "Test-ChromiumInstalled" in content
|
||||
assert "E2E_INSTALL_BROWSERS" in content
|
||||
|
||||
def test_test_step_has_timeout(self):
|
||||
"""L'étape `playwright test` est bornée (E2E_TIMEOUT_SEC, défaut 900)."""
|
||||
content = _read(self.SCRIPT)
|
||||
assert "E2E_TIMEOUT_SEC" in content
|
||||
assert "Wait-Process -Timeout" in content
|
||||
assert re.search(r"E2E_TIMEOUT_SEC.*else\s*\{\s*900\s*\}", content), (
|
||||
"défaut E2E_TIMEOUT_SEC=900 attendu"
|
||||
)
|
||||
|
||||
|
||||
class TestE2ELocalSh:
|
||||
SCRIPT = "scripts/run-e2e-local.sh"
|
||||
|
||||
def test_npx_never_prompts(self):
|
||||
content = _read(self.SCRIPT)
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("#") or stripped.startswith("echo") or "npx" not in stripped:
|
||||
continue
|
||||
if "playwright" in stripped:
|
||||
assert "--yes" in stripped, f"appel npx sans --yes : {stripped}"
|
||||
|
||||
def test_test_step_has_timeout(self):
|
||||
content = _read(self.SCRIPT)
|
||||
assert "E2E_TIMEOUT_SEC" in content
|
||||
assert "run_with_timeout" in content
|
||||
|
||||
|
||||
class TestE2EServerPs:
|
||||
SCRIPT = "scripts/e2e-server.ps1"
|
||||
|
||||
def test_pidfile_refreshed_with_port_owner(self):
|
||||
"""Le pidfile est resynchronisé sur le vrai PID d'écoute après READY."""
|
||||
content = _read(self.SCRIPT)
|
||||
assert "Get-PortOwner" in content
|
||||
ready_pos = content.find("[OK] READY")
|
||||
assert ready_pos != -1
|
||||
assert "Set-Content -LiteralPath $PidFile" in content[ready_pos - 600:ready_pos]
|
||||
|
||||
def test_stop_kills_process_tree(self):
|
||||
"""`stop` tue aussi les enfants du PID enregistré (pas d'orphelins)."""
|
||||
content = _read(self.SCRIPT)
|
||||
assert "ParentProcessId=$srvPid" in content
|
||||
|
||||
|
||||
class TestPlaywrightConfig:
|
||||
CONFIG = "playwright.config.ts"
|
||||
|
||||
def test_global_timeout_set(self):
|
||||
"""Timeout global : la suite abandonne au lieu de pendre toute la nuit."""
|
||||
content = _read(self.CONFIG)
|
||||
assert "globalTimeout" in content
|
||||
assert "E2E_GLOBAL_TIMEOUT_MS" in content
|
||||
Reference in New Issue
Block a user