Automated How-To video recorder for any web application.
Write YAML. Get videos.
Quick Start • Two Modes • Configuration • Actions • Plugins • Examples
HowTo Recorder turns declarative YAML scenarios into polished screen recordings of any web application. It drives a real browser with animated cursor movements, supports AI voiceover via ElevenLabs, background music, and frosted-glass title cards — all from a simple YAML file.
# 1. Clone and install
git clone https://github.com/straussbastian/howTo-Recorder.git
cd howTo-Recorder
npm install
npx playwright install chromium
# 2. Configure
cp recorder.config.example.yaml recorder.config.yaml
cp .env.example .env
# Edit both files for your app
# 3. Record
npm run record -- example-flowNo title field = silent .webm video. Perfect for internal demos and quick checks.
name: dashboard-flow
description: Login and navigate to dashboard
steps:
- login
- wait: 1000
- click.text: Dashboard
- wait.url: /dashboard
- wait: 2000Set title to activate the Howto pipeline: ElevenLabs TTS, background music, frosted-glass title card, and MP4 output.
name: howto-create-user
title: "How do I create a user?"
description: Tutorial for creating a new user
music: background.mp3
fadeOut: 1.5
steps:
- login
- wait.ready
- narrate: |
In this short tutorial I'll show you
how to create a new user.
- click.text: Users
narrate: We open the user list.
- wait.url: /users
- wait: 500
- fill:
Name: Max Mustermann
narrate: We enter the name.
- click.button: Create
narrate: One click on Create and the user is saved.
- wait.idle
- wait: 1000Record multiple scenarios in sequence:
name: all-howtos
scenarios:
- howto-create-user
- howto-edit-profile
- howto-dashboardAll app-specific settings live in one file. Copy the example and adjust:
cp recorder.config.example.yaml recorder.config.yamlapp:
base_url: https://your-app.com
ready_selector: "body" # Element that signals "app is ready"
loading_selector: ".loading" # Loading spinners that must disappear
auth:
login_path: /login
selectors:
email: "input[type='email']"
password: "input[type='password']"
submit: "button[type='submit']"
typing_delay: 15 # ms/char (fast, behind title card)
cursor_speed: 200 # ms cursor animation
titleCard:
background: "rgba(255,255,255,0.30)"
blur: 10
text_color: "#ffffff"
box_background: "rgba(0,0,0,0.75)"
font_size: 56
min_display_ms: 3500
cursor:
transition_ms: 1100
move_wait_ms: 1200
typing:
delay_ms: 80 # ms/char in visible flow
plugins: []The recorder.config.example.yaml includes ready-to-use presets for:
| Framework | Ready Selector | Login Path |
|---|---|---|
| WordPress | #wpadminbar |
/wp-login.php |
| Next.js (NextAuth) | #__next |
/auth/signin |
| React-Admin | .RaLayout-content |
/login |
| Laravel Filament | .fi-topbar |
/login |
| Vue + Vuetify | #app |
/login |
| Django Admin | #header |
/admin/login/ |
Just uncomment the matching preset in your config.
Secrets go in .env (never committed):
RECORDER_BASE_URL=https://your-app.com
RECORDER_EMAIL=user@example.com
RECORDER_PASSWORD=secret
# Only for Howto videos:
ELEVENLABS_API_KEY=
ELEVENLABS_VOICE_ID=Place these files in assets/ to customize the title card:
assets/logo.svg— Logo in the top-left corner (optional)assets/title-font.ttf— Custom font for the title text (optional)
If not present, the title card uses system fonts and no logo.
| Action | Argument | Description |
|---|---|---|
login |
— | Login with .env credentials |
goto |
URL | Navigate to URL (relative or absolute) |
wait.ready |
— | Wait for ready_selector + loading idle |
wait.idle |
— | Wait until all loading_selector elements disappear |
wait.url |
substring | Wait until URL contains substring |
wait.selector |
CSS selector | Wait until element is visible |
wait |
milliseconds | Hard pause |
| Action | Argument | Description |
|---|---|---|
click |
CSS selector | Click first match with cursor animation |
click.text |
visible text | Click first visible element with this text |
click.button |
button label | Click button or link by role + name |
fill |
{ Label: value } |
Type into form field by label |
| Action | Argument | Description |
|---|---|---|
narrate |
text | Voiceover pause (standalone step) |
hud |
text or { text, ms } |
Show keystroke overlay |
screenshot |
file path | Save PNG screenshot |
Any step can carry a narrate: field for synchronized voiceover:
- click.text: Users
narrate: We open the user list.The narration fires before the action. The runner waits until the audio clip finishes (or the action takes longer).
Plugins add custom actions for app-specific UI patterns.
// plugins/my-app/index.ts
import type { Page } from 'playwright';
type ActionHandler = (page: Page, arg: unknown) => Promise<void>;
export function register(actions: Record<string, ActionHandler>): void {
actions['modal.open'] = async (page, arg) => {
const text = typeof arg === 'string' ? arg : '';
await page.getByRole('button', { name: text }).click();
await page.waitForSelector('.modal', { state: 'visible' });
};
}# recorder.config.yaml
plugins: ["./plugins/my-app"]npm run record -- --list-actionsSee the examples/ directory for real-world recordings:
| Example | Description | Type |
|---|---|---|
| create-partner | Partner anlegen in Laravel Filament | Howto (MP4 + Voiceover) |
Each example includes the YAML scenario, the output video, and a README explaining the setup.
howto-recorder/
├── recorder.config.yaml # App-specific config (selectors, auth, plugins)
├── .env # Secrets (credentials, API keys)
├── scenarios/ # YAML scenario files
├── assets/ # Optional: logo.svg, title-font.ttf, music
├── plugins/ # Custom action plugins
├── videos/ # Output directory
├── src/
│ ├── cli.ts # Entry point
│ ├── config.ts # Config loader
│ ├── recorder.ts # Recording session + Howto pipeline
│ ├── yaml-scenario.ts # YAML parser + step runner
│ ├── actions.ts # Action registry + plugin loader
│ ├── types.ts # Shared interfaces
│ ├── audio/
│ │ ├── elevenlabs.ts # ElevenLabs TTS
│ │ └── mix.ts # ffmpeg muxing
│ ├── overlays/
│ │ ├── titleCard.ts # Frosted-glass title card
│ │ ├── cursor.ts # Animated cursor
│ │ └── hud.ts # Keystroke HUD
│ └── core/
│ ├── auth.ts # Login (selectors from config)
│ ├── cursor-click.ts # clickWithCursor
│ ├── forms.ts # fillByLabel
│ └── wait.ts # waitForReady, waitForIdle
└── examples/ # Real-world example recordings
- Pre-synthesis — All
narratetexts are sent to ElevenLabs TTS before the browser opens - Browser launch — Playwright opens Chromium (1920x1080) with cursor, HUD, and title card overlays
- Step execution — Each YAML step runs its action with animated cursor movements
- Post-processing — ffmpeg muxes the raw WebM with narration clips, background music, and fade effects into a final MP4
# Record a single scenario
npm run record -- my-scenario
# Record with visible browser (for debugging)
HEADED=1 npm run record -- my-scenario
# List all available actions
npm run record -- --list-actions
# Record a batch (master YAML)
npm run record -- all-howtosMIT
Built with Playwright, ElevenLabs & ffmpeg by Wolf+Strauss Solutions
