felix-starman

felix-starman

Bibbidi - Low-level Elixir client for W3C WebDriver BiDi

Bibbidi — W3C WebDriver BiDi Protocol for Elixir

I’m excited to share Bibbidi, a low-level Elixir implementation of the W3C WebDriver BiDi Protocol.

BEAM Interface to Browsers with BiDi — the name is a nod to the Fairy Godmother’s spell in Disney’s Cinderella.

What is WebDriver BiDi?

WebDriver BiDi is a bidirectional protocol for browser automation. Unlike classic WebDriver (HTTP request/response), BiDi uses a persistent WebSocket connection that lets you send commands and receive real-time events from the browser — think console logs, network requests, navigation events, and more. It’s the successor to CDP (Chrome DevTools Protocol) but is a W3C standard with cross-browser support.

What is Bibbidi?

Bibbidi is a building-block library. It gives you WebSocket connectivity, command/response correlation, and event dispatch, but imposes no supervision tree. You supervise Bibbidi.Connection processes yourself, exactly how you want.

It’s designed as a foundation for RPA frameworks, browser testing libraries, and anything else that needs to talk BiDi to a browser.

Features

  • Full protocol coverage — command builder modules for all BiDi domains: BrowsingContext, Script, Session, Input, Network, Storage, Browser, Emulation, Log, and WebExtension (although I’m adding more Integration tests to validate functionality)

  • Real-time events — subscribe to browser events and receive them as regular Erlang messages ({:bibbidi_event, method, params})

  • Swappable transport — ships with a mint_web_socket transport; bring your own via the Bibbidi.Transport behaviour

  • Browser lifecycleBibbidi.Browser GenServer can launch and manage browser OS processes for you (OS process “culling” PRs or comments would be appreciated, this is an area for improvement)

  • Types & events generated from the W3C CDDL spec — attempts to stay as close to the protocol definition as possible

  • No opinionated supervision — fits into any OTP application architecture (I may add Igniter recipes in the future that include some useful supervision abstractions, but ideally this is not part of the library)

  • Interactive Livebook — an included example with a Kino UI for navigating, clicking, running JS, taking screenshots, and watching live events

Quick example


{:ok, conn} = Bibbidi.Connection.start_link(url: "ws://localhost:9222/session")

{:ok, _caps} = Bibbidi.Session.new(conn)

{:ok, tree} = Bibbidi.Commands.BrowsingContext.get_tree(conn)

context = hd(tree["contexts"])["context"]

{:ok, _} = Bibbidi.Commands.BrowsingContext.navigate(conn, context, "https://example.com", wait: "complete")

{:ok, %{"result" => %{"value" => title}}} =

Bibbidi.Commands.Script.evaluate(conn, "document.title", %{context: context})

IO.puts("Page title: #{title}")

Supervision

Bibbidi doesn’t impose a process tree — add connections to your own supervisor:


children = [

{Bibbidi.Connection, url: "ws://localhost:9222/session", name: MyApp.Browser}

]

Supervisor.start_link(children, strategy: :one_for_one)

Try it in Livebook

The repo includes an Interactive Browser Livebook that lets you point-and-click navigate, run JavaScript, take screenshots, and watch browser events in real time.

Links

Installation


def deps do

[
  {:bibbidi, "~> 0.1.0"}
]

end

This is v0.1.0 and still fairly early, so feedback, bug reports, and contributions are very welcome! I’d love to hear what you think and what you’d like to build with it.

https://github.com/petermueller/bibbidi

First 4 of 4 Posts! Switch mode

felix-starman

felix-starman

A note: This was created with heavy use of LLMs, although the CDDL parsing did most of the codegen, and the actual GenServer parts aren’t particularly complicated.

For anyone curious on next steps, I’m planning to take a crack at making a bibbidi_wallaby or phoenix_test_bibbidi mainly to stress the interface, and because it won’t cause as many issues if the API needed to change, since end users would be less likely to interact w/ Bibbidi directly.

I’d also be adding some useful igniter generators of different supervision patterns that someone could use.

Medium time horizon, I want to play around with some dependent plugins in the vein of puppeteer-extra and it’s sub-project playwright-extra, in particular the stealth plugins

Longer term, once the interface is more stable (I may go to structs/protocols, we’ll see, FEEDBACK WELCOME!) I am hoping to have some useful graph-walking approaches and Jido examples w/ igniter templates. The idea being it could walk a graph to control the browser and/or have a Jido Agent able to work w/ JidoBrowser (I need to look into this more anyways) and Bibbidi to provide a full set of functionality and controls.

felix-starman

felix-starman

I released a 0.2, and a quick 0.3

TL;DR because some of the changes are less important

  • Everything is a struct now, commands sent, events coming back
  • A Bibbidi.Encodable protocol for writing custom commands if you want
    • I started an Expandable protocol but that immediately became a workflow engine
    • see bibbidi_runic for a very-alpha/unreleased example of how to use bibbidi with a workflow engine
    • or, run mix bibbidi.gen.workflow or look at example OpWorkflow.Op for another self-maintained option
  • There’s :telemetry set up for your use, and a Bibbidi.Telemetry.Metadata to make it easier to emit attributes that help correlate commands/events, or custom workflow operations together
  • Types all over the place (thank you Zoi!)
    • It’s currently a lot, but I will do my best to improve and push new docs as I think of better ways to inject useful examples into the module/function docs without breaking the CDDL module generation (how most of the changes to commands/events/types are made)
  • Bibbidi.Keys for converting to BiDi compliant unicode characters (e.g. Super, Ctrl, Alt, Enter)

Feedback on documentation, or anything that pops into your mind would be appreciated.

The 0.2.0 and 0.3.0 changelog is here:

## v0.3.0

### Features

- **Type modules (`Bibbidi.Types.*`)** — all named BiDi protocol types now have generated modules under `Bibbidi.Types.*` with Zoi schemas (`schema/0`), `@type t`, and ExDoc documentation. Covers primitive aliases, string enums, struct-like maps, and choice/union types (~163 modules).
- **Improved command struct documentation** — moduledocs include spec links, field descriptions with ExDoc cross-references to type modules (e.g. `t:Bibbidi.Types.Script.Target.t/0`), and required/optional annotations.
- **Typed schemas on command structs** — `@schema` fields now reference type module schemas (e.g. `Bibbidi.Types.BrowsingContext.schema()`) instead of `Zoi.any()`, providing real validation and introspection for command fields.
- **Event structs** — BiDi events are parsed into typed structs (one per event method). Subscribers receive `{:bibbidi_event, method, %EventStruct{}}` instead of raw maps. Unknown events still arrive as raw maps.
- **`Bibbidi.Events.parse/2`** — top-level dispatcher that converts raw event params into typed structs, delegating to per-module parsers (`Events.BrowsingContext.parse/2`, etc.)
- **`Bibbidi.Telemetry.Metadata` protocol** — extracts correlation metadata from command and event structs for telemetry enrichment. Command structs derive `%{meta: value}`, event structs derive relevant correlation keys (`:context`, `:navigation`, `:request`).
- **`:meta` field on command structs** — user-supplied correlation data included in command telemetry metadata but excluded from wire params
- **Telemetry correlation** — command start/stop events include `:meta`, event received includes `:context`/`:navigation`/`:request` when present
- **`:connection_mod` option on facade functions** — all generated facade functions accept `connection_mod: MyMod` to override the module used for `execute/3`, enabling clean Mox-based testing without GenServer or transport mocks
- **`Bibbidi.Connection` behaviour** — defines `@callback execute/3` so facade modules can accept alternative implementations via `:connection_mod`
- **CDDL generator: embedded group resolution** — `resolve_command_fields` now handles `{:embed, ref}` entries, correctly resolving fields from embedded groups like `BaseNavigationInfo` and `network.BaseParameters`
- **`Bibbidi.Keys`** — maps human-friendly key names (`:enter`, `"ArrowUp"`) to BiDi Unicode codepoints for `input.performActions` keyboard actions
- **`mix test.all` alias** — runs unit + integration tests (`mix test --include integration`)

### Breaking

- **Event subscribers receive structs** — `{:bibbidi_event, method, params}` where `params` is now a struct (e.g., `%Bibbidi.Events.BrowsingContext.Load{}`) instead of a raw map. Use `event.context` instead of `event["context"]`. Unknown events still arrive as raw maps.
- **Event telemetry `:params` is a struct** — `[:bibbidi, :event, :received]` metadata `:params` is now a parsed event struct instead of a raw map
- **All facade functions accept `opts`** — functions that previously took no options (e.g., `Browser.close(conn)`) now accept `opts \\ []` (e.g., `Browser.close(conn, opts \\ [])`). Captures of the old arity (e.g., `&Browser.close/1`) must be updated.
- **Command structs have a `:meta` field** — defaults to `nil`, excluded from `Encodable.params/1` output

### Changed

- CDDL generator deduplicates fields from group choices and embedded groups
- Telemetry tests use `async: false` with `on_exit` cleanup for reliable handler detachment
- Command facade tests use Mox (`Bibbidi.MockConnection`) instead of `Task.async` + mock transport, eliminating timing-dependent flaky tests

### Fixed

- `resolve_command_fields` no longer skips `{:embed, ref}` entries — events like `browsingContext.NavigationInfo` (embeds `BaseNavigationInfo`) and `network.*Parameters` (embed `BaseParameters`) now resolve all fields correctly

## v0.2.0

### Features

- **Encodable protocol** — `Bibbidi.Encodable` with `method/1` and `params/1` for encoding command structs into BiDi wire format
- **Command structs** — one struct per BiDi command (~60 total), each implementing `Encodable`, generated from the CDDL spec
- **Zoi schemas on command structs** — every command struct exposes `schema/0`, `opts_schema/0`, and `result_schema/0` for runtime validation, introspection, and auto-generated documentation
- **Typed facade specs** — facade functions use real CDDL-derived typespecs (e.g. `String.t()`) instead of `term()`, and `CommandName.opts()` / `CommandName.result()` instead of `keyword()` / `map()`
- **Opts validation** — facade functions validate keyword options via `Zoi.parse!/2` before constructing command structs
- **`Connection.execute/2,3`** — accepts `Encodable` structs directly; existing function-based API is fully backwards-compatible
- **Telemetry** — `[:bibbidi, :command, :start|:stop|:exception]` events on `Connection.execute/2` and `[:bibbidi, :event, :received]` on incoming BiDi events. `Bibbidi.Telemetry` documents all events, measurements, and metadata.
- **`mix bibbidi.gen.workflow`** — Igniter generator that scaffolds a Multi-style Op pipeline builder, Operation record, and sequential Runner into the consumer's project
- **`examples/op_workflow/`** — standalone Mix project demonstrating the Op builder pattern for composing BiDi commands
- **`mix bibbidi.cddl.inspect`** — dev task for inspecting parsed CDDL rules, resolved fields, and extracted commands; now shows CDDL type info in `--fields` and `--commands` output

### Breaking

- **`Bibbidi.Types.*` modules replaced** — the v0.1.0 type modules were removed in v0.2.0. In v0.3.0, new type modules were generated from CDDL with Zoi schemas, `@type t`, and ExDoc docs. These are a different API — delete any old `alias Bibbidi.Types.*` references and refer to the new modules.
- **`Connection.send_command/4` is now a documented low-level escape hatch** — use `Connection.execute/2` for normal usage. `send_command/4` does not emit telemetry or go through `Encodable`; it's useful for vendor extensions or commands not yet in the spec.
- **`Commands.Session.end_session/1` renamed to `Commands.Session.session_end/1`** — `end` is a reserved word; the generated facade uses the `session_end` name. `Bibbidi.Session.end_session/1` is unchanged.
- **`Commands.Session.unsubscribe/3` changed to `Commands.Session.unsubscribe/2`** — `events` moved from a required positional arg into the keyword opts. `Bibbidi.Session.unsubscribe/3` is unchanged.
- **`Script.evaluate/4` changed to `Script.evaluate/5`** — `await_promise` moved from an option (defaulting to `true`) to a required positional arg. Update calls: `Script.evaluate(conn, expr, target)` → `Script.evaluate(conn, expr, target, true)`.
- **`Script.call_function/4` changed to `Script.call_function/5`** — `await_promise` moved from an option (defaulting to `true`) to a required positional arg, inserted before `target`. Update calls: `Script.call_function(conn, fn_decl, target, opts)` → `Script.call_function(conn, fn_decl, true, target, opts)`.
- **`Bibbidi.Transport` behaviour** — added required `send_pong/1` callback. Custom transport implementations must add this function.
- **All facade functions now validate opts with Zoi** — passing unrecognized or wrongly-typed keyword options will raise from `Zoi.parse!/3` instead of being silently ignored.

### Changed

- CDDL code generator migrated to Igniter for proper diffing, dry-run support, and formatting
- Command module functions now construct structs internally and route through `Connection.execute/2`
- Command structs use `Zoi.Struct.enforce_keys/1` and `Zoi.Struct.struct_fields/1` instead of manual `@enforce_keys` / `defstruct`
- Interactive Livebook example updated to use command structs and `Connection.execute/2`

### Fixed

- Regenerated `Session.Unsubscribe` struct — was empty, now correctly includes `events` and `subscriptions` fields from the CDDL choice type
- `Bibbidi.Session.unsubscribe/3` no longer passes `subscriptions: nil` when the option is not provided

mikehostetler

mikehostetler

Felix, happy to collab on this! I have hit some of the same issues you ran into that prompted creating this package - would be interesting to discuss the best way forward.

felix-starman

felix-starman

I am going to release 0.4 shortly once some docs improvements are combined with that branch.

The interface though is fairly stable. There’s a backwards compatible breaking change that allows people to control the interface for how they receive messages, but defaults to the event structs.

I have a design plan for a higher level library called Bobbidi that I think would probably be the better interface for general purpose use as it will contain all of the useful tooling for the different libraries to adapt to their use case.

I’d like to keep Bibbidi as close to the spec as possible, and have some useful helpers for constructing some of the more complex structs such as sending keys and other things like that.

Bobbidi will probably be the place, where if you just need some high-level hooks for interacting with a browser in a typical way and you don’t need to control absolutely everything, you would interface with it.

Let me know some of the pain points you’ve had as well. I have some ideas for how to write bobbidi adapters for the different upstream libraries but knowing the pain points will really help

Where Next?

Trending in Announcing Top

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
385 14863 120
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
zachdaniel
Introducing AshStorage! Attachment and file management that slots directly into your resources :smiling_face_with_sunglasses: I had hope...
New

Other Trending Topics Top

type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
yureehuh
Introduction Founded in 2017 by landscape ecologist and fire mitigation expert Harry Statter, Frontline developed the first fully integra...
New

We're in Beta

About us Mission Statement