Add Fluffy and configure your Phoenix endpoint first. Complete Playwright setup if you need browser tests, and Ecto sandbox setup if your application uses a database. Then follow Usage for your first test and shared test case.
Elixir dependency
defp deps do
[
{:fluffy, "~> 0.6.0", only: :test}
]
endPhoenix endpoint
Configure your application endpoint once in config/test.exs. Fluffy uses
it for in-process requests and derives the default session URL from the
endpoint. When the endpoint has an active HTTP or HTTPS listener, its actual
bind address and port are authoritative. Fluffy falls back to
MyAppWeb.Endpoint.url() when no listener is running. This supports endpoints
configured with port: 0:
config :fluffy, endpoint: MyAppWeb.Endpoint
config :my_app, MyAppWeb.Endpoint, server: trueThe server is required for Playwright tests. If Playwright targets an
application hosted elsewhere, configure config :fluffy, base_url: "..."
instead. Explicit endpoint: and base_url: session options override the
global values for multi-endpoint or dynamic-server tests.
The application's normal browser bundle must connect its LiveSocket. See Actionability checks and waiting for Fluffy's automatic LiveView connection wait.
Playwright setup
Only the Playwright backend needs JavaScript and a browser. Install the tested Playwright version in your application:
pnpm add --save-dev playwright@1.63.0
pnpm exec playwright install chromium
Configure its installation directory in config/test.exs:
config :fluffy,
playwright: [
enabled: true,
engine: :chromium,
assets_dir: Path.expand("..", __DIR__),
timeout: 15_000,
launch_options: [headless: true],
artifact_dir: System.get_env("FLUFFY_ARTIFACT_DIR"),
trace_dir: System.get_env("FLUFFY_TRACE_DIR", "traces"),
js_logger: Fluffy.Playwright.ConsoleLogger
]Fluffy uses an explicit executable first. When assets_dir is configured, it
resolves node_modules/playwright/cli.js inside that directory. Otherwise, it uses
./assets/node_modules/playwright/cli.js if present, falling back to
playwright_ex's default (playwright on PATH). Relative directories are
resolved against the current working directory.
Set launch_options: [timeout: 10_000] to give browser launch a separate timeout;
otherwise it uses the global Playwright timeout. To use a system browser, set
launch_options: [executable_path: System.find_executable("google-chrome-stable")].
A nil executable path is treated as omitted, leaving Playwright to select its
default browser.
Setting artifact_dir is recommended for every project that runs Playwright
tests. Fluffy writes HTML, a full-page screenshot, and the formatted error
there when a public browser operation fails. Use a writable per-run directory
locally and configure CI to upload it when the test job fails; artifact capture
does not run for successful operations.
The default console logger sends browser console messages and uncaught page errors
through Elixir's Logger; set js_logger: false to disable it or configure another
module implementing PlaywrightEx.JsLogger. Trace files are created only when a test
calls Fluffy.Playwright.trace/1 or Fluffy.Playwright.trace/2.
Set engine: :firefox or engine: :webkit after installing the corresponding
browser.
See Browser baseline for conformance coverage.
Fluffy launches one browser lazily per configured runtime lane and creates a
fresh isolated BrowserContext for each session. ExUnit max_cases is the
default concurrency bound; Fluffy does not add a second pool limiter.
Lower it when browser startup or the host becomes resource-constrained.
Ecto sandbox
Add the standard Phoenix Ecto sandbox plug before the rest of the endpoint and expose request headers to the LiveView socket:
plug(Phoenix.Ecto.SQL.Sandbox,
header: Fluffy.Sandbox.header(),
sandbox: Fluffy.Sandbox.allowance()
)
socket("/live", Phoenix.LiveView.Socket,
websocket: [
connect_info: [Fluffy.Sandbox.connect_info(), session: @session_options]
]
)Add the Fluffy mount hook before application hooks:
live_session :test, on_mount: Fluffy.Sandbox do
# routes
endName the application's repositories once, then establish one scope in every Fluffy test through the same callback used by applications without Ecto:
config :fluffy, ecto_repos: [MyApp.Repo]setup context do
Fluffy.Test.setup(context)
endThe scope adopts an existing DataCase/ConnCase checkout when present. It closes browser contexts and in-process LiveViews before stopping sandbox owners, including when the owner test process fails. Sessions also close when their owning process exits. Call setup once per test, whether or not Ecto is used.
Use Fluffy.Test.setup(context, sandbox: false) for a test that deliberately
needs lifecycle management without the configured repositories, or pass
repos: [...] to override them in an umbrella or multi-repository test.
The defaults are the x-fluffy-sandbox header and
Ecto.Adapters.SQL.Sandbox. Applications that already use Phoenix Ecto's
user-agent transport or need to grant additional process ownership can
configure both values in config/test.exs:
config :fluffy, Fluffy.Sandbox,
header: "user-agent",
sandbox: MyApp.TestSandboxSandbox metadata defaults to trap_exit: true, following Phoenix Ecto. Set
trap_exit: false in this configuration if the application must retain ordinary
exit handling, for example when linked tasks finish normally in a LiveView.
The sandbox value may be a module implementing allow(repo, owner, child) or
Phoenix Ecto's {module, function, extra_arguments} form. For example:
defmodule MyApp.TestSandbox do
def allow(repo, owner, child) do
:ok = Ecto.Adapters.SQL.Sandbox.allow(repo, owner, child)
MyApp.Mocks.allow(owner, child)
end
endFluffy validates this configuration when its application starts. The same header and
allowance value must be passed to the endpoint plug, and Fluffy.Sandbox.connect_info/0
selects :user_agent or :x_headers for the socket. An application may instead retain
its own LiveView/channel hook when additional ownership rules require application state;
use the same encoded metadata and call Phoenix.Ecto.SQL.Sandbox.allow/2 there.
Upload limits
config :fluffy, file_input_max_bytes: 10_000_000file_input_max_bytes bounds the aggregate bytes Fluffy will snapshot for one
Fluffy.set_input_files/3 or Fluffy.set_input_files/4 action. It applies to local
paths and in-memory payloads on all three drivers and can be overridden with the
action's max_bytes: option.