| name | scan |
| description | Live site QA via agent-browser (token-efficient) and Playwright (login flows). Scans pages, catches visual/functional issues, compares with baselines. Use when testing a live site or after deploying. |
| triggers | ["scan","scan it","test it","qa","visual qa"] |
| allowed-tools | Bash, Read, Write, Edit, Grep, Glob |
| model | opus |
| user-invocable | true |
| argument-hint | [url or scope] |
Scan — Live Site QA
Live site QA without relying on MCP servers. Two tools:
- agent-browser — fast, token-efficient page snapshots. Default for unauthenticated pages.
- Playwright — handles login flows, OAuth redirects, Google SSO realistically. Use when auth is required.
Catches what typecheck and build cannot: visual bugs, broken links, console errors, accessibility violations, performance regressions.
Usage
| Command | What It Does |
|---|
scan | Detect URL from project, run quick scan on key pages |
scan http://localhost:3000 | Scan specific URL |
scan full | Deep scan — all pages, all categories |
scan auth | Login via Playwright, then scan authenticated pages |
scan compare | Scan and compare against last baseline |
scan errors | Console + network errors only |
scan a11y | axe-core accessibility audit |
scan perf | Lighthouse performance only |
scan mobile | Mobile viewport screenshots + responsive check |
Step 1: Detect or Start Target URL
Check in order:
- User provided a URL → use it
- Dev server already running → check ports 3000, 3001, 5173, 8080:
for port in 3000 3001 5173 8080; do curl -s http://localhost:$port > /dev/null 2>&1 && echo "http://localhost:$port" && break; done
- No server running → auto-start one:
node -e "const p=require('./package.json');const s=p.scripts||{};console.log(s.dev||s.start||'')"
If a dev script exists, start it in the background:
Bash({ command: "npm run dev", run_in_background: true })
Wait 5 seconds, then re-check ports.
- Vercel preview → check
.vercel/ or recent deploy URL
- Production URL → check
package.json homepage or CLAUDE.md
If nothing found after all checks, ask the user.
Step 2: Discover Site Structure
curl -sL "$TARGET_URL" | grep -oE 'href="[^"]+"' | sed 's/href="//;s/"$//' | sort -u | head -30
Or use agent-browser to get a structured snapshot of navigation:
agent-browser open "$TARGET_URL"
agent-browser snapshot -i
Priority pages (scan these first):
- Landing/home page
- Auth pages (login, signup)
- Dashboard/main app page
- Settings/profile
- Any page with forms
Step 3: Run Scans (Unauthenticated)
Use agent-browser for speed and token efficiency.
Quick Scan (default)
For each priority page:
mkdir -p .claude/screenshots/$(date +%Y-%m-%d)
DIR=".claude/screenshots/$(date +%Y-%m-%d)"
agent-browser open "$PAGE_URL"
agent-browser screenshot "$DIR/$(basename $PAGE_URL)-desktop.png"
agent-browser viewport 375 812
agent-browser screenshot "$DIR/$(basename $PAGE_URL)-mobile.png"
agent-browser errors > "$DIR/$(basename $PAGE_URL)-errors.txt"
Full Scan (all pages)
Loop over discovered URLs, scanning each. Cap at 20 pages to keep scan time reasonable (~5-10 min).
Step 4: Authenticated Scan (Playwright)
agent-browser handles simple form logins; use Playwright for OAuth, Google SSO, 2FA, or any redirect-heavy flow — it behaves more like a real user.
Create .claude/scripts/auth-scan.js:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: false });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 720 } });
const page = await ctx.newPage();
const errors = [];
page.on('console', m => m.type() === 'error' && errors.push(m.text()));
page.on('pageerror', e => errors.push(`UNCAUGHT: ${e.message}`));
page.on('response', r => r.status() >= 400 && errors.push(`${r.status()} ${r.url()}`));
await page.goto(process.env.LOGIN_URL);
await page.fill('input[type="email"]', process.env.TEST_USER_EMAIL);
await page.fill('input[type="password"]', process.env.TEST_USER_PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL(url => !url.pathname.includes('login'), { timeout: 30000 });
await ctx.storageState({ path: '.claude/auth-state.json' });
const pages = (process.env.PAGES || '/dashboard').split(',');
for (const path of pages) {
await page.goto(new URL(path, process.env.BASE_URL).href);
await page.waitForLoadState('networkidle');
await page.screenshot({ path: `.claude/screenshots/auth-${path.replace(/\//g, '_')}.png`, fullPage: true });
}
require('fs').writeFileSync('.claude/screenshots/auth-errors.txt', errors.join('\n'));
await browser.close();
})();
Run it:
LOGIN_URL=http://localhost:3000/login \
TEST_USER_EMAIL=$TEST_USER_EMAIL \
TEST_USER_PASSWORD=$TEST_USER_PASSWORD \
BASE_URL=http://localhost:3000 \
PAGES=/dashboard,/settings,/profile \
node .claude/scripts/auth-scan.js
For Google SSO: launch with headless: false, complete the login manually the first time, and storageState persists the session. Subsequent runs can use storageState: '.claude/auth-state.json' in the context options.
Step 5: Analyze Screenshots
Read the saved PNGs directly. Look for:
Layout issues:
- Content overflow or clipping
- Elements overlapping
- Broken responsive layout on mobile (horizontal scroll, squashed grids)
- Missing content or blank sections
- Misaligned elements
Design quality:
- Generic/bland aesthetic (AI slop indicators)
- Inconsistent spacing or typography
- Poor color contrast
- Missing visual hierarchy
- No clear call-to-action
Functional issues:
- Error states visible
- Missing images or broken icons
- Loading spinners stuck
- Empty states without guidance
Step 6: Accessibility (axe-core)
Inject axe-core into Playwright to get WCAG violations:
const { AxeBuilder } = require('@axe-core/playwright');
const results = await new AxeBuilder({ page }).analyze();
console.log(JSON.stringify(results.violations, null, 2));
Install once: npm install -D @axe-core/playwright
Step 7: Performance (Lighthouse)
Standalone, no MCP needed:
npx lighthouse "$PAGE_URL" --only-categories=performance --chrome-flags="--headless" --output=json --output-path=.claude/lighthouse.json
node -e "const r=require('./.claude/lighthouse.json');console.log('Perf:',r.categories.performance.score*100,'LCP:',r.audits['largest-contentful-paint'].displayValue)"
For mobile: add --preset=perf --emulated-form-factor=mobile.
Step 8: Compare with Baseline
ls .claude/scans/ 2>/dev/null
Auto-save baseline on first scan:
mkdir -p .claude/scans
Save JSON with: URLs scanned, error counts per page, Lighthouse scores, axe violation counts, screenshot paths.
Compare with previous baseline — report regressions and resolutions.
Step 9: Report
Scan Results: [URL]
═══════════════════
Pages scanned: [N]
Scan time: [T]
| Category | Score | Issues |
|----------|-------|--------|
| Performance (Lighthouse) | XX/100 | N issues |
| Accessibility (axe) | N violations | critical: N |
| Console errors | N pages affected | |
| Network errors | 4xx/5xx count | |
Critical Issues:
1. [page] — [issue] → [fix]
2. ...
Visual Issues (from screenshots):
1. [page] — [what's wrong visually]
2. ...
Compared to baseline: [improved/regressed/new scan]
- Performance: +5 (was 72, now 77)
- New issues: 3
- Resolved: 7
Integration with Other Skills
| Skill | How Scan Integrates |
|---|
ship | Run scan as post-deploy verification |
auto | After UI tasks, scan the affected page |
fix | When fixing UI bugs, scan to verify the fix |
design | Review screenshots for visual quality and AI slop indicators |
Rules
- Always scan both desktop AND mobile viewports
- Screenshot every page you scan — visual issues are invisible to code analysis
- Compare with baselines when available — regressions matter more than absolute scores
- Don't create stories for Lighthouse scores above 90 — focus on real issues
- Console errors are always critical — fix them before anything else
- Save scan results to
.claude/scans/ for future comparisons
- For OAuth/SSO, use Playwright with
storageState — don't try to automate password entry on Google