pi-heartbeat
Configure periodic health checks via HEARTBEAT.md and the /heartbeat command.
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
$ npx -y skills add espennilsen/pi --skill playwright --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/playwrightContext preview
The summary Claude sees to decide when to auto-load this skill.
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
name: playwright description: > 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)
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
Many SPAs (Penpot, React apps, ClojureScript apps) fail or render blank in headless mode. Always launch headed:
chromium.launch({ headless: false })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 typeUse `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"]', 'test@example.com');
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 EThis repository is my Pi coding agent home directory. I keep it under version control and symlink it to ~/.pi/agent so Pi loads configuration, extensions, skills, and runtime state from here.
Configure periodic health checks via HEARTBEAT.md and the /heartbeat command.
Manage persistent memory across sessions. Use when asked to remember, recall, forget, or review what was stored. Covers long-term…
Track and manage projects with git status via dashboard and tool.
Delegate tasks to specialized subagents for parallel or sequential execution.
Audit, generate, update, and lint AGENTS.md files across all projects. Use when asked to check project context files, scaffold…