obscura-benchmark

September 12, 2026 ยท View on GitHub

Conformance, capability, and performance benchmarks for Obscura, a headless browser engine written in Rust for web scraping and AI agent automation. WPT coverage here is intentionally limited to the classic DOM/JavaScript standards path. Rendering regressions belong to Obscura's deterministic render-repros/ suite in the main repository, where changes can be reviewed against engine-owned expectations.

Benchmark tracks

trackquestion it answerswhere
WPT conformancehow much of the web platform does Obscura implement correctlycrates/wpt-runner, crates/triage
Obstacle coursedoes it handle the modern web (React/SPA/async/web APIs), and how fastobstacle-course/
vs headless Chromehow does its speed and memory compare to the standard headless enginecompare/
Real-world corpusdoes it render real public pages, including SPAsrealworld/
Stealth benchdoes it present a consistent, undetectable Chrome fingerprintstealth-bench/
Perf benchper-page fetch / scrape latency on a small URL setcrates/perf-bench
Reliabilitydoes it crash, panic, or hang on a large corpus of real pagesreliability/

Results

The latest full pass ran on 2026-09-11 against Obscura commit 01e1caa33360f6c02643457307894ec885e82eef and pinned WPT commit 03f14d4780c4d981bc84c65679b18e9327a1affe. The complete compressed JSON and triage report are under results/published/2026-09-11-wpt-no-render/.

Web Platform Tests (conformance)

Subtest pass rate by tier (a tier is a capability scope, defined in crates/triage/src/tiers.list, not a cherry-picked subset). A subtest is one assertion; a single test file holds many, so subtest pass rate is the standard conformance measure:

tiersubtests passingrole
Core327,094 / 392,547 (83.3%)the DOM/HTML/URL/fetch scraping contract
Relevant521,393 / 600,709 (86.8%)Core plus broader JS-observable correctness
Full603,997 / 893,328 (67.6%)the whole testharness suite, for transparency

Core subtest pass rate over time:

dateobscura stateCore subtests
2026-06-03baseline8.0%
2026-06-04round 215.4%
2026-06-04+ charset/URL encoding72.7%
2026-06-04+ IDL reflection, attr folding, storage81.6%
2026-07-03historical main83.3%
2026-09-1101e1caa383.3%

The "Full" tier includes large subtrees outside Obscura's classic profile scope (layout, rendering, media, and hardware), so it is reported only for transparency. Core and Relevant remain the headline because they exclude those areas by capability, not by outcome. See crates/triage/src/tiers.list for the exact rules. For cross-engine context, the same WPT areas for Chrome/Firefox/Safari are published on wpt.fyi.

Obstacle course (capability + speed)

33 / 33 stages pass, median ~44 ms per stage (cold obscura fetch, including process startup). Covers client-side React/Preact/Vue, SSR hydration, ES modules and dynamic import, IntersectionObserver/MutationObserver, fetch + pushState SPAs, the URL/TextDecoder/FileAPI/Range/Selection/custom-element/dialog web-API surface, the --dump extraction modes, charset decoding, cookies, and stealth fingerprint consistency.

vs headless Chrome (speed + memory)

Cold process per page, both fully rendering the same client-side app (verified: obscura serializes the post-JavaScript DOM, e.g. 100 React <li> elements that are not in the shipped HTML):

pageobscuraheadless Chromeobscura advantage
react81 ms, 30 MB815 ms, 190 MB10x faster, 6x less memory
preact45 ms, 30 MB745 ms, 191 MB16x faster, 6x less memory
vue86 ms, 31 MB796 ms, 190 MB9x faster, 6x less memory

Across all 33 obstacle-course fixtures the median is ~21x faster and ~7x less memory (obscura ~27 MB vs Chrome ~190 MB per process). The framework rows above are the heaviest-render, most conservative cases; lighter pages widen the gap because Chrome pays the same fixed startup regardless of the page.

Throughput and memory as concurrency rises (24 React-app loads, idle host):

engine1 worker4 workers8 workers
obscura12 pg/s, 30 MB40 pg/s, 112 MB18 pg/s, 159 MB
headless Chrome1.2 pg/s, 1.1 GB3.0 pg/s, 4.2 GB3.0 pg/s, 8.1 GB

Obscura sustains far higher throughput at a fraction of the memory. Chrome pays a large fixed startup (process + browser stack) on every page; under concurrency its RAM climbs into the gigabytes while Obscura stays in the low hundreds of MB. This is the no-render scraping profile; production Chrome would reuse one browser across tabs (the cold-process numbers are Chrome's worst case).

Real-world corpus (vs headless Chrome)

98 live public pages, fetched with obscura and headless Chrome side by side:

enginerenderedmedian latencymedian peak RSS
obscura94 / 98 (95.9%)5.2 s64.2 MB
headless Chrome85 / 98 (86.7%)2.1 s201.2 MB

Obscura renders more of the corpus than headless Chrome (94 vs 85), and at ~3x less memory. Chrome's misses are mostly sites that serve a datacenter IP an anti-bot or CAPTCHA wall, which is not an engine failure on either side. Latency is mixed: obscura is much faster on static content pages and slower on heavy client-rendered SPAs, where its post-load settle wait (--wait) dominates the median. The live web drifts, so these are a snapshot; see realworld/sites.txt.

Reliability (crash / hang sweep)

Conformance and speed do not matter if the engine crashes or hangs on a live page. A 1500-URL corpus (a one-level crawl from the real-world seed list) is rendered through obscura, classifying each outcome from the exit code and stderr:

outcomecount
rendered1432 / 1500 (95.5%)
thin / blocked67
bounded hang (deadline)1
crash (signal)0
panic0

Zero crashes and zero panics across 1500 diverse pages. Any page that does not finish in its budget is terminated deterministically (a V8 termination watchdog plus a process-level hard deadline), so no page can wedge a worker. The single bounded hang (kayak.com) is a heavy page that exceeds the tight per-page budget under concurrency; it renders normally when run on its own. Run it with OBSCURA_BIN=<bin> python3 reliability/sweep.py.

1. WPT conformance

crates/wpt-runner runs every supported WPT testharness file through a separate obscura fetch process and reads the result left by wpt-overlay/resources/testharnessreport.js. WPT reftests are not run here: rendering regression ownership stays with the deterministic fixtures in the main Obscura repository.

Tests requiring unimplemented runner automation (testdriver, PAC, or HTTP/3 serving) are reported as unsupported, not as engine failures. crates/triage reports that count separately and groups real failures into deduplicated root causes.

# one-time setup: check out the revision in wpt-config/WPT_COMMIT,
# install the report overlay, and build the manifest
scripts/setup-wpt.sh
# then add the WPT hostnames once (needs sudo), as printed by setup-wpt.sh:
#   ( cd wpt && ./wpt make-hosts-file ) | sudo tee -a /etc/hosts

# full classic WPT pass
OBSCURA_BIN=/path/to/obscura-no-render \
  scripts/run-wpt.sh --concurrency 32 --wait-secs 15

# a subset, by one path substring
OBSCURA_BIN=/path/to/obscura-no-render \
  scripts/run-wpt.sh FileAPI/url/

The suite is roughly 38k test variants at the current pin. Use a concurrency that does not oversubscribe the host: oversubscription makes per-test timeouts fire and depresses the pass rate. Outputs are written under results/, including the JSON report and a triage Markdown report.

Build the classic Obscura binary without optional features:

BENCH_BIN_DIR=/path/to/obscura-benchmark/.bench-bin
mkdir -p "$BENCH_BIN_DIR"
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release \
  -p obscura-cli --bins --no-default-features
cp target/release/obscura "$BENCH_BIN_DIR/obscura-no-render"

Run those build commands in the Obscura repository. Record its exact commit with the result commit; the WPT side is reproducibly pinned by this repository.

2. Obstacle course

A curated set of small self-contained modern-web pages that a no-DOM-engine browser cannot handle. Each fixture runs its JavaScript and asserts a deterministic result; the runner also times each page. See obstacle-course/README.md for the full stage list.

OBSCURA_BIN=/path/to/obscura scripts/run-obstacle-course.sh
OBSCURA_BIN=/path/to/obscura scripts/run-obstacle-course.sh --filter react --runs 10

It exits non-zero if any stage's result does not match, so it doubles as a correctness regression check.

3. Head-to-head vs Chrome

compare/ runs Obscura and headless Chrome as cold processes over the same pages and reports latency, peak memory, and throughput under concurrency. See compare/README.md.

OBSCURA_BIN=/path/to/obscura CHROME_BIN=google-chrome scripts/run-compare.sh

Run it on an idle host; CPU contention skews throughput (memory is unaffected).

4. Real-world corpus

realworld/ fetches a list of live public pages and reports render-success rate, latency, and memory. See realworld/README.md.

OBSCURA_BIN=/path/to/obscura scripts/run-realworld.sh

5. Stealth bench

stealth-bench/ evaluates Obscura's browser fingerprint, anti-detection measures, and cross-page consistency when running in --stealth mode. It runs locally and does not require external network access.

OBSCURA_BIN=/path/to/obscura-stealth python3 stealth-bench/run.py --stealth

6. Perf bench

crates/perf-bench times obscura fetch / obscura scrape on a small default URL set (override by passing URLs).

OBSCURA_BIN=/path/to/obscura scripts/run-bench.sh
OBSCURA_BIN=/path/to/obscura scripts/run-bench.sh https://example.com https://news.ycombinator.com

Repo layout

crates/
  wpt-runner/      classic WPT testharness runner
  triage/          groups WPT failures into root causes; tiers.list defines the tiers
  perf-bench/      times `obscura fetch` / `obscura scrape`
obstacle-course/   modern-web capability + speed fixtures (+ run.py, manifest.json)
compare/           obscura vs headless Chrome: head-to-head.py, scale.py
realworld/         live-page render-success corpus: sites.txt, run.py
stealth-bench/     stealth fingerprint, anti-detection, and consistency test suite
wpt-overlay/       the custom WPT report script installed into the WPT checkout
scripts/           setup and run wrappers
results/           generated run artifacts (gitignored)
wpt/               the WPT checkout (gitignored; created by setup-wpt.sh)

Setup and requirements

  • Rust toolchain (cargo build --release builds the runner, triage, and perf-bench).
  • An Obscura binary, passed via OBSCURA_BIN.
  • Python 3 for the obstacle-course, compare, and realworld runners. compare/scale.py uses psutil for memory sampling (pip install psutil), and the compare/realworld harnesses read peak RSS from GNU time -v.
  • For the head-to-head, a Chrome or Chromium build (CHROME_BIN).
  • For WPT, the one-time scripts/setup-wpt.sh (checks out the pinned WPT commit, builds the manifest, and installs the report overlay) plus the WPT hostnames in /etc/hosts.

results/ and the wpt/ checkout are gitignored; run artifacts are regenerated by the scripts.