Files
ObsiGate/tests/test_e2e_harness.py
T
bruno b6e2029770
CI / lint (push) Successful in 2m22s
CI / security (push) Successful in 1m37s
CI / test (push) Successful in 4m48s
CI / build (push) Successful in 1m33s
CI / e2e (push) Successful in 14m3s
fix: E2E node-direct, timeouts réalistes 25/30 min (complément BUG-080)
2026-09-27 11:28:35 -04:00

118 lines
4.5 KiB
Python

"""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_playwright_via_node_no_npx(self):
"""Playwright est lancé via `node` direct, jamais via `npx`.
`Start-Process` ne peut pas exécuter `npx` (ni le `.ps1` ni le
`.cmd` ne sont des applications Win32 directes : "%1 is not a valid
Win32 application"), et `npx` sans `--yes` peut pendre sur un prompt
interactif. Seules les mentions en commentaires/logs sont tolérées.
"""
content = _read(self.SCRIPT)
bare = [
line.strip()
for line in content.splitlines()
if re.match(r"^\s*(?:&\s*)?npx\s", line)
]
assert not bare, f"invocations npx nues : {bare}"
assert "node_modules/@playwright/test/cli.js" in content, (
"CLI Playwright locale attendue (via node)"
)
# `$Args` est une variable automatique PowerShell : un paramètre de
# ce nom serait écrasé (helper lancé sans arguments → exit 0 muet).
# (commentaires `#` exclus : la mise en garde elle-même le cite).
code_lines = [
line for line in content.splitlines()
if not line.strip().startswith("#")
]
assert not re.search(r"\$Args\b", "\n".join(code_lines)), (
"BUG-080 : paramètre `$Args` interdit (shadowing par $args automatique)"
)
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 1800).
Le défaut dépasse le globalTimeout Playwright (25 min en local) pour
que ce soit Playwright qui abandonne proprement (avec rapport) en premier.
"""
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*1800\s*\}", content), (
"défaut E2E_TIMEOUT_SEC=1800 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