用途
Playwright 可以把浏览器交互录制为视频,适合作为 Remotion 合成中的演示素材。该技能覆盖从安装、编写录制脚本到输出视频并接入 Remotion 的完整流程。
安装
在视频项目中执行:
npm init -y
npm install -D playwright @playwright/test
npx playwright install chromium
基础录制脚本
创建 scripts/record-demo.ts:
import { chromium } from 'playwright';
async function recordDemo() {
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: {
dir: './recordings',
size: { width: 1920, height: 1080 }
}
});
const page = await context.newPage();
// Your recording actions
await page.goto('https://example.com');
await page.waitForTimeout(2000);
await page.click('button.demo');
await page.waitForTimeout(3000);
// Close to save video
await context.close();
await browser.close();
console.log('Recording saved to ./recordings/');
}
recordDemo();
运行方式:
npx ts-node scripts/record-demo.ts
# 或
npx tsx scripts/record-demo.ts
录制配置
视口尺寸可选:标准 1080p(推荐用于 Remotion){ width: 1920, height: 1080 };720p(文件更小){ width: 1280, height: 720 };方形(社交媒体){ width: 1080, height: 1080 };移动端 { width: 390, height: 844 }(iPhone 14)。视频尺寸建议与视口匹配以获得清晰输出。慢动作需在启动浏览器时设置 slowMo:
const browser = await chromium.launch({
slowMo: 100 // 100ms delay between actions
});
录制模式
资料给出了多种录制模式示例,均使用 recordVideo 输出到 ./recordings,尺寸为 1920x1080:
- 表单提交演示:
slowMo: 50,使用page.fill填写姓名、邮箱,点击提交并等待.success-message。 - 多页导航:
slowMo: 100,依次访问首页、功能页、定价页,使用waitForLoadState('networkidle')等待。 - 滚动演示:通过
page.evaluate循环window.scrollBy({ top: 200, behavior: 'smooth' })实现平滑滚动。 - 登录流程:
slowMo: 75,填写邮箱与密码,点击提交,使用page.waitForURL('**/dashboard')等待跳转。
光标高亮与点击涟漪
Playwright 默认不显示光标,可通过注入 CSS 和元素添加可视化指示。示例包括注入 .playwright-cursor 样式并在 page.evaluate 中监听 mousemove、mousedown、mouseup 更新光标位置与点击状态;以及通过 .click-ripple 样式和 clickWithRipple 函数在点击位置生成涟漪效果。
输出对接 Remotion
可将录制文件移动到 public/demos/:
import { chromium } from 'playwright';
import * as fs from 'fs';
import * as path from 'path';
async function recordForRemotion(outputName: string) {
const browser = await chromium.launch({ slowMo: 50 });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: { dir: './temp-recordings', size: { width: 1920, height: 1080 } }
});
const page = await context.newPage();
// ... recording actions ...
await context.close();
const video = page.video();
const videoPath = await video?.path();
if (videoPath) {
const destPath = `./public/demos/${outputName}.webm`;
fs.mkdirSync(path.dirname(destPath), { recursive: true });
fs.renameSync(videoPath, destPath);
console.log(`Recording saved to: ${destPath}`);
}
await browser.close();
}
获取时长可使用:ffprobe -v error -show_entries format=duration -of csv=p=0 file.webm。
Playwright 输出为 WebM,可转换为 MP4:
ffmpeg -i recording.webm -c:v libx264 -crf 20 -preset medium -movflags faststart public/demos/demo.mp4
交互式录制
对于由用户手动操作的录制,可注入 ESC 键监听停止录制:
async function injectStopListener(page: Page): Promise<void> {
await page.evaluate(() => {
if ((window as any).__escListenerAdded) return;
(window as any).__escListenerAdded = true;
(window as any).__stopRecording = false;
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.preventDefault();
(window as any).__stopRecording = true;
}
});
});
}
while (!stopped) {
try {
const shouldStop = await page.evaluate(() => (window as any).__stopRecording === true);
if (shouldStop) break;
} catch {
// Page navigating - continue recording
}
await new Promise(r => setTimeout(r, 200));
}
关键点:page.evaluate() 在页面导航时会抛出异常,应使用 try/catch 并继续,不应将错误视为停止信号。
笔记本电脑窗口缩放
在录制完整 1080p 的同时显示较小窗口:
const scale = 0.75; // 75% window size
const context = await browser.newContext({
viewport: { width: 1920 * scale, height: 1080 * scale },
deviceScaleFactor: 1 / scale,
recordVideo: { dir: './recordings', size: { width: 1920, height: 1080 } },
});
关闭 Cookie 弹窗
资料提供常见同意平台的综合选择器列表:
const COOKIE_SELECTORS = [
'#onetrust-accept-btn-handler', // OneTrust
'#CybotCookiebotDialogBodyButtonAccept', // Cookiebot
'.cc-btn.cc-dismiss', // Cookie Consent by Insites
'[class*="cookie"] button[class*="accept"]',
'[class*="consent"] button[class*="accept"]',
'button:has-text("Accept all")',
'button:has-text("Accept cookies")',
'button:has-text("Got it")',
];
async function dismissCookieBanners(page: Page): Promise<void> {
await page.waitForTimeout(500);
for (const selector of COOKIE_SELECTORS) {
try {
const btn = page.locator(selector).first();
if (await btn.isVisible({ timeout: 100 })) {
await btn.click({ timeout: 500 });
return;
}
} catch { /* try next */ }
}
}
应在 page.goto() 之后以及导航时的 page.on('load') 中调用。注意:注入的任何 DOM 元素(光标、控制面板、覆盖层)都会被录制进视频;若需无 UI 的录制,仅使用基于终端的控制(Ctrl+C、最大时长计时器)。
演示录制技巧
资料列出以下建议:使用 slowMo(50-100ms 使动作可见);添加 waitForTimeout(在动作之间暂停以便理解);等待动画(使用 waitForLoadState('networkidle'));匹配 Remotion 尺寸(通常 1920x1080、30fps);先不录制进行测试;使用全新 context 清理浏览器状态;关闭 Cookie 弹窗;导航后重新注入(光标/监听器在页面加载时重置)。
限制与说明
资料未提供该技能的具体收费、价格、平台发行信息。