Fluffy (Fluffy v0.7.0)

Copy Markdown View Source

Pipeable feature testing for Phoenix applications.

Start a session with the Phoenix (:phoenix) or Playwright (:playwright) backend, then compose locators, actions, expectations, and event waits. Import Fluffy.Expect for expectations, or use Fluffy.Assert for ExUnit-style assertions. The Phoenix backend selects its Static (Phoenix.ConnTest) or LiveView (Phoenix.LiveViewTest) driver for each page; the Playwright backend uses the Playwright driver.

Unless a function documents additional entries, action, assertion, reload, and event-wait option lists accept:

  • :timeout (non_neg_integer/0) - Maximum time in milliseconds to wait for the operation. Playwright browser bookkeeping and navigation synchronization use the session timeout separately.

Summary

Lifecycle and navigation

Closes a session and its resources. Returns :ok or {:error, reason}.

Reloads the active page's current document and returns the updated session.

Starts an isolated :phoenix or :playwright session.

Actions

Fills a control with the literal string representation of value.

Presses a key on one strict target. Behavior depends on the current driver.

Selects options by value or label, or explicitly with %{value: value}, %{label: label}, or %{index: index}.

Selects local paths or in-memory Fluffy.FilePayload values for a file input, or clears its FileList with [].

Submits one form through its native submission path.

Events

Consumes a pending event and returns its value. Its deadline starts at registration.

Removes the most recently registered listener matching this source, event type, and handler.

Registers a persistent handler. Handlers run independently, linked to the registering caller.

Registers a handler for the first matching event, removing it before invocation.

Registers an event wait immediately. Await the pending handle once with await/1.

Diagnostics and native access

Opens a temporary HTML snapshot of the active page in your default browser.

Groups the operations in fun under a named diagnostic step.

Runs a driver-specific native operation and returns the reconciled session.

Lifecycle and navigation

close_session(session)

Closes a session and its resources. Returns :ok or {:error, reason}.

reload(session, options \\ [])

@spec reload(Fluffy.Session.t(), [action_option()]) :: Fluffy.Session.t()

Reloads the active page's current document and returns the updated session.

The Phoenix backend re-dispatches the current URL and selects the Static or LiveView driver for the returned page. The Playwright backend asks the active browser page to reload.

start_session(backend, options \\ [])

@spec start_session(backend(), [session_option()]) :: Fluffy.Session.t()

Starts an isolated :phoenix or :playwright session.

Configure the application endpoint once with config :fluffy, endpoint: MyAppWeb.Endpoint. The session base URL uses the endpoint's active HTTP or HTTPS listener when available and otherwise falls back to MyAppWeb.Endpoint.url(). An explicit :endpoint or :base_url option overrides the application configuration for this session.

Options

visit(session, path)

Actions

check(session, locator, options \\ [])

click(session, locator, options \\ [])

fill(session, locator, value, options \\ [])

Fills a control with the literal string representation of value.

Accepts any value implementing String.Chars, including numbers, dates, and custom structs. Conversion uses to_string/1, without HTML escaping.

Filling contenteditable elements requires Playwright. Static and LiveView support input and textarea controls.

press(session, locator, key, options \\ [])

Presses a key on one strict target. Behavior depends on the current driver.

Playwright performs a native key press, including browser default actions, and accepts Playwright key names, characters, and modifier combinations.

LiveView forwards %{"key" => key} unchanged to LiveViewTest's render_keydown/2 and render_keyup/2 for bindings on the selected element. Either or both phases may be bound, including phx-window-keydown and phx-window-keyup on that element. An element with no keyboard binding raises. LiveViewTest handles event values and targeting. Fluffy does not filter phx-key, translate key names, parse modifier combinations, synthesize input values, change focus, edit text, activate controls, or submit forms. The caller is responsible for supplying the intended key value.

Static pages do not support press and raise Fluffy.CapabilityError. Use submit/2 to submit a form explicitly on any driver.

select_option(session, locator, requested, options \\ [])

@spec select_option(Fluffy.Session.t(), Fluffy.Locator.t(), term(), [action_option()]) ::
  Fluffy.Session.t()

Selects options by value or label, or explicitly with %{value: value}, %{label: label}, or %{index: index}.

Values and labels accept String.Chars and are converted without HTML escaping. Indexes remain zero-based integers. A list requests multiple options; wrap a charlist in %{value: charlist} or %{label: charlist} to use it as one option.

set_input_files(session, locator_or_chooser, selection, options \\ [])

Selects local paths or in-memory Fluffy.FilePayload values for a file input, or clears its FileList with [].

One path or payload selects one file. A non-empty homogeneous list selects files in list order on an input with the multiple attribute. [] clears the current selection. The complete value is validated and snapshotted before the page changes. Static and LiveView drivers carry those snapshots into form submission or the supported managed-upload lifecycle; Playwright uses its native path or payload transport.

The default aggregate selection limit is configured with config :fluffy, file_input_max_bytes: 10_000_000. Override it for one action with :max_bytes. File contents are never included in size or validation errors.

Pass a chooser returned by Fluffy.Event.file_chooser/1 directly. It targets its original page and preserves the session's current-page selection. Choosers require Playwright. The returned session keeps this action pipeable:

pending = wait_for(session, Fluffy.Event.file_chooser())

session
|> click(by_role(:button, name: "Upload"))
|> set_input_files(await(pending), "test/fixtures/report.pdf")
|> click(by_role(:button, name: "Save"))

Register the wait before clicking the button. await/1 returns the chooser; set_input_files/4 fills its input and returns the session.

Options

  • :timeout (non_neg_integer/0) - Maximum time in milliseconds to wait for the operation. Playwright browser bookkeeping and navigation synchronization use the session timeout separately.

  • :max_bytes (non_neg_integer/0) - Maximum aggregate size of the selected files in bytes.

submit(session, locator, options \\ [])

Submits one form through its native submission path.

This is a semantic form action, not a synthetic keyboard event. Clicking a particular submit button remains the way to select a submitter and its name/value or override attributes.

Playwright follows browser-native constraint validation. Static and LiveView deliberately bypass it and submit the current structural form state; use Playwright when the test concerns invalid events, validity UI, focus, or browser-blocked submission.

uncheck(session, locator, options \\ [])

Events

await(pending)

@spec await(Fluffy.Event.Pending.t()) :: term()

Consumes a pending event and returns its value. Its deadline starts at registration.

off(session, event, handler, options \\ [])

Removes the most recently registered listener matching this source, event type, and handler.

on(session, event, handler, options \\ [])

Registers a persistent handler. Handlers run independently, linked to the registering caller.

once(session, event, handler, options \\ [])

Registers a handler for the first matching event, removing it before invocation.

wait_for(session, event, options \\ [])

Registers an event wait immediately. Await the pending handle once with await/1.

Diagnostics and native access

open_browser(session)

@spec open_browser(Fluffy.Session.t()) :: Fluffy.Session.t()

Opens a temporary HTML snapshot of the active page in your default browser.

Returns the unchanged session so it can be used in a pipeline. LiveView pages use Phoenix.LiveViewTest.open_browser/1. Static pages resolve local assets through the session endpoint; Playwright pages resolve assets against the page URL. Scripts are removed. This is an HTML snapshot, not an offline archive: browser resources must remain accessible, and DOM properties such as unsaved input values, canvas contents, and shadow roots are not serialized.

The temporary file remains available after the session closes.

session
|> visit("/chamber")
|> open_browser()

step(session, name, fun)

(macro)

Groups the operations in fun under a named diagnostic step.

Playwright sessions with tracing enabled record a nested trace group at the call site's source location. Phoenix sessions and untraced Playwright sessions simply run the callback. The callback must return the updated session, which is also returned by step/3.

unwrap(session, fun)

Runs a driver-specific native operation and returns the reconciled session.

Static callbacks receive the current Plug.Conn and must return an updated Plug.Conn. LiveView callbacks receive the current Phoenix.LiveViewTest.View, and Playwright callbacks receive a Fluffy.Playwright.Handle. Successful LiveView and Playwright callback return values are ignored.

Native operations are not cross-driver compatible. Prefer the shared Fluffy API whenever it covers the behavior under test.

Types

action_option()

@type action_option() :: {:timeout, non_neg_integer()}

backend()

@type backend() :: :phoenix | :playwright

file_input_option()

@type file_input_option() ::
  {:timeout, non_neg_integer()} | {:max_bytes, non_neg_integer()}

session_option()

@type session_option() ::
  {:endpoint, atom()}
  | {:base_url, binary()}
  | {:headers, [{atom() | binary(), binary()}]}
  | {:conn, struct()}
  | {:timeout, pos_integer()}
  | {:browser_context, keyword()}