diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29d20c5..ace85db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -267,26 +267,38 @@ jobs: fail-fast: false matrix: include: + # `extra: --full` adds the mouse/keyboard/canvas/navsurface interaction + # checks. Only on linux-x86_64 (historically the most reliable hosted + # runner): the interaction code is platform-identical JS (omni.ja), so + # one reliable full run catches a firefox-2-class regression for all + # platforms. The other legs run SMOKE (launch+http+UA+webdriver) — the + # firefox-8/juggler catcher — which is robust even on the flaky + # windows-latest runner. See scripts/ci_drive_gate.py. - leg: linux-x86_64 runner: ubuntu-24.04 kind: linux asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz + extra: '--full' - leg: linux-arm64 runner: ubuntu-24.04-arm kind: linux asset: firefox-150.0.1-stealth-linux-arm64.tar.gz + extra: '' - leg: win-x86_64 runner: windows-latest kind: win asset: firefox-150.0.1-stealth-win-x86_64.zip + extra: '' - leg: macos-arm64 runner: macos-15 kind: mac asset: firefox-150.0.1-stealth-macos-arm64.tar.gz + extra: '' - leg: macos-x86_64 runner: macos-15-intel kind: mac asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz + extra: '' steps: - name: Checkout wrapper (for scripts/ci_drive_gate.py) uses: actions/checkout@v4 @@ -319,9 +331,9 @@ jobs: 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 + - name: DRIVE GATE — Playwright launch via juggler + real page (+ interaction on --full) shell: bash - run: python scripts/ci_drive_gate.py "$FF_EXE" + run: python scripts/ci_drive_gate.py "$FF_EXE" ${{ matrix.extra }} publish: name: publish-draft-release diff --git a/.github/workflows/verify-assets.yml b/.github/workflows/verify-assets.yml index cceac70..7d946ed 100644 --- a/.github/workflows/verify-assets.yml +++ b/.github/workflows/verify-assets.yml @@ -38,26 +38,33 @@ jobs: fail-fast: false matrix: include: + # --full (interaction) only on the reliable linux-x86_64 leg; others run + # the robust SMOKE drive. Same rationale as release.yml's gate. - leg: linux-x86_64 runner: ubuntu-24.04 kind: linux asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz + extra: '--full' - leg: linux-arm64 runner: ubuntu-24.04-arm kind: linux asset: firefox-150.0.1-stealth-linux-arm64.tar.gz + extra: '' - leg: win-x86_64 runner: windows-latest kind: win asset: firefox-150.0.1-stealth-win-x86_64.zip + extra: '' - leg: macos-arm64 runner: macos-15 kind: mac asset: firefox-150.0.1-stealth-macos-arm64.tar.gz + extra: '' - leg: macos-x86_64 runner: macos-15-intel kind: mac asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz + extra: '' steps: - name: Checkout wrapper (for scripts/ci_drive_gate.py) uses: actions/checkout@v4 @@ -97,6 +104,6 @@ jobs: 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 + - name: DRIVE GATE — Playwright launch via juggler + real page (+ interaction on --full) shell: bash - run: python scripts/ci_drive_gate.py "$FF_EXE" + run: python scripts/ci_drive_gate.py "$FF_EXE" ${{ matrix.extra }} diff --git a/scripts/ci_drive_gate.py b/scripts/ci_drive_gate.py index c5fbb86..2b6ebf0 100644 --- a/scripts/ci_drive_gate.py +++ b/scripts/ci_drive_gate.py @@ -4,39 +4,39 @@ A raw `firefox --screenshot` proves nothing about automation: a juggler-less binary renders a screenshot just fine and ships broken (firefox-8 did exactly that). This DRIVES the binary the way users will — Playwright launches it over -the juggler pipe and exercises the input/DOM paths real callers depend on. +the juggler pipe and exercises real paths. -It deliberately covers the failure modes that HISTORICALLY shipped green: - - juggler missing entirely → TargetClosedError on launch (firefox-8) - - mouse/keyboard input broken → click/move/type assertions (firefox-2 #9: - jugglerSendMouseEvent / synthesizeMouseEvent) - - canvas non-deterministic → identical draw → identical dataURL (stealth - seed must be per-session, not per-readback) - - headless navigator tells → navigator.webdriver falsy, languages - non-empty, plugins is a real PluginArray - - real HTTP navigation broken → the page is served over http://127.0.0.1 - and a `response` is awaited (not data:/about:blank) +Two levels (see `--full`): -All of this is headless, NO screenshot → GPU-free (can't false-fail on the -GPU-less hosted runners). The HTTP server is loopback-only → no external network, -no proxy, no secrets → safe in public CI. WebGL determinism is intentionally NOT -checked here (needs SWGL, false-fails headless); it lives in the local realness -gate, along with the faithful cross-origin iframe test (issue #20 — a same-origin -in-gate iframe is a weak proxy AND races Juggler's frame tracking). + SMOKE (default — run on ALL 5 legs, on every binary's native runner): + launch over juggler-pipe → navigate a real http://127.0.0.1 page → assert a + response, the Firefox UA, navigator.webdriver falsy, and a DOM read. This is + the firefox-8 catcher (a juggler-less binary throws TargetClosedError on + launch) plus a base stealth + drivability check. It is intentionally LIGHT: + the free hosted runners — windows-latest especially — are content-process + unstable under a heavy headless interaction sequence (clicks/moves cascade + into "context destroyed" / selector-timeout / eval-CSP), so the gate that + must be GREEN on every leg stays minimal and reliable. -Robustness (learned the hard way, across many runner round-trips): - - The page is served over real `http://127.0.0.1:/`. A `data:` URL gets - re-normalized (re-navigated) by Firefox, `about:blank` + a redundant goto - intermittently "destroys the execution context by navigation", and both can - carry a CSP that blocks `eval()`. A plain loopback HTTP page has none of that. - - Every `page.evaluate` is an ARROW FUNCTION (Playwright callFunction, never - eval'd) — immune to a page CSP that blocks eval. Listeners are wired in an - inline