Fluffy
Phoenix feature tests. Three drivers, one Playwright-shaped API.
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? Keep Phoenix and browser tests in the same module.
Fluffy adds strict, composable locators and
assertion retries and action waiting for LiveView and browser tests.
Mix non-browser and browser tests in a single test module -
simply add @tag backend: :playwright to select tests.
See the mixed-module example and 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.
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. Download captures can filter by filename and URL; URL assertions also accept URI predicates. See Advanced events and pages for examples and the Capability matrix for what each backend supports.
Guides
- Installation and runtime — endpoint, browser, and Ecto sandbox setup
- Usage — locators, actions, expectations, backend choice, and diagnostics
- Assertion styles — imported
assert/refuteandexpectvocabularies compared - Advanced events and pages — tabs, windows, iframe limitations, downloads, navigation, dialogs, and network events
- Migrating from PhoenixTest — an alternate starting point for existing PhoenixTest suites
- Capability matrix — backend differences and limitations
Developing Fluffy
Fluffy requires playwright_ex 0.11 or newer.
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.