SuperAPI AI 工具集

playwright-recording

测试与质量

查看项目

更新于 10-13 20:08

技能介绍

用途

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 弹窗;导航后重新注入(光标/监听器在页面加载时重置)。

限制与说明

资料未提供该技能的具体收费、价格、平台发行信息。