Modules/businessdev.ALbuild.Containers/Public/Get-BcCaptureBrowser.ps1
|
function Get-BcCaptureBrowser { <# .SYNOPSIS Reports whether this host can capture Business Central web client screenshots. Changes nothing. .DESCRIPTION 'albuild screenshot' drives a headless browser through Playwright, which needs Node.js, the pinned Playwright package and a downloaded Chromium. Together that is a few hundred megabytes - too much to put on every build agent by reflex - so it is NOT installed with the module and NOT installed on first use. This function says whether it is there; Install-BcCaptureBrowser puts it there. The split is deliberate. 'albuild doctor' calls this, and a readiness check that installs half a gigabyte as a side effect would violate the first rule the CLI holds itself to: describing never acts. Nothing here is fatal. A missing component is REPORTED (Ready = $false, named in Missing, with a runnable Remedy) rather than thrown, because the caller asking is usually asking precisely because it does not know yet. WHERE THE BROWSER LIVES. Under the ALbuild base folder, per Playwright version - not in the user profile. The MCP server commonly runs as a Windows service under LocalSystem, and a browser under some interactive account's %LOCALAPPDATA% is invisible to it: the install looks like it worked and every capture then fails as if nothing were installed. .PARAMETER InstallPath Where the capture browser is expected. Default: <BaseFolder>\capture\<playwright-version>. .PARAMETER NodeExecutable The Node.js executable to probe (default 'node'). .OUTPUTS PSCustomObject with Ready, Missing, Remedy, NodePath, NodeVersion, PlaywrightVersion, PlaywrightPath, InstallPath, BrowsersPath and ChromiumPath. .EXAMPLE (Get-BcCaptureBrowser).Ready .EXAMPLE Get-BcCaptureBrowser | Format-List # Reports what is missing and the command that installs it. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [string] $InstallPath, [string] $NodeExecutable = 'node' ) # Playwright 1.63 needs Node 18+; below that npm resolves the package and the engine then fails at # runtime with a syntax error, which reads like a bug in the engine rather than an old runtime. $minimumNodeMajor = 18 $resource = Get-BcCaptureResource $pinned = $resource.PlaywrightVersion if (-not $InstallPath) { $baseFolder = Get-ALbuildConfig -Name BaseFolder $InstallPath = Join-Path -Path (Join-Path -Path $baseFolder -ChildPath 'capture') -ChildPath $pinned } $browsersPath = Join-Path -Path $InstallPath -ChildPath 'browsers' $missing = @() # --- Node.js ------------------------------------------------------------------------------------- $nodePath = $null $nodeVersion = $null $nodeCommand = Get-Command -Name $NodeExecutable -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if ($nodeCommand) { $nodePath = $nodeCommand.Source $probe = Invoke-ALbuildProcess -FilePath $nodePath -Arguments @('--version') -PassThru -SuccessExitCodes @(0) if ($probe.Success) { $nodeVersion = "$($probe.StdOut)".Trim() } } $nodeMajor = 0 if ($nodeVersion -match '^v?(\d+)') { $nodeMajor = [int] $Matches[1] } if (-not $nodeVersion -or $nodeMajor -lt $minimumNodeMajor) { $missing += 'node' } # --- Playwright, at the pinned version ----------------------------------------------------------- # The installed version is read back rather than assumed: the folder is named for the pin, but a # half-finished npm install leaves the folder there with the wrong thing (or nothing) inside it. $playwrightPath = Join-Path -Path (Join-Path -Path $InstallPath -ChildPath 'node_modules') -ChildPath 'playwright' $playwrightInstalled = $null $playwrightManifest = Join-Path -Path $playwrightPath -ChildPath 'package.json' if (Test-Path -LiteralPath $playwrightManifest) { try { $parsed = Get-Content -LiteralPath $playwrightManifest -Raw | ConvertFrom-Json if ($parsed.PSObject.Properties['version']) { $playwrightInstalled = "$($parsed.version)".Trim() } } catch { Write-ALbuildLog -Level Warning "Could not read '$playwrightManifest': $($_.Exception.Message)" } } if ($playwrightInstalled -ne $pinned) { $missing += 'playwright' } # --- Chromium ------------------------------------------------------------------------------------ # The INSTALLED Playwright is asked where its browser is, rather than this function looking for a # file. The layout under the browser store is not a contract: it moved from 'chrome-win\chrome.exe' # to the Chrome for Testing shape 'chrome-win64\chrome.exe', and there is a second build # ('chromium_headless_shell-<rev>') beside it. A path guess one release out of date reports "no # browser" about a browser that is sitting right there - and sends the caller off to download a few # hundred megabytes that were never missing. This cost one round to find out. $chromiumPath = $null if ($playwrightInstalled -eq $pinned -and $nodePath) { $resolver = Join-Path -Path $resource.Folder -ChildPath 'resolve-browser.js' $previousBrowsersPath = $env:PLAYWRIGHT_BROWSERS_PATH try { $env:PLAYWRIGHT_BROWSERS_PATH = $browsersPath $resolved = Invoke-ALbuildProcess -FilePath $nodePath -Arguments @($resolver, $InstallPath) -PassThru -SuccessExitCodes @(0) if ($resolved.Success) { $candidate = "$($resolved.StdOut)".Trim() # Playwright answers with the path it WOULD launch, whether or not the download ran, so # the file still has to be there. if ($candidate -and (Test-Path -LiteralPath $candidate)) { $chromiumPath = $candidate } } else { Write-ALbuildLog -Level Warning ("Could not ask Playwright $pinned where its Chromium is: " + "$("$($resolved.StdErr)".Trim())") } } finally { $env:PLAYWRIGHT_BROWSERS_PATH = $previousBrowsersPath } } if (-not $chromiumPath) { $missing += 'chromium' } # --- Remedy -------------------------------------------------------------------------------------- # One runnable command, never a template. Node is the only part ALbuild does not install itself, so # it is named separately and first - 'albuild setup --screenshots' cannot succeed without it. $remedy = $null if ($missing -contains 'node') { $isWindowsHost = (Test-BcPlatform).IsWindows $remedy = if ($isWindowsHost) { 'winget install OpenJS.NodeJS.LTS # then: albuild setup --screenshots' } else { 'Install Node.js 18 or newer (https://nodejs.org), then: albuild setup --screenshots' } } elseif ($missing.Count -gt 0) { $remedy = 'albuild setup --screenshots' } [PSCustomObject]@{ Ready = ($missing.Count -eq 0) Missing = @($missing) Remedy = $remedy NodePath = $nodePath NodeVersion = $nodeVersion PlaywrightVersion = $pinned PlaywrightPath = $(if ($playwrightInstalled -eq $pinned) { $playwrightPath } else { $null }) InstallPath = $InstallPath BrowsersPath = $browsersPath ChromiumPath = $chromiumPath } } |