Skill: mahone-bot/playwright-npx (version 1.0.0)

--- name: playwright-npx description: Fast browser automation using Node.js scripts with Playwright (run via `node script.mjs`). Use for web scraping, screenshots, form automation, and any browser task requiring programmatic control. For simple page fetching without JavaScript execution, use web_fetch first. For interactive CLI browsing without writing code, use browser tool or playwright-cli. This skill is ideal when you need full control, custom logic, or reusable scripts. metadata: {"clawdbot":{"emoji":"🎭","requires":{"bins":["node","npx"]}}, "created_by": "Kuba + Mahone", "created_date": "2026-02-04", "is_custom": true} --- # Playwright Browser Automation > 🤝 Developed together by Kuba + Mahone · Feb 2026 Code-first browser automation with Playwright. ## When to Use | Tool | Use When | |------|----------| | **web_fetch** | Simple pages, no JavaScript needed | | **This skill** | JavaScript-heavy sites, complex interactions, full control | | **stealth-browser** | Bot detection / Cloudflare issues | | **browser tool** | Visual exploration, last resort | | **playwright-cli** | Interactive CLI without writing code | ## Setup ```bash # One-time per project npm init -y npm install playwright npx playwright install chromium ``` **package.json example:** ```json { "name": "my-automation", "type": "module", "dependencies": { "playwright": "^1.40.0" } } ``` ## Minimal Example ```javascript // tmp/example.mjs import { chromium } from 'playwright'; const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); console.log('Title:', await page.title()); await browser.close(); ``` ```bash node tmp/example.mjs ``` ## Quick Patterns ### Screenshot ```javascript import { chromium } from 'playwright'; const browser = await chromium.launch(); const page = await browser.newPage(); await page.setViewportSize({ width: 1280, height: 800 }); await page.goto('https://example.com'); await page.screenshot({ path: 'tmp/screenshot.png', fullPage: true }); await browser.close(); ``` ### Scrape Data ```javascript import { chromium } from 'playwright'; const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('https://news.ycombinator.com'); const stories = await page.$$eval('.titleline > a', links => links.slice(0, 5).map(a => ({ title: a.innerText, url: a.href })) ); console.log(JSON.stringify(stories, null, 2)); await browser.close(); ``` ### Form Interaction ```javascript await page.goto('https://example.com/login'); await page.fill('input[name="email"]', '[REDACTED_SECRET]'); await page.fill('input[name="password"]', 'password'); await page.click('button[type="submit"]'); ``` ### Wait for Dynamic Content ```javascript // Wait for network idle (SPA) await page.goto(url, { waitUntil: 'networkidle' }); // Wait for specific element await page.waitForSelector('.results', { timeout: 10000 }); // Wait for condition await page.waitForFunction(() => document.querySelectorAll('.item').length > 0 ); ``` ### Persistent Session ```javascript import fs from 'fs'; const SESSION_FILE = 'tmp/session.json'; let context; if (fs.existsSync(SESSION_FILE)) { context = await browser.newContext({ storageState: SESSION_FILE }); } else { context = await browser.newContext(); } const page = await context.newPage(); // ... login ... await context.storageState({ path: SESSION_FILE }); ``` ## Headless vs Headed ```javascript // Headless (default, fastest) await chromium.launch({ headless: true }); // Headed (see the browser) await chromium.launch({ headless: false }); // Slow motion (debugging) await chromium.launch({ headless: false, slowMo: 100 }); ``` ## Selectors Quick Reference ```javascript // CSS await page.click('button.submit'); await page.fill('input#email', 'text'); // Text content await page.click('text=Submit'); await page.click('text=/log\s*in/i'); // regex // XPath await page.click('xpath=//button[@type="submit"]'); // ARIA role await page.click('role=button[name="Submit"]'); // Test ID (most stable) await page.click('[data-testid="submit-btn"]'); // Chain selectors await page.click('nav >> text=Settings'); ``` **See [references/selectors.md](references/selectors.md) for complete selector guide.** ## Error Handling ```javascript try { await page.goto('https://example.com', { timeout: 30000 }); const hasResults = await page.locator('.results').isVisible().catch(() => false); if (!hasResults) { console.log('No results'); process.exit(0); } } catch (error) { console.error('Error:', error.message); await page.screenshot({ path: 'tmp/error.png' }); process.exit(1); } finally { await browser.close(); } ``` ## Examples & Templates ### Working Examples - [examples/screenshot.mjs](examples/screenshot.mjs) - Full-page screenshots - [examples/scrape.mjs](examples/scrape.mjs) - Data extraction - [examples/form-interaction.mjs](examples/form-interaction.mjs) - Form automation - [examples/login-session.mjs](examples/login-session.mjs) - Persistent sessions ### Reusable Templates - [scripts/minimal-template.mjs](scripts/minimal-template.mjs) - Clean starting point - [scripts/screenshot-template.mjs](scripts/screenshot-template.mjs) - Configurable screenshot - [scripts/scrape-template.mjs](scripts/scrape-template.mjs) - Data scraping scaffold **Copy templates:** ```bash cp scripts/minimal-template.mjs tmp/my-task.mjs # Edit tmp/my-task.mjs, then run: node tmp/my-task.mjs ``` ## Tooling Commands ```bash # Record interactions to generate code npx playwright codegen https://example.com # Debug selectors npx playwright codegen --target javascript https://example.com # Show trace npx playwright show-trace tmp/trace.zip ``` ## Deep References - **[references/selectors.md](references/selectors.md)** - Complete selector guide (CSS, text, XPath, ARIA, test-id) - **[references/debugging.md](references/debugging.md)** - Debugging techniques (headless, slowMo, screenshots) - **[references/troubleshooting.md](references/troubleshooting.md)** - Common errors and solutions ## Tips - Always put scripts in `tmp/` — it's gitignored - Use `.mjs` extension for ES modules (no `type: module` needed) - Add `console.log()` liberally for debugging - Use `page.screenshot()` when things go wrong - For complex sites, add `await page.waitForLoadState('networkidle')` - See [references/debugging.md](references/debugging.md) for detailed debugging guide - See [references/troubleshooting.md](references/troubleshooting.md) for common issues

path: _meta.json
content: createdBy: Kuba + Mahone
createdDate: 2026-02-04
isCustom: yes
repository: https://github.com/mahone-bot/playwright-npx
notes: Fast browser automation using Node.js scripts with Playwright. Published to GitHub, pending ClawHub publish (account age restriction).

path: examples/form-interaction.mjs
content: import { chromium } from 'playwright';

/**
 * Form Interaction Example
 * Demonstrates filling and submitting a form.
 * 
 * Usage: node examples/form-interaction.mjs
 * Note: This example uses httpbin.org for demonstration.
 */

const url = 'https://httpbin.org/forms/post';

const browser = await chromium.launch({ headless: false }); // Show browser
const page = await browser.newPage();

console.log('Opening form page...');
await page.goto(url, { waitUntil: 'networkidle' });

// Fill form fields
console.log('Filling form...');
await page.fill('input[name="custname"]', 'John Doe');
await page.fill('input[name="custtel"]', '555-0123');
await page.fill('input[name="custemail"]', '[REDACTED_SECRET]');

// Select dropdown
await page.selectOption('select[name="size"]', 'large');

// Check radio button
await page.check('input[value="bacon"]');

// Check multiple checkboxes
await page.check('input[name="topping"][value="cheese"]');
await page.check('input[name="topping"][value="mushroom"]');

// Add delivery instructions
await page.fill('textarea[name="comments"]', 'Please deliver to side door');

console.log('Form filled. Submitting...');

// Submit form and wait for navigation
await Promise.all([
  page.waitForNavigation(),
  page.click('button[type="submit"]')
]);

console.log(`Form submitted! Current URL: ${page.url()}`);
console.log('Taking screenshot of result...');
await page.screenshot({ path: 'tmp/form-result.png' });

// Keep browser open briefly to see result
await page.waitForTimeout(3000);
await browser.close();
console.log('Done!');


path: examples/login-session.mjs
content: import { chromium } from 'playwright';
import fs from 'fs';

/**
 * Login Session Example
 * Demonstrates persistent sessions - login once, reuse session.
 * 
 * Usage: node examples/login-session.mjs
 * 
 * This example uses GitHub login page structure but won't actually
 * log in (no credentials). Replace with your actual login flow.
 */

const SESSION_FILE = 'tmp/session.json';
const TARGET_URL = 'https://github.com';

const browser = await chromium.launch({ headless: false });
let context;
let page;

// Check for existing session
if (fs.existsSync(SESSION_FILE)) {
  console.log('Loading saved session...');
  context = await browser.newContext({
    storageState: SESSION_FILE
  });
  page = await context.newPage();
  
  // Navigate to target - should already be logged in
  await page.goto(TARGET_URL);
  console.log('Session restored!');
  
  // Check if we're logged in (look for avatar or username indicator)
  const isLoggedIn = await page.locator('[data-testid="header-menu-button"]').isVisible().catch(() => false);
  
  if (isLoggedIn) {
    console.log('Successfully logged in via saved session');
  } else {
    console.log('Session expired or invalid. Please log in manually.');
    // Delete invalid session
    fs.unlinkSync(SESSION_FILE);
  }
} else {
  console.log('No saved session found. Creating new session...');
  context = await browser.newContext();
  page = await context.newPage();
  
  await page.goto(`${TARGET_URL}/login`);
  
  console.log('\n=== MANUAL LOGIN REQUIRED ===');
  console.log('Please log in manually in the browser window.');
  console.log('After logging in, press Enter in this terminal to save the session.\n');
  
  // Wait for user input
  process.stdin.once('data', async () => {
    // Save session after login
    await context.storageState({ path: SESSION_FILE });
    console.log(`Session saved to ${SESSION_FILE}`);
    
    await browser.close();
    process.exit(0);
  });
  
  // Keep running until user presses Enter
  await new Promise(() => {});
}

// If we got here with a valid session, take a screenshot
if (page) {
  await page.screenshot({ path: 'tmp/session-test.png' });
  console.log('Screenshot saved to tmp/session-test.png');
}

await browser.close();


path: examples/scrape.mjs
content: import { chromium } from 'playwright';

/**
 * Web Scraping Example
 * Extracts article titles and links from Hacker News.
 * 
 * Usage: node examples/scrape.mjs [url]
 */

const url = process.argv[2] || 'https://news.ycombinator.com';

const browser = await chromium.launch();
const page = await browser.newPage();

console.log(`Scraping ${url}...`);
await page.goto(url, { waitUntil: 'networkidle' });

// Extract data using page.$$eval
const stories = await page.$$eval('.titleline > a', links =>
  links.slice(0, 10).map(a => ({
    title: a.innerText.trim(),
    url: a.href
  }))
);

console.log(`\nFound ${stories.length} stories:\n`);
console.log(JSON.stringify(stories, null, 2));

await browser.close();


path: examples/screenshot.mjs
content: import { chromium } from 'playwright';

/**
 * Screenshot Example
 * Takes a full-page screenshot of a website.
 * 
 * Usage: node examples/screenshot.mjs <url> [output-path]
 */

const url = process.argv[2] || 'https://example.com';
const outputPath = process.argv[3] || 'tmp/screenshot.png';

const browser = await chromium.launch();
const page = await browser.newPage();

// Set viewport for consistent screenshots
await page.setViewportSize({ width: 1280, height: 800 });

console.log(`Navigating to ${url}...`);
await page.goto(url, { waitUntil: 'networkidle' });

// Wait a moment for any final renders
await page.waitForTimeout(500);

// Take screenshot
await page.screenshot({ path: outputPath, fullPage: true });
console.log(`Screenshot saved to ${outputPath}`);

await browser.close();


path: references/debugging.md
content: # Debugging Reference

Techniques for debugging Playwright scripts.

## Headless vs Headed Mode

### Headless (Default)

Faster, no UI - use for production runs.

```javascript
const browser = await chromium.launch({ headless: true });
```

### Headed (Visual)

See the browser window - essential for debugging.

```javascript
const browser = await chromium.launch({ headless: false });
```

### Slow Motion

Add delays between actions to observe behavior.

```javascript
const browser = await chromium.launch({
  headless: false,
  slowMo: 100  // 100ms delay between actions
});
```

## Visual Debugging

### Screenshots on Error

```javascript
try {
  await page.click('.submit-button');
} catch (error) {
  await page.screenshot({ path: 'tmp/error-screenshot.png' });
  throw error;
}
```

### Full Page Screenshots

```javascript
await page.screenshot({ path: 'tmp/page.png', fullPage: true });
```

### Element Screenshots

```javascript
await page.locator('.chart').screenshot({ path: 'tmp/chart.png' });
```

### Before/After Screenshots

```javascript
await page.screenshot({ path: 'tmp/before.png' });
await page.click('.expand-btn');
await page.waitForTimeout(500);
await page.screenshot({ path: 'tmp/after.png' });
```

## Console Logging

### Browser Console Messages

```javascript
page.on('console', msg => {
  console.log(`[${msg.type()}] ${msg.text()}`);
});

page.on('pageerror', error => {
  console.error('Page error:', error.message);
});
```

### Network Request Logging

```javascript
page.on('request', request => {
  console.log(`>> ${request.method()} ${request.url()}`);
});

page.on('response', response => {
  console.log(`<< ${response.status()} ${response.url()}`);
});
```

### Add Your Own Logs

```javascript
console.log('Navigating to page...');
await page.goto('https://example.com');
console.log('Page loaded, URL:', page.url());

const title = await page.title();
console.log('Page title:', title);
```

## Common Error Patterns

### Timeout Errors

```
Error: Timeout 30000ms exceeded
```

**Causes & Fixes:**
- Selector doesn't match any element → Verify selector with codegen
- Element exists but not visible → Use `state: 'attached'` or wait for visibility
- Page still loading → Add explicit wait

```javascript
// Wait for element to be attached to DOM
await page.waitForSelector('.result', { state: 'attached' });

// Wait for element to be visible
await page.waitForSelector('.result', { state: 'visible' });

// Increase timeout for slow operations
await page.waitForSelector('.result', { timeout: 60000 });
```

### Selector Not Found

```
Error: locator.click: Error: strict mode violation
```

**Fix:** Selector matches multiple elements. Be more specific:

```javascript
// ❌ Matches multiple buttons
await page.click('button');

// ✅ More specific
await page.click('button[type="submit"]');
await page.click('text=Submit order');
await page.click('button:has-text("Add")');
```

### Navigation Timeout

```
Error: page.goto: net::ERR_CONNECTION_REFUSED
```

**Check:**
- URL is correct
- Site is accessible
- No VPN/firewall blocking

### Frame/Iframe Issues

```javascript
// Access iframe content
const frame = page.frameLocator('iframe[name="content"]');
await frame.locator('.inside-iframe').click();
```

## Using Codegen for Debugging

Launch codegen to explore selectors interactively:

```bash
npx playwright codegen https://example.com
```

This opens:
1. Browser window you can interact with
2. Inspector showing generated code
3. Selector picker tool (🔍 icon)

## Trace Viewer (Advanced)

Record detailed traces for post-mortem debugging:

```javascript
const context = await browser.newContext();
await context.tracing.start({ screenshots: true, snapshots: true });

// ... your code ...

await context.tracing.stop({ path: 'tmp/trace.zip' });
```

View trace:
```bash
npx playwright show-trace tmp/trace.zip
```

## Element State Debugging

```javascript
// Check if element exists
const exists = await page.locator('.item').count() > 0;

// Check visibility
const isVisible = await page.locator('.modal').isVisible().catch(() => false);

// Check if enabled
const isEnabled = await page.locator('button').isEnabled();

// Get element count
const count = await page.locator('.item').count();
console.log(`Found ${count} items`);
```

## Network Debugging

```javascript
// Wait for specific request
await page.waitForRequest('**/api/data');

// Wait for response
const response = await page.waitForResponse('**/api/users');
console.log(await response.json());

// Mock slow network
const context = await browser.newContext({
  slowMo: 500  // Slow all operations
});
```

## Best Practices

1. **Start headed** - Always develop in headed mode (`headless: false`)
2. **Add screenshots** - Capture on errors and key milestones
3. **Log liberally** - Add console.log at each step
4. **Use slowMo** - Slow down to observe timing issues
5. **Verify selectors** - Use codegen to find correct selectors
6. **Check waits** - Ensure proper wait conditions for dynamic content


path: references/selectors.md
content: # Selectors Reference

Complete guide to element selection in Playwright.

## Selector Types

### CSS Selectors

Most common and versatile. Use standard CSS syntax.

```javascript
// ID
await page.click('#submit-button');

// Class
await page.click('.btn-primary');

// Attribute
await page.fill('input[name="email"]', '[REDACTED_SECRET]');
await page.click('[data-testid="login-btn"]');

// Descendant
await page.click('nav a[href="/about"]');

// nth-child (1-based index)
await page.click('.item:nth-child(3)');

// Complex combinations
await page.click('div.card > h2.title + button');
```

### Text Selectors

Find elements by their visible text content.

```javascript
// Exact match (case-insensitive)
await page.click('text=Submit');

// Contains substring
await page.click('text="Add to cart"');

// Regular expression
await page.click('text=/submit/i');           // case-insensitive
await page.click('text=/^Start.*$/');         // starts with "Start"

// Escape special characters
await page.click('text="Price: $10.00"');
```

### XPath Selectors

Use for complex traversal or when CSS isn't enough.

```javascript
// Basic XPath
await page.click('xpath=//button[@type="submit"]');

// Contains text
await page.click('xpath=//button[contains(text(), "Submit")]');

// Parent/child relationships
await page.click('xpath=//div[@class="card"]//button');

// Following sibling
await page.click('xpath=//label[text()="Email"]/following-sibling::input');

// Position-based
await page.click('xpath=(//button)[3]');       // 3rd button on page
```

### ARIA Role Selectors

Select by accessibility role (best for resilient tests).

```javascript
// Basic role
await page.click('role=button');

// Role with accessible name
await page.click('role=button[name="Submit"]');
await page.click('role=link[name="Learn more"]');

// Common roles
await page.fill('role=textbox[name="Email"]', '[REDACTED_SECRET]');
await page.check('role=checkbox[name="I agree"]');
await page.click('role=tab[name="Settings"]');
```

### Test ID Selectors

Most stable - not affected by layout or styling changes.

```javascript
// data-testid (convention)
await page.click('[data-testid="submit-button"]');
await page.fill('[data-testid="email-input"]', '[REDACTED_SECRET]');

// Custom data attribute
await page.click('[data-qa="login-btn"]');
await page.click('[data-automation-id="menu"]');
```

## Chaining Selectors

Combine multiple selector engines for precision.

```javascript
// CSS + text (within scope)
await page.click('.menu >> text=Settings');

// Within specific element
await page.click('nav >> css=a.active');

// Multiple levels
await page.click('section.products >> div.card:has-text("Laptop") >> button');
```

## Best Practices

### 1. Prefer Stable Selectors

```javascript
// ✅ Good - test ID (won't change with redesign)
await page.click('[data-testid="submit-btn"]');

// ⚠️ Okay - semantic role + name
await page.click('role=button[name="Submit order"]');

// ❌ Avoid - positional (fragile)
await page.click('div > div:nth-child(3) > button');

// ❌ Avoid - class names that may change
await page.click('.css-1a2b3c4');
```

### 2. Use Has-Text for Dynamic Content

```javascript
// Find row containing specific text, then click its button
await page.click('tr:has-text("Product A") >> button.edit');
```

### 3. Handling Multiple Matches

```javascript
// Get first match (default)
await page.click('.item');

// Get specific index
await page.click('.item >> nth=2');    // 3rd item (0-based)

// Count matches
const count = await page.locator('.item').count();

// Iterate all matches
const items = await page.locator('.item').all();
for (const item of items) {
  const text = await item.textContent();
  console.log(text);
}
```

### 4. Waiting for Selectors

```javascript
// Wait for element to appear
await page.waitForSelector('.results', { timeout: 10000 });

// Wait for element to be visible
await page.waitForSelector('.modal:visible');

// Wait for element to be hidden/removed
await page.waitForSelector('.loading', { state: 'hidden' });

// Wait for specific count
await page.waitForFunction(() => 
  document.querySelectorAll('.item').length >= 5
);
```

## Locator vs ElementHandle

Use `locator()` for modern, retryable selectors:

```javascript
// ✅ Modern - auto-retries, resilient
const submitBtn = page.locator('[data-testid="submit"]');
await submitBtn.click();

// Legacy - static snapshot
const submitBtn = await page.$('[data-testid="submit"]');
await submitBtn.click();
```

## Debugging Selectors

```javascript
// Highlight matching elements
await page.locator('.item').highlight();

// Count matches
const count = await page.locator('.item').count();
console.log(`Found ${count} items`);

// Check visibility
const isVisible = await page.locator('.modal').isVisible();

// Get element text for debugging
const text = await page.locator('h1').textContent();
console.log('Heading:', text);

// Use codegen to discover selectors
// npx playwright codegen https://example.com
```


path: references/troubleshooting.md
content: # Troubleshooting Guide

Common Playwright issues and solutions.

## Browser Binary Not Found

### Error
```
Executable doesn't exist at /path/to/chromium
```

### Solution

Install browser binaries:

```bash
# Install all browsers
npx playwright install

# Or just Chromium
npx playwright install chromium

# Or specific browser
npx playwright install firefox
npx playwright install webkit
```

### System Dependencies (Linux)

On Linux, you may need system dependencies:

```bash
# Ubuntu/Debian
npx playwright install-deps

# Or for specific browser
npx playwright install-deps chromium
```

### Verify Installation

```javascript
// test-browsers.mjs
import { chromium, firefox, webkit } from 'playwright';

for (const browserType of [chromium, firefox, webkit]) {
  try {
    const browser = await browserType.launch();
    console.log(`${browserType.name()}: OK`);
    await browser.close();
  } catch (e) {
    console.error(`${browserType.name()}: ${e.message}`);
  }
}
```

## Timeout Issues

### Page Load Timeout

```javascript
// Increase navigation timeout
await page.goto(url, { timeout: 60000 });

// Wait for specific state
await page.goto(url, { waitUntil: 'domcontentloaded' });  // faster
await page.goto(url, { waitUntil: 'networkidle' });       // wait for network
```

### Selector Timeout

```javascript
// Increase selector timeout
await page.waitForSelector('.result', { timeout: 60000 });

// Wait for different states
await page.waitForSelector('.loading', { state: 'hidden' });
await page.waitForSelector('.result', { state: 'visible' });

// Wait for function
await page.waitForFunction(() => 
  document.querySelectorAll('.item').length > 0
);
```

## Selector Not Found

### Strict Mode Violation

```
Error: locator.click: Error: strict mode violation: 
"button" resolved to 5 elements
```

**Fix:** Make selector more specific:

```javascript
// ❌ Too broad
await page.click('button');

// ✅ Specific
await page.click('button[type="submit"]');
await page.click('button:has-text("Submit")');
await page.click('[data-testid="submit-btn"]');

// Or use first/last/nth
await page.locator('button').first().click();
await page.locator('button').nth(2).click();
```

### Element Not Visible

```javascript
// Scroll into view first
await page.locator('.item').scrollIntoViewIfNeeded();
await page.locator('.item').click();

// Or force click
await page.locator('.item').click({ force: true });
```

## Network Issues

### Connection Refused

```
Error: net::ERR_CONNECTION_REFUSED
```

**Check:**
- URL is correct (including protocol)
- Site is running and accessible
- No VPN/proxy blocking

```javascript
// Add http:// if missing
const url = userInput.startsWith('http') ? userInput : `https://${userInput}`;
```

### SSL Certificate Errors

```javascript
// Ignore HTTPS errors (dev only!)
const browser = await chromium.launch({
  ignoreHTTPSErrors: true
});
```

### Proxy Configuration

```javascript
const browser = await chromium.launch({
  proxy: {
    server: 'http://proxy.example.com:3128',
    username: 'user',
    password: 'pass'
  }
});
```

## Module/Import Errors

### Cannot Find Module

```
Error: Cannot find module 'playwright'
```

**Fix:**

```bash
# Install playwright
npm install playwright

# Verify node_modules exists
ls node_modules/playwright
```

### ES Module Errors

```
SyntaxError: Cannot use import statement outside a module
```

**Fix:** Use `.mjs` extension or add `"type": "module"` to package.json:

```bash
# Rename to .mjs
mv script.js script.mjs

# Or create package.json
echo '{"type": "module"}' > package.json
```

## Permission Errors

### Cannot Write Screenshot

```javascript
// Ensure directory exists
import fs from 'fs';
fs.mkdirSync('tmp', { recursive: true });

await page.screenshot({ path: 'tmp/screenshot.png' });
```

### Permission Denied (Linux)

```bash
# Fix permissions on node_modules
chmod -R 755 node_modules

# Or reinstall
rm -rf node_modules package-lock.json
npm install
```

## Memory Issues

### Out of Memory

```javascript
// Close pages when done
const page = await browser.newPage();
// ... do work ...
await page.close();  // Free memory

// Process in batches
for (const url of urls) {
  const page = await browser.newPage();
  await processPage(page, url);
  await page.close();
}
```

## File Upload Issues

```javascript
// Upload single file
await page.locator('input[type="file"]').setInputFiles('/path/to/file.pdf');

// Upload multiple files
await page.locator('input[type="file"]').setInputFiles([
  '/path/to/file1.pdf',
  '/path/to/file2.pdf'
]);

// From buffer
await page.locator('input[type="file"]').setInputFiles({
  name: 'file.txt',
  mimeType: 'text/plain',
  buffer: Buffer.from('file content')
});
```

## Dialog Handling

```javascript
// Handle alerts
page.on('dialog', dialog => {
  console.log(`Dialog: ${dialog.message()}`);
  dialog.accept();
});

// Handle confirm dialogs
page.on('dialog', async dialog => {
  if (dialog.type() === 'confirm') {
    await dialog.accept();
  } else {
    await dialog.dismiss();
  }
});
```

## Context/Isolation Issues

```javascript
// Use new context for clean session
const context = await browser.newContext();
const page = await context.newPage();

// Isolate cookies/storage per context
const context1 = await browser.newContext();
const context2 = await browser.newContext();  // Separate cookies
```

## Getting Help

1. **Check Playwright docs:** https://playwright.dev
2. **Use codegen:** `npx playwright codegen <url>`
3. **Enable debug mode:** `DEBUG=pw:* node script.mjs`
4. **Check trace:** Record and view traces for detailed debugging


path: scripts/minimal-template.mjs
content: import { chromium } from 'playwright';

/**
 * Minimal Template
 * 
 * A clean starting point for any Playwright script.
 * Copy this file and modify for your needs.
 * 
 * Usage:
 *   cp scripts/minimal-template.mjs tmp/my-task.mjs
 *   node tmp/my-task.mjs
 */

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();

try {
  await page.goto('https://example.com');
  
  // Your code here
  const title = await page.title();
  console.log('Page title:', title);
  
} catch (error) {
  console.error('Error:', error.message);
  await page.screenshot({ path: 'tmp/error.png' });
} finally {
  await browser.close();
}


path: scripts/scrape-template.mjs
content: import { chromium } from 'playwright';

/**
 * Scrape Template
 * 
 * Reusable template for web scraping tasks.
 * 
 * Usage:
 *   cp scripts/scrape-template.mjs tmp/my-scrape.mjs
 *   node tmp/my-scrape.mjs [url]
 */

// Configuration
const url = process.argv[2] || 'https://example.com';
const selectors = {
  // Define your selectors here
  items: '.item',
  title: '.title',
  link: 'a'
};

const browser = await chromium.launch();
const page = await browser.newPage();

try {
  console.log(`Scraping ${url}...`);
  await page.goto(url, { waitUntil: 'networkidle' });
  
  // Wait for content to load
  await page.waitForSelector(selectors.items);
  
  // Extract data
  const data = await page.$$eval(selectors.items, (elements, selectors) => {
    return elements.map(el => ({
      title: el.querySelector(selectors.title)?.innerText?.trim(),
      url: el.querySelector(selectors.link)?.href,
      // Add more fields as needed
    }));
  }, selectors);
  
  console.log(`\nFound ${data.length} items:\n`);
  console.log(JSON.stringify(data, null, 2));
  
  // Optional: save to file
  // import fs from 'fs';
  // fs.writeFileSync('tmp/scraped.json', JSON.stringify(data, null, 2));
  
} catch (error) {
  console.error('Error:', error.message);
  
  // Debug: screenshot on error
  await page.screenshot({ path: 'tmp/scrape-error.png' });
  console.log('Error screenshot saved to tmp/scrape-error.png');
  
  process.exit(1);
} finally {
  await browser.close();
}


path: scripts/screenshot-template.mjs
content: import { chromium } from 'playwright';

/**
 * Screenshot Template
 * 
 * Reusable template for capturing screenshots.
 * 
 * Usage:
 *   cp scripts/screenshot-template.mjs tmp/my-screenshot.mjs
 *   node tmp/my-screenshot.mjs [url] [output.png]
 */

// Configuration
const url = process.argv[2] || 'https://example.com';
const outputPath = process.argv[3] || 'tmp/screenshot.png';
const viewport = { width: 1280, height: 800 };
const fullPage = true;

const browser = await chromium.launch();
const page = await browser.newPage();

// Set viewport for consistent screenshots
await page.setViewportSize(viewport);

try {
  console.log(`Navigating to ${url}...`);
  await page.goto(url, { 
    waitUntil: 'networkidle',
    timeout: 30000 
  });
  
  // Optional: wait for specific element
  // await page.waitForSelector('.loaded-indicator');
  
  // Optional: additional wait for animations
  await page.waitForTimeout(500);
  
  // Take screenshot
  await page.screenshot({ 
    path: outputPath, 
    fullPage 
  });
  
  console.log(`✓ Screenshot saved: ${outputPath}`);
  
} catch (error) {
  console.error('✗ Error:', error.message);
  process.exit(1);
} finally {
  await browser.close();
}


Security scan verdicts — ClawScan: clean | static analysis: malicious | VirusTotal: clean | SkillSpector:  ()