Pipeable demo steps. Each takes and returns the PhoenixTest session (conn),
so they mix freely with visit/2, click_link/2, assert_has/3, etc.
PhxDemo.Case imports them. Outside a demo (in a plain test) they
still work but don't record to a timeline.
Targets are anything PhxDemo.Target accepts: a CSS selector string, or
{:link, text}, {:button, text}, {:label, text}, {:text, text}, {:role, role, name}.
Summary
Functions
Waits until LiveView is connected and no phx-*-loading classes remain.
glide/3 and the glide_click_* steps already call this. Use it after
plain PhoenixTest actions such as visit/2 if you need a settled page.
See PhxDemo.LiveView.
Waits for in-flight LiveView uploads to finish. upload/4, drop_files/4,
glide/3 and the glide_click_* steps already do; use it after something
that starts an upload another way.
Removes the spotlight.
Drags files onto a drop zone (an element with LiveView's phx-drop-target).
Glides the cursor to the center of target, scrolling it into view first.
Waits for LiveView to settle before measuring.
Glides to a button, then clicks it with PhoenixTest.click_button/2.
Glides to a link, then clicks it with PhoenixTest.click_link/2.
Hides the overlay cursor, e.g. for a scene that's only a static page.
A named marker in the timeline.
Waits so the viewer can take in what's on screen.
A narration line. It becomes a caption, and, when a TTS adapter is
configured (see PhxDemo.TTS), spoken audio in the rendered MP4.
Starts a new scene: a chapter in the MP4. The previous scene's final state
is screenshotted for drift checks (PhxDemo.Drift).
Shows the overlay cursor again.
Estimated time to speak text at about 150 words per minute, with a 900ms minimum.
Dims the page around target and outlines it.
Ends the fake camera: the tracks the app holds are stopped and fire ended,
and new getUserMedia video requests fail with NotFoundError.
Ends the fake microphone: the tracks the app holds are stopped and fire
ended, and new getUserMedia audio requests fail with NotFoundError.
Glides to a form control and types into it one key at a time.
Uploads files through a file input, the way a person would: the cursor glides to the input (or its label), a click shows, then the files are attached and the step waits for LiveView to finish uploading them.
Uploads an audio file (voice note, podcast, recording) through a file input.
A thin wrapper over upload/4 that also records an :audio_input timeline
event (mode: "upload", file, duration_ms from ffprobe; its at_ms is
when the file was attached).
Makes the page's camera requests (getUserMedia({video: ...})) return
frames from a video fixture. See PhxDemo.Camera for how it works and which
formats are supported (non-WebM videos are converted with ffmpeg and cached).
Like use_camera/3, with a still image as the camera feed (QR or document scanning).
Makes the page's microphone requests (getUserMedia({audio: ...})) return
audio from a fixture file. Playback starts when the app requests the
microphone, and the :audio_input timeline event (mode: "microphone",
action: "play") records that moment. See PhxDemo.Audio for the details and
formats (anything ffmpeg decodes; formats the browser can't decode are
converted to WAV and cached).
Functions
Waits until LiveView is connected and no phx-*-loading classes remain.
glide/3 and the glide_click_* steps already call this. Use it after
plain PhoenixTest actions such as visit/2 if you need a settled page.
See PhxDemo.LiveView.
Waits for in-flight LiveView uploads to finish. upload/4, drop_files/4,
glide/3 and the glide_click_* steps already do; use it after something
that starts an upload another way.
Removes the spotlight.
Drags files onto a drop zone (an element with LiveView's phx-drop-target).
conn |> drop_files("#drop-zone", "avatar.png")File icons labeled with the file names fly in from off-screen to the zone
while the cursor glides there, then dragenter, dragover and drop are
dispatched with a DataTransfer holding the files' real bytes, built in the
page. It then waits for LiveView to finish uploading.
Bytes are sent to the page base64-encoded, so keep demo fixtures small.
Paths resolve as in upload/4. Records an :upload event with
mode: "drop".
Options:
:flight_ms: how long the icons travel (default 900):steps: as inglide/3:settle: milliseconds to hold after the upload finishes (default 300)
Glides the cursor to the center of target, scrolling it into view first.
Waits for LiveView to settle before measuring.
Options:
:steps: intermediate mouse moves (default 25; more is smoother and slower)
Glides to a button, then clicks it with PhoenixTest.click_button/2.
Glides to a link, then clicks it with PhoenixTest.click_link/2.
Hides the overlay cursor, e.g. for a scene that's only a static page.
A named marker in the timeline.
Waits so the viewer can take in what's on screen.
A narration line. It becomes a caption, and, when a TTS adapter is
configured (see PhxDemo.TTS), spoken audio in the rendered MP4.
The demo pauses so the line can finish: for the real clip length plus
:gap with TTS, or for an estimated speaking time without.
Options:
:wait: milliseconds to pause instead, orfalseto not pause (the next steps then happen while the line is spoken):gap: extra pause after a TTS clip (default 250)
Starts a new scene: a chapter in the MP4. The previous scene's final state
is screenshotted for drift checks (PhxDemo.Drift).
Shows the overlay cursor again.
Estimated time to speak text at about 150 words per minute, with a 900ms minimum.
iex> PhxDemo.Steps.speaking_ms("one two three")
1154
Dims the page around target and outlines it.
Ends the fake camera: the tracks the app holds are stopped and fire ended,
and new getUserMedia video requests fail with NotFoundError.
Ends the fake microphone: the tracks the app holds are stopped and fire
ended, and new getUserMedia audio requests fail with NotFoundError.
Glides to a form control and types into it one key at a time.
Options:
:speed::fast,:human(default) or:slow, or milliseconds per key:steps: as inglide/3
Uploads files through a file input, the way a person would: the cursor glides to the input (or its label), a click shows, then the files are attached and the step waits for LiveView to finish uploading them.
conn
|> upload("#doc-upload", "report.pdf")
|> upload({:label, "Photos"}, ["a.png", "b.png"], picker: true)target is the file input or its label (any PhxDemo.Target). A hidden
input is fine: the cursor goes to its label. Relative paths resolve from
test/demos/fixtures/ (see PhxDemo.Fixtures); a missing file raises
PhxDemo.Env.Error, and literal paths are checked before the browser starts.
Playwright attaches files without a native dialog. LiveView's upload
progress is awaited with no sleeps (PhxDemo.LiveView), so the next step
sees the finished entries. In a demo this step shadows
PhoenixTest.upload/3,4,5; call that one qualified if you need it.
Records an :upload timeline event with files (name, size),
mode: "input", the target and its box, which auto-zoom uses.
Options:
:picker: show a fake file-picker sheet with the files highlighted for about a second before attaching (defaultfalse):picker_ms: how long the sheet stays (default 1100):steps: as inglide/3:settle: milliseconds to hold after the upload finishes (default 300)
Uploads an audio file (voice note, podcast, recording) through a file input.
A thin wrapper over upload/4 that also records an :audio_input timeline
event (mode: "upload", file, duration_ms from ffprobe; its at_ms is
when the file was attached).
conn |> upload_audio("#voice-note", "hello.wav")
conn |> upload_audio("#drop-zone", "hello.wav", drop: true, audible: true)path resolves as in upload/4. Options are those of upload/4 (or
drop_files/4 with drop: true) plus:
:drop: attach by dropping onto the target instead of the file input (defaultfalse):audible: mix the audio into the MP4 at that moment (defaultfalse). Music ducks under it as undersay, and a warning is logged if it overlaps a spoken line. SeePhxDemo.Audio.
Makes the page's camera requests (getUserMedia({video: ...})) return
frames from a video fixture. See PhxDemo.Camera for how it works and which
formats are supported (non-WebM videos are converted with ffmpeg and cached).
Calling it again switches the fixture on the live stream, so an app that is already showing the camera sees the new video.
Options:
:loop: restart the video when it ends (defaulttrue):audio: use the video's soundtrack as the microphone (defaultfalse), played when the app requests audio. Also takes:audible, as inuse_microphone/3.stop_camera/1stops it too.
Like use_camera/3, with a still image as the camera feed (QR or document scanning).
Makes the page's microphone requests (getUserMedia({audio: ...})) return
audio from a fixture file. Playback starts when the app requests the
microphone, and the :audio_input timeline event (mode: "microphone",
action: "play") records that moment. See PhxDemo.Audio for the details and
formats (anything ffmpeg decodes; formats the browser can't decode are
converted to WAV and cached).
conn
|> use_microphone("question.wav")
|> visit(~p"/record")
|> glide_click_button("Record")Calling it again switches the fixture; an app that is already listening hears
the new one. Relative paths resolve as in upload/4.
Options:
:loop: repeat the audio while the app listens (defaultfalse: it plays once per request):audible: mix the audio into the MP4 (defaultfalse). Music ducks under it as undersay; a warning is logged if it overlaps a spoken line.