* fix(juggler): pace a humanized move on its own schedule sendTrajectoryAcked waited each point's full pause after the previous point's ack, so every ack's round trip was added to the move: it took its plan plus one round trip per point. On a page whose main thread is busy 19ms in every 20, moves capped at 0.5s took 0.54-0.71s. Each point is now due at its planned offset from the start of the move, so a late ack delays only its own point and the move ends on its planned time. tests/patches/humanize-pacing.py times eleven capped moves on such a page and checks their median against the cap. It failed 3/3 before (medians 587-607ms) and passes after (484-487ms). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(launch): write toggle prefs on every launch, on or off block_webrtc, block_images and disable_coop wrote their pref only when switched on. A persistent profile keeps a user.js pref in prefs.js, so removing the flag later left the setting in force. A real profile launched once with block_webrtc still had media.peerconnection.enabled false long after, and BrowserScan reported WebRTC disabled. Each pref is now written every launch, with the stock value when its flag is off. A caller's own firefox_user_prefs entry still wins. The same change is made in the TypeScript launcher, with its tests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tests
Two things live here, and neither is a copy of anyone else's suite.
patches/ — patch guards
One standalone script per shipped behaviour. Each exits 0 or 1 and drives the browser through the Python package. Whether the patches apply is the build's check; these are the most direct evidence that a Firefox bump did not quietly neuter a patch that still applies cleanly — the failure a compile check cannot catch.
Each guard belongs to one of three groups (GROUPS in
ci/run_patch_guards.py), and CI runs each group as its own job:
- spoofing: a spoofed value still reaches the page and holds together (media devices, voices, fonts, the touchscreen digitizer, the sealed setters, …);
- automation: Playwright stays invisible to the page and never deadlocks it (isolated evaluate, trusted events, humanized and edge-case mouse input, …);
- parity: what a page or the OS can observe matches stock Firefox (content-accessible files, GPU probes, the popup blocker, the Windows manifest, …).
A new guard must be added to a group; ci/tests fails until it is.
python3 -m ci.run_patch_guards --binary /path/to/camoufox-bin
python3 -m ci.run_patch_guards --binary /path/to/camoufox-bin --group automation
python3 -m ci.run_patch_guards --binary /path/to/camoufox-bin --only isolated-evaluate
camoufox/ — Camoufox's own Playwright tests
Tests for behaviour upstream Playwright has no equivalent for, or asserts the
opposite of on purpose. ci/suite.py overlays them onto the upstream checkout
so they run against its harness. See camoufox/README.md for when to add one.
Where the Playwright suite went
This directory used to hold a fork of a ~v1.55-era playwright-python suite. It was deleted, because measuring it showed it was strictly weaker than running upstream's own suite:
- 73 of the 74 tests it skipped as "Not supported by Camoufox" pass when the same binary runs upstream's copy. The skips predated main-world execution and were never revisited; the suite was asserting that Camoufox was worse than it is.
- Of its passing tests, eight had no upstream counterpart. Six of those were
Camoufox-specific and are now the three modules in
camoufox/; the other two were tests upstream had since renamed. - Everything else was upstream code, one generation stale.
ci/run_playwright.py fetches playwright-python at the tag ci/versions.py
resolves for the browser under test, applies ci/skiplist.yml, and overlays
camoufox/. A suite that is re-fetched every run cannot go stale, and a
deliberate difference from upstream now has to be written down in the skiplist
with a reason instead of being encoded as a silent fork.
make tests # the whole suite
make tests headful=true # ... headed
python3 -m ci.run_playwright --binary /path/to/camoufox-bin --shard 3/6