Commit graph

53 commits

Author SHA1 Message Date
feder-cr
64eef4daff release: 0.1.8 - fix #20 cross-origin iframe regression (pref-only)
Pin fission.webContentIsolationStrategy=0 (IsolateNothing) in baseline
prefs. FF150 ships strategy=1 (IsolateEverything) by default, which
site-isolates every cross-origin iframe into a separate webIsolated
content process even when fission.autostart=False - different from
FF146 Playwright behavior. The parent's Juggler FrameTree then sees
the iframe placeholder with no docShell, no URL and a stale execution
context, so content_frame() returns None, frame.evaluate() throws
cross-origin permission errors, and frame_locator(...).click() times
out. Reproduced with a local cross-origin HTTP harness (two 127.0.0.1
ports = two SOP origins).

The fix is a single pref because that's the level the original Juggler
code paths assume - vanilla Playwright Firefox 146 ran with the looser
default. Disabling site-isolation costs nothing for a single-user
puppet browser; process-per-browser/profile isolation is unaffected.
Caller can A/B per session via
extra_prefs={"fission.webContentIsolationStrategy": 1}.

Tests: tests/test_cross_origin_iframe.py adds 4 unit sentinels (pref
in baseline, survives translate_profile_to_prefs, extra_prefs override
works) and 5 e2e sentinels that spin up two local HTTP servers on
random free ports and verify the four protocol operations that
regressed (page.frames URL tracking, content_frame(), frame.evaluate(),
frame_locator(...).locator(...)) plus dispatch_event('click')
end-to-end, for plain, sandboxed and titled iframes. A future FF
upgrade or A/B flipping the pref will fail the suite before shipping.

Verified clean: pytest -m unit (342 passed), fppro_full.py (ALL
CRITICAL FLAGS CLEAN), fppro_consistency.py (visitor_id stable).

BINARY_VERSION stays firefox-7 - Python-only release.

Issue: https://github.com/feder-cr/invisible_playwright/issues/20

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:05:02 -07:00
feder-cr
cb3755cdd5 chore(issue-templates): split bug_report into 3 focused templates
Replaces the single bug_report.yml with 3 templates that each require
only the fields relevant to their bug category. The old form asked every
reporter for URL / selector / runnable repro snippet, which made no sense
for launch failures (no browser to navigate, no element to select) and
overshot for detection reports (proxy region matters more than selector).

  01-launch-failure.yml      browser or wrapper never reaches new_page
  02-site-or-action-bug.yml  browser starts, an op (click / navigate / eval) fails
  03-stealth-detection.yml   a detector flags the browser (FpJS, CreepJS, BotD, ...)

Each template asks for the env basics plus the fields specific to its
category. The site/action one keeps the strict requirements introduced in
the previous template revision (headless, proxy, profile_dir, URL, selector,
runnable repro). The detection one drops selector/profile and asks for
detector name + verdict paste + screenshot. The launch one drops URL /
selector / proxy and asks for install command + full traceback.

Also fixes the contact_links: the old "Bug in the patched Firefox itself"
link pointed to feder-cr/firefox-stealth, a repo that was deleted on
2026-05-19 when source moved to feder-cr/invisible-firefox. Updated to
the new URL and clarified the boundary with the new stealth detection
template (detection results stay in this repo, source-level C++/IDL
patches go in invisible-firefox).
2026-05-22 20:28:31 -07:00
feder-cr
9571c3049d chore(issue-template): require headless / proxy / profile / URL / selector / runnable repro
Re-shapes the bug report form so reporters cannot submit underspecified
tickets like #18 / #19 / #20 which all required multiple round-trips
asking for basic environment context. Every field is now required unless
explicitly marked optional.

New required fields:
- headless=True/False (or N/A) — many bugs only repro on Windows
  headless=True where we use a hidden alt-desktop
- Proxy region (no proxy / UK / US / other / datacenter) — site
  behavior is often geo-dependent (e.g. GDPR consent on UK only)
- Profile directory (transient / persistent) — affects cookie state
- Exact URL string (no more "the homepage")
- Exact selector / locator (for click / locator bugs)
- Runnable reproduction Python snippet (pre-filled with scaffold so
  reporters cannot submit "just open the link")

Existing fields preserved: version, OS, Python, expected, actual,
confirmations.

New optional fields: screenshot upload area, browser/Playwright DEBUG
logs textarea, free-form notes.
2026-05-22 19:49:48 -07:00
feder-cr
1701b34688 release: 0.1.7 - pin to firefox-7 (issue #18 real fix)
Bumps BINARY_VERSION to firefox-7 which ships the real fix for issue #18
(id.sky.com tab crash on Windows headless=True).

firefox-7 contents:
- Canvas2D getImageData stealth spoof moved from read-only mapped surface
  to JS Uint8ClampedArray writable buffer (commit 2e17b4871f93 in
  invisible-firefox). Fixes the segfault on GPU-backed canvases that hit
  during page unload.

Paired with the wrapper-side security.sandbox.content.level=4 workaround
shipped in 2e0adbd (this repo), the combination resolves both halves of
issue #18: cross-process navigation crash in loop AND segfault at close.

End-to-end tested with InvisiblePlaywright headless=True + UK proxy on
id.sky.com: page survives 30s+, no crash in loop, no crash at close.

firefox-6 was rolled back when its partial-fix hypothesis turned out to
be the wrong root cause. Skipping straight to firefox-7.
2026-05-21 20:42:00 -07:00
feder-cr
b98455bf8a test: unblock pre-push hook collection
- pyproject.toml: norecursedirs for tests/playwright-upstream/, a vendored
  Microsoft Playwright test suite with its own pixelmatch API version
  mismatch. We run it explicitly when doing compat audits, not on every
  push. Default collection now ignores it so the pre-push hook (which
  runs the full default pytest collection) doesn't error out.
- tests/test_service_worker.py: replace em-dash with hyphen inside a
  bytes literal at line 91. Python rejects non-ASCII bytes literals
  with SyntaxError at collection time. Now collects cleanly.

Both were blocking unrelated pushes (e.g. the issue #18 fix in the
previous commit). Splitting them out so the issue #18 commit stays
focused.
2026-05-21 20:21:12 -07:00
feder-cr
2e0adbde33 fix: id.sky.com tab crash on Windows headless=True (issue #18)
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.
2026-05-21 20:20:58 -07:00
feder-cr
cf59e98fa9 test: fingerprint surface + consistency e2e tests
96 e2e tests reproducing the canonical anti-bot / fingerprinting
libraries' checks against a local InvisiblePlaywright session
on about:blank (no network).

Surface (28 tests):
  - BotD: webdriver, app_version, UA tokens, function_bind, productSub,
    process, eval.length, languages, plugins, mimeTypes, distinctive
    window/document globals, html attributes, window size, webgl_debug
  - sannysoft: chrome consistency, permissions.query, iframe chrome,
    iframe languages
  - FpJS: canvas 2D, audio offline, color-gamut, color-depth
  - PIN-locked: screen.width/height, hw.concurrency, audio.sampleRate,
    audio.maxChannelCount
  - fpscanner: UA<->platform, no userAgentData on Firefox

Consistency (68 tests):
  - Math determinism: 17 transcendentals + Math.pow
  - Worker scope vs main: 5 navigator props via Blob worker
  - Iframe scope vs window: 4 props + screen
  - UA self-consistency: UA<->platform, UA<->oscpu, UA<->appVersion
  - Native function self-toString: 8 native APIs
  - AudioContext / WebGL determinism
  - Locale<->Intl: DateTimeFormat / NumberFormat / Collator
  - Descriptor shape lies: 16 navigator props (each must be a getter,
    not Object.defineProperty(value=...))
  - performance.timeOrigin + .now() monotonic
  - Window dimension invariants
  - Firefox UA invariants (vendor='', appName='Netscape',
    appVersion short form)

All marked @pytest.mark.e2e so they're excluded from the default
suite that the pre-push hook runs. Invoke explicitly:

    pytest -m e2e -v

Or against a local build:

    INVPW_BINARY_PATH=/path/to/firefox.exe pytest -m e2e -v

Sources: github.com/fingerprintjs/BotD, abrahamjuliot/creepjs,
fingerprintjs/fingerprintjs, antoinevastel/fpscanner,
niespodd/browser-fingerprinting, bot.sannysoft.com.

Verified: 95 passed, 1 skipped (Chromium-only invariant), 0 failed
against firefox-5 local build.
2026-05-21 13:47:14 -07:00
feder-cr
22b1171518 feat: persistent profile dir + C7 closure (firefox-5 / 0.1.6)
- 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.
2026-05-21 12:19:38 -07:00
feder-cr
acd568f5d3 ci: add pre-push hook to block red pushes
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.
2026-05-20 13:25:08 -07:00
feder-cr
5f0ba5d659 test: fortress coverage for download + constants + e2e
#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.
2026-05-20 12:20:11 -07:00
feder-cr
a0b61d1abf chore: bump to 0.1.5
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.
2026-05-20 12:10:45 -07:00
Dennis B.
1eb3d5f55a
fix(download): strip *-prefix from sha256sum filenames (#15)
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.
2026-05-20 12:09:26 -07:00
feder-cr
567717dfd7 release: 0.1.4 - firefox-4 binary with Page.uncaughtError fix (#13)
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).
2026-05-20 07:05:23 -07:00
feder-cr
f1f3148d8f release: 0.1.3 - firefox-3 binary with all C5-C7 fixes (Win + Linux from clean fork)
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)
2026-05-19 21:08:55 -07:00
feder-cr
7f7a791564 docs: rename feder-cr/firefox-stealth refs to feder-cr/invisible-firefox
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)
2026-05-19 15:41:44 -07:00
feder-cr
f7b5e86793 release: 0.1.2 — point BINARY_VERSION at firefox-2 (mouse fix shipped)
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).
2026-05-18 15:31:28 -07:00
feder-cr
589c848e07 fix: every mouse action failed on FF150 — jugglerSendMouseEvent was never landed (#9)
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.
2026-05-18 14:45:01 -07:00
feder-cr
0ac0581747 chore: add community standards (CoC, contributing, security, templates)
Adds the seven files needed to pass the GitHub Community Standards checklist:

- CODE_OF_CONDUCT.md (Contributor Covenant 2.1)
- CONTRIBUTING.md (scope, dev setup, tests, PR rules; points users at
  feder-cr/firefox-stealth for C++/spoofing issues)
- SECURITY.md (private vuln reporting via GH advisories or email,
  out-of-scope items redirected to firefox-stealth / Mozilla)
- .github/ISSUE_TEMPLATE/bug_report.yml
- .github/ISSUE_TEMPLATE/feature_request.yml
- .github/ISSUE_TEMPLATE/config.yml (disables blank issues, links to
  security advisories, firefox-stealth, and discussions)
- .github/PULL_REQUEST_TEMPLATE.md
2026-05-18 11:46:16 -07:00
feder-cr
e37a4bc102 docs: add LinkedIn badge 2026-05-16 17:16:14 -07:00
feder-cr
07701a901d Merge pull request #2 from christianbaumann/main
Add comprehensive test suite (270+ tests across 14 files)

- Bayesian network primitives (25 tests)
- fingerprint sampler (55 tests)
- Profile dataclass + pin system (43 tests)
- configure_proxy decision table (24 tests)
- Windows + platform-agnostic prefs (27 tests)
- CLI / download gap coverage (17 tests)
- Multi-module integration (12 tests)
- E2e launcher lifecycle (9 tests)
- launcher / headless / async_api (32 tests)
- Linux-specific prefs / headless (16 tests)
- Linux tar.gz download (4 tests)
- Linux integration pipeline (3 tests)
- Linux launcher e2e (4 tests)
- pytest markers + shared conftest

Resolves conflicts in pyproject.toml, test_prefs.py, test_proxy.py by
keeping the upstream additions and merging with the previous wheel-build
fix. 323 tests pass on Windows; 14 e2e tests deselected by default
(require patched Firefox binary).
2026-05-16 17:14:08 -07:00
feder-cr
1d77f99770 docs: add Related projects section (arkenfox, LibreWolf, Camoufox) 2026-05-16 17:09:52 -07:00
feder-cr
9872b1995a docs: collapse layer table into a link to firefox-stealth 2026-05-16 10:49:32 -07:00
feder-cr
3a96103ab6 ci: remove PyPI publish workflow (not publishing) 2026-05-16 10:43:05 -07:00
feder-cr
dd140d04ae docs: install via git+https (package not on PyPI) 2026-05-16 10:43:04 -07:00
feder-cr
abaf798896 ci: add PyPI publish workflow on tag v* (uses trusted publishing, no token) 2026-05-16 10:39:41 -07:00
feder-cr
3d303a4915 tests: add wheel regression — fail if wheel has duplicate zip entries 2026-05-16 10:39:40 -07:00
feder-cr
c60a1891b8 fix(build): remove redundant force-include that caused duplicate wheel entries (PyPI reject), add pytest slow marker 2026-05-16 10:39:39 -07:00
feder-cr
b42dc31456 tests: add/update README.md 2026-05-15 20:02:02 -07:00
feder-cr
9de222aa15 tests: add/update tests/test_prefs.py 2026-05-15 20:02:01 -07:00
feder-cr
93da245b8a tests: add/update tests/test_cli.py 2026-05-15 20:02:00 -07:00
feder-cr
280d6838e1 tests: add/update .github/workflows/tests.yml 2026-05-15 20:01:59 -07:00
feder-cr
fbfb4fc8c4 tests: add/update tests/test_launcher_config.py 2026-05-15 20:01:58 -07:00
feder-cr
03f8453398 tests: add/update tests/test_pin.py 2026-05-15 20:01:56 -07:00
feder-cr
4f38b2ec29 tests: add/update tests/test_fpforge.py 2026-05-15 20:01:55 -07:00
feder-cr
67e2824876 tests: add/update tests/test_proxy.py 2026-05-15 20:01:54 -07:00
feder-cr
508cb0f59d tests: add/update tests/test_imports.py 2026-05-15 20:01:53 -07:00
feder-cr
6185dbc078 docs: tighten technical claims (mDNS, Chromium open-source layers, competitor list, font metrics) 2026-05-15 19:52:25 -07:00
chrissbaumann
70c1ca464f test(e2e): add 4 Linux launcher-routing tests for Phase 9
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>
2026-05-14 14:07:03 +02:00
chrissbaumann
b8139c2873 test(integration): add 3 Linux pipeline tests for Phase 8
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>
2026-05-14 13:53:24 +02:00
chrissbaumann
a3353fc861 test(download): add 4 Linux tar.gz download tests for Phase 7
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:40:42 +02:00
chrissbaumann
d392ca2971 test(prefs, headless): add 16 Linux-specific tests for Phase 6 + 10
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>
2026-05-14 13:21:17 +02:00
chrissbaumann
4f6254469e test(launcher, headless, async_api): add 32 Phase 10 gap-coverage tests
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>
2026-05-14 12:59:47 +02:00
chrissbaumann
234fe7e406 test(e2e): add 9 launcher lifecycle tests for Phase 9
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>
2026-05-14 12:52:46 +02:00
chrissbaumann
9c8d24408b test(integration): add 12 multi-module pipeline tests for Phase 8
Covers profile->prefs end-to-end, SOCKS/HTTP proxy + prefs composition,
pin propagation, seed determinism/variation, font whitelist passthrough,
dark/light theme palette overlay, and a Windows-specific virtual_display
+ SOCKS combo. Linux-specific branches stay covered by their unit tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:44:46 +02:00
chrissbaumann
ca8d815216 test(cli, download): add 17 gap-coverage tests for Phase 7
CLI: clear-cache (existing + missing), path (ok + error), fetch happy
path, no/unknown subcommand error paths. Calls cli.main() directly
instead of subprocess to keep tests fast and capture stderr cleanly.

Download: cache hit skips HTTP, tar.gz extraction, comment/blank
checksum lines, unknown archive format, missing entry after extract,
unsupported platform. Also covers pure helpers _parse_owner_repo,
_sha256_file, and _github_token env precedence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:35:57 +02:00
chrissbaumann
54ae310bf2 test(prefs): add 27 Windows + platform-agnostic gap tests
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>
2026-05-14 12:30:46 +02:00
chrissbaumann
074b4b3274 test(proxy): add 24 unit tests for configure_proxy decision table
Covers every input partition: None/empty/direct, SOCKS4/5/default,
HTTP/HTTPS passthrough, case-insensitive scheme detection, malformed
inputs, mutation contract, and edge cases (IPv6 brackets, whitespace,
non-numeric ports).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:24:57 +02:00
chrissbaumann
38ae41289d test(profile): add 43 unit tests for Profile dataclass and pin system
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>
2026-05-14 11:46:09 +02:00
chrissbaumann
3286123b11 test(sampler): add 55 unit tests for fingerprint Bayesian sampler
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>
2026-05-14 11:35:24 +02:00
chrissbaumann
8709ef77d2 test(network): add 25 unit tests for Bayesian network primitives
Covers _weighted_pick, _parent_key, _topsort, Node.sample, Network.sample
following ECP, BVA, and error-guessing techniques from the plan.
2026-05-14 11:24:57 +02:00