Non-testing browser automation - web scraping, form filling, screenshot capture, PDF generation, workflow automation. For TESTING with Playwright, use e2e-playwright skill instead. Activates for web scraping, form automation, screenshot, PDF, headless browser, Puppeteer, Selenium, automation scripts, data extraction.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Expert in browser automation using Playwright, Puppeteer, and Selenium. Specializes in UI testing, web scraping, form automation, and automated workflows.
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
await browser.close();
// Fill and submit form
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'SecurePass123!');
await page.click('button[type="submit"]');
// Wait for success message
const success = await page.waitForSelector('.success-message', { timeout: 5000 });
const message = await success.textContent();
console.log('Success:', message);
const products = [];
let page = 1;
while (page <= 10) {
await browser.goto(`https://example.com/products?page=${page}`);
const items = await browser.$$eval('.product-item', (elements) =>
elements.map((el) => ({
title: el.querySelector('.title')?.textContent,
price: el.querySelector('.price')?.textContent,
image: el.querySelector('img')?.src,
}))
);
products.push(...items);
page++;
}
await page.route('**/api/analytics', (route) => route.abort());
await page.route('**/api/user', (route) =>
route.fulfill({
status: 200,
body: JSON.stringify({ id: 1, name: 'Test User' }),
})
);
import { expect } from '@playwright/test';
// Capture baseline
await page.screenshot({ path: 'baseline.png' });
// After changes, compare
const screenshot = await page.screenshot();
expect(screenshot).toMatchSnapshot('homepage.png');
// ID selector (most reliable)
await page.click('#submit-button');
// Data attribute (best practice)
await page.click('[data-testid="login-button"]');
// Class selector
await page.click('.btn-primary');
// Combined selector
await page.click('button.submit[type="submit"]');
// Text-based selection
await page.click('//button[contains(text(), "Submit")]');
// Complex hierarchy
await page.click('//div[@class="form"]//input[@name="email"]');
// Text-based
await page.getByText('Submit').click();
// Role-based (accessibility)
await page.getByRole('button', { name: 'Submit' }).click();
// Label-based
await page.getByLabel('Email address').fill('test@example.com');
// Placeholder-based
await page.getByPlaceholder('Enter your email').fill('test@example.com');
❌ Bad: .css-4j6h2k-button (auto-generated class)
✅ Good: [data-testid="submit-button"]
❌ Bad: await page.waitForTimeout(3000);
✅ Good: await page.waitForSelector('.results', { state: 'visible' });
try {
await page.click('button', { timeout: 5000 });
} catch (error) {
await page.screenshot({ path: 'error.png' });
console.error('Click failed:', error.message);
throw error;
}
try {
// automation code
} finally {
await browser.close();
}
class LoginPage {
constructor(private page: Page) {}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email"]', email);
await this.page.fill('[data-testid="password"]', password);
await this.page.click('[data-testid="submit"]');
}
async isLoggedIn() {
return this.page.locator('[data-testid="dashboard"]').isVisible();
}
}
// ❌ Race condition
await page.click('button');
const text = await page.textContent('.result'); // May fail!
// ✅ Wait for element
await page.click('button');
await page.waitForSelector('.result');
const text = await page.textContent('.result');
// ❌ Element may become stale
const element = await page.$('button');
await page.reload();
await element.click(); // Error: element detached from DOM
// ✅ Re-query after page changes
await page.reload();
await page.click('button');
// ❌ Assumes immediate load
await page.goto('https://example.com');
await page.click('.dynamic-content'); // May fail!
// ✅ Wait for dynamic content
await page.goto('https://example.com');
await page.waitForLoadState('networkidle');
await page.click('.dynamic-content');
const browser = await chromium.launch({ headless: false, slowMo: 100 });
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'passed') {
await page.screenshot({ path: `failure-${testInfo.title}.png` });
}
});
await context.tracing.start({ screenshots: true, snapshots: true });
await page.goto('https://example.com');
// ... automation steps
await context.tracing.stop({ path: 'trace.zip' });
page.on('console', (msg) => console.log('Browser log:', msg.text()));
await page.route('**/*.{png,jpg,jpeg,gif,svg,css}', (route) => route.abort());
const context = await browser.newContext();
const page1 = await context.newPage();
const page2 = await context.newPage();
// Share cookies, storage, etc.
await Promise.all([
page1.goto('https://example.com/page1'),
page2.goto('https://example.com/page2'),
page3.goto('https://example.com/page3'),
]);
Ask me about:
e2e-playwright skillfrontend skill for understanding DOM structureapi-testing skill for mocking network requests