Browse

Hex.pmHex DocsCILicense: MIT

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.

Installation

Add browse to your list of dependencies in mix.exs:

def deps do
  [
    {:browse, "~> 0.1.0"}
  ]
end

Browser Implementations

Browse provides the shared contract and pool implementation. To drive a real browser, use a backend package such as:

Design

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:

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.

Example

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
  )

Behavior

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.

Telemetry

Browse emits telemetry for the lifecycle it owns:

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.

License

MIT License. See LICENSE for details.