Install
$ agentstack add skill-eliasoulkadi-shokunin-playwright Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Dangerous shell/eval execution.
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Playwright — Browser Automation Skill
Intelligent browser automation executor. Analyzes the user's request, selects the optimal pattern from 30+ built-in templates, generates production-grade Playwright code, and executes it with real-time reporting.
Setup
Playwright must be available in the environment. If not installed:
npm init -y
npm install playwright @playwright/test
npx playwright install chromium
For all 3 browsers:
npx playwright install
Check what's installed:
npx playwright install --dry-run
Trigger Decision Tree
When user asks for browser automation, classify the task:
User request
├── "screenshot" / "capture" / "take a picture"
│ → screenshot template
├── "responsive" / "mobile" / "different sizes"
│ → responsive check + per-viewport screenshots
├── "login" / "sign in" / "authenticate"
│ → login flow with error detection
├── "form" / "fill" / "submit" / "input"
│ → form testing with validation check
├── "broken links" / "check links" / "link validation"
│ → broken link scanner
├── "scrape" / "extract" / "get data" / "crawl"
│ → data extraction (single page or crawl)
├── "mock" / "intercept" / "stub" / "fake api"
│ → API mocking with route interception
├── "accessibility" / "a11y" / "axe" / "wcag"
│ → accessibility audit (requires axe-core)
├── "performance" / "lighthouse" / "speed" / "load time"
│ → performance audit with budgets
├── "visual" / "visual regression" / "diff"
│ → visual comparison screenshots
├── "download" / "file download"
│ → file download handler
├── "console" / "errors" / "logs"
│ → console error detector
├── "pdf" / "generate pdf"
│ → page-to-PDF converter
└── else → generic browse + report
Workflow
Step 1: Detect the environment
Before writing any code, determine what's available:
- Dev servers: Check common ports (3000, 3001, 5173, 8080, 8000, 4200, 5000, 9000) for running processes
- Installed browsers: Run
npx playwright install --dry-runto see which browsers are available - Framework indicators: Look for
package.jsondependencies (react, vue, svelte, next, nuxt) to understand the app under test - Available credentials: Check if the user mentioned login credentials or has
.envfiles
Step 2: Select template + generate code
Based on task classification and environment info, select the appropriate template and customize it with:
- Detected URL (dev server or user-supplied)
- Any user-specific parameters (credentials, selectors, viewports)
- Best-practice patterns auto-inserted (waitForSelector instead of fixed waits, graceful error handling, cleanup in finally)
Rules for code generation:
- Use
getByRole/getByText/getByLabelover CSS selectors (stable, accessible) - Parameterize URL in
TARGET_URLconstant at top - Never use
waitForTimeoutfor conditions — usewaitForSelector,waitForURL,waitForResponse - Always wrap in try/catch/finally with
browser.close()in finally - Include console.error logging at each step
- Comment each logical block
- Write generated file to OS temp dir as
playwright-task-{timestamp}.js
Step 3: Execute
Run the generated script directly with Node:
node
For inline one-off tasks (quick screenshot, check title):
node -e "
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('$URL');
console.log('Title:', await page.title());
await browser.close();
})();
"
Step 4: Report results
Present results based on task type:
| Task | Output format | |------|--------------| | Screenshot | Display path + preview description | | Responsive | Table: viewport × status × screenshot path | | Login | Pass/fail + redirect URL + session file | | Broken links | Summary: working/broken + detailed table | | Scrape | Number of records + sample rows | | A11y | Violation count + top 3 issues | | Performance | Score table + largest offenders | | Visual diff | Pass/fail + mismatch pixels |
Smart Defaults
These are applied automatically to every generated script:
| Setting | Default | Why | |---------|---------|-----| | headless | false | User sees what's happening | | slowMo | 100 | Visible transitions for debugging | | viewport | 1280x720 | Standard desktop | | Timeout | 15000 (15s) | Balance patience vs feedback | | args | --no-sandbox | Linux/WSL compatibility | | Trace | On first failure | Diagnostic data | | Video | On failure | Visual debug | | Screenshot | On failure | Visual assert | | Console listener | Active | Capture browser errors | | Page error listener | Active | Catch JS exceptions |
Important Rules
- No hardcoded URLs: Always parameterize as
TARGET_URL - No fixed waits: Use
waitForSelector,waitForURL,waitForResponse,waitForLoadState - Auth state reuse: Save to
.auth/directory for multi-step flows - Temp files only: Write scripts to OS temp dir, never to project or skill dir
- Failure screenshots: Always capture on error for debugging
- Close browser: Always
browser.close()infinallyblock
Error Recovery
| Error | Diagnosis | Response | |-------|-----------|----------| | ECONNREFUSED | Server not running | Suggest dev server or check URL | | Timeout 30s | Element/page not loading | Retry with waitForSelector, suggest specific selector | | NoSuchElement | Wrong selector | Auto-capture page HTML snippet, suggest better selector | | Auth failure | Login credentials wrong | Capture error message, suggest alternative flow | | 404/500 | Bad URL or server error | Log status + body, offer to check URL |
Common Fast Patterns
Quick screenshot
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
await page.screenshot({ path: `${require('os').tmpdir()}/screenshot.png`, fullPage: true });
console.log('Screenshot saved to temp/screenshot.png');
await browser.close();
})();
Quick page info
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(TARGET_URL);
console.log('Title:', await page.title());
console.log('URL:', page.url());
const meta = await page.$('meta[name="description"]');
console.log('Meta description:', meta ? await meta.getAttribute('content') : 'N/A');
await browser.close();
})();
Quick form test
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(TARGET_URL);
const inputs = await page.locator('input, textarea, select').count();
const buttons = await page.locator('button, input[type="submit"]').count();
console.log(`Form has ${inputs} inputs and ${buttons} buttons`);
await browser.close();
})();
Advanced Patterns
Login flow with auth state reuse
const { chromium } = require('playwright');
const path = require('path');
const AUTH_FILE = path.join(__dirname, '.auth', 'state.json');
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
// Try to reuse saved auth state
try {
const state = require(AUTH_FILE);
await context.addCookies(state.cookies);
} catch {}
const page = await context.newPage();
await page.goto(TARGET_URL);
// Check if logged in
const isLoggedIn = await page.$('[data-logged-in]');
if (!isLoggedIn) {
await page.fill('[name="username"]', USERNAME);
await page.fill('[name="password"]', PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard');
// Save auth state
const cookies = await context.cookies();
require('fs').writeFileSync(AUTH_FILE, JSON.stringify({ cookies }, null, 2));
}
// Continue with authenticated actions...
await browser.close();
})();
API mocking with route interception
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
// Mock API responses
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Test User' }])
});
});
// Block analytics/tracking requests
await page.route(/google-analytics|facebook|gtag/, route => route.abort());
await page.goto(TARGET_URL);
// Test with mocked data...
await browser.close();
})();
Accessibility audit (requires axe-core)
npm install @axe-core/playwright
const { chromium } = require('playwright');
const { injectAxe, checkA11y } = require('@axe-core/playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(TARGET_URL);
await injectAxe(page);
const results = await checkA11y(page, null, {
detailedReport: true,
detailedReportOptions: { html: true }
});
console.log(`Violations: ${results.violations.length}`);
results.violations.forEach(v => console.log(`- ${v.help}: ${v.nodes.length} instances`));
await browser.close();
})();
Anti-Patterns
| Anti-Pattern | Why It's Bad | Do This Instead | |-------------|-------------|-----------------| | page.waitForTimeout(5000) | Flaky, slows tests, false passes | page.waitForSelector('.loaded') | | page.$eval('.css-selector', ...) | Brittle, breaks on DOM changes | page.getByRole('button', { name: 'Submit' }) | | Hardcoded localhost:3000 | Fails when port changes | Parameterize as TARGET_URL env var | | No browser.close() | Hangs, leaks resources | Always in finally block | | page.click() without wait | Race condition, element not ready | page.click() already waits for actionability | | Sequential tests in one script | One failure kills everything | Separate scripts or use test runner | | networkidle on SPAs | Never resolves on polling apps | Use load + explicit element wait | | Capturing fullPage on infinite scroll | Huge images, timeouts | Clip viewport only | | Reusing selectors between environments | Dev vs prod DOM differs | Use data-testid or role-based locators |
Error Handling
Playwright-specific errors and remedies
| Error | Likely Cause | Resolution | |-------|-------------|------------| | browserType.launch: Executable doesn't exist | Chromium not installed | Run npx playwright install chromium | | page.goto: net::ERR_CONNECTION_REFUSED | Dev server not running | Start dev server or verify URL | | page.click: Target closed | Navigation happened during click | Use page.waitForNavigation() before click or Promise.all([page.waitForNavigation(), page.click()]) | | locator.click: Timeout 30000ms exceeded | Element never becomes actionable | Check selector; maybe element is hidden/disabled/covered | | page.fill: Element is not an | Wrong element type targeted | Use getByRole('textbox') for proper targeting | | route.fulfill: Request already served | Multiple routes matched same request | Order routes most-specific first; use route.fallback() for pass-through | | browser.newContext: Cookie domain mismatch | Auth state reuse on wrong domain | Ensure cookies match the target URL's domain | | page.pdf: Protocol error | Headless mode disabled | PDF generation requires headless: true | | page.waitForSelector: Target closed | Navigation destroyed the page | Wrap navigation + post-navigation actions in a single await chain | | require('playwright'): Cannot find module | Install incomplete | npm install playwright then verify node_modules/playwright exists |
Checklist
- [ ] Locator uses
getByRole,getByText, orgetByTestId— never XPath or CSS selectors with fragile class names - [ ] Auto-waiting relied on (no manual
waitForTimeoutorsleep) - [ ] Test isolates state (clean DB or API mock per test)
- [ ] Cross-browser tested (Chromium + Firefox + WebKit) for critical flows
- [ ] Trace viewer enabled on failure for debugging CI flakes
Sources
- Playwright official documentation (playwright.dev) — locator strategies, auto-waiting, fixtures
- Testing Trophy methodology — 80% integration / 20% unit testing ratio
- @axe-core/playwright for accessibility audits
- Lighthouse CI for performance budgets
- Production E2E patterns from 30+ project codebases
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: EliasOulkadi
- Source: EliasOulkadi/shokunin
- License: MIT
- Homepage: https://eliasoulkadi.github.io/shokunin/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.