Wallabidi is a fork of Wallaby with a
public API close to Wallaby's for easy migration. The Browser, Query, Element,
Feature, and DSL APIs are the same.
What's different from Wallaby?
Protocol: Browser communication goes over WebSocket — Chrome via CDP or BiDi, Lightpanda via CDP — never HTTP polling. This means event-driven log capture, lower latency, and access to features impossible with request-response HTTP.
LiveView-aware by default: Every interaction automatically waits for the right thing — no manual sleeps or retry loops needed:
visit/2waits for the LiveSocket to connect before returning.click/2inspects the target element's bindings (phx-click,data-phx-link, plainhref) and classifies the interaction as patch, navigate, or full-page in the same round-trip as the click itself. It then awaits the corresponding DOM patch, page load, or LiveView reconnection automatically.fill_in/3onphx-changeinputs fuses silent-clear + set-value + drain-patches into one round-trip — the call returns only once the server has finished processing the final phx-change.assert_has/2uses an event-drivenawait_selectorthat hooks into LiveView'sonPatchEndcallback and aMutationObserver— it fires the next-match check exactly when the DOM changes, never polls.has_text?/2,has_value?/2route through the same event-driven pattern: a single Promise inside the browser resolves the moment the predicate matches, replacing Elixir-side polling loops.
All of this is installed via injected JavaScript — no changes to your app.js or LiveSocket config are needed.
Architecture: A single opcode interpreter (W.run) on the page side handles every Elixir → browser call. The Elixir side ships opcode lists like [["query","css",".btn"],["classify_first","click"],["click_first"]], never raw JS function bodies. Compound operations (click_aware, fill_in, has_text) fuse multiple steps into a single Promise so each logical operation is one network round-trip. See the Architecture guide for the full picture.
Lazy elements: Most Browser APIs that find then immediately operate (Browser.text, attr, fill_in, click, has_text?...) skip the V8-object-id ref-fetch that Wallaby would do — the element op re-resolves the query inline on the page. Saves one round-trip per element op without changing semantics.
New features:
await_patch/2— Wait for the next LiveView DOM patch (any server-driven re-render of the mounted view that firesonPatchEnd— anassignre-render,handle_info,assign_async, orpush_patch— not justpush_patch, and notpush_navigate/redirect, which are navigations). Useful for server-pushed updates that aren't triggered by a browser interaction. See the API guide for the full definition.
Four drivers: LiveView (in-process, no browser), Lightpanda (headless CDP), Chrome CDP (full browser, direct DevTools Protocol), Chrome BiDi (full browser, W3C WebDriver BiDi via chromium-bidi). Tests declare their minimum requirement with @tag :headless or @tag :browser.
Removed:
- Selenium driver — replaced with native BiDi + CDP
- HTTPoison / Hackney dependencies — replaced with Mint
create_session_fn/end_session_fnoptions
Simplified:
- Direct CDP/BiDi transport — no chromedriver process to manage
- Event-driven JS error detection (no HTTP polling per command)
- W3C capabilities format (
goog:chromeOptions)
Migration steps
- Replace the dependency:
# mix.exs
{:wallabidi, "~> 0.4.0-rc", runtime: false, only: :test}- Find and replace in your project:
| Wallaby | Wallabidi |
|---|---|
Wallaby. | Wallabidi. |
:wallaby | :wallabidi |
config :wallaby, | config :wallabidi, |
- Remove if present:
# No longer needed
config :wallaby, driver: Wallaby.Chrome
config :wallaby, hackney_options: [...]Pin the driver to
:chrome_cdpto start. Wallaby ran every test in a real Chrome; wallabidi instead routes by capability and defaults untagged tests to the in-process:live_viewdriver (see Setup and the driver table in the README). Your existing Wallaby suite has no@tag :headless/@tag :browsertags, so without this every test would route to LiveView and fail. Keep the Wallaby behaviour first, then adopt tags incrementally:# config/test.exs — preserves "everything runs in Chrome" config :wallabidi, otp_app: :your_app, endpoint: YourAppWeb.Endpoint, driver: :chrome_cdpOnce you're green on Chrome, tag the tests that don't need a full browser (
@tag :headless→ Lightpanda, untagged → LiveView) to claw back the speed. You can drop thedriver: :chrome_cdpline entirely when the suite is fully tagged and you want the cheapest-driver defaults.That's it. The
Browser,Query,Element,Feature, andDSLAPIs are the same.