Video Recording

How to Record Website Videos Programmatically in 2026

Published April 5, 2026 · 13 min read

Recording website videos — capturing page loads, animations, interactions, and scrolling as MP4 files — is increasingly useful for documentation, QA, marketing demos, and monitoring. This guide covers Playwright's built-in recording, custom ffmpeg pipelines, and SnapAPI's video endpoint that handles everything in one API call.

Playwright Built-in Video Recording

Playwright can record videos of browser sessions natively. Configure it per-context to capture everything that happens in the browser:

import { chromium } from 'playwright';

async function recordPageLoad(url, outputDir = './videos') {
  const browser = await chromium.launch();

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

  const page = await context.newPage();
  await page.goto(url, { waitUntil: 'networkidle' });

  // Wait for animations to complete
  await page.waitForTimeout(3000);

  // Scroll to capture full page content
  await page.evaluate(async () => {
    const delay = (ms) => new Promise(r => setTimeout(r, ms));
    for (let i = 0; i < document.body.scrollHeight; i += 200) {
      window.scrollTo(0, i);
      await delay(100);
    }
  });

  await page.waitForTimeout(1000);
  await context.close(); // Video is saved on context close

  const video = page.video();
  const path = await video.path();
  console.log(`Video saved: ${path}`);

  await browser.close();
  return path;
}

await recordPageLoad('https://example.com');

Recording User Interactions

Capture specific user flows — form submissions, navigation, feature demos — by scripting the interactions:

import { chromium } from 'playwright';

async function recordUserFlow(outputDir = './videos') {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    recordVideo: { dir: outputDir, size: { width: 1280, height: 720 } },
    viewport: { width: 1280, height: 720 },
  });

  const page = await context.newPage();

  // Step 1: Visit homepage
  await page.goto('https://yourapp.com');
  await page.waitForTimeout(2000);

  // Step 2: Click sign-up
  await page.getByRole('link', { name: 'Get Started' }).click();
  await page.waitForTimeout(1500);

  // Step 3: Fill registration form
  await page.getByLabel('Email').fill('demo@example.com');
  await page.waitForTimeout(500);
  await page.getByLabel('Password').fill('DemoPass123!');
  await page.waitForTimeout(500);

  // Step 4: Submit
  await page.getByRole('button', { name: 'Create Account' }).click();
  await page.waitForTimeout(3000);

  // Step 5: Show dashboard
  await expect(page).toHaveURL('/dashboard');
  await page.waitForTimeout(2000);

  await context.close();
  const videoPath = await page.video().path();
  console.log(`Flow recorded: ${videoPath}`);
  await browser.close();
}

Custom ffmpeg Pipeline

For advanced control over frame rate, encoding, and output format, combine Playwright screenshots with ffmpeg:

import { chromium } from 'playwright';
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

async function recordWithFrames(url, options = {}) {
  const {
    duration = 5000,
    fps = 30,
    width = 1280,
    height = 720,
    output = 'recording.mp4',
  } = options;

  const framesDir = '/tmp/frames';
  fs.mkdirSync(framesDir, { recursive: true });

  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width, height } });
  await page.goto(url, { waitUntil: 'networkidle' });

  // Capture frames
  const frameInterval = 1000 / fps;
  const totalFrames = Math.ceil(duration / frameInterval);

  for (let i = 0; i < totalFrames; i++) {
    const framePath = path.join(framesDir, `frame-${String(i).padStart(5, '0')}.png`);
    await page.screenshot({ path: framePath });
    await page.waitForTimeout(frameInterval);
  }

  await browser.close();

  // Encode with ffmpeg
  execSync(
    `ffmpeg -y -framerate ${fps} -i ${framesDir}/frame-%05d.png ` +
    `-c:v libx264 -pix_fmt yuv420p -crf 23 ${output}`
  );

  // Cleanup frames
  fs.rmSync(framesDir, { recursive: true });
  console.log(`Video encoded: ${output}`);
  return output;
}

SnapAPI Video API

SnapAPI's /v1/video endpoint records website videos without any browser or ffmpeg infrastructure. One API call captures page loads, animations, and scrolling as MP4:

import SnapAPI from 'snapapi-js';

const snap = new SnapAPI('sk_live_your_key');

// Record a page load
const result = await snap.video({
  url: 'https://yourapp.com',
  duration: 10,          // seconds
  width: 1280,
  height: 720,
  format: 'mp4',
  scroll: true,          // auto-scroll through page
  block_ads: true,
});

console.log(result.url); // CDN-hosted MP4 URL

// Record with device emulation
const mobileVideo = await snap.video({
  url: 'https://yourapp.com',
  duration: 8,
  device: 'iPhone 14 Pro',
  scroll: true,
});

Use Cases

Approach Comparison

ApproachOutputControlInfrastructureBest For
Playwright recordVideoWebMContext-levelBrowser serverTest artifacts
Screenshots + ffmpegMP4/GIF/WebMFull (frame-level)Browser + ffmpegCustom encoding
CDP screencastFrames (JPEG)Low-levelBrowser serverReal-time streaming
SnapAPI /v1/videoMP4API paramsNone (managed)Everything — zero setup

Record Website Videos — No Infrastructure Required

SnapAPI captures page loads, scrolling, and animations as MP4. Plus screenshots, scraping, PDFs, and AI analysis in the same API. Free tier: 200 captures/month.

Start Free — No Credit Card Required