Communitygithub.com

UI-Demo-Video-Recorder

Nehmt hochwertige UI-Demo-Videos mit Playwright auf. Verwendet es, wenn der Nutzer ein Demo-, Walkthrough-, Bildschirmaufnahme- oder Tutorial-Video einer Webanwendung erstellen möchte. Produziert WebM-Videos mit sichtbarem Mauszeiger, natürlichem Tempo und professionellem Gefühl.

Was ist UI-Demo-Video-Recorder?

Diese Fähigkeit ermöglicht es einem KI-Agenten (wie Claude Code, Cursor oder Codex), automatisch polierte Demo-Videos von Webanwendungen mit Playwright zu erstellen. Sie ist besonders nützlich für Entwickler und Tester, die Produktdemos, Onboarding-Videos, Dokumentationen oder Präsentationen für Stakeholder erstellen müssen. Die Fähigkeit folgt einem strukturierten Drei-Phasen-Prozess: Entdecken (Untersuchung der Zielseiten und Erstellung einer Feldkarte), Proben (Überprüfung aller Selektoren mit einem lauten Fehlermodus) und Aufnehmen (Erstellung des endgültigen Videos mit erzählerischem Flow, natürlichem Timing, einem eingeblendeten Mauszeiger-Overlay, Untertitel-Leiste und sanften Mausbewegungen). Sie funktioniert plattformunabhängig mit jedem Playwright-Setup (Node.js) und stellt sicher, dass Aufnahmen nicht durch stille Selektorfehler oder unvorhergesehene UI-Zustände beschädigt werden. Typische Anwendungsfälle sind Walkthroughs von Dashboards, Formular-Workflows und Funktionen-Demos, bei denen die visuelle Qualität entscheidend ist.

Funktioniert mit~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/ui-demo

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht UI-Demo-Video-Recorder?

Erstellt polierte Demo-Videos von Webanwendungen mit Playwrights Videoaufzeichnung, einem eingeblendeten Mauszeiger-Overlay, natürlichem Tempo und erzählerischem Flow.

Wann verwenden

  • Der Nutzer fragt nach einem "Demo-Video", "Bildschirmaufnahme", "Walkthrough" oder "Tutorial"
  • Der Nutzer möchte eine Funktion oder einen Arbeitsablauf visuell präsentieren
  • Der Nutzer benötigt ein Video für die Dokumentation, das Onboarding oder eine Präsentation für Stakeholder

Drei-Phasen-Prozess

Jedes Demo durchläuft drei Phasen: Entdecken -> Proben -> Aufnehmen. Nie direkt zur Aufnahme springen.


Phase 1: Entdecken

Bevor ein Skript geschrieben wird, die Zielseiten erkunden, um zu verstehen, was tatsächlich vorhanden ist.

Warum

Man kann kein Skript für etwas schreiben, das man nicht gesehen hat. Felder können <input> statt <textarea> sein, Dropdowns können benutzerdefinierte Komponenten statt <select> sein, und Kommentarfelder können @Erwähnungen oder #Tags unterstützen. Annahmen zerstören Aufnahmen stillschweigend.

Wie

Zu jeder Seite im Ablauf navigieren und deren interaktive Elemente ausgeben:

// Dies für jede Seite im Ablauf VOR dem Schreiben des Demo-Skripts ausführen
const fields = await page.evaluate(() => {
  const els = [];
  document.querySelectorAll('input, select, textarea, button, [contenteditable]').forEach(el => {
    if (el.offsetParent !== null) {
      els.push({
        tag: el.tagName,
        type: el.type || '',
        name: el.name || '',
        placeholder: el.placeholder || '',
        text: el.textContent?.trim().substring(0, 40) || '',
        contentEditable: el.contentEditable === 'true',
        role: el.getAttribute('role') || '',
      });
    }
  });
  return els;
});
console.log(JSON.stringify(fields, null, 2));

Worauf achten

  • Formularfelder: Sind es <select>, <input>, benutzerdefinierte Dropdowns oder Comboboxen?
  • Select-Optionen: Option-Werte UND -Text ausgeben. Platzhalter haben oft value="0" oder value="", was nicht leer aussieht. Array.from(el.options).map(o => ({ value: o.value, text: o.text })) verwenden. Optionen überspringen, bei deren Text "Auswählen" enthalten ist oder deren Wert "0" ist.
  • Rich-Text: Unterstützt das Kommentarfeld @Erwähnungen, #Tags, Markdown oder Emojis? Platzhaltertext prüfen.
  • Pflichtfelder: Welche Felder blockieren das Absenden des Formulars? required, * in Labels prüfen und versuchen, das Formular leer abzusenden, um Validierungsfehler zu sehen.
  • Dynamischer Inhalt: Erscheinen Felder erst, nachdem andere Felder ausgefüllt wurden?
  • Button-Labels: Exakter Text wie "Absenden", "Antrag absenden" oder "Senden".
  • Tabellen-Überschriften: Bei tabellengesteuerten Modalen jedes input[type="number"] seiner Spaltenüberschrift zuordnen, anstatt anzunehmen, dass alle numerischen Eingaben dasselbe bedeuten.

Ausgabe

Eine Feldkarte für jede Seite, die verwendet wird, um korrekte Selektoren im Skript zu schreiben. Beispiel:

/purchase-requests/new:
  - Budgetcode: <select> (erstes Select auf der Seite, 4 Optionen)
  - Gewünschte Lieferung: <input type="date">
  - Kontext: <textarea> (nicht input)
  - BOM-Tabelle: inline-editierbare Zellen mit span.cursor-pointer -> input-Muster
  - Absenden: <button> text="Absenden"

/purchase-requests/N (Detail):
  - Kommentar: <input placeholder="Nachricht eingeben..."> unterstützt @Benutzer und #PR-Tags
  - Senden: <button> text="Senden" (deaktiviert, bis das Eingabefeld Inhalt hat)

Phase 2: Proben

Alle Schritte ohne Aufzeichnung durchlaufen. Überprüfen, ob sich jeder Selektor auflösen lässt.

Warum

Stille Selektorfehler sind der Hauptgrund, warum Demo-Aufnahmen fehlschlagen. Das Proben fängt sie ab, bevor eine Aufnahme verschwendet wird.

Wie

ensureVisible verwenden, einen Wrapper, der protokolliert und laut fehlschlägt:

async function ensureVisible(page, locator, label) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    const msg = `PROBEN FEHLGESCHLAGEN: "${label}" nicht gefunden - Selektor: ${typeof locator === 'string' ? locator : '(locator-Objekt)'}`;
    console.error(msg);
    const found = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('button, input, select, textarea, a'))
        .filter(el => el.offsetParent !== null)
        .map(el => `${el.tagName}[${el.type || ''}] "${el.textContent?.trim().substring(0, 30)}"`)
        .join('\n  ');
    });
    console.error('  Sichtbare Elemente:\n  ' + found);
    return false;
  }
  console.log(`PROBEN OK: "${label}"`);
  return true;
}

Struktur des Proben-Skripts

const steps = [
  { label: 'E-Mail-Feld Login', selector: '#email' },
  { label: 'Login absenden', selector: 'button[type="submit"]' },
  { label: 'Neuen Antrag-Button', selector: 'button:has-text("Neuer Antrag")' },
  { label: 'Budgetcode-Auswahl', selector: 'select' },
  { label: 'Lieferdatum', selector: 'input[type="date"]:visible' },
  { label: 'Beschreibungsfeld', selector: 'textarea:visible' },
  { label: 'Element hinzufügen-Button', selector: 'button:has-text("Element hinzufügen")' },
  { label: 'Absenden-Button', selector: 'button:has-text("Absenden")' },
];

let allOk = true;
for (const step of steps) {
  if (!await ensureVisible(page, step.selector, step.label)) {
    allOk = false;
  }
}
if (!allOk) {
  console.error('PROBEN FEHLGESCHLAGEN - Selektoren vor der Aufnahme korrigieren');
  process.exit(1);
}
console.log('PROBEN BESTANDEN - alle Selektoren überprüft');

Wenn das Proben fehlschlägt

  1. Die Ausgabe der sichtbaren Elemente lesen.
  2. Den korrekten Selektor finden.
  3. Das Skript aktualisieren.
  4. Das Proben erneut ausführen.
  5. Nur fortfahren, wenn jeder Selektor bestanden ist.

Phase 3: Aufnehmen

Nur nachdem Entdecken und Proben bestanden sind, die Aufnahme erstellen.

Aufnahmeprinzipien

1. Erzählerischer Flow

Das Video als Geschichte planen. Vom Benutzer festgelegte Reihenfolge befolgen oder diese Standardreihenfolge verwenden:

  • Einstieg: Einloggen oder zum Startpunkt navigieren
  • Kontext: Die Umgebung schwenken, damit sich die Zuschauer orientieren können
  • Aktion: Die Haupt-Workflow-Schritte ausführen
  • Variation: Eine sekundäre Funktion wie Einstellungen, Design oder Lokalisierung zeigen
  • Ergebnis: Das Ergebnis, die Bestätigung oder den neuen Zustand zeigen

2. Tempo

  • Nach dem Login: 4s
  • Nach der Navigation: 3s
  • Nach einem Button-Klick: 2s
  • Zwischen großen Schritten: 1.5-2s
  • Nach der letzten Aktion: 3s
  • Tippverzögerung: 25-40ms pro Zeichen

3. Mauszeiger-Overlay

Einen SVG-Pfeil-Mauszeiger einfügen, der Mausbewegungen folgt:

async function injectCursor(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-cursor')) return;
    const cursor = document.createElement('div');
    cursor.id = 'demo-cursor';
    cursor.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M5 3L19 12L12 13L9 20L5 3Z" fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
    </svg>`;
    cursor.style.cssText = `
      position: fixed; z-index: 999999; pointer-events: none;
      width: 24px; height: 24px;
      transition: left 0.1s, top 0.1s;
      filter: drop-shadow(1px 1px 2px rgba(0,0,0,0.3));
    `;
    cursor.style.left = '0px';
    cursor.style.top = '0px';
    document.body.appendChild(cursor);
    document.addEventListener('mousemove', (e) => {
      cursor.style.left = e.clientX + 'px';
      cursor.style.top = e.clientY + 'px';
    });
  });
}

injectCursor(page) nach jeder Seitennavigation aufrufen, da das Overlay bei Navigation zerstört wird.

4. Mausbewegung

Den Mauszeiger niemals teleportieren. Vor dem Klicken zum Ziel bewegen:

async function moveAndClick(page, locator, label, opts = {}) {
  const { postClickDelay = 800, ...clickOpts } = opts;
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`WARNUNG: moveAndClick übersprungen - "${label}" nicht sichtbar`);
    return false;
  }
  try {
    await el.scrollIntoViewIfNeeded();
    await page.waitForTimeout(300);
    const box = await el.boundingBox();
    if (box) {
      await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 10 });
      await page.waitForTimeout(400);
    }
    await el.click(clickOpts);
  } catch (e) {
    console.error(`WARNUNG: moveAndClick fehlgeschlagen bei "${label}": ${e.message}`);
    return false;
  }
  await page.waitForTimeout(postClickDelay);
  return true;
}

Jeder Aufruf sollte ein beschreibendes label für die Fehlersuche enthalten.

5. Tippen

Sichtbar tippen, nicht sofort ausfüllen:

async function typeSlowly(page, locator, text, label, charDelay = 35) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`WARNUNG: typeSlowly übersprungen - "${label}" nicht sichtbar`);
    return false;
  }
  await moveAndClick(page, el, label);
  await el.fill('');
  await el.pressSequentially(text, { delay: charDelay });
  await page.waitForTimeout(500);
  return true;
}

6. Scrollen

Sanftes Scrollen anstelle von Sprüngen verwenden:

await page.evaluate(() => window.scrollTo({ top: 400, behavior: 'smooth' }));
await page.waitForTimeout(1500);

7. Dashboard-Schwenk

Beim Anzeigen eines Dashboards oder einer Übersichtsseite den Mauszeiger über die wichtigsten Elemente bewegen:

async function panElements(page, selector, maxCount = 6) {
  const elements = await page.locator(selector).all();
  for (let i = 0; i < Math.min(elements.length, maxCount); i++) {
    try {
      const box = await elements[i].boundingBox();
      if (box && box.y < 700) {
        await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 8 });
        await page.waitForTimeout(600);
      }
    } catch (e) {
      console.warn(`WARNUNG: panElements übersprungen Element ${i} (Selektor: "${selector}"): ${e.message}`);
    }
  }
}

8. Untertitel

Eine Untertitel-Leiste am unteren Rand des Viewports einfügen:

async function injectSubtitleBar(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-subtitle')) return;
    const bar = document.createElement('div');
    bar.id = 'demo-subtitle';
    bar.style.cssText = `
      position: fixed; bottom: 0; left: 0; right: 0; z-index: 999998;
      text-align: center; padding: 12px 24px;
      background: rgba(0, 0, 0, 0.75);
      color: white; font-family: -apple-system, "Segoe UI", sans-serif;
      font-size: 16px; font-weight: 500; letter-spacing: 0.3px;
      transition: opacity 0.3s;
      pointer-events: none;
    `;
    bar.textContent = '';
    bar.style.opacity = '0';
    document.body.appendChild(bar);
  });
}

async function showSubtitle(page, text) {
  await page.evaluate((t) => {
    const bar = document.getElementById('demo-subtitle');
    if (!bar) return;
    if (t) {
      bar.textContent = t;
      bar.style.opacity = '1';
    } else {
      bar.style.opacity = '0';
    }
  }, text);
  if (text) await page.waitForTimeout(800);
}

injectSubtitleBar(page) zusammen mit injectCursor(page) nach jeder Navigation aufrufen.

Verwendungsmuster:

await showSubtitle(page, 'Schritt 1 - Einloggen');
await showSubtitle(page, 'Schritt 2 - Dashboard-Übersicht');
await showSubtitle(page, '');

Richtlinien:

  • Untertiteltext kurz halten, idealerweise unter 60 Zeichen.
  • Das Format Schritt N - Aktion für Konsistenz verwenden.
  • Den Untertitel bei längeren Pausen löschen, in denen die UI für sich selbst sprechen kann.

Skript-Vorlage

'use strict';
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');

const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:3000';
const VIDEO_DIR = path.join(__dirname, 'screenshots');
const OUTPUT_NAME = 'demo-FEATURE.webm';
const REHEARSAL = process.argv.includes('--rehearse');

// Hier injectCursor, injectSubtitleBar, showSubtitle, moveAndClick,
// typeSlowly, ensureVisible und panElements einfügen.

(async () => {
  const browser = await chromium.launch({ headless: true });

  if (REHEARSAL) {
    const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
    const page = await context.newPage();
    // Durch den Ablauf navigieren und ensureVisible für jeden Selektor ausführen.
    await browser.close();
    return;
  }

  const context = await browser.newContext({
    recordVideo: { dir: VIDEO_DIR, size: { width: 1280, height: 720 } },
    viewport: { width: 1280, height: 720 }
  });
  const page = await context.newPage();

  try {
    await injectCursor(page);
    await injectSubtitleBar(page);

    await showSubtitle(page, 'Schritt 1 - Einloggen');
    // Login-Aktionen

    await page.goto(`${BASE_URL}/dashboard`);
    await injectCursor(page);
    await injectSubtitleBar(page);
    await showSubtitle(page, 'Schritt 2 - Dashboard-Übersicht');
    // Dashboard schwenken

    await showSubtitle(page, 'Schritt 3 - Haupt-Workflow');
    // Aktionssequenz

    await showSubtitle(page, 'Schritt 4 - Ergebnis');
    // Endgültige Enthüllung
    await showSubtitle(page, '');
  } catch (err) {
    console.error('DEMO-FEHLER:', err.message);
  } finally {
    await context.close();
    const video = page.video();
    if (video) {
      const src = await video.path();
      const dest = path.join(VIDEO_DIR, OUTPUT_NAME);
      try {
        fs.copyFileSync(src, dest);
        console.log('Video gespeichert:', dest);
      } catch (e) {
        console.error('FEHLER: Video konnte nicht kopiert werden:', e.message);
        console.error('  Quelle:', src);
        console.error('  Ziel:', dest);
      }
    }
    await browser.close();
  }
})();

Verwendung:

# Phase 2: Proben
node demo-script.cjs --rehearse

# Phase 3: Aufnehmen
node demo-script.cjs

Checkliste vor der Aufnahme

  • Entdeckungsphase abgeschlossen
  • Probe bestanden mit allen OK-Selektoren
  • Headless-Modus aktiviert
  • Auflösung auf 1280x720 eingestellt
  • Mauszeiger- und Untertitel-Overlays nach jeder Navigation neu eingefügt
  • showSubtitle(page, 'Schritt N - ...') bei wichtigen Übergängen verwendet
  • moveAndClick für alle Klicks mit beschreibenden Labels verwendet
  • typeSlowly für sichtbare Eingaben verwendet
  • Keine stillen Catches; ...

[zur Übersetzung gekürzt]

Hinweis: Der ursprüngliche englische Text wurde aus technischen Gründen gekürzt. Bitte die vollständigen Funktionsimplementierungen aus dem Original übernehmen.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Verwandte Skills