Wallaby
Concurrent browser tests for your Elixir web apps.
Install / Use
/learn @elixir-wallaby/WallabyREADME
Wallaby helps you test your web applications by simulating realistic user interactions. By default it runs each test case concurrently and manages browsers for you. Here's an example test for a simple Todo application:
defmodule MyApp.Features.TodoTest do
use ExUnit.Case, async: true
use Wallaby.Feature
import Wallaby.Query, only: [css: 2, text_field: 1, button: 1]
feature "users can create todos", %{session: session} do
session
|> visit("/todos")
|> fill_in(text_field("New Todo"), with: "Write my first Wallaby test")
|> click(button("Save"))
|> assert_has(css(".alert", text: "You created a todo"))
|> assert_has(css(".todo-list > .todo", text: "Write my first Wallaby test"))
end
end
Because Wallaby manages multiple browsers for you, its possible to test several users interacting with a page simultaneously.
defmodule MyApp.Features.MultipleUsersTest do
use ExUnit.Case, async: true
use Wallaby.Feature
import Wallaby.Query, only: [text_field: 1, button: 1, css: 2]
@page message_path(Endpoint, :index)
@message_field text_field("Share Message")
@share_button button("Share")
def message(msg), do: css(".messages > .message", text: msg)
@sessions 2
feature "That users can send messages to each other", %{sessions: [user1, user2]} do
user1
|> visit(@page)
|> fill_in(@message_field, with: "Hello there!")
|> click(@share_button)
user2
|> visit(@page)
|> fill_in(@message_field, with: "Hello yourself")
|> click(@share_button)
user1
|> assert_has(message("Hello yourself"))
user2
|> assert_has(message("Hello there!"))
end
end
Read on to see what else Wallaby can do or check out the Official Documentation.
Sponsors
Your company's name and logo could be here!
Setup
Requirements
Wallaby requires Elixir 1.12+ and OTP 22+.
Wallaby also requires bash to be installed. Generally bash is widely available, but it does not come pre-installed on Alpine Linux.
Installation
Add Wallaby to your list of dependencies in mix.exs:
def deps do
[
{:wallaby, "~> 0.30", runtime: false, only: :test}
]
end
Configure the driver.
# Chrome
config :wallaby, driver: Wallaby.Chrome # default
# Selenium
config :wallaby, driver: Wallaby.Selenium
You'll need to install the actual drivers as well.
-
Chrome
-
Selenium
seleniumgeckodriver(for Firefox) orchromedriver(for Chrome)
Ensure that Wallaby is started in your test_helper.exs:
{:ok, _} = Application.ensure_all_started(:wallaby)
When calling use Wallaby.Feature and using Ecto, please configure your otp_app.
config :wallaby, otp_app: :your_app
Phoenix
Enable Phoenix to serve endpoints in tests:
# config/test.exs
config :your_app, YourAppWeb.Endpoint,
server: true
In your test_helper.exs you can provide some configuration to Wallaby.
At a minimum, you need to specify a :base_url, so Wallaby knows how to resolve relative paths.
# test/test_helper.exs
Application.put_env(:wallaby, :base_url, YourAppWeb.Endpoint.url)
Ecto
If you're testing a Phoenix application with Ecto and a database that supports sandbox mode, you can enable concurrent testing by adding the Phoenix.Ecto.SQL.Sandbox plug to your Endpoint.
It's important that this is at the top of endpoint.ex before any other plugs.
# lib/your_app_web/endpoint.ex
defmodule YourAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :your_app
if Application.compile_env(:your_app, :sandbox, false) do
plug Phoenix.Ecto.SQL.Sandbox
end
# ...
socket("/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [:user_agent, session: @session_options]]
)
It's also important to make sure the user_agent is passed in the connect_info in order to allow the database and browser session to be wired up correctly.
Then make sure sandbox is enabled:
# config/test.exs
config :your_app, :sandbox, Ecto.Adapters.SQL.Sandbox
This enables the database connection to be owned by the process that is running your test, but the connection is shared to the process receiving the HTTP requests from the browser, so that the same data is visible in both processes.
If you have other resources that should be shared by both processes (e.g. expectations or stubs if using Mox), then you can define a custom sandbox module:
# test/support/sandbox.ex
defmodule YourApp.Sandbox do
def allow(repo, owner_pid, child_pid) do
# Delegate to the Ecto sandbox
Ecto.Adapters.SQL.Sandbox.allow(repo, owner_pid, child_pid)
# Add custom process-sharing configuration
Mox.allow(MyMock, owner_pid, child_pid)
end
end
And update the test config to use your custom sandbox:
# config/test.exs
config :your_app, :sandbox, YourApp.Sandbox
Assets
Assets are not re-compiled when you run mix test.
This can lead to confusion if you've made changes in JavaScript or CSS but tests are still failing.
There are two common ways to avoid this confusion.
esbuild
If you're using esbuild you can add esbuild default to the test alias in the mix config file.
defp aliases do
[
"test": [
"esbuild default",
"ecto.create --quiet",
"ecto.migrate",
"test",
]
]
end
Webpack
The first solution is to run webpack --mode development --watch from the assets directory.
This will ensure that assets get recompiled after any changes.
The second solution is to add a new alias to your mix config that recompiles assets for you:
def project do
[
app: :my_app,
version: "1.0.0",
aliases: aliases()
]
end
defp aliases, do: [
"test": [
"assets.compile --quiet",
"ecto.create --quiet",
"ecto.migrate",
"test",
],
"assets.compile": &compile_assets/1
]
defp compile_assets(_) do
Mix.shell().cmd("cd assets && ./node_modules/.bin/webpack --mode development",
quiet: true
)
end
This method is less error prone but it will cause a delay when starting your test suite.
LiveView
In order to test Phoenix LiveView (as of version 0.17.7) with Wallaby you'll also need to add a function to each mount/3 function in your LiveViews, or use the on_mount live_session lifecycle hook in the router:
defmodule MyApp.Hooks.AllowEctoSandbox do
import Phoenix.LiveView
import Phoenix.Component
def on_mount(:default, _params, _session, socket) do
allow_ecto_sandbox(socket)
{:cont, socket}
end
defp allow_ecto_sandbox(socket) do
%{assigns: %{phoenix_ecto_sandbox: metadata}} =
assign_new(socket, :phoenix_ecto_sandbox, fn ->
if connected?(socket), do: get_connect_info(socket, :user_agent)
end)
Phoenix.Ecto.SQL.Sandbox.allow(metadata, Application.get_env(:your_app, :sandbox))
end
end
and then including the function usage in the router:
live_session :default, on_mount: MyApp.Hooks.AllowEctoSandbox do
# ...
end
Umbrella Apps
If you're testing an umbrella application containing a Phoenix application for the web interface (MyWebApp) and a separate persistence application (MyPersistenceApp) using Ecto 2 or 3 with a database that supports sandbox mode, then you can use the same setup as above, with a few tweaks.
# my_web_app/lib/endpoint.ex
defmodule MyWebApp.Endpoint do
use Phoenix.Endpoint, otp_app: :my_web_app
if Application.compile_env(:my_persistence_app, :sandbox, false) do
plug Phoenix.Ecto.SQL.Sandbox
end
Make sure MyWebApp is set up to serve endpoints in tests and that the SQL sandbox is enabled:
# my_web_app/config/test.exs
config :my_web_app, MyWebApp.Endpoint,
server: true
config :my_persistence_app, :sql_sandbox, true
Then in MyWebApp's test_helper.exs you can provide some configuration to Wallaby.
At minimum, you need to specify a :base_url, so Wallaby knows how to resolve relative paths.
# my_web_app/test/test_helper.exs
Application.put_env(:wallaby, :base_url, MyWebApp.Endpoint.url)
You will also want to add phoenix_ecto as a dependency to MyWebApp:
# my_web_app/mix.exs
def deps do
[
{:phoenix_ecto, "~> 3.0", only: :test}
]
end
Writing tests
It's easiest to add Wallaby to your test suite by using the Wallaby.Feature module.
defmodule YourApp.UserListTest do
use ExUnit.Case, async: true
use Wallaby.Feature
feature "users have names", %{session: session} do
session
|> visit("/users")
|> find(Query.css(".user", count: 3))
|> List.first()
|> assert_has(Query.css(".user-name", text: "Chris"))
end
end
API
The full documentation for the DSL is in the official documentation.
Queries and Actions
Wallaby's API is broken into 2 concepts: Queries and Actions
Related Skills
gh-issues
347.0kFetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
node-connect
347.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Writing Hookify Rules
107.8kThis skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.

