Browse docs
Native Chromium Fork
BetterWright can run the pinned BetterWright Chromium 150 fork while keeping
its public run(), human.*, captcha.*, snapshot, policy, proxy, and vault
APIs unchanged. On macOS arm64 and Linux x64, betterwright setup /
betterwright update download the fork into the zero-config discovery root;
CloakBrowser remains the fallback when no artifact is present (and the only
path on Windows today).
Install / update
betterwright update # download fork → ~/.betterwright/chromium/
betterwright update --force # re-fetch + re-verify even if already present
betterwright setup # fork (when shipped) + CloakBrowser fallback
betterwright setup --cloak-only
Artifacts come from the GitHub Release tag chromium-<version> (see
CHROMIUM_FORK_RELEASE_TAG / CHROMIUM_FORK_ASSETS in
src/chromium-fork.ts). Each zip is SHA-256 pinned in that manifest before
extract. Apple-licensed fonts are not in the public zip.
Runtime Selection
Use an exact executable path:
export BETTERWRIGHT_CHROMIUM_PATH=/absolute/path/to/chrome
betterwright run -c 'return await page.title()'
Or point at the packaged artifact root:
export BETTERWRIGHT_CHROMIUM_ROOT=/absolute/path/to/artifacts
betterwright run -c 'return await page.title()'
The artifact-root layout is fixed:
artifacts/
mac-arm64/Chromium.app/Contents/MacOS/Chromium
linux-x64/chrome
win-x64/chrome.exe
Only macOS arm64 and Linux x64 are built and runtime-tested. Windows is reserved for a future verified artifact.
BETTERWRIGHT_CHROMIUM_PATH takes precedence over
BETTERWRIGHT_CHROMIUM_ROOT. Configured paths must be absolute and must exist;
BetterWright fails closed instead of silently falling back to another browser.
Zero-Config Discovery and Platform Routing
With neither variable set, BetterWright checks the default root
~/.betterwright/chromium/ for the current platform's artifact. Found → the
fork runs with no configuration at all. Not found (or no artifact shipped for
the platform, e.g. Windows) → managed CloakBrowser, exactly as before.
~/.betterwright/chromium/
mac-arm64/Chromium.app/Contents/MacOS/Chromium
linux-x64/chrome (+ fonts/ttf/ for the macOS-metric font set)
This makes mixed fleets trivial: run betterwright update (or setup) on
Linux and macOS hosts, betterwright setup --cloak-only on Windows, and every
machine picks the right backend with the same configuration. Set
BETTERWRIGHT_CHROMIUM_ROOT=off (or BETTERWRIGHT_CHROMIUM_PATH=off) to force
the managed path on a host that has the artifact installed.
Profiles are not interchangeable. Fork and Cloak share
$BETTERWRIGHT_HOME/browser/profile by default. Prefer a dedicated home for
fork deployments (e.g. BETTERWRIGHT_HOME=~/.betterwright-fork). Switching
off after the fork has upgraded that profile requires deleting
browser/profile first.
Match timezone/locale to egress (or enable geoip with upstreamProxy).
Nothing in the fork hard-codes Singapore or any other region — pin whatever
geography the exit IP actually has.
Control Plane
The fork is launched through stock playwright-core over normal CDP. Custom
behavior lives in Chromium/V8 (deferred inspector console delivery). No
page-world stealth shim is installed. Patchright/stealthRuntimeFix is
rejected when the fork is configured.
Identity
| Option | Native behavior |
|---|---|
locale | --lang and --fingerprint-locale |
timezone | --bw-timezone (alias --fingerprint-timezone) |
platform | --fingerprint-platform; defaults to macos (see masking below) |
headless | Real native headless |
headedInvisible | Headed window parked off-screen |
upstreamProxy / geoip | Policy-checked egress; locale/tz from egress when enabled |
Platform Masking (Linux → macOS)
A headless-Linux browser identity is one of the strongest automation signals
risk engines score, so the fork masks the host platform as a realistic
consumer Mac by default (platform: "macos"; opt out with platform: "linux" or "windows" in the constructor / --platform= on the CLI).
The identity is not invented — every value was captured from genuine Google
Chrome 150.0.7871.129 (the fork's exact pinned version) on an Apple M4 Pro
MacBook Pro running macOS 26.6: UA and UA-CH (brands, macOS 26.6.0,
arm), navigator.platform = MacIntel, 1800×1169 @2x screen geometry with
real menu-bar/Dock availHeight, 12 cores, deviceMemory 16. See
src/fork-identity.ts.
Two layers apply it without any page-world JavaScript shims:
- Launch layer —
--fingerprint-platform, window-size/DPR flags, and a context-leveluserAgentbaseline (correct from the first navigation). - CDP emulation layer — per-page
Emulation.setUserAgentOverridewith fullUserAgentMetadata(the DevTools protocol path, invisible to getter/toString probes), plus hardware-concurrency override where the build supports it.
The binary patch set in
chromium-fork-patches.md is implemented and
verified: UA/UA-CH at the source, navigator.platform, WebGL
renderer/vendor, deterministic per-profile canvas/audio farbling, screen
geometry, and a bundled macOS-metric font set loaded through a launch-time
FONTCONFIG_FILE. With timezone/locale matched to egress geography the
headless Linux fork returns real Google SERPs instead of /sorry.
Cloaking V2 identity coherence still applies; see cloaking-v2.md.