The largest feature release since v1. Firefox joins Chrome as a first-class engine, sessions can now be recorded to video over BiDi screencast, the Java client reaches parity with JavaScript and Python, and nightly builds publish automatically from the newest green commit on main. Under all of it, several long-standing wedges — stalled pipes, lost events, unrecoverable daemons — are fixed.
🦊 Firefox support
--engine firefoxdrives Firefox end to end — auto-installed on macOS and Linux, cached alongside Chrome for Testing, and verified against Mozilla'sSHA256SUMSbefore it unpacks. (#308, #313)- Network events and request interception work on Firefox. (#324)
- Dialogs, console, downloads, popups, and navigation capture are delivered on Firefox. (#323, #348)
- Batched
session.subscribeno longer fails whole, which had silenced every Firefox event when one name was unknown. (#365) - Channel and version pinning —
--channel betaselects a pre-release build,VIBIUM_ENGINE_VERSIONpins an exact one so CI and fleets don't move under you. (#326, #322)
🎥 Video recording
recording.startgains a video track on every surface — CLI, MCP, and all three clients — captured natively over BiDi screencast rather than stitched from screenshots. Requires Firefox 154+. (#309, #310, #311)- Video is a track of a recording, not a separate mode: one
record.zipcarries screenshots, snapshots, and the WebM together. (#332) - Recording defaults are timestamped and name-seeded, and
stop()returns a result object describing what was written. (#317, #319) - Video dimensions are read from the encoded WebM instead of reported as
0x0. (#358)
☕ Java client parity
- One-shot capture APIs —
capture().request(),.response(),.navigation(),.download(),.dialog(). (#367 by @adrianrobinsondev) - WebSocket monitoring (#368), request post data (#339 by @adrianrobinsondev), and screencast APIs (#342).
- Exceptions carry caller frames, so a failing call points at the code that made it. (#337 by @adrianrobinsondev)
- The packaged binary extracts to a per-version directory, so a version bump can no longer keep running the old one. (#330 by @adrianrobinsondev)
- Java is now covered in the API reference. (#369)
🔀 Concurrency and isolation
- Named CLI sessions —
--session <name>(orVIBIUM_SESSION) gives a script its own daemon, browser, and socket, so two scripts on one host stop colliding. (#207) - The pipe transport no longer wedges when a client stalls reading — the root cause behind intermittent, permanent session hangs. (#231, #232)
- The CLI recovers when the browser is closed or crashes externally instead of failing forever. (#219 by @Jhoan0714)
- Event setup is awaited before later commands dispatch, so a fast trigger no longer loses the event. (#351)
📦 Install and packaging
pipeensures the browser is installed itself, so client libraries never shell out to a second vibium subcommand. (#312)- Downloaded browsers are checksum-verified. (#313)
- Nightly prereleases publish from the newest CI-green commit on
main—npm install vibium@nightly,pip install --pre vibium==<version>, and aYYYY.M-SNAPSHOTMaven snapshot, each with an audit manifest. (#388)
🐛 Fixes
vibium evalsurfaces the real script exception instead of an empty message. (#221 by @Jhoan0714)vibium storage restoreactually restoreslocalStorage/sessionStorage. (#217 by @Jhoan0714)element.text()returnstextContent, notinnerText. (#215 by @Jhoan0714)- Python
element.highlight()implemented. (#208 by @Jhoan0714) countno longer crashes on unmarshal;isexits non-zero on false;findtimeout is adjustable. (#206 by @paulsmith)- Java
waitForFunctionno longer double-wraps expressions. (#174 by @lana-20) - Python
screenshot(full_page=True)no longer overflows the asyncio readline buffer on large PNGs. (#168 by @lana-20) vibium window <w> <h>survives the original page being closed. (#346)--headless startno longer silently reuses a headed daemon. (#194)- Remote connect attaches to an existing BiDi session instead of creating a second one. (#240)
- Custom Chrome flags via
VIBIUM_CHROME_ARGS, and a real explanation when Chrome won't start as root. (#141)
⚠️ Notable behavior changes
- CLI commands send explicitly requested launch options ahead of the command. An engine, headless, or channel mismatch with a running daemon now errors instead of silently using the existing browser. (#307, #321)
waitUntilis nowwaitForin the JS and Python clients, matching every other surface. The old name still works. (#304)capture().navigation()waits for a move away from the current URL. A navigation to the URL the page is already on is no longer captured — it previously completed on a stale event and returned the wrong URL.- Engine-specific differences — native video recording currently requires Firefox, and PDF output differs between engines. See Using Firefox.
🙏 Thanks
To @adrianrobinsondev for a thorough pass over the Java client's parity gaps, @Jhoan0714 for finding the CLI's recovery, storage, and error-reporting bugs, and @lana-20 and @paulsmith for continued cross-client testing.
What's Changed
- fix: eval returns proper JSON for objects and arrays by @vincebln2 in #180
- fix: negative values parsed as flags in sleep, fill, type, geolocation by @vincebln2 in #179
- Reap orphaned sync-test-server.js processes in cleanup targets by @hugs in #183
- Unify --timeout flag across CLI: accept both 5s and 5000 (ms) by @hugs in #182
- test(cli): make input-tools negative-value tests hermetic by @hugs in #185
- test(cli): pipe stderr in actionability's expected-failure tests by @hugs in #186
- fix: vibium fill accepts empty string to clear a field by @hugs in #189
- fix: vibium fill supports range and other value-bearing input types by @hugs in #190
- test: cap concurrent browser launches in 'make test' to fix startup-contention flakiness by @hugs in #191
- fix(clients): retry + bound browser startup so a slow launch isn't fatal by @hugs in #192
- test(py): use ThreadingHTTPServer to fix navigate-timeout flake under load by @hugs in #193
- Ci/add test workflow by @vincebln2 in #223
- Fix orphaned Chrome cleanup on Linux by @vincebln2 in #224
- Harden CI Workflow by @vincebln2 in #226
- BiDi client: single reader goroutine with pending-command registry by @vincebln2 in #227
- Fix MCP shutdown races on the browser session fields by @vincebln2 in #228
- Fix make test overload and the intermittent pipe-transport session wedge by @hugs in #237
- Document source build prerequisites by @paulsmith in #175
- tests: never hit live external sites by @hugs in #239
- fix: attach to remote BiDi sessions instead of creating a second one by @hugs in #242
- Harden browser launch: sweep orphaned temp dirs, bound two unbounded waits by @hugs in #243
- Add VM_FAST_LAUNCH to skip the dead-GPU Metal stall in macOS VMs by @hugs in #245
- Share one daemon per file in the daemon tests by @hugs in #246
- Derive test parallelism from core count by @hugs in #247
- Close the extra tab in the sync-api Multi-page test by @hugs in #249
- Foreground the target tab before pointer input (#248) by @hugs in #250
- Drop the stale #248 comment from the sync-api Multi-page test by @hugs in #251
- Replace the npm-package binary atomically in build-go by @hugs in #252
- Route every BiDi script result through one decoder (#221, #124) by @hugs in #253
- Fix Java client params the engine never reads (#174) by @hugs in #254
- Send the request body under the key the engine reads by @hugs in #256
- Add urlmatch: one URL glob implementation for the engine by @hugs in #257
- Use urlmatch for waitForURL and delete the fifth glob dialect by @hugs in #258
- Queue client writes so a stalled reader can't freeze the session (#232) by @hugs in #259
- Serialize dispatched commands only while recording by @hugs in #260
- Fail fast when a context is blocked by an open dialog (#151) by @hugs in #261
- Track prompts on the agent path too (#151) by @hugs in #262
- Detect a browser that went away instead of failing forever (#219) by @hugs in #263
- Ask for issues rather than unprompted code PRs by @hugs in #266
- Refuse operations the element cannot support, and fix three CLI exit paths by @hugs in #267
- Match a submit input by its accessible name (#204) by @hugs in #268
- Target the active context from every script-backed command (#205) by @hugs in #269
- Detect the dead-GPU stall instead of requiring a flag by @hugs in #271
- Tell an absent attribute apart from a present, valueless one (#198) by @hugs in #270
- Refuse to attach a browser running in the other mode (#194) by @hugs in #272
- Five small CLI and client fixes by @hugs in #273
- Resolve elements semantically in the interaction builders (#106, #108) by @hugs in #274
- Make the zsh completion script safe to source (#201) by @hugs in #275
- Share the editable predicate, and resolve chrome/driver as a pair (#264, #265) by @hugs in #276
- Listen for the download events the engine actually emits (#255) by @hugs in #277
- Read daemon messages with a reader that grows (#209) by @hugs in #279
- Parse trailing flags without breaking negative positionals (#241) by @hugs in #278
- Make
isexit-honest andfind --timeoutreal (#206) by @hugs in #280 - Track SPA navigation via history.pushState (#126) by @hugs in #281
- Say why session creation failed (#107) by @hugs in #282
- Add >> and >>> pierce combinators for shadow DOM (#118, #203) by @hugs in #283
- Document the pierce combinators by @hugs in #284
- Write screenshots where the CLI was told to (#119) by @hugs in #286
- Bump the pinned GitHub Actions off the Node 20 runtime by @hugs in #287
- Harden daemon control-file permissions by @paulsmith in #176
- feat(cli): named daemon sessions for concurrent use (--session / VIBIUM_SESSION) by @paulsmith in #207
- Bind serve to loopback and reject non-local WebSocket origins by @paulsmith in #177
- Refresh the vendored WebDriver BiDi spec by @hugs in #290
- Add a CLI round-trip test for storage export/restore by @hugs in #285
- Stop filmstrip captures from stalling on an in-flight navigation (#289) by @hugs in #291
- Honor -o paths for pdf and record, like screenshot (#119) by @hugs in #292
- Fix the pipe example and document driving the protocol by hand by @hugs in #293
- Stop swallowing client responses with high ids (#158) by @hugs in #294
- Guard the #128 route-before-navigate deadlock by @hugs in #295
- Document vibium serve as a BiDi endpoint, and which transport to use by @hugs in #296
- Keep page.expose() functions across navigations (#135) by @hugs in #297
- Type newlines as Enter (#61) by @hugs in #299
- Custom Chrome flags, and say why Chrome will not start as root (#141) by @hugs in #300
- Move CI to Node 24 and drop the per-file timeout workaround by @hugs in #301
- Skip CI for documentation-only changes by @hugs in #303
- Remove the storage test's temp dir, not just the files in it by @hugs in #305
- Add Firefox support and native BiDi screen recording by @vincebln2 in #307
- Video as a recording track: recording.start gains video on every surface by @hugs in #332
- Session close probes the browser instead of stalling behind it by @hugs in #333
- Always run Java tests instead of letting Gradle skip them as up-to-date by @hugs in #334
- Resize the window over BiDi so it survives page closes (#346) by @vincebln2 in #347
- Add capability-based cross-engine test selection by @vincebln2 in #344
- Await event setup before dispatching later commands (#351) by @vincebln2 in #355
- Bump the actions group across 1 directory with 2 updates by @dependabot[bot] in #288
- Fix stale CLI command names in the API reference by @hugs in #357
- Test the Firefox release channel now that 154 shipped by @vincebln2 in #360
- Report video dimensions from the encoded WebM instead of 0x0 (#358) by @vincebln2 in #359
- Write user.js prefs in sorted order by @vincebln2 in #361
- Validate --firefox-channel at the CLI by @vincebln2 in #362
- Test firefoxDownloadURL shapes and the videoSupportError passthrough by @vincebln2 in #363
- Pin the Firefox version with VIBIUM_FIREFOX_VERSION by @vincebln2 in #364
- Fall back to per-event subscription when a batched subscribe is rejected - #19 by @vincebln2 in #366
- Enable console, download, popup, and navigation capture on Firefox by @vincebln2 in #370
- Enable dialogs and prompt blocking on Firefox by @vincebln2 in #371
- Enable network events and request interception on Firefox by @vincebln2 in #372
- Extract the Java binary to a per-version directory by @vincebln2 in #378
- Preserve Java command caller frames by @vincebln2 in #377
- Add one-shot capture APIs to Java by @vincebln2 in #374
- Add Java WebSocket monitoring by @vincebln2 in #375
- Sort docs by reader intent by @vincebln2 in #373
- Add Java request post data by @vincebln2 in #376
- docs: add Java to API reference by @vincebln2 in #379
- Add Java recording engine tests by @vincebln2 in #381
- Suppress Node test hooks when capability requirements are missing by @vincebln2 in #384
- Capability tooling cleanups by @vincebln2 in #385
- Add flat waitFor names to the JS and Python page wait methods by @vincebln2 in #386
- Verify Firefox downloads against Mozilla's SHA256SUMS by @vincebln2 in #387
- Move browser ensure-install into the binary by @vincebln2 in #390
- Publish automated nightly releases by @vincebln2 in #389
- Install npm dependencies before stamping nightly versions by @vincebln2 in #395
- Fix the nightly build path on fresh checkouts and Linux runners by @vincebln2 in #399
- Accept --version and -V in the Python CLI by @vincebln2 in #398
- Name the repo for gh release commands in checkout-less jobs by @vincebln2 in #400
- Ship the GitHub prerelease nightly without registry publishing by @vincebln2 in #401
- Bound the stdin drain in the Python client's command path by @vincebln2 in #402
- Give the engine options engine-neutral names before they ship by @hugs in #405
- Fix the docs the 26.8.21 release would have shipped by @hugs in #406
New Contributors
- @vincebln2 made their first contribution in #180
- @paulsmith made their first contribution in #175
- @dependabot[bot] made their first contribution in #288
Full Changelog: v26.5.31...v26.8.21