ブラウザゲームの自動テストの方法|画面を出さないChromeで遊ばせて、壊れたゲームを見つける

ブラウザゲームの動作確認は、画面を出さないChromeに操作を送って、ゲームの変数を読めば自動にできます。この記事のスクリプトは、ページを開き、キーを押し、状態を確かめて、エラーを集めます。外部のライブラリは要らず、Nodeだけで動く形です。
スクリプトを、正常なゲームと、わざと壊した3つのゲームに実行して、見つけられるかを確かめました。確かめたのは、ゲームが動く範囲と、エラーの有無です。面白さは、確かめられません。
この記事で分かること
- ゲームを自動で確かめるスクリプトのコード全体
- テストの手順を書いた設定ファイルの例
- わざと壊したゲーム3つを、見つけられたかの結果
- アナリティクスに、テストのアクセスを数えさせない設定
- 確かめられていないこと
テストにかけたのは、ブラウザゲームの作り方で作ったキャッチゲームです。AIにゲームを作らせたあとに、動きを確かめる手順としても使えます。作るところは、ChatGPT・Geminiでゲームを作る手順にあります。
この記事の目次
自動で確かめる4段

ChromeにはDevTools Protocolという、外から操作する窓口があります。Chromeを、リモートデバッグのポートを開いて起動し、WebSocketでつなぐと、ページの移動、キーの入力、JavaScriptの実行ができます。ゲームの変数(かごの位置や、残り時間など)を読めば、画面を見なくても、状態を確かめられます。
コード全体
次のスクリプトを、run.mjsという名前で保存します。Node 24で動かしました。WebSocketとfetchを標準の機能で使うため、古いNodeでは動かないことがあります。ChromeのパスはWindowsの例なので、環境に合わせて書き換えます。
// ブラウザゲームの自動テスト。画面を出さないChromeでページを開き、操作して、結果を確かめる
// 実行: node run.mjs test.json
import { spawn } from 'node:child_process';
import { readFileSync } from 'node:fs';
const cfg = JSON.parse(readFileSync(process.argv[2], 'utf-8'));
const PORT = cfg.port || 9400;
const chrome = spawn('C:/Program Files/Google/Chrome/Application/chrome.exe', [
'--headless=new', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader', `--remote-debugging-port=${PORT}`,
`--window-size=${cfg.w || 400},${cfg.h || 700}`,
// 自分のテストのアクセスが、アナリティクスに数えられないようにする
'--host-resolver-rules=MAP www.googletagmanager.com 127.0.0.1, MAP *.google-analytics.com 127.0.0.1, MAP *.analytics.google.com 127.0.0.1',
'--user-data-dir=' + process.env.TEMP + '\gametest-prof' + PORT, 'about:blank']);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let tabs;
for (let i = 0; i < 40; i++) { try { tabs = await (await fetch(`http://127.0.0.1:${PORT}/json`)).json(); if (tabs.length) break; } catch (e) {} await sleep(250); }
const ws = new WebSocket(tabs.find((t) => t.type === 'page').webSocketDebuggerUrl);
await new Promise((r) => (ws.onopen = r));
let id = 0; const pend = {}; const errors = [];
ws.onmessage = (m) => {
const d = JSON.parse(m.data);
if (d.id && pend[d.id]) { pend[d.id](d); delete pend[d.id]; }
if (d.method === 'Runtime.exceptionThrown') errors.push(d.params.exceptionDetails.exception?.description?.split('\n')[0] || d.params.exceptionDetails.text);
if (d.method === 'Runtime.consoleAPICalled' && d.params.type === 'error') errors.push('console.error: ' + d.params.args.map((a) => a.value ?? a.description).join(' '));
};
const send = (method, params = {}) => new Promise((r) => { const i = ++id; pend[i] = r; ws.send(JSON.stringify({ id: i, method, params })); });
const ev = async (expr) => (await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true })).result?.result?.value;
await send('Runtime.enable'); await send('Page.enable');
const t0 = Date.now();
await send('Page.navigate', { url: cfg.url });
await sleep(cfg.load || 1500);
const results = [];
const KEYS = { ArrowRight: 39, ArrowLeft: 37, Space: 32 };
for (const s of cfg.steps) {
if (s.wait) await sleep(s.wait);
else if (s.keyDown) await send('Input.dispatchKeyEvent', { type: 'keyDown', code: s.keyDown, key: s.keyDown, windowsVirtualKeyCode: KEYS[s.keyDown] });
else if (s.keyUp) await send('Input.dispatchKeyEvent', { type: 'keyUp', code: s.keyUp, key: s.keyUp, windowsVirtualKeyCode: KEYS[s.keyUp] });
else if (s.eval) await ev(s.eval);
else if (s.expect) {
const v = await ev(s.expect);
results.push({ name: s.name, ok: v === true, value: v });
}
}
results.push({ name: 'コンソールにエラーが出ていない', ok: errors.length === 0, value: errors.slice(0, 3) });
for (const r of results) console.log(r.ok ? 'PASS' : 'FAIL', r.name, r.ok ? '' : JSON.stringify(r.value));
console.log('合計', results.filter((r) => r.ok).length + '/' + results.length, '所要', Date.now() - t0, 'ms');
ws.close(); chrome.kill();
process.exitCode = results.every((r) => r.ok) ? 0 : 1;
起動の引数には、アナリティクスの計測先を127.0.0.1に向ける指定が入っています。公開済みのゲームをテストするときに、テストのアクセスが、アクセス解析に混ざるのを防ぐためです。
手順を書く設定ファイル
テストの手順は、JSONで書きます。「expect」は、JavaScriptの式を評価して、結果がtrueなら合格です。名前は、結果の表示に使います。
{
"url": "http://127.0.0.1:8791/tools/gametest-demo/ok.html",
"port": 9400,
"steps": [
{
"expect": "typeof player==='object' && document.querySelector('canvas').width===360",
"name": "canvasが360幅で出ている"
},
{
"eval": "player.x=100;0"
},
{
"keyDown": "ArrowRight"
},
{
"wait": 600
},
{
"keyUp": "ArrowRight"
},
{
"expect": "player.x>180",
"name": "右キーを0.6秒押すと、かごが右へ動く"
},
{
"eval": "left=0.3;0"
},
{
"wait": 600
},
{
"expect": "running===false",
"name": "時間がなくなるとゲームが終わる"
}
]
}
このテストは、4つを確かめます。canvasが出ていること、右キーを0.6秒押すとかごが右へ動くこと、残り時間がなくなるとゲームが終わること、コンソールにエラーがないことです。最後の項目は、設定に書かなくても、スクリプトが毎回足します。
壊したゲームを見つけられるか
キャッチゲームのコードを、3通りに壊しました。
- 起動時に、存在しない関数を呼ぶ
- キーの状態を記録する行を消して、キーで動かないようにする
- 残り時間を減らす行を消して、ゲームが終わらないようにする

正常なゲームは4つとも合格しました。起動時にエラーが出るゲームは、4つとも不合格で、エラーの内容(「ReferenceError: undefinedFunction is not defined」)も記録されました。キーで動かないゲームで落ちたのは、「キーで動く」の1つだけでした。終わらないゲームも、「時間で終わる」の1つだけです。4回とも、実行は約2.7秒で終わりました。
スクリプトは、1つでも不合格があると、終了コードを1にして終わります。自動で動かす仕組みに組み込めば、壊れたゲームを公開する前に止められます。
AIにゲームを書かせたあとに使う
AIが書いたゲームは、一見動いても、細かい部分が抜けていることがあります。この記事のスクリプトに、確認したい項目を足すと、毎回同じ手順で確かめられます。たとえば次のような項目です。
- スコアが、操作に応じて増える
- 終わったあとに、もう一度遊べる
- 画面の大きさを変えても、エラーが出ない
これらの項目を、AIが書いたコードに実際に当てた結果は、確かめていません。
確かめられていないこと
- ゲームの面白さや分かりやすさ自動テストは、動作とエラーしか確かめられません。
- 実機のスマートフォンでの操作(画面の大きさだけは、起動の引数で変えられます)
- Chrome以外のブラウザ
- 画像を比べて、見た目のずれを見つける方法
- macOSやLinuxでのChromeのパスと、起動の引数
- キーを短く連続で押す操作(この記事は、押す・待つ・離す、の形だけです)
確かめていないものは、動かして確かめてから、別の記事で扱います。
うまくいかないとき
| 症状 | 確認すること |
|---|---|
| Chromeが起動しない | スクリプトの先頭にあるChromeのパスが、実際の場所と合っているかを確かめます |
| つながらない | ポート番号が、ほかの実行や、別のChromeと重なっていないかを確かめます |
| expectが不合格になる | 式を、ブラウザのコンソールで評価して、値を確かめます。ゲームの変数が、letやconstで宣言されていると、window.の形では読めません。名前だけで書きます |
| キーを送っても動かない | keyDownのあと、少し待ってからkeyUpを送っているかを確かめます |
| 実行が終わらない | ゲーム側で、ページの読み込みが止まっていないかを確かめます。loadの待ち時間を増やします |
関連記事ブラウザゲームの作り方|HTML1枚で動くキャッチゲームと、当たり判定が抜ける条件
よくある疑問
PlaywrightやPuppeteerは、使わないのですか
この記事では、ライブラリなしで動くスクリプトを書きました。ほかの道具との比較は、確かめていません。
画面を出すこともできますか
起動の引数から、ヘッドレスの指定を外せば、画面が出ます。この記事では、画面を出さない形だけを確かめました。
公開したゲームも、テストできますか
URLを書き換えれば、公開済みのページも開けます。ただし、相手のサービスの規約や負荷を確かめてから、使ってください。この記事では、手元のサーバーでだけ確かめています。
この記事のまとめ
この記事のまとめ
- 画面を出さないChromeに、キーを送り、ゲームの変数を読めば、動作を自動で確かめられる
- わざと壊したゲーム3つは、壊した場所に応じたチェックで見つかった
- テストの起動には、アナリティクスに数えられない設定を入れる
- 面白さや、実機のスマートフォンでの操作は、確かめていない












