Installation and runtime

Copy Markdown View Source

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}
  ]
end

Phoenix 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: true

The 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
end

Name 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)
end

The 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.TestSandbox

Sandbox 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
end

Fluffy 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_000

file_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.