agentsclimarketplace

Playwright automation

Skill bdmorin/.claude/skills/playwright-automation

My friend Brian's opinionated docker-dev-box with a focus on Claude Code workflows

Install
npx -y skills add bdmorin/.claude --skill playwright-automation

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Execute complex browser automation using Playwright Python. Use for video recording, multi-page navigation, data extraction. Triggers on "browser script", "record video of website", "extract data from webpage".

SKILL.md

7.0 KB, as published. Nobody here has run it

Playwright Automation Skill

Python-based browser automation for complex workflows requiring video recording, multi-page navigation, or data extraction.

When to Use

  • Video recording of browser interactions
  • Multi-page navigation sequences
  • Data extraction with post-processing
  • Form filling with validation
  • Screenshot sequences or comparisons
  • Any automation requiring Python logic

Output Directories

/workspace/.claude/.data/playwright/
├── screencaps/    # Screenshots (.png, .jpg)
├── videos/        # Recordings (.webm)
├── pdfs/          # Generated PDFs
├── traces/        # Debug traces (.zip)
└── data/          # Extracted data (.json, .csv)

Wait Strategy Guide

StrategyUse CaseSpeedReliability
domcontentloadedGeneral web pages (default)Fast (1-3s)High
loadMedia-heavy sites, SPAsMedium (2-5s)High
networkidleStatic sites that fully settleSlow (5-30s+)Variable

Recommendation: Use domcontentloaded (default) for most sites. Only use networkidle for static pages where you need all resources loaded.

Utility Scripts

Screenshot Utility

cd /workspace/.claude/skills/playwright-automation && \
uv run python scripts/screenshot.py <url> \
  [--full-page] \
  [--wait-until domcontentloaded|load|networkidle] \
  [--timeout 30000] \
  [--output filename.png]

Video Recorder

cd /workspace/.claude/skills/playwright-automation && \
uv run python scripts/video_recorder.py <url> \
  [--duration 10] \
  [--wait-until domcontentloaded|load|networkidle] \
  [--timeout 30000] \
  [--output filename.webm]

Script Template

Use this template for custom automation scripts:

#!/usr/bin/env python3
"""
Browser automation script: [DESCRIPTION]
Output: /workspace/.claude/.data/playwright/[TYPE]/[FILENAME]
"""
import os
from datetime import datetime
from loguru import logger
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError

SCREENCAP_DIR = "/workspace/.claude/.data/playwright/screencaps"
VIDEO_DIR = "/workspace/.claude/.data/playwright/videos"
DATA_DIR = "/workspace/.claude/.data/playwright/data"

os.makedirs(SCREENCAP_DIR, exist_ok=True)
os.makedirs(VIDEO_DIR, exist_ok=True)
os.makedirs(DATA_DIR, exist_ok=True)

def main():
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            viewport={"width": 1920, "height": 1080},
            record_video_dir=VIDEO_DIR,
            record_video_size={"width": 1280, "height": 720}
        )
        page = context.new_page()

        try:
            # Navigate with fallback
            page.goto("https://example.com", wait_until="domcontentloaded", timeout=30000)
            page.wait_for_timeout(2000)  # Allow dynamic content to load

            # Your automation code here
            page.screenshot(path=f"{SCREENCAP_DIR}/example_{timestamp}.png", full_page=True)

        except PlaywrightTimeoutError:
            logger.error("Navigation timeout")
            raise
        finally:
            # Get video path BEFORE closing page
            video = page.video
            video_path = video.path() if video else None

            page.close()
            if video_path:
                print(f"Video saved: {video_path}")
            context.close()
            browser.close()

if __name__ == "__main__":
    main()

Common Patterns

Screenshot Capture

SCREENCAP_DIR = "/workspace/.claude/.data/playwright/screencaps"

# Full page
page.screenshot(path=f"{SCREENCAP_DIR}/full.png", full_page=True)

# Viewport only
page.screenshot(path=f"{SCREENCAP_DIR}/viewport.png")

# Specific element
page.locator("#element").screenshot(path=f"{SCREENCAP_DIR}/element.png")

# With options
page.screenshot(
    path=f"{SCREENCAP_DIR}/custom.jpg",
    type="jpeg",
    quality=80,
    clip={"x": 0, "y": 0, "width": 800, "height": 600}
)

Video Recording

VIDEO_DIR = "/workspace/.claude/.data/playwright/videos"

# Enable in context creation
context = browser.new_context(
    record_video_dir=VIDEO_DIR,
    record_video_size={"width": 1280, "height": 720}
)
page = context.new_page()
# ... perform actions ...

# CRITICAL: Get path BEFORE closing
video = page.video
video_path = video.path() if video else None
page.close()  # Video finalized here

Form Interaction

page.fill("#username", "testuser")
page.fill("#password", "testpass")
page.click("button[type='submit']")
page.select_option("#country", "US")
page.check("#agree-terms")
page.set_input_files("#file-upload", "/path/to/file.pdf")

Data Extraction

import json

DATA_DIR = "/workspace/.claude/.data/playwright/data"

title = page.locator("h1").text_content()
items = page.locator(".item").all()
data = [item.text_content() for item in items]

with open(f"{DATA_DIR}/extracted.json", "w") as f:
    json.dump({"title": title, "items": data}, f, indent=2)

Wait Strategies

# Wait for specific element
page.wait_for_selector("#dynamic-content")

# Wait for URL change
page.wait_for_url("**/success")

# Wait for load state (use sparingly)
page.wait_for_load_state("networkidle")

# Custom timeout
page.wait_for_selector("#slow-element", timeout=60000)

# Fixed delay for dynamic content
page.wait_for_timeout(2000)

Error Handling with Fallback

from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

try:
    page.goto(url, wait_until="networkidle", timeout=30000)
except PlaywrightTimeoutError:
    # Fallback to faster strategy
    page.goto(url, wait_until="domcontentloaded", timeout=30000)

Tracing for Debug

DATA_DIR = "/workspace/.claude/.data/playwright/data"

context.tracing.start(screenshots=True, snapshots=True, sources=True)
# ... perform actions ...
context.tracing.stop(path=f"{DATA_DIR}/trace.zip")
# View with: playwright show-trace trace.zip

Execution

Run scripts using uv from the skill directory:

cd /workspace/.claude/skills/playwright-automation && uv run python scripts/<script>.py

Or for custom scripts in other locations:

cd /workspace/.claude/skills/playwright-automation && uv run python /path/to/custom_script.py

Troubleshooting

IssueSolution
Browser launch failsEnsure headless=True in container
Element not foundUse wait_for_selector() before interaction
Video not savedGet page.video.path() BEFORE page.close()
Permission deniedCheck output directory permissions
Timeout errorsUse domcontentloaded instead of networkidle
Streaming sites timeoutAdd page.wait_for_timeout(3000) after navigation

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.