agentsclimarketplace

Playwright harness

Skill simiancraft/simiancraft-skills/skills/playwright-harness

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.From its SKILL.md

Install
npx -y skills add simiancraft/simiancraft-skills --skill playwright-harness

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things 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.
  • runs commandsInstructs the agent to run 7 commands, including `npm i -D playwright` and 6 more.

SKILL.md

8.6 KB, ~2.0k tokens by cl100k_base, 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 listing error 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. ffmpeg for video/GIF encode); each declares its own in a "Prerequisites" block.

The run pattern

  1. 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 as const TARGET_URL = process.env.TARGET_URL || '<default>' so it is never hardcoded.
  2. Make playwright resolvable from the script's own directory. ESM resolves a bare import from the SCRIPT's location upward; cwd is irrelevant, so running a /tmp script 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 /tmp installs and then the import silently fails to resolve):
    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.mjs
    
    No install handy? cd /tmp/pw-<task> && npm i playwright right there. Confirm it resolves before relying on it: from the scratch dir, node -e "import('playwright').then(() => console.log('resolves'))".
  3. Default to headless. It is faster and gives clean, chrome-free screenshots. Use headless: false only 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 + console errors (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 fixed waitForTimeout.

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_URL at 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.

What ships with it: 2 files

8.6 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.