phaser-playtest
This skill should be used when the user asks to "playtest my game", "test my Phaser game", "does my game actually run", "check the game in a browser", "verify the game works", "smoke test the game", "automate game testing", "catch black screens", "check for asset 404s", "measure FPS", "screenshot the game", "write a playtest scenario", or whenever Phaser 4 code has been written or changed and needs runtime verification before being handed back to the user.
How do I install this agent skill?
npx skills add https://github.com/yakoub-ai/phaser4-gamedev --skill phaser-playtestIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill is a specialized testing harness for Phaser 4 games that uses Playwright to perform runtime verification such as asset loading, canvas rendering, and frame rate monitoring. It follows security best practices by recommending that testing hooks only be exposed in development environments. No malicious patterns, exfiltration attempts, or unauthorized access vectors were detected.
- Socketwarn
1 alert: gptAnomaly
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Phaser 4 Playtesting (Runtime Verification)
npx tsc --noEmit proves the code compiles. It says nothing about whether the game
runs. Every failure below type-checks perfectly and still ships a broken game:
| Failure | What the player sees | What TypeScript says |
|---|---|---|
| Asset path typo | Invisible sprites | Nothing — it's a string |
Scene missing from scene: [] | Black screen | Nothing |
create() throws after first line | Half-built scene | Nothing |
| Depth/alpha/camera-scroll mistake | Blank canvas | Nothing |
| Physics body never added | Player falls through floor | Nothing |
| Uncapped emitter | 12 fps | Nothing |
Runtime verification is not optional. Run the harness after any change that touches scene lifecycle, asset loading, physics, or rendering — before telling the user it works.
Run It
node "${CLAUDE_PLUGIN_ROOT}/skills/phaser-playtest/scripts/playtest.mjs" --project .
The harness starts the dev server itself, drives headless Chromium, and shuts the server down again. No config, no test files required.
Requires Playwright in the project (once):
npm install -D playwright && npx playwright install chromium
What it checks
- Page loads — HTTP status of the document
- Canvas created — a sized
<canvas>exists (fails ⇒ Phaser never booted) - Phaser instance reachable — finds the
Phaser.Gameonwindow - Game booted —
isBooted === true - Active scenes — which scenes run, and how many display objects each holds
- Scenes render content — warns on a scene with an empty display list
- Frame rate — median and 5th-percentile FPS sampled over real frames
- Canvas renders content — pixel analysis; catches the black screen
- No uncaught exceptions —
pageerrorwith stack - No console errors
- All assets load — including files the dev server masks as
200 text/html
Exit code 0 = pass, 1 = check failures, 2 = harness error. Writes
.playtest/report.json plus PNG screenshots.
Options
| Flag | Purpose |
|---|---|
--project DIR | Project root (default: cwd) |
--url URL | Test an already-running server instead of starting one |
--mode dev|build | build runs npm run build + preview — catches base-path and bundling bugs |
--scenario FILE | Drive a scripted play session (see below) |
--device iphone|android | Mobile viewport, DPR, touch, and UA |
--settle MS | Wait before probing (default 3000; raise for slow preloads) |
--fail-on-warn | Treat console warnings as failures (CI) |
--json | Emit only JSON — use when parsing results |
--headed | Show the browser |
Make the Game Testable (one line)
The deep checks need the game instance. Bundled games keep it in module scope, so expose it in dev builds only:
const game = new Phaser.Game(config);
if (import.meta.env.DEV) (window as any).__PHASER_GAME__ = game;
export default game;
Without it the harness still catches black screens, exceptions, and 404s, but skips
scene, FPS, and state assertions — and says so. Add the line; it costs nothing in
production. See references/instrumenting-games.md for deterministic-testing hooks
(seeded RNG, time control, state injection).
Scenario Scripts
A scenario drives input and asserts on live game state. game is bound to the
running instance inside every expression.
// playtest/combat.mjs
export default [
{ name: 'reaches gameplay', action: 'expect',
expect: { expression: `game.scene.isActive('GameScene')`, equals: true } },
{ name: 'walk right', action: 'key', key: 'ArrowRight', duration: 600 },
{ name: 'player advanced', action: 'expect',
expect: { expression: `game.scene.getScene('GameScene').player.x > 400`, equals: true } },
{ name: 'attack lands', action: 'press', key: 'Space',
expect: { expression: `game.registry.get('enemyHp')`, atMost: 90 } },
{ name: 'after-combat', action: 'screenshot' },
{ name: 'holds frame rate', action: 'expect',
expect: { expression: `game.loop.actualFps`, atLeast: 55 } },
];
Actions: wait (ms), key (key, duration), press (key), click (x, y
canvas-relative), screenshot (name), expect.
Assertions: equals (deep), atLeast, atMost, or bare expression for truthy.
Full reference: references/playtest-harness.md.
Worked example: examples/scenario.example.mjs.
Turning a Plan into Tests
Acceptance criteria from /phaser-gdd or the phaser-architect agent translate
directly into scenario steps. Write the scenario when the feature is specified,
not after it breaks:
| Acceptance criterion | Scenario assertion |
|---|---|
| "Player jumps 3 tiles high" | game.scene.getScene('GameScene').player.y, atMost: spawnY - 96 |
| "Enemy dies in 3 hits" | press attack ×3, then enemy.active, equals: false |
| "Score persists across scenes" | start GameOver, then game.registry.get('score'), atLeast: 10 |
| "Runs at 60fps with 50 enemies" | spawn via eval, then game.loop.actualFps, atLeast: 55 |
Reading a Failure
| Harness output | Root cause to check first |
|---|---|
canvas created fails | Scene threw in constructor/init, or the bundle 500s. Read console errors. |
active scenes: no scene is running | Scene missing from scene: [], or create() threw before completing |
canvas renders content blank, scenes active | Objects off-camera, alpha: 0, wrong depth, or camera not following |
scenes render content warns empty | create() returned early — usually an exception swallowed by a try |
all assets load fails with text/html | Path typo, or the asset is in src/ instead of public/ |
frame rate low | Uncapped particles, no object pooling, per-frame allocation. Use /phaser-analyze. |
Phaser game instance found warns | Add the __PHASER_GAME__ line above |
CI
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx tsc --noEmit
- run: node scripts/playtest.mjs --project . --mode build --fail-on-warn
Copy playtest.mjs into the project's own scripts/ so CI does not depend on the
plugin being installed.
Discipline
- Run the harness before reporting a feature complete — not after the user reports a bug.
- A passing
tscplus a failing playtest means the work is not done. - When it fails, read
.playtest/report.jsonand the screenshots before editing code. Investigation-first applies here exactly as inphaser-debugger. - Keep a scenario per major feature under
playtest/; they are regression tests.
When the failure came from a player
If you are here because someone reported a bug rather than because you just changed code,
use skills/phaser-feedback/ instead of writing a scenario straight away. It covers
triaging the report first (a "too hard" complaint is not a defect and has no repro), and
maps each claim shape to the right construct — --repeat for "sometimes", sample for
"gets stuck", waitFor for "takes too long", --heap for "slows down over time".
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/yakoub-ai/phaser4-gamedev/phaser-playtest">View phaser-playtest on skillZs</a>