Playwright harness
Skill simiancraft/simiancraft-skills/skills/playwright-harness
Public Claude Code skills and agents from simiancraft. Marketplace-installable.
npx -y skills add simiancraft/simiancraft-skills --skill playwright-harnessAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Drive a real browser headlessly with Playwright on Linux/WSL and gate on what you observe: write a script, launch Chromium (real GPU under WSLg via ANGLE when WebGL matters), drive the page, and assert on screenshots plus collected page errors. The operational trunk for browser work; specializations layer on top. Use for ANY web task: "drive the UI headlessly", "smoke-test a screen", "screenshot a component or canvas", "reproduce a console error", "fill and submit a form", "test a login flow", "check responsive layout", "find broken links", or "automate a browser flow". Open references/interactions.md and references/flows.md for the element vocabulary and multi-step recipes; switch to playwright-camera-mask-testing for a camera or person feed, or playwright-gif-capture for an animated GIF. Validated on Linux/WSL, headless Chromium, with the ANGLE GPU path for WebGL.
SKILL.md
8.6 KB, as published. Nobody here has run it
Playwright Harness (headless, Linux/WSL)
Project-agnostic kernel for driving a browser and asserting on it; the operational trunk the rest of the suite hangs off.
Map of the suite
This skill is the trunk: what to install, plus the run-and-assert pattern. Everything else hangs off it.
playwright-harness/ <- you are here: prerequisites + run pattern + assert loop
├── references/
│ ├── interactions.md address elements: locators, actions, waits, assertions
│ └── flows.md recipes: login, forms, responsive, link-checking, network stubbing
└── specializations (separate, discoverable skills; read this one first):
├── playwright-camera-mask-testing a real person through getUserMedia; assert segmentation/mask by vision
└── playwright-gif-capture an animated GIF of a page, canvas, or WebGL animation
references/ is this skill's own depth, loaded by reading the file. The
specializations are separate, discoverable skills; open one when its input (a
camera feed, a GIF) is what you need.
Runtime/package manager. Examples use plain
node+npm; substitute your own runner (bun,pnpm,yarn) wherever they appear. Playwright itself is unaffected.
Prerequisites (install once)
- Node 18+ and a package manager.
- Playwright + a browser binary. Playwright ships no browser by default:
npm i -D playwright # or add to the project that already has it npx playwright install chromium - Headless system libraries (Linux/WSL). A fresh box is missing the shared
libs Chromium needs (
libnss3,libatk,libgbm, …); the symptom is a launch error listingerror while loading shared libraries. Install them once:npx playwright install-deps chromium # needs sudo; or your distro's equivalent packages - Some specializations need extra binaries (e.g.
ffmpegfor video/GIF encode); each declares its own in a "Prerequisites" block.
The run pattern
- Write the script into a dedicated scratch dir, never into the skill or the
project:
/tmp/pw-<task>/run.mjs, with any output alongside it (/tmp/pw-<task>/shot.png). A per-task dir stops parallel runs from colliding on a shared filename. Parameterize the URL asconst TARGET_URL = process.env.TARGET_URL || '<default>'so it is never hardcoded. - Make
playwrightresolvable from the script's own directory. ESM resolves a bare import from the SCRIPT's location upward; cwd is irrelevant, so running a/tmpscript from inside a project that has playwright does NOT work. Symlink an existing install into the scratch dir (do not reuse a shared/tmp/node_modules; it collides with other/tmpinstalls and then the import silently fails to resolve):
No install handy?mkdir -p /tmp/pw-<task> ln -sfn /path/to/an-install/node_modules /tmp/pw-<task>/node_modules # an install that has playwright node /tmp/pw-<task>/run.mjscd /tmp/pw-<task> && npm i playwrightright there. Confirm it resolves before relying on it: from the scratch dir,node -e "import('playwright').then(() => console.log('resolves'))". - Default to headless. It is faster and gives clean, chrome-free screenshots.
Use
headless: falseonly to watch a flow interactively while debugging.
// /tmp/pw-task/run.mjs
import { chromium } from 'playwright';
const TARGET_URL = process.env.TARGET_URL || 'http://localhost:8080/';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
const errors = [];
page.on('pageerror', (e) => errors.push(`pageerror: ${e.message}`));
page.on('console', (m) => m.type() === 'error' && errors.push(`console: ${m.text()}`));
await page.goto(TARGET_URL, { waitUntil: 'load' });
await page.locator('#root').screenshot({ path: '/tmp/pw-task/shot.png' }); // element-cropped; use page.screenshot() for the full page
await browser.close();
console.log(errors.length ? `ERRORS:\n${errors.join('\n')}` : 'no page errors');
Assert on what you observe
- Gate on page errors. Collect
pageerror+consoleerrors (filter favicon/DevTools noise), and treat a non-empty list as a failure. - Then Read the screenshot (vision); that is the half only your eyes can do.
Crop with
locator.screenshot()so the assertion is about the thing under test, not the whole page. - Wait on conditions, not the clock:
waitForSelector,waitForURL,waitForLoadState, or a page-exposed signal, beats a fixedwaitForTimeout.
Interactions and flows
The element vocabulary and the multi-step recipes live in the two references above. They exist to reach observable states worth gating on, not to be a general automation toolkit; a new recipe earns its place by ending on something you assert, not just an action it performs.
WebGL / GPU caveat (WSLg, headless)
Default headless Chromium renders WebGL via SwiftShader, which silently no-ops heavy GPU work: a canvas-heavy page or a generative shader renders black/empty with GPU time ~0, no error. If a canvas is empty headless but works in a real browser, relaunch reaching the real GPU under WSLg:
chromium.launch({ headless: true, args: ['--use-gl=angle', '--use-angle=gl', '--ignore-gpu-blocklist'] });
Confirm you got a real GPU, not SwiftShader, via the renderer string:
await page.evaluate(() => { const g = document.createElement('canvas').getContext('webgl2');
const x = g.getExtension('WEBGL_debug_renderer_info');
return x ? g.getParameter(x.UNMASKED_RENDERER_WEBGL) : 'no-debug-renderer-info'; });
Read the string case-insensitively for the software markers SwiftShader and
llvmpipe: if either is present you are on the no-op software path. The leading
ANGLE ( proves nothing on its own; the software path can arrive wrapped, e.g.
ANGLE (Google, Vulkan ... SwiftShader driver). A real GPU names an actual
adapter, e.g. ANGLE (Intel..., D3D12 (Intel(R) UHD Graphics 770), ...).
Let any shader/animation settle a second or two after first paint (PSO compile) before capturing, or early frames stutter.
Serving an exported static site
To test a production build, serve it and point TARGET_URL at it. If the app
deploys under a sub-path (project Pages sites), serve it under that sub-path;
root-served local runs hide absolute-path 404s (/_app/..., /static/...) that
only bite in production:
mkdir -p /tmp/site && ln -sfn "$(pwd)/dist" /tmp/site/<base-path>
python3 -m http.server 8091 -d /tmp/site # test http://localhost:8091/<base-path>/
python3 -m http.server PORT -d DIR serves any static directory (a single loose
fixture works the same way). It is static-only and cannot answer an app's live
endpoints (/api/...); for those, point TARGET_URL at the running dev server,
or stub the endpoints with the network-stubbing recipe in references/flows.md.
Scope
- CAN validate: page loads and routing, UI wiring and flows, element presence,
console/page-error cleanliness, visual state by vision, the real deployed site
(point
TARGET_URLat it; no install needed). - CANNOT validate: real frame-rate/perf (headless timing is not device timing), true input-device fidelity, or Firefox/WebKit capability differences unless you launch those browsers explicitly.
The specialization contract
A sibling skill (mapped above) says "Read playwright-harness first" and adds only its delta: its inputs and its assertion. It does not re-document the run pattern, the assert loop, or the GPU caveat. A new reference follows the same rule: it states its patterns and points back here for execution.