Modules/businessdev.ALbuild.Containers/Private/Invoke-BcCaptureEngine.ps1

function Invoke-BcCaptureEngine {
    <#
    .SYNOPSIS
        Runs capture.js over a job and returns its events, with the password kept out of the command line.
 
    .DESCRIPTION
        The transport between PowerShell and the browser:
 
          * the job goes out as a FILE (its path is the only argument),
          * the password goes out as an ENVIRONMENT VARIABLE, because a command line is readable by
            every process on the host,
          * the results come back as NDJSON on stdout - one complete JSON object per line, so a long
            run can be streamed rather than buffered,
          * the PNGs never travel through here at all; the engine writes them and reports paths.
 
        PLAYWRIGHT_BROWSERS_PATH is set for the child and restored afterwards, so a long-lived session
        (the MCP server) is not left with a global pointing at one version's browser store.
 
    .PARAMETER Job
        The job as a hashtable; it is serialised to a temporary file.
 
    .PARAMETER Credential
        The container credentials. The password is read out of them here and handed to the child
        through the environment - it is never logged and never placed in an argument.
 
    .PARAMETER Browser
        The Get-BcCaptureBrowser result identifying node, the install and the browser store.
 
    .PARAMETER OnEvent
        Optional scriptblock invoked with each parsed event as it is read - the streaming hook for a
        caller that wants progress rather than a verdict.
 
    .OUTPUTS
        PSCustomObject with Events, Done, Errors, Captures and ExitCode.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [hashtable] $Job,
        [Parameter(Mandatory)] [PSCredential] $Credential,
        [Parameter(Mandatory)] [PSCustomObject] $Browser,
        [scriptblock] $OnEvent
    )

    $resource = Get-BcCaptureResource
    $engine = Join-Path -Path $resource.Folder -ChildPath 'capture.js'
    if (-not (Test-Path -LiteralPath $engine)) {
        throw "The capture engine is missing from the module ('$engine'). Reinstall businessdev.ALbuild."
    }

    $jobFile = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("albuild-capture-$([guid]::NewGuid().ToString('N')).json")
    $Job | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $jobFile -Encoding UTF8

    $previousPassword = $env:ALBUILD_CAPTURE_PASSWORD
    $previousBrowsers = $env:PLAYWRIGHT_BROWSERS_PATH
    try {
        $env:ALBUILD_CAPTURE_PASSWORD = $Credential.GetNetworkCredential().Password
        $env:PLAYWRIGHT_BROWSERS_PATH = $Browser.BrowsersPath

        # Exit 1 means "some target failed", which is a result to read, not a launch failure - so it
        # is a success exit code here and the events decide.
        $result = Invoke-ALbuildProcess -FilePath $Browser.NodePath -Arguments @($engine, $jobFile) `
            -PassThru -SuccessExitCodes @(0, 1)
    }
    finally {
        $env:ALBUILD_CAPTURE_PASSWORD = $previousPassword
        $env:PLAYWRIGHT_BROWSERS_PATH = $previousBrowsers
        Remove-Item -LiteralPath $jobFile -Force -ErrorAction SilentlyContinue
    }

    $events = [System.Collections.Generic.List[object]]::new()
    foreach ($line in ("$($result.StdOut)" -split "`r?`n")) {
        if (-not $line.Trim()) { continue }
        try { $parsed = $line | ConvertFrom-Json } catch { continue }
        $events.Add($parsed)
        if ($OnEvent) { & $OnEvent $parsed }
    }

    # Nothing on stdout at all means node never got as far as the engine. Say that, with whatever it
    # wrote to stderr, instead of reporting "no captures" about a run that never started.
    if ($events.Count -eq 0) {
        $stderr = Format-BcRedactedText -Text "$($result.StdErr)"
        throw "CAPTURE_FAILED: the capture engine produced no output [exit $($result.ExitCode)]. $stderr"
    }

    [PSCustomObject]@{
        ExitCode = $result.ExitCode
        Events   = @($events)
        Done     = @($events | Where-Object { $_.type -eq 'done' })[0]
        Errors   = @($events | Where-Object { $_.type -eq 'error' })
        Captures = @($events | Where-Object { $_.type -eq 'capture' })
    }
}