feat: admin frontend - React + Vite, auth pages, user management, roles, permissions, webhooks, devices, logs
This commit is contained in:
397
frontend/admin/scripts/run-cdp-smoke.ps1
Normal file
397
frontend/admin/scripts/run-cdp-smoke.ps1
Normal file
@@ -0,0 +1,397 @@
|
||||
param(
|
||||
[int]$Port = 0,
|
||||
[string[]]$Command = @('node', './scripts/run-cdp-smoke.mjs')
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not $Command -or $Command.Count -eq 0) {
|
||||
throw 'Command must not be empty'
|
||||
}
|
||||
|
||||
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 = 60,
|
||||
[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 Get-FreeTcpPort {
|
||||
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
|
||||
$listener.Start()
|
||||
try {
|
||||
return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port
|
||||
} finally {
|
||||
$listener.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-BrowserPath {
|
||||
if ($env:E2E_BROWSER_PATH) {
|
||||
return $env:E2E_BROWSER_PATH
|
||||
}
|
||||
|
||||
if ($env:CHROME_HEADLESS_SHELL_PATH) {
|
||||
return $env:CHROME_HEADLESS_SHELL_PATH
|
||||
}
|
||||
|
||||
if ($env:PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH) {
|
||||
return $env:PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
|
||||
}
|
||||
|
||||
$baseDir = Join-Path $env:LOCALAPPDATA 'ms-playwright'
|
||||
$candidate = Get-ChildItem $baseDir -Directory -Filter 'chromium_headless_shell-*' |
|
||||
Sort-Object Name -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if ($candidate) {
|
||||
return (Join-Path $candidate.FullName 'chrome-headless-shell-win64\chrome-headless-shell.exe')
|
||||
}
|
||||
|
||||
foreach ($fallback in @(
|
||||
'C:\Program Files\Google\Chrome\Application\chrome.exe',
|
||||
'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe',
|
||||
'C:\Program Files\Microsoft\Edge\Application\msedge.exe',
|
||||
'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
|
||||
)) {
|
||||
if (Test-Path $fallback) {
|
||||
return $fallback
|
||||
}
|
||||
}
|
||||
|
||||
throw 'No compatible browser found; set E2E_BROWSER_PATH or CHROME_HEADLESS_SHELL_PATH explicitly if needed'
|
||||
}
|
||||
|
||||
function Test-HeadlessShellBrowser {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$BrowserPath
|
||||
)
|
||||
|
||||
return [System.IO.Path]::GetFileName($BrowserPath).ToLowerInvariant().Contains('headless-shell')
|
||||
}
|
||||
|
||||
function Get-BrowserArguments {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$BrowserPath,
|
||||
[Parameter(Mandatory = $true)][int]$Port,
|
||||
[Parameter(Mandatory = $true)][string]$ProfileDir
|
||||
)
|
||||
|
||||
$arguments = @(
|
||||
"--remote-debugging-port=$Port",
|
||||
"--user-data-dir=$ProfileDir",
|
||||
'--no-sandbox'
|
||||
)
|
||||
|
||||
if (Test-HeadlessShellBrowser -BrowserPath $BrowserPath) {
|
||||
$arguments += '--single-process'
|
||||
} else {
|
||||
$arguments += @(
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-background-networking',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-renderer-backgrounding',
|
||||
'--disable-sync',
|
||||
'--headless=new'
|
||||
)
|
||||
}
|
||||
|
||||
$arguments += 'about:blank'
|
||||
return $arguments
|
||||
}
|
||||
|
||||
function Get-BrowserProcessIds {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$BrowserPath
|
||||
)
|
||||
|
||||
$processName = [System.IO.Path]::GetFileNameWithoutExtension($BrowserPath)
|
||||
try {
|
||||
return @(Get-Process -Name $processName -ErrorAction Stop | Select-Object -ExpandProperty Id)
|
||||
} catch {
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BrowserProcessesByProfile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$BrowserPath,
|
||||
[Parameter(Mandatory = $true)][string]$ProfileDir
|
||||
)
|
||||
|
||||
$processFileName = [System.IO.Path]::GetFileName($BrowserPath)
|
||||
$profileFragment = $ProfileDir.ToLowerInvariant()
|
||||
|
||||
try {
|
||||
return @(
|
||||
Get-CimInstance Win32_Process -Filter ("Name = '{0}'" -f $processFileName) -ErrorAction Stop |
|
||||
Where-Object {
|
||||
$commandLine = $_.CommandLine
|
||||
$commandLine -and $commandLine.ToLowerInvariant().Contains($profileFragment)
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ChildProcessIds {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][int]$ParentId
|
||||
)
|
||||
|
||||
$pending = [System.Collections.Generic.Queue[int]]::new()
|
||||
$seen = [System.Collections.Generic.HashSet[int]]::new()
|
||||
$pending.Enqueue($ParentId)
|
||||
|
||||
while ($pending.Count -gt 0) {
|
||||
$currentParentId = $pending.Dequeue()
|
||||
|
||||
try {
|
||||
$children = @(Get-CimInstance Win32_Process -Filter ("ParentProcessId = {0}" -f $currentParentId) -ErrorAction Stop)
|
||||
} catch {
|
||||
$children = @()
|
||||
}
|
||||
|
||||
foreach ($child in $children) {
|
||||
if ($seen.Add([int]$child.ProcessId)) {
|
||||
$pending.Enqueue([int]$child.ProcessId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @($seen)
|
||||
}
|
||||
|
||||
function Get-BrowserCleanupIds {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]$Handle
|
||||
)
|
||||
|
||||
$ids = [System.Collections.Generic.HashSet[int]]::new()
|
||||
|
||||
if ($Handle.Process) {
|
||||
$null = $ids.Add([int]$Handle.Process.Id)
|
||||
foreach ($childId in Get-ChildProcessIds -ParentId $Handle.Process.Id) {
|
||||
$null = $ids.Add([int]$childId)
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($processInfo in Get-BrowserProcessesByProfile -BrowserPath $Handle.BrowserPath -ProfileDir $Handle.ProfileDir) {
|
||||
$null = $ids.Add([int]$processInfo.ProcessId)
|
||||
}
|
||||
|
||||
$liveIds = @()
|
||||
foreach ($processId in $ids) {
|
||||
try {
|
||||
Get-Process -Id $processId -ErrorAction Stop | Out-Null
|
||||
$liveIds += $processId
|
||||
} catch {
|
||||
# Process already exited.
|
||||
}
|
||||
}
|
||||
|
||||
return @($liveIds | Sort-Object -Unique)
|
||||
}
|
||||
|
||||
function Start-BrowserProcess {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$BrowserPath,
|
||||
[Parameter(Mandatory = $true)][int]$Port,
|
||||
[Parameter(Mandatory = $true)][string]$ProfileDir
|
||||
)
|
||||
|
||||
$baselineIds = Get-BrowserProcessIds -BrowserPath $BrowserPath
|
||||
$arguments = Get-BrowserArguments -BrowserPath $BrowserPath -Port $Port -ProfileDir $ProfileDir
|
||||
$stdoutPath = Join-Path $ProfileDir 'browser-stdout.log'
|
||||
$stderrPath = Join-Path $ProfileDir 'browser-stderr.log'
|
||||
Remove-Item $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$process = Start-Process `
|
||||
-FilePath $BrowserPath `
|
||||
-ArgumentList $arguments `
|
||||
-PassThru `
|
||||
-WindowStyle Hidden `
|
||||
-RedirectStandardOutput $stdoutPath `
|
||||
-RedirectStandardError $stderrPath
|
||||
|
||||
return [pscustomobject]@{
|
||||
BrowserPath = $BrowserPath
|
||||
BaselineIds = $baselineIds
|
||||
ProfileDir = $ProfileDir
|
||||
Process = $process
|
||||
StdOut = $stdoutPath
|
||||
StdErr = $stderrPath
|
||||
}
|
||||
}
|
||||
|
||||
function Show-BrowserLogs {
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]$Handle
|
||||
)
|
||||
|
||||
if (-not $Handle) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($path in @($Handle.StdOut, $Handle.StdErr)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($path) -and (Test-Path $path)) {
|
||||
Get-Content $path -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-BrowserProcess {
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]$Handle
|
||||
)
|
||||
|
||||
if (-not $Handle) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($Handle.Process -and -not $Handle.Process.HasExited) {
|
||||
foreach ($cleanupCommand in @(
|
||||
{ param($id) taskkill /PID $id /T /F *> $null },
|
||||
{ param($id) Stop-Process -Id $id -Force -ErrorAction Stop }
|
||||
)) {
|
||||
try {
|
||||
& $cleanupCommand $Handle.Process.Id
|
||||
} catch {
|
||||
# Ignore cleanup errors here; the residual PID check below is authoritative.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$residualIds = @()
|
||||
|
||||
for ($attempt = 0; $attempt -lt 12; $attempt++) {
|
||||
$residualIds = @(Get-BrowserCleanupIds -Handle $Handle)
|
||||
|
||||
foreach ($processId in $residualIds) {
|
||||
foreach ($cleanupCommand in @(
|
||||
{ param($id) taskkill /PID $id /T /F *> $null },
|
||||
{ param($id) Stop-Process -Id $id -Force -ErrorAction Stop }
|
||||
)) {
|
||||
try {
|
||||
& $cleanupCommand $processId
|
||||
} catch {
|
||||
# Ignore per-process cleanup errors during retry loop.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 500
|
||||
$residualIds = @(Get-BrowserCleanupIds -Handle $Handle)
|
||||
|
||||
if ($residualIds.Count -eq 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($residualIds.Count -gt 0) {
|
||||
throw "browser cleanup leaked PIDs: $($residualIds -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-BrowserLogs {
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]$Handle
|
||||
)
|
||||
|
||||
if (-not $Handle) {
|
||||
return
|
||||
}
|
||||
|
||||
$paths = @($Handle.StdOut, $Handle.StdErr) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
if ($paths.Count -gt 0) {
|
||||
Remove-Item $paths -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$browserPath = Resolve-BrowserPath
|
||||
Write-Host "CDP browser: $browserPath"
|
||||
$Port = if ($Port -gt 0) { $Port } else { Get-FreeTcpPort }
|
||||
$profileRoot = Join-Path (Resolve-Path (Join-Path $PSScriptRoot '..')).Path '.cache\cdp-profiles'
|
||||
New-Item -ItemType Directory -Force $profileRoot | Out-Null
|
||||
$profileDir = Join-Path $profileRoot "pw-profile-cdp-smoke-win-$Port"
|
||||
$browserReadyUrl = "http://127.0.0.1:$Port/json/version"
|
||||
$browserCdpBaseUrl = "http://127.0.0.1:$Port"
|
||||
$browserHandle = $null
|
||||
|
||||
try {
|
||||
for ($attempt = 1; $attempt -le 2; $attempt++) {
|
||||
Remove-Item -Recurse -Force $profileDir -ErrorAction SilentlyContinue
|
||||
$browserHandle = Start-BrowserProcess -BrowserPath $browserPath -Port $Port -ProfileDir $profileDir
|
||||
|
||||
try {
|
||||
Wait-UrlReady -Url $browserReadyUrl -Label "browser CDP endpoint (attempt $attempt)"
|
||||
Write-Host "CDP endpoint ready: $browserReadyUrl"
|
||||
break
|
||||
} catch {
|
||||
Show-BrowserLogs $browserHandle
|
||||
Stop-BrowserProcess $browserHandle
|
||||
Remove-BrowserLogs $browserHandle
|
||||
$browserHandle = $null
|
||||
|
||||
if ($attempt -eq 2) {
|
||||
throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $env:E2E_COMMAND_TIMEOUT_MS) {
|
||||
$env:E2E_COMMAND_TIMEOUT_MS = '120000'
|
||||
}
|
||||
|
||||
$env:E2E_SKIP_BROWSER_LAUNCH = '1'
|
||||
$env:E2E_CDP_PORT = "$Port"
|
||||
$env:E2E_CDP_BASE_URL = $browserCdpBaseUrl
|
||||
$env:E2E_PLAYWRIGHT_CDP_URL = $browserCdpBaseUrl
|
||||
$env:E2E_EXTERNAL_CDP = '1'
|
||||
|
||||
$commandName = $Command[0]
|
||||
$commandArgs = @()
|
||||
if ($Command.Count -gt 1) {
|
||||
$commandArgs = $Command[1..($Command.Count - 1)]
|
||||
}
|
||||
|
||||
Write-Host "Launching command: $commandName $($commandArgs -join ' ')"
|
||||
& $commandName @commandArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "command failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
} finally {
|
||||
Stop-BrowserProcess $browserHandle
|
||||
Remove-BrowserLogs $browserHandle
|
||||
Remove-Item Env:E2E_SKIP_BROWSER_LAUNCH -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:E2E_CDP_PORT -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:E2E_CDP_BASE_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:E2E_PLAYWRIGHT_CDP_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:E2E_EXTERNAL_CDP -ErrorAction SilentlyContinue
|
||||
Remove-Item -Recurse -Force $profileDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
Reference in New Issue
Block a user