Commit graph

105 commits

Author SHA1 Message Date
feder-cr
ef86cd57dc test(e2e): use the full BotD + FpJS API (per-detector + components)
Maximize what the real detectors exercise on CI:
- BotD: assert the aggregate detect().bot==false AND every individual detector
  via getDetections() (webDriver/userAgent/appVersion/plugins/process/...). This
  restores the per-detector granularity of the deleted hand-rolled test_botd_*,
  but from the real library, with diagnostics on which detector flagged.
- FpJS: visitorId present + stable across two launches, AND a rich component
  surface (>=15 signals — a suppressed/thin surface is itself a tell). We don't
  hard-assert zero errored components (some are legitimately unsupported per
  browser); visitorId stability is the authoritative aggregate check.

All 3 green locally against firefox-9.
2026-06-09 18:15:25 +02:00
feder-cr
2410582960 test: drop the 15 hand-rolled test_botd_* (real BotD now runs on CI)
These mirrored BotD's individual detectors (webdriver/appVersion/userAgent/
functionBind/productSub/process/evalLength/languages/plugins/mimeTypes/
distinctive-props/documentAttributes/windowSize/webGL) — our reverse-engineering
of BotD from before we ran the real library. Now that test_detectors_e2e.py runs
the actual @fingerprintjs/botd against the binary on CI (asserts bot===false),
they're redundant. Kept the complementary/unique tests: fpjs-surface, pinning,
sannysoft + fpscanner (which mimic detectors we do NOT run, so not covered by
BotD/FpJS), and the determinism/consistency suite.
2026-06-09 18:02:36 +02:00
feder-cr
df4493d553 test(e2e): run the real detectors (BotD + FingerprintJS OSS) on CI
Instead of only our hand-rolled signal checks, load the actual MIT detection
libraries against the patched binary and assert it isn't flagged:
- BotD (the client-side bot detector FingerprintJS Pro itself uses): detect()
  must return bot=false (no automation/headless tell).
- FingerprintJS OSS: visitorId present and stable across two fresh launches
  with the same seed (drift = per-session entropy = a bot tell).

Hermetic: the libs are vendored (tests/vendor/, pinned, MIT) and served from a
localhost server — no external CDN (Firefox tracking-protection blocks it
anyway), no IP/network dependency, runs identically on a dev box and the GitHub
runner. Both green locally against firefox-9.
2026-06-09 17:53:11 +02:00
feder-cr
8ba88958be test(e2e): hermetic SOCKS5 auth + routing e2e (runs on CI)
Proves the patched nsProtocolProxyService end to end: the binary performs the
RFC1929 user/pass handshake with the configured socks_username/password and
relays the page through the proxy — something Playwright's own proxy= can't do,
and which test_proxy only unit-checks at the pref level.

Fully hermetic so it runs identically locally and on the GitHub runner: a local
SOCKS5 server (requires auth, records the creds it saw) + a local HTTP target,
with the localhost target forced through the proxy via allow_hijacking_localhost
+ no_proxies_on="". No external site, no secrets. 3/3 local.
2026-06-09 17:37:40 +02:00
feder-cr
4564b26158 test: stabilize 2 e2e for CI (hover wait, webrtc srflx env-skip)
Running the full e2e on GitHub (xvfb) surfaced 2 env-sensitive failures, neither
a binary bug:
- test_hover_triggers_mouseenter read window.__h immediately after hover(); the
  mouseenter can land a beat later on a virtual display. Use wait_for_function
  (still fails if the event genuinely never fires). 5/5 locally now.
- test_not_blocked_behind_tcp_only_socks needs a remote origin loaded fully
  through the proxy to inject the synthetic srflx; that path is environment-
  sensitive on a datacenter CI box. Keep the hard "zero candidates = blocked =
  FAIL" check, but skip (not fail) if the srflx didn't engage — validated locally.
2026-06-09 17:23:46 +02:00
feder-cr
036a1a1d5f ci: run the full e2e browser suite on GitHub (linux, xvfb, every push/PR)
Maximize what's tested on CI: a new e2e job fetches the public firefox-9 binary
and runs all 138 @pytest.mark.e2e via scripts/run_e2e.py under xvfb-run. The
wrapper launches Firefox HEADED on a (virtual) display, which is stable on the
hosted runners — unlike the true-headless drive-gate that flaked. Secret-free
(public binary + local fake-SOCKS for webrtc), so safe in public CI; the proxy
realness gate (fppro) stays local. Trial-running now; will tune if xvfb/timeouts
need it.
2026-06-09 17:03:43 +02:00
feder-cr
0b53e18e23 ship: bump BINARY_VERSION to firefox-9 + refuse the broken firefox-8 (audit B5)
firefox-9 is published (build 5/5, drive-gate 5/5, full e2e 137/1-skip, fppro
ALL CRITICAL CLEAN, WebRTC real, sha256-verified download path). Point the
wrapper at it.

Guard: ensure_binary() now refuses any version in BROKEN_VERSIONS (={firefox-8})
with a clear error instead of handing over an undrivable binary — a cached
firefox-8 from before this bump would otherwise keep being used silently. Tests:
the current BINARY_VERSION can never be in BROKEN_VERSIONS; firefox-8 stays
flagged; ensure_binary("firefox-8") raises.
2026-06-09 16:02:52 +02:00
feder-cr
62cdf626a0 ci: pin actions to SHA + single-source the playwright pin (audit B6/B4)
B6: pin every third-party action in the build/publish path to an immutable
commit SHA (a retagged actions/checkout or action-gh-release would otherwise
inject code into the binary users download). The other workflows (tests, webrtc,
launch-matrix) handle no secrets, so they're left on tags.

B4: the playwright pin lived in two workflow files with no shared source. Move
it to scripts/playwright_pin.txt that both read, so they can't drift. The drive
gate already ENFORCES playwright<->juggler compatibility (an incompatible pin
fails the launch/drive and nothing publishes); the file is the single bump point
when the juggler is re-synced.
2026-06-09 15:59:18 +02:00
feder-cr
5dac302938 test: activate the full e2e (browser-driving) suite + add fetch --force
The 138 @pytest.mark.e2e tests were doubly inactive: deselected by addopts AND
skipped without a cached binary — and 3 of the 6 per-file firefox_binary
fixtures silently ignored INVPW_BINARY_PATH, so they'd test whatever was cached
even when you pointed the suite elsewhere (a false-confidence trap).

- Centralize firefox_binary into conftest.py (env INVPW_BINARY_PATH → cache →
  skip); delete the 6 duplicates. Unify test_webrtc_realness onto the same env.
- scripts/run_e2e.py: one command that runs ALL e2e against a chosen binary,
  with reruns so an under-load interaction flake (dblclick/hover pass 3/3 in
  isolation) self-heals while a real break fails every attempt. The webrtc e2e
  fake a TCP-only SOCKS locally, so the suite is offline. This is the MANDATORY
  pre-release browser gate (local — hosted runners are too interaction-flaky).
- Running the suite against firefox-9 surfaced a real gap: `invisible_playwright
  fetch --force` was unrecognized (the subparser took no args) though the e2e
  test + docstring expect it. Implement it: drop the cached version dir, refetch.
- Add pytest-rerunfailures + playwright to the dev extras.

Baseline against firefox-9: 136 passed, 1 skipped (linux_only on win host),
1 was the --force gap now fixed.
2026-06-09 15:40:02 +02:00
feder-cr
67b5e7cd5e ci: split drive gate — smoke on all 5 legs, full interaction on linux-x86_64
The free hosted runners (windows-latest worst) are content-process unstable
under a heavy headless interaction sequence: clicks/moves cascade into
context-destroyed / selector-timeout / eval-CSP, even across 3 retries, even on
linux-arm64. That's an environment limit, not a binary defect (the binaries
drive 20/20 locally and the stable legs pass).

So: SMOKE (launch + http page + UA + webdriver + DOM read) runs on all 5 legs —
the firefox-8/juggler catcher, robust everywhere. FULL (+ mouse/keyboard/canvas/
navsurface, the firefox-2 class) runs only on linux-x86_64; the interaction code
is platform-identical JS (omni.ja), so one reliable full run covers every
platform, and win interaction stays covered by local pre-release testing.
2026-06-09 15:01:21 +02:00
feder-cr
5f546f4d63 ci: serve drive-gate page over loopback HTTP + retries (robust on win-CI)
windows-latest headless kept flaking on special-scheme pages: data: URLs get
re-normalized (re-nav), about:blank + redundant goto destroys the context, and
both can carry a CSP that blocks eval(). Serve the test page over a real
http://127.0.0.1 instead — none of those quirks, and it adds real-navigation
coverage (await a response). Evaluates stay arrow-functions (no eval), listeners
are inline-script (no on* attrs), and transient context-destroyed/detached/timeout
gets up to 2 retries. A genuinely broken binary fails all 3 attempts.
2026-06-09 14:53:05 +02:00
feder-cr
2dd2224e73 ci: drive gate uses arrow-function evaluates (fixes win-CI eval-CSP block)
windows-latest failed both attempts with "Page.evaluate: call to eval() blocked
by CSP". Bare-string evaluates (page.evaluate("navigator.userAgent")) make
Playwright fall back to eval(), which a page CSP blocks. Pass arrow functions
instead (called via callFunction, never eval'd), and wire the click listener
with addEventListener instead of an inline onclick (also CSP-sensitive). Both
are Playwright best practice and platform-agnostic.
2026-06-09 14:47:02 +02:00
feder-cr
610f09d2c2 ci: build drive-gate DOM on about:blank, not a data: URL (fixes win-CI flake)
linux+macOS drive went green but windows-latest kept throwing "execution
context destroyed by navigation" at a wandering evaluate (passed 20/20 win-local,
no browser crash logged). Root cause: the unencoded data: URL gets re-normalized
(re-navigated to its percent-encoded form) by Firefox; the slower win runner
races that re-nav against the evaluates. about:blank is canonical and never
re-navigates, so the DOM is now built there via innerHTML. Also add one logged
retry on transient context-destroyed/detached (a broken binary fails both).
2026-06-09 14:42:26 +02:00
feder-cr
90529ff181 ci: de-flake drive gate — drop the racy iframe probe, keep input/canvas checks
Re-running the enriched gate on the real binaries exposed a ~1-in-5 flake: the
iframe probe (nested data: / srcdoc) re-navigates, so Juggler's frame id changes
mid-check ("execution context destroyed" / "Frame was detached"). set_content
isn't an option either — this build rejects its document.write ("operation is
insecure").

Drop the iframe from the gate: a same-origin srcdoc iframe is a weak proxy for
the cross-origin issue #20 anyway. The page is now a plain subframe-free
goto(data:), and the mouse/keyboard/canvas-determinism/navigator-surface checks
(the firefox-2 class + stealth smoke) stay. 20/20 clean locally. The faithful
#20 sentinel (tests/test_cross_origin_iframe.py, two localhost origins) should
be wired as its own e2e gate job.
2026-06-09 14:35:50 +02:00
feder-cr
8d7b6eafdf ci: drive gate exercises mouse/keyboard/iframe/canvas, not just navigate+eval
An adversarial audit of the pipeline found the drive gate only did goto+evaluate,
so several historically-shipped breakages would still pass it green:
- firefox-2 (jugglerSendMouseEvent missing) — no mouse/keyboard was tested
- issue #20 (cross-origin iframe content_frame() None) — no iframe was tested
- canvas non-determinism (stealth seed) and headless navigator tells

ci_drive_gate.py now clicks a button, moves the mouse, types into an input,
reaches into an iframe, checks an identical canvas draw is byte-stable, and
checks navigator.languages/plugins — all offline (data: URLs), GPU-free, no
proxy. Validated against the real build.

Pipeline hardening from the same audit:
- Windows: stop swallowing `mach package` failure and never fall back to the
  dev tree dist/bin (that masked the firefox-7/8 packaging bugs)
- macOS: plutil -lint Info.plist + required-key checks (a malformed plist ships
  fine through a headless drive but Finder calls the .app "damaged")
- publish: assert all 5 archives present + fail_on_unmatched_files (no silent
  partial release if a build leg drops out)
2026-06-09 12:53:42 +02:00
feder-cr
7260f461bb ci: verify-assets needs contents:write to read draft releases
gh release download 404s ("release not found") on a draft tag when the token
is contents:read — GitHub only shows drafts to tokens with push access. The
workflow still only reads assets; the scope bump is purely for draft visibility.
2026-06-09 12:29:35 +02:00
feder-cr
86a04d2d34 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.
2026-06-09 12:24:06 +02:00
feder-cr
eec373a719 ci: $0 5-target GitHub Actions release pipeline + wrapper macOS/arm64 support
release.yml builds linux-x64/arm64 + win-x64 (cross) on free Linux runners and
macos-arm64/x64 on native Mac runners; packages per the wrapper contract
(juggler-gated so binaries are Playwright-drivable, issue-#14 symlink-safe via
cp -aL), validate_release.py gate, ad-hoc macOS codesign, DRAFT publish.
constants.py: arm64 + darwin ARCHIVE_NAME + BINARY_ENTRY_REL (Firefox.app).
download.py: macOS post-extract xattr quarantine strip.
BINARY_VERSION unchanged (firefox-8); the juggler-fixed firefox-9 is a separate
release cut + pin bump.
2026-06-09 10:45:27 +02:00
Federico
215b8801d7
Update README.md 2026-06-08 06:16:49 +02:00
feder-cr
cc7d95c8ae release: pin BINARY_VERSION to firefox-8
firefox-8 carries the WebRTC fixes: behind a proxy, ICE now completes with an
mDNS .local host and a server-reflexive candidate on the proxy IP (genuine
nICEr priority/foundation) instead of coming up blocked, and IPv6 host
candidates are suppressed. Binary published on the releases page; validated on
both Windows and Linux via scripts/validate_release.py.
2026-06-06 22:34:20 +02:00
feder-cr
8bf72da40c test(webrtc): realness sentinels + e2e behind a fake TCP-only SOCKS proxy
Unit sentinels (run in CI) assert the rules a real WebRTC profile must meet:
host candidate is mDNS .local (never a raw LAN IP), the synthetic srflx carries
the egress IP with a genuine nICEr priority (rejecting the old local_pref
0xFFFF) and a foundation distinct from the host one, and CreepJS's resolver
returns the egress (and reads a host-only SDP as blocked).

e2e tests launch the binary and check the live gather. "Behind a proxy" is
reproduced without any external proxy: an in-process SOCKS5 server relays TCP
CONNECT but refuses UDP ASSOCIATE (a TCP-only residential proxy), and the
egress IP is injected as an RFC 5737 TEST-NET address.
test_not_blocked_behind_tcp_only_socks guards the gather-fails-behind-proxy bug.

webrtc-e2e.yml runs the e2e on demand (needs a binary that carries the fixes).
2026-06-06 18:39:03 +02:00
Federico
e2bcd0cd4c
Update README.md 2026-06-06 15:24:23 +02:00
Federico
6f44e1af38
Update README.md 2026-06-06 15:19:45 +02:00
Federico
262d388b99
Update README.md 2026-06-06 07:51:46 +02:00
Federico
b7eda606a2
Update README.md 2026-06-06 07:50:00 +02:00
Federico
e3b8a42ded
Update README.md 2026-06-06 07:48:19 +02:00
Federico
26fa962d24
Update README.md 2026-06-06 05:45:37 +02:00
Federico
7b860b7398
Update README.md 2026-06-06 05:43:55 +02:00
feder-cr
f2664f96e1 feat: timezone="auto" resolves from any egress + weekly geoip auto-update
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).
2026-06-06 05:16:20 +02:00
feder-cr
369f3f7fdb docs: document timezone="auto" in API docstrings + CHANGELOG
Follow-up to the timezone="auto" feature. The launcher/config timezone
docstrings still said "empty means host TZ", now incomplete since
""+proxy defaults to auto.

- launcher timezone arg: full precedence (auto default, host escape
  hatch, fail-early on unresolvable zone behind a proxy)
- config.get_default_stealth_prefs: note it does NOT resolve "auto"
  (pure pref builder, no proxy/network context)
- CHANGELOG: Unreleased entry for timezone="auto" + new deps
2026-06-06 04:43:40 +02:00
feder-cr
d6c3de7730 feat: timezone="auto" derives the zone from the proxy egress IP
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
2026-06-06 04:16:22 +02:00
Federico
143aff4bd2
docs: warn about true-headless gotcha in public config API (#27)
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.
2026-05-28 17:19:26 -07:00
Federico
ee0fe57ced
feat: public config helpers for third-party integrations (#25)
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.
2026-05-28 17:05:22 -07:00
feder-cr
929da150bc chore: bump version to 0.1.9
Carries the #24 fix (importlib.metadata-driven __version__ + top-level
--version flag) so users see a real version bump after `pip install --upgrade`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:24:05 -07:00
feder-cr
66c6b09821 fix: __version__ comes from package metadata; add --version flag (#24)
Two bugs reported in #24:

1. `python -m invisible_playwright version` printed the literal "0.1.0"
   regardless of the installed version. Root cause: __version__ in
   __init__.py was hardcoded and never bumped when the package version
   moved past 0.1.0. Fix: read from importlib.metadata so __version__
   stays in lockstep with pyproject.toml's `version` field by construction.

2. `python -m invisible_playwright --version` errored with "the
   following arguments are required: cmd". Root cause: the parser had
   `required=True` on its subparsers and no top-level --version flag.
   Fix: add a top-level `--version`/`-V` flag using argparse's standard
   version action, drop `required=True`, and reroute the "no subcommand"
   case through parser.error() so the existing test_no_subcommand_errors
   contract is preserved.

7 new unit tests pin both behaviours so they can't regress silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:18:03 -07:00
feder-cr
f208f5262c docs(README): drop LLM-style typographic characters
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>
2026-05-25 22:57:29 -07:00
feder-cr
35508595fa docs(README): drop "(binary only)" qualifier on CloakBrowser row
The Closed source label is enough; the parenthetical was extra noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:54:23 -07:00
feder-cr
97a3cdfc17 docs(hero.gif): remove top dark band, top-align screenshots
Previous build still had ~25px of dark padding above the screenshot
plus a thin shadow border around it. Both gone now. Screenshot is
flush against the top edge of the frame and runs full-width; the only
remaining chrome is the 90px caption bar at the bottom with the green
checkmark and detector verdict.

Net effect: roughly 25-30% more usable screenshot area per frame, no
visible "bar" above. Size 478 KB (was 381 KB, larger because the
embedded pixels are now bigger).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:51:51 -07:00
feder-cr
033d0e9b35 docs(hero.gif): drop top brand bar, enlarge screenshot area
The "invisible_playwright" / github URL strip at the top of the
hero was redundant - the README headline right above the embed
already says both. Removing it frees vertical pixels for the actual
screenshot, which is what the visitor is here to see.

Layout now: screenshot fills everything above the 90px caption bar
at the bottom. Each screenshot renders ~10% larger.

Size 381 KB (was 356 KB - slightly larger because the screenshots
themselves are bigger after the scale change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:48:06 -07:00
feder-cr
def731e6ec docs(README): drop disclaimer + closing pitch after comparison table
The two trailing paragraphs (testing disclaimer and the
"if you need Firefox + active maintenance" wrap-up) restated what
the table already showed. The table is the comparison; let it stand
on its own and jump straight to Install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:38:31 -07:00
feder-cr
4a71a0142a docs(README): flag CloakBrowser as closed source
The previous wording lumped CloakBrowser in with Camoufox as
"open-source peers." That was wrong: CloakBrowser publishes a Chromium
binary plus a wrapper, but the C++ source patches that produce the
binary are not in the repo. From a user's standpoint that's the same
trust profile as a closed-source commercial fork.

Comparison table updated to reflect this: Open source column now
distinguishes Camoufox (MPL, full source) and ours (MIT, full source
in invisible_firefox) from CloakBrowser (binary only) and the four
SaaS competitors (closed).

Intro paragraph rewritten to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:19:13 -07:00
feder-cr
9432e789c6 docs(README): drop 6-bullet hook under hero
The hero GIF already shows each detector with its verdict in-frame
(reCAPTCHA 0.90, CreepJS 0 lies, FingerprintJS Pro not detected,
WebRTC no leak, sannysoft all green). Repeating the same five points
as a bullet list immediately below the visual was duplication that
pushed the comparison table and install snippet further down.

Above the fold is now: tagline, hero GIF, then straight into Why
it's powerful.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:17:38 -07:00
feder-cr
75e6927904 docs(README): drop Results section
The hero GIF above the fold already lists each detector with its
verdict in-frame. The Results section was repeating the same five
items in prose right after the visual, which is the kind of
above-the-fold padding that pushes the install snippet further down
without adding signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:43:57 -07:00
feder-cr
f4d42dcac4 docs(README): add hero.gif above-the-fold + consolidate Results section
Hero is a 5-frame slideshow (12.5s loop, 356KB, 1200x675) cycling
through the five detection-test screenshots with a green-check
caption per frame. Branded with the project name top-left and a
github URL top-right so it works when embedded in tweets or blogs
without context.

Sits immediately after the tagline so the first thing a visitor sees
above the fold is moving visual proof, before any text. Industry
research on top-performing OSS READMEs in 2026 consistently puts
GIF/video first as the single biggest conversion lever for star intent.

Also collapsed the five expanded Results subsections down to a single
section: the GIF already shows each tester live, so the prose now
gives one short bullet per detector plus links to the original
full-resolution screenshots for anyone who wants to inspect them.

Net effect: roughly 200 lines of vertical scroll removed above the
fold, hero visual added, deep-link to per-tester screenshot
preserved.

No new test surface. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:28:18 -07:00
feder-cr
0375cf3f79 docs(README): tighten positioning + benchmark vs camoufox and CloakBrowser
Three changes aimed at making the project's value legible in 5 seconds
for someone landing from a Trending list or HN thread:

- New tagline + quantified bullet hook at top. Concrete numbers
  (0.90 reCAPTCHA, 0 CreepJS lies, 5/5 detection suites passed) up
  front instead of generic "passes the hardest detectors" wording.

- Comparison table rewritten. The commercial-stack columns (Multilogin,
  GoLogin, AdsPower, Dolphin Anty, Kameleo) were noise for the OSS
  audience we want to reach. Replaced with the two relevant
  source-level peers: Camoufox (Firefox, currently in a long
  maintenance gap) and CloakBrowser (Chromium, fresh, but capped at
  the Chromium reCAPTCHA ceiling).

- "Why it's powerful" opens with explicit positioning vs the two
  peers, plus the reCAPTCHA v3 score that's the most defensible
  numeric claim.

No code change. Repo homepage URL set separately via API to deep-link
the "Why it's powerful" section so visitors from external links land
on the value pitch rather than the badges row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 08:26:12 -07:00
feder-cr
62b9030d2a ci: fix firefox --version check (stdout match, not exit code)
firefox.exe --version on Windows prints the version string but may
return non-zero exit code (sub-process fork quirk). The previous check
treated that as a launch failure, producing a false-positive failure
across the whole matrix while the binary actually launched cleanly.

Switch to matching the printed output instead, so we only fail when the
binary really can't start.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:25:23 -07:00
feder-cr
60d13a2b6e ci: cross-Windows-edition smoke matrix for firefox binary launch
Triggered by issue #22 (firefox-7 SxS mismatch reported on Win11 26200
by jannusdorfer-create). Verifies the shipped binary launches cleanly
on every Windows runner GitHub offers: windows-2022, windows-2025,
windows-latest, across Python 3.11 / 3.12 / 3.13.

Each cell does the reporter's exact flow: fresh checkout, pip install
from source, python -m invisible_playwright fetch, then runs the
InvisiblePlaywright(seed=9128) snippet.

If all cells pass the bug is environment-specific to the reporter
(corporate edition, EDR, GPO). If any cell fails the same way we ship
a sidecar mozglue.manifest in the next release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 07:20:00 -07:00
feder-cr
3d8ba0b82c feat: deterministic reCAPTCHA cookie pre-seed via Bayesian browsing history
Adds opt-in helper that auto-injects coherent cookie history into every
BrowserContext created via new_context(). Content is fully deterministic
from the persona seed so a given seed always presents the same cookies
across sessions.

Composition (per persona, all derived from seed):
  - 5 cookies on .google.com (NID, CONSENT, SOCS, _GRECAPTCHA, ENID).
    Excludes 1P_JAR which was deprecated by Google in 2022. CONSENT
    `lang+region` token derived from the persona's IANA timezone
    (Europe/Rome -> it+IT, America/* -> en+FX, etc.). NID prefix
    broadened to 100-540 to cover historical versions.
  - Per-site cookies on 13-25 "visited" everyday domains, sampled from a
    Bayesian network conditioned on gpu_class - workstation/high_end
    personas trend toward dev/tech sites, low_end/integrated_old trend
    toward shop/news/reference. Each site contributes 1-7 cookies based
    on a `cookie_profile` tag. Cookie pool includes _ga, _gid, _clck,
    _clsk, __cf_bm, OneTrust/CookieYes consent, _fbp (Facebook Pixel),
    _dc_gtm_<id> (Tag Manager helper), __hssrc (HubSpot helper).

API:
    Stealthfox(seed=42, prep_recaptcha=True)

No per-call configuration: visited-sites + cookie composition all derived
from the persona seed via the Bayesian sampler.

Gated server-side: forced False if profile_dir is set (persistent profile
owns its own state). All expiries capped to 395 days per Chrome/Firefox
400-day RFC 6265bis-15 limit.

Bayesian integration:
  - New `derive_browsing_history(gpu_class, rng)` in _fpforge/_sampler.py
    (parallel to `derive_font_prefs`).
  - New data files: browsing_pool.json (50 site entries) and
    cpt_browsing_given_class.json (per-class probabilities).
  - Profile dataclass exposes `browsing_history` field.
  - _recaptcha_seed.py consumes Profile.browsing_history; receives
    timezone separately to derive CONSENT lang+region.

Also drops a dead Chromium-only e2e test that always skipped on our
Firefox-only wrapper.

Test coverage: 29 unit tests covering composition, profile recipes
(minimal/ga_only/ga_cf/ga_consent/ga_consent_clarity), determinism,
Chrome 400-day cap, Playwright field requirements, CONSENT lang
mapping (IT/DE/US/default), helper-cookie probability distributions,
end-to-end with real fpforge Profile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:01:26 -07:00
feder-cr
413db06690 docs(README): fix browser-launches badge to show only the number
The per-asset shields.io endpoint rendered the asset filename next to
the count ('1 [launch.txt]'). Switching to the per-tag total endpoint
renders as just the integer.
2026-05-23 17:07:19 -07:00
feder-cr
70b6a54dbc docs(README): add browser launches badge
Pulls the github download_count of the companion repo's usage-counter
asset. Click-through lands on the invisible_firefox release where the
full disclosure of what the counter measures lives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:59:17 -07:00