<# .SYNOPSIS ObsiGate — E2E locaux (Playwright) sous Windows/PowerShell. .DESCRIPTION Équivalent PowerShell de `scripts/run-e2e-local.sh`, pour les postes Windows où `bash` n'est pas utilisable (WSL indisponible, git-bash bloqué par une politique de contrôle d'application). Démarre le backend nativement via uvicorn (auth désactivée, fixtures TestVault/TestDir, port 2029 — mêmes conditions que le job CI `e2e`), lance la suite Playwright puis nettoie. .PARAMETER PlaywrightArgs Arguments transmis à `playwright test` (via `node`), ex. `-g "image viewer"`, `--headed`. .EXAMPLE ./scripts/run-e2e-local.ps1 ./scripts/run-e2e-local.ps1 -g "BUG-072" ./scripts/run-e2e-local.ps1 --headed #> [CmdletBinding()] param( [Parameter(ValueFromRemainingArguments = $true)] [string[]]$PlaywrightArgs ) $ErrorActionPreference = "Stop" $Root = Split-Path -Parent $PSScriptRoot Set-Location -LiteralPath $Root $Port = if ($env:E2E_PORT) { $env:E2E_PORT } else { "2029" } $BaseUrl = "http://127.0.0.1:$Port" $ServerLog = "data/e2e-server.log" $ServerErrLog = "data/e2e-server.err.log" # BUG-080 : garde-fous anti-blocage (un run E2E ne doit jamais pendre toute la nuit). # E2E_TIMEOUT_SEC dépasse volontairement le globalTimeout Playwright (25 min en # local) pour que ce soit Playwright qui abandonne proprement (avec rapport) en premier. $TestTimeoutSec = if ($env:E2E_TIMEOUT_SEC) { [int]$env:E2E_TIMEOUT_SEC } else { 1800 } $BrowserTimeoutSec = if ($env:E2E_BROWSER_INSTALL_TIMEOUT_SEC) { [int]$env:E2E_BROWSER_INSTALL_TIMEOUT_SEC } else { 600 } function Invoke-NativeWithTimeout([string]$Label, [int]$TimeoutSec, [string]$Exe, [string[]]$Arguments) { # Lance un processus natif en gardant la sortie console en direct, et le # tue après $TimeoutSec s'il n'a pas terminé (exit 124, comme `timeout`). # NOTE : le paramètre NE DOIT PAS s'appeler `$Args` (variable automatique # PowerShell qui l'écraserait → `node` lancé sans arguments, exit 0 # silencieux immédiat en lisant un stdin vide). $stamp = Get-Date -Format "HH:mm:ss" Write-Host "[$stamp] $Label (timeout ${TimeoutSec}s)..." $proc = Start-Process -FilePath $Exe -ArgumentList $Arguments -NoNewWindow -PassThru $proc | Wait-Process -Timeout $TimeoutSec -ErrorAction SilentlyContinue if (-not $proc.HasExited) { Write-Host "[ERR] $Label : timeout après ${TimeoutSec}s, arrêt du processus (PID $($proc.Id))." Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue return 124 } return $proc.ExitCode } function Test-ChromiumInstalled { $base = Join-Path $env:USERPROFILE "AppData\Local\ms-playwright" if (-not (Test-Path -LiteralPath $base)) { return $false } $hit = Get-ChildItem -LiteralPath $base -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "chromium-*" } | Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName "chrome-win\chrome.exe") } | Select-Object -First 1 return ($null -ne $hit) } function Assert-Command([string]$Name, [string]$Hint) { if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { throw "[ERR] $Name introuvable. $Hint" } } Assert-Command "uv" "Installez-le : https://docs.astral.sh/uv/" Assert-Command "node" "Installez Node.js (>= 20)." # ----- Venv Python 3.11 (créé une seule fois) ----- $Python = ".venv-e2e/Scripts/python.exe" if (-not (Test-Path -LiteralPath $Python)) { Write-Host "[INFO] Création du venv .venv-e2e (Python 3.11)..." uv venv .venv-e2e --python 3.11 uv pip install --python $Python -r backend/requirements.txt } # ----- Port déjà occupé ? ----- try { Invoke-WebRequest -Uri "$BaseUrl/api/health" -TimeoutSec 2 -UseBasicParsing | Out-Null Write-Host "[ERR] Quelque chose répond déjà sur $BaseUrl." Write-Host " Arrêtez-le ou choisissez un autre port : `$env:E2E_PORT=2030; ./scripts/run-e2e-local.ps1" exit 1 } catch { # port libre } # ----- Démarrage du serveur (mêmes conditions que le CI e2e) ----- Write-Host "[INFO] Démarrage d'ObsiGate sur $BaseUrl (auth désactivée)..." New-Item -ItemType Directory -Force -Path "data" | Out-Null $env:OBSIGATE_AUTH_ENABLED = "false" $env:VAULT_1_NAME = "TestVault" $env:VAULT_1_PATH = (Resolve-Path -LiteralPath "test_vault").Path $env:DIR_1_NAME = "TestDir" $env:DIR_1_PATH = (Resolve-Path -LiteralPath "test_dir").Path $server = Start-Process -FilePath $Python ` -ArgumentList "-m", "uvicorn", "backend.main:app", "--host", "127.0.0.1", "--port", $Port ` -RedirectStandardOutput $ServerLog -RedirectStandardError $ServerErrLog ` -PassThru -WindowStyle Hidden $exitCode = 1 try { # ----- Attente du health check (30 s max, comme le CI) ----- $ready = $false for ($i = 0; $i -lt 30; $i++) { try { Invoke-WebRequest -Uri "$BaseUrl/api/health" -TimeoutSec 2 -UseBasicParsing | Out-Null $ready = $true break } catch { if ($server.HasExited) { Write-Host "[ERR] Le serveur a quitté prématurément. Log :" Get-Content -LiteralPath $ServerErrLog -Tail 30 -ErrorAction SilentlyContinue exit 1 } Start-Sleep -Seconds 1 } } if (-not $ready) { Write-Host "[ERR] Serveur injoignable sur $BaseUrl. Log :" Get-Content -LiteralPath $ServerErrLog -Tail 30 -ErrorAction SilentlyContinue exit 1 } Write-Host "[OK] Serveur prêt." # ----- Playwright via node direct (pas npx) ----- # BUG-080 : `Start-Process` ne peut pas lancer `npx` (ni le `.ps1` ni le # `.cmd` ne sont des applications Win32 directes) → on appelle la CLI # locale via `node.exe`, sans prompt interactif possible. Skip de # l'install si un chromium est déjà présent (sauf E2E_INSTALL_BROWSERS=1), # timeouts dédiés sur chaque étape. $PlaywrightCli = Join-Path $Root "node_modules/@playwright/test/cli.js" if (-not (Test-Path -LiteralPath $PlaywrightCli)) { throw "[ERR] $PlaywrightCli introuvable. Lancez d'abord : npm ci" } if (($env:E2E_INSTALL_BROWSERS -eq "1") -or (-not (Test-ChromiumInstalled))) { $code = Invoke-NativeWithTimeout "playwright install chromium" $BrowserTimeoutSec "node" @($PlaywrightCli, "install", "chromium") if ($code -ne 0) { exit $code } } else { Write-Host "[INFO] Chromium Playwright déjà installé, étape sautée (E2E_INSTALL_BROWSERS=1 pour forcer)." } # ----- Exécution de la suite (projet CI : chromium-desktop) ----- Write-Host "[INFO] BASE_URL=$BaseUrl node $PlaywrightCli test --project=chromium-desktop $($PlaywrightArgs -join ' ')" $env:BASE_URL = $BaseUrl $testArgs = @($PlaywrightCli, "test", "--project=chromium-desktop") if ($PlaywrightArgs) { $testArgs += @($PlaywrightArgs) } $exitCode = Invoke-NativeWithTimeout "playwright test" $TestTimeoutSec "node" $testArgs } catch { Write-Host "[ERR] $($_.Exception.Message)" $exitCode = 1 } finally { Write-Host "[INFO] Arrêt du serveur (PID $($server.Id))..." if (-not $server.HasExited) { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue } # uvicorn (via uv) peut lancer un interpréteur enfant : tuer le groupe resté sur le port. Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue } } exit $exitCode