Current section
Files
Jump to
Current section
Files
README.md
**English** | [Korean](README.ko.md) | [Japanese](README.ja.md)
<p align="center">
<img src="https://raw.githubusercontent.com/glendix-labs/glendam/main/doc_assets/header_1.png" alt="Glendam" style="max-width: 450px; width: 100%;">
</p>
<h1 align="center">Glendam</h1>
<p align="center">
â› Multipurpose headless browser automation for Gleam and the BEAM â›
</p>
<p align="center">
<a href="https://hex.pm/packages/glendam">
<img src="https://img.shields.io/hexpm/v/glendam" alt="Package Version">
</a>
<a href="https://hexdocs.pm/glendam/">
<img src="https://img.shields.io/badge/hex-docs-ffaff3" alt="Hex Docs">
</a>
<img alt="Target: Erlang" src="https://img.shields.io/badge/target-erlang-red?logo=erlang">
</p>
## Project status and lineage
**Glendam started as a fork of
[Chrobot](https://github.com/JonasGruenwald/chrobot).** That origin is a core
part of the project's history: Chrobot established the original design,
high-level API, and Chrome DevTools Protocol foundation on which Glendam
builds. Glendam is independently maintained and is not an official release
from Chrobot's original author.
Today, most Glendam changes support web integration in the
[Glendix](https://github.com/glendix-labs/glendix) ecosystem, including browser
workflows used by `mxpak` and the family test harnesses. This is the current
development focus, not the limit of the project. **Glendam's long-term goal
is to become a multifunction, general-purpose headless browser** for automation,
testing, scraping, rendering, document generation, and other browser-driven
workflows in Gleam and on the BEAM.
The project is not kept in lockstep with Chrobot. Features, dependency
upgrades, compatibility fixes, and releases are developed here under the
Glendam identity. The package and Gleam module namespace are
`glendam`, and environment variables use the `GLENDAM_` prefix.
## About
Glendam provides typed bindings to the stable
[Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/),
plus higher-level browser automation APIs. It manages a Chrome or Chromium
instance through an Erlang Port and handles protocol communication for you.
Typical uses include:
- generating PDFs from HTML;
- taking screenshots;
- web scraping and archiving;
- browser integration tests; and
- direct, typed Chrome DevTools Protocol calls.
> [!WARNING]
> The generated protocol surface is large and not every binding is covered by
> tests. Treat untested protocol calls as experimental.
## Requirements
- Gleam 1.17.0 or newer
- Erlang/OTP 29 (the tested development version is 29.0.4)
- rebar3 3.27.0 or newer
- Google Chrome or Chromium for browser-backed operations
The versions used by contributors and CI are recorded in `.mise.toml` and the
GitHub Actions workflow.
## Installation
### Gleam
```sh
gleam add glendam@1
```
### Elixir
```elixir
# mix.exs
defp deps do
[
{:glendam, "~> 1.0", app: false, manager: :rebar3}
]
end
```
## Browser setup
Glendam can use an existing Google Chrome or Chromium installation. Set
`GLENDAM_BROWSER_PATH` when automatic discovery is not suitable.
The package also includes a utility that installs
[Chrome for Testing](https://github.com/GoogleChromeLabs/chrome-for-testing)
inside the current project:
```sh
gleam run -m glendam/install
```
From Elixir:
```sh
mix run -e :glendam@install.main
```
The installer can be configured with `GLENDAM_TARGET_VERSION` and
`GLENDAM_TARGET_PATH`. Browser launch configuration can be supplied with:
- `GLENDAM_BROWSER_PATH`
- `GLENDAM_BROWSER_ARGS`
- `GLENDAM_BROWSER_TIMEOUT`
- `GLENDAM_LOG_LEVEL`
- `GLENDAM_TRANSPORT` (`auto`, `pipe`, `websocket`, or `ws`)
## Examples
### Take a screenshot
```gleam
import glendam
import glendam/chrome
import gleam/result
pub type ExampleError {
BrowserCouldNotLaunch(reason: chrome.LaunchError)
BrowserWorkflowFailed(
reason: glendam.DeferredBrowserError(chrome.RequestError),
)
}
pub fn main() -> Result(Nil, ExampleError) {
use browser <- result.try(
glendam.launch()
|> result.map_error(BrowserCouldNotLaunch),
)
glendam.defer_quit(browser, fn() {
use page <- result.try(
glendam.open(browser, "https://gleam.run", 30_000),
)
use _ <- result.try(glendam.await_selector(page, "body"))
use screenshot <- result.try(glendam.screenshot(page))
glendam.to_file(screenshot, "hi_lucy")
})
|> result.map_error(BrowserWorkflowFailed)
}
```
### Generate a PDF with Lustre
```gleam
import glendam
import glendam/chrome
import gleam/result
import lustre/element
import lustre/element/html
pub type ExampleError {
BrowserCouldNotLaunch(reason: chrome.LaunchError)
BrowserWorkflowFailed(
reason: glendam.DeferredBrowserError(chrome.RequestError),
)
}
fn build_page() -> String {
html.body([], [
html.h1([], [element.text("Spanakorizo")]),
html.p([], [element.text("A page rendered by Lustre and printed by Glendam.")]),
])
|> element.to_document_string()
}
pub fn main() -> Result(Nil, ExampleError) {
use browser <- result.try(
glendam.launch()
|> result.map_error(BrowserCouldNotLaunch),
)
glendam.defer_quit(browser, fn() {
use page <- result.try(
glendam.create_page(browser, build_page(), 10_000),
)
use document <- result.try(glendam.pdf(page))
glendam.to_file(document, "recipe")
})
|> result.map_error(BrowserWorkflowFailed)
}
```
### Scrape a page
```gleam
import glendam
import glendam/chrome
import gleam/io
import gleam/list
import gleam/result
pub type ExampleError {
BrowserCouldNotLaunch(reason: chrome.LaunchError)
BrowserWorkflowFailed(
reason: glendam.DeferredBrowserError(chrome.RequestError),
)
}
pub fn main() -> Result(Nil, ExampleError) {
use browser <- result.try(
glendam.launch()
|> result.map_error(BrowserCouldNotLaunch),
)
glendam.defer_quit(browser, fn() {
use page <- result.try(
glendam.open(
browser,
"https://books.toscrape.com/",
30_000,
),
)
use _ <- result.try(glendam.await_selector(page, "body"))
use items <- result.try(
glendam.select_all(page, ".product_pod h3 a"),
)
use titles <- result.try(
list.map(items, fn(item) {
glendam.get_attribute(page, item, "title")
})
|> result.all(),
)
io.debug(titles)
Ok(Nil)
})
|> result.map_error(BrowserWorkflowFailed)
}
```
Please respect websites' terms of service, robots policies, and capacity when
scraping.
### Use from Elixir
```elixir
{:ok, browser} = :glendam.launch()
{:ok, page} = :glendam.open(browser, "https://example.com", 10_000)
{:ok, object} = :glendam.select(page, "h1")
{:ok, text} = :glendam.get_text(page, object)
:ok = case :glendam.quit(browser) do
{:ok, nil} -> :ok
other -> other
end
```
## Migrating from Chrobot
The rename is intentionally visible because `chrobot` and `glendam` are
separate Hex packages:
```diff
- gleam add chrobot
+ gleam add glendam
- import chrobot
- import chrobot/protocol/page
+ import glendam
+ import glendam/protocol/page
- CHROBOT_BROWSER_PATH=/path/to/chrome
+ GLENDAM_BROWSER_PATH=/path/to/chrome
```
Most API names remain familiar, but major Glendam releases may introduce
breaking changes as the maintained fork evolves. Read `CHANGELOG.md` before
upgrading.
## Development
```sh
gleam deps download
gleam check
gleam test
gleam format --check src test dev vendor/justin_fork/src vendor/justin_fork/test
```
Browser-backed tests run when `GLENDAM_TEST_BROWSER_PATH` points to a
Chrome or Chromium executable. Without it, the test entry point explains how to
configure the browser.
Protocol source files are maintained in this repository. If the checked-in
bindings must be regenerated from the JSON schemas in `assets/`, run the Gleam
development module directly:
```sh
gleam run -m codegen/generate_bindings
gleam format src test dev
```
This is a maintenance operation, not an upstream synchronization workflow.
## Documentation
- Package documentation: <https://hexdocs.pm/glendam>
- High-level API: <https://hexdocs.pm/glendam/glendam.html>
- Protocol entry point: <https://hexdocs.pm/glendam/glendam/protocol.html>
- Original Chrobot repository: <https://github.com/JonasGruenwald/chrobot>
## License
Glendam remains available under the MIT License. See `LICENSE` for the
original copyright notice and the continuation copyright notice.