Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/CodySwannGT/lisa --skill phaser-testing명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
any non-trivial request —…
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | phaser-testing |
| description | writing or designing tests for… |
Games are testable when the game is not welded to the engine. The stack's test pyramid, bottom-up:
src/logic/** — the bulk of coverage.Phaser.HEADLESS for things that
genuinely need the engine loop.There is no official Phaser testing harness — this layering IS the strategy. The dividing line: pure logic is unit-tested; everything that needs the engine is a runtime gate, never a brittle unit test that mocks the world.
src/logic/** contains game rules as plain TypeScript with no phaser
imports: damage calculation, wave spawning schedules, inventory, scoring,
state machines, procedural generation. Scenes call logic; logic never touches
GameObjects.
// src/logic/score.ts
export function comboMultiplier(streak: number): number { … }
// tests/logic/score.test.ts — plain vitest, node environment, no browser
import { comboMultiplier } from "../../src/logic/score";
it("caps the combo multiplier at 8x", () => {
expect(comboMultiplier(999)).toBe(8);
});
Determinism makes this work for anything random: logic takes a
Phaser.Math.RandomDataGenerator-compatible interface (or just a () => number)
as a parameter, production passes the seeded RND, tests pass a fixed sequence.
If a piece of "logic" can't be tested without constructing a Scene, that is a design smell — extract the rule from the scene first, then test it.
Cheap tests that catch the silent runtime failures Phaser is famous for:
src/assets.ts appears in
public/assets/pack.json (and optionally vice versa). A missing pack entry
otherwise ships as a green-square texture (the official loading-assets skill).SceneKeys constant has a scene class registered in
the game config's scene array.Phaser.HEADLESS (renderer type 3) boots a Game without creating a canvas or
WebGL context — but Phaser still expects DOM-ish globals, so it needs a
browser-like environment (jsdom/happy-dom) and careful teardown
(game.destroy(true)). Reserve headless boots for integration tests of things
that genuinely need the engine loop (scene transitions, timer events). Most of
what people reach for headless to test belongs in Layer 1 instead.
The non-negotiable end of the pyramid — proof the game runs:
test("game boots and renders", async ({ page }) => {
const errors: string[] = [];
page.on("pageerror", e => errors.push(e.message));
await page.goto("/");
const canvas = page.locator("canvas");
await expect(canvas).toBeVisible();
// Past the Preloader: poll a window hook the game sets, e.g. in MainMenu.create()
await page.waitForFunction(() => (window as never as { __sceneReady?: string }).__sceneReady === "MainMenu");
expect(errors).toEqual([]);
await expect(page).toHaveScreenshot("boot.png", { maxDiffPixelRatio: 0.02 });
});
Convention: scenes set window.__sceneReady = key in create() (dev/test
builds) so tests await real readiness instead of sleeping. Run against
bun run dev (or vite preview in CI).
Scenes and entities are deliberately excluded from unit coverage — mocking the engine to "unit test" a scene tests the mock. Instead, CI runs a set of deterministic gates against a real (or headless) game. Each gate fails the build on regression; together they are the contract that the engine-coupled layer keeps working.
Boot smoke — the Playwright test above, hardened: page error listener, wait
for __sceneReady, assert zero console errors. This is the floor.
Allocation / perf budget — run the game for N frames and assert the frame
budget and heap growth. Per-frame allocation is what the no-allocation-in-update
and no-create-in-update lint rules forbid statically; this gate catches what
slips through dynamically.
test("steady-state frames stay within budget", async ({ page }) => {
await page.goto("/"); await bootTo(page, "Game");
const stats = await page.evaluate(async () => {
const g = (window as any).__game as Phaser.Game;
const frames: number[] = []; let last = performance.now();
for (let i = 0; i < 600; i++) { await new Promise(r => g.events.once("postrender", r)); const n = performance.now(); frames.push(n - last); last = n; }
return { p95: frames.sort((a,b)=>a-b)[Math.floor(frames.length*0.95)], heap: (performance as any).?. };
});
(stats.).();
});
Leak gate — start and stop a scene N times and assert that texture count,
event-listener count, active tweens, and timers all return to baseline. This is
the runtime enforcement of the require-shutdown-cleanup rule and the
on/off-discipline in [[phaser-services]].
test("scene start/stop leaves no leaks", async ({ page }) => {
await page.goto("/"); await bootTo(page, "MainMenu");
const before = await page.evaluate(() => (window as any).__game.textures.getTextureKeys().length);
for (let i = 0; i < 20; i++) await page.evaluate(async () => {
const g = (window as any).__game as Phaser.Game; g.scene.start("Game");
await new Promise(r => setTimeout(r, 50)); g.scene.stop("Game");
await new Promise(r => setTimeout(r, ));
});
after = page.( ( )...().);
(after).(before);
});
Determinism gate — boot twice with the same seed, drive the same inputs, and
assert an identical state hash. Logic exposes a serializable snapshot; the gate
hashes it. Same seed → same hash, always. This is the runtime backstop for the
no-Math.random()/Date.now()/performance.now() rules ([[phaser-services]]).
const run = (seed: number) => page.evaluate(s => (window as any).__sim(s, REPLAY), seed);
expect(hash(await run(1234))).toBe(hash(await run(1234)));
Deterministic visual regression — toHaveScreenshot under software GL
(--use-gl=swiftshader), a frozen frame (pause the loop / step a fixed
number of ticks at a fixed delta), and masked dynamic regions (timers,
particles). Without all three, screenshots flake. Pin Playwright's browser and
OS in CI so the baseline is stable.
await page.evaluate(() => { const g=(window as any).__game; g.loop.sleep(); g.step(0,16.6); });
await expect(page).toHaveScreenshot("game.png", { mask: [page.locator("#timer")], maxDiffPixelRatio: 0.01 });
Bundle-size budget — assert the built bundle stays under a byte budget so a
stray dependency or an un-split phaser chunk fails the PR. Wire it to the prod
build's manualChunks split ([[phaser-build-deploy]]).
// size-limit / custom check after `bun run build`
[{ "path": "dist/assets/index-*.js", "limit": "60 kB" },
{ "path": "dist/assets/phaser-*.js", "limit": "400 kB" }]
bun run test = Vitest (layers 1–2, coverage-gated over src/logic/**
only — scenes/entities are out of scope here). The layer-5 gates run as their
own CI jobs against a built vite preview (and a headless boot for the
determinism gate).phaser ^4.2.0, Vite, TypeScript 6.The testing setup itself is verified when bun run test passes with coverage
over src/logic/**, the Playwright smoke fails when you deliberately break boot
(rename a pack file), and each layer-5 gate fails on its targeted regression —
leak gate when a listener loses its .off(), determinism gate when a
Math.random() sneaks in, visual gate when the frame changes. A gate that
can't fail is decoration.