ScreenshotNeo

BlogHow-to

How to Capture a Video from a URL with Browser Automation

Record a browser-rendered video with Playwright or download the source file. Learn timing, authentication, storage, troubleshooting, and automation choices.

By the ScreenshotNeo team1 October 20268 min read

How to Capture a Video from a URL with Browser Automation

There are two different ways to capture video from a URL:

  • Record the rendered browser session. Playwright opens the page, plays the video, and saves a recording of the viewport and interactions.
  • Download the underlying media file. If the page exposes a download action, Playwright waits for the download and saves the file delivered by the site.

Record the session when you need proof of playback, captions, controls, page layout, or authenticated interactions. Download the file when you need the original media bytes. A URL alone does not guarantee that a downloadable file exists: the page may stream media, require a gesture, use authentication, or expose a player without a download control.

Choose recording or downloading

Question Record the browser Download the file
What is captured? The rendered viewport, browser controls shown in the page, captions, and interactions The media file delivered by the site
Does playback need to work? Yes No, if a real download is available
What controls dimensions? Viewport and recording dimensions The source file’s format and dimensions
How is completion detected? Close the page or browser context, then read or save the video Wait for the download event and call saveAs
Authentication Uses the browser session, cookies, and page state Uses the session that initiated the download
A browser recording captures what is rendered; a download saves the file delivered by the site.
A browser recording captures what is rendered; a download saves the file delivered by the site.

Record a rendered video with Playwright

Playwright records a page when the browser context is created with recordVideo. The video is finalized when the page or context closes. Playwright’s documentation states that videos are saved upon browser context closure; always await context.close() before treating the artifact as complete (video guide, BrowserContext API).

JavaScript: complete runnable example

import { chromium } from 'playwright';

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

const page = await context.newPage();
try {
  await page.goto('https://example.com/video-page', {
    waitUntil: 'domcontentloaded',
    timeout: 60_000
  });

  // Complete consent, authentication, or other page setup here.
  // Example: await page.getByRole('button', { name: /accept/i }).click();

  // Start playback if the player requires a user gesture.
  // await page.getByRole('button', { name: /play/i }).click();

  await page.waitForTimeout(5_000);
} finally {
  // Closing the context finalizes the recording.
  await context.close();
  await browser.close();
}

Install the dependency and run the file with an ES module capable Node.js version:

npm install playwright
npx playwright install chromium
node record-video.js

Set the viewport deliberately when output dimensions matter. If recording dimensions are omitted, Playwright scales the output to fit its configured defaults. The recordVideo.size dimensions should match the viewport when you want a predictable 16:9 result.

Find or persist the recorded file

For a page associated with a recording, page.video() returns a video object. Its path is available after the page or context closes. video.saveAs() persists it to a path you choose and waits until the recording is complete.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({
  viewport: { width: 1280, height: 720 },
  recordVideo: { dir: 'videos/' }
});
const page = await context.newPage();
const video = page.video();

try {
  await page.goto('https://example.com/video-page');
  await page.waitForTimeout(5_000);
} finally {
  await context.close();
  await browser.close();
}

if (video) {
  console.log('Temporary path:', await video.path());
  await video.saveAs('output/video.webm');
}

Create the destination directory before calling saveAs. Do not read or upload the path before the context is closed.

Python: record a browser session

from pathlib import Path
from playwright.sync_api import sync_playwright

Path("videos").mkdir(exist_ok=True)

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(
        viewport={"width": 1280, "height": 720},
        record_video_dir="videos",
        record_video_size={"width": 1280, "height": 720},
    )
    page = context.new_page()
    page.goto("https://example.com/video-page", wait_until="domcontentloaded", timeout=60_000)
    # Perform consent, login, or a play click here.
    page.wait_for_timeout(5_000)
    context.close()  # finalizes the video
    browser.close()

Install Python Playwright and its browser binaries with:

pip install playwright
playwright install chromium

Download the underlying video file

Use this workflow only when the page has a real download control or another authorized download action. Start waiting for the event before clicking, then save the completed download to a durable path. Playwright deletes downloads associated with a browser context when that context closes (Download API).

JavaScript: wait for a download

import { chromium } from 'playwright';
import fs from 'node:fs/promises';

await fs.mkdir('downloads', { recursive: true });
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

try {
  await page.goto('https://example.com/video-page', {
    waitUntil: 'domcontentloaded',
    timeout: 60_000
  });

  const downloadPromise = page.waitForEvent('download');
  await page.getByRole('link', { name: /download/i }).click();
  const download = await downloadPromise;
  await download.saveAs('downloads/video.mp4');
  console.log('Saved:', await download.path());
} finally {
  await context.close();
  await browser.close();
}

Python: download event

from pathlib import Path
from playwright.sync_api import sync_playwright

Path("downloads").mkdir(exist_ok=True)

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(accept_downloads=True)
    page = context.new_page()
    page.goto("https://example.com/video-page", wait_until="domcontentloaded")
    with page.expect_download() as event:
        page.get_by_role("link", name="Download").click()
    download = event.value
    download.save_as("downloads/video.mp4")
    context.close()
    browser.close()

cURL: direct file URLs only

cURL is useful when you already have an authorized, direct media URL. It does not render a page, click a player, accept consent, or execute JavaScript.

curl -L --fail --retry 3 \
  -o video.mp4 \
  'https://example.com/path/video.mp4'

If the media requires a session, pass the appropriate cookie or authorization header only when you are permitted to access it:

curl -L --fail \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Cookie: session=YOUR_SESSION' \
  -o video.mp4 \
  'https://example.com/path/video.mp4'

Node.js: direct file download

const res = await fetch('https://example.com/path/video.mp4');
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('video.mp4', buffer));

Make recording reliable

Wait for the state you need

  • Use waitUntil: 'domcontentloaded' for initial navigation, then wait for the player or a page-specific selector.
  • Use a deliberate delay when you need a fixed playback interval.
  • For a known duration, wait for the video element’s duration or poll application state rather than guessing.
  • Close the context in a finally block so errors do not leave an incomplete recording.
await page.waitForSelector('video', { state: 'visible', timeout: 30_000 });
await page.locator('video').evaluate((el) => {
  if (el.paused) el.play();
});
await page.waitForTimeout(10_000);

Autoplay policies may block playback until a user gesture. Click the site’s play button or call play() only when the page permits it.

Complete consent before recording if the banner covers the player. For authenticated pages, establish the session in the same context, or load an authorized storage state. Never put credentials in source control or logs.

Control artifact retention

In Playwright Test, video recording is off by default. Supported modes include on, retain-on-failure, on-first-retry, and on-all-retries. Use a retention mode when recordings are evidence for failed tests but should not consume storage for every passing run.

// playwright.config.js
export default {
  use: {
    video: 'retain-on-failure'
  }
};

Performance, storage, and cost considerations

  • Recording adds video encoding work and writes an artifact for the whole session. Keep the viewport and recording duration to the minimum needed.
  • Downloading preserves the delivered media format and is usually the better choice when you need the source file rather than visual evidence.
  • Save downloads and recordings to durable storage before closing the context or worker.
  • Retries can multiply browser time and artifact size. Retain only the attempts needed for diagnosis.
  • There is no universal speed, success-rate, or cost benchmark for these workflows. Measure your own pages, authentication flow, playback duration, and storage policy.

Troubleshooting

Symptom Likely cause Fix
No video file appears The browser context was not closed Await context.close() before reading the path or uploading the file.
Recording is blank or short Navigation failed, playback never started, or the context closed too early Check the response and console, wait for the player, perform a play gesture, and keep the context open for the required interval.
Video has unexpected dimensions Viewport and recording size were not set deliberately Set both viewport and recordVideo.size.
Download event times out The control opens a player, uses JavaScript streaming, or is not a download Inspect the action. Use recording for rendered playback, or locate an authorized direct download endpoint.
Download disappears after the script exits The file was not saved before context closure Call download.saveAs() while the context is still open.
Playback is blocked Autoplay policy, consent, login, or bot protection Complete the required interaction in the browser context and verify the player state.
Authentication works in a normal browser but not automation Missing cookies, headers, storage state, or a required login step Perform login in the same context or load an authorized storage state; do not bypass access controls.
Capture fails intermittently Network delays, changing page state, or insufficient timeouts Wait for a stable selector, use bounded retries, record diagnostics, and set timeouts appropriate to the page.

Automating a page does not grant permission to copy or redistribute its video. Capture only content you are authorized to use, and respect the site’s terms, copyright, access controls, and applicable law. Playwright documents the automation mechanics; it does not provide a license to protected or DRM media.

Or skip the browser setup

If you need a clean image or PDF of the page surrounding a video rather than a video recording, ScreenshotNeo provides a single HTTP request. Its screenshot API captures PNG, JPEG, WebP, or PDF output. See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/video-page -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/video-page"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/video-page' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account to capture page images without setting up a browser.

FAQ

Can Playwright download any video shown in a player?

No. A player may stream segments, require authentication, use DRM, or provide no download action. Recording the rendered session is the appropriate workflow when you are authorized to capture what the browser displays.

Page cleanup steps can remove overlays before a static capture.
Page cleanup steps can remove overlays before a static capture.

When is a screenshot API appropriate?

Use one when you need a static image or PDF of a rendered page. It does not replace a video recorder when the output must contain motion and audio.

What must happen before I upload a recording?

Close the page or browser context, await the close operation, verify that the file exists and is complete, then upload it from durable storage.

How do I keep CI artifacts manageable?

Record only on failures or retries, use a fixed viewport, limit playback duration, and delete artifacts after the retention period required by your team.