PhxDemo.Steps (phx_demo v0.1.1)

Copy Markdown View Source

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.

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

await_liveview(conn, opts \\ [])

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.

await_uploads(conn, opts \\ [])

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.

clear_spotlight(conn)

Removes the spotlight.

drop_files(conn, target, paths, opts \\ [])

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 in glide/3
  • :settle: milliseconds to hold after the upload finishes (default 300)

glide(conn, target, opts \\ [])

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)

glide_click_button(conn, text, opts \\ [])

Glides to a button, then clicks it with PhoenixTest.click_button/2.

glide_click_link(conn, text, opts \\ [])

Glides to a link, then clicks it with PhoenixTest.click_link/2.

hide_cursor(conn)

Hides the overlay cursor, e.g. for a scene that's only a static page.

mark(conn, label)

A named marker in the timeline.

pause(conn, ms)

Waits so the viewer can take in what's on screen.

say(conn, text, opts \\ [])

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, or false to not pause (the next steps then happen while the line is spoken)
  • :gap: extra pause after a TTS clip (default 250)

scene(conn, title)

Starts a new scene: a chapter in the MP4. The previous scene's final state is screenshotted for drift checks (PhxDemo.Drift).

show_cursor(conn)

Shows the overlay cursor again.

speaking_ms(text)

Estimated time to speak text at about 150 words per minute, with a 900ms minimum.

iex> PhxDemo.Steps.speaking_ms("one two three")
1154

spotlight(conn, target)

Dims the page around target and outlines it.

stop_camera(conn)

Ends the fake camera: the tracks the app holds are stopped and fire ended, and new getUserMedia video requests fail with NotFoundError.

stop_microphone(conn)

Ends the fake microphone: the tracks the app holds are stopped and fire ended, and new getUserMedia audio requests fail with NotFoundError.

type_into(conn, target, text, opts \\ [])

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 in glide/3

upload(conn, target, paths, opts \\ [])

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 (default false)
  • :picker_ms: how long the sheet stays (default 1100)
  • :steps: as in glide/3
  • :settle: milliseconds to hold after the upload finishes (default 300)

upload_audio(conn, target, path, opts \\ [])

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 (default false)
  • :audible: mix the audio into the MP4 at that moment (default false). Music ducks under it as under say, and a warning is logged if it overlaps a spoken line. See PhxDemo.Audio.

use_camera(conn, path, opts \\ [])

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 (default true)
  • :audio: use the video's soundtrack as the microphone (default false), played when the app requests audio. Also takes :audible, as in use_microphone/3. stop_camera/1 stops it too.

use_camera_image(conn, path)

Like use_camera/3, with a still image as the camera feed (QR or document scanning).

use_microphone(conn, path, opts \\ [])

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 (default false: it plays once per request)
  • :audible: mix the audio into the MP4 (default false). Music ducks under it as under say; a warning is logged if it overlaps a spoken line.