ScreenshotNeo

BlogHow-to

How to Take Website Screenshots with Elixir

Capture screenshots from Elixir with Wallaby for browser tests or ChromicPDF for application workflows. See setup, runnable examples, options, and fixes for common failures.

By the ScreenshotNeo team30 September 202610 min read

How to Take Website Screenshots with Elixir

To take a website screenshot with Elixir, choose the capture path that matches where the screenshot is used. For a browser-driven test, use Wallaby: start a browser session, navigate to the page, then call Wallaby.Browser.take_screenshot(session). Wallaby saves the current window as a file. For an application workflow that needs image data in Elixir, use ChromicPDF: ChromicPDF.capture_screenshot/2 accepts a URL source and returns a result containing a base64-encoded PNG by default. These libraries have different setup and output models; one is not a drop-in replacement for the other.

See the Wallaby guide, screenshot API reference, and ChromicPDF API reference for version-specific details. The code below follows the documented APIs; check the versions your application locks before copying configuration.

1. Choose the Elixir screenshot workflow

Your need Start with What you get
Capture the page while exercising a feature test Wallaby A screenshot file from the active browser session, normally in a screenshots directory.
Capture a URL from application code and process or store the result ChromicPDF A result/blob, base64-encoded PNG by default, with capture options passed to Chrome.
Need full-page or custom image format behavior Check the selected library’s current API ChromicPDF documents full_page and format options; don’t assume Wallaby exposes the same controls.

Both approaches rely on a real browser engine and therefore need browser-compatible runtime setup. Wallaby is explicitly a browser automation and testing library. ChromicPDF is useful when the capture is part of an application function or job rather than an assertion-focused test. A screenshot is a rendered visual artifact: it does not by itself prove that text, controls, or accessibility semantics are correct.

2. Capture a page with Wallaby in a browser test

Install and configure Wallaby

Add Wallaby as a test-only dependency. The current repository documentation lists Elixir 1.17+ and OTP 26+ requirements and says to install browser-driver software separately. For Chrome, configure Wallaby’s Chrome driver and install chromedriver. For Selenium, configure the Selenium driver and install Selenium plus either geckodriver or chromedriver. Verify requirements against the release in your lockfile because these details can change. Wallaby also needs bash; Alpine-based environments may need it installed explicitly. See the Wallaby repository setup.

Wallaby captures the current browser session after the test navigates to the intended page state.
Wallaby captures the current browser session after the test navigates to the intended page state.
# mix.exs
def deps do
  [
    {:wallaby, "~> 0.30", runtime: false, only: :test}
  ]
end

# config/test.exs
config :wallaby, driver: Wallaby.Chrome

# test/test_helper.exs
{:ok, _} = Application.ensure_all_started(:wallaby)
Application.put_env(:wallaby, :base_url, "http://localhost:4002")

For a Phoenix feature test, the endpoint must be running during the test. Wallaby’s setup guide shows enabling server: true for the endpoint and using its URL as :base_url. Use the actual URL and port for your test environment. If your tests use Ecto sandbox mode, follow Wallaby’s Phoenix and sandbox setup rather than assuming browser requests share the test process’s database transaction.

defmodule MyAppWeb.HomeScreenshotTest do
  use Wallaby.Feature

  import Wallaby.Browser
  import Wallaby.Query

  feature "capture the home page", %{session: session} do
    session
    |> visit("/")
    |> assert_has(css("main"))
    |> take_screenshot(name: "home-page", log: true)
  end
end

This assumes your project has configured Wallaby.Feature, started the application endpoint, and supplied Wallaby’s base URL. The screenshot call acts on the current session; it does not start Chrome, navigate, or wait for your page for you. Put assertions or explicit waits before capture so the image reflects the state the test is intended to inspect. Wallaby’s API accepts a name and a log option; by default, the filename uses a timestamp and the location is under screenshots in the directory where tests run. Its guide documents configuring :screenshot_dir.

# config/test.exs
config :wallaby,
  screenshot_dir: "tmp/test-screenshots",
  screenshot_on_failure: true

# Or set these in test/test_helper.exs:
Application.put_env(:wallaby, :screenshot_dir, "tmp/test-screenshots")
Application.put_env(:wallaby, :screenshot_on_failure, true)

Automatic failure capture is documented for tests using Wallaby.Feature.feature/3. Keep generated screenshots out of source control unless snapshots are intentionally part of your review process. In CI, upload the directory as a build artifact if people need to inspect failures; a path in an ephemeral worker is not persistent by itself.

3. Capture a URL with ChromicPDF from Elixir

ChromicPDF’s screenshot function blocks until the screenshot has been created and returns a result. Its documentation demonstrates a URL source and a base64-encoded PNG blob. Add ChromicPDF to your dependencies and start its process as part of your application’s supervision tree, following the package’s installation guide for the chosen release.

# mix.exs, representative dependency declaration
def deps do
  [
    {:chromic_pdf, "~> 1.17"}
  ]
end
defmodule MyApp.PageCapture do
  @spec capture(binary()) :: {:ok, binary()} | {:error, term()}
  def capture(url) do
    ChromicPDF.capture_screenshot({:url, url})
  end
end

case MyApp.PageCapture.capture("https://example.com") do
  {:ok, png_base64} ->
    {:ok, png_bytes} = Base.decode64(png_base64)
    File.write!("page.png", png_bytes)

  {:error, reason} ->
    Logger.error("Screenshot capture failed: #{inspect(reason)}")
    {:error, reason}
end

The URL argument is a page source, not a local file path. The documentation’s local-file example uses {:url, "file:///example.html"}. For network URLs, Chrome must be able to resolve and reach the host from the machine or container running the capture. Decode the base64 result before writing PNG bytes. Check the returned error tuple before using the value; pattern matching only on {:ok, blob} can crash a job when Chrome exits or navigation fails.

Format and full-page options

ChromicPDF passes screenshot settings to Chrome through the :capture_screenshot option. The docs show selecting JPEG with a map. It also supports full_page: true to enlarge the viewport to fit the page content; that option requires Chrome 91 or later.

{:ok, jpeg_base64} = ChromicPDF.capture_screenshot(
  {:url, "https://example.com"},
  capture_screenshot: %{format: "jpeg"}
)

{:ok, full_page_png} = ChromicPDF.capture_screenshot(
  {:url, "https://example.com/long-page"},
  full_page: true
)

The documented default example returns PNG. Follow the Chrome screenshot protocol’s supported option names and value types when passing additional settings; don’t infer that every option from another screenshot library is accepted. The ChromicPDF API reference also describes navigation options, cookies, script evaluation, and wait options in connection with its print APIs. Confirm an option’s applicability to capture_screenshot/2 before depending on it.

If using ChromicPDF.Template as the screenshot source, the docs warn that many page-related styles do not take effect for screenshots. For ordinary web pages, use a URL source and check the rendered result where CSS, fonts, or page scripts matter.

4. cURL, Python, and Node.js alternatives for an HTTP screenshot API

If your requirement is specifically to trigger a website screenshot from code without installing and managing a browser driver in the Elixir application, a hosted screenshot API is another workflow. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its single GET request accepts a URL and returns PNG, JPEG, WebP, or PDF. The examples below use the documented endpoint and target URL; create an API key before running them. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

That Node example uses Bun’s file writer. With Node.js, write the response body using the runtime’s filesystem API, for example by converting the response’s ArrayBuffer to a buffer and calling writeFile. Keep API keys in environment configuration, not checked-in source code. Treat the response as binary image data, not JSON.

5. Or skip the browser setup

ScreenshotNeo makes one HTTP request and returns the screenshot. Its docs list the request options and response behavior:

A hosted capture flow can clean known overlays before returning the page image.
A hosted capture flow can clean known overlays before returning the page image.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers indicate the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. All features are on every plan.

Create a free ScreenshotNeo account and capture your first 1,000 screenshots a month without a card.

6. Options and edge cases to plan for

Concern Wallaby ChromicPDF
Browser lifecycle Use a running test session and configured driver. Run the ChromicPDF process and local Chrome setup, or configure the environment as documented.
Output File saved in screenshot directory; name and logging can be configured on the call. Result/blob, base64 PNG by default; decode before writing binary data.
Full page Don’t assume it from the documented current-window API. full_page: true, requiring Chrome 91+.
Directory Configure :screenshot_dir. Your code decides where decoded output is stored.
Failure artifact screenshot_on_failure for Wallaby feature tests. Handle the error result and log enough context to investigate.

Very long pages and large images consume more browser memory and produce larger outputs. A full-page capture can be substantially taller than the initial viewport. Choose the smallest capture scope that answers the question. Dynamic pages may still be changing when captured; use Wallaby assertions or the appropriate documented wait mechanism for the library and version in use. Pages behind authentication require appropriate session or cookie configuration, and secrets should not be written to logs or committed with screenshot fixtures.

7. Performance, reliability, and cost

Browser startup, page navigation, JavaScript execution, font loading, and image loading all add latency. Reusing a supervised capture service or a warm browser process can avoid repeated startup overhead where the selected library supports that model, but measure it in your deployment before setting job timeouts. Keep concurrency bounded: each active browser capture uses CPU and memory, and a burst of long pages can exhaust a container. Apply a timeout at the job or request boundary and record the URL, elapsed time, and failure class without logging credentials.

For test reliability, wait for a meaningful page condition instead of sleeping a fixed long interval. A screenshot captured before client rendering completes may be valid bytes but show a blank or incomplete page. For repeatable visual artifacts, control viewport size and test data, and be aware that browser versions, fonts, device pixel ratio, animation, and third-party content can alter pixels. Wallaby’s screenshot is the current window; Chromium-based full-page behavior should be explicitly enabled where available.

Self-hosted libraries have no per-shot API charge specified in the cited documentation, but they do require browser installation, maintenance, compute, storage, and operational work. ScreenshotNeo pricing is Free for 1,000 shots/month, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free. Choose based on capture volume and whether hosted browser operations, cleanup, and billing verdicts are useful to your workflow.

8. Troubleshooting

Symptom Likely cause Fix
Wallaby cannot start the browser Driver binary missing, incompatible, or not on PATH. Install the selected driver and verify its version and executable path against the Wallaby/browser setup.
Wallaby relative URL fails No base URL is configured, or the test endpoint is not serving. Set :base_url and start the Phoenix endpoint for the test.
Screenshot directory is empty The test did not reach capture, or artifacts were written to another working directory. Enable log: true, set an explicit screenshot directory, and inspect the test output and CI artifact paths.
ChromicPDF returns an error or Chrome exits Chrome is absent, cannot launch, or cannot reach the target URL. Check executable configuration, container dependencies, network/DNS access, and the returned error details.
Saved image is unreadable Base64 text was written as though it were PNG bytes. Decode the returned blob with Base.decode64/1 before writing.
Screenshot has missing content Capture happened before client rendering or external resources completed. Wait for a selector or application-specific ready state using an option supported by your installed library.
Full-page option has no effect ChromicPDF needs Chrome 91+ for this option, or the installed library version differs. Check the actual Chrome version and the locked ChromicPDF documentation.
Template screenshot ignores some styles ChromicPDF documents limitations for page-related styles in ChromicPDF.Template screenshots. Use a URL source where appropriate or adapt the page styling to the documented template behavior.

9. FAQ

Does take_screenshot/1 open a browser?

No. It captures the current Wallaby session, so the test must start a session, navigate, and wait for the intended state first.

Can ChromicPDF return a file path directly?

The documented screenshot example returns a base64 blob. Decode it and write the bytes where your application needs them.

Which Elixir option is best for production screenshots?

It depends on whether browser ownership and runtime management belong in your application. The documentation reviewed here does not establish a universal production winner or cover every deployment environment.

Can I use Wallaby’s screenshot to check accessibility?

A visual screenshot shows rendered appearance, not the accessibility tree or semantic correctness. Keep accessibility assertions separate.

How do I capture an element rather than a page?

The referenced Wallaby and ChromicPDF docs cited here do not establish a supported element-capture workflow for these examples. Verify the current API before relying on one.

References