Loading...
Loading...
Complete browser automation and web testing with Playwright. Auto-detects dev servers, manages server lifecycle, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, debug dynamic webapps, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.
npx skill4agent add dedalus-erp-pas/foundation-skills playwright-skillUser task → Is server already running?
├─ Yes → Direct Testing
│ ├─ Static HTML? → Navigate directly (file:// or http://)
│ └─ Dynamic webapp? → Use Reconnaissance-Then-Action pattern
│
└─ No → Server Management Required
├─ Single server → Start server, then test
└─ Multiple servers → Start all servers, coordinate testing/tmp/playwright-test-*.jsheadless: falsewaitForLoadState('networkidle')// /tmp/playwright-test-reconnaissance.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3000';
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// STEP 1: Navigate and wait for dynamic content
await page.goto(TARGET_URL);
await page.waitForLoadState('networkidle'); // CRITICAL for dynamic apps
// STEP 2: Reconnaissance - discover what's on the page
await page.screenshot({ path: '/tmp/inspect.png', fullPage: true });
const buttons = await page.locator('button').all();
console.log(`Found ${buttons.length} buttons`);
for (let i = 0; i < buttons.length; i++) {
const text = await buttons[i].textContent();
console.log(` Button ${i}: "${text}"`);
}
// STEP 3: Action - interact with discovered elements
const loginButton = page.locator('button:has-text("Login")');
if (await loginButton.isVisible()) {
await loginButton.click();
console.log('✅ Clicked login button');
}
await browser.close();
})();# Check if port is in use
lsof -i :3000 # Mac/Linux
netstat -ano | findstr :3000 # Windows// /tmp/playwright-test-with-server.js
const { chromium } = require('playwright');
const { spawn } = require('child_process');
const TARGET_URL = 'http://localhost:3000';
(async () => {
// Start server
console.log('Starting server...');
const server = spawn('npm', ['run', 'dev'], { shell: true });
server.stdout.on('data', (data) => console.log(`Server: ${data}`));
server.stderr.on('data', (data) => console.error(`Server Error: ${data}`));
// Wait for server to be ready
await new Promise(resolve => setTimeout(resolve, 3000));
// Run tests
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(TARGET_URL);
await page.waitForLoadState('networkidle');
// Your test logic here
console.log('Title:', await page.title());
await browser.close();
// Clean up server
server.kill();
console.log('✅ Tests complete, server stopped');
})();// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 100 });
const page = await browser.newPage();
// Desktop test
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(TARGET_URL);
console.log('Desktop - Title:', await page.title());
await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });
// Mobile test
await page.setViewportSize({ width: 375, height: 667 });
await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });
await browser.close();
})();// /tmp/playwright-test-login.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/login`);
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
// Wait for redirect
await page.waitForURL('**/dashboard');
console.log('✅ Login successful, redirected to dashboard');
await browser.close();
})();// /tmp/playwright-test-form.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false, slowMo: 50 });
const page = await browser.newPage();
await page.goto(`${TARGET_URL}/contact`);
await page.fill('input[name="name"]', 'John Doe');
await page.fill('input[name="email"]', 'john@example.com');
await page.fill('textarea[name="message"]', 'Test message');
await page.click('button[type="submit"]');
// Verify submission
await page.waitForSelector('.success-message');
console.log('✅ Form submitted successfully');
await browser.close();
})();const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3000');
const links = await page.locator('a[href^="http"]').all();
const results = { working: 0, broken: [] };
for (const link of links) {
const href = await link.getAttribute('href');
try {
const response = await page.request.head(href);
if (response.ok()) {
results.working++;
} else {
results.broken.push({ url: href, status: response.status() });
}
} catch (e) {
results.broken.push({ url: href, error: e.message });
}
}
console.log(`✅ Working links: ${results.working}`);
console.log(`❌ Broken links:`, results.broken);
await browser.close();
})();const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
try {
await page.goto('http://localhost:3000', {
waitUntil: 'networkidle',
timeout: 10000,
});
await page.screenshot({
path: '/tmp/screenshot.png',
fullPage: true,
});
console.log('📸 Screenshot saved to /tmp/screenshot.png');
} catch (error) {
console.error('❌ Error:', error.message);
} finally {
await browser.close();
}
})();// /tmp/playwright-test-responsive-full.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
const viewports = [
{ name: 'Desktop', width: 1920, height: 1080 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Mobile', width: 375, height: 667 },
];
for (const viewport of viewports) {
console.log(
`Testing ${viewport.name} (${viewport.width}x${viewport.height})`,
);
& Discovery
```javascript
// Check visibility
const isVisible = await page.locator('button').isVisible();
// Get text
const text = await page.locator('h1').textContent();
// Get attribute
const href = await page.locator('a').getAttribute('href');
// Find all elements
const allButtons = await page.locator('button').all();
const allLinks = await page.locator('a').all();
// Check if element exists
const count = await page.locator('.modal').count();
console.log(`Found ${count} modals`);// Intercept requests
await page.route('**/api/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ mocked: true })
});
});
// Wait for response
const response = await page.waitForResponse('**/api/data');
console.log(await response.json());
// Capture console logs
page.on('console', msg => {
console.log(`Browser console [${msg.type()}]:`, msg.text());
});page.waitForLoadState('networkidle')headless: falsetext=role=waitForSelector()waitForLoadState()/tmp
### Selectors & Interactions
```javascript
// Click
await page.click('button.submit');
await page.dblclick('.item');
// Fill input
await page.fill('input[name="email"]', 'test@example.com');
await page.getByLabel('Email').fill('user@example.com');
// Checkbox
await page.check('input[type="checkbox"]');
await page.uncheck('input[type="checkbox"]');
// Select dropdown
await page.selectOption('select#country', 'usa');
// Type with delay
await page.type('#username', 'testuser', { delay: 100 });// Wait for navigation
await page.waitForURL('**/dashboard');
await page.waitForLoadState('networkidle');
// Wait for element
await page.waitForSelector('.success-message');
await page.waitForSelector('.spinner', { state: 'hidden' });
// Wait for timeout (use sparingly)
await page.waitForTimeout(1000);// Full page screenshot
await page.screenshot({
path: '/tmp/screenshot.png',
fullPage: true
});
// Element screenshot
await page.locator('.chart').screenshot({
path: '/tmp/chart.png'
});
```Quick Tips
- **Visible browser** - Always `headless: false` unless explicitly requested
- **Write to /tmp** - Scripts go to `/tmp/playwright-test-*.js`, never to project directories
- **Parameterize URLs** - Use `TARGET_URL` constant at top of script
- **Slow down** - Use `slowMo: 100` to see actions in real-time
- **Wait smart** - Use `waitForLoadState('networkidle')` for dynamic apps before inspecting
- **Error handling** - Wrap in try-catch with proper cleanup in finally block
- **Progress feedback** - Use `console.log()` to
const text = await page.locator('h1').textContent();
// Get attribute
const href = await page.locator('a').getAttribute('href');// Intercept requests
await page.route('**/api/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ mocked: true })
});
});
// Wait for response
const response = await page.waitForResponse('**/api/data');
console.log(await response.json());headless: false/tmp/playwright-test-*.jsTARGET_URLslowMo: 100waitForURLwaitForSelectorwaitForLoadStateconsole.log()/tmp