---
Generates browser-based demo recordings using Playwright scripts in Kubernetes Jobs.
/plugin marketplace add estsauver/demo-creator/plugin install estsauver-demo-creator@estsauver/demo-creatorThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Screenenv is a Playwright-based tool that runs in Kubernetes Jobs to record browser-based demos. This skill explains how to use it within the demo-creator pipeline.
Screenenv is a headless browser recording tool from HuggingFace that:
Important: Screenenv is NOT an MCP server. You don't call it directly via tools. Instead, you write Playwright scripts that screenenv will execute.
┌─────────────────────────────────────────────────────────────┐
│ Phase 1: Script Development (detailed-script agent) │
│ - Agent writes Playwright Python script │
│ - Uses domain knowledge of the app │
│ - Tools: Read, Write, Grep, Bash │
└─────────────────┬───────────────────────────────────────────┘
│
▼
script.py saved to .demo/{demo_id}/
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 2: Recording (record-demo agent) │
│ - Uses ScreenenvJobManager Python utility │
│ - Creates Helm release with screenenv-job chart │
│ - Passes script.py to K8s Job │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 3: Execution (Kubernetes Job) │
│ - Screenenv runs in k8s Pod │
│ - Executes Playwright script in headless Chrome │
│ - Records screen at 1920x1080 │
│ - Saves video to PVC │
└─────────────────────────────────────────────────────────────┘
When developing demo scripts (in the detailed-script agent), write standard Playwright Python code:
"""
Demo Script: {Feature Name}
Generated by: demo-creator detailed-script agent
"""
from playwright.sync_api import sync_playwright
import time
def run_demo(page):
"""Execute the demo script."""
# Scene 1: Navigate to Feature
print("Scene 1: Navigate to Feature")
page.goto("http://localhost:3000/drugs")
page.wait_for_load_state("networkidle")
time.sleep(2)
# Verify page loaded
assert page.locator('input[placeholder*="Search"]').is_visible()
page.screenshot(path="scene_1.png")
# Scene 2: Interact with UI
print("Scene 2: Apply Filters")
page.click('button:has-text("Filter")')
time.sleep(0.5)
page.fill('input[name="search"]', "EGFR")
time.sleep(0.4)
page.click('button[type="submit"]')
time.sleep(1.5)
page.screenshot(path="scene_2.png")
def setup():
"""Run setup commands before demo (optional)."""
import subprocess
# Example: Seed test data
subprocess.run([
"kubectl", "exec", "-n", "your-namespace",
"deployment/backend", "--",
"python", "scripts/seed_demo_data.py"
], check=True)
def teardown():
"""Run cleanup commands after demo (optional)."""
import subprocess
subprocess.run([
"kubectl", "exec", "-n", "your-namespace",
"deployment/backend", "--",
"python", "scripts/cleanup_demo_data.py"
], check=True)
def main():
"""Main entry point - screenenv executes this."""
setup()
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
record_video_dir="./recordings",
record_video_size={"width": 1920, "height": 1080}
)
page = context.new_page()
try:
run_demo(page)
finally:
context.close()
browser.close()
teardown()
if __name__ == "__main__":
main()
Navigation:
page.goto("http://localhost:3000/path")
page.wait_for_load_state("networkidle")
page.wait_for_url("**/drugs")
Clicking:
page.click('button:has-text("Submit")')
page.locator('.submit-btn').click()
page.click('button[aria-label="Close"]')
Typing:
page.fill('input[name="search"]', "text")
page.type('input', "text", delay=100) # With typing delay
Assertions:
assert page.locator('.result').is_visible()
page.wait_for_selector('.result', timeout=5000)
Screenshots:
page.screenshot(path="scene_1.png")
page.locator('.specific-element').screenshot(path="element.png")
Waits:
time.sleep(2) # Explicit wait (use for timing)
page.wait_for_timeout(2000) # Playwright wait
page.wait_for_selector('.element') # Wait for element
page.click('button:has-text("Submit")')page.fill('input[placeholder="Search"]', text)button[aria-label="Close"][data-testid="submit-btn"]The record-demo agent uses the Python utility to manage K8s Jobs:
import sys
sys.path.append("plugins/demo-creator")
from utils.screenenv_job import ScreenenvJobManager, create_and_run_recording
from utils.manifest import Manifest
# Load manifest
manifest = Manifest("{demo_id}")
manifest.load()
# Get script path
script_path = manifest.get_file_path("script.py")
# Option 1: Convenience function (recommended)
result = create_and_run_recording(
demo_id=manifest.demo_id,
script_url=f"http://demo-script-server/scripts/{manifest.demo_id}.py",
output_path=manifest.get_file_path("raw_recording.mp4"),
target_url="http://localhost:3000",
cleanup=True
)
# Option 2: Manual control
manager = ScreenenvJobManager(
namespace="infra",
helm_chart_path="k8s/infra/charts/screenenv-job",
context="k3d-local"
)
# Create job
job_result = manager.create_job(
demo_id=manifest.demo_id,
script_url=f"http://demo-script-server/scripts/{manifest.demo_id}.py",
target_url="http://localhost:3000",
resolution="1920x1080",
frame_rate="30"
)
# Wait for completion
wait_result = manager.wait_for_completion(
demo_id=manifest.demo_id,
poll_interval=5,
max_wait=600
)
# Retrieve recording
if wait_result["status"] == "completed":
success = manager.retrieve_recording(
demo_id=manifest.demo_id,
output_path=manifest.get_file_path("raw_recording.mp4")
)
if success:
print("✅ Recording retrieved")
# Cleanup
manager.cleanup_job(manifest.demo_id)
The screenenv Helm chart is at k8s/infra/charts/screenenv-job/.
| Parameter | Default | Description |
|---|---|---|
demoId | - | Unique demo identifier |
scriptUrl | - | URL to Playwright script |
targetUrl | http://localhost:3000 | App base URL |
resolution | 1920x1080 | Video resolution |
frameRate | 30 | Video frame rate |
image.repository | ghcr.io/huggingface/screenenv | Docker image |
/recordings/{demo_id}/raw_recording.mp4kubectl cp to extract videokubectl get job screenenv-{demo_id} -n infra --context k3d-local
kubectl logs job/screenenv-{demo_id} -n infra --context k3d-local
# Get pod name
POD=$(kubectl get pods -n infra -l job-name=screenenv-{demo_id} \
--context k3d-local -o jsonpath='{.items[0].metadata.name}')
# Check file exists
kubectl exec -n infra $POD --context k3d-local -- \
ls -lh /recordings/{demo_id}/
The scriptUrl must be accessible from inside the K8s cluster. For local development:
print() statementskubectl exec ... -- curl http://localhost:3000--wait --timeout 10mpage.wait_for_selector() that never resolvesheadless=False to verify selectorsassert to catch UI changes that break the scriptmanager.cleanup_job() after retrieval# 1. Load context
import sys
sys.path.append("plugins/demo-creator")
from utils.manifest import Manifest
manifest = Manifest("{demo_id}")
manifest.load()
# Read outline
with open(manifest.get_file_path("outline.md")) as f:
outline = f.read()
# 2. Write Playwright script based on outline
# (Use domain knowledge of the app, not screenenv MCP calls)
script_content = """
from playwright.sync_api import sync_playwright
import time
def run_demo(page):
# Based on outline, write the actual interactions
page.goto("http://localhost:3000/drugs")
# ... etc
"""
# 3. Save script
with open(manifest.get_file_path("script.py"), "w") as f:
f.write(script_content)
# 4. Update manifest
manifest.complete_stage(2, {
"script_path": "script.py",
"estimated_duration_seconds": 30
})
print("✅ Stage 2 complete: Playwright script created")
.mcp.json for screenenv in pluginsWhen to use this skill:
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.