Files
coffeegrind123andClaude Opus 5.5 0cb8c60bb9 Pace humanized moves on their own schedule, and write toggle prefs on every launch (#808)
* 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>
2026-09-29 21:20:58 +00:00
..

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