param( [string]$Plan = "$PSScriptRoot\plan.json", [string]$Ausgabe = "$PSScriptRoot\bilder" ) # Edge ohne Fenster über das DevTools-Protokoll steuern: laden, echt warten, # optional JavaScript ausführen (klicken, scrollen), Screenshot speichern. $ErrorActionPreference = "Stop" New-Item -ItemType Directory -Force $Ausgabe | Out-Null $edge = "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe" $profil = Join-Path $env:TEMP ("edge-cdp-" + [guid]::NewGuid()) $port = 9333 $proc = Start-Process $edge -ArgumentList @("--headless=new", "--disable-gpu", "--hide-scrollbars", "--remote-debugging-port=$port", "--user-data-dir=$profil", "--no-first-run", "about:blank") -PassThru function Warte-Auf-Port { for ($i = 0; $i -lt 40; $i++) { try { return Invoke-RestMethod "http://127.0.0.1:$port/json/list" } catch { Start-Sleep -Milliseconds 250 } } throw "Edge antwortet nicht" } $script:id = 0 function Sende($ws, $methode, $parameter) { $script:id++ $nachricht = @{ id = $script:id; method = $methode; params = $parameter } | ConvertTo-Json -Depth 20 -Compress $bytes = [Text.Encoding]::UTF8.GetBytes($nachricht) $ws.SendAsync([ArraySegment[byte]]$bytes, [Net.WebSockets.WebSocketMessageType]::Text, $true, [Threading.CancellationToken]::None).Wait() $puffer = New-Object byte[] (1024 * 1024) while ($true) { $ms = New-Object IO.MemoryStream do { $teil = $ws.ReceiveAsync([ArraySegment[byte]]$puffer, [Threading.CancellationToken]::None) $teil.Wait() $ms.Write($puffer, 0, $teil.Result.Count) } while (-not $teil.Result.EndOfMessage) $antwort = [Text.Encoding]::UTF8.GetString($ms.ToArray()) | ConvertFrom-Json if ($antwort.id -eq $script:id) { return $antwort } } } try { $ziele = Warte-Auf-Port $seite = $ziele | Where-Object { $_.type -eq "page" } | Select-Object -First 1 $ws = New-Object Net.WebSockets.ClientWebSocket $ws.Options.KeepAliveInterval = [TimeSpan]::FromSeconds(20) $ws.ConnectAsync([Uri]$seite.webSocketDebuggerUrl, [Threading.CancellationToken]::None).Wait() Sende $ws "Page.enable" @{} | Out-Null Sende $ws "Runtime.enable" @{} | Out-Null $schritte = Get-Content $Plan -Raw -Encoding UTF8 | ConvertFrom-Json $breit = 1280; $hoch = 900; $handy = $false foreach ($s in $schritte) { # Ein Schritt mit eigener Adresse legt die Größe fest; Schritte ohne # Adresse (klicken, scrollen) behalten die des vorigen. if ($s.url) { $breit = if ($s.breit) { $s.breit } else { 1280 } $hoch = if ($s.hoch) { $s.hoch } else { 900 } $handy = [bool]$s.handy } $em = Sende $ws "Emulation.setDeviceMetricsOverride" @{ width = [int]$breit; height = [int]$hoch; deviceScaleFactor = $(if ($handy) { 2 } else { 1 }); mobile = $handy } if ($em.error) { Write-Output ("Emulation: " + ($em.error | ConvertTo-Json -Compress)) } if ($s.url) { Sende $ws "Page.navigate" @{ url = $s.url } | Out-Null Start-Sleep -Seconds $(if ($s.warte) { $s.warte } else { 6 }) } # Einstellungsreiter per Klick öffnen - ein Anker in der Adresse greift # nicht, wenn nur er sich ändert. if ($s.vorher) { Sende $ws "Runtime.evaluate" @{ expression = "new bootstrap.Tab(document.getElementById('einstellung-$($s.vorher)-tab')).show()" } | Out-Null Start-Sleep -Seconds 2 } if ($s.js) { $r = Sende $ws "Runtime.evaluate" @{ expression = $s.js; awaitPromise = $true; returnByValue = $true } if ($r.result.exceptionDetails) { Write-Output ("JS-Fehler in " + $s.datei + ": " + $r.result.exceptionDetails.exception.description) } if ($s.zeige) { Write-Output ("JS " + $s.datei + ": " + ($r.result.result.value | ConvertTo-Json -Compress)) } Start-Sleep -Seconds $(if ($s.danach) { $s.danach } else { 2 }) } if ($s.datei) { # Lage der markierten Elemente in Prozent des Bildausschnitts - daraus # setzt die Einführung ihre nummerierten Punkte. if ($s.marken) { $sel = $s.marken | ConvertTo-Json -Compress $ausdruck = "(() => { const m = $sel; const aus = {}; for (const k in m) { const el = document.querySelector(m[k]); if (!el) { aus[k] = null; continue; } const r = el.getBoundingClientRect(); aus[k] = { x: +((r.left + r.width / 2) / innerWidth * 100).toFixed(1), y: +((r.top + r.height / 2) / innerHeight * 100).toFixed(1) }; } return JSON.stringify(aus); })()" $r = Sende $ws "Runtime.evaluate" @{ expression = $ausdruck; returnByValue = $true } $name = [IO.Path]::GetFileNameWithoutExtension($s.datei) [IO.File]::WriteAllText((Join-Path $Ausgabe "$name.json"), $r.result.result.value, [Text.Encoding]::UTF8) if ($r.result.result.value -match "null") { Write-Output ("Marke fehlt in " + $s.datei + ": " + $r.result.result.value) } } $bild = Sende $ws "Page.captureScreenshot" @{ format = "jpeg"; quality = 82 } [IO.File]::WriteAllBytes((Join-Path $Ausgabe $s.datei), [Convert]::FromBase64String($bild.result.data)) Write-Output ("ok " + $s.datei) } } } finally { if ($proc -and -not $proc.HasExited) { Stop-Process -Id $proc.Id -Force } Get-Process msedge -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like "*$profil*" } | Stop-Process -Force -ErrorAction SilentlyContinue }