Universal self-hosted visual testing kit for AI coding agents — Playwright multi-viewport capture + Lost Pixel self-hosted + layout-engine diagnosis + scoped auto-fix loop.
Works with any frontend — React, Vue, Next.js, Nuxt, Svelte, Astro, or plain HTML. No lock-in to a specific stack or design system.
This is the universal successor to the T2Editor-specific t2-capture-emulate / t2-surface-diagnose / t2-patch-verify kit.
| # | Skill | File | Purpose |
|---|---|---|---|
| 1 | browser-capture-emulate | src/skills/browser-capture-emulate.mjs |
Emulate real user devices (viewport/touch/dark/session), wait for network+fonts, freeze animations, capture integrity-guaranteed screenshots |
| 2 | visual-diagnosis-engine | src/skills/visual-diagnosis-engine.mjs |
Parse pixel diffs (Lost Pixel or local pixelmatch) + live DOM metrics, infer root causes via CSS Box Model / Flexbox / Grid / Stacking Context |
| 3 | patch-verify-loop | src/skills/patch-verify-loop.mjs |
Apply scoped minimal patches, re-run capture+diagnosis until 0 defects (max 3 iters, then escalate) |
Skills are also registered as opencode skills in .opencode/skills/*/SKILL.md.
# 1. Install
npm install
npx playwright install chromium webkit
# 2. (Optional) Start Lost Pixel self-hosted
docker compose up -d
# → http://127.0.0.1:9000
# Without Docker, local pixelmatch fallback is used automatically.
# 3. Capture
BASE_URL=http://127.0.0.1:3000 node src/skills/browser-capture-emulate.mjs --url http://127.0.0.1:3000/ --out tests/visual/.current/home.png
# 4. Diagnose (live surface)
node src/skills/visual-diagnosis-engine.mjs --surface tests/visual/surface.json
# or pixel compare
node src/skills/visual-diagnosis-engine.mjs --compare --baseline tests/visual/.baseline/home.png --current tests/visual/.current/home.png --diff tests/visual/.diff/home.png
# 5. Patch + verify loop
BASE_URL=http://127.0.0.1:3000 node src/skills/patch-verify-loop.mjs --url http://127.0.0.1:3000/BASE_URL=http://127.0.0.1:3000 npm run visual
# update baselines after intentional change
npm run visual:updateimport { emulateAndCapture, captureSurface } from './src/skills/browser-capture-emulate.mjs';
import { SURFACE_JS, diagnoseSurface } from './src/skills/visual-diagnosis-engine.mjs';
import { verifyLoop } from './src/skills/patch-verify-loop.mjs';
// 1. capture
const { browser, ctx, page } = await emulateAndCapture('http://127.0.0.1:3000/', {
viewport:{w:390,h:844}, dark:true, touch:true
});
const surface = await page.evaluate(SURFACE_JS);
console.log(diagnoseSurface(surface).summary);
// 2. loop until clean
await verifyLoop({
url:'http://127.0.0.1:3000/',
patcher: async (diag) => {
if (diag.causes.some(c=>c.category.includes('Viewport')))
return { filePath:'src/styles/globals.css', oldString:'width: 100vw', newString:'width: 100%' };
return null; // escalate to human
}
});
await ctx.close(); await browser.close();| Env | Default | Purpose |
|---|---|---|
BASE_URL |
http://127.0.0.1:3000 |
App URL (all skills + configs) |
PLAYWRIGHT_WEB_COMMAND |
(none) | Dev server to auto-start |
LOST_PIXEL_PLATFORM |
local |
local (filesystem) or lost-pixel (container) |
LOST_PIXEL_API_KEY |
local-dev-key |
For self-hosted dashboard |
Edit playwright.config.ts viewports/projects and lost-pixel.config.ts shots for your routes.
- No design tokens required — works with Tailwind, CSS Modules, vanilla, etc.
- No PHP / T2 stack — any
BASE_URLworks. - Docker optional —
docker compose up -dfor Lost Pixel dashboard, otherwisepixelmatch+pngjslocal diff. - Scoped edits —
DEFAULT_ALLOWED_DIRSguards against broad rewrites; loop caps at 3.
.
├── playwright.config.ts # multi-viewport projects
├── lost-pixel.config.ts # self-hosted baseline/current/diff
├── docker-compose.yml # lost-pixel container
├── src/skills/
│ ├── browser-capture-emulate.mjs # skill 1
│ ├── visual-diagnosis-engine.mjs # skill 2
│ └── patch-verify-loop.mjs # skill 3
├── .opencode/skills/*/SKILL.md # opencode registration
└── tests/visual/
├── .baseline/ # committed baselines
├── .current/ # gitignored
└── .diff/ # gitignored
MIT