playwright — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited playwright (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Owner: Appie-3 (CTO) Created: 2026-04-07 Purpose: Browser automation for web scraping, form filling, testing, and repetitive web tasks
Microsoft's browser automation framework. Gives your agent full control of a real browser — click buttons, fill forms, scrape content, take screenshots, test websites.
Works with: Chromium, Firefox, WebKit (all three browsers)
# As npm package
npm install playwright
# Install browsers
npx playwright install chromium
npx playwright install firefox
npx playwright install webkit
# MCP server (for agent integration)
npx @playwright/mcp@latest| Capability | What It Does |
|---|---|
| Navigation | Go to URLs, click links, navigate back/forward |
| Interaction | Click buttons, fill inputs, select dropdowns, checkboxes |
| Extraction | Scrape text, images, tables, JSON from any page |
| Screenshots | Full page or element screenshots |
| PDF Generation | Save pages as PDF |
| Network Interception | Block requests, mock responses |
| Authentication | Login to sites, handle cookies |
| File Upload | Upload files to web forms |
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Navigate
page.goto("https://example.com")
# Interact
page.fill("#search-input", "my search query")
page.click("#search-button")
# Extract
results = page.query_selector_all(".result-item")
for result in results:
title = result.query_selector("h3").inner_text()
link = result.query_selector("a").get_attribute("href")
print(f"{title}: {link}")
# Screenshot
page.screenshot(path="screenshot.png", full_page=True)
browser.close()const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.fill('#search', 'query');
await page.click('#submit');
const results = await page.$$eval('.result', els =>
els.map(el => ({ title: el.textContent, url: el.href }))
);
console.log(results);
await browser.close();
})();# Scrape job listings from a site
page.goto("https://jobs.company.com")
page.fill("#keyword", "engineer")
page.click(".search-btn")
page.wait_for_selector(".job-card")
jobs = []
for card in page.query_selector_all(".job-card"):
jobs.append({
"title": card.query_selector("h3").inner_text(),
"company": card.query_selector(".company").inner_text(),
"url": card.query_selector("a").get_attribute("href")
})# Fill and submit a contact form
page.goto("https://example.com/contact")
page.fill("#name", "Jane Smith")
page.fill("#email", "[email protected]")
page.fill("#message", "Interested in your services")
page.select_option("#budget", "$5,000-10,000")
page.click("#submit")
page.wait_for_selector(".confirmation")# Login to a site
page.goto("https://app.example.com/login")
page.fill("#username", "[email protected]")
page.fill("#password", "password123")
page.click(".login-btn")
page.wait_for_selector(".dashboard")
# Now do authenticated actions
page.click(".new-project-btn")# Take screenshot of a page
page.goto("https://staging.example.com")
page.screenshot(path="staging-homepage.png", full_page=True)
# Take element screenshot
element = page.query_selector(".pricing-card.highlight")
element.screenshot(path="pricing-highlighted.png")# Verify page loads without errors
page.goto("https://new-site.vercel.app")
assert page.title() == "Expected Title"
assert page.query_selector(".hero") is not None
console_errors = []
page.on("console", lambda msg: console_errors.append(msg.text) if msg.type == "error" else None)
page.reload()
assert len(console_errors) == 0, f"Console errors: {console_errors}"# Pull data from a data table
page.goto("https://analytics.example.com/reports")
page.wait_for_selector(".data-table")
headers = [th.inner_text() for th in page.query_selector_all(".data-table th")]
rows = []
for tr in page.query_selector_all(".data-table tbody tr"):
cells = [td.inner_text() for td in tr.query_selector_all("td")]
rows.append(dict(zip(headers, cells)))Appie writes and executes Playwright scripts directly. Best for one-off tasks.
Run npx @playwright/mcp@latest — Appie can call browser tools via MCP protocol. Better for recurring tasks.
Create reusable SKILL.md patterns for common browser tasks. Appie reads and follows.
| Practice | Why |
|---|---|
| Always `wait_for_selector` | DOM might not be ready |
| Use `page.goto(..., wait_until='networkidle')` | Wait for page to fully load |
| Close browser when done | Prevent resource leaks |
| Use headless=True for automation | Faster, no UI needed |
| Set reasonable timeouts | page.set_default_timeout(30000) |
| Handle popups | page.on("dialog", lambda d: d.accept()) |
| Issue | Fix |
|---|---|
| Element not found | Add wait_for_selector() before interacting |
| Page not loading | Check URL, add longer timeout |
| Login failing | Check for hidden inputs, CSRF tokens |
| Screenshot blank | Use full_page=True or wait for content |
| Memory leak | Always browser.close() in finally block |
Learn more: https://playwright.dev
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.