The examples use the standard test-module convention from the usage guide:
import Fluffy.Expect, plus aliases for Fluffy.Event and Fluffy.Page.
Every wait_for(Event.*(...), action) call is listener-before-action and
pipeable. The action callback must return its updated session. Captured keys
are immutable and results are non-consuming. Fluffy installs the listener
before it invokes the action callback.
Downloads
session
|> wait_for(Event.download(:report), fn session ->
click(session, by_role(:button, name: "Download potion ledger"))
end)
|> expect(download_suggested_filename(:report, "potions.csv"))
|> expect(download_content_type(:report, "text/csv"))download(session, :report) returns %Fluffy.Download{} with filename,
content_type, bytes, and url. Override the default retained-byte limit
with max_bytes: on Event.download/2 or wait_for/4.
New pages and tabs (Playwright only)
session
|> wait_for(Event.popup(:secret_chamber), fn session ->
click(session, by_role(:button, name: "Open chamber in new tab"))
end)
|> switch_page(:secret_chamber)
|> expect(Page.to_have_opener(:main))
|> expect(Page.to_have_url(path: "/chambers/secrets"))
|> expect(visible(by_role(:heading, name: "Secret chamber")))
|> close_page()
|> expect(visible(by_text("Creature index")))Multiple pages and tabs require Playwright. Phoenix follows links and submits
forms in its current page, ignoring target and formtarget, including
_blank. Popup capture, page switching, and closing pages raise a capability
error in Phoenix. Multiple isolated sessions remain supported by both backends.
All pages inside one Playwright session share cookies/storage; independent
sessions do not.
page(session, :secret_chamber) returns the captured opaque Fluffy.Page; use
Page.name/1, Page.url/1, Page.status/1, Page.opener/1, and
Page.revision/1 to inspect its metadata without switching.
Navigation
session
|> wait_for(Event.navigation(:forbidden_forest), fn session ->
click(session, by_role(:link, name: "Follow the spiders"))
end)
|> expect(navigation_url(:forbidden_forest, forbidden_forest_url))
|> expect(navigation_status(:forbidden_forest, 200))Dialogs
Dialogs are atomic browser-only events. The decision is installed before the action so an unhandled modal cannot deadlock the click:
session
|> wait_for(Event.dialog(:release_basilisk, accept: true), fn session ->
click(session, by_role(:button, name: "Release basilisk"))
end)
|> expect(dialog_type(:release_basilisk, :confirm))
|> expect(dialog_message(:release_basilisk, "Release the basilisk?"))
|> expect(dialog_action(:release_basilisk, :accept))Use :dismiss, {:accept, "prompt text"}, or a decision function receiving
the unhandled %Fluffy.Dialog{}.
Requests and responses
session
|> wait_for(Event.response(:potions, ~r{/api/potions}), fn session ->
click(session, by_role(:button, name: "Refresh potions"))
end)
|> expect(response_status(:potions, 200))
|> expect(response_resource_type(:potions, "fetch"))Matchers can be an exact URL, regex, or normalized-event predicate. These are browser-wide subresource streams and therefore Playwright-only. The event contains method, URL, headers, resource type, post data, status fields, and the known page name.
File choosers
Directly locating the file input is preferred, even when it is hidden. Applications sometimes expose only a button whose JavaScript opens the input's native chooser. Capture that browser event before clicking, then pass its key to the same file-selection action:
session
|> wait_for(Event.file_chooser(:portrait), fn session ->
click(session, by_role(:button, name: "Choose creature portrait"))
end)
|> set_input_files(:portrait, %Fluffy.FilePayload{
name: "fluffy.png",
bytes: png_bytes,
content_type: "image/png"
})file_chooser(session, :portrait) returns the opaque
%Fluffy.FileChooser{}; Fluffy.FileChooser.multiple?/1 exposes whether
it accepts multiple files. The chooser result is non-consuming and lives with
the session, but it belongs to the page that opened it. Scripted chooser events
are Playwright-only because Static and LiveView do not execute application
JavaScript. This API sets files programmatically; it does not operate the OS
picker UI.
Failure artifacts
Setting FLUFFY_ARTIFACT_DIR through the Playwright artifact_dir
configuration is recommended. It captures full-page PNG, HTML, and formatted
exception text when a public Playwright driver operation fails; configure CI
to upload that directory on failure. A capture error never replaces the actual
test failure. Artifacts are evidence for debugging, not conformance input.