7.0 KiB
| name | description |
|---|---|
| browser-test | Test and screenshot HTML/JS web apps in a headless browser (Puppeteer): catch JavaScript exceptions, console errors and 404s, drive the UI (click, shift-click, type, set inputs) and take screenshots to check visually. Use whenever a task produces an HTML page, canvas/WebGL app or any browser UI that must be verified. |
Headless browser testing
Use the ready-made harness shot.js. Do NOT write your own Puppeteer
launcher and do NOT start an HTTP server: the page is opened via file://,
CDN imports work from there.
Setup (once per project)
cp <skill-dir>/shot.js . && (npm ls puppeteer > /dev/null 2>&1 || npm install puppeteer > npm.log 2>&1) && echo ok
<skill-dir> is the absolute skill directory shown at the top of this text.
The skill folder is read-only - you work with the copy ./shot.js, and if the
harness lacks something, extend THAT copy with edit_file. npm install puppeteer
downloads a matching Chrome (~150 MB, 1-2 min). With an existing Chrome:
CHROME_PATH=/path/to/chrome plus npm install puppeteer-core.
Run
node shot.js app.html # load, record errors, shots/01_initial.png
node shot.js app.html actions.js # + drive the UI
node shot.js --check # environment probe: which Chrome flags give WebGL
Always call it with run_command(..., timeout=120): the harness has its own
watchdog (60 s, SHOT_TIMEOUT=90 node shot.js ... to raise it) and must be
allowed to report before the outer timeout kills it.
Exit codes: 1 = the page threw / console.error / failed resource / actions.js
threw (error list at the end); 2 = setup problem (puppeteer missing, no WebGL
with any flag set); 3 = watchdog - the output ends with WATCHDOG ... stuck in step: <helper + args>, i.e. the last traced step is the one that hangs.
Every helper call is traced as » name args, so partial output always tells
you how far the test got. Screenshots go to ./shots/NN_name.png; look at
them with view_image.
Environment problems are not app bugs
No WebGL context with any flag set,WebGL context could not be created, a watchdog in stepgoto, orNavigation timeout: the headless browser or the network is the problem. Runnode shot.js --check, tryCHROME_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome(macOS) ornpm i puppeteer@latest. NEVER restructure the app to run without WebGL, wraprendererinif (renderer), or add "GL not supported" fallbacks- that degrades the product the user asked for while the test setup is the
actual culprit. If the environment cannot be fixed, say so in the summary and
verify with
check_html.pyplus a hook-level test of the non-3D logic.
- that degrades the product the user asked for while the test setup is the
actual culprit. If the environment cannot be fixed, say so in the summary and
verify with
- A watchdog inside
eval/download/upload: the app function called never returns (infinite loop, awaiting a promise that never resolves, a download that never starts). Read that app function; don't patch shot.js. - Don't edit
shot.jsto work around a failing test. If a helper is really missing, add it to the project copy once, with a clear name, and keep using the traced helpers (h.pagegives you the full Puppeteer API anyway).
actions.js
module.exports = async (h) => {
await h.clickEl('#add-box'); // click a button
await h.clickEl('#add-sphere', { shift: true }); // shift-click an element
await h.set('#pos-x', 12); // set an input, fires input+change
await h.click(640, 400, { shift: true }); // (shift-)click at page coordinates
await h.key('Delete');
const n = await h.eval(() => window.__app.state.objects.length); // read app state
if (n !== 1) throw new Error(`expected 1 object, got ${n}`);
await h.upload('#btn-load-svg', 'fixtures/circle.svg'); // REAL import: button -> file dialog -> app code
const f = await h.download('#btn-save-json'); // REAL export: button -> <a download> -> file
if (!f.text.includes('"position"')) throw new Error('saved file lacks position: ' + f.text);
await h.shot('final');
};
Helpers: shot, click, clickEl, set, key, upload, download, eval,
wait, page (full Puppeteer API), errors. Complete example:
actions-example.js.
How to test an app without going in circles
- First run without actions. Fix every reported error before testing features.
- Drive state through the test hook, not through pixels. The app exposes
window.__app = { state, add, select, toJSON, ... }(see the single-file-webapp skill). To test "group a solid with a hole", doh.eval(() => { __app.add('box'); __app.add('cylinder'); __app.select([1, 2]); }), then click the Group button and assert on__app.state. Canvas picking is tested ONCE, separately: add one object at a known position, click its projected screen position (__app.project(id)or compute it), assert selection. - Never change the app to make a test pass (e.g. "buttons now select existing objects") - change the test, or fix a real bug in the app.
- Two failures with the same message = stop editing the test. Print the
relevant state in the error (
JSON.stringify(await h.eval(...))), then read the app function involved (search_files+read_filewith a line range). - One actions file per feature area (
test_shapes.js,test_group.js,test_files.js), each under ~40 lines; short, deterministic, throws on unexpected state. One 200-line file that runs everything means every fix re-runs everything and one hang hides all other results. Add ashot()after every visible change. Keeph.evalresults small (ids, counts, plain numbers) - returning meshes or whole state objects serializes megabytes and can stall the protocol. - WebGL apps: check the screenshot for a rendered object, not just a dark canvas.
- Test the path the user takes, not a stand-in for it. A feature wired to a
button, file dialog or download is verified ONLY by
h.clickEl,h.uploadorh.downloadon that control. Calling the underlying function via__app, or re-implementing the import inside the test with the library exposed onwindow, proves nothing about the button - that is exactly how "verified" features turn out broken for the user. Use__appfor setup and assertions, the real control for the action under test. Write fixture files (fixtures/box.stl,fixtures/circle.svg, a savedscene.json) once and keep them. - Never delete the test setup.
shot.js,test_*.js,fixtures/,node_modules/andpackage.jsonstay in the project so the user can rerunnode shot.js app.html test_x.jsafter you are done. Addshots/andnode_modules/to.gitignoreinstead of removing them. - Change an existing test with
edit_file; rewriting a 5 KB test withwrite_filefor a two-line change is slow and error-prone. - The final summary names each feature with the test file and the kind of check (real control / hook only / screenshot only). "Hook only" is not "verified".