Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Playwright Demo Videos

ASecurity

Use when recording a polished UI demo/walkthrough video of a web app with Playwright. Covers cursor and subtitle overlay injection, moveAndClick/typeSlowly helpers, pacing values, video output handling, and recording gotchas.

2 stars
0 votes
0 copies
0 views
Added 9/19/2026
ai-agentsjavascriptgojava

Works with

cursorcli

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add Mixard/fable-pack --skill playwright-demo-videos --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Playwright Demo Videos?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Playwright Demo Videos
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mixard-playwright-demo-videos/badge)](https://www.skillsdirectory.com/skills/mixard-playwright-demo-videos)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: playwright-demo-videos
description: Use when recording a polished UI demo/walkthrough video of a web app with Playwright. Covers cursor and subtitle overlay injection, moveAndClick/typeSlowly helpers, pacing values, video output handling, and recording gotchas.
---

# Playwright Demo Videos

Playwright's `recordVideo` context option captures WebM video, but raw automation looks robotic: no visible cursor, instant fills, teleporting clicks. These helpers fix that.

## Recording Setup

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

Playwright writes the video to a random filename inside `dir`; copy it to a stable name after `context.close()`:

```javascript
await context.close();
const video = page.video();
if (video) {
  fs.copyFileSync(await video.path(), path.join(VIDEO_DIR, 'demo-feature.webm'));
}
```

Popup windows record to separate video files; capture popup pages explicitly and merge afterwards if needed.

## Cursor Overlay

Headless recordings have no visible cursor. Inject an SVG arrow that follows `mousemove`:

```javascript
async function injectCursor(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-cursor')) return;
    const cursor = document.createElement('div');
    cursor.id = 'demo-cursor';
    cursor.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M5 3L19 12L12 13L9 20L5 3Z" fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
    </svg>`;
    cursor.style.cssText = `
      position: fixed; z-index: 999999; pointer-events: none;
      width: 24px; height: 24px;
      transition: left 0.1s, top 0.1s;
      filter: drop-shadow(1px 1px 2px rgba(0,0,0,0.3));
    `;
    cursor.style.left = '0px';
    cursor.style.top = '0px';
    document.body.appendChild(cursor);
    document.addEventListener('mousemove', (e) => {
      cursor.style.left = e.clientX + 'px';
      cursor.style.top = e.clientY + 'px';
    });
  });
}
```

The overlay lives in the DOM, so navigation destroys it - re-inject after every `page.goto` (same for the subtitle bar below).

## Click and Type Helpers

Move the mouse to the target with intermediate steps before clicking; never teleport:

```javascript
async function moveAndClick(page, locator, label, opts = {}) {
  const { postClickDelay = 800, ...clickOpts } = opts;
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`WARNING: moveAndClick skipped - "${label}" not visible`);
    return false;
  }
  try {
    await el.scrollIntoViewIfNeeded();
    await page.waitForTimeout(300);
    const box = await el.boundingBox();
    if (box) {
      await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 10 });
      await page.waitForTimeout(400);
    }
    await el.click(clickOpts);
  } catch (e) {
    console.error(`WARNING: moveAndClick failed on "${label}": ${e.message}`);
    return false;
  }
  await page.waitForTimeout(postClickDelay);
  return true;
}

async function typeSlowly(page, locator, text, label, charDelay = 35) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`WARNING: typeSlowly skipped - "${label}" not visible`);
    return false;
  }
  await moveAndClick(page, el, label);
  await el.fill('');
  await el.pressSequentially(text, { delay: charDelay });
  await page.waitForTimeout(500);
  return true;
}
```

Pass a descriptive `label` on every call so failures are traceable in the log; the helpers warn instead of throwing so one broken selector does not abort the recording, but silent `catch {}` blocks are what make recordings fail mysteriously.

## Pacing

Values that feel natural to a human viewer:

| Moment | Pause |
|--------|-------|
| After login | 4 s |
| After navigation | 3 s |
| After clicking a button | 2 s |
| Between major steps | 1.5-2 s |
| After the final action | 3 s |
| Typing delay | 25-40 ms per character |

Smooth scrolling instead of jumps:

```javascript
await page.evaluate(() => window.scrollTo({ top: 400, behavior: 'smooth' }));
await page.waitForTimeout(1500);
```

## Subtitle Bar

```javascript
async function injectSubtitleBar(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-subtitle')) return;
    const bar = document.createElement('div');
    bar.id = 'demo-subtitle';
    bar.style.cssText = `
      position: fixed; bottom: 0; left: 0; right: 0; z-index: 999998;
      text-align: center; padding: 12px 24px;
      background: rgba(0, 0, 0, 0.75);
      color: white; font-family: -apple-system, "Segoe UI", sans-serif;
      font-size: 16px; font-weight: 500; letter-spacing: 0.3px;
      transition: opacity 0.3s; pointer-events: none;
    `;
    bar.style.opacity = '0';
    document.body.appendChild(bar);
  });
}

async function showSubtitle(page, text) {
  await page.evaluate((t) => {
    const bar = document.getElementById('demo-subtitle');
    if (!bar) return;
    bar.textContent = t || '';
    bar.style.opacity = t ? '1' : '0';
  }, text);
  if (text) await page.waitForTimeout(800);
}
```

Keep subtitles under 60 characters, `Step N - Action` format; clear with `showSubtitle(page, '')` when the UI speaks for itself.

## Gotchas

- Overlays (cursor, subtitle bar) vanish on navigation - re-inject after every `goto`.
- The recorded file has a random name; copy it via `page.video().path()` to a stable output.
- Native `<select>` placeholder options often have `value="0"` or `value=""` and text like "Select..."; dump `Array.from(el.options).map(o => ({ value: o.value, text: o.text }))` and skip placeholders before picking.
- Field types differ from assumptions: inputs vs textareas, custom dropdowns vs `<select>`, contenteditable comment boxes with `@mention`/`#tag` support. Dump visible `input, select, textarea, button, [contenteditable]` elements per page before scripting selectors.
- In table-driven forms, map each `input[type="number"]` to its column header instead of assuming all numeric inputs mean the same thing.
- Modals feel abrupt without a read pause before confirming.
- Send buttons are often disabled until the input has content - type first, then click.

Attribution

MixardMixard
View sourceMore from Mixard →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.

1023331 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

686011 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3331 votes

catchup

Recovers prior coding-agent session context by running `catchup <agent> --since-compact`, which extracts a clean summary of a previous Codex, Claude Code, Antigravity, OpenCode, or Pi Agent session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", or asks to recover/summarize a previous session before continuing. Do NOT use for the current conversation, git history, or any non-agent log.

611 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →