Getting started
Requirements
- Node.js 22 or later.
- A game that loads through an
index.htmlwith<script type="module" src="…">, as Vite projects do. Keiko uses your project's Vite config if it has one, so aliases and plugins work.
Install
npm install -D keikoUsing debuff? New projects already have keiko and example tests. Run them with npx debuff test and skip to Playing the game.
Expose your game's state
Tests assert on a snapshot of your game. Add one in development builds, as JSON-friendly data:
// src/main.ts
if (import.meta.env.DEV) {
window.__KEIKO__ = {
state: () => ({ player: { x: player.x, y: player.y, vx: player.vx }, score, lives }),
scene: () => currentScene, // optional
};
}import.meta.env.DEV is false in production builds, so bundlers drop this code. Put in what you'll want to check: positions, speeds, counts, flags. For TypeScript, add import type {} from "keiko"; to that file to type window.__KEIKO__.
You don't need anything else to play the game: Keiko steps your requestAnimationFrame loop and sends real keyboard and pointer events. See Exposing your game for params, commands and engines with their own fixed step.
Write a test
Create tests/game.test.ts:
import { expect, play, test } from "keiko";
interface State {
player: { x: number; y: number; vx: number };
score: number;
lives: number;
}
test("the player walks right while ArrowRight is held", async () => {
const game = await play<State>();
const start = game.state.player.x;
game.hold("ArrowRight", "1s");
expect(game.state.player.x).toBeAbove(start + 50);
});If you use TypeScript, add "tests" to include in tsconfig.json.
Run it
npx keikokeiko 稽古 · practice for web games
pass tests/game.test.ts › the player walks right while ArrowRight is held 41 ms
✔ 1 passed in 612 msKeiko finds *.test.ts (and .js, .mts, .mjs) files in tests/ and src/. The first test takes longer while Vite warms up; after that each game boots in tens of milliseconds.
What play() does
- Opens a fresh headless page and loads your
index.html's markup. - Runs the page's module scripts (your game) through Vite, on a virtual clock with a seeded
Math.random(). - Waits for the game's first network requests (assets, data) to finish, and for
readyif you passed one. - Plays the first frame, as a browser paints before a player can act.
Each play() is a brand-new game. Nothing carries over between tests.
Next
- Playing the game: time, input, params and commands.
- Asking game questions:
until,duringandtrace. - Engine recipes: Phaser, PixiJS, KAPLAY, Excalibur, three.js.