Communitygithub.com

UIデモ動画レコーダー

Playwrightを使用して、洗練されたUIデモ動画を録画します。ユーザーがWebアプリケーションのデモ、チュートリアル、画面録画を求めるときに使用します。表示されるカーソル、自然なテンポ、プロフェッショナルな仕上がりのWebM動画を生成します。

UIデモ動画レコーダー とは?

このスキルは、Playwrightのビデオ録画機能と注入されたカーソルオーバーレイを使用して、プロフェッショナルなUIデモ動画を作成するためのものです。開発者は、Webアプリケーションのワークフローを、自然なテンポとストーリーテリングの流れで録画できます。このスキルは、発見、リハーサル、録画の3段階プロセスに従います。発見フェーズでは、対象ページのインタラクティブ要素を調査してセレクターの精度を確保します。リハーサルフェーズでは、すべてのセレクターが解決可能かを検証し、録画の失敗を防ぎます。録画フェーズでは、カスタムカーソル、スムーズなマウス移動、タイピング、スクロール、字幕バーを使用して、視聴者にとってわかりやすい動画を生成します。このスキルは、Claude Code、Cursor、CodexなどのAIエージェントが使用することを想定しており、Playwrightスクリプトのテンプレートとユーティリティ関数を提供します。

対応~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/ui-demo

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

UIデモ動画レコーダー は何をしますか?

Playwrightのビデオ録画機能と注入されたカーソルオーバーレイを使用して、Webアプリケーションの洗練されたデモ動画を録画します。自然なテンポとストーリーテリングの流れを実現します。

使用するタイミング

  • ユーザーが「デモ動画」「画面録画」「ウォークスルー」「チュートリアル」を依頼したとき
  • ユーザーが機能やワークフローを視覚的に紹介したいとき
  • ドキュメント、オンボーディング、またはステークホルダー向けのプレゼンテーション用動画が必要なとき

3フェーズプロセス

すべてのデモは、発見 -> リハーサル -> 録画 の3つのフェーズを経ます。決して録画に直接スキップしないでください。


フェーズ1: 発見

スクリプトを書く前に、対象のページを探索して実際に何があるかを理解します。

理由

見たことのないものをスクリプト化することはできません。フィールドは <input> ではなく <textarea> かもしれません。ドロップダウンは <select> ではなくカスタムコンポーネントかもしれません。コメントボックスは @メンション や #タグ をサポートしているかもしれません。仮定があると、録画は静かに失敗します。

方法

フロー内の各ページに移動し、そのインタラクティブ要素をダンプします:

// デモスクリプトを書く前に、フロー内の各ページでこれを実行します
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));

確認すべき点

  • フォームフィールド: <select>、<input>、カスタムドロップダウン、またはコンボボックスですか?
  • セレクトオプション: オプションの値とテキストを両方ダンプします。プレースホルダーは value="0" または value="" になることが多く、空でないように見えます。Array.from(el.options).map(o => ({ value: o.value, text: o.text })) を使用します。テキストに「Select」が含まれているか、値が "0" のオプションはスキップします。
  • リッチテキスト: コメントボックスは @メンション、#タグ、マークダウン、または絵文字をサポートしていますか?プレースホルダーテキストを確認します。
  • 必須フィールド: どのフィールドがフォーム送信をブロックしますか?required、ラベルの * を確認し、空の状態で送信を試みてバリデーションエラーを確認します。
  • 動的コンテンツ: 他のフィールドが入力された後にフィールドが表示されますか?
  • ボタンラベル: 「Submit」「Submit Request」「Send」などの正確なテキスト。
  • テーブルの列ヘッダー: テーブル駆動のモーダルの場合、各 input[type="number"] を列ヘッダーにマッピングします。すべての数値入力が同じ意味を持つと仮定しないでください。

出力

各ページのフィールドマップ。スクリプトで正しいセレクターを記述するために使用します。例:

/purchase-requests/new:
  - 予算コード: <select> (ページの最初のセレクト、4つのオプション)
  - 希望納期: <input type="date">
  - コンテキスト: <textarea> (inputではない)
  - BOMテーブル: インライン編集可能セル span.cursor-pointer -> inputパターン
  - 送信: <button> text="送信"

/purchase-requests/N (詳細):
  - コメント: <input placeholder="メッセージを入力..."> @user と #PR タグをサポート
  - 送信: <button> text="送信" (入力にコンテンツがあるまで無効)

フェーズ2: リハーサル

録画なしで全ステップを実行します。すべてのセレクターが解決可能かを検証します。

理由

セレクターのサイレント失敗は、デモ録画が壊れる主な原因です。リハーサルでそれをキャッチし、録画を無駄にせずに済みます。

方法

ログを出力して大声で失敗するラッパー ensureVisible を使用します:

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 = `リハーサル失敗: "${label}" が見つかりません - セレクター: ${typeof locator === 'string' ? locator : '(locator object)'}`;
    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('  表示されている要素:\n  ' + found);
    return false;
  }
  console.log(`リハーサルOK: "${label}"`);
  return true;
}

リハーサルスクリプトの構造

const steps = [
  { label: 'ログインメールフィールド', selector: '#email' },
  { label: 'ログイン送信ボタン', selector: 'button[type="submit"]' },
  { label: '新規リクエストボタン', selector: 'button:has-text("新規リクエスト")' },
  { label: '予算コードセレクト', selector: 'select' },
  { label: '納期日付', selector: 'input[type="date"]:visible' },
  { label: '説明フィールド', selector: 'textarea:visible' },
  { label: 'アイテム追加ボタン', selector: 'button:has-text("アイテムを追加")' },
  { label: '送信ボタン', selector: 'button:has-text("送信")' },
];

let allOk = true;
for (const step of steps) {
  if (!await ensureVisible(page, step.selector, step.label)) {
    allOk = false;
  }
}
if (!allOk) {
  console.error('リハーサル失敗 - 録画前にセレクターを修正してください');
  process.exit(1);
}
console.log('リハーサル成功 - すべてのセレクターが確認されました');

リハーサルが失敗した場合

  1. 表示要素のダンプを読みます。
  2. 正しいセレクターを見つけます。
  3. スクリプトを更新します。
  4. リハーサルを再実行します。
  5. すべてのセレクターが成功した場合のみ次に進みます。

フェーズ3: 録画

発見フェーズとリハーサルが成功した場合のみ、録画を作成します。

録画の原則

1. ストーリーテリングの流れ

動画をストーリーとして計画します。ユーザー指定の順序に従うか、またはこのデフォルトを使用します:

  • エントリー: ログインまたは開始点に移動
  • コンテキスト: 周囲を見渡して視聴者が方向を把握できるようにする
  • アクション: メインワークフローのステップを実行
  • バリエーション: 設定、テーマ、ローカリゼーションなどの二次機能を表示
  • 結果: 結果、確認、または新しい状態を表示

2. テンポ

  • ログイン後: 4s
  • ナビゲーション後: 3s
  • ボタンクリック後: 2s
  • 主要ステップ間: 1.5-2s
  • 最終アクション後: 3s
  • タイピング遅延: 1文字あたり 25-40ms

3. カーソルオーバーレイ

マウスの動きを追跡するSVG矢印カーソルを注入します:

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) をすべてのページ遷移後に呼び出します(オーバーレイは遷移時に破棄されるため)。

4. マウス移動

カーソルをテレポートさせないでください。クリックする前にターゲットに移動します:

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(`警告: moveAndClickがスキップされました - "${label}" が表示されていません`);
    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(`警告: moveAndClickが "${label}" で失敗しました: ${e.message}`);
    return false;
  }
  await page.waitForTimeout(postClickDelay);
  return true;
}

すべての呼び出しには、デバッグ用に説明的な label を含めてください。

5. タイピング

瞬間入力ではなく、視覚的にタイプします:

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(`警告: typeSlowlyがスキップされました - "${label}" が表示されていません`);
    return false;
  }
  await moveAndClick(page, el, label);
  await el.fill('');
  await el.pressSequentially(text, { delay: charDelay });
  await page.waitForTimeout(500);
  return true;
}

6. スクロール

ジャンプではなくスムーズスクロールを使用します:

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

7. ダッシュボードのパンニング

ダッシュボードや概要ページを表示するときは、キー要素にカーソルを移動します:

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(`警告: panElementsが要素 ${i} をスキップしました(セレクター: "${selector}"): ${e.message}`);
    }
  }
}

8. 字幕

ビューポートの下部に字幕バーを注入します:

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);
}

すべてのナビゲーション後に、injectCursor(page) と並行して injectSubtitleBar(page) を呼び出します。

使用パターン:

await showSubtitle(page, 'ステップ 1 - ログイン中');
await showSubtitle(page, 'ステップ 2 - ダッシュボード概要');
await showSubtitle(page, '');

ガイドライン:

  • 字幕テキストは短く、理想的には60文字未満にします。
  • 一貫性のために ステップ N - アクション 形式を使用します。
  • UIがそれ自体を説明できる長い一時停止中は字幕をクリアします。

スクリプトテンプレート

'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');

// ここに injectCursor、injectSubtitleBar、showSubtitle、moveAndClick、
// typeSlowly、ensureVisible、panElements を貼り付けます。

(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();
    // フローをナビゲートし、各セレクターに対して ensureVisible を実行します。
    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, 'ステップ 1 - ログイン中');
    // ログイン操作

    await page.goto(`${BASE_URL}/dashboard`);
    await injectCursor(page);
    await injectSubtitleBar(page);
    await showSubtitle(page, 'ステップ 2 - ダッシュボード概要');
    // ダッシュボードをパン

    await showSubtitle(page, 'ステップ 3 - メインワークフロー');
    // アクションシーケンス

    await showSubtitle(page, 'ステップ 4 - 結果');
    // 最終表示
    await showSubtitle(page, '');
  } catch (err) {
    console.error('デモエラー:', 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('動画を保存しました:', dest);
      } catch (e) {
        console.error('エラー: 動画のコピーに失敗しました:', e.message);
        console.error('  ソース:', src);
        console.error('  保存先:', dest);
      }
    }
    await browser.close();
  }
})();

使用方法:

# フェーズ2: リハーサル
node demo-script.cjs --rehearse

# フェーズ3: 録画
node demo-script.cjs

録画前のチェックリスト

  • 発見フェーズが完了
  • リハーサルがすべてのセレクターOKで成功
  • ヘッドレスモードが有効
  • 解像度が 1280x720 に設定されている
  • すべてのナビゲーション後にカーソルと字幕オーバーレイが再注入されている
  • 主要な遷移で showSubtitle(page, 'ステップ N - ...') が使用されている
  • すべてのクリックで説明的なラベル付き moveAndClick が使用されている
  • 表示入力に typeSlowly が使用されている
  • サイレントキャッチがないこと。

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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

関連スキル