Shared browser automation contract and pool implementation for Elixir browser backends.
Browse defines a transport-agnostic contract for browser automation packages such as a Chrome implementation, a Servo implementation, or any future engine backend. The goal is to expose browser capabilities without leaking implementation details such as CDP sessions or engine-specific RPC handles.
Add browse to your list of dependencies in mix.exs:
def deps do
[
{:browse, "~> 0.1.0"}
]
endBrowse provides the shared contract and pool implementation. To drive a real browser, use a backend package such as:
Browse is not a browser engine and it does not speak a wire protocol itself, but it does provide the shared pool implementation and browser capability contract that engine backends plug into.
It provides:
Browse.Browseras the shared browser capability behaviorBrowseas the actual pool implementation and facade
This lets packages like Chrona and a future Servo implementation expose the same API while keeping CDP or any other backend protocol as an implementation detail.
Configure your pools and the default pool:
config :browse,
default_pool: MyApp.ChromePool,
pools: [
MyApp.ChromePool: [implementation: MyApp.Chrome, pool_size: 4],
MyApp.SecondaryChromePool: [implementation: MyApp.Chrome, pool_size: 2]
]Start the configured pools under your application supervisor:
children = Browse.children()Or start one configured pool directly:
{:ok, _pid} = Browse.start_link(MyApp.ChromePool)Then use the pool through the unified API:
Browse.checkout(fn browser ->
:ok = Browse.navigate(browser, "https://example.com")
Browse.capture_screenshot(browser, format: "jpeg", quality: 90)
end)If you have multiple pools, you can still target one explicitly:
Browse.checkout(MyApp.SecondaryChromePool, fn browser ->
Browse.current_url(browser)
end)You can also override config at startup time if needed:
{:ok, _pid} =
Browse.start_link(MyApp.ChromePool,
implementation: MyApp.Chrome,
pool_size: 1
)Implementations are expected to satisfy Browse.Browser.
Browse owns pool startup, checkout, and worker lifecycle. Implementations are configured per pool and only provide browser initialization, termination, and browser operations such as navigation and screenshots.
Pooling is not a behavior. It is a concrete concern of this package, implemented by Browse itself. Backends plug into that runtime by implementing Browse.Browser.
The browser handle passed around by the behavior is intentionally opaque. Each implementation is free to represent it however it needs.
Browse emits telemetry for the lifecycle it owns:
[:browse, :pool, :start, :start | :stop | :exception][:browse, :checkout, :start | :stop | :exception][:browse, :worker, :init, :start | :stop | :exception][:browse, :worker, :remove][:browse, :worker, :terminate]
Attach handlers with :telemetry.attach_many/4:
events = [
[:browse, :pool, :start, :stop],
[:browse, :checkout, :stop],
[:browse, :worker, :terminate]
]
:telemetry.attach_many(
"browse-metrics",
events,
fn event, measurements, metadata, _config ->
IO.inspect({event, measurements, metadata}, label: "browse telemetry")
end,
nil
)See Browse.Telemetry for the full event contract, measurements, and metadata.
MIT License. See LICENSE for details.