Two-part fix for the wrapper-repo issue #18 (tab crash on id.sky.com
and similar sites with cross-process navigation):
Part 1 (this commit, wrapper-side):
- _WIN_VIRT_DESKTOP_WORKAROUNDS adds security.sandbox.content.level=4.
When the Chromium sandbox runs at content level >4 (default 6) it sets
STARTUPINFO.lpDesktop=kAlternateWinstation for the content process,
putting it on a different desktop than the browser process. Combined
with our hidden alt-desktop (CreateDesktop) for headless windows
hiding, that means cross-process navigations (Adobe AppMeasurement
triggers a new origin -> new content process) can't reparent windows
across desktops; the new content process exits cleanly and Playwright
fires page.on('crash'). Lowering content sandbox to 4 keeps content
processes on the parent's desktop. Level 4 still blocks file/registry/
network access; only the alt-winstation isolation is dropped, which
is what the desktop bug requires.
- README.md adds a Known Issues section pointing at issue #18 with
the workaround (headless=False or Linux+Xvfb) for users on wrapper
versions before firefox-7.
Part 2 (separate commit in invisible-firefox@2e17b4871f93):
- CanvasRenderingContext2D::GetImageDataArray moved the stealth pixel
noise from rawData.mData (read-only DataSourceSurface::Map) to the
JS Uint8ClampedArray's backing buffer. The original write to a
read-only mapped surface segfaulted on GPU-backed canvases during
browser.close() teardown.
Verified end-to-end with InvisiblePlaywright headless=True + Evomi UK
proxy on id.sky.com: page survives, no crash in loop, no crash at
teardown.
Reporter: @gamefireat123-eng.
- Add profile_dir= kwarg to InvisiblePlaywright (sync + async).
Maps to firefox.launch_persistent_context(); returns a BrowserContext.
Cookies / localStorage / extensions / cache / prefs all persisted.
- Drop the firefox-4 era workaround that filtered locale + timezone_id
out of the persistent kwargs. firefox-5 ships the C++
docShell.overrideTimezone IDL method (50 LOC patch in
docshell/base/nsIDocShell.idl + nsDocShell.cpp, see patch.md
section 19 in feder-cr/invisible-firefox), so per-realm overrides
land without crashing the launch handshake.
- Bump BINARY_VERSION firefox-4 -> firefox-5.
- Sentinel unit tests added: persistent kwargs MUST include locale +
timezone_id (defends against re-introducing the workaround) and
must NOT include timezone_id when timezone="" is the "host TZ" sentinel.
Validation: smoke test against the local firefox-5 build, persistent
context UP in 21s (was 180s timeout), Intl.timeZone == Europe/London,
hardwareConcurrency / screen / DPR / locale all reflect the PIN.
git config core.hooksPath .githooks (one-time per clone).
Runs pytest with the default markers (unit + integration) and refuses
to push if anything fails. ~2s overhead per push.
Bypass for emergency WIP with --no-verify; never on release branches.
#15 shipped because unit tests only covered text-mode sha256sum output.
This adds a comprehensive parser test matrix (binary mode `*` prefix,
mixed, CRLF, BOM, indent, trailing whitespace, multiple stars, empty,
comment-only, sha256sum -b coreutils format) plus the integration
sentinel test_ensure_binary_accepts_binary_mode_checksums that
reproduces #15 against the live wire format.
Also covered for the first time:
- _resolve_asset_url public/private branches, auth header propagation,
asset-missing failure, HTTP 4xx propagation
- _download_file 200/404/500, parent mkdir, auth on api.github.com
only (not leaking to CDN URLs)
- cache_root / cache_dir_for_version path shape and version isolation
- _parse_owner_repo malformed inputs and dash/underscore/dot repo names
ARCHIVE_NAME case-matrix (uppercase platform, lowercase machine),
unsupported arch rejection (i386, ppc64le, arm64), unsupported platform
rejection (darwin, freebsd), BINARY_ENTRY_REL <-> ARCHIVE_NAME invariant,
RELEASE_URL_TEMPLATE shape (https, placeholders, owner pointer).
New e2e tests (marker `e2e`, excluded by default):
clean venv install, fetch against live release, binary launch, real-site
Playwright sanity. This is the test suite that would have caught #15
end-to-end before publish.
Stats: 275 -> 327 unit tests (+52), 0 -> 6 e2e tests.
Controprova: rolling back the parser fix makes 9 of the new tests fail
with the exact "no SHA256 for ..." error from #15.
Marker release for the #15 checksum parser fix that landed on main.
First-time fetch was broken for every user since checksums.txt
started shipping with sha256sum's binary-mode `*` prefix.
sha256sum binary-mode output prefixes filenames with `*` and the parser was using parts[-1] verbatim, so checksum lookups by bare filename returned None and the wrapper raised RuntimeError instead of installing the binary.
Thanks LostBoxArt.
Fixes#13: every page that threw an uncaught JS error (bunny.net is the
reporter's repro) crashed the Playwright client with
"TypeError: Cannot read properties of undefined (reading 'url')".
Root cause: upstream Playwright Juggler added a required `location` field
to the Page.uncaughtError event in their 2026-05-07 patch roll; our fork
was carrying the pre-roll schema in every firefox-N build, and any
Playwright client released after the roll read pageError.location.url
strictly.
Fix is in the patched binary (feder-cr/invisible-firefox@1ba55d93), JS-only
inside chrome/juggler/. xul.dll and firefox.exe are byte-identical to
firefox-3 — only the Juggler protocol files change.
BINARY_VERSION bumped firefox-3 → firefox-4. Package version 0.1.2 → 0.1.4
(0.1.3 was never published to PyPI; the changelog entry is kept as
historical record of the firefox-3 binary release).
BINARY_VERSION bumped from firefox-2 to firefox-3. Both archives on this
release are built from a clean clone of feder-cr/invisible-firefox#stealth/150
(the consolidated source-of-truth fork) at commit 68906f1f9c55.
Changes vs firefox-2:
- Proper C++ jugglerSendMouseEvent (replaces JS workaround from 0.1.1)
- C1+C2: setDownloadInterceptor re-landed
- C4: 5 nsIDocShell stealth attributes
- C5: launcher + wmain juggler-pipe handle inheritance (Win)
- C6: juggler-navigation observer notifications
- C7 (partial): nsIDocShell.languageOverride storage stub
- First native Linux build with the full patch series (previous releases
shipped a stale Linux archive copied from earlier builds)
Verified with the 4-test smoke gate on both Windows and Linux:
- test_launch (pipe stays connected, gap C5 sentinel)
- test_new_page (Page.ready fires, gap C6 sentinel)
- test_mouse (jugglerSendMouseEvent C++)
- test_stealth (navigator.webdriver=false + sannysoft check)
The companion C++ patches repo at feder-cr/firefox-stealth was deleted
2026-05-19 and the source-of-truth fork was renamed from feder-cr/firefox
to feder-cr/invisible-firefox. The branch stealth/150 on the renamed
fork is now the single source of truth for all C++ patches.
GitHub's auto-redirect is in effect for the old URLs but is not a
forever guarantee, so update all references now.
Updated:
README.md × 2 hyperlinks (intro paragraph + license section)
CONTRIBUTING.md × 3 references (quick links, scope, bug reports)
SECURITY.md × 1 reference (out-of-scope vulnerabilities)
The 0.1.1 release shipped the source-level fix for issue #9 (every
mouse path failing on FF150) but kept BINARY_VERSION at firefox-1
because the archive itself hadn't been refreshed yet. firefox-2 is
now live on GitHub Releases with the JS hot-swap applied to both
the Windows zip and the Linux tarball; users picking up 0.1.2 will
fetch the patched archive on first run.
Archive integrity verified on both platforms before publishing
(Windows boot test, Linux file-level checks, 21/21 assertions).
The Juggler JS in upstream Playwright calls win.windowUtils.jugglerSendMouseEvent
at four sites, but when the Juggler was ported FF146 -> FF150 the matching C++
patch to nsIDOMWindowUtils.idl + nsDOMWindowUtils.cpp was dropped. Result: every
page.mouse.*, page.click(selector), locator.click(), page.hover(), mouse.wheel()
threw "win.windowUtils.jugglerSendMouseEvent is not a function" on first call.
The fix is shipped in the patched Firefox source (feder-cr/firefox-stealth):
six call sites in juggler/protocol/PageHandler.js and juggler/content/PageAgent.js
were swapped to win.synthesizeMouseEvent — a Mozilla chrome-scope helper that is
already present in FF150. scrollRectIntoViewIfNeeded was also guarded at the two
PageHandler.js sites where it was called unconditionally on the FF150
_linkedBrowser, which no longer exposes that method.
This invisible_playwright release adds the regression suite in tests/test_mouse.py
(12 cases inspired by microsoft/playwright-python/tests/async/test_click.py),
the CHANGELOG, and the version bump. The patched Firefox archive on GitHub
Releases must be refreshed before users actually receive the fix; the
BINARY_VERSION bump to firefox-2 will land with that asset.
Reporter: @trob9 (issue #9) — provided ready-to-apply JS patches, 4-line minimal
repro, and confirmed reCAPTCHA v3 = 0.90 holds after the swap.
E9-E12 exercise the launcher's Linux code paths without spawning a
real Firefox binary or Xvfb. They monkeypatch ``sys.platform`` and
stub ``make_virtual_display`` / ``_binary_on_path`` so the tests run
on any host:
- E9 ``_build_prefs(headless=True)`` on Linux passes
``virtual_display=False`` to the translator, so the Win32-only
``security.sandbox.gpu.level`` workaround never leaks into Linux
prefs (Xvfb handles window hiding instead).
- E10 ``_resolve_headless`` on Linux + headless=True invokes the
dispatcher and stores the returned object on ``self._virtual_display``.
- E11 ``_teardown`` stops the Linux virtual display, clears the
reference, and is idempotent on a second call.
- E12 With Xvfb missing from PATH, ``_resolve_headless`` raises a
clear ``RuntimeError`` mentioning ``Xvfb`` instead of a cryptic
FileNotFoundError.
Suite on Linux/WSL: 286 passed, 5 skipped (4 binary-gated E2E
lifecycle tests + 1 Win32 ctypes test). Binary-gated E1/E2/E5/E8
remain ready to run on Linux once the patched Firefox tar.gz is
fetched locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IT11–IT13 mirror IT10 on the Linux platform branch, verifying:
- Xvfb workarounds coexist with SOCKS5 proxy mutation
- MSAA pin propagates through prefs translation on Linux
- _LINUX_GENERIC_FONT_FACTORS is prepended to per-font metrics
Tests use monkeypatch on sys.platform so they run on any host OS.
Verified green on Linux/WSL alongside the existing Windows tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover the Linux branches in prefs.py that previously had no tests
(font metrics, GPU spoofing, MSAA from profile, canvas noise mask
per renderer, WebGL extension preservation, Xvfb workarounds,
virtual_display no-op) and add construction smoke tests for
_LinuxVirtualDisplay. Also fix two host-platform-dependent tests
so the suite stays green on both Windows and Linux.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final sweep adds unit tests for the modules left at 0% direct coverage
after Phases 1-9:
- launcher._tz_env: 7 tests covering the IANA -> POSIX mapping
including the Phoenix / Honolulu no-DST regression cases
- launcher._humanize_max_seconds, _default_context_kwargs: 11 tests
on the constructor-side helpers (no browser launch)
- _headless.make_virtual_display dispatcher + _WindowsVirtualDesktop
init/teardown: 8 tests (Linux dispatch branch covered without
spawning Xvfb, since __init__ does no I/O)
- async_api.InvisiblePlaywright constructor parity with sync: 8 tests
guarding against drift between the two APIs
Suite: 230 -> 264 passing. Pyramid stays clean: 243 unit / 12
integration / 9 e2e.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five test the constructor only (seed handling, eager profile build,
fail-fast pin validation) and always run. Four spin up the patched
Firefox and exercise the full `with InvisiblePlaywright(...)` lifecycle,
gated on a locally cached binary so CI without the binary skips
cleanly. All 230 tests pass on Windows with the binary fetched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers _accept_language, _font_metrics_for_platform, Windows GPU/MSAA
clearing, Windows canvas noise mask (intel path), Windows WebGL extension
clearing, timezone handling, extra_prefs overlay (add/delete/override/no-op),
dark-theme system colors palette, locale normalization, Xvfb-key absence on
Windows, virtual_display sandbox workaround, and seed-derived LAN IP.
Linux-specific branches are intentionally not covered in this commit per
scoping instruction; they remain available in the plan for a follow-up
pass when running on Linux.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers _validate_pin_key (all groups + negatives), _apply_pins_to_raw
(fonts list/tuple/typeerror, multi-pin, no-mutation, unknown-key guard),
and generate_profile (determinism, seed coercion, pin propagation through
to_prefs_dict, frozen-instance, dark_theme bool coercion, fonts list
roundtrip, int31 boundary). Includes a guard test that every dotted pin
key has a _PIN_TO_RAW mapping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers classify_gpu (decision-table over 28 GPU strings inc. boundary
values for AMD Radeon number ranges), _screen_tier resolution
classification, derive_font_prefs / derive_font_whitelist coherence and
determinism, and the public Forge / sample entry points (locked
identity, key set, type correctness, seed determinism).
One plan deviation: the original plan claimed `AMD FirePro W7100` →
`workstation`, but the workstation regex requires a `Radeon` prefix,
so FirePro alone falls through to the `mid_range` fallback. Test
asserts the actual behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
invisible-playwright: a patched Firefox 150.0.1 for browser-fingerprint
stealth, shipped as a Playwright-compatible Python wrapper.
* Sync + async InvisiblePlaywright launcher (firefox_user_prefs, virtual
desktop on Windows, SOCKS5 auth via patched nsProtocolProxyService)
* fpforge: Bayesian fingerprint sampler over GPU / audio / fonts /
screen / ~400 other navigator fields
* WebRTC stealth: srflx address swap, synthetic srflx fallback,
private-LAN host candidates. No real public IP leak via STUN.
* GPU sandbox fix for FF150 alt-desktop regression
* Bezier-curve mouse motion baked into Juggler
Targets Windows x86_64 + Linux x86_64. Binary fetched on first run from
GitHub Release "firefox-1".