Record a web UI demonstration with Playwright, capturing screenshots at one or more viewports and optionally a video clip, then return the asset paths. Reusable recording primitive used by verify-pr and validate-implementation.
Scanned 10/6/2026
npx -y skills add tomzx/agents --skill record-playwright --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Record Playwright?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/tomzx-record-playwright)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: record-playwright
description: Record a web UI demonstration with Playwright, capturing screenshots at one or more viewports and optionally a video clip, then return the asset paths. Reusable recording primitive used by verify-pr and validate-implementation.
allowed-tools: Bash(npx:*, npm:*, node:*, uv:*, python:*, python3:*, curl:*, ~/.agents/scripts/get-env:*), Read, Write, Glob
argument-hint: "[<slug>] [url]"
---
# Record Web UI Demo (Playwright)
Drive a real browser with Playwright to capture visual proof of a web UI change: screenshots at one or more viewports (desktop, mobile, etc.) and, optionally, a short video clip. Returns the asset paths so they can be embedded inline in a GitHub PR or comment.
This is the recording primitive for web UI changes. For CLI changes, use `/record-asciinema`.
## Prerequisites
- Playwright available one of:
- `npx playwright` (Node project with `@playwright/test`, or installed globally)
- `uv run playwright` / `python -m playwright` (Python project with `playwright`)
- Chromium browser installed for Playwright (`npx playwright install chromium`, or `playwright install chromium`). The skill installs it if missing.
- A reachable target URL. Either the caller provides one that is already running, or provides a server command the skill starts and tears down.
## Inputs
| Input | Meaning | Default |
|-------|---------|---------|
| `$RECORD_SLUG` (`$1`) | Filename slug, e.g. `login-page` | `web-demo` |
| `$RECORD_URL` (`$2`) | URL to capture, e.g. `http://localhost:3000/login` | `http://localhost:3000` |
| `$RECORD_DIR` | Output directory | `/tmp/record-playwright` |
| `$RECORD_VIEWPORTS` | Space-separated `WxH` specs | `1280x720 375x812` (desktop + mobile) |
| `$RECORD_VIDEO` | Set to any value to also record a video clip | *(unset; screenshots only)* |
| `$RECORD_SCENARIO` | Prose describing clicks/nav to perform before capture | *(none; capture the URL as-is)* |
| `$RECORD_SERVER_CMD` | Command to start the dev server (skill manages its lifecycle) | *(none; assume URL is already up)* |
## Workflow
```
Inputs resolved (slug, url, viewports, scenario, server cmd?)
|
v
Server command provided?
/ \
Yes No
| |
v v
Start server, Verify URL is reachable
wait for port (curl poll)
| |
+-------+-------+
|
v
Playwright available? (npx | uv/python)
/ \
Yes No
| |
v v
Write capture Stop. Signal caller to
script from fall back to a text
template + description of the change
scenario
|
v
Run capture script (screenshots + optional video)
|
v
Tear down server (if started)
|
v
Return asset paths (PNGs per viewport + video)
```
## Steps
### 1. Resolve inputs
```bash
RECORD_SLUG="${1:-web-demo}"
RECORD_URL="${RECORD_URL:-http://localhost:3000}"
RECORD_DIR="${RECORD_DIR:-/tmp/record-playwright}"
RECORD_VIEWPORTS="${RECORD_VIEWPORTS:-1280x720 375x812}"
mkdir -p "$RECORD_DIR"
```
### 2. Start the dev server if a command was given
If `$RECORD_SERVER_CMD` is set, start it in the background and poll the URL until it responds:
```bash
if [ -n "$RECORD_SERVER_CMD" ]; then
sh -c "$RECORD_SERVER_CMD" &
SERVER_PID=$!
for i in $(seq 1 60); do
curl -sf "$RECORD_URL" >/dev/null 2>&1 && break
sleep 1
done
fi
```
If no server command is given, verify the URL is already reachable:
```bash
curl -sf "$RECORD_URL" >/dev/null 2>&1 || { echo "URL not reachable: $RECORD_URL"; exit 0; }
```
If unreachable and no server command was provided, stop and signal the caller. Do not error out.
### 3. Ensure Playwright is available
Detect the project's Playwright runtime:
```bash
if [ -f package.json ] && grep -q '"@playwright/test"' package.json 2>/dev/null; then
PW="npx playwright"
elif command -v npx >/dev/null 2>&1; then
PW="npx playwright"
elif command -v uv >/dev/null 2>&1 && [ -f pyproject.toml ]; then
PW="uv run playwright"
elif command -v python3 >/dev/null 2>&1; then
PW="python3 -m playwright"
else
echo "Playwright not available"; exit 0
fi
```
Install Chromium if it is missing:
```bash
$PW install chromium >/dev/null 2>&1 || true
```
### 4. Write the capture script
Write a self-contained Playwright script to `$RECORD_DIR/capture-${RECORD_SLUG}.mjs` (Node) or `.py` (Python). Translate `$RECORD_SCENARIO` into concrete Playwright actions (clicks, navigation, form fills) before capture.
Node template (`capture.mjs`):
```javascript
import { chromium } from 'playwright';
const url = process.env.RECORD_URL;
const dir = process.env.RECORD_DIR;
const slug = process.env.RECORD_SLUG;
const viewports = process.env.RECORD_VIEWPORTS.split(' ');
const wantVideo = !!process.env.RECORD_VIDEO;
const browser = await chromium.launch();
for (const spec of viewports) {
const [w, h] = spec.split('x').map(Number);
const context = await browser.newContext({
viewport: { width: w, height: h },
recordVideo: wantVideo ? { dir } : undefined,
});
const page = await context.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
// >>> Translate $RECORD_SCENARIO into actions here, e.g.:
// await page.click('#submit');
// await page.waitForTimeout(500);
await page.screenshot({ path: `${dir}/${slug}-${w}x${h}.png`, fullPage: true });
await context.close(); // flushes the video if one was being recorded
}
await browser.close();
```
Python template (`capture.py`):
```python
import os
from playwright.sync_api import sync_playwright
url = os.environ["RECORD_URL"]
dir_ = os.environ["RECORD_DIR"]
slug = os.environ["RECORD_SLUG"]
viewports = os.environ["RECORD_VIEWPORTS"].split()
want_video = bool(os.environ.get("RECORD_VIDEO"))
with sync_playwright() as p:
browser = p.chromium.launch()
for spec in viewports:
w, h = (int(x) for x in spec.split("x"))
context = browser.new_context(
viewport={"width": w, "height": h},
record_video_dir=dir_ if want_video else None,
)
page = context.new_page()
page.goto(url, wait_until="networkidle")
# >>> Translate $RECORD_SCENARIO into actions here.
page.screenshot(path=f"{dir_}/{slug}-{w}x{h}.png", full_page=True)
context.close()
browser.close()
```
### 5. Run the capture script
```bash
export RECORD_URL RECORD_DIR RECORD_SLUG RECORD_VIEWPORTS RECORD_VIDEO
if [[ "$PW" == npx* ]]; then
node "$RECORD_DIR/capture-${RECORD_SLUG}.mjs"
else
$PW install-deps chromium >/dev/null 2>&1 || true
python3 "$RECORD_DIR/capture-${RECORD_SLUG}.py"
fi
```
### 6. Tear down the server
```bash
if [ -n "$RECORD_SERVER_CMD" ] && [ -n "$SERVER_PID" ]; then
kill "$SERVER_PID" 2>/dev/null || true
fi
```
### 7. Return the asset paths
Report the captured assets so the caller can upload and embed them:
- Screenshots: `$RECORD_DIR/${RECORD_SLUG}-<W>x<H>.png` (one per viewport)
- Video (if `$RECORD_VIDEO` set): `$RECORD_DIR/*.webm` (Playwright writes a timestamped name under `$RECORD_DIR`)
## Failure Modes
| Mode | Response |
|------|----------|
| **Playwright not available** | Stop cleanly; signal caller to fall back to a text description of the UI change |
| **Chromium not installed and install fails** | Stop cleanly; signal fallback |
| **URL not reachable and no server command** | Stop cleanly; signal caller to start the server first |
| **Server fails to come up within the poll window** | Stop, report the URL, signal fallback |
| **Capture script throws (selector not found, etc.)** | Re-run with a looser scenario or screenshot the error state; report what happened |
| **No screenshots produced** | Report the error; signal fallback to text |
## Example Usage
**Scenario 1: Static screenshots against a running dev server (called by verify-pr)**
```
/record-playwright login-form http://localhost:5173/login
```
Capture desktop (1280x720) and mobile (375x812) PNGs of the login page, return their paths.
**Scenario 2: Scenario-driven capture with a video**
```
RECORD_VIDEO=1 RECORD_SCENARIO="fill #email with test@example.com, click #submit, wait for .dashboard" \
/record-playwright onboarding http://localhost:3000/onboarding
```
Writes a capture script that performs the steps, takes a full-page screenshot per viewport, and records a video clip.
**Scenario 3: Start the server, capture, tear down (called by validate-implementation)**
```
RECORD_SERVER_CMD="npm run dev" RECORD_VIEWPORTS="1280x720" \
/record-playwright new-dashboard http://localhost:3000/dashboard
```
Starts `npm run dev`, waits for the port, captures, kills the server, returns the PNG.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!