firefox-9 ships linux x86_64/arm64, windows x86_64 and macOS arm64/x86_64
since the CI pipeline; the README still said Windows+Linux only. Also
document the macOS quarantine step (ad-hoc signed, not notarized) and the
fetch --force flag added with firefox-9.
Refine timezone="auto" so it ALWAYS resolves (drop the "host" sentinel):
- ""/"auto" resolve from the proxy egress when a proxy is set, else from the
host own public IP (direct lookup); an explicit zone is the only opt-out.
- on failure: with a proxy raise; without a proxy fall back to the host TZ.
GeoIP DB now auto-updates against daijro/geoip-all-in-one weekly rebuild:
cache the latest, re-check after GEOIP_REFRESH_DAYS (7), prune old tags,
reuse a stale cache offline; GEOIP_MMDB_VERSION is only the cold fallback.
tests: test_geo.py (37) + test_geoip_update.py; full unit suite 429 green
plus 8 live combinations (proxy / no-proxy / explicit / failing / freshness).
A proxy in a different country paired with the host timezone is the
classic timezone_mismatch signal, so a session with a proxy and no
explicit timezone now resolves the zone automatically.
- discover the egress IP through the proxy (SOCKS via requests[socks]),
map it to an IANA zone with an offline mmdb (daijro/geoip-all-in-one,
downloaded + cached like the Firefox binary; GPL so not vendored)
- precedence: explicit zone wins; ""+proxy and "auto"+proxy resolve;
""/"auto" without a proxy stay host; "host"/"local" force host TZ
- fail-early when a proxy is set but the zone cannot be resolved, never
a silent host-TZ fallback
- deps: requests[socks], maxminddb, tzdata (zoneinfo ships no DB on Windows)
- resolve_session_timezone / ensure_geoip_mmdb exported for integrations
Live smoke test caught a footgun: passing headless=True directly to
playwright.firefox.launch() with our prefs puts Firefox in true
headless mode (no rendering pipeline) which breaks canvas/audio/WebGL
fingerprint coherence. InvisiblePlaywright translates user-facing
headless=True to Playwright headless=False + virtual display
automatically; the new public helpers do not, so the docstring +
README now flag this explicitly.
Verified: same prefs + headless=False via firefox.launch() reaches
bot.sannysoft.com with 23 passed / 0 failed, matching what
InvisiblePlaywright produces.
Adds invisible_playwright.config module with:
- get_default_stealth_prefs(seed, *, pin, locale, timezone,
extra_prefs, humanize, virtual_display) -> dict
- get_default_args() -> list
Both also re-exported at the package root alongside the existing
InvisiblePlaywright. ensure_binary is also re-exported there for
parity with the cloakbrowser.download.ensure_binary integration
pattern that downstream projects (Skyvern PR #5340, crawlee-python
PR #1794, agno PR #8129) already expect.
These helpers let third-party fetchers (changedetection.io plugins,
Crawlee BrowserPool subclasses, agno toolkits) drive
playwright.firefox.launch(executable_path=..., firefox_user_prefs=...)
themselves without depending on the InvisiblePlaywright context
manager owning the lifecycle. Same seed semantics, same humanize
toggle, same extra_prefs overlay as the existing wrapper.
Tests: tests/unit/test_config_public.py adds 14 unit tests covering
deterministic seed, locale/timezone/pin/extra_prefs/humanize
variations, and round-trip via the public namespace. Full unit suite
(392 tests) stays green.
Backwards compatible: InvisiblePlaywright surface is unchanged.
BINARY_VERSION stays at firefox-7. Python-only release.
Replaced em-dashes (—) with commas, colons, or periods depending on
context. Kept all emoji (✅/❌/⚠️) in the comparison table since
those are scannable scoring cues, not stylistic.
Net cleanup: 6 em-dashes removed from tagline, hero alt-text,
"Why it's powerful" paragraph, and the comparison intro.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>