skills/playwright/SKILL.md
Take screenshots and interact with web pages using a headed Playwright browser. Use when you need to visually inspect a URL, capture a screenshot, interact with a page (click, type, scroll), or verify what a page looks like rendered. **Triggers — use this skill when:** - You need to screenshot a URL (Penpot share link, localhost, public site) - User says "show me", "how does it look", "take a screenshot" - You want to visually verify your own work (e.g. after creating Penpot designs) - User asks to review a live page or prototype - You need to interact with a page (click buttons, fill forms, trigger states)
npx skillsauth add espennilsen/pi playwrightInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use Playwright to open a real browser, navigate to URLs, take screenshots, and interact with pages. This is the go-to approach for visual verification of any web content — Penpot designs, local dev servers, production sites, etc.
Playwright is installed at /Users/espen/node_modules/playwright.
When writing scripts, always require from the absolute path:
const { chromium } = require('/Users/espen/node_modules/playwright');
Write a .cjs script to /tmp/, run it with node, then read the resulting PNG.
const { chromium } = require('/Users/espen/node_modules/playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(3000);
await page.screenshot({ path: '/tmp/screenshot.png' });
} finally {
await browser.close();
}
})();
node /tmp/screenshot.cjs
Then view it:
read /tmp/screenshot.png
headless: falseMany SPAs (Penpot, React apps, ClojureScript apps) fail or render blank in headless mode. Always launch headed:
chromium.launch({ headless: false })
.cjs)Write scripts as .cjs files with require(). ESM imports fail because
Playwright isn't in the local node_modules:
// ✅ Works
const { chromium } = require('/Users/espen/node_modules/playwright');
// ❌ Fails — "Cannot find package 'playwright'"
import { chromium } from 'playwright';
SPAs need time to hydrate after the initial HTML loads. Always add a wait:
| Site type | Wait time | |-----------|-----------| | Static HTML | 1-2 seconds | | Simple SPA (React, Svelte) | 3-4 seconds | | Complex SPA (Penpot, Figma) | 6-8 seconds |
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(6000); // adjust per site type
Use waitUntil: 'domcontentloaded' instead of 'networkidle' — network-idle
can time out on SPAs that keep WebSocket connections open.
Always save screenshots and scripts to /tmp/ to avoid polluting the workspace:
await page.screenshot({ path: '/tmp/my-screenshot.png' });
const context = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
const page = await context.newPage();
const { devices } = require('/Users/espen/node_modules/playwright');
const iPhone = devices['iPhone 15'];
const context = await browser.newContext({ ...iPhone });
| Use case | Width × Height | |----------|---------------| | Desktop (default) | 1400 × 1000 | | Mobile | 390 × 844 | | Tablet | 768 × 1024 | | Wide | 1920 × 1080 |
await page.click('button.submit');
await page.fill('input[name="email"]', '[email protected]');
await page.keyboard.press('Enter');
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.hover('.card');
await page.waitForTimeout(500);
await page.screenshot({ path: '/tmp/card-hover.png' });
await page.waitForSelector('.loaded-content', { timeout: 10000 });
Loop over multiple URLs and save each:
const pages_to_capture = [
{ url: 'https://example.com/', name: 'home' },
{ url: 'https://example.com/about', name: 'about' },
];
for (const p of pages_to_capture) {
await page.goto(p.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(3000);
await page.screenshot({ path: `/tmp/${p.name}.png` });
}
For pages that require authentication:
const context = await browser.newContext({ viewport: { width: 1400, height: 1000 } });
await context.addCookies([{
name: 'auth-token',
value: 'your-token-here',
domain: 'example.com',
path: '/',
}]);
const page = await context.newPage();
| Symptom | Cause | Fix |
|---------|-------|-----|
| "Internal Error" / blank page | Headless mode | Use headless: false |
| "Cannot find package" | ESM import or wrong cwd | Use absolute require() path, .cjs extension |
| Black / empty screenshot | SPA not rendered yet | Increase waitForTimeout |
| Timeout on goto | networkidle on WebSocket app | Switch to domcontentloaded |
| Wrong page content | Auth required | Set cookies or use share link |
| Blurry screenshots | Low DPR | Set deviceScaleFactor: 2 in context |
When a page isn't rendering as expected, capture console output:
const { chromium } = require('/Users/espen/node_modules/playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
try {
const page = await browser.newPage();
page.on('console', msg => {
if (msg.type() === 'error') console.log('CONSOLE ERROR:', msg.text());
});
page.on('pageerror', err => console.log('PAGE ERROR:', err.message));
await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(4000);
const title = await page.title();
console.log('Title:', title);
await page.screenshot({ path: '/tmp/debug.png' });
} finally {
await browser.close();
}
})();
tools
# pi-a2a Long-Running Tasks Skill ## Overview The pi-a2a extension supports **long-running tasks** that can execute for hours or days without timeouts. This is essential for: - Data processing pipelines - Batch operations - Research and aggregation tasks - External API jobs with unpredictable duration - Any A2A task that exceeds the standard timeout ## When to Use **Use long-running tasks when:** - Task execution time is unpredictable or known to exceed 10 minutes - The remote agent is proc
development
Orchestrate cmux terminal panes — split terminals, run parallel processes, read output from other panes, and use the built-in browser. Use when working inside cmux and you need to run a dev server, watch tests, spawn sub-agents, or preview web pages.
testing
Review UI designs and implementations for accessibility, consistency, usability, and visual quality. Use when asked to review a design, audit accessibility, check UI consistency, compare implementation against mockups, or evaluate a user interface.
tools
Create, review, and improve skills for Pi agents. A skill is a folder with a SKILL.md that teaches an agent specialized workflows, domain knowledge, or tool integrations. Use when asked to create a new skill, improve an existing skill, review a skill for quality, scaffold a skill from a workflow, or convert documentation into a skill. Also triggers on "make a skill for", "build a skill", "skill for [topic]", "teach the agent to", or "package this workflow as a skill".