param( [int]$BrowserPort = 0 ) $ErrorActionPreference = 'Stop' $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path $frontendRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $tempCacheRoot = Join-Path $env:TEMP 'ums-e2e-cache' $goCacheDir = Join-Path $tempCacheRoot 'go-build' $goModCacheDir = Join-Path $tempCacheRoot 'gomod' $goPathDir = Join-Path $tempCacheRoot 'gopath' $serverExePath = Join-Path $env:TEMP ("ums-server-smoke-" + [guid]::NewGuid().ToString('N') + '.exe') New-Item -ItemType Directory -Force $goCacheDir, $goModCacheDir, $goPathDir | Out-Null function Test-UrlReady { param( [Parameter(Mandatory = $true)][string]$Url ) try { $response = Invoke-WebRequest $Url -UseBasicParsing -TimeoutSec 2 return $response.StatusCode -ge 200 -and $response.StatusCode -lt 500 } catch { return $false } } function Wait-UrlReady { param( [Parameter(Mandatory = $true)][string]$Url, [Parameter(Mandatory = $true)][string]$Label, [int]$RetryCount = 120, [int]$DelayMs = 500 ) for ($i = 0; $i -lt $RetryCount; $i++) { if (Test-UrlReady -Url $Url) { return } Start-Sleep -Milliseconds $DelayMs } throw "$Label did not become ready: $Url" } function Start-ManagedProcess { param( [Parameter(Mandatory = $true)][string]$Name, [Parameter(Mandatory = $true)][string]$FilePath, [string[]]$ArgumentList = @(), [Parameter(Mandatory = $true)][string]$WorkingDirectory ) $stdoutPath = Join-Path $env:TEMP "$Name-stdout.log" $stderrPath = Join-Path $env:TEMP "$Name-stderr.log" Remove-Item $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue if ($ArgumentList -and $ArgumentList.Count -gt 0) { $process = Start-Process ` -FilePath $FilePath ` -ArgumentList $ArgumentList ` -WorkingDirectory $WorkingDirectory ` -PassThru ` -WindowStyle Hidden ` -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath } else { $process = Start-Process ` -FilePath $FilePath ` -WorkingDirectory $WorkingDirectory ` -PassThru ` -WindowStyle Hidden ` -RedirectStandardOutput $stdoutPath ` -RedirectStandardError $stderrPath } return [pscustomobject]@{ Name = $Name Process = $process StdOut = $stdoutPath StdErr = $stderrPath } } function Stop-ManagedProcess { param( [Parameter(Mandatory = $false)]$Handle ) if (-not $Handle) { return } if ($Handle.Process -and -not $Handle.Process.HasExited) { try { taskkill /PID $Handle.Process.Id /T /F *> $null } catch { Stop-Process -Id $Handle.Process.Id -Force -ErrorAction SilentlyContinue } } } function Show-ManagedProcessLogs { param( [Parameter(Mandatory = $false)]$Handle ) if (-not $Handle) { return } if (Test-Path $Handle.StdOut) { Get-Content $Handle.StdOut -ErrorAction SilentlyContinue } if (Test-Path $Handle.StdErr) { Get-Content $Handle.StdErr -ErrorAction SilentlyContinue } } function Remove-ManagedProcessLogs { param( [Parameter(Mandatory = $false)]$Handle ) if (-not $Handle) { return } Remove-Item $Handle.StdOut, $Handle.StdErr -Force -ErrorAction SilentlyContinue } $backendHandle = $null $frontendHandle = $null $startedBackend = $false $startedFrontend = $false try { Push-Location $projectRoot try { $env:GOCACHE = $goCacheDir $env:GOMODCACHE = $goModCacheDir $env:GOPATH = $goPathDir go build -o $serverExePath .\cmd\server\main.go if ($LASTEXITCODE -ne 0) { throw 'server build failed' } } finally { Pop-Location Remove-Item Env:GOCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOMODCACHE -ErrorAction SilentlyContinue Remove-Item Env:GOPATH -ErrorAction SilentlyContinue } if (-not (Test-UrlReady -Url 'http://127.0.0.1:8080/health')) { $backendHandle = Start-ManagedProcess ` -Name 'ums-backend-smoke' ` -FilePath $serverExePath ` -WorkingDirectory $projectRoot $startedBackend = $true try { Wait-UrlReady -Url 'http://127.0.0.1:8080/health' -Label 'backend smoke' } catch { Show-ManagedProcessLogs $backendHandle throw } } if (-not (Test-UrlReady -Url 'http://127.0.0.1:3000')) { $frontendHandle = Start-ManagedProcess ` -Name 'ums-frontend-smoke' ` -FilePath 'npm.cmd' ` -ArgumentList @('run', 'dev', '--', '--host', '127.0.0.1', '--port', '3000') ` -WorkingDirectory $frontendRoot $startedFrontend = $true try { Wait-UrlReady -Url 'http://127.0.0.1:3000' -Label 'frontend smoke' } catch { Show-ManagedProcessLogs $frontendHandle throw } } Push-Location $frontendRoot try { & (Join-Path $PSScriptRoot 'run-cdp-smoke.ps1') -Port $BrowserPort } finally { Pop-Location } } finally { if ($startedFrontend) { Stop-ManagedProcess $frontendHandle Remove-ManagedProcessLogs $frontendHandle } if ($startedBackend) { Stop-ManagedProcess $backendHandle Remove-ManagedProcessLogs $backendHandle } Remove-Item $serverExePath -Force -ErrorAction SilentlyContinue }