mirror of
https://github.com/feder-cr/invisible_playwright.git
synced 2026-06-19 09:08:06 +02:00
Drive gate: set the realistic newtab prefs so a human-cursor click can't lose the about:newtab race
FF150+Fission auto-loads about:newtab (TopSitesFeed) ~100ms-1s after a tab's first navigation — a cross-process BC swap that replaces the page. The wrapper always disables this (prefs.py); the raw-Playwright drive gate did not, so any action that adds latency (e.g. the human-cursor motion path) let the page vanish mid-sequence and surfaced as a phantom "waiting for locator" timeout — an environment artifact, not a binary defect. The gate now launches with the same newtab-suppression prefs every real run uses. Add republish.yml: re-gate + publish an existing build run's artifacts without rebuilding, for when all builds succeeded but a gate/test bug blocked publish.
This commit is contained in:
parent
29262a644e
commit
30a79cefc9
2 changed files with 233 additions and 1 deletions
214
.github/workflows/republish.yml
vendored
Normal file
214
.github/workflows/republish.yml
vendored
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# republish.yml — re-gate + publish an EXISTING build's artifacts WITHOUT rebuilding.
|
||||
#
|
||||
# When a release run's 5 builds all SUCCEEDED but the publish was blocked by a
|
||||
# GATE/TEST bug (not a binary defect), rebuilding all five targets (hours) just to
|
||||
# re-run a few-minute test is wasteful. This workflow downloads the binary artifacts
|
||||
# from a prior `release` run, re-runs the (now-fixed) drive gate + cloak/WebGL guards
|
||||
# against those exact byte-identical binaries on their native runners, and — only if
|
||||
# green — publishes the DRAFT release. Same publish logic as release.yml (draft, notes,
|
||||
# checksums, source-commit) so the human still runs the realness gate before un-drafting.
|
||||
#
|
||||
# Inputs:
|
||||
# build_run_id : the `release` run whose `asset-*` + `source-commit` artifacts to reuse
|
||||
# (must be within the 7-day artifact retention window).
|
||||
# release_tag : tag to publish the draft under (e.g. firefox-11).
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
name: republish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_run_id:
|
||||
description: 'release run id whose built artifacts to reuse (no rebuild)'
|
||||
required: true
|
||||
release_tag:
|
||||
description: 'release tag to publish the draft under (e.g. firefox-11)'
|
||||
required: true
|
||||
|
||||
env:
|
||||
SOURCE_REPO: feder-cr/invisible_firefox
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
name: gate-${{ matrix.leg }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 25
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
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
|
||||
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 — the FIXED gate)
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with: { fetch-depth: 1 }
|
||||
- name: Download asset from the existing build run (no rebuild)
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: asset-${{ matrix.leg }}
|
||||
path: art
|
||||
run-id: ${{ github.event.inputs.build_run_id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with: { python-version: '3.11' }
|
||||
- name: Install Playwright driver (pinned, no bundled browser)
|
||||
shell: bash
|
||||
run: python -m pip install --quiet "playwright==$(cat scripts/playwright_pin.txt)"
|
||||
- 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 (+ interaction on --full)
|
||||
shell: bash
|
||||
run: python scripts/ci_drive_gate.py "$FF_EXE" ${{ matrix.extra }}
|
||||
- name: Install pyobjc Quartz (macOS — to read the cloak window alpha)
|
||||
if: matrix.kind == 'mac'
|
||||
run: python -m pip install --quiet pyobjc-framework-Quartz
|
||||
- name: Cloak + WebGL-masking guards (headed)
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --quiet ".[dev]"
|
||||
INVPW_BINARY_PATH="$FF_EXE" python -m pytest \
|
||||
tests/test_cloak.py \
|
||||
"tests/test_fingerprint_surface.py::test_webgl_readpixels_no_masking_signature" \
|
||||
-m e2e -o addopts='' -q
|
||||
|
||||
publish:
|
||||
name: publish-draft-release
|
||||
needs: [gate]
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout wrapper (for scripts/gen_release_notes.py)
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with: { fetch-depth: 1 }
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with: { python-version: '3.11' }
|
||||
- name: Download all build assets from the existing build run (no rebuild)
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
pattern: asset-*
|
||||
path: dl
|
||||
merge-multiple: true
|
||||
run-id: ${{ github.event.inputs.build_run_id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Download source-commit metadata from the existing build run
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
with:
|
||||
name: source-commit
|
||||
path: src-meta
|
||||
run-id: ${{ github.event.inputs.build_run_id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Assert all 5 target archives present (no silent partial release)
|
||||
run: |
|
||||
cd dl
|
||||
EXPECTED="
|
||||
firefox-150.0.1-stealth-linux-x86_64.tar.gz
|
||||
firefox-150.0.1-stealth-linux-arm64.tar.gz
|
||||
firefox-150.0.1-stealth-win-x86_64.zip
|
||||
firefox-150.0.1-stealth-macos-arm64.tar.gz
|
||||
firefox-150.0.1-stealth-macos-x86_64.tar.gz
|
||||
"
|
||||
for a in $EXPECTED; do
|
||||
[ -s "$a" ] || { echo "ERROR: missing/empty release asset: $a (a build leg silently dropped out?)"; exit 1; }
|
||||
done
|
||||
echo "all 5 target archives present"
|
||||
- name: Generate checksums.txt
|
||||
run: |
|
||||
cd dl; ls -la
|
||||
sha256sum firefox-150.0.1-stealth-* > checksums.txt
|
||||
echo "----- checksums.txt -----"; cat checksums.txt
|
||||
- name: Resolve release tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.release_tag }}"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
N="${TAG#firefox-}"
|
||||
echo "num=$N" >> "$GITHUB_OUTPUT"
|
||||
case "$N" in (*[!0-9]*|'') echo "prevtag=" >> "$GITHUB_OUTPUT";;
|
||||
(*) echo "prevtag=firefox-$((N-1))" >> "$GITHUB_OUTPUT";; esac
|
||||
echo "publishing DRAFT release for tag: $TAG"
|
||||
- name: Build release notes from the source commits
|
||||
id: notes
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
CUR="$(cat src-meta/source-commit.txt 2>/dev/null | tr -d '[:space:]')"
|
||||
echo "this build's source commit: ${CUR:-<none>}"
|
||||
PREV=""
|
||||
PREVTAG="${{ steps.tag.outputs.prevtag }}"
|
||||
if [ -n "$PREVTAG" ] && gh release download "$PREVTAG" -R "${{ github.repository }}" \
|
||||
--pattern source-commit.txt --dir prev 2>/dev/null; then
|
||||
PREV="$(cat prev/source-commit.txt | tr -d '[:space:]')"
|
||||
echo "previous ($PREVTAG) source commit: $PREV"
|
||||
else
|
||||
echo "no previous source-commit.txt — changelog section omitted this time"
|
||||
fi
|
||||
python scripts/gen_release_notes.py --tag "${{ steps.tag.outputs.tag }}" \
|
||||
--current "$CUR" --prev-sha "$PREV" --source-repo "${{ env.SOURCE_REPO }}" > body.md
|
||||
echo "----- generated body.md -----"; cat body.md
|
||||
cp src-meta/source-commit.txt dl/source-commit.txt
|
||||
- name: Create DRAFT release with all assets
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.tag }}
|
||||
name: invisible_firefox (150.0.1) rev ${{ steps.tag.outputs.num }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
dl/*.tar.gz
|
||||
dl/*.zip
|
||||
dl/checksums.txt
|
||||
dl/source-commit.txt
|
||||
body_path: body.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -87,12 +87,30 @@ def _start_server():
|
|||
return srv, srv.server_address[1]
|
||||
|
||||
|
||||
# FF150 + Fission auto-loads about:newtab (TopSitesFeed) ~100ms-1s after a tab's
|
||||
# first navigation — a cross-process BC swap that REPLACES the page out from under
|
||||
# the test. The wrapper always disables it (see prefs.py); raw Playwright does not,
|
||||
# so the binary's realistic config must set it here too. Without this the drive page
|
||||
# can vanish mid-sequence (it loses the race whenever an action adds latency, e.g.
|
||||
# the human-cursor path), surfacing as a phantom "waiting for locator" timeout that
|
||||
# is an environment artifact, not a binary defect.
|
||||
_REALISTIC_PREFS = {
|
||||
"browser.startup.page": 0,
|
||||
"browser.newtabpage.enabled": False,
|
||||
"browser.newtab.preload": False,
|
||||
"browser.newtabpage.activity-stream.feeds.topsites": False,
|
||||
"browser.newtabpage.activity-stream.feeds.section.topstories": False,
|
||||
"browser.newtabpage.activity-stream.enabled": False,
|
||||
}
|
||||
|
||||
|
||||
def _drive(exe: str, url: str, full: bool) -> str:
|
||||
"""One full drive attempt. Returns the UA on success; raises on failure."""
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.firefox.launch(executable_path=exe, headless=True)
|
||||
browser = p.firefox.launch(executable_path=exe, headless=True,
|
||||
firefox_user_prefs=_REALISTIC_PREFS)
|
||||
try:
|
||||
page = browser.new_page()
|
||||
resp = page.goto(url, wait_until="load")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue