PhxDemo (phx_demo v0.1.1)

Copy Markdown View Source

Automated, reproducible demo videos for Phoenix / LiveView apps.

A demo is an ExUnit test built on PhoenixTest.Playwright.Case, so it gets a real browser, the Ecto sandbox, and seed data from the test process. PhxDemo.Case adds video recording, an overlay cursor, and a timeline of what happened when. The steps in PhxDemo.Steps add pacing.

defmodule MyAppWeb.Demos.PostsDemo do
  use PhxDemo.Case
  use MyAppWeb, :verified_routes

  demo "create a post", %{conn: conn} do
    conn
    |> scene("Creating a post")
    |> visit(~p"/posts")
    |> say("Here are the posts we already have.")
    |> glide_click_link("New Post")
    |> type_into({:label, "Title"}, "Hello world")
    |> glide_click_button("Save Post")
    |> assert_has("#flash-info", text: "Post created")
  end
end

Each demo writes into <output_dir>/<module>/<demo>/:

  • *.webm: the raw recording. Playwright finalizes it when the browser context closes, after the test's own on_exit callbacks.
  • timeline.json: scenes, narration lines, and cursor targets with millisecond offsets from roughly the start of the recording.

Summary

Functions

Title card content for :intro / :outro settings, or nil.

Directory that demo output goes under.

Presentation settings for a demo, resolved at runtime. Priority: use PhxDemo.Case options, then config :phx_demo, <key>, then defaults.

Turns a module or demo name into a filesystem-safe slug.

Viewport and video size. Defaults to 1280x720.

Functions

card(arg1, card, demo, app)

Title card content for :intro / :outro settings, or nil.

iex> PhxDemo.card(:intro, true, "create a post", "my_app")
%{title: "Create a post", subtitle: "My App"}

iex> PhxDemo.card(:outro, true, "create a post", "my_app")
%{title: "My App", subtitle: "Thanks for watching"}

iex> PhxDemo.card(:intro, %{title: "Hi"}, "x", nil)
%{title: "Hi"}

iex> PhxDemo.card(:intro, false, "x", nil)
nil

output_dir(opts \\ [])

Directory that demo output goes under.

Priority: :output_dir option, then config :phx_demo, :output_dir, then tmp/demos.

settings(opts \\ [])

Presentation settings for a demo, resolved at runtime. Priority: use PhxDemo.Case options, then config :phx_demo, <key>, then defaults.

  • :intro, :outro: true (generated title card), false, or %{title: ..., subtitle: ...}
  • :captions: :both (drawn into the video and a soft subtitle track), :burned, :soft, or false
  • :zoom: true, false, or a max zoom factor (default 1.6)
  • :trim: cut dead time before the first step and after the last one
  • :intro_ms, :outro_ms: card durations
  • :music: path to a background track (any format ffmpeg reads, or a .mid/.midi file rendered with fluidsynth), a prompt to generate a track with ElevenLabs (%{elevenlabs: "calm lo-fi piano"}, see PhxDemo.MusicGen), or false (default). Plays whenever a path is set.
  • :music_volume: music level in dB (default -18)
  • :soundfont: .sf2 file used to render a MIDI :music track. Defaults to the one from mix phx_demo.soundfont.setup, then common system locations. See PhxDemo.Midi.

slug(module)

Turns a module or demo name into a filesystem-safe slug.

iex> PhxDemo.slug("Create a Post!")
"create-a-post"

iex> PhxDemo.slug(MyAppWeb.Demos.PostsDemo)
"my_app_web-demos-posts_demo"

viewport(opts \\ [])

Viewport and video size. Defaults to 1280x720.