Obscurax
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
- Rust 1.91+ (rustup) — only needed for
:force_buildor local development. Arust-toolchain.tomlis included so rustup automatically installs the correct version. - C++ compiler — clang/gcc on Linux, Xcode Command Line Tools on macOS, MSVC on Windows (required when building from source)
- git — to fetch obscura as a Cargo dependency
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
- Full obscura API — Browser, Page, Element operations, CookieStore
- Synchronous-feeling Elixir API — async navigation and V8 evaluation under the hood, clean
{:ok, result}/{:error, %Obscurax.Error{}}returns - Dirty CPU scheduler — V8 evaluation runs on dirty CPU schedulers so it never blocks the BEAM
- Passive observers —
on_request/2andon_response/2fire messages into a callback process for every network event - Request interception — pause, inspect, modify, mock, or block requests via
enable_interception/1andreply_intercept/3 - Stealth mode (opt-in) —
Obscurax.Browser.new(stealth: true)to reduce detection
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