데모 비디오 녹화하기 (무료, 스크립트 기반)
실제 앱의 무음 제품 데모 비디오를 브라우저를 스크립팅하고 mp4로 렌더링하여 만듭니다. 무료이며 로컬에서 작동합니다: Playwright가 앱을 구동하고 ffmpeg가 비디오를 만듭니다. 유료 SaaS 없고, 나레이션 없고, 사람이 클릭할 필요 없습니다. 동일한 스크립트는 UX 스모크 테스트로도 사용할 수 있으며, UI가 변경되면 다시 렌더링합니다. 이것이 바로 여러 기능 데모를 지속 가능하게 제작하는 방법입니다.
사용 시기
- 사용자가 하나 또는 여러 기능 데모/워크스루 비디오를 원하지만 도구에 비용을 지불하고 싶지 않을 때.
- 매번 새로운 기능마다 누군가가 화면을 녹화해야 하는 반복적인 문제.
- 실제 UI에서 기능이 처음부터 끝까지 작동한다는 빠른 시각적 증명이 필요할 때.
다음에는 적합하지 않음: 음성 해설이 포함된 애니메이션이 많은 마케팅 비디오(실제 편집기 사용) 또는 카메라 앞에서 반응하는 사람이 필요한 모든 것.
구조
로그인 세션 발급 → Playwright로 앱 구동 (.webm 녹화) → ffmpeg → .mp4
세 개의 파일이 대상 리포지토리의 demo/ 아래에 생성됩니다 (resources/에서 복사):
record.mjs— 엔진. CONFIG 블록(리포지토리별) + FLOWS 맵(비디오당 하나의 항목).polish.sh—.webm→.mp4, 로그인 진입 부분 자르기, 선택적 캡션 삽입.run.sh— 하나의 명령: 개발 서버를 스테이징으로 설정하고, 녹화하고, 다듬고, 정리.README.md— 리포지토리별 사용법.
새 비디오를 만들려면: FLOWS 항목을 복사하고 url + steps를 변경하세요. 이것이 전체 과정입니다.
설정 체크리스트 (리포지토리당 한 번 수행)
- 도구 설치 (한 번):
npm install -D @playwright/test && npx playwright install chromium.ffmpeg필요 (Homebrew). 자막 삽입을 위해서는libfreetype으로 빌드된 ffmpeg가 필요합니다; 그렇지 않으면 자막은 자동으로 건너뛰고 다른 모든 기능은 정상 작동합니다. - 리포지토리의 세부 사항 파악 및
record.mjs의 CONFIG 블록 채우기:- 개발 명령어 — 앱이 로컬에서 실행되는 방식 (
npm run dev, 종종congrats/와 같은 하위 디렉토리에서). - Supabase 클라이언트 모듈 경로 — 일반적으로
src/integrations/supabase/client.ts. 녹화기가 페이지 내에서 로그인하기 위해 이를 가져옵니다 (Gotcha 1 참조). - 녹화할 DB — 스테이징 사용, 프로덕션 절대 사용 금지 (실제 사용자 PII 없음). URL + 익명/공개 키 + 서비스 키 + 직접 DB URL을 찾으세요. 이 조직에서는 오디션 리포지토리의
.env.local에 있습니다 (VETTED_SUPABASE_*는 VettedAI 스테이징용,STAGING_CONGRATS_SUPABASE_*는 Congrats 스테이징용). - 로그인 이메일 — 데모 가능한 데이터를 소유한 합성/테스트 계정. 세션을 발급하기 전에 항상 사용자에게 어떤 계정인지 물어보세요 (프로젝트 규칙: 허가 없이 실제 사용자 테스트 금지).
- 경로 — 녹화할 플로우의 딥 링크 경로.
- 개발 명령어 — 앱이 로컬에서 실행되는 방식 (
- 로컬 개발 서버를 스테이징으로 설정 (Gotcha 2 참조).
- 플로우 작성,
bash demo/run.sh <flow>실행, 프레임 추출하여 확인 (Gotcha 8 참조).
주의 사항 (어렵게 얻은 교훈 — 디버깅 전에 읽어보세요)
1. 앱 자체 클라이언트를 통해 로그인하세요, localStorage를 위조하지 마세요
Supabase-js는 브라우저에서 기본이 아닌 키/엔벨로프 아래에 세션을 기록합니다. 수동으로 localStorage['sb-<ref>-auth-token'] = JSON.stringify(session)를 주입하면 올바르게 보이지만 클라이언트가 읽지 못해 → 앱이 /login으로 리디렉션됩니다. 대신: 서버 측에서 세션을 발급하고 (admin generateLink → verifyOtp로 access_token+refresh_token 제공, 비밀번호 불필요), 페이지 내에서 앱의 Supabase 클라이언트를 import하여 supabase.auth.setSession({access_token, refresh_token})을 호출하세요. 그러면 올바른 저장소가 기록되고 세션이 내비게이션 간 유지됩니다. 이것이 가장 중요한 단계입니다.
2. --mode staging이 자동으로 무시될 수 있음 → .env.development.local 사용
vite-react-ssg dev (및 일부 설정)은 env 파일 로딩에 --mode를 무시하므로 .env.staging이 절대 로드되지 않아 앱이 계속 프로덕션을 가리킵니다 (프로덕션 Supabase 호스트에 요청이 가는 것을 볼 수 있습니다). Vite는 항상 개발 모드에서 .env/.env.local보다 높은 우선순위로 .env.development.local을 자동 로드합니다. 따라서 run.sh는 녹화를 위해 스테이징 구성을 .env.development.local에 복사한 다음, 종료 시 삭제합니다 (그렇지 않으면 이후 모든 npm run dev가 자동으로 스테이징을 가리키게 되어 문제가 발생합니다). 일반 vite 리포지토리는 --mode를 존중할 수 있습니다; 앱이 호출하는 Supabase 호스트를 확인하여 테스트하세요.
3. 스테이징 스키마가 프론트엔드보다 뒤처질 수 있음 → 대상별 추가 조정
스테이징 브랜치에는 프론트엔드가 선택하는 최신 컬럼이 없을 수 있습니다 → 데이터 쿼리가 400 오류 (column X does not exist)를 반환하고 화면이 빈 상태로 렌더링됩니다. 마이그레이션을 재실행하지 마세요. 화면이 읽는 몇 개 테이블의 컬럼을 비교하고 (스테이징 대 프로덕션, information_schema.columns 통해), 누락된 컬럼만 추가적이고 nullable로 ALTER TABLE ... ADD COLUMN IF NOT EXISTS를 적용하세요. 사용자의 승인을 받으세요 — 스테이징 DB 변경입니다.
4. 시드 데이터의 스케일이 잘못될 수 있음
합성 시드 점수/값이 UI 포맷터가 예상하는 범위와 일치하지 않을 수 있습니다 (예: 점수가 0–100으로 저장되었는데 UI가 value/5*100을 수행하여 "1392%"가 표시됨). 포맷터의 예상 범위를 프로덕션의 실제 값과 비교하세요; 필요한 경우 시드 데이터의 스케일을 조정하세요 (먼저 물어보세요 — 데이터 변경입니다).
5. Playwright 비디오에 마우스 커서가 표시되지 않음
addInitScript를 통해 가짜 커서 오버레이를 주입하세요 (마우스 이동을 따라가고 마우스 다운 시 커지는 점). 템플릿이 이미 이 작업을 수행합니다. 클릭하기 전에 마우스를 실제 좌표로 이동시켜 추적되도록 하세요.
6. ffmpeg에 drawtext가 없을 수 있음
libfreetype 없이 빌드된 경우 자막 삽입 시 오류가 발생합니다. polish.sh는 drawtext를 감지하고 누락된 경우 캡션을 건너뜁니다 — 캡션 실패로 mp4 생성이 차단되지 않도록 하세요.
7. 로그인/부팅 진입 부분 자동 자르기
녹화는 로그인 + 앱 부팅 (불필요한 초)을 캡처합니다. record.mjs는 진입 부분을 측정하고 .trim 사이드카 파일을 작성합니다; polish.sh가 이를 잘라냅니다 (ffmpeg -ss).
8. 프레임으로 확인하고, "녹화되었다"는 말로 확인하지 마세요
ffmpeg -ss <t> -i out.mp4 -frames:v 1 frame.png를 여러 타임스탬프에서 실행하고 실제로 확인하세요. 처음 실행 시 /login, 스켈레톤 UI, 또는 잘못된 탭에 멈출 수 있습니다 — 프레임이 이를 잡아내는 방법입니다.
9. 데이터 및 인증 안전
합성 데이터(예: seed_…@test.vetted.com)를 사용하여 스테이징에서 녹화하고, 절대 실제 고객 프로젝트(PII)를 사용하지 마세요. 항상 사용자에게 어떤 계정으로 로그인할지 물어보세요. 인증 세션 발급은 실제 작업이므로 먼저 확인하세요.
플로우 작성하기
record.mjs에서 FLOWS는 { label, url, steps(page, ui) }의 맵입니다. ui 헬퍼가 대부분의 요구를 처리합니다:
ui.settle(what, [words])— 콘텐츠가 로드될 때까지 대기 (networkidle + 화면이 로드되었음을 증명하는 단어)ui.scrollThrough(fraction, steps)— 부드럽게 아래로 스크롤했다가 다시 위로ui.clickFirstCandidate()/ui.clickFirst(selectors)로 일반화 — 최선 시도 클릭, 절대 하드 실패하지 않음ui.beat(ms)— 비디오가 잘 읽히도록 의도적인 일시 정지
플로우는 선택자를 가볍게 유지하고 최선 시도 방식으로 (try/catch 클릭) 유지하여, 선택자를 놓쳐도 "여전히 괜찮은 스크롤 데모"로 저하되고 충돌하지 않도록 하세요. 간격을 넉넉하게 유지하세요 (800–1800ms 비트); 좋은 무음 데모는 15–35초입니다.
출력 및 전달
mp4는 demo/output/에 저장됩니다 (gitignore 처리됨). open demo/output/<flow>.mp4로 재생하세요 (상대 마크다운 링크는 IDE에서 재생되지 않습니다 — 파일이 편집기에서 열립니다). 경로를 공유하거나 사용자를 위해 open 명령을 실행하세요.
이 스킬 전파하기 (skill-master에 있음)
이 스킬은 submodules/skill-master/.agent/skills/recording-demo-videos/에 표준으로 있으며 각 리포지토리의 .agent/skills/에 심볼릭 링크됩니다. 편집 후에는 먼저 서브모듈을 커밋하고 푸시한 다음, 부모 리포지토리 포인터를 업데이트하고, /distribute-skill을 실행하여 형제 리포지토리 전체에 반영하세요 (푸시 전에 사용자에게 물어보세요). skill-master-submodule-sync 규칙을 참조하세요.