# WebView2 CDP (Chrome DevTools Protocol) E2E helper. # Usage: . .\scripts\cdp.ps1; Invoke-Cdp "document.title" # . .\scripts\cdp.ps1; Save-CdpScreenshot shot.png # Prereq: app launched with WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9223 # NOTE: ASCII only in this file (PS 5.1 reads BOM-less files as ANSI). function Invoke-Cdp { param( [Parameter(Mandatory)] [string]$Expression, [int]$Port = 9223, [int]$TimeoutSec = 20 ) $targets = Invoke-RestMethod "http://127.0.0.1:$Port/json" $page = $targets | Where-Object { $_.type -eq 'page' -and $_.url -notlike 'devtools*' } | Select-Object -First 1 if (-not $page) { throw "no CDP page target" } $ws = New-Object System.Net.WebSockets.ClientWebSocket $ct = [System.Threading.CancellationToken]::None $uri = [Uri]$page.webSocketDebuggerUrl $ws.ConnectAsync($uri, $ct).GetAwaiter().GetResult() | Out-Null $msg = @{ id = 1 method = "Runtime.evaluate" params = @{ expression = $Expression returnByValue = $true awaitPromise = $true } } | ConvertTo-Json -Depth 6 -Compress $bytes = [System.Text.Encoding]::UTF8.GetBytes($msg) $seg = New-Object System.ArraySegment[byte] -ArgumentList @(,$bytes) $ws.SendAsync($seg, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).GetAwaiter().GetResult() | Out-Null $buffer = New-Object byte[] 1048576 $deadline = (Get-Date).AddSeconds($TimeoutSec) $response = $null while ((Get-Date) -lt $deadline) { $sb = New-Object System.Text.StringBuilder do { $rseg = New-Object System.ArraySegment[byte] -ArgumentList @(,$buffer) $result = $ws.ReceiveAsync($rseg, $ct).GetAwaiter().GetResult() $sb.Append([System.Text.Encoding]::UTF8.GetString($buffer, 0, $result.Count)) | Out-Null } while (-not $result.EndOfMessage) $obj = $sb.ToString() | ConvertFrom-Json if ($obj.id -eq 1) { $response = $obj; break } } $ws.Abort() if (-not $response) { throw "CDP response timeout" } if ($response.result.exceptionDetails) { throw "JS exception: $($response.result.exceptionDetails.text) $($response.result.exceptionDetails.exception.description)" } return $response.result.result.value } # Call any CDP method and return the raw result object. # Invoke-Cdp is hardcoded to Runtime.evaluate; use this for Page.*, Emulation.*, etc. function Invoke-CdpMethod { param( [Parameter(Mandatory)] [string]$Method, [hashtable]$Params = @{}, [int]$Port = 9223, [int]$TimeoutSec = 30 ) $targets = Invoke-RestMethod "http://127.0.0.1:$Port/json" $page = $targets | Where-Object { $_.type -eq 'page' -and $_.url -notlike 'devtools*' } | Select-Object -First 1 if (-not $page) { throw "no CDP page target" } $ws = New-Object System.Net.WebSockets.ClientWebSocket $ct = [System.Threading.CancellationToken]::None $ws.ConnectAsync([Uri]$page.webSocketDebuggerUrl, $ct).GetAwaiter().GetResult() | Out-Null $msg = @{ id = 1; method = $Method; params = $Params } | ConvertTo-Json -Depth 6 -Compress $bytes = [System.Text.Encoding]::UTF8.GetBytes($msg) $seg = New-Object System.ArraySegment[byte] -ArgumentList @(, $bytes) $ws.SendAsync($seg, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).GetAwaiter().GetResult() | Out-Null # Screenshots are large: keep reading frames until EndOfMessage, then match the id. $buffer = New-Object byte[] 1048576 $deadline = (Get-Date).AddSeconds($TimeoutSec) $response = $null while ((Get-Date) -lt $deadline) { $sb = New-Object System.Text.StringBuilder do { $rseg = New-Object System.ArraySegment[byte] -ArgumentList @(, $buffer) $result = $ws.ReceiveAsync($rseg, $ct).GetAwaiter().GetResult() $sb.Append([System.Text.Encoding]::UTF8.GetString($buffer, 0, $result.Count)) | Out-Null } while (-not $result.EndOfMessage) $obj = $sb.ToString() | ConvertFrom-Json if ($obj.id -eq 1) { $response = $obj; break } } $ws.Abort() if (-not $response) { throw "CDP response timeout" } if ($response.error) { throw "CDP error: $($response.error.message)" } return $response.result } # Save the page as a PNG (what the WebView actually renders, not the OS window). function Save-CdpScreenshot { param( [Parameter(Mandatory)] [string]$Path, [int]$Port = 9223, [int]$TimeoutSec = 30 ) $r = Invoke-CdpMethod -Method "Page.captureScreenshot" -Params @{ format = "png" } -Port $Port -TimeoutSec $TimeoutSec if (-not $r.data) { throw "no screenshot data" } [System.IO.File]::WriteAllBytes($Path, [System.Convert]::FromBase64String($r.data)) return (Get-Item $Path).Length }