Fluffy

A friendly three-headed Fluffy asleep beside an Elixir wizard playing a flute, with the heads labeled Live, Static, and Playwright

Phoenix feature tests. Three drivers, one Playwright-shaped API.

Hex.pm HexDocs CI License

Fluffy runs tests through ConnTest, LiveViewTest, or a real browser. Its in-process drivers are checked against Playwright. Driver differences are documented in the capability matrix.

Coming from PhoenixTest? Fluffy adds strict, composable locators, per-test backend selection within one module, and assertion retries and action waiting for LiveView and browser tests. See the differences →

creature_row =
by_role(:table, name: "Hagrid's creatures")
|> by_role(:row)
|> filter(has: by_text("Fluffy", exact: true))
start_session(:phoenix)
|> visit("/creatures")
|> click(by_role(creature_row, :button, name: "Play flute"))
|> assert(visible(by_role(creature_row, :cell, name: "Asleep")))

This example assumes the imports and test setup below. Locators are reusable queries: find Fluffy's row in the “Hagrid's creatures” table and scope both the action and the assertion to it.

HTML behind this example

<h1 id="guards-heading">Hagrid's creatures</h1>
<table aria-labelledby="guards-heading">
  <thead>
    <tr><th>Creature</th><th>State</th><th>Actions</th></tr>
  </thead>
  <tbody>
    <tr>
      <td>Norbert</td>
      <td>Awake</td>
      <td>
        <form method="post" action="/creatures">
          <input type="hidden" name="creature" value="norbert">
          <button name="action" value="feed">Feed dragon</button>
        </form>
      </td>
    </tr>
    <tr>
      <td>Fluffy</td>
      <td>Awake</td>
      <td>
        <form method="post" action="/creatures">
          <input type="hidden" name="creature" value="fluffy">
          <button name="action" value="flute">Play flute</button>
        </form>
      </td>
    </tr>
  </tbody>
</table>

Getting started

Add the dependency:

# mix.exs
defp deps do
[
{:fluffy, "~> 0.3.0", only: :test}
]
end

Configure the endpoint:

# config/test.exs
config :fluffy, endpoint: MyAppWeb.Endpoint

Every Fluffy test establishes a lifecycle scope and imports the shared API:

use ExUnit.Case, async: true
use Fluffy.Assert
import Fluffy
import Fluffy.Locator
setup context do
Fluffy.Test.setup(context)
end

Start with start_session(:phoenix) for in-process Static and LiveView tests. Use start_session(:playwright) when a test needs a real browser. See Installation and runtime for Playwright and Ecto sandbox setup, then Usage for writing tests and a shared FluffyCase.

Assertion styles

The guides use ExUnit-style assertions. Both styles use the same execution engine, retries, and diagnostics; this is just a choice of vocabulary.

ExUnit style — use Fluffy.Assert Expect style — import Fluffy.Expect
assert(visible(locator)) expect(to_be_visible(locator))
refute(visible(locator)) expect(not_(to_be_visible(locator)))
assert(page_url("/creatures")) expect(page_to_have_url("/creatures"))

Each call above is a step in a session |> … pipeline. See Assertion styles for setup and a fuller comparison.

Migrating from PhoenixTest

The pipeline stays familiar; actions and expectations use composable locators:

PhoenixTest Fluffy
fill_in("Name", with: "Basilisk") fill(by_label("Name", exact: true), "Basilisk")
click_button("Register") click(by_role(:button, name: "Register"))
assert_path("/creatures") assert(page_url(path: "/creatures"))

See Migrating from PhoenixTest for the full translation table and behavior differences.

Even :phoenix checks structural visibility. See Visibility and DOM presence before translating assert_has or refute_has into visibility assertions.

Beyond page interactions

Capture downloads, open new tabs, handle dialogs, and observe network events with Fluffy's event API. See Advanced events and pages for examples and the Capability matrix for what each backend supports.

Guides

Developing Fluffy

The project pins the current local-development toolchain in .tool-versions. Fluffy's compatibility floor remains Elixir 1.18 and Node.js 20; development also requires pnpm 11.19.0 and PostgreSQL. From a clean checkout:

mix setup
mix test

Run the ordinary local gate—Styler-backed formatting, warnings-as-errors compilation, strict Credo, and the test suite—with:

mix check

Add test-environment Dialyzer analysis with:

mix quality

License

Fluffy is released under the MIT License.