skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
fellowship-dev/dogfooded-skills171 installs

playwright

Use when automating a browser with Playwright CLI over CDP — navigate, screenshot, fill forms, or extract data.

How do I install this agent skill?

npx skills add https://github.com/fellowship-dev/dogfooded-skills --skill playwright
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill facilitates browser automation using Playwright. It involves installing standard packages, executing shell commands for process management, and generating local JavaScript scripts. The primary security considerations involve the ingestion of untrusted web data and the dynamic execution of generated scripts.

  • Socketpass

    No alerts

  • Snykwarn

    Risk: MEDIUM · 1 issue

What does this agent skill do?

Playwright Web Automation

Browser automation via Chromium + playwright-core over CDP. No MCP server needed — scripts run as .mjs files.

Prerequisites

# Install playwright-core (no browser download — uses existing Chromium)
npm install playwright-core

# Verify Chromium exists
which chromium || ls /Applications/Chromium.app/Contents/MacOS/Chromium

Launch Chromium with CDP

# Kill any existing debug instance
pkill -f "remote-debugging-port=9222" 2>/dev/null

# Launch headless (cron/CI) or headed (interactive)
chromium --remote-debugging-port=9222 \
  --user-data-dir="/tmp/playwright-profile" \
  --disable-blink-features=AutomationControlled \
  --no-first-run --no-default-browser-check \
  --headless=new \
  "about:blank" > /tmp/chromium.log 2>&1 &

# macOS: use full path /Applications/Chromium.app/Contents/MacOS/Chromium
# Linux: chromium-browser or chromium

# Wait for CDP
sleep 3 && curl -sf http://localhost:9222/json/version > /dev/null && echo "CDP ready"

Connect via Playwright (ESM)

Write scripts as .mjs files:

// run: node script.mjs
import { chromium } from 'playwright-core';

const browser = await chromium.connectOverCDP('http://localhost:9222');
const ctx = browser.contexts()[0];
const page = ctx.pages()[0] || await ctx.newPage();

await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 15000 });
await page.waitForTimeout(2000);
await page.screenshot({ path: '/tmp/screenshot.png' });

await browser.close(); // disconnects, doesn't kill Chromium

IMPORTANT: Always use .mjs extension — playwright-core is ESM-only.

Key Patterns

Screenshot & Inspect

await page.screenshot({ path: '/tmp/screen.png' });
// Read /tmp/screen.png in Claude to see it visually

Fill Forms & Click

// Inspect inputs first
const inputs = await page.evaluate(() =>
  Array.from(document.querySelectorAll('input')).map(i => ({
    name: i.name, type: i.type, placeholder: i.placeholder
  }))
);
console.log(JSON.stringify(inputs, null, 2));

await page.fill('input[name="email"]', 'user@example.com');
await page.click('button:has-text("Submit")');
await page.waitForTimeout(2000);

Extract Page Content

const text = await page.evaluate(() => document.body.innerText);
const html = await page.content();
const title = await page.title();

Wait for Navigation

await Promise.all([
  page.waitForNavigation({ waitUntil: 'networkidle' }),
  page.click('button:has-text("Login")')
]);

SPA / Client-Side Rendered Data

SPAs may not hydrate fully in headless. Use data endpoints if available:

const data = await page.evaluate(async () => {
  const res = await fetch('/api/data', { credentials: 'include' });
  return res.json();
});

Multi-Page Flow

await page.goto('https://app.example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: 'networkidle' });
await page.screenshot({ path: '/tmp/after-login.png' });

// Continue to next page
await page.click('a:has-text("Dashboard")');
await page.waitForLoadState('networkidle');
await page.screenshot({ path: '/tmp/dashboard.png' });

Record Video

// Must create a NEW context for video recording
const ctx = await browser.newContext({
  recordVideo: { dir: '/tmp/videos/', size: { width: 1280, height: 720 } }
});
const page = await ctx.newPage();
await page.goto('https://example.com');
// ... do things ...
await ctx.close(); // video saved to /tmp/videos/*.webm

Console & Network Monitoring

page.on('console', msg => console.log(`[${msg.type()}] ${msg.text()}`));
page.on('response', res => {
  if (res.status() >= 400) console.log(`[${res.status()}] ${res.url()}`);
});

Selectors (prefer in this order)

  1. data-testid="foo" — page.click('[data-testid="foo"]')
  2. Role — page.getByRole('button', { name: 'Submit' })
  3. Text — page.click('button:has-text("Submit")')
  4. CSS — page.click('.submit-btn') (fragile, last resort)

Cleanup

pkill -f "remote-debugging-port=9222"

Troubleshooting

ProblemSolution
CDP not respondingWait longer, check curl http://localhost:9222/json/version
Port 9222 in usepkill -f "remote-debugging-port=9222" then relaunch
ESM import errorUse .mjs extension, not .js
Page not loadingIncrease timeout, use waitUntil: 'domcontentloaded'
Screenshots blankAdd page.waitForTimeout(2000) before capture
CSS not loadedUse waitUntil: 'networkidle' or wait for a visible element
Form submit failsCheck if element is visible: await el.isVisible()

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/fellowship-dev/dogfooded-skills/playwright">View playwright on skillZs</a>