Obscurax

CIcodecovElixirLicense: MIT

Elixir binding for the obscura headless browser engine, built with Rustler.

Obscurax wraps the full obscura API — browser launch, page navigation, V8 JavaScript evaluation, element operations, request/response observers, and request interception — behind a synchronous-feeling Elixir API. Each page runs on its own OS thread with a dedicated V8 isolate, so multiple pages execute JavaScript in true parallel.

Installation

The package is not on Hex yet. Add it as a git dependency:

def deps do
[
{:obscurax, git: "https://github.com/gilbertwong96/obscurax.git", branch: "main"}
]
end

Build requirements

By default, Obscurax downloads a precompiled NIF at compile time — no Rust toolchain required. The precompiled binaries are hosted on GitHub Releases and verified with SHA-256 checksums.

To force building from source instead (e.g. for development or unsupported targets), set OBSCURAX_BUILD=true:

OBSCURAX_BUILD=true mix compile

The first source build takes ~5 minutes because V8 builds from source. Subsequent builds are fast — the V8 artifact is cached by Cargo.

Rustler compiles the Rust crate into a native shared library (obscurax.so on Linux/macOS, obscurax.dll on Windows) and places it in priv/native/.

Quick start

{:ok, browser} = Obscurax.Browser.new()
{:ok, page} = Obscurax.Browser.new_page(browser)
:ok = Obscurax.Page.goto(page, "https://example.com")
{:ok, title} = Obscurax.Page.evaluate(page, "document.title")
IO.puts(title)
Obscurax.Page.close(page)

Bang variants raise Obscurax.Error on failure:

browser = Obscurax.Browser.new!()
page = Obscurax.Browser.new_page!(browser)
Obscurax.Page.goto!(page, "https://example.com")
"Example Domain" = Obscurax.Page.evaluate!(page, "document.title")

Features

Concurrency

Each Obscurax.Page runs on its own OS thread with a dedicated current_thread tokio runtime and V8 isolate. Pages from the same browser (or different browsers) run in true parallel — V8 evaluation in one page never blocks another:

{:ok, browser} = Obscurax.Browser.new()
urls = ["https://example.com", "https://example.org", "https://example.net"]
tasks =
for url <- urls do
Task.async(fn ->
{:ok, page} = Obscurax.Browser.new_page(browser)
:ok = Obscurax.Page.goto(page, url)
{:ok, html} = Obscurax.Page.content(page)
Obscurax.Page.close(page)
html
end)
end
results = Task.await_many(tasks, 30_000)

License

MIT