mirror of
https://github.com/feder-cr/invisible_playwright.git
synced 2026-06-10 08:45:13 +02:00
ci: drive-test every release binary via Playwright, not just screenshot
The old gate ran firefox --headless --screenshot, which renders fine even when the juggler automation layer is missing from the package — so a binary Playwright can't actually drive (firefox-8) passed and shipped broken. Replace it with a real drive gate: a 5-leg matrix that launches each binary over the juggler pipe on its native runner, loads a page, and round-trips JS (also asserts navigator.webdriver stays hidden). Headless and no screenshot, so it stays GPU-free on the hosted runners and needs no proxy or secrets. Same logic is reusable standalone via verify-assets.yml to drive-test an existing release's assets without a rebuild.
This commit is contained in:
parent
eec373a719
commit
86a04d2d34
3 changed files with 226 additions and 26 deletions
105
.github/workflows/release.yml
vendored
105
.github/workflows/release.yml
vendored
|
|
@ -9,12 +9,17 @@
|
|||
# strip + sanitize + tar at ROOT, then validate_release.py as a HARD
|
||||
# in-pipeline gate (the exact battle-tested script from the source repo).
|
||||
# Win → mach package; zip the CONTENTS of dist/firefox (clean tree, NOT
|
||||
# dist/bin) so firefox.exe sits at the zip ROOT. Runtime-gated on a real
|
||||
# windows-latest runner (headless screenshot).
|
||||
# dist/bin) so firefox.exe sits at the zip ROOT.
|
||||
# macOS → mach package; ad-hoc codesign the .app; PRESERVE its internal relative
|
||||
# symlinks (a .app legitimately has them — cp -aL would break it); verify
|
||||
# every symlink is relative+internal; tar the bundle. --version self-gate.
|
||||
#
|
||||
# DRIVE GATE (the firefox-8 catcher): after build, every binary is DRIVEN by
|
||||
# Playwright on its native runner (launch via juggler + real page + JS roundtrip,
|
||||
# headless, no screenshot → GPU-free, zero proxy). A juggler-less binary renders
|
||||
# a screenshot fine but is undrivable — only an actual drive catches that. The
|
||||
# proxy realness gate (fppro/webrtc) stays LOCAL — it needs secrets.
|
||||
#
|
||||
# Trigger: push a tag `firefox-N`, or run manually. Hybrid runners, all free.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
name: release
|
||||
|
|
@ -233,37 +238,85 @@ jobs:
|
|||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
# Windows binary is cross-built on Linux → gate it on a real Windows runner.
|
||||
gate-windows:
|
||||
name: gate-windows
|
||||
# DRIVE GATE — the firefox-8 catcher. A raw `firefox --screenshot` proves
|
||||
# nothing about automation: a juggler-less binary renders fine and ships
|
||||
# broken (firefox-8 did exactly that). So we DRIVE every binary the way users
|
||||
# will: Playwright launches it over the juggler pipe, loads a real page, and
|
||||
# round-trips JS. A binary missing/broken juggler throws TargetClosedError
|
||||
# here and the release never publishes. Headless, NO screenshot → GPU-free,
|
||||
# so it can't false-fail on the GPU-less hosted runners. Zero proxy / zero
|
||||
# secrets → safe in public CI (the proxy realness gate stays local, by design).
|
||||
# Each leg runs on its NATIVE runner so we test the real artifact, not a cross
|
||||
# surrogate. Playwright is pinned to a version validated against this build's
|
||||
# juggler; bump it in lockstep when the juggler is re-synced from upstream.
|
||||
gate:
|
||||
name: gate-${{ matrix.leg }}
|
||||
needs: build
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 20
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 25
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- leg: linux-x86_64
|
||||
runner: ubuntu-24.04
|
||||
kind: linux
|
||||
asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz
|
||||
- leg: linux-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
kind: linux
|
||||
asset: firefox-150.0.1-stealth-linux-arm64.tar.gz
|
||||
- leg: win-x86_64
|
||||
runner: windows-latest
|
||||
kind: win
|
||||
asset: firefox-150.0.1-stealth-win-x86_64.zip
|
||||
- leg: macos-arm64
|
||||
runner: macos-15
|
||||
kind: mac
|
||||
asset: firefox-150.0.1-stealth-macos-arm64.tar.gz
|
||||
- leg: macos-x86_64
|
||||
runner: macos-15-intel
|
||||
kind: mac
|
||||
asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz
|
||||
steps:
|
||||
- name: Download win asset
|
||||
- name: Checkout wrapper (for scripts/ci_drive_gate.py)
|
||||
uses: actions/checkout@v4
|
||||
with: { fetch-depth: 1 }
|
||||
- name: Download asset
|
||||
uses: actions/download-artifact@v4
|
||||
with: { name: asset-win-x86_64, path: art }
|
||||
- name: Extract + structure + headless render gate
|
||||
shell: pwsh
|
||||
with:
|
||||
name: asset-${{ matrix.leg }}
|
||||
path: art
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with: { python-version: '3.11' }
|
||||
- name: Install Playwright driver (no bundled browser — we override executable_path)
|
||||
run: python -m pip install --quiet "playwright==1.55.0"
|
||||
- name: Linux system deps for headless firefox
|
||||
if: matrix.kind == 'linux'
|
||||
run: sudo "$(which python)" -m playwright install-deps firefox
|
||||
- name: Extract + locate firefox binary
|
||||
shell: bash
|
||||
run: |
|
||||
$zip = Get-ChildItem art -Filter *.zip | Select-Object -First 1
|
||||
Expand-Archive $zip.FullName -DestinationPath ff -Force
|
||||
foreach ($f in 'firefox.exe','application.ini','dependentlibs.list') {
|
||||
if (-not (Test-Path (Join-Path ff $f))) { throw "missing critical file: $f" }
|
||||
}
|
||||
$exe = Join-Path (Resolve-Path ff) 'firefox.exe'
|
||||
Remove-Item shot.png -ErrorAction SilentlyContinue
|
||||
$p = Start-Process -FilePath $exe -ArgumentList '--headless','--no-remote','--profile','prof','--screenshot',"$PWD\shot.png",'https://example.com' -PassThru -NoNewWindow
|
||||
if (-not $p.WaitForExit(90000)) { $p.Kill(); throw 'win gate TIMEOUT' }
|
||||
Start-Sleep 1
|
||||
if (-not (Test-Path shot.png) -or (Get-Item shot.png).Length -lt 3000) { throw 'win gate: no/empty screenshot' }
|
||||
Write-Output "win gate OK: firefox.exe runs + renders ($((Get-Item shot.png).Length) bytes)"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with: { name: gate-win-screenshot, path: shot.png, if-no-files-found: warn, retention-days: 7 }
|
||||
set -e
|
||||
mkdir -p ff
|
||||
A="art/${{ matrix.asset }}"
|
||||
case "${{ matrix.kind }}" in
|
||||
win) python -c "import zipfile; zipfile.ZipFile('$A').extractall('ff')"; EXE="ff/firefox.exe";;
|
||||
linux) tar xzf "$A" -C ff; EXE="ff/firefox";;
|
||||
mac) tar xzf "$A" -C ff; EXE="ff/Firefox.app/Contents/MacOS/firefox";;
|
||||
esac
|
||||
[ -e "$EXE" ] || { echo "ERROR: firefox binary not found at $EXE"; exit 1; }
|
||||
chmod +x "$EXE" 2>/dev/null || true
|
||||
echo "FF_EXE=$EXE" >> "$GITHUB_ENV"
|
||||
echo "located: $EXE"
|
||||
- name: DRIVE GATE — Playwright launch via juggler + real page + JS roundtrip
|
||||
shell: bash
|
||||
run: python scripts/ci_drive_gate.py "$FF_EXE"
|
||||
|
||||
publish:
|
||||
name: publish-draft-release
|
||||
needs: [build, gate-windows]
|
||||
needs: [build, gate]
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
|
|
|||
98
.github/workflows/verify-assets.yml
vendored
Normal file
98
.github/workflows/verify-assets.yml
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# verify-assets.yml — re-runnable DRIVE GATE for an EXISTING release's assets.
|
||||
#
|
||||
# release.yml drive-gates every binary it builds. This does the same drive test
|
||||
# WITHOUT rebuilding: it downloads a release's already-published assets (works on
|
||||
# DRAFT releases too via GITHUB_TOKEN) and drives each one on its native runner.
|
||||
#
|
||||
# Use it to:
|
||||
# • drive-test a release that was built before the in-pipeline gate existed
|
||||
# (e.g. firefox-9, built on the old release.yml), or
|
||||
# • re-verify any shipped release on demand (regression check).
|
||||
#
|
||||
# Same single-source-of-truth drive logic as release.yml: scripts/ci_drive_gate.py.
|
||||
# Headless, no screenshot → GPU-free. Zero proxy / zero secrets.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
name: verify-assets
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'release tag whose assets to drive-test (e.g. firefox-9)'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
drive:
|
||||
name: drive-${{ matrix.leg }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 25
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- leg: linux-x86_64
|
||||
runner: ubuntu-24.04
|
||||
kind: linux
|
||||
asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz
|
||||
- leg: linux-arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
kind: linux
|
||||
asset: firefox-150.0.1-stealth-linux-arm64.tar.gz
|
||||
- leg: win-x86_64
|
||||
runner: windows-latest
|
||||
kind: win
|
||||
asset: firefox-150.0.1-stealth-win-x86_64.zip
|
||||
- leg: macos-arm64
|
||||
runner: macos-15
|
||||
kind: mac
|
||||
asset: firefox-150.0.1-stealth-macos-arm64.tar.gz
|
||||
- leg: macos-x86_64
|
||||
runner: macos-15-intel
|
||||
kind: mac
|
||||
asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz
|
||||
steps:
|
||||
- name: Checkout wrapper (for scripts/ci_drive_gate.py)
|
||||
uses: actions/checkout@v4
|
||||
with: { fetch-depth: 1 }
|
||||
- name: Download the release asset (draft releases included)
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p art
|
||||
gh release download "${{ github.event.inputs.release_tag }}" \
|
||||
--repo "${{ github.repository }}" \
|
||||
--pattern "${{ matrix.asset }}" \
|
||||
--dir art
|
||||
ls -la art/
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with: { python-version: '3.11' }
|
||||
- name: Install Playwright driver (no bundled browser — we override executable_path)
|
||||
run: python -m pip install --quiet "playwright==1.55.0"
|
||||
- name: Linux system deps for headless firefox
|
||||
if: matrix.kind == 'linux'
|
||||
run: sudo "$(which python)" -m playwright install-deps firefox
|
||||
- name: Extract + locate firefox binary
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p ff
|
||||
A="art/${{ matrix.asset }}"
|
||||
case "${{ matrix.kind }}" in
|
||||
win) python -c "import zipfile; zipfile.ZipFile('$A').extractall('ff')"; EXE="ff/firefox.exe";;
|
||||
linux) tar xzf "$A" -C ff; EXE="ff/firefox";;
|
||||
mac) tar xzf "$A" -C ff; EXE="ff/Firefox.app/Contents/MacOS/firefox";;
|
||||
esac
|
||||
[ -e "$EXE" ] || { echo "ERROR: firefox binary not found at $EXE"; exit 1; }
|
||||
chmod +x "$EXE" 2>/dev/null || true
|
||||
echo "FF_EXE=$EXE" >> "$GITHUB_ENV"
|
||||
echo "located: $EXE"
|
||||
- name: DRIVE GATE — Playwright launch via juggler + real page + JS roundtrip
|
||||
shell: bash
|
||||
run: python scripts/ci_drive_gate.py "$FF_EXE"
|
||||
Loading…
Add table
Add a link
Reference in a new issue